]> git.ipfire.org Git - thirdparty/git.git/commitdiff
Merge branch 'maint' to synchronize with 1.5.1.6
authorJunio C Hamano <junkio@cox.net>
Sun, 20 May 2007 07:19:30 +0000 (00:19 -0700)
committerJunio C Hamano <junkio@cox.net>
Sun, 20 May 2007 07:19:30 +0000 (00:19 -0700)
* maint:
  GIT 1.5.1.6
  git-svn: don't minimize-url when doing an init that tracks multiple paths
  git-svn: avoid crashing svnserve when creating new directories
  user-manual: Add section on ignoring files
  user-manual: finding commits referencing given file content
  user-manual: discourage shared repository
  tutorial: revise index introduction
  tutorials: add user-manual links

Conflicts:

GIT-VERSION-GEN
RelNotes

214 files changed:
.gitignore
Documentation/Makefile
Documentation/RelNotes-1.5.2.txt [new file with mode: 0644]
Documentation/SubmittingPatches
Documentation/asciidoc.conf
Documentation/cmd-list.perl
Documentation/config.txt
Documentation/core-tutorial.txt
Documentation/git-add.txt
Documentation/git-archive.txt
Documentation/git-bisect.txt
Documentation/git-blame.txt
Documentation/git-check-attr.txt [new file with mode: 0644]
Documentation/git-clean.txt
Documentation/git-commit-tree.txt
Documentation/git-cvsserver.txt
Documentation/git-diff-tree.txt
Documentation/git-format-patch.txt
Documentation/git-fsck.txt
Documentation/git-index-pack.txt
Documentation/git-log.txt
Documentation/git-ls-files.txt
Documentation/git-pack-objects.txt
Documentation/git-read-tree.txt
Documentation/git-repack.txt
Documentation/git-rev-list.txt
Documentation/git-rm.txt
Documentation/git-show.txt
Documentation/git-tag.txt
Documentation/git-update-index.txt
Documentation/git.txt
Documentation/gitattributes.txt [new file with mode: 0644]
Documentation/hooks.txt
Documentation/pretty-formats.txt
Documentation/pretty-options.txt [new file with mode: 0644]
Documentation/user-manual.txt
GIT-VERSION-GEN
Makefile
RelNotes
alloc.c
archive-tar.c
archive-zip.c
attr.c [new file with mode: 0644]
attr.h [new file with mode: 0644]
blob.c
builtin-add.c
builtin-apply.c
builtin-archive.c
builtin-blame.c
builtin-bundle.c
builtin-cat-file.c
builtin-check-attr.c [new file with mode: 0644]
builtin-checkout-index.c
builtin-commit-tree.c
builtin-count-objects.c
builtin-diff.c
builtin-fetch--tool.c
builtin-fsck.c
builtin-log.c
builtin-ls-files.c
builtin-ls-tree.c
builtin-mv.c
builtin-pack-objects.c
builtin-push.c
builtin-read-tree.c
builtin-rev-list.c
builtin-revert.c
builtin-rm.c
builtin-shortlog.c
builtin-unpack-objects.c
builtin-update-index.c
builtin-write-tree.c
builtin.h
cache-tree.c
cache.h
combine-diff.c
commit.c
commit.h
config.mak.in
configure.ac
contrib/completion/git-completion.bash
contrib/emacs/git-blame.el
contrib/emacs/git.el
contrib/fast-import/import-tars.perl
contrib/gitview/gitview
contrib/hooks/post-receive-email
contrib/hooks/update-paranoid [new file with mode: 0644]
convert.c
copy.c
csum-file.c
csum-file.h
date.c
decorate.c [new file with mode: 0644]
decorate.h [new file with mode: 0644]
diff-lib.c
diff.c
diff.h
diffcore-pickaxe.c
diffcore-rename.c
diffcore.h
dir.c
dir.h
entry.c
exec_cmd.h
fast-import.c
git-bisect.sh
git-checkout.sh
git-clean.sh
git-commit.sh
git-compat-util.h
git-cvsexportcommit.perl
git-cvsserver.perl
git-fetch.sh
git-gui/.gitignore
git-gui/GIT-VERSION-GEN
git-gui/Makefile
git-gui/git-gui.sh
git-gui/lib/blame.tcl [new file with mode: 0644]
git-gui/lib/branch.tcl [new file with mode: 0644]
git-gui/lib/browser.tcl [new file with mode: 0644]
git-gui/lib/class.tcl [new file with mode: 0644]
git-gui/lib/commit.tcl [new file with mode: 0644]
git-gui/lib/console.tcl [new file with mode: 0644]
git-gui/lib/database.tcl [new file with mode: 0644]
git-gui/lib/diff.tcl [new file with mode: 0644]
git-gui/lib/error.tcl [new file with mode: 0644]
git-gui/lib/index.tcl [new file with mode: 0644]
git-gui/lib/merge.tcl [new file with mode: 0644]
git-gui/lib/option.tcl [new file with mode: 0644]
git-gui/lib/remote.tcl [new file with mode: 0644]
git-gui/lib/shortcut.tcl [new file with mode: 0644]
git-gui/lib/transport.tcl [new file with mode: 0644]
git-lost-found.sh
git-merge.sh
git-remote.perl
git-request-pull.sh
git-reset.sh
git-send-email.perl
git.c
git.spec.in
gitweb/gitweb.css
gitweb/gitweb.perl
ident.c
imap-send.c
index-pack.c
list-objects.c
lockfile.c
log-tree.c
mailmap.c [new file with mode: 0644]
mailmap.h [new file with mode: 0644]
match-trees.c [new file with mode: 0644]
merge-recursive.c
object-refs.c
object.c
object.h
pack-check.c
pack-redundant.c
pack-write.c [new file with mode: 0644]
pack.h
patch-ids.c [new file with mode: 0644]
patch-ids.h [new file with mode: 0644]
path-list.h
perl/Git.pm
pkt-line.c
progress.c [new file with mode: 0644]
progress.h [new file with mode: 0644]
read-cache.c
refs.c
refs.h
revision.c
revision.h
sha1_file.c
sha1_name.c
show-index.c
ssh-upload.c
symlinks.c [new file with mode: 0644]
t/diff-lib.sh [changed mode: 0755->0644]
t/lib-read-tree-m-3way.sh [changed mode: 0755->0644]
t/t0020-crlf.sh
t/t0021-conversion.sh [new file with mode: 0755]
t/t1000-read-tree-m-3way.sh
t/t2200-add-update.sh [new file with mode: 0755]
t/t3030-merge-recursive.sh [new file with mode: 0755]
t/t3040-subprojects-basic.sh [new file with mode: 0755]
t/t3600-rm.sh
t/t4013-diff-various.sh
t/t4013/diff.format-patch_--inline_--stdout_--subject-prefix=TESTCASE_initial..master [new file with mode: 0644]
t/t4020-diff-external.sh [new file with mode: 0755]
t/t4122-apply-symlink-inside.sh [new file with mode: 0755]
t/t4201-shortlog.sh [new file with mode: 0755]
t/t5000-tar-tree.sh
t/t5300-pack-object.sh
t/t5301-sliding-window.sh
t/t5302-pack-index.sh [new file with mode: 0755]
t/t5502-quickfetch.sh [new file with mode: 0755]
t/t6002-rev-list-bisect.sh
t/t6004-rev-list-path-optim.sh
t/t6022-merge-rename.sh
t/t6023-merge-file.sh [changed mode: 0644->0755]
t/t6024-recursive-merge.sh [changed mode: 0644->0755]
t/t6025-merge-symlinks.sh [changed mode: 0644->0755]
t/t6026-merge-attr.sh [new file with mode: 0755]
t/t6030-bisect-porcelain.sh [new file with mode: 0755]
t/t6030-bisect-run.sh [deleted file]
t/t7300-clean.sh [new file with mode: 0755]
t/t9400-git-cvsserver-server.sh [new file with mode: 0755]
t/test-lib.sh [changed mode: 0755->0644]
tag.c
test-genrandom.c [new file with mode: 0644]
test-match-trees.c [new file with mode: 0644]
tree.c
unpack-trees.c
unpack-trees.h
wt-status.c

index e8d2731ee5d7b3ad58bd0bebfe1ae87bed140615..4dc0c395fa7d6e7cff7588ee66c928c954cc014d 100644 (file)
@@ -1,4 +1,5 @@
 GIT-CFLAGS
+GIT-GUI-VARS
 GIT-VERSION-FILE
 git
 git-add
@@ -15,6 +16,7 @@ git-blame
 git-branch
 git-bundle
 git-cat-file
+git-check-attr
 git-check-ref-format
 git-checkout
 git-checkout-index
@@ -76,6 +78,7 @@ git-merge-ours
 git-merge-recursive
 git-merge-resolve
 git-merge-stupid
+git-merge-subtree
 git-mergetool
 git-mktag
 git-mktree
@@ -141,11 +144,14 @@ git-verify-tag
 git-whatchanged
 git-write-tree
 git-core-*/?*
+gitk-wish
 gitweb/gitweb.cgi
 test-chmtime
 test-date
 test-delta
 test-dump-cache-tree
+test-genrandom
+test-match-trees
 common-cmds.h
 *.tar.gz
 *.dsc
index ad87736b0c0fbbaf6d2271fd66148f9e483c57bd..3f92783d55b401a5c919f7c9176a12953d0c4904 100644 (file)
@@ -2,9 +2,10 @@ MAN1_TXT= \
        $(filter-out $(addsuffix .txt, $(ARTICLES) $(SP_ARTICLES)), \
                $(wildcard git-*.txt)) \
        gitk.txt
+MAN5_TXT=gitattributes.txt
 MAN7_TXT=git.txt
 
-DOC_HTML=$(patsubst %.txt,%.html,$(MAN1_TXT) $(MAN7_TXT))
+DOC_HTML=$(patsubst %.txt,%.html,$(MAN1_TXT) $(MAN5_TXT) $(MAN7_TXT))
 
 ARTICLES = tutorial
 ARTICLES += tutorial-2
@@ -23,12 +24,14 @@ SP_ARTICLES = howto/revert-branch-rebase user-manual
 DOC_HTML += $(patsubst %,%.html,$(ARTICLES) $(SP_ARTICLES))
 
 DOC_MAN1=$(patsubst %.txt,%.1,$(MAN1_TXT))
+DOC_MAN5=$(patsubst %.txt,%.5,$(MAN5_TXT))
 DOC_MAN7=$(patsubst %.txt,%.7,$(MAN7_TXT))
 
 prefix?=$(HOME)
 bindir?=$(prefix)/bin
 mandir?=$(prefix)/man
 man1dir=$(mandir)/man1
+man5dir=$(mandir)/man5
 man7dir=$(mandir)/man7
 # DESTDIR=
 
@@ -53,18 +56,27 @@ all: html man
 
 html: $(DOC_HTML)
 
-$(DOC_HTML) $(DOC_MAN1) $(DOC_MAN7): asciidoc.conf
+$(DOC_HTML) $(DOC_MAN1) $(DOC_MAN5) $(DOC_MAN7): asciidoc.conf
 
-man: man1 man7
+man: man1 man5 man7
 man1: $(DOC_MAN1)
+man5: $(DOC_MAN5)
 man7: $(DOC_MAN7)
 
 install: man
-       $(INSTALL) -d -m755 $(DESTDIR)$(man1dir) $(DESTDIR)$(man7dir)
+       $(INSTALL) -d -m755 $(DESTDIR)$(man1dir)
+       $(INSTALL) -d -m755 $(DESTDIR)$(man5dir)
+       $(INSTALL) -d -m755 $(DESTDIR)$(man7dir)
        $(INSTALL) -m644 $(DOC_MAN1) $(DESTDIR)$(man1dir)
+       $(INSTALL) -m644 $(DOC_MAN5) $(DESTDIR)$(man5dir)
        $(INSTALL) -m644 $(DOC_MAN7) $(DESTDIR)$(man7dir)
 
 
+../GIT-VERSION-FILE: .FORCE-GIT-VERSION-FILE
+       $(MAKE) -C ../ GIT-VERSION-FILE
+
+-include ../GIT-VERSION-FILE
+
 #
 # Determine "include::" file references in asciidoc files.
 #
@@ -94,17 +106,25 @@ cmd-list.made: cmd-list.perl $(MAN1_TXT)
 git.7 git.html: git.txt core-intro.txt
 
 clean:
-       rm -f *.xml *.html *.1 *.7 howto-index.txt howto/*.html doc.dep
+       rm -f *.xml *.xml+ *.html *.html+ *.1 *.5 *.7 howto-index.txt howto/*.html doc.dep
        rm -f $(cmds_txt) *.made
 
 %.html : %.txt
-       $(ASCIIDOC) -b xhtml11 -d manpage -f asciidoc.conf $(ASCIIDOC_EXTRA) $<
+       rm -f $@+ $@
+       $(ASCIIDOC) -b xhtml11 -d manpage -f asciidoc.conf \
+               $(ASCIIDOC_EXTRA) -o - $< | \
+               sed -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' >$@+
+       mv $@+ $@
 
-%.1 %.7 : %.xml
+%.1 %.5 %.7 : %.xml
        xmlto -m callouts.xsl man $<
 
 %.xml : %.txt
-       $(ASCIIDOC) -b docbook -d manpage -f asciidoc.conf $<
+       rm -f $@+ $@
+       $(ASCIIDOC) -b docbook -d manpage -f asciidoc.conf \
+               $(ASCIIDOC_EXTRA) -o - $< | \
+               sed -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' >$@+
+       mv $@+ $@
 
 user-manual.xml: user-manual.txt user-manual.conf
        $(ASCIIDOC) -b docbook -d book $<
@@ -135,3 +155,5 @@ install-webdoc : html
 
 quick-install:
        sh ./install-doc-quick.sh $(DOC_REF) $(mandir)
+
+.PHONY: .FORCE-GIT-VERSION-FILE
diff --git a/Documentation/RelNotes-1.5.2.txt b/Documentation/RelNotes-1.5.2.txt
new file mode 100644 (file)
index 0000000..7dbdb26
--- /dev/null
@@ -0,0 +1,201 @@
+GIT v1.5.2 Release Notes (draft)
+========================
+
+Updates since v1.5.1
+--------------------
+
+* Plumbing level subproject support.
+
+  You can include a subdirectory that has an independent git
+  repository in your index and tree objects as a
+  "subproject".  This plumbing (i.e. "core") level subproject
+  support explicitly excludes recursive behaviour.
+
+  The "subproject" entries in the index and trees are
+  incompatible with older versions of git.  Experimenting with
+  the plumbing level support is encouraged, but be warned that
+  unless everybody in your project updates to this release or
+  later, using this feature would make your project
+  inaccessible by people with older versions of git.
+
+* Plumbing level gitattributes support.
+
+  The gitattributes mechanism allows you to add 'attributes' to
+  paths in your project, and affect the way certain git
+  operations work.  Currently you can influence if a path is
+  considered a binary or text (the former would be treated by
+  'git diff' not to produce textual output; the latter can go
+  through the line endings conversion process in repositories
+  with core.autocrlf set), expand and unexpand '$Id$' keyword
+  with blob object name, specify a custom 3-way merge driver,
+  and specify a custom diff driver.  You can also apply
+  arbitrary filter to contents on check-in/check-out codepath
+  but this feature is an extremely sharp-edged razor and needs
+  to be handled with caution (do not use it unless you
+  understand the earlier mailing list discussion on keyword
+  expansion).
+
+* The packfile format now optionally suports 64-bit index.
+
+  This release supports the "version 2" format of the .idx
+  file.  This is automatically enabled when a huge packfile
+  needs more than 32-bit to express offsets of objects in the
+  pack.
+
+* Comes with an updated git-gui 0.7.0
+
+* Updated gitweb:
+
+  - can show combined diff for merges;
+  - uses font size of user's preference, not hardcoded in pixels;
+
+* New commands and options.
+
+  - "git bisect start" can optionally take a single bad commit and
+    zero or more good commits on the command line.
+
+  - "git shortlog" can optionally be told to wrap its output.
+
+  - "subtree" merge strategy allows another project to be merged in as
+    your subdirectory.
+
+  - "git format-patch" learned a new --subject-prefix=<string>
+    option, to override the built-in "[PATCH]".
+
+  - "git add -u" is a quick way to do the first stage of "git
+    commit -a" (i.e. update the index to match the working
+    tree); it obviously does not make a commit.
+
+  - "git clean" honors a new configuration, "clean.requireforce".  When
+    set to true, this makes "git clean" a no-op, preventing you
+    from losing files by typing "git clean" when you meant to
+    say "make clean".  You can still say "git clean -f" to
+    override this.
+
+  - "git log" family of commands learned --date={local,relative,default}
+    option.  --date=relative is synonym to the --relative-date.
+    --date=local gives the timestamp in local timezone.
+
+* Updated behavior of existing commands.
+
+  - When $GIT_COMMITTER_EMAIL or $GIT_AUTHOR_EMAIL is not set
+    but $EMAIL is set, the latter is used as a substitute.
+
+  - "git diff --stat" shows size of preimage and postimage blobs
+    for binary contents.  Earlier it only said "Bin".
+
+  - "git lost-found" shows stuff that are unreachable except
+    from reflogs.
+
+  - "git checkout branch^0" now detaches HEAD at the tip commit
+    on the named branch, instead of just switching to the
+    branch (use "git checkout branch" to switch to the branch,
+    as before).
+
+  - "git bisect next" can be used after giving only a bad commit
+    without giving a good one (this starts bisection half-way to
+    the root commit).  We used to refuse to operate without a
+    good and a bad commit.
+
+  - "git push", when pushing into more than one repository, does
+    not stop at the first error.
+
+  - "git archive" does not insist you to give --format parameter
+    anymore; it defaults to "tar".
+
+  - "git cvsserver" can use backends other than sqlite.
+
+  - "gitview" (in contrib/ section) learned to better support
+    "git-annotate".
+
+  - "git diff $commit1:$path2 $commit2:$path2" can now report
+    mode changes between the two blobs.
+
+  - Local "git fetch" from a repository whose object store is
+    one of the alternates (e.g. fetching from the origin in a
+    repository created with "git clone -l -s") avoids
+    downloading objects unnecessarily.
+
+  - "git blame" uses .mailmap to canonicalize the author name
+    just like "git shortlog" does.
+
+  - "git pack-objects" pays attention to pack.depth
+    configuration variable.
+
+  - "git cherry-pick" and "git revert" does not use .msg file in
+    the working tree to prepare commit message; instead it uses
+    $GIT_DIR/MERGE_MSG as other commands do.
+
+* Builds
+
+  - git-p4import has never been installed; now there is an
+    installation option to do so.
+
+  - gitk and git-gui can be configured out.
+
+  - Generated documentation pages automatically get version
+    information from GIT_VERSION.
+
+  - Parallel build with "make -j" descending into subdirectory
+    was fixed.
+
+* Performance Tweaks
+
+  - Optimized "git-rev-list --bisect" (hence "git-bisect").
+
+  - Optimized "git-add $path" in a large directory, most of
+    whose contents are ignored.
+
+  - Optimized "git-diff-tree" for reduced memory footprint.
+
+  - The recursive merge strategy updated a worktree file that
+    was changed identically in two branches, when one of them
+    renamed it.  We do not do that when there is no rename, so
+    match that behaviour.  This avoids excessive rebuilds.
+
+  - The default pack depth has been increased to 50, as the
+    recent addition of delta_base_cache makes deeper delta chains
+    much less expensive to access.  Depending on the project, it was
+    reported that this reduces the resulting pack file by 10%
+    or so.
+
+
+Fixes since v1.5.1
+------------------
+
+All of the fixes in v1.5.1 maintenance series are included in
+this release, unless otherwise noted.
+
+* Bugfixes
+
+  - Switching branches with "git checkout" refused to work when
+    a path changes from a file to a directory between the
+    current branch and the new branch, in order not to lose
+    possible local changes in the directory that is being turned
+    into a file with the switch.  We now allow such a branch
+    switch after making sure that there is no locally modified
+    file nor un-ignored file in the directory.  This has not
+    been backported to 1.5.1.x series, as it is rather an
+    intrusive change.
+
+  - Merging branches that have a file in one and a directory in
+    another at the same path used to get quite confused.  We
+    handle such a case a bit more carefully, even though that is
+    still left as a conflict for the user to sort out.  This
+    will not be backported to 1.5.1.x series, as it is rather an
+    intrusive change.
+
+  - git-fetch had trouble with a remote with insanely large number
+    of refs.
+
+  - "git clean -d -X" now does not remove non-excluded directories.
+
+* Documentation updates
+
+* Performance Tweaks
+
+--
+exec >/var/tmp/1
+O=v1.5.2-rc3
+echo O=`git describe refs/heads/master`
+git shortlog --no-merges $O..refs/heads/master ^refs/heads/maint
index 8cf5093dd9b51881977437a327fe69cad12e7efd..6a4da2ddd94093aaa03656303c40a5b20bae2fef 100644 (file)
@@ -30,6 +30,9 @@ Checklist (and a short version for the impatient):
        - provide additional information (which is unsuitable for
          the commit message) between the "---" and the diffstat
        - send the patch to the list _and_ the maintainer
+       - if you change, add, or remove a command line option or
+         make some other user interface change, the associated
+         documentation should be updated as well.
 
 Long version:
 
index 44b1ce4c6b56348e1661b60fc923cb80cb44d4ff..fa7dc94845be148dd85dfc2265dade2093a6c11f 100644 (file)
@@ -31,6 +31,25 @@ ifdef::backend-docbook[]
 {title#}</example>
 endif::backend-docbook[]
 
+ifdef::doctype-manpage[]
+ifdef::backend-docbook[]
+[header]
+template::[header-declarations]
+<refentry>
+<refmeta>
+<refentrytitle>{mantitle}</refentrytitle>
+<manvolnum>{manvolnum}</manvolnum>
+<refmiscinfo class="source">Git</refmiscinfo>
+<refmiscinfo class="version">@@GIT_VERSION@@</refmiscinfo>
+<refmiscinfo class="manual">Git Manual</refmiscinfo>
+</refmeta>
+<refnamediv>
+  <refname>{manname}</refname>
+  <refpurpose>{manpurpose}</refpurpose>
+</refnamediv>
+endif::backend-docbook[]
+endif::doctype-manpage[]
+
 ifdef::backend-xhtml11[]
 [gitlink-inlinemacro]
 <a href="{target}.html">{target}{0?({0})}</a>
index 0381590d383c4fffbf04895be4c47e407dd94e80..443802a9a3e4982c33f831217602ea600f0f2775 100755 (executable)
@@ -84,6 +84,7 @@ git-bundle                              mainporcelain
 git-cat-file                            plumbinginterrogators
 git-checkout-index                      plumbingmanipulators
 git-checkout                            mainporcelain
+git-check-attr                          purehelpers
 git-check-ref-format                    purehelpers
 git-cherry                              ancillaryinterrogators
 git-cherry-pick                         mainporcelain
index a7daa08731715846d68b35c4e9da740e878ed05a..ea434af9db1e864c4230b60886f1ca24237327e4 100644 (file)
@@ -300,6 +300,10 @@ branch.<name>.merge::
        branch.<name>.merge to the desired branch, and use the special setting
        `.` (a period) for branch.<name>.remote.
 
+clean.requireForce::
+       A boolean to make git-clean do nothing unless given -f or -n.  Defaults
+       to false.
+
 color.branch::
        A boolean to enable/disable color in the output of
        gitlink:git-branch[1]. May be set to `true` (or `always`),
@@ -423,8 +427,34 @@ gitcvs.allbinary::
        causes the client to treat all files as binary files which suppresses
        any newline munging it otherwise might do. A work-around for the
        fact that there is no way yet to set single files to mode '-kb'.
+
+gitcvs.dbname::
+       Database used by git-cvsserver to cache revision information
+       derived from the git repository. The exact meaning depends on the
+       used database driver, for SQLite (which is the default driver) this
+       is a filename. Supports variable substitution (see
+       gitlink:git-cvsserver[1] for details). May not contain semicolons (`;`).
+       Default: '%Ggitcvs.%m.sqlite'
+
+gitcvs.dbdriver::
+       Used Perl DBI driver. You can specify any available driver
+        for this here, but it might not work. git-cvsserver is tested
+       with 'DBD::SQLite', reported to work with 'DBD::Pg', and
+       reported *not* to work with 'DBD::mysql'. Experimental feature.
+       May not contain double colons (`:`). Default: 'SQLite'.
        See gitlink:git-cvsserver[1].
 
+gitcvs.dbuser, gitcvs.dbpass::
+       Database user and password. Only useful if setting 'gitcvs.dbdriver',
+       since SQLite has no concept of database users and/or passwords.
+       'gitcvs.dbuser' supports variable substitution (see
+       gitlink:git-cvsserver[1] for details).
+
+All gitcvs variables except for 'gitcvs.allbinary' can also specifed
+as 'gitcvs.<access_method>.<varname>' (where 'access_method' is one
+of "ext" and "pserver") to make them apply only for the given access
+method.
+
 http.sslVerify::
        Whether to verify the SSL certificate when fetching or pushing
        over HTTPS. Can be overridden by the 'GIT_SSL_NO_VERIFY' environment
@@ -499,10 +529,27 @@ merge.verbosity::
        conflicts, 2 outputs conflicts and file changes.  Level 5 and
        above outputs debugging information.  The default is level 2.
 
+merge.<driver>.name::
+       Defines a human readable name for a custom low-level
+       merge driver.  See gitlink:gitattributes[5] for details.
+
+merge.<driver>.driver::
+       Defines the command that implements a custom low-level
+       merge driver.  See gitlink:gitattributes[5] for details.
+
+merge.<driver>.recursive::
+       Names a low-level merge driver to be used when
+       performing an internal merge between common ancestors.
+       See gitlink:gitattributes[5] for details.
+
 pack.window::
        The size of the window used by gitlink:git-pack-objects[1] when no
        window size is given on the command line. Defaults to 10.
 
+pack.depth::
+       The maximum delta depth used by gitlink:git-pack-objects[1] when no
+       maximum depth is given on the command line. Defaults to 50.
+
 pull.octopus::
        The default merge strategy to use when pulling multiple branches
        at once.
@@ -567,8 +614,8 @@ tar.umask::
 
 user.email::
        Your email address to be recorded in any newly created commits.
-       Can be overridden by the 'GIT_AUTHOR_EMAIL' and 'GIT_COMMITTER_EMAIL'
-       environment variables.  See gitlink:git-commit-tree[1].
+       Can be overridden by the 'GIT_AUTHOR_EMAIL', 'GIT_COMMITTER_EMAIL', and
+       'EMAIL' environment variables.  See gitlink:git-commit-tree[1].
 
 user.name::
        Your full name to be recorded in any newly created commits.
index 97cdb90cb4e5763a4d0cec1a0d01f5380b4e512f..6b9b9ad7d1083ee09955ddaf46db4fad1a37834b 100644 (file)
@@ -319,10 +319,9 @@ argument to `git-commit-tree`.
 `git-commit-tree` normally takes several arguments -- it wants to know
 what the 'parent' of a commit was, but since this is the first commit
 ever in this new repository, and it has no parents, we only need to pass in
-the object name of the tree. However, `git-commit-tree`
-also wants to get a commit message
-on its standard input, and it will write out the resulting object name for the
-commit to its standard output.
+the object name of the tree. However, `git-commit-tree` also wants to get a
+commit message on its standard input, and it will write out the resulting
+object name for the commit to its standard output.
 
 And this is where we create the `.git/refs/heads/master` file
 which is pointed at by `HEAD`. This file is supposed to contain
@@ -1304,7 +1303,7 @@ So, we can use somebody else's work from a remote repository, but
 how can *you* prepare a repository to let other people pull from
 it?
 
-Your do your real work in your working tree that has your
+You do your real work in your working tree that has your
 primary repository hanging under it as its `.git` subdirectory.
 You *could* make that repository accessible remotely and ask
 people to pull from it, but in practice that is not the way
index 38c72b87329720c630419768c9c68cbe718d7a15..a0c9f68580594f785aaec0472ca8404701d4d86d 100644 (file)
@@ -7,7 +7,7 @@ git-add - Add file contents to the changeset to be committed next
 
 SYNOPSIS
 --------
-'git-add' [-n] [-v] [-f] [--interactive | -i] [--] <file>...
+'git-add' [-n] [-v] [-f] [--interactive | -i] [-u] [--] <file>...
 
 DESCRIPTION
 -----------
@@ -56,6 +56,13 @@ OPTIONS
        Add modified contents in the working tree interactively to
        the index.
 
+-u::
+       Update only files that git already knows about. This is similar
+       to what "git commit -a" does in preparation for making a commit,
+       except that the update is limited to paths specified on the
+       command line. If no paths are specified, all tracked files are
+       updated.
+
 \--::
        This option can be used to separate command-line options from
        the list of files, (useful when filenames might be mistaken
index 5fd3b62cf17b76b06dd8a799b03143a7203a8309..721e0351c3b7b50cfeadaa64af4a9e8cd5be27d5 100644 (file)
@@ -31,7 +31,8 @@ OPTIONS
 -------
 
 --format=<fmt>::
-       Format of the resulting archive: 'tar', 'zip'...
+       Format of the resulting archive: 'tar', 'zip'...  The default
+       is 'tar'.
 
 --list, -l::
        Show all available formats.
index b2bc58d8513b0c064333d8b0aa357ebcea3ba28f..5f68ee1584b294a9395f2a3d4f29e0b1208f5913 100644 (file)
@@ -15,7 +15,7 @@ DESCRIPTION
 The command takes various subcommands, and different options depending
 on the subcommand:
 
- git bisect start [<paths>...]
+ git bisect start [<bad> [<good>...]] [--] [<paths>...]
  git bisect bad <rev>
  git bisect good <rev>
  git bisect reset [<branch>]
@@ -134,15 +134,26 @@ $ git reset --hard HEAD~3         # try 3 revs before what
 Then compile and test the one you chose to try. After that, tell
 bisect what the result was as usual.
 
-Cutting down bisection by giving path parameter to bisect start
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+Cutting down bisection by giving more parameters to bisect start
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
 You can further cut down the number of trials if you know what part of
 the tree is involved in the problem you are tracking down, by giving
 paths parameters when you say `bisect start`, like this:
 
 ------------
-$ git bisect start arch/i386 include/asm-i386
+$ git bisect start -- arch/i386 include/asm-i386
+------------
+
+If you know beforehand more than one good commits, you can narrow the
+bisect space down without doing the whole tree checkout every time you
+give good commits. You give the bad revision immediately after `start`
+and then you give all the good revisions you have:
+
+------------
+$ git bisect start v2.6.20-rc6 v2.6.20-rc4 v2.6.20-rc1 --
+                   # v2.6.20-rc6 is bad
+                   # v2.6.20-rc4 and v2.6.20-rc1 are good
 ------------
 
 Bisect run
index 8f9439a6ddfa505c0f3cd0d1f0ba826fc12cc1ba..44678b0c3601512df024e3670aadeabd5317b0c9 100644 (file)
@@ -8,7 +8,7 @@ git-blame - Show what revision and author last modified each line of a file
 SYNOPSIS
 --------
 [verse]
-'git-blame' [-c] [-l] [-t] [-f] [-n] [-p] [--incremental] [-L n,m]
+'git-blame' [-c] [-b] [--root] [-s] [-l] [-t] [-f] [-n] [-p] [--incremental] [-L n,m]
             [-S <revs-file>] [-M] [-C] [-C] [--since=<date>]
             [<rev> | --contents <file>] [--] <file>
 
@@ -60,6 +60,9 @@ include::blame-options.txt[]
 -n, --show-number::
        Show line number in the original commit (Default: off).
 
+-s::
+       Suppress author name and timestamp from the output.
+
 THE PORCELAIN FORMAT
 --------------------
 
diff --git a/Documentation/git-check-attr.txt b/Documentation/git-check-attr.txt
new file mode 100644 (file)
index 0000000..ceb5195
--- /dev/null
@@ -0,0 +1,37 @@
+git-check-attr(1)
+=================
+
+NAME
+----
+git-check-attr - Display gitattributes information.
+
+
+SYNOPSIS
+--------
+'git-check-attr' attr... [--] pathname...
+
+DESCRIPTION
+-----------
+For every pathname, this command will list if each attr is 'unspecified',
+'set', or 'unset' as a gitattribute on that pathname.
+
+OPTIONS
+-------
+\--::
+       Interpret all preceding arguments as attributes, and all following
+       arguments as path names. If not supplied, only the first argument will
+       be treated as an attribute.
+
+
+Author
+------
+Written by Junio C Hamano <junkio@cox.net>
+
+Documentation
+--------------
+Documentation by James Bowes.
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
index c61afbcdbac49ccbb7ee755e3fdb19d7d77225d0..e3252d59daa33576c0b00bd5f2daa3a5b50c9b7a 100644 (file)
@@ -8,7 +8,7 @@ git-clean - Remove untracked files from the working tree
 SYNOPSIS
 --------
 [verse]
-'git-clean' [-d] [-n] [-q] [-x | -X] [--] <paths>...
+'git-clean' [-d] [-f] [-n] [-q] [-x | -X] [--] <paths>...
 
 DESCRIPTION
 -----------
@@ -25,6 +25,10 @@ OPTIONS
 -d::
        Remove untracked directories in addition to untracked files.
 
+-f::
+       If the git configuration specifies clean.requireForce as true,
+       git-clean will refuse to run unless given -f or -n.
+
 -n::
        Don't actually remove anything, just show what would be done.
 
index 1571dbbb7449fb0d790fccf6b6adbca8d4f5c664..504a3aa1b460ffac9308bec4ff72cf657f8fec30 100644 (file)
@@ -61,6 +61,7 @@ either `.git/config` file, or using the following environment variables.
        GIT_COMMITTER_NAME
        GIT_COMMITTER_EMAIL
        GIT_COMMITTER_DATE
+       EMAIL
 
 (nb "<", ">" and "\n"s are stripped)
 
index f9e0c7737952891633a1f5503f8dc5ad46fbf53f..d22844ba49859b9a189317744e0f14431267e60a 100644 (file)
@@ -31,6 +31,10 @@ over pserver for anonymous CVS access.
 
 CVS clients cannot tag, branch or perform GIT merges.
 
+git-cvsserver maps GIT branches to CVS modules. This is very different
+from what most CVS users would expect since in CVS modules usually represent
+one or more directories.
+
 INSTALLATION
 ------------
 
@@ -65,9 +69,22 @@ env variable, you can rename git-cvsserver to cvs.
 
 ------
 Note: you need to ensure each user that is going to invoke git-cvsserver has
-write access to the log file and to the git repository. When offering anon
-access via pserver, this means that the nobody user should have write access
-to at least the sqlite database at the root of the repository.
+write access to the log file and to the database (see
+<<dbbackend,Database Backend>>. If you want to offer write access over
+SSH, the users of course also need write access to the git repository itself.
+
+[[configaccessmethod]]
+All configuration variables can also be overriden for a specific method of
+access. Valid method names are "ext" (for SSH access) and "pserver". The
+following example configuration would disable pserver access while still
+allowing access over SSH.
+------
+   [gitcvs]
+        enabled=0
+
+   [gitcvs "ext"]
+        enabled=1
+------
 --
 3. On the client machine you need to set the following variables.
    CVSROOT should be set as per normal, but the directory should point at the
@@ -93,6 +110,90 @@ Example:
      cvs co -d project-master master
 ------
 
+[[dbbackend]]
+Database Backend
+----------------
+
+git-cvsserver uses one database per git head (i.e. CVS module) to
+store information about the repository for faster access. The
+database doesn't contain any persitent data and can be completly
+regenerated from the git repository at any time. The database
+needs to be updated (i.e. written to) after every commit.
+
+If the commit is done directly by using git (as opposed to
+using git-cvsserver) the update will need to happen on the
+next repository access by git-cvsserver, independent of
+access method and requested operation.
+
+That means that even if you offer only read access (e.g. by using
+the pserver method), git-cvsserver should have write access to
+the database to work reliably (otherwise you need to make sure
+that the database if up-to-date all the time git-cvsserver is run).
+
+By default it uses SQLite databases in the git directory, named
+`gitcvs.<module_name>.sqlite`. Note that the SQLite backend creates
+temporary files in the same directory as the database file on
+write so it might not be enough to grant the users using
+git-cvsserver write access to the database file without granting
+them write access to the directory, too.
+
+You can configure the database backend with the following
+configuration variables:
+
+Configuring database backend
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+git-cvsserver uses the Perl DBI module. Please also read
+its documentation if changing these variables, especially
+about `DBI->connect()`.
+
+gitcvs.dbname::
+       Database name. The exact meaning depends on the
+       used database driver, for SQLite this is a filename.
+       Supports variable substitution (see below). May
+       not contain semicolons (`;`).
+       Default: '%Ggitcvs.%m.sqlite'
+
+gitcvs.dbdriver::
+       Used DBI driver. You can specify any available driver
+       for this here, but it might not work. cvsserver is tested
+       with 'DBD::SQLite', reported to work with
+       'DBD::Pg', and reported *not* to work with 'DBD::mysql'.
+       Please regard this as an experimental feature. May not
+       contain double colons (`:`).
+       Default: 'SQLite'
+
+gitcvs.dbuser::
+       Database user. Only useful if setting `dbdriver`, since
+       SQLite has no concept of database users. Supports variable
+       substitution (see below).
+
+gitcvs.dbpass::
+       Database password.  Only useful if setting `dbdriver`, since
+       SQLite has no concept of database passwords.
+
+All variables can also be set per access method, see <<configaccessmethod,above>>.
+
+Variable substitution
+^^^^^^^^^^^^^^^^^^^^^
+In `dbdriver` and `dbuser` you can use the following variables:
+
+%G::
+       git directory name
+%g::
+       git directory name, where all characters except for
+       alpha-numeric ones, `.`, and `-` are replaced with
+       `_` (this should make it easier to use the directory
+       name in a filename if wanted)
+%m::
+       CVS module/git head name
+%a::
+       access method (one of "ext" or "pserver")
+%u::
+       Name of the user running git-cvsserver.
+       If no name can be determined, the
+       numeric uid is used.
+
 Eclipse CVS Client Notes
 ------------------------
 
index 5d6e9dc751aef6e30582b399ae09f81c043e6de1..6e660e2d08c2d9d795b5ccfb91f755160cf58930 100644 (file)
@@ -73,7 +73,7 @@ separated with a single space are given.
        This flag causes "git-diff-tree --stdin" to also show
        the commit message before the differences.
 
-include::pretty-formats.txt[]
+include::pretty-options.txt[]
 
 --no-commit-id::
        git-diff-tree outputs a line with the commit ID when
@@ -104,6 +104,9 @@ include::pretty-formats.txt[]
        if the diff itself is empty.
 
 
+include::pretty-formats.txt[]
+
+
 Limiting Output
 ---------------
 If you're only interested in differences in a subset of files, for
index 111d7c60bf1832bbfc27f8b819da77b8761236da..a33d157b970740aa7d056ebb459350de89513a8b 100644 (file)
@@ -10,11 +10,12 @@ SYNOPSIS
 --------
 [verse]
 'git-format-patch' [-n | -k] [-o <dir> | --stdout] [--thread]
-                  [--attach[=<boundary>] | --inline[=<boundary>]]
-                  [-s | --signoff] [<common diff options>] [--start-number <n>]
-                  [--in-reply-to=Message-Id] [--suffix=.<sfx>]
-                  [--ignore-if-in-upstream]
-                  <since>[..<until>]
+                   [--attach[=<boundary>] | --inline[=<boundary>]]
+                   [-s | --signoff] [<common diff options>] [--start-number <n>]
+                   [--in-reply-to=Message-Id] [--suffix=.<sfx>]
+                   [--ignore-if-in-upstream]
+                   [--subject-prefix=Subject-Prefix]
+                   <since>[..<until>]
 
 DESCRIPTION
 -----------
@@ -98,6 +99,12 @@ include::diff-options.txt[]
        patches being generated, and any patch that matches is
        ignored.
 
+--subject-prefix=<Subject-Prefix>::
+       Instead of the standard '[PATCH]' prefix in the subject
+       line, instead use '[<Subject-Prefix>]'. This
+       allows for useful naming of a patch series, and can be
+       combined with the --numbered option.
+
 --suffix=.<sfx>::
        Instead of using `.patch` as the suffix for generated
        filenames, use specifed suffix.  A common alternative is
index 058009d2fab4c73edcbfefe7b48c5f9348459da3..8c68cf037259b3abc7ea16952d232b2fb2f07a25 100644 (file)
@@ -9,7 +9,7 @@ git-fsck - Verifies the connectivity and validity of the objects in the database
 SYNOPSIS
 --------
 [verse]
-'git-fsck' [--tags] [--root] [--unreachable] [--cache]
+'git-fsck' [--tags] [--root] [--unreachable] [--cache] [--no-reflogs]
                 [--full] [--strict] [<object>*]
 
 DESCRIPTION
@@ -38,6 +38,12 @@ index file and all SHA1 references in .git/refs/* as heads.
        Consider any object recorded in the index also as a head node for
        an unreachability trace.
 
+--no-reflogs::
+       Do not consider commits that are referenced only by an
+       entry in a reflog to be reachable.  This option is meant
+       only to search for commits that used to be in a ref, but
+       now aren't, but are still in that corresponding reflog.
+
 --full::
        Check not just objects in GIT_OBJECT_DIRECTORY
        ($GIT_DIR/objects), but also the ones found in alternate
index c498bed845e56eeca3fd520eeba7289261759183..226926964e5f3e6620f20b23b74c0299a168d07f 100644 (file)
@@ -70,6 +70,11 @@ OPTIONS
        message can later be searched for within all .keep files to
        locate any which have outlived their usefulness.
 
+--index-version=<version>[,<offset>]::
+       This is intended to be used by the test suite only. It allows
+       to force the version for the generated pack index, and to force
+       64-bit index entries on objects located above the given offset.
+
 
 Note
 ----
index 49bb539dea6ad86fa8844cf6bbe27a74af41b4e4..0f353f6558e5843b1534f4cb9acd75746c313510 100644 (file)
@@ -25,7 +25,7 @@ This manual page describes only the most frequently used options.
 OPTIONS
 -------
 
-include::pretty-formats.txt[]
+include::pretty-options.txt[]
 
 -<n>::
        Limits the number of commits to show.
@@ -51,10 +51,16 @@ include::pretty-formats.txt[]
        a record about how the tip of a reference was changed.
        See also gitlink:git-reflog[1].
 
+--decorate::
+    Print out the ref names of any commits that are shown.
+
 <paths>...::
        Show only commits that affect the specified paths.
 
 
+include::pretty-formats.txt[]
+
+
 Examples
 --------
 git log --no-merges::
index 79e0b7b71a126537091fdb159a1141036215fb63..076cebca1747ed8e35b71aefa2cae9d428a0d1e4 100644 (file)
@@ -42,8 +42,8 @@ OPTIONS
        Show other files in the output
 
 -i|--ignored::
-       Show ignored files in the output
-       Note the this also reverses any exclude list present.
+       Show ignored files in the output.
+       Note that this also reverses any exclude list present.
 
 -s|--stage::
        Show stage files in the output
index fdc6f9728921e3bf71a57d441c99c8fb7d280803..bd3ee456e336add5d880d6a1192646220e7c34d7 100644 (file)
@@ -83,7 +83,7 @@ base-name::
        it too deep affects the performance on the unpacker
        side, because delta data needs to be applied that many
        times to get to the necessary object.
-       The default value for both --window and --depth is 10.
+       The default value for --window is 10 and --depth is 50.
 
 --incremental::
        This flag causes an object already in a pack ignored
@@ -138,6 +138,11 @@ base-name::
        length, this option typically shrinks the resulting
        packfile by 3-5 per-cent.
 
+--index-version=<version>[,<offset>]::
+       This is intended to be used by the test suite only. It allows
+       to force the version for the generated pack index, and to force
+       64-bit index entries on objects located above the given offset.
+
 
 Author
 ------
index 0ff2890c7fb76155e08eaf9e361bf1eb6cf50f25..019c8bef7af2868af150b4a13472b8ce26744ea3 100644 (file)
@@ -8,7 +8,7 @@ git-read-tree - Reads tree information into the index
 
 SYNOPSIS
 --------
-'git-read-tree' (<tree-ish> | [[-m [--aggressive] | --reset | --prefix=<prefix>] [-u | -i]] [--exclude-per-directory=<gitignore>] <tree-ish1> [<tree-ish2> [<tree-ish3>]])
+'git-read-tree' (<tree-ish> | [[-m [--aggressive] | --reset | --prefix=<prefix>] [-u | -i]] [--exclude-per-directory=<gitignore>] [--index-output=<file>] <tree-ish1> [<tree-ish2> [<tree-ish3>]])
 
 
 DESCRIPTION
@@ -86,6 +86,18 @@ OPTIONS
        file (usually '.gitignore') and allows such an untracked
        but explicitly ignored file to be overwritten.
 
+--index-output=<file>::
+       Instead of writing the results out to `$GIT_INDEX_FILE`,
+       write the resulting index in the named file.  While the
+       command is operating, the original index file is locked
+       with the same mechanism as usual.  The file must allow
+       to be rename(2)ed into from a temporary file that is
+       created next to the usual index file; typically this
+       means it needs to be on the same filesystem as the index
+       file itself, and you need write permission to the
+       directories the index file and index output file are
+       located in.
+
 <tree-ish#>::
        The id of the tree object(s) to be read/merged.
 
index d39abc126dec70108625af13d11541b771c8e85d..cc3b0b21c727349eb3a51d9837bc006b987537c3 100644 (file)
@@ -63,7 +63,7 @@ OPTIONS
        space. `--depth` limits the maximum delta depth; making it too deep
        affects the performance on the unpacker side, because delta data needs
        to be applied that many times to get to the necessary object.
-       The default value for both --window and --depth is 10.
+       The default value for --window is 10 and --depth is 50.
 
 
 Configuration
index fde9a7208de04a0ee3165dd7d398061270649116..c3c2043d183e0e6c7292f5af7c28af57958c4dcd 100644 (file)
@@ -22,11 +22,14 @@ SYNOPSIS
             [ \--topo-order ]
             [ \--parents ]
             [ \--left-right ]
+            [ \--cherry-pick ]
             [ \--encoding[=<encoding>] ]
             [ \--(author|committer|grep)=<pattern> ]
+            [ \--date={local|relative|default} ]
             [ [\--objects | \--objects-edge] [ \--unpacked ] ]
             [ \--pretty | \--header ]
             [ \--bisect ]
+            [ \--bisect-vars ]
             [ \--merge ]
             [ \--reverse ]
             [ \--walk-reflogs ]
@@ -84,13 +87,24 @@ Using these options, gitlink:git-rev-list[1] will act similar to the
 more specialized family of commit log tools: gitlink:git-log[1],
 gitlink:git-show[1], and gitlink:git-whatchanged[1]
 
-include::pretty-formats.txt[]
+include::pretty-options.txt[]
 
 --relative-date::
 
-       Show dates relative to the current time, e.g. "2 hours ago".
+       Synonym for `--date=relative`.
+
+--date={relative,local,default}::
+
        Only takes effect for dates shown in human-readable format, such
        as when using "--pretty".
++
+`--date=relative` shows dates relative to the current time,
+e.g. "2 hours ago".
++
+`--date=local` shows timestamps in user's local timezone.
++
+`--date=default` shows timestamps in the original timezone
+(either committer's or author's).
 
 --header::
 
@@ -223,6 +237,20 @@ limiting may be applied.
        In addition to the '<commit>' listed on the command
        line, read them from the standard input.
 
+--cherry-pick::
+
+       Omit any commit that introduces the same change as
+       another commit on the "other side" when the set of
+       commits are limited with symmetric difference.
++
+For example, if you have two branches, `A` and `B`, a usual way
+to list all commits on only one side of them is with
+`--left-right`, like the example above in the description of
+that option.  It however shows the commits that were cherry-picked
+from the other branch (for example, "3rd on b" may be cherry-picked
+from branch A).  With this option, such pairs of commits are
+excluded from the output.
+
 -g, --walk-reflogs::
 
        Instead of walking the commit ancestry chain, walk
@@ -280,6 +308,18 @@ introduces a regression is thus reduced to a binary search: repeatedly
 generate and test new 'midpoint's until the commit chain is of length
 one.
 
+--bisect-vars::
+
+This calculates the same as `--bisect`, but outputs text ready
+to be eval'ed by the shell. These lines will assign the name of
+the midpoint revision to the variable `bisect_rev`, and the
+expected number of commits to be tested after `bisect_rev` is
+tested to `bisect_nr`, the expected number of commits to be
+tested if `bisect_rev` turns out to be good to `bisect_good`,
+the expected number of commits to be tested if `bisect_rev`
+turns out to be bad to `bisect_bad`, and the number of commits
+we are bisecting right now to `bisect_all`.
+
 --
 
 Commit Ordering
@@ -327,6 +367,10 @@ These options are mostly targeted for packing of git repositories.
        Only useful with '--objects'; print the object IDs that are not
        in packs.
 
+
+include::pretty-formats.txt[]
+
+
 Author
 ------
 Written by Linus Torvalds <torvalds@osdl.org>
index 6feebc0400b86407a118840d9a6cc994a94715d6..a65f24a0f698eb801ac43bba5aa7dc746e96edc0 100644 (file)
@@ -7,7 +7,7 @@ git-rm - Remove files from the working tree and from the index
 
 SYNOPSIS
 --------
-'git-rm' [-f] [-n] [-r] [--cached] [--] <file>...
+'git-rm' [-f] [-n] [-r] [--cached] [--ignore-unmatch] [--quiet] [--] <file>...
 
 DESCRIPTION
 -----------
@@ -47,6 +47,13 @@ OPTIONS
        the paths only from the index, leaving working tree
        files.
 
+\--ignore-unmatch::
+       Exit with a zero status even if no files matched.
+
+\--quiet::
+       git-rm normally outputs one line (in the form of an "rm" command)
+       for each file removed. This option suppresses that output.
+
 
 DISCUSSION
 ----------
index 5a219ab5776c719190095ab4e9af95514a888ea3..34c5caf2d08324cc1595013cbdcd40d89d7a1968 100644 (file)
@@ -38,6 +38,9 @@ OPTIONS
        For a more complete list of ways to spell object names, see
        "SPECIFYING REVISIONS" section in gitlink:git-rev-parse[1].
 
+include::pretty-options.txt[]
+
+
 include::pretty-formats.txt[]
 
 
index 70235e8ddb3e43805d699dd0bf264f585409697e..4e3e02756c8863dbc6fe1d745e1a8c9a97c18919 100644 (file)
@@ -9,9 +9,10 @@ git-tag - Create, list, delete or verify a tag object signed with GPG
 SYNOPSIS
 --------
 [verse]
-'git-tag' [-a | -s | -u <key-id>] [-f | -v] [-m <msg> | -F <file>]  <name> [<head>]
+'git-tag' [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>]  <name> [<head>]
 'git-tag' -d <name>...
 'git-tag' -l [<pattern>]
+'git-tag' -v <name>
 
 DESCRIPTION
 -----------
@@ -77,8 +78,10 @@ committer identity (of the form "Your Name <your@email.address>") to
 find a key.  If you want to use a different default key, you can specify
 it in the repository configuration as follows:
 
+-------------------------------------
 [user]
     signingkey = <gpg-key-id>
+-------------------------------------
 
 
 DISCUSSION
index cd5e014d4899954b204173f6e1d84e011a307ac8..6cfbd9a842f0601cf81f8780ecf6b7939b25dccb 100644 (file)
@@ -27,6 +27,9 @@ Modifies the index or directory cache. Each file mentioned is updated
 into the index and any 'unmerged' or 'needs updating' state is
 cleared.
 
+See also gitlink:git-add[1] for a more user-friendly way to do some of
+the most common operations on the index.
+
 The way "git-update-index" handles files it is told about can be modified
 using the various options:
 
@@ -306,7 +309,8 @@ The command looks at `core.ignorestat` configuration variable.  See
 
 See Also
 --------
-gitlink:git-config[1]
+gitlink:git-config[1],
+gitlink:git-add[1]
 
 
 Author
index c2b7f3fec6072eac2d9f8e1ae466cea9b54a291d..157ef29131ebcdbd60f81507681e68ac84ce307c 100644 (file)
@@ -35,34 +35,35 @@ documentation can be viewed at
 ifdef::stalenotes[]
 [NOTE]
 ============
-You are reading the documentation for the latest version of git.
-Documentation for older releases are available here:
-
-* link:RelNotes-1.5.1.txt[release notes for 1.5.1]
-
-* link:v1.5.0.7/git.html[documentation for release 1.5.0.7]
-
-* link:RelNotes-1.5.0.7.txt[release notes for 1.5.0.7]
-
-* link:RelNotes-1.5.0.6.txt[release notes for 1.5.0.6]
 
-* link:RelNotes-1.5.0.5.txt[release notes for 1.5.0.5]
-
-* link:RelNotes-1.5.0.3.txt[release notes for 1.5.0.3]
-
-* link:RelNotes-1.5.0.2.txt[release notes for 1.5.0.2]
-
-* link:RelNotes-1.5.0.1.txt[release notes for 1.5.0.1]
+You are reading the documentation for the latest (possibly
+unreleased) version of git, that is available from 'master'
+branch of the `git.git` repository.
+Documentation for older releases are available here:
 
-* link:RelNotes-1.5.0.txt[release notes for 1.5.0]
+* link:v1.5.1.5/git.html[documentation for release 1.5.1.5]
 
-* link:v1.4.4.4/git.html[documentation for release 1.4.4.4]
+* release notes for link:RelNotes-1.5.1.5.txt[1.5.1.5],
+  link:RelNotes-1.5.1.4.txt[1.5.1.4],
+  link:RelNotes-1.5.1.3.txt[1.5.1.3],
+  link:RelNotes-1.5.1.2.txt[1.5.1.2],
+  link:RelNotes-1.5.1.1.txt[1.5.1.1],
+  link:RelNotes-1.5.1.txt[1.5.1].
 
-* link:v1.3.3/git.html[documentation for release 1.3.3]
+* link:v1.5.0.7/git.html[documentation for release 1.5.0.7]
 
-* link:v1.2.6/git.html[documentation for release 1.2.6]
+* release notes for link:RelNotes-1.5.0.7.txt[1.5.0.7],
+  link:RelNotes-1.5.0.6.txt[1.5.0.6],
+  link:RelNotes-1.5.0.5.txt[1.5.0.5],
+  link:RelNotes-1.5.0.3.txt[1.5.0.3],
+  link:RelNotes-1.5.0.2.txt[1.5.0.2],
+  link:RelNotes-1.5.0.1.txt[1.5.0.1],
+  link:RelNotes-1.5.0.txt[1.5.0].
 
-* link:v1.0.13/git.html[documentation for release 1.0.13]
+* documentation for release link:v1.4.4.4/git.html[1.4.4.4],
+  link:v1.3.3/git.html[1.3.3],
+  link:v1.2.6/git.html[1.2.6],
+  link:v1.0.13/git.html[1.0.13].
 
 ============
 
@@ -344,6 +345,7 @@ git Commits
 'GIT_COMMITTER_NAME'::
 'GIT_COMMITTER_EMAIL'::
 'GIT_COMMITTER_DATE'::
+'EMAIL'::
        see gitlink:git-commit-tree[1]
 
 git Diffs
diff --git a/Documentation/gitattributes.txt b/Documentation/gitattributes.txt
new file mode 100644 (file)
index 0000000..d3ac9c7
--- /dev/null
@@ -0,0 +1,379 @@
+gitattributes(5)
+================
+
+NAME
+----
+gitattributes - defining attributes per path
+
+SYNOPSIS
+--------
+$GIT_DIR/info/attributes, gitattributes
+
+
+DESCRIPTION
+-----------
+
+A `gitattributes` file is a simple text file that gives
+`attributes` to pathnames.
+
+Each line in `gitattributes` file is of form:
+
+       glob    attr1 attr2 ...
+
+That is, a glob pattern followed by an attributes list,
+separated by whitespaces.  When the glob pattern matches the
+path in question, the attributes listed on the line are given to
+the path.
+
+Each attribute can be in one of these states for a given path:
+
+Set::
+
+       The path has the attribute with special value "true";
+       this is specified by listing only the name of the
+       attribute in the attribute list.
+
+Unset::
+
+       The path has the attribute with special value "false";
+       this is specified by listing the name of the attribute
+       prefixed with a dash `-` in the attribute list.
+
+Set to a value::
+
+       The path has the attribute with specified string value;
+       this is specified by listing the name of the attribute
+       followed by an equal sign `=` and its value in the
+       attribute list.
+
+Unspecified::
+
+       No glob pattern matches the path, and nothing says if
+       the path has or does not have the attribute, the
+       attribute for the path is said to be Unspecified.
+
+When more than one glob pattern matches the path, a later line
+overrides an earlier line.  This overriding is done per
+attribute.
+
+When deciding what attributes are assigned to a path, git
+consults `$GIT_DIR/info/attributes` file (which has the highest
+precedence), `.gitattributes` file in the same directory as the
+path in question, and its parent directories (the further the
+directory that contains `.gitattributes` is from the path in
+question, the lower its precedence).
+
+Sometimes you would need to override an setting of an attribute
+for a path to `unspecified` state.  This can be done by listing
+the name of the attribute prefixed with an exclamation point `!`.
+
+
+EFFECTS
+-------
+
+Certain operations by git can be influenced by assigning
+particular attributes to a path.  Currently, three operations
+are attributes-aware.
+
+Checking-out and checking-in
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+These attributes affect how the contents stored in the
+repository are copied to the working tree files when commands
+such as `git checkout` and `git merge` run.  They also affect how
+git stores the contents you prepare in the working tree in the
+repository upon `git add` and `git commit`.
+
+`crlf`
+^^^^^^
+
+This attribute controls the line-ending convention.
+
+Set::
+
+       Setting the `crlf` attribute on a path is meant to mark
+       the path as a "text" file.  'core.autocrlf' conversion
+       takes place without guessing the content type by
+       inspection.
+
+Unset::
+
+       Unsetting the `crlf` attribute on a path is meant to
+       mark the path as a "binary" file.  The path never goes
+       through line endings conversion upon checkin/checkout.
+
+Unspecified::
+
+       Unspecified `crlf` attribute tells git to apply the
+       `core.autocrlf` conversion when the file content looks
+       like text.
+
+Set to string value "input"::
+
+       This is similar to setting the attribute to `true`, but
+       also forces git to act as if `core.autocrlf` is set to
+       `input` for the path.
+
+Any other value set to `crlf` attribute is ignored and git acts
+as if the attribute is left unspecified.
+
+
+The `core.autocrlf` conversion
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+If the configuration variable `core.autocrlf` is false, no
+conversion is done.
+
+When `core.autocrlf` is true, it means that the platform wants
+CRLF line endings for files in the working tree, and you want to
+convert them back to the normal LF line endings when checking
+in to the repository.
+
+When `core.autocrlf` is set to "input", line endings are
+converted to LF upon checkin, but there is no conversion done
+upon checkout.
+
+
+`ident`
+^^^^^^^
+
+When the attribute `ident` is set to a path, git replaces
+`$Id$` in the blob object with `$Id:`, followed by
+40-character hexadecimal blob object name, followed by a dollar
+sign `$` upon checkout.  Any byte sequence that begins with
+`$Id:` and ends with `$` in the worktree file is replaced
+with `$Id$` upon check-in.
+
+
+Interaction between checkin/checkout attributes
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+In the check-in codepath, the worktree file is first converted
+with `ident` (if specified), and then with `crlf` (again, if
+specified and applicable).
+
+In the check-out codepath, the blob content is first converted
+with `crlf`, and then `ident`.
+
+
+`filter`
+^^^^^^^^
+
+A `filter` attribute can be set to a string value.  This names
+filter driver specified in the configuration.
+
+A filter driver consists of `clean` command and `smudge`
+command, either of which can be left unspecified.  Upon
+checkout, when `smudge` command is specified, the command is fed
+the blob object from its standard input, and its standard output
+is used to update the worktree file.  Similarly, `clean` command
+is used to convert the contents of worktree file upon checkin.
+
+Missing filter driver definition in the config is not an error
+but makes the filter a no-op passthru.
+
+The content filtering is done to massage the content into a
+shape that is more convenient for the platform, filesystem, and
+the user to use.  The keyword here is "more convenient" and not
+"turning something unusable into usable".  In other words, it is
+"hanging yourself because we gave you a long rope" if your
+project uses filtering mechanism in such a way that it makes
+your project unusable unless the checkout is done with a
+specific filter in effect.
+
+
+Interaction between checkin/checkout attributes
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+In the check-in codepath, the worktree file is first converted
+with `filter` driver (if specified and corresponding driver
+defined), then the result is processed with `ident` (if
+specified), and then finally with `crlf` (again, if specified
+and applicable).
+
+In the check-out codepath, the blob content is first converted
+with `crlf`, and then `ident` and fed to `filter`.
+
+
+Generating diff text
+~~~~~~~~~~~~~~~~~~~~
+
+The attribute `diff` affects if `git diff` generates textual
+patch for the path or just says `Binary files differ`.
+
+Set::
+
+       A path to which the `diff` attribute is set is treated
+       as text, even when they contain byte values that
+       normally never appear in text files, such as NUL.
+
+Unset::
+
+       A path to which the `diff` attribute is unset will
+       generate `Binary files differ`.
+
+Unspecified::
+
+       A path to which the `diff` attribute is unspecified
+       first gets its contents inspected, and if it looks like
+       text, it is treated as text.  Otherwise it would
+       generate `Binary files differ`.
+
+String::
+
+       Diff is shown using the specified custom diff driver.
+       The driver program is given its input using the same
+       calling convention as used for GIT_EXTERNAL_DIFF
+       program.
+
+
+Defining a custom diff driver
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+The definition of a diff driver is done in `gitconfig`, not
+`gitattributes` file, so strictly speaking this manual page is a
+wrong place to talk about it.  However...
+
+To define a custom diff driver `jcdiff`, add a section to your
+`$GIT_DIR/config` file (or `$HOME/.gitconfig` file) like this:
+
+----------------------------------------------------------------
+[diff "jcdiff"]
+       command = j-c-diff
+----------------------------------------------------------------
+
+When git needs to show you a diff for the path with `diff`
+attribute set to `jcdiff`, it calls the command you specified
+with the above configuration, i.e. `j-c-diff`, with 7
+parameters, just like `GIT_EXTERNAL_DIFF` program is called.
+See gitlink:git[7] for details.
+
+
+Performing a three-way merge
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The attribute `merge` affects how three versions of a file is
+merged when a file-level merge is necessary during `git merge`,
+and other programs such as `git revert` and `git cherry-pick`.
+
+Set::
+
+       Built-in 3-way merge driver is used to merge the
+       contents in a way similar to `merge` command of `RCS`
+       suite.  This is suitable for ordinary text files.
+
+Unset::
+
+       Take the version from the current branch as the
+       tentative merge result, and declare that the merge has
+       conflicts.  This is suitable for binary files that does
+       not have a well-defined merge semantics.
+
+Unspecified::
+
+       By default, this uses the same built-in 3-way merge
+       driver as is the case the `merge` attribute is set.
+       However, `merge.default` configuration variable can name
+       different merge driver to be used for paths to which the
+       `merge` attribute is unspecified.
+
+String::
+
+       3-way merge is performed using the specified custom
+       merge driver.  The built-in 3-way merge driver can be
+       explicitly specified by asking for "text" driver; the
+       built-in "take the current branch" driver can be
+       requested with "binary".
+
+
+Defining a custom merge driver
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+The definition of a merge driver is done in `gitconfig` not
+`gitattributes` file, so strictly speaking this manual page is a
+wrong place to talk about it.  However...
+
+To define a custom merge driver `filfre`, add a section to your
+`$GIT_DIR/config` file (or `$HOME/.gitconfig` file) like this:
+
+----------------------------------------------------------------
+[merge "filfre"]
+       name = feel-free merge driver
+       driver = filfre %O %A %B
+       recursive = binary
+----------------------------------------------------------------
+
+The `merge.*.name` variable gives the driver a human-readable
+name.
+
+The `merge.*.driver` variable's value is used to construct a
+command to run to merge ancestor's version (`%O`), current
+version (`%A`) and the other branches' version (`%B`).  These
+three tokens are replaced with the names of temporary files that
+hold the contents of these versions when the command line is
+built.
+
+The merge driver is expected to leave the result of the merge in
+the file named with `%A` by overwriting it, and exit with zero
+status if it managed to merge them cleanly, or non-zero if there
+were conflicts.
+
+The `merge.*.recursive` variable specifies what other merge
+driver to use when the merge driver is called for an internal
+merge between common ancestors, when there are more than one.
+When left unspecified, the driver itself is used for both
+internal merge and the final merge.
+
+
+EXAMPLE
+-------
+
+If you have these three `gitattributes` file:
+
+----------------------------------------------------------------
+(in $GIT_DIR/info/attributes)
+
+a*     foo !bar -baz
+
+(in .gitattributes)
+abc    foo bar baz
+
+(in t/.gitattributes)
+ab*    merge=filfre
+abc    -foo -bar
+*.c    frotz
+----------------------------------------------------------------
+
+the attributes given to path `t/abc` are computed as follows:
+
+1. By examining `t/.gitattributes` (which is in the same
+   diretory as the path in question), git finds that the first
+   line matches.  `merge` attribute is set.  It also finds that
+   the second line matches, and attributes `foo` and `bar`
+   are unset.
+
+2. Then it examines `.gitattributes` (which is in the parent
+   directory), and finds that the first line matches, but
+   `t/.gitattributes` file already decided how `merge`, `foo`
+   and `bar` attributes should be given to this path, so it
+   leaves `foo` and `bar` unset.  Attribute `baz` is set.
+
+3. Finally it examines `$GIT_DIR/info/gitattributes`.  This file
+   is used to override the in-tree settings.  The first line is
+   a match, and `foo` is set, `bar` is reverted to unspecified
+   state, and `baz` is unset.
+
+As the result, the attributes assignement to `t/abc` becomes:
+
+----------------------------------------------------------------
+foo    set to true
+bar    unspecified
+baz    set to false
+merge  set to string value "filfre"
+frotz  unspecified
+----------------------------------------------------------------
+
+
+GIT
+---
+Part of the gitlink:git[7] suite
index 80ba6709ad8759ec6f3f8ed63b804ff6a901c111..aabb9750fddbc28091c46780ccf7b835b62b04c7 100644 (file)
@@ -115,8 +115,9 @@ If the hook exits with non-zero status, none of the refs will be
 updated. If the hook exits with zero, updating of individual refs can
 still be prevented by the <<update,'update'>> hook.
 
-If you want to report something to the `git-send-pack` on the other end,
-you can simply `echo` your messages.
+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.
 
 [[update]]
 update
@@ -153,9 +154,9 @@ Another use suggested on the mailing list is to use this hook to
 implement access control which is finer grained than the one
 based on filesystem group.
 
-The standard output of this hook is sent to `stderr`, so if you
-want to report something to the `git-send-pack` on the other end,
-you can simply `echo` your messages.
+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.
 
 The default 'update' hook, when enabled--and with
 `hooks.allowunannotated` config option turned on--prevents
@@ -171,17 +172,20 @@ It executes on the remote repository once after all the refs have
 been updated.
 
 This hook executes once for the receive operation.  It takes no
-arguments, but gets the same information as the `pre-receive`
+arguments, but gets the same information as the
+<<pre-receive,'pre-receive'>>
 hook does on its standard input.
 
 This hook does not affect the outcome of `git-receive-pack`, as it
 is called after the real work is done.
 
-This supersedes the [[post-update]] hook in that it actually get's
-both old and new values of all the refs.
+This supersedes the <<post-update,'post-update'>> hook in that it get's
+both old and new values of all the refs in addition to their
+names.
 
-If you want to report something to the `git-send-pack` on the
-other end, you can simply `echo` your messages.
+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.
 
 The default 'post-receive' hook is empty, but there is
 a sample script `post-receive-email` provided in the `contrib/hooks`
@@ -205,12 +209,10 @@ the outcome of `git-receive-pack`.
 
 The 'post-update' hook can tell what are the heads that were pushed,
 but it does not know what their original and updated values are,
-so it is a poor place to do log old..new.
-
-In general, `post-receive` hook is preferred when the hook needs
-to decide its acion on the status of the entire set of refs
-being updated, as this hook is called once per ref, with
-information only on a single ref at a time.
+so it is a poor place to do log old..new. The
+<<post-receive,'post-receive'>> hook does get both original and
+updated values of the refs. You might consider it instead if you need
+them.
 
 When enabled, the default 'post-update' hook runs
 `git-update-server-info` to keep the information used by dumb
@@ -219,4 +221,5 @@ a git repository that is accessible via HTTP, you should
 probably enable this hook.
 
 Both standard output and standard error output are forwarded to
-`git-send-pack` on the other end.
+`git-send-pack` on the other end, so you can simply `echo` messages
+for the user.
index 2fe6c319675926afe1609ad7c221dceb42c82310..d922e8e86c173491fc492d3060bf92f4ee7b6c2d 100644 (file)
@@ -1,31 +1,32 @@
---pretty[='<format>']::
-
-        Pretty-prints the details of a commit.  `--pretty`
-       without an explicit `=<format>` defaults to 'medium'.
-       If the commit is a merge, and if the pretty-format
-        is not 'oneline', 'email' or 'raw', an additional line is
-        inserted before the 'Author:' line.  This line begins with
-        "Merge: " and the sha1s of ancestral commits are printed,
-        separated by spaces.  Note that the listed commits may not
-        necessarily be the list of the *direct* parent commits if you
-        have limited your view of history: for example, if you are
-        only interested in changes related to a certain directory or
-        file.  Here are some additional details for each format:
-
-        * 'oneline'
+PRETTY FORMATS
+--------------
+
+If the commit is a merge, and if the pretty-format
+is not 'oneline', 'email' or 'raw', an additional line is
+inserted before the 'Author:' line.  This line begins with
+"Merge: " and the sha1s of ancestral commits are printed,
+separated by spaces.  Note that the listed commits may not
+necessarily be the list of the *direct* parent commits if you
+have limited your view of history: for example, if you are
+only interested in changes related to a certain directory or
+file.
+
+Here are some additional details for each format:
+
+* 'oneline'
 
          <sha1> <title line>
 +
 This is designed to be as compact as possible.
 
-        * 'short'
+* 'short'
 
          commit <sha1>
          Author: <author>
 
              <title line>
 
-        * 'medium'
+* 'medium'
 
          commit <sha1>
          Author: <author>
@@ -35,7 +36,7 @@ This is designed to be as compact as possible.
 
              <full commit message>
 
-        * 'full'
+* 'full'
 
          commit <sha1>
          Author: <author>
@@ -45,7 +46,7 @@ This is designed to be as compact as possible.
 
              <full commit message>
 
-        * 'fuller'
+* 'fuller'
 
          commit <sha1>
          Author: <author>
@@ -57,18 +58,16 @@ This is designed to be as compact as possible.
 
               <full commit message>
 
-
-        * 'email'
+* 'email'
 
          From <sha1> <date>
          From: <author>
          Date: <date & time>
          Subject: [PATCH] <title line>
 
-         full commit message>
-
+         <full commit message>
 
-       * 'raw'
+* 'raw'
 +
 The 'raw' format shows the entire commit exactly as
 stored in the commit object.  Notably, the SHA1s are
@@ -77,19 +76,22 @@ displayed in full, regardless of whether --abbrev or
 true parent commits, without taking grafts nor history
 simplification into account.
 
-       * 'format:'
+* 'format:'
 +
 The 'format:' format allows you to specify which information
 you want to show. It works a little bit like printf format,
 with the notable exception that you get a newline with '%n'
 instead of '\n'.
-
-E.g, 'format:"The author of %h was %an, %ar%nThe title was >>%s<<"'
++
+E.g, 'format:"The author of %h was %an, %ar%nThe title was >>%s<<%n"'
 would show something like this:
-
++
+-------
 The author of fe6e0ee was Junio C Hamano, 23 hours ago
 The title was >>t4119: test autocomputing -p<n> for traditional diff input.<<
 
+--------
++
 The placeholders are:
 
 - '%H': commit hash
@@ -117,13 +119,6 @@ The placeholders are:
 - '%Cgreen': switch color to green
 - '%Cblue': switch color to blue
 - '%Creset': reset color
+- '%m': left, right or boundary mark
 - '%n': newline
 
-
---encoding[=<encoding>]::
-       The commit objects record the encoding used for the log message
-       in their encoding header; this option can be used to tell the
-       command to re-code the commit log message in the encoding
-       preferred by the user.  For non plumbing commands this
-       defaults to UTF-8.
-
diff --git a/Documentation/pretty-options.txt b/Documentation/pretty-options.txt
new file mode 100644 (file)
index 0000000..7d515be
--- /dev/null
@@ -0,0 +1,14 @@
+--pretty[='<format>']::
+
+       Pretty print the contents of the commit logs in a given format,
+       where '<format>' can be one of 'oneline', 'short', 'medium',
+       'full', 'fuller', 'email', 'raw' and 'format:<string>'.
+       When left out the format default to 'medium'.
+
+--encoding[=<encoding>]::
+       The commit objects record the encoding used for the log message
+       in their encoding header; this option can be used to tell the
+       command to re-code the commit log message in the encoding
+       preferred by the user.  For non plumbing commands this
+       defaults to UTF-8.
+
index 534ece464bc5be0e765e0b89a7718be1439463fe..52247aa7134345e94b3c2cc131d33224073f6eeb 100644 (file)
@@ -1864,7 +1864,7 @@ $ chmod a+x hooks/post-update
 
 (For an explanation of the last two lines, see
 gitlink:git-update-server-info[1], and the documentation
-link:hooks.txt[Hooks used by git].)
+link:hooks.html[Hooks used by git].)
 
 Advertise the url of proj.git.  Anybody else should then be able to
 clone or pull from that url, for example with a commandline like:
@@ -1939,7 +1939,7 @@ Setting up a shared repository
 Another way to collaborate is by using a model similar to that
 commonly used in CVS, where several developers with special rights
 all push to and pull from a single shared repository.  See
-link:cvs-migration.txt[git for CVS users] for instructions on how to
+link:cvs-migration.html[git for CVS users] for instructions on how to
 set this up.
 
 However, while there is nothing wrong with git's support for shared
index 582f423e9ebe511da2b2b01193e8d335dfd05599..bd4a0443c4a7307dc1a2e756f3daa4e62c55a4bb 100755 (executable)
@@ -1,7 +1,7 @@
 #!/bin/sh
 
 GVF=GIT-VERSION-FILE
-DEF_VER=v1.5.1.6.GIT
+DEF_VER=v1.5.2-rc3.GIT
 
 LF='
 '
index b61c5d4cd25395207096c0bc415847e43379eef1..29243c6e8b49958ddcb08df0eb4223b14fd3e19f 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -110,6 +110,18 @@ all::
 # Define NO_PERL_MAKEMAKER if you cannot use Makefiles generated by perl's
 # MakeMaker (e.g. using ActiveState under Cygwin).
 #
+# Define WITH_P4IMPORT to build and install Python git-p4import script.
+#
+# Define NO_TCLTK if you do not want Tcl/Tk GUI.
+#
+# The TCL_PATH variable governs the location of the Tcl interpreter
+# used to optimize git-gui for your system.  Only used if NO_TCLTK
+# is not set.  Defaults to the bare 'tclsh'.
+#
+# The TCLTK_PATH variable governs the location of the Tcl/Tk interpreter.
+# If not set it defaults to the bare 'wish'. If it is set to the empty
+# string then NO_TCLTK will be forced (this is used by configure script).
+#
 
 GIT-VERSION-FILE: .FORCE-GIT-VERSION-FILE
        @$(SHELL_PATH) ./GIT-VERSION-GEN
@@ -132,7 +144,8 @@ STRIP ?= strip
 prefix = $(HOME)
 bindir = $(prefix)/bin
 gitexecdir = $(bindir)
-template_dir = $(prefix)/share/git-core/templates/
+sharedir = $(prefix)/share/
+template_dir = $(sharedir)/git-core/templates/
 ifeq ($(prefix),/usr)
 sysconfdir = /etc
 else
@@ -157,13 +170,17 @@ GITWEB_FAVICON = git-favicon.png
 GITWEB_SITE_HEADER =
 GITWEB_SITE_FOOTER =
 
-export prefix bindir gitexecdir template_dir sysconfdir
+export prefix bindir gitexecdir sharedir template_dir sysconfdir
 
 CC = gcc
 AR = ar
 TAR = tar
 INSTALL = install
 RPMBUILD = rpmbuild
+TCL_PATH = tclsh
+TCLTK_PATH = wish
+
+export TCL_PATH TCLTK_PATH
 
 # sparse is architecture-neutral, which means that we need to tell it
 # explicitly what architecture to check for. Fix this up for yours..
@@ -201,9 +218,20 @@ SCRIPT_PERL = \
        git-svnimport.perl git-cvsexportcommit.perl \
        git-send-email.perl git-svn.perl
 
+SCRIPT_PYTHON = \
+       git-p4import.py
+
+ifdef WITH_P4IMPORT
 SCRIPTS = $(patsubst %.sh,%,$(SCRIPT_SH)) \
          $(patsubst %.perl,%,$(SCRIPT_PERL)) \
+         $(patsubst %.py,%,$(SCRIPT_PYTHON)) \
          git-status git-instaweb
+else
+SCRIPTS = $(patsubst %.sh,%,$(SCRIPT_SH)) \
+         $(patsubst %.perl,%,$(SCRIPT_PERL)) \
+         git-status git-instaweb
+endif
+
 
 # ... and all the rest that could be moved out of bindir to gitexecdir
 PROGRAMS = \
@@ -236,6 +264,14 @@ BUILT_INS = \
 # what 'all' will build and 'install' will install, in gitexecdir
 ALL_PROGRAMS = $(PROGRAMS) $(SCRIPTS)
 
+ALL_PROGRAMS += git-merge-subtree$X
+
+# what 'all' will build but not install in gitexecdir
+OTHER_PROGRAMS = git$X gitweb/gitweb.cgi
+ifndef NO_TCLTK
+OTHER_PROGRAMS += gitk-wish
+endif
+
 # Backward compatibility -- to be removed after 1.0
 PROGRAMS += git-ssh-pull$X git-ssh-push$X
 
@@ -246,6 +282,9 @@ endif
 ifndef PERL_PATH
        PERL_PATH = /usr/bin/perl
 endif
+ifndef PYTHON_PATH
+       PYTHON_PATH = /usr/local/bin/python
+endif
 
 export PERL_PATH
 
@@ -257,7 +296,7 @@ LIB_H = \
        diff.h object.h pack.h pkt-line.h quote.h refs.h list-objects.h sideband.h \
        run-command.h strbuf.h tag.h tree.h git-compat-util.h revision.h \
        tree-walk.h log-tree.h dir.h path-list.h unpack-trees.h builtin.h \
-       utf8.h reflog-walk.h
+       utf8.h reflog-walk.h patch-ids.h attr.h decorate.h progress.h mailmap.h
 
 DIFF_OBJS = \
        diff.o diff-lib.o diffcore-break.o diffcore-order.o \
@@ -269,16 +308,17 @@ LIB_OBJS = \
        date.o diff-delta.o entry.o exec_cmd.o ident.o \
        interpolate.o \
        lockfile.o \
-       object.o pack-check.o patch-delta.o path.o pkt-line.o sideband.o \
-       reachable.o reflog-walk.o \
+       patch-ids.o \
+       object.o pack-check.o pack-write.o patch-delta.o path.o pkt-line.o \
+       sideband.o reachable.o reflog-walk.o \
        quote.o read-cache.o refs.o run-command.o dir.o object-refs.o \
        server-info.o setup.o sha1_file.o sha1_name.o strbuf.o \
        tag.o tree.o usage.o config.o environment.o ctype.o copy.o \
        revision.o pager.o tree-walk.o xdiff-interface.o \
-       write_or_die.o trace.o list-objects.o grep.o \
+       write_or_die.o trace.o list-objects.o grep.o match-trees.o \
        alloc.o merge-file.o path-list.o help.o unpack-trees.o $(DIFF_OBJS) \
        color.o wt-status.o archive-zip.o archive-tar.o shallow.o utf8.o \
-       convert.o
+       convert.o attr.o decorate.o progress.o mailmap.o symlinks.o
 
 BUILTIN_OBJS = \
        builtin-add.o \
@@ -289,6 +329,7 @@ BUILTIN_OBJS = \
        builtin-branch.o \
        builtin-bundle.o \
        builtin-cat-file.o \
+       builtin-check-attr.o \
        builtin-checkout-index.o \
        builtin-check-ref-format.o \
        builtin-commit-tree.o \
@@ -614,7 +655,11 @@ ifdef NO_PERL_MAKEMAKER
        export NO_PERL_MAKEMAKER
 endif
 
-QUIET_SUBDIR0  = $(MAKE) -C # space to separate -C and subdir
+ifeq ($(TCLTK_PATH),)
+NO_TCLTK=NoThanks
+endif
+
+QUIET_SUBDIR0  = +$(MAKE) -C # space to separate -C and subdir
 QUIET_SUBDIR1  =
 
 ifneq ($(findstring $(MAKEFLAGS),w),w)
@@ -630,7 +675,7 @@ ifndef V
        QUIET_LINK     = @echo '   ' LINK $@;
        QUIET_BUILT_IN = @echo '   ' BUILTIN $@;
        QUIET_GEN      = @echo '   ' GEN $@;
-       QUIET_SUBDIR0  = @subdir=
+       QUIET_SUBDIR0  = +@subdir=
        QUIET_SUBDIR1  = ;$(NO_SUBDIR) echo '   ' SUBDIR $$subdir; \
                         $(MAKE) $(PRINT_DIR) -C $$subdir
        export V
@@ -652,6 +697,8 @@ 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)
 
@@ -662,24 +709,32 @@ LIB_OBJS += $(COMPAT_OBJS)
 ALL_CFLAGS += $(BASIC_CFLAGS)
 ALL_LDFLAGS += $(BASIC_LDFLAGS)
 
-export prefix gitexecdir TAR INSTALL DESTDIR SHELL_PATH template_dir
+export TAR INSTALL DESTDIR SHELL_PATH
 
 
 ### Build rules
 
-all:: $(ALL_PROGRAMS) $(BUILT_INS) git$X gitk gitweb/gitweb.cgi
+all:: $(ALL_PROGRAMS) $(BUILT_INS) $(OTHER_PROGRAMS)
 ifneq (,$X)
        $(foreach p,$(patsubst %$X,%,$(filter %$X,$(ALL_PROGRAMS) $(BUILT_INS) git$X)), rm -f '$p';)
 endif
 
 all::
+ifndef NO_TCLTK
        $(QUIET_SUBDIR0)git-gui $(QUIET_SUBDIR1) all
+endif
        $(QUIET_SUBDIR0)perl $(QUIET_SUBDIR1) PERL_PATH='$(PERL_PATH_SQ)' prefix='$(prefix_SQ)' all
        $(QUIET_SUBDIR0)templates $(QUIET_SUBDIR1)
 
 strip: $(PROGRAMS) git$X
        $(STRIP) $(STRIP_OPTS) $(PROGRAMS) git$X
 
+gitk-wish: gitk GIT-GUI-VARS
+       $(QUIET_GEN)rm -f $@ $@+ && \
+       sed -e '1,3s|^exec .* "$$0"|exec $(subst |,'\|',$(TCLTK_PATH_SQ)) "$$0"|' <gitk >$@+ && \
+       chmod +x $@+ && \
+       mv -f $@+ $@
+
 git$X: git.c common-cmds.h $(BUILTIN_OBJS) $(GITLIBS) GIT-CFLAGS
        $(QUIET_LINK)$(CC) -DGIT_VERSION='"$(GIT_VERSION)"' \
                $(ALL_CFLAGS) -o $@ $(filter %.c,$^) \
@@ -687,6 +742,9 @@ git$X: git.c common-cmds.h $(BUILTIN_OBJS) $(GITLIBS) GIT-CFLAGS
 
 help.o: common-cmds.h
 
+git-merge-subtree$X: git-merge-recursive$X
+       $(QUIET_BUILT_IN)rm -f $@ && ln git-merge-recursive$X $@
+
 $(BUILT_INS): git$X
        $(QUIET_BUILT_IN)rm -f $@ && ln git$X $@
 
@@ -705,6 +763,15 @@ $(patsubst %.sh,%,$(SCRIPT_SH)) : % : %.sh
 
 $(patsubst %.perl,%,$(SCRIPT_PERL)): perl/perl.mak
 
+$(patsubst %.py,%,$(SCRIPT_PYTHON)) : % : %.py
+       rm -f $@ $@+
+       sed -e '1s|#!.*/python|#!$(PYTHON_PATH_SQ)|' \
+           -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \
+           -e 's/@@NO_CURL@@/$(NO_CURL)/g' \
+           $@.py >$@+
+       chmod +x $@+
+       mv $@+ $@
+
 perl/perl.mak: GIT-CFLAGS
        $(QUIET_SUBDIR0)perl $(QUIET_SUBDIR1) PERL_PATH='$(PERL_PATH_SQ)' prefix='$(prefix_SQ)' $(@F)
 
@@ -858,15 +925,33 @@ GIT-CFLAGS: .FORCE-GIT-CFLAGS
                echo "$$FLAGS" >GIT-CFLAGS; \
             fi
 
+### Detect Tck/Tk interpreter path changes
+ifndef NO_TCLTK
+TRACK_VARS = $(subst ','\'',-DTCLTK_PATH='$(TCLTK_PATH_SQ)')
+
+GIT-GUI-VARS: .FORCE-GIT-GUI-VARS
+       @VARS='$(TRACK_VARS)'; \
+           if test x"$$VARS" != x"`cat $@ 2>/dev/null`" ; then \
+               echo 1>&2 "    * new Tcl/Tk interpreter location"; \
+               echo "$$VARS" >$@; \
+            fi
+
+.PHONY: .FORCE-GIT-GUI-VARS
+endif
+
 ### Testing rules
 
+TEST_PROGRAMS = test-chmtime$X test-genrandom$X
+
+all:: $(TEST_PROGRAMS)
+
 # GNU make supports exporting all variables by "export" without parameters.
 # However, the environment gets quite big, and some programs have problems
 # with that.
 
 export NO_SVN_TESTS
 
-test: all test-chmtime$X
+test: all
        $(MAKE) -C t/ all
 
 test-date$X: test-date.c date.o ctype.o
@@ -881,9 +966,15 @@ test-dump-cache-tree$X: dump-cache-tree.o $(GITLIBS)
 test-sha1$X: test-sha1.o $(GITLIBS)
        $(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(LIBS)
 
+test-match-trees$X: test-match-trees.o $(GITLIBS)
+       $(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(LIBS)
+
 test-chmtime$X: test-chmtime.c
        $(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $<
 
+test-genrandom$X: test-genrandom.c
+       $(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $<
+
 check-sha1:: test-sha1$X
        ./test-sha1.sh
 
@@ -898,10 +989,13 @@ install: all
        $(INSTALL) -d -m755 '$(DESTDIR_SQ)$(bindir_SQ)'
        $(INSTALL) -d -m755 '$(DESTDIR_SQ)$(gitexecdir_SQ)'
        $(INSTALL) $(ALL_PROGRAMS) '$(DESTDIR_SQ)$(gitexecdir_SQ)'
-       $(INSTALL) git$X gitk '$(DESTDIR_SQ)$(bindir_SQ)'
+       $(INSTALL) git$X '$(DESTDIR_SQ)$(bindir_SQ)'
        $(MAKE) -C templates DESTDIR='$(DESTDIR_SQ)' install
        $(MAKE) -C perl prefix='$(prefix_SQ)' install
+ifndef NO_TCLTK
+       $(INSTALL) gitk-wish '$(DESTDIR_SQ)$(bindir_SQ)'/gitk
        $(MAKE) -C git-gui install
+endif
        if test 'z$(bindir_SQ)' != 'z$(gitexecdir_SQ)'; \
        then \
                ln -f '$(DESTDIR_SQ)$(bindir_SQ)/git$X' \
@@ -956,9 +1050,10 @@ dist-doc:
        gzip -n -9 -f $(htmldocs).tar
        :
        rm -fr .doc-tmp-dir
-       mkdir .doc-tmp-dir .doc-tmp-dir/man1 .doc-tmp-dir/man7
+       mkdir -p .doc-tmp-dir/man1 .doc-tmp-dir/man5 .doc-tmp-dir/man7
        $(MAKE) -C Documentation DESTDIR=./ \
                man1dir=../.doc-tmp-dir/man1 \
+               man5dir=../.doc-tmp-dir/man5 \
                man7dir=../.doc-tmp-dir/man7 \
                install
        cd .doc-tmp-dir && $(TAR) cf ../$(manpages).tar .
@@ -969,7 +1064,7 @@ dist-doc:
 
 clean:
        rm -f *.o mozilla-sha1/*.o arm/*.o ppc/*.o compat/*.o xdiff/*.o \
-               test-chmtime$X $(LIB_FILE) $(XDIFF_LIB)
+               test-chmtime$X test-genrandom$X $(LIB_FILE) $(XDIFF_LIB)
        rm -f $(ALL_PROGRAMS) $(BUILT_INS) git$X
        rm -f *.spec *.pyc *.pyo */*.pyc */*.pyo common-cmds.h TAGS tags
        rm -rf autom4te.cache
@@ -980,10 +1075,13 @@ clean:
        rm -f gitweb/gitweb.cgi
        $(MAKE) -C Documentation/ clean
        $(MAKE) -C perl clean
-       $(MAKE) -C git-gui clean
        $(MAKE) -C templates/ clean
        $(MAKE) -C t/ clean
-       rm -f GIT-VERSION-FILE GIT-CFLAGS
+ifndef NO_TCLTK
+       rm -f gitk-wish
+       $(MAKE) -C git-gui clean
+endif
+       rm -f GIT-VERSION-FILE GIT-CFLAGS GIT-GUI-VARS
 
 .PHONY: all install clean strip
 .PHONY: .FORCE-GIT-VERSION-FILE TAGS tags .FORCE-GIT-CFLAGS
index e5de2fa02b2a5eab1252868dd5719206d2a387fa..c543b1d1eedf821fe1e8c31d902a719bfc6ffb20 120000 (symlink)
--- a/RelNotes
+++ b/RelNotes
@@ -1 +1 @@
-Documentation/RelNotes-1.5.1.6.txt
\ No newline at end of file
+Documentation/RelNotes-1.5.2.txt
\ No newline at end of file
diff --git a/alloc.c b/alloc.c
index 460db192d5a7de4073eb89972871720dd1c8fe1e..216c23a6f854c614d38c743cd7687a37f304161b 100644 (file)
--- a/alloc.c
+++ b/alloc.c
 
 #define BLOCKING 1024
 
-#define DEFINE_ALLOCATOR(name)                                 \
+#define DEFINE_ALLOCATOR(name, type)                           \
 static unsigned int name##_allocs;                             \
-struct name *alloc_##name##_node(void)                         \
+void *alloc_##name##_node(void)                                        \
 {                                                              \
        static int nr;                                          \
-       static struct name *block;                              \
+       static type *block;                                     \
+       void *ret;                                              \
                                                                \
        if (!nr) {                                              \
                nr = BLOCKING;                                  \
-               block = xcalloc(BLOCKING, sizeof(struct name)); \
+               block = xmalloc(BLOCKING * sizeof(type));       \
        }                                                       \
        nr--;                                                   \
        name##_allocs++;                                        \
-       return block++;                                         \
+       ret = block++;                                          \
+       memset(ret, 0, sizeof(type));                           \
+       return ret;                                             \
 }
 
-DEFINE_ALLOCATOR(blob)
-DEFINE_ALLOCATOR(tree)
-DEFINE_ALLOCATOR(commit)
-DEFINE_ALLOCATOR(tag)
+union any_object {
+       struct object object;
+       struct blob blob;
+       struct tree tree;
+       struct commit commit;
+       struct tag tag;
+};
+
+DEFINE_ALLOCATOR(blob, struct blob)
+DEFINE_ALLOCATOR(tree, struct tree)
+DEFINE_ALLOCATOR(commit, struct commit)
+DEFINE_ALLOCATOR(tag, struct tag)
+DEFINE_ALLOCATOR(object, union any_object)
 
 #ifdef NO_C99_FORMAT
 #define SZ_FMT "%u"
index d9c30d33dc286fa2926c80f647304687ba13295b..33e76576f149d89922deda1de5548be7bc42d0eb 100644 (file)
@@ -82,12 +82,13 @@ static void strbuf_append_string(struct strbuf *sb, const char *s)
 {
        int slen = strlen(s);
        int total = sb->len + slen;
-       if (total > sb->alloc) {
-               sb->buf = xrealloc(sb->buf, total);
-               sb->alloc = total;
+       if (total + 1 > sb->alloc) {
+               sb->buf = xrealloc(sb->buf, total + 1);
+               sb->alloc = total + 1;
        }
        memcpy(sb->buf + sb->len, s, slen);
        sb->len = total;
+       sb->buf[total] = '\0';
 }
 
 /*
@@ -166,7 +167,7 @@ static void write_entry(const unsigned char *sha1, struct strbuf *path,
        } else {
                if (verbose)
                        fprintf(stderr, "%.*s\n", path->len, path->buf);
-               if (S_ISDIR(mode)) {
+               if (S_ISDIR(mode) || S_ISDIRLNK(mode)) {
                        *header.typeflag = TYPEFLAG_DIR;
                        mode = (mode | 0777) & ~tar_umask;
                } else if (S_ISLNK(mode)) {
@@ -270,20 +271,21 @@ static int write_tar_entry(const unsigned char *sha1,
                path.alloc = PATH_MAX;
                path.len = path.eof = 0;
        }
-       if (path.alloc < baselen + filenamelen) {
+       if (path.alloc < baselen + filenamelen + 1) {
                free(path.buf);
-               path.buf = xmalloc(baselen + filenamelen);
-               path.alloc = baselen + filenamelen;
+               path.buf = xmalloc(baselen + filenamelen + 1);
+               path.alloc = baselen + filenamelen + 1;
        }
        memcpy(path.buf, base, baselen);
        memcpy(path.buf + baselen, filename, filenamelen);
        path.len = baselen + filenamelen;
-       if (S_ISDIR(mode)) {
+       path.buf[path.len] = '\0';
+       if (S_ISDIR(mode) || S_ISDIRLNK(mode)) {
                strbuf_append_string(&path, "/");
                buffer = NULL;
                size = 0;
        } else {
-               buffer = read_sha1_file(sha1, &type, &size);
+               buffer = convert_sha1_file(path.buf, sha1, mode, &type, &size);
                if (!buffer)
                        die("cannot read %s", sha1_to_hex(sha1));
        }
index 7c4984886f14aaba5d6a71c3b9213934b45d713a..3cbf6bb8ac4045803140ca5019126015e817155e 100644 (file)
@@ -182,10 +182,10 @@ static int write_zip_entry(const unsigned char *sha1,
                goto out;
        }
 
-       if (S_ISDIR(mode)) {
+       if (S_ISDIR(mode) || S_ISDIRLNK(mode)) {
                method = 0;
                attr2 = 16;
-               result = READ_TREE_RECURSIVE;
+               result = (S_ISDIR(mode) ? READ_TREE_RECURSIVE : 0);
                out = NULL;
                uncompressed_size = 0;
                compressed_size = 0;
@@ -195,7 +195,7 @@ static int write_zip_entry(const unsigned char *sha1,
                if (S_ISREG(mode) && zlib_compression_level != 0)
                        method = 8;
                result = 0;
-               buffer = read_sha1_file(sha1, &type, &size);
+               buffer = convert_sha1_file(path, sha1, mode, &type, &size);
                if (!buffer)
                        die("cannot read %s", sha1_to_hex(sha1));
                crc = crc32(crc, buffer, size);
diff --git a/attr.c b/attr.c
new file mode 100644 (file)
index 0000000..a071254
--- /dev/null
+++ b/attr.c
@@ -0,0 +1,565 @@
+#include "cache.h"
+#include "attr.h"
+
+const char git_attr__true[] = "(builtin)true";
+const char git_attr__false[] = "\0(builtin)false";
+static const char git_attr__unknown[] = "(builtin)unknown";
+#define ATTR__TRUE git_attr__true
+#define ATTR__FALSE git_attr__false
+#define ATTR__UNSET NULL
+#define ATTR__UNKNOWN git_attr__unknown
+
+/*
+ * The basic design decision here is that we are not going to have
+ * insanely large number of attributes.
+ *
+ * This is a randomly chosen prime.
+ */
+#define HASHSIZE 257
+
+#ifndef DEBUG_ATTR
+#define DEBUG_ATTR 0
+#endif
+
+struct git_attr {
+       struct git_attr *next;
+       unsigned h;
+       int attr_nr;
+       char name[FLEX_ARRAY];
+};
+static int attr_nr;
+
+static struct git_attr_check *check_all_attr;
+static struct git_attr *(git_attr_hash[HASHSIZE]);
+
+static unsigned hash_name(const char *name, int namelen)
+{
+       unsigned val = 0;
+       unsigned char c;
+
+       while (namelen--) {
+               c = *name++;
+               val = ((val << 7) | (val >> 22)) ^ c;
+       }
+       return val;
+}
+
+static int invalid_attr_name(const char *name, int namelen)
+{
+       /*
+        * Attribute name cannot begin with '-' and from
+        * [-A-Za-z0-9_.].  We'd specifically exclude '=' for now,
+        * as we might later want to allow non-binary value for
+        * attributes, e.g. "*.svg      merge=special-merge-program-for-svg"
+        */
+       if (*name == '-')
+               return -1;
+       while (namelen--) {
+               char ch = *name++;
+               if (! (ch == '-' || ch == '.' || ch == '_' ||
+                      ('0' <= ch && ch <= '9') ||
+                      ('a' <= ch && ch <= 'z') ||
+                      ('A' <= ch && ch <= 'Z')) )
+                       return -1;
+       }
+       return 0;
+}
+
+struct git_attr *git_attr(const char *name, int len)
+{
+       unsigned hval = hash_name(name, len);
+       unsigned pos = hval % HASHSIZE;
+       struct git_attr *a;
+
+       for (a = git_attr_hash[pos]; a; a = a->next) {
+               if (a->h == hval &&
+                   !memcmp(a->name, name, len) && !a->name[len])
+                       return a;
+       }
+
+       if (invalid_attr_name(name, len))
+               return NULL;
+
+       a = xmalloc(sizeof(*a) + len + 1);
+       memcpy(a->name, name, len);
+       a->name[len] = 0;
+       a->h = hval;
+       a->next = git_attr_hash[pos];
+       a->attr_nr = attr_nr++;
+       git_attr_hash[pos] = a;
+
+       check_all_attr = xrealloc(check_all_attr,
+                                 sizeof(*check_all_attr) * attr_nr);
+       check_all_attr[a->attr_nr].attr = a;
+       check_all_attr[a->attr_nr].value = ATTR__UNKNOWN;
+       return a;
+}
+
+/*
+ * .gitattributes file is one line per record, each of which is
+ *
+ * (1) glob pattern.
+ * (2) whitespace
+ * (3) whitespace separated list of attribute names, each of which
+ *     could be prefixed with '-' to mean "set to false", '!' to mean
+ *     "unset".
+ */
+
+/* What does a matched pattern decide? */
+struct attr_state {
+       struct git_attr *attr;
+       const char *setto;
+};
+
+struct match_attr {
+       union {
+               char *pattern;
+               struct git_attr *attr;
+       } u;
+       char is_macro;
+       unsigned num_attr;
+       struct attr_state state[FLEX_ARRAY];
+};
+
+static const char blank[] = " \t\r\n";
+
+static const char *parse_attr(const char *src, int lineno, const char *cp,
+                             int *num_attr, struct match_attr *res)
+{
+       const char *ep, *equals;
+       int len;
+
+       ep = cp + strcspn(cp, blank);
+       equals = strchr(cp, '=');
+       if (equals && ep < equals)
+               equals = NULL;
+       if (equals)
+               len = equals - cp;
+       else
+               len = ep - cp;
+       if (!res) {
+               if (*cp == '-' || *cp == '!') {
+                       cp++;
+                       len--;
+               }
+               if (invalid_attr_name(cp, len)) {
+                       fprintf(stderr,
+                               "%.*s is not a valid attribute name: %s:%d\n",
+                               len, cp, src, lineno);
+                       return NULL;
+               }
+       } else {
+               struct attr_state *e;
+
+               e = &(res->state[*num_attr]);
+               if (*cp == '-' || *cp == '!') {
+                       e->setto = (*cp == '-') ? ATTR__FALSE : ATTR__UNSET;
+                       cp++;
+                       len--;
+               }
+               else if (!equals)
+                       e->setto = ATTR__TRUE;
+               else {
+                       char *value;
+                       int vallen = ep - equals;
+                       value = xmalloc(vallen);
+                       memcpy(value, equals+1, vallen-1);
+                       value[vallen-1] = 0;
+                       e->setto = value;
+               }
+               e->attr = git_attr(cp, len);
+       }
+       (*num_attr)++;
+       return ep + strspn(ep, blank);
+}
+
+static struct match_attr *parse_attr_line(const char *line, const char *src,
+                                         int lineno, int macro_ok)
+{
+       int namelen;
+       int num_attr;
+       const char *cp, *name;
+       struct match_attr *res = NULL;
+       int pass;
+       int is_macro;
+
+       cp = line + strspn(line, blank);
+       if (!*cp || *cp == '#')
+               return NULL;
+       name = cp;
+       namelen = strcspn(name, blank);
+       if (strlen(ATTRIBUTE_MACRO_PREFIX) < namelen &&
+           !prefixcmp(name, ATTRIBUTE_MACRO_PREFIX)) {
+               if (!macro_ok) {
+                       fprintf(stderr, "%s not allowed: %s:%d\n",
+                               name, src, lineno);
+                       return NULL;
+               }
+               is_macro = 1;
+               name += strlen(ATTRIBUTE_MACRO_PREFIX);
+               name += strspn(name, blank);
+               namelen = strcspn(name, blank);
+               if (invalid_attr_name(name, namelen)) {
+                       fprintf(stderr,
+                               "%.*s is not a valid attribute name: %s:%d\n",
+                               namelen, name, src, lineno);
+                       return NULL;
+               }
+       }
+       else
+               is_macro = 0;
+
+       for (pass = 0; pass < 2; pass++) {
+               /* pass 0 counts and allocates, pass 1 fills */
+               num_attr = 0;
+               cp = name + namelen;
+               cp = cp + strspn(cp, blank);
+               while (*cp)
+                       cp = parse_attr(src, lineno, cp, &num_attr, res);
+               if (pass)
+                       break;
+               res = xcalloc(1,
+                             sizeof(*res) +
+                             sizeof(struct attr_state) * num_attr +
+                             (is_macro ? 0 : namelen + 1));
+               if (is_macro)
+                       res->u.attr = git_attr(name, namelen);
+               else {
+                       res->u.pattern = (char*)&(res->state[num_attr]);
+                       memcpy(res->u.pattern, name, namelen);
+                       res->u.pattern[namelen] = 0;
+               }
+               res->is_macro = is_macro;
+               res->num_attr = num_attr;
+       }
+       return res;
+}
+
+/*
+ * Like info/exclude and .gitignore, the attribute information can
+ * come from many places.
+ *
+ * (1) .gitattribute file of the same directory;
+ * (2) .gitattribute file of the parent directory if (1) does not have
+ *      any match; this goes recursively upwards, just like .gitignore.
+ * (3) $GIT_DIR/info/attributes, which overrides both of the above.
+ *
+ * In the same file, later entries override the earlier match, so in the
+ * global list, we would have entries from info/attributes the earliest
+ * (reading the file from top to bottom), .gitattribute of the root
+ * directory (again, reading the file from top to bottom) down to the
+ * current directory, and then scan the list backwards to find the first match.
+ * This is exactly the same as what excluded() does in dir.c to deal with
+ * .gitignore
+ */
+
+static struct attr_stack {
+       struct attr_stack *prev;
+       char *origin;
+       unsigned num_matches;
+       struct match_attr **attrs;
+} *attr_stack;
+
+static void free_attr_elem(struct attr_stack *e)
+{
+       int i;
+       free(e->origin);
+       for (i = 0; i < e->num_matches; i++) {
+               struct match_attr *a = e->attrs[i];
+               int j;
+               for (j = 0; j < a->num_attr; j++) {
+                       const char *setto = a->state[j].setto;
+                       if (setto == ATTR__TRUE ||
+                           setto == ATTR__FALSE ||
+                           setto == ATTR__UNSET ||
+                           setto == ATTR__UNKNOWN)
+                               ;
+                       else
+                               free((char*) setto);
+               }
+               free(a);
+       }
+       free(e);
+}
+
+static const char *builtin_attr[] = {
+       "[attr]binary -diff -crlf",
+       NULL,
+};
+
+static struct attr_stack *read_attr_from_array(const char **list)
+{
+       struct attr_stack *res;
+       const char *line;
+       int lineno = 0;
+
+       res = xcalloc(1, sizeof(*res));
+       while ((line = *(list++)) != NULL) {
+               struct match_attr *a;
+
+               a = parse_attr_line(line, "[builtin]", ++lineno, 1);
+               if (!a)
+                       continue;
+               res->attrs = xrealloc(res->attrs,
+                       sizeof(struct match_attr *) * (res->num_matches + 1));
+               res->attrs[res->num_matches++] = a;
+       }
+       return res;
+}
+
+static struct attr_stack *read_attr_from_file(const char *path, int macro_ok)
+{
+       FILE *fp;
+       struct attr_stack *res;
+       char buf[2048];
+       int lineno = 0;
+
+       res = xcalloc(1, sizeof(*res));
+       fp = fopen(path, "r");
+       if (!fp)
+               return res;
+
+       while (fgets(buf, sizeof(buf), fp)) {
+               struct match_attr *a;
+
+               a = parse_attr_line(buf, path, ++lineno, macro_ok);
+               if (!a)
+                       continue;
+               res->attrs = xrealloc(res->attrs,
+                       sizeof(struct match_attr *) * (res->num_matches + 1));
+               res->attrs[res->num_matches++] = a;
+       }
+       fclose(fp);
+       return res;
+}
+
+#if DEBUG_ATTR
+static void debug_info(const char *what, struct attr_stack *elem)
+{
+       fprintf(stderr, "%s: %s\n", what, elem->origin ? elem->origin : "()");
+}
+static void debug_set(const char *what, const char *match, struct git_attr *attr, void *v)
+{
+       const char *value = v;
+
+       if (ATTR_TRUE(value))
+               value = "set";
+       else if (ATTR_FALSE(value))
+               value = "unset";
+       else if (ATTR_UNSET(value))
+               value = "unspecified";
+
+       fprintf(stderr, "%s: %s => %s (%s)\n",
+               what, attr->name, (char *) value, match);
+}
+#define debug_push(a) debug_info("push", (a))
+#define debug_pop(a) debug_info("pop", (a))
+#else
+#define debug_push(a) do { ; } while (0)
+#define debug_pop(a) do { ; } while (0)
+#define debug_set(a,b,c,d) do { ; } while (0)
+#endif
+
+static void bootstrap_attr_stack(void)
+{
+       if (!attr_stack) {
+               struct attr_stack *elem;
+
+               elem = read_attr_from_array(builtin_attr);
+               elem->origin = NULL;
+               elem->prev = attr_stack;
+               attr_stack = elem;
+
+               elem = read_attr_from_file(GITATTRIBUTES_FILE, 1);
+               elem->origin = strdup("");
+               elem->prev = attr_stack;
+               attr_stack = elem;
+               debug_push(elem);
+
+               elem = read_attr_from_file(git_path(INFOATTRIBUTES_FILE), 1);
+               elem->origin = NULL;
+               elem->prev = attr_stack;
+               attr_stack = elem;
+       }
+}
+
+static void prepare_attr_stack(const char *path, int dirlen)
+{
+       struct attr_stack *elem, *info;
+       int len;
+       char pathbuf[PATH_MAX];
+
+       /*
+        * At the bottom of the attribute stack is the built-in
+        * set of attribute definitions.  Then, contents from
+        * .gitattribute files from directories closer to the
+        * root to the ones in deeper directories are pushed
+        * to the stack.  Finally, at the very top of the stack
+        * we always keep the contents of $GIT_DIR/info/attributes.
+        *
+        * When checking, we use entries from near the top of the
+        * stack, preferring $GIT_DIR/info/attributes, then
+        * .gitattributes in deeper directories to shallower ones,
+        * and finally use the built-in set as the default.
+        */
+       if (!attr_stack)
+               bootstrap_attr_stack();
+
+       /*
+        * Pop the "info" one that is always at the top of the stack.
+        */
+       info = attr_stack;
+       attr_stack = info->prev;
+
+       /*
+        * Pop the ones from directories that are not the prefix of
+        * the path we are checking.
+        */
+       while (attr_stack && attr_stack->origin) {
+               int namelen = strlen(attr_stack->origin);
+
+               elem = attr_stack;
+               if (namelen <= dirlen &&
+                   !strncmp(elem->origin, path, namelen))
+                       break;
+
+               debug_pop(elem);
+               attr_stack = elem->prev;
+               free_attr_elem(elem);
+       }
+
+       /*
+        * Read from parent directories and push them down
+        */
+       while (1) {
+               char *cp;
+
+               len = strlen(attr_stack->origin);
+               if (dirlen <= len)
+                       break;
+               memcpy(pathbuf, path, dirlen);
+               memcpy(pathbuf + dirlen, "/", 2);
+               cp = strchr(pathbuf + len + 1, '/');
+               strcpy(cp + 1, GITATTRIBUTES_FILE);
+               elem = read_attr_from_file(pathbuf, 0);
+               *cp = '\0';
+               elem->origin = strdup(pathbuf);
+               elem->prev = attr_stack;
+               attr_stack = elem;
+               debug_push(elem);
+       }
+
+       /*
+        * Finally push the "info" one at the top of the stack.
+        */
+       info->prev = attr_stack;
+       attr_stack = info;
+}
+
+static int path_matches(const char *pathname, int pathlen,
+                       const char *pattern,
+                       const char *base, int baselen)
+{
+       if (!strchr(pattern, '/')) {
+               /* match basename */
+               const char *basename = strrchr(pathname, '/');
+               basename = basename ? basename + 1 : pathname;
+               return (fnmatch(pattern, basename, 0) == 0);
+       }
+       /*
+        * match with FNM_PATHNAME; the pattern has base implicitly
+        * in front of it.
+        */
+       if (*pattern == '/')
+               pattern++;
+       if (pathlen < baselen ||
+           (baselen && pathname[baselen - 1] != '/') ||
+           strncmp(pathname, base, baselen))
+               return 0;
+       return fnmatch(pattern, pathname + baselen, FNM_PATHNAME) == 0;
+}
+
+static int fill_one(const char *what, struct match_attr *a, int rem)
+{
+       struct git_attr_check *check = check_all_attr;
+       int i;
+
+       for (i = 0; 0 < rem && i < a->num_attr; i++) {
+               struct git_attr *attr = a->state[i].attr;
+               const char **n = &(check[attr->attr_nr].value);
+               const char *v = a->state[i].setto;
+
+               if (*n == ATTR__UNKNOWN) {
+                       debug_set(what, a->u.pattern, attr, v);
+                       *n = v;
+                       rem--;
+               }
+       }
+       return rem;
+}
+
+static int fill(const char *path, int pathlen, struct attr_stack *stk, int rem)
+{
+       int i;
+       const char *base = stk->origin ? stk->origin : "";
+
+       for (i = stk->num_matches - 1; 0 < rem && 0 <= i; i--) {
+               struct match_attr *a = stk->attrs[i];
+               if (a->is_macro)
+                       continue;
+               if (path_matches(path, pathlen,
+                                a->u.pattern, base, strlen(base)))
+                       rem = fill_one("fill", a, rem);
+       }
+       return rem;
+}
+
+static int macroexpand(struct attr_stack *stk, int rem)
+{
+       int i;
+       struct git_attr_check *check = check_all_attr;
+
+       for (i = stk->num_matches - 1; 0 < rem && 0 <= i; i--) {
+               struct match_attr *a = stk->attrs[i];
+               if (!a->is_macro)
+                       continue;
+               if (check[a->u.attr->attr_nr].value != ATTR__TRUE)
+                       continue;
+               rem = fill_one("expand", a, rem);
+       }
+       return rem;
+}
+
+int git_checkattr(const char *path, int num, struct git_attr_check *check)
+{
+       struct attr_stack *stk;
+       const char *cp;
+       int dirlen, pathlen, i, rem;
+
+       bootstrap_attr_stack();
+       for (i = 0; i < attr_nr; i++)
+               check_all_attr[i].value = ATTR__UNKNOWN;
+
+       pathlen = strlen(path);
+       cp = strrchr(path, '/');
+       if (!cp)
+               dirlen = 0;
+       else
+               dirlen = cp - path;
+       prepare_attr_stack(path, dirlen);
+       rem = attr_nr;
+       for (stk = attr_stack; 0 < rem && stk; stk = stk->prev)
+               rem = fill(path, pathlen, stk, rem);
+
+       for (stk = attr_stack; 0 < rem && stk; stk = stk->prev)
+               rem = macroexpand(stk, rem);
+
+       for (i = 0; i < num; i++) {
+               const char *value = check_all_attr[check[i].attr->attr_nr].value;
+               if (value == ATTR__UNKNOWN)
+                       value = ATTR__UNSET;
+               check[i].value = value;
+       }
+
+       return 0;
+}
diff --git a/attr.h b/attr.h
new file mode 100644 (file)
index 0000000..f1c2038
--- /dev/null
+++ b/attr.h
@@ -0,0 +1,34 @@
+#ifndef ATTR_H
+#define ATTR_H
+
+/* An attribute is a pointer to this opaque structure */
+struct git_attr;
+
+/*
+ * Given a string, return the gitattribute object that
+ * corresponds to it.
+ */
+struct git_attr *git_attr(const char *, int);
+
+/* Internal use */
+extern const char git_attr__true[];
+extern const char git_attr__false[];
+
+/* For public to check git_attr_check results */
+#define ATTR_TRUE(v) ((v) == git_attr__true)
+#define ATTR_FALSE(v) ((v) == git_attr__false)
+#define ATTR_UNSET(v) ((v) == NULL)
+
+/*
+ * Send one or more git_attr_check to git_checkattr(), and
+ * each 'value' member tells what its value is.
+ * Unset one is returned as NULL.
+ */
+struct git_attr_check {
+       struct git_attr *attr;
+       const char *value;
+};
+
+int git_checkattr(const char *path, int, struct git_attr_check *);
+
+#endif /* ATTR_H */
diff --git a/blob.c b/blob.c
index 0a9ea417b8af4b06871eae8c47168dc178e9bb29..bd7d078e1ae5fe4ce0a16fda62a2c1743237941b 100644 (file)
--- a/blob.c
+++ b/blob.c
@@ -6,12 +6,8 @@ const char *blob_type = "blob";
 struct blob *lookup_blob(const unsigned char *sha1)
 {
        struct object *obj = lookup_object(sha1);
-       if (!obj) {
-               struct blob *ret = alloc_blob_node();
-               created_object(sha1, &ret->object);
-               ret->object.type = OBJ_BLOB;
-               return ret;
-       }
+       if (!obj)
+               return create_object(sha1, OBJ_BLOB, alloc_blob_node());
        if (!obj->type)
                obj->type = OBJ_BLOB;
        if (obj->type != OBJ_BLOB) {
index 9fcf514dbc4cb76e15b47142e77c4019997ecd5d..159117106a3db52b430cb1bf82d094e54693f690 100644 (file)
@@ -8,10 +8,15 @@
 #include "dir.h"
 #include "exec_cmd.h"
 #include "cache-tree.h"
+#include "diff.h"
+#include "diffcore.h"
+#include "commit.h"
+#include "revision.h"
 
 static const char builtin_add_usage[] =
-"git-add [-n] [-v] [-f] [--interactive | -i] [--] <filepattern>...";
+"git-add [-n] [-v] [-f] [--interactive | -i] [-u] [--] <filepattern>...";
 
+static int take_worktree_changes;
 static const char *excludes_file;
 
 static void prune_directory(struct dir_struct *dir, const char **pathspec, int prefix)
@@ -87,11 +92,50 @@ static void fill_directory(struct dir_struct *dir, const char **pathspec)
        }
 
        /* Read the directory and prune it */
-       read_directory(dir, path, base, baselen);
+       read_directory(dir, path, base, baselen, pathspec);
        if (pathspec)
                prune_directory(dir, pathspec, baselen);
 }
 
+static void update_callback(struct diff_queue_struct *q,
+                           struct diff_options *opt, void *cbdata)
+{
+       int i, verbose;
+
+       verbose = *((int *)cbdata);
+       for (i = 0; i < q->nr; i++) {
+               struct diff_filepair *p = q->queue[i];
+               const char *path = p->one->path;
+               switch (p->status) {
+               default:
+                       die("unexpacted diff status %c", p->status);
+               case DIFF_STATUS_UNMERGED:
+               case DIFF_STATUS_MODIFIED:
+                       add_file_to_cache(path, verbose);
+                       break;
+               case DIFF_STATUS_DELETED:
+                       remove_file_from_cache(path);
+                       if (verbose)
+                               printf("remove '%s'\n", path);
+                       break;
+               }
+       }
+}
+
+static void update(int verbose, const char **files)
+{
+       struct rev_info rev;
+       init_revisions(&rev, "");
+       setup_revisions(0, NULL, &rev, NULL);
+       rev.prune_data = get_pathspec(rev.prefix, files);
+       rev.diffopt.output_format = DIFF_FORMAT_CALLBACK;
+       rev.diffopt.format_callback = update_callback;
+       rev.diffopt.format_callback_data = &verbose;
+       if (read_cache() < 0)
+               die("index file corrupt");
+       run_diff_files(&rev, 0);
+}
+
 static int git_add_config(const char *var, const char *value)
 {
        if (!strcmp(var, "core.excludesfile")) {
@@ -133,7 +177,7 @@ int cmd_add(int argc, const char **argv, const char *prefix)
 
        git_config(git_add_config);
 
-       newfd = hold_lock_file_for_update(&lock_file, get_index_file(), 1);
+       newfd = hold_locked_index(&lock_file, 1);
 
        for (i = 1; i < argc; i++) {
                const char *arg = argv[i];
@@ -156,8 +200,18 @@ int cmd_add(int argc, const char **argv, const char *prefix)
                        verbose = 1;
                        continue;
                }
+               if (!strcmp(arg, "-u")) {
+                       take_worktree_changes = 1;
+                       continue;
+               }
                usage(builtin_add_usage);
        }
+
+       if (take_worktree_changes) {
+               update(verbose, argv + i);
+               goto finish;
+       }
+
        if (argc <= i) {
                fprintf(stderr, "Nothing specified, nothing added.\n");
                fprintf(stderr, "Maybe you wanted to say 'git add .'?\n");
@@ -205,11 +259,12 @@ int cmd_add(int argc, const char **argv, const char *prefix)
        }
 
        for (i = 0; i < dir.nr; i++)
-               add_file_to_index(dir.entries[i]->name, verbose);
+               add_file_to_cache(dir.entries[i]->name, verbose);
 
+ finish:
        if (active_cache_changed) {
                if (write_cache(newfd, active_cache, active_nr) ||
-                   close(newfd) || commit_lock_file(&lock_file))
+                   close(newfd) || commit_locked_index(&lock_file))
                        die("Unable to write new index file");
        }
 
index db5272245569f4080a07cdb3a2aacd2c0cbda38c..0399743c4e2288a0812faeea92c07a5f6a13fccc 100644 (file)
@@ -30,7 +30,7 @@ static int unidiff_zero;
 static int p_value = 1;
 static int p_value_known;
 static int check_index;
-static int write_index;
+static int update_index;
 static int cached;
 static int diffstat;
 static int numstat;
@@ -185,7 +185,7 @@ static void *read_patch_file(int fd, unsigned long *sizep)
        void *buffer = xmalloc(alloc);
 
        for (;;) {
-               int nr = alloc - size;
+               ssize_t nr = alloc - size;
                if (nr < 1024) {
                        alloc += CHUNKSIZE;
                        buffer = xrealloc(buffer, alloc);
@@ -1468,15 +1468,15 @@ static int read_old_data(struct stat *st, const char *path, char **buf_p, unsign
                        return error("unable to open %s", path);
                got = 0;
                for (;;) {
-                       int ret = xread(fd, buf + got, size - got);
+                       ssize_t ret = xread(fd, buf + got, size - got);
                        if (ret <= 0)
                                break;
                        got += ret;
                }
                close(fd);
                nsize = got;
-               nbuf = buf;
-               if (convert_to_git(path, &nbuf, &nsize)) {
+               nbuf = convert_to_git(path, buf, &nsize);
+               if (nbuf) {
                        free(buf);
                        *buf_p = nbuf;
                        *alloc_p = nsize;
@@ -2009,6 +2009,29 @@ static int apply_data(struct patch *patch, struct stat *st, struct cache_entry *
        return 0;
 }
 
+static int check_to_create_blob(const char *new_name, int ok_if_exists)
+{
+       struct stat nst;
+       if (!lstat(new_name, &nst)) {
+               if (S_ISDIR(nst.st_mode) || ok_if_exists)
+                       return 0;
+               /*
+                * A leading component of new_name might be a symlink
+                * that is going to be removed with this patch, but
+                * still pointing at somewhere that has the path.
+                * In such a case, path "new_name" does not exist as
+                * far as git is concerned.
+                */
+               if (has_symlink_leading_path(new_name, NULL))
+                       return 0;
+
+               return error("%s: already exists in working directory", new_name);
+       }
+       else if ((errno != ENOENT) && (errno != ENOTDIR))
+               return error("%s: %s", new_name, strerror(errno));
+       return 0;
+}
+
 static int check_patch(struct patch *patch, struct patch *prev_patch)
 {
        struct stat st;
@@ -2095,15 +2118,9 @@ static int check_patch(struct patch *patch, struct patch *prev_patch)
                    !ok_if_exists)
                        return error("%s: already exists in index", new_name);
                if (!cached) {
-                       struct stat nst;
-                       if (!lstat(new_name, &nst)) {
-                               if (S_ISDIR(nst.st_mode) || ok_if_exists)
-                                       ; /* ok */
-                               else
-                                       return error("%s: already exists in working directory", new_name);
-                       }
-                       else if ((errno != ENOENT) && (errno != ENOTDIR))
-                               return error("%s: %s", new_name, strerror(errno));
+                       int err = check_to_create_blob(new_name, ok_if_exists);
+                       if (err)
+                               return err;
                }
                if (!patch->new_mode) {
                        if (0 < patch->is_new)
@@ -2308,7 +2325,7 @@ static void patch_stats(struct patch *patch)
 
 static void remove_file(struct patch *patch, int rmdir_empty)
 {
-       if (write_index) {
+       if (update_index) {
                if (remove_file_from_cache(patch->old_name) < 0)
                        die("unable to remove %s from index", patch->old_name);
                cache_tree_invalidate_path(active_cache_tree, patch->old_name);
@@ -2335,7 +2352,7 @@ static void add_index_file(const char *path, unsigned mode, void *buf, unsigned
        int namelen = strlen(path);
        unsigned ce_size = cache_entry_size(namelen);
 
-       if (!write_index)
+       if (!update_index)
                return;
 
        ce = xcalloc(1, ce_size);
@@ -2355,9 +2372,8 @@ static void add_index_file(const char *path, unsigned mode, void *buf, unsigned
 
 static int try_create_file(const char *path, unsigned int mode, const char *buf, unsigned long size)
 {
-       int fd, converted;
+       int fd;
        char *nbuf;
-       unsigned long nsize;
 
        if (has_symlinks && S_ISLNK(mode))
                /* Although buf:size is counted string, it also is NUL
@@ -2369,13 +2385,10 @@ static int try_create_file(const char *path, unsigned int mode, const char *buf,
        if (fd < 0)
                return -1;
 
-       nsize = size;
-       nbuf = (char *) buf;
-       converted = convert_to_working_tree(path, &nbuf, &nsize);
-       if (converted) {
+       nbuf = convert_to_working_tree(path, buf, &size);
+       if (nbuf)
                buf = nbuf;
-               size = nsize;
-       }
+
        while (size) {
                int written = xwrite(fd, buf, size);
                if (written < 0)
@@ -2387,7 +2400,7 @@ static int try_create_file(const char *path, unsigned int mode, const char *buf,
        }
        if (close(fd) < 0)
                die("closing file %s: %s", path, strerror(errno));
-       if (converted)
+       if (nbuf)
                free(nbuf);
        return 0;
 }
@@ -2661,10 +2674,10 @@ static int apply_patch(int fd, const char *filename, int inaccurate_eof)
        if (whitespace_error && (new_whitespace == error_on_whitespace))
                apply = 0;
 
-       write_index = check_index && apply;
-       if (write_index && newfd < 0)
-               newfd = hold_lock_file_for_update(&lock_file,
-                                                 get_index_file(), 1);
+       update_index = check_index && apply;
+       if (update_index && newfd < 0)
+               newfd = hold_locked_index(&lock_file, 1);
+
        if (check_index) {
                if (read_cache() < 0)
                        die("unable to read index file");
@@ -2869,9 +2882,9 @@ int cmd_apply(int argc, const char **argv, const char *unused_prefix)
                                whitespace_error == 1 ? "s" : "");
        }
 
-       if (write_index) {
+       if (update_index) {
                if (write_cache(newfd, active_cache, active_nr) ||
-                   close(newfd) || commit_lock_file(&lock_file))
+                   close(newfd) || commit_locked_index(&lock_file))
                        die("Unable to write new index file");
        }
 
index 8ea6cb1efc4f988fb09051852f9e51fc88b5efd7..7f4e409c998ba4a864bd39032556d71693ff6793 100644 (file)
@@ -149,7 +149,7 @@ int parse_archive_args(int argc, const char **argv, struct archiver *ar)
 {
        const char *extra_argv[MAX_EXTRA_ARGS];
        int extra_argc = 0;
-       const char *format = NULL; /* might want to default to "tar" */
+       const char *format = "tar";
        const char *base = "";
        int verbose = 0;
        int i;
@@ -190,8 +190,6 @@ int parse_archive_args(int argc, const char **argv, struct archiver *ar)
        /* We need at least one parameter -- tree-ish */
        if (argc - 1 < i)
                usage(archive_usage);
-       if (!format)
-               die("You must specify an archive format");
        if (init_archiver(format, ar) < 0)
                die("Unknown archive format '%s'", format);
 
index 65d029a773691f994711478bc2475681093e0088..35471fc2615992451c8c5b51a346fe171029b572 100644 (file)
 #include "quote.h"
 #include "xdiff-interface.h"
 #include "cache-tree.h"
+#include "path-list.h"
+#include "mailmap.h"
 
 static char blame_usage[] =
-"git-blame [-c] [-l] [-t] [-f] [-n] [-p] [-L n,m] [-S <revs-file>] [-M] [-C] [-C] [--contents <filename>] [--incremental] [commit] [--] file\n"
+"git-blame [-c] [-b] [-l] [--root] [-t] [-f] [-n] [-s] [-p] [-L n,m] [-S <revs-file>] [-M] [-C] [-C] [--contents <filename>] [--incremental] [commit] [--] file\n"
 "  -c                  Use the same output mode as git-annotate (Default: off)\n"
 "  -b                  Show blank SHA-1 for boundary commits (Default: off)\n"
 "  -l                  Show long commit SHA1 (Default: off)\n"
@@ -26,6 +28,7 @@ static char blame_usage[] =
 "  -t                  Show raw timestamp (Default: off)\n"
 "  -f, --show-name     Show original filename (Default: auto)\n"
 "  -n, --show-number   Show original linenumber (Default: off)\n"
+"  -s                  Suppress author name and timestamp (Default: off)\n"
 "  -p, --porcelain     Show in a format designed for machine consumption\n"
 "  -L n,m              Process only line range n,m, counting from 1\n"
 "  -M, -C              Find line movements within and across files\n"
@@ -42,6 +45,7 @@ static int show_root;
 static int blank_boundary;
 static int incremental;
 static int cmd_is_annotate;
+static struct path_list mailmap;
 
 #ifndef DEBUG
 #define DEBUG 0
@@ -1294,8 +1298,8 @@ static void get_ac_line(const char *inbuf, const char *what,
                        int bufsz, char *person, const char **mail,
                        unsigned long *time, const char **tz)
 {
-       int len;
-       char *tmp, *endp;
+       int len, tzlen, maillen;
+       char *tmp, *endp, *timepos;
 
        tmp = strstr(inbuf, what);
        if (!tmp)
@@ -1321,17 +1325,42 @@ static void get_ac_line(const char *inbuf, const char *what,
        while (*tmp != ' ')
                tmp--;
        *tz = tmp+1;
+       tzlen = (person+len)-(tmp+1);
 
        *tmp = 0;
        while (*tmp != ' ')
                tmp--;
        *time = strtoul(tmp, NULL, 10);
+       timepos = tmp;
 
        *tmp = 0;
        while (*tmp != ' ')
                tmp--;
        *mail = tmp + 1;
        *tmp = 0;
+       maillen = timepos - tmp;
+
+       if (!mailmap.nr)
+               return;
+
+       /*
+        * mailmap expansion may make the name longer.
+        * make room by pushing stuff down.
+        */
+       tmp = person + bufsz - (tzlen + 1);
+       memmove(tmp, *tz, tzlen);
+       tmp[tzlen] = 0;
+       *tz = tmp;
+
+       tmp = tmp - (maillen + 1);
+       memmove(tmp, *mail, maillen);
+       tmp[maillen] = 0;
+       *mail = tmp;
+
+       /*
+        * Now, convert e-mail using mailmap
+        */
+       map_email(&mailmap, tmp + 1, person, tmp-person-1);
 }
 
 static void get_commit_info(struct commit *commit,
@@ -1513,6 +1542,7 @@ static const char *format_time(unsigned long time, const char *tz_str,
 #define OUTPUT_SHOW_NAME       020
 #define OUTPUT_SHOW_NUMBER     040
 #define OUTPUT_SHOW_SCORE      0100
+#define OUTPUT_NO_AUTHOR       0200
 
 static void emit_porcelain(struct scoreboard *sb, struct blame_entry *ent)
 {
@@ -1607,10 +1637,15 @@ static void emit_other(struct scoreboard *sb, struct blame_entry *ent, int opt)
                        if (opt & OUTPUT_SHOW_NUMBER)
                                printf(" %*d", max_orig_digits,
                                       ent->s_lno + 1 + cnt);
-                       printf(" (%-*.*s %10s %*d) ",
-                              longest_author, longest_author, ci.author,
-                              format_time(ci.author_time, ci.author_tz,
-                                          show_raw_time),
+
+                       if (!(opt & OUTPUT_NO_AUTHOR))
+                               printf(" (%-*.*s %10s",
+                                      longest_author, longest_author,
+                                      ci.author,
+                                      format_time(ci.author_time,
+                                                  ci.author_tz,
+                                                  show_raw_time));
+                       printf(" %*d) ",
                               max_digits, ent->lno + 1 + cnt);
                }
                do {
@@ -2122,6 +2157,8 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
                        output_option |= OUTPUT_RAW_TIMESTAMP;
                else if (!strcmp("-l", arg))
                        output_option |= OUTPUT_LONG_OBJECT_NAME;
+               else if (!strcmp("-s", arg))
+                       output_option |= OUTPUT_NO_AUTHOR;
                else if (!strcmp("-S", arg) && ++i < argc)
                        revs_file = argv[i];
                else if (!prefixcmp(arg, "-M")) {
@@ -2372,6 +2409,8 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
                die("reading graft file %s failed: %s",
                    revs_file, strerror(errno));
 
+       read_mailmap(&mailmap, ".mailmap", NULL);
+
        assign_blame(&sb, &revs, opt);
 
        if (incremental)
index d1635a0a6b009ecb6706084828abd0e3928f2b8d..306ad29597dbf9002a44ba509c2e9d7a737b159d 100644 (file)
@@ -48,7 +48,7 @@ static int read_string(int fd, char *buffer, int size)
 {
        int i;
        for (i = 0; i < size - 1; i++) {
-               int count = xread(fd, buffer + i, 1);
+               ssize_t count = xread(fd, buffer + i, 1);
                if (count < 0)
                        return error("Read error: %s", strerror(errno));
                if (count == 0) {
index d61d3d5b74ae23bb1ab10b23f518fa7a85ac5481..f132d583d3e2a2ac0fe696b66723c846902d0a19 100644 (file)
@@ -83,17 +83,21 @@ int cmd_cat_file(int argc, const char **argv, const char *prefix)
        void *buf;
        unsigned long size;
        int opt;
+       const char *exp_type, *obj_name;
 
        git_config(git_default_config);
        if (argc != 3)
                usage("git-cat-file [-t|-s|-e|-p|<type>] <sha1>");
-       if (get_sha1(argv[2], sha1))
-               die("Not a valid object name %s", argv[2]);
+       exp_type = argv[1];
+       obj_name = argv[2];
+
+       if (get_sha1(obj_name, sha1))
+               die("Not a valid object name %s", obj_name);
 
        opt = 0;
-       if ( argv[1][0] == '-' ) {
-               opt = argv[1][1];
-               if ( !opt || argv[1][2] )
+       if ( exp_type[0] == '-' ) {
+               opt = exp_type[1];
+               if ( !opt || exp_type[2] )
                        opt = -1; /* Not a single character option */
        }
 
@@ -121,15 +125,17 @@ int cmd_cat_file(int argc, const char **argv, const char *prefix)
        case 'p':
                type = sha1_object_info(sha1, NULL);
                if (type < 0)
-                       die("Not a valid object name %s", argv[2]);
+                       die("Not a valid object name %s", obj_name);
 
                /* custom pretty-print here */
-               if (type == OBJ_TREE)
-                       return cmd_ls_tree(2, argv + 1, NULL);
+               if (type == OBJ_TREE) {
+                       const char *ls_args[3] = {"ls-tree", obj_name, NULL};
+                       return cmd_ls_tree(2, ls_args, NULL);
+               }
 
                buf = read_sha1_file(sha1, &type, &size);
                if (!buf)
-                       die("Cannot read object %s", argv[2]);
+                       die("Cannot read object %s", obj_name);
                if (type == OBJ_TAG) {
                        pprint_tag(sha1, buf, size);
                        return 0;
@@ -138,15 +144,15 @@ int cmd_cat_file(int argc, const char **argv, const char *prefix)
                /* otherwise just spit out the data */
                break;
        case 0:
-               buf = read_object_with_reference(sha1, argv[1], &size, NULL);
+               buf = read_object_with_reference(sha1, exp_type, &size, NULL);
                break;
 
        default:
-               die("git-cat-file: unknown option: %s\n", argv[1]);
+               die("git-cat-file: unknown option: %s\n", exp_type);
        }
 
        if (!buf)
-               die("git-cat-file %s: bad file", argv[2]);
+               die("git-cat-file %s: bad file", obj_name);
 
        write_or_die(1, buf, size);
        return 0;
diff --git a/builtin-check-attr.c b/builtin-check-attr.c
new file mode 100644 (file)
index 0000000..9d77f76
--- /dev/null
@@ -0,0 +1,59 @@
+#include "builtin.h"
+#include "attr.h"
+#include "quote.h"
+
+static const char check_attr_usage[] =
+"git-check-attr attr... [--] pathname...";
+
+int cmd_check_attr(int argc, const char **argv, const char *prefix)
+{
+       struct git_attr_check *check;
+       int cnt, i, doubledash;
+
+       doubledash = -1;
+       for (i = 1; doubledash < 0 && i < argc; i++) {
+               if (!strcmp(argv[i], "--"))
+                       doubledash = i;
+       }
+
+       /* If there is no double dash, we handle only one attribute */
+       if (doubledash < 0) {
+               cnt = 1;
+               doubledash = 1;
+       } else
+               cnt = doubledash - 1;
+       doubledash++;
+
+       if (cnt <= 0 || argc < doubledash)
+               usage(check_attr_usage);
+       check = xcalloc(cnt, sizeof(*check));
+       for (i = 0; i < cnt; i++) {
+               const char *name;
+               struct git_attr *a;
+               name = argv[i + 1];
+               a = git_attr(name, strlen(name));
+               if (!a)
+                       return error("%s: not a valid attribute name", name);
+               check[i].attr = a;
+       }
+
+       for (i = doubledash; i < argc; i++) {
+               int j;
+               if (git_checkattr(argv[i], cnt, check))
+                       die("git_checkattr died");
+               for (j = 0; j < cnt; j++) {
+                       const char *value = check[j].value;
+
+                       if (ATTR_TRUE(value))
+                               value = "set";
+                       else if (ATTR_FALSE(value))
+                               value = "unset";
+                       else if (ATTR_UNSET(value))
+                               value = "unspecified";
+
+                       write_name_quoted("", 0, argv[i], 1, stdout);
+                       printf(": %s: %s\n", argv[j+1], value);
+               }
+       }
+       return 0;
+}
index afe4b0e4520f47f1dace1383bfc2b364b1b1def0..8460f97b6637127d78b58caf2e29d25f3ad0b5a0 100644 (file)
@@ -202,10 +202,7 @@ int cmd_checkout_index(int argc, const char **argv, const char *prefix)
                if (!strcmp(arg, "-u") || !strcmp(arg, "--index")) {
                        state.refresh_cache = 1;
                        if (newfd < 0)
-                               newfd = hold_lock_file_for_update
-                                       (&lock_file, get_index_file(), 1);
-                       if (newfd < 0)
-                               die("cannot open index.lock file.");
+                               newfd = hold_locked_index(&lock_file, 1);
                        continue;
                }
                if (!strcmp(arg, "-z")) {
@@ -302,7 +299,7 @@ int cmd_checkout_index(int argc, const char **argv, const char *prefix)
 
        if (0 <= newfd &&
            (write_cache(newfd, active_cache, active_nr) ||
-            close(newfd) || commit_lock_file(&lock_file)))
+            close(newfd) || commit_locked_index(&lock_file)))
                die("Unable to write new index file");
        return 0;
 }
index 4a8d8d8b674c6f272243a074030af289403eadde..ccbcbe30dab634d9ff393f1e849c18388b9d53d4 100644 (file)
@@ -16,9 +16,8 @@
  */
 static void init_buffer(char **bufp, unsigned int *sizep)
 {
-       char *buf = xmalloc(BLOCKING);
+       *bufp = xmalloc(BLOCKING);
        *sizep = 0;
-       *bufp = buf;
 }
 
 static void add_buffer(char **bufp, unsigned int *sizep, const char *fmt, ...)
index 6263d8af295a5abce3e417157af7cb41e3623f38..ff90ebd465002882781507ecfcfc33cab2f759fc 100644 (file)
@@ -111,7 +111,7 @@ int cmd_count_objects(int ac, const char **av, const char *prefix)
                for (p = packed_git; p; p = p->next) {
                        if (!p->pack_local)
                                continue;
-                       packed += num_packed_objects(p);
+                       packed += p->num_objects;
                        num_pack++;
                }
                printf("count: %lu\n", loose);
index 21d13f0b30359295b8385754fccb4bb71f995dba..7f367b6b9d545ea760224fdacb68056261ce1617 100644 (file)
 #include "log-tree.h"
 #include "builtin.h"
 
-/* NEEDSWORK: struct object has place for name but we _do_
- * know mode when we extracted the blob out of a tree, which
- * we currently lose.
- */
 struct blobinfo {
        unsigned char sha1[20];
        const char *name;
+       unsigned mode;
 };
 
 static const char builtin_diff_usage[] =
@@ -35,7 +32,7 @@ static void stuff_change(struct diff_options *opt,
        struct diff_filespec *one, *two;
 
        if (!is_null_sha1(old_sha1) && !is_null_sha1(new_sha1) &&
-           !hashcmp(old_sha1, new_sha1))
+           !hashcmp(old_sha1, new_sha1) && (old_mode == new_mode))
                return;
 
        if (opt->reverse_diff) {
@@ -70,8 +67,12 @@ static int builtin_diff_b_f(struct rev_info *revs,
                die("'%s': %s", path, strerror(errno));
        if (!(S_ISREG(st.st_mode) || S_ISLNK(st.st_mode)))
                die("'%s': not a regular file or symlink", path);
+
+       if (blob[0].mode == S_IFINVALID)
+               blob[0].mode = canon_mode(st.st_mode);
+
        stuff_change(&revs->diffopt,
-                    canon_mode(st.st_mode), canon_mode(st.st_mode),
+                    blob[0].mode, canon_mode(st.st_mode),
                     blob[0].sha1, null_sha1,
                     path, path);
        diffcore_std(&revs->diffopt);
@@ -88,8 +89,14 @@ static int builtin_diff_blobs(struct rev_info *revs,
        if (argc > 1)
                usage(builtin_diff_usage);
 
+       if (blob[0].mode == S_IFINVALID)
+               blob[0].mode = mode;
+
+       if (blob[1].mode == S_IFINVALID)
+               blob[1].mode = mode;
+
        stuff_change(&revs->diffopt,
-                    mode, mode,
+                    blob[0].mode, blob[1].mode,
                     blob[0].sha1, blob[1].sha1,
                     blob[0].name, blob[1].name);
        diffcore_std(&revs->diffopt);
@@ -225,6 +232,7 @@ int cmd_diff(int argc, const char **argv, const char *prefix)
                if (diff_setup_done(&rev.diffopt) < 0)
                        die("diff_setup_done failed");
        }
+       rev.diffopt.allow_external = 1;
 
        /* Do we have --cached and not have a pending object, then
         * default to HEAD by hand.  Eek.
@@ -271,6 +279,7 @@ int cmd_diff(int argc, const char **argv, const char *prefix)
                                die("more than two blobs given: '%s'", name);
                        hashcpy(blob[blobs].sha1, obj->sha1);
                        blob[blobs].name = name;
+                       blob[blobs].mode = list->mode;
                        blobs++;
                        continue;
 
index 2ad45dcd447c582fc37dc133c9a7bb3260dc31ea..12adb3833cd60771f8e63b2ebaecff3769fa74e2 100644 (file)
@@ -6,11 +6,11 @@
 
 static char *get_stdin(void)
 {
-       int offset = 0;
+       size_t offset = 0;
        char *data = xmalloc(CHUNK_SIZE);
 
        while (1) {
-               int cnt = xread(0, data + offset, CHUNK_SIZE);
+               ssize_t cnt = xread(0, data + offset, CHUNK_SIZE);
                if (cnt < 0)
                        die("error reading standard input: %s",
                            strerror(errno));
@@ -433,10 +433,87 @@ static int expand_refs_wildcard(const char *ls_remote_result, int numrefs,
        return 0;
 }
 
+static int pick_rref(int sha1_only, const char *rref, const char *ls_remote_result)
+{
+       int err = 0;
+       int lrr_count = lrr_count, i, pass;
+       const char *cp;
+       struct lrr {
+               const char *line;
+               const char *name;
+               int namelen;
+               int shown;
+       } *lrr_list = lrr_list;
+
+       for (pass = 0; pass < 2; pass++) {
+               /* pass 0 counts and allocates, pass 1 fills... */
+               cp = ls_remote_result;
+               i = 0;
+               while (1) {
+                       const char *np;
+                       while (*cp && isspace(*cp))
+                               cp++;
+                       if (!*cp)
+                               break;
+                       np = strchr(cp, '\n');
+                       if (!np)
+                               np = cp + strlen(cp);
+                       if (pass) {
+                               lrr_list[i].line = cp;
+                               lrr_list[i].name = cp + 41;
+                               lrr_list[i].namelen = np - (cp + 41);
+                       }
+                       i++;
+                       cp = np;
+               }
+               if (!pass) {
+                       lrr_count = i;
+                       lrr_list = xcalloc(lrr_count, sizeof(*lrr_list));
+               }
+       }
+
+       while (1) {
+               const char *next;
+               int rreflen;
+               int i;
+
+               while (*rref && isspace(*rref))
+                       rref++;
+               if (!*rref)
+                       break;
+               next = strchr(rref, '\n');
+               if (!next)
+                       next = rref + strlen(rref);
+               rreflen = next - rref;
+
+               for (i = 0; i < lrr_count; i++) {
+                       struct lrr *lrr = &(lrr_list[i]);
+
+                       if (rreflen == lrr->namelen &&
+                           !memcmp(lrr->name, rref, rreflen)) {
+                               if (!lrr->shown)
+                                       printf("%.*s\n",
+                                              sha1_only ? 40 : lrr->namelen + 41,
+                                              lrr->line);
+                               lrr->shown = 1;
+                               break;
+                       }
+               }
+               if (lrr_count <= i) {
+                       error("pick-rref: %.*s not found", rreflen, rref);
+                       err = 1;
+               }
+               rref = next;
+       }
+       free(lrr_list);
+       return err;
+}
+
 int cmd_fetch__tool(int argc, const char **argv, const char *prefix)
 {
        int verbose = 0;
        int force = 0;
+       int sopt = 0;
 
        while (1 < argc) {
                const char *arg = argv[1];
@@ -444,6 +521,8 @@ int cmd_fetch__tool(int argc, const char **argv, const char *prefix)
                        verbose = 1;
                else if (!strcmp("-f", arg))
                        force = 1;
+               else if (!strcmp("-s", arg))
+                       sopt = 1;
                else
                        break;
                argc--;
@@ -488,6 +567,15 @@ int cmd_fetch__tool(int argc, const char **argv, const char *prefix)
                        reflist = get_stdin();
                return parse_reflist(reflist);
        }
+       if (!strcmp("pick-rref", argv[1])) {
+               const char *ls_remote_result;
+               if (argc != 4)
+                       return error("pick-rref takes 2 args");
+               ls_remote_result = argv[3];
+               if (!strcmp(ls_remote_result, "-"))
+                       ls_remote_result = get_stdin();
+               return pick_rref(sopt, argv[2], ls_remote_result);
+       }
        if (!strcmp("expand-refs-wildcard", argv[1])) {
                const char *reflist;
                if (argc < 4)
index 75e10e25ecdee18b226fe434c270d5660eb5d74b..44ce629a498f986f239e008a61d32e10968907e0 100644 (file)
@@ -14,6 +14,7 @@
 static int show_root;
 static int show_tags;
 static int show_unreachable;
+static int include_reflogs = 1;
 static int check_full;
 static int check_strict;
 static int keep_cache_objects;
@@ -255,6 +256,7 @@ static int fsck_tree(struct tree *item)
                case S_IFREG | 0644:
                case S_IFLNK:
                case S_IFDIR:
+               case S_IFDIRLNK:
                        break;
                /*
                 * This is nonstandard, but we had a few of these
@@ -354,7 +356,7 @@ static int fsck_tag(struct tag *tag)
        return 0;
 }
 
-static int fsck_sha1(unsigned char *sha1)
+static int fsck_sha1(const unsigned char *sha1)
 {
        struct object *obj = parse_object(sha1);
        if (!obj) {
@@ -523,7 +525,8 @@ static int fsck_handle_ref(const char *refname, const unsigned char *sha1, int f
 static void get_default_heads(void)
 {
        for_each_ref(fsck_handle_ref, NULL);
-       for_each_reflog(fsck_handle_reflog, NULL);
+       if (include_reflogs)
+               for_each_reflog(fsck_handle_reflog, NULL);
 
        /*
         * Not having any default heads isn't really fatal, but
@@ -630,6 +633,10 @@ int cmd_fsck(int argc, char **argv, const char *prefix)
                        keep_cache_objects = 1;
                        continue;
                }
+               if (!strcmp(arg, "--no-reflogs")) {
+                       include_reflogs = 0;
+                       continue;
+               }
                if (!strcmp(arg, "--full")) {
                        check_full = 1;
                        continue;
@@ -661,12 +668,9 @@ int cmd_fsck(int argc, char **argv, const char *prefix)
                        verify_pack(p, 0);
 
                for (p = packed_git; p; p = p->next) {
-                       uint32_t i, num = num_packed_objects(p);
-                       for (i = 0; i < num; i++) {
-                               unsigned char sha1[20];
-                               nth_packed_object_sha1(p, i, sha1);
-                               fsck_sha1(sha1);
-                       }
+                       uint32_t i, num = p->num_objects;
+                       for (i = 0; i < num; i++)
+                               fsck_sha1(nth_packed_object_sha1(p, i));
                }
        }
 
@@ -706,8 +710,14 @@ int cmd_fsck(int argc, char **argv, const char *prefix)
                int i;
                read_cache();
                for (i = 0; i < active_nr; i++) {
-                       struct blob *blob = lookup_blob(active_cache[i]->sha1);
+                       unsigned int mode;
+                       struct blob *blob;
                        struct object *obj;
+
+                       mode = ntohl(active_cache[i]->ce_mode);
+                       if (S_ISDIRLNK(mode))
+                               continue;
+                       blob = lookup_blob(active_cache[i]->sha1);
                        if (!blob)
                                continue;
                        obj = &blob->object;
index 51294900dd12bac253a1f8a67f093f1a1dfbcb4b..37447123f924149f012a298eaf31dacb0c87b724 100644 (file)
 #include "builtin.h"
 #include "tag.h"
 #include "reflog-walk.h"
+#include "patch-ids.h"
+#include "refs.h"
 
 static int default_show_root = 1;
 
 /* this is in builtin-diff.c */
 void add_head(struct rev_info *revs);
 
+static void add_name_decoration(const char *prefix, const char *name, struct object *obj)
+{
+       int plen = strlen(prefix);
+       int nlen = strlen(name);
+       struct name_decoration *res = xmalloc(sizeof(struct name_decoration) + plen + nlen);
+       memcpy(res->name, prefix, plen);
+       memcpy(res->name + plen, name, nlen + 1);
+       res->next = add_decoration(&name_decoration, obj, res);
+}
+
+static int add_ref_decoration(const char *refname, const unsigned char *sha1, int flags, void *cb_data)
+{
+       struct object *obj = parse_object(sha1);
+       if (!obj)
+               return 0;
+       add_name_decoration("", refname, obj);
+       while (obj->type == OBJ_TAG) {
+               obj = ((struct tag *)obj)->tagged;
+               if (!obj)
+                       break;
+               add_name_decoration("tag: ", refname, obj);
+       }
+       return 0;
+}
+
 static void cmd_log_init(int argc, const char **argv, const char *prefix,
                      struct rev_info *rev)
 {
        int i;
+       int decorate = 0;
 
        rev->abbrev = DEFAULT_ABBREV;
        rev->commit_format = CMIT_FMT_DEFAULT;
@@ -38,8 +66,11 @@ static void cmd_log_init(int argc, const char **argv, const char *prefix,
                                git_log_output_encoding = xstrdup(arg);
                        else
                                git_log_output_encoding = "";
-               }
-               else
+               } else if (!strcmp(arg, "--decorate")) {
+                       if (!decorate)
+                               for_each_ref(add_ref_decoration, NULL);
+                       decorate = 1;
+               } else
                        die("unrecognized argument: %s", arg);
        }
 }
@@ -333,25 +364,12 @@ static int reopen_stdout(struct commit *commit, int nr, int keep_subject)
 
 }
 
-static int get_patch_id(struct commit *commit, struct diff_options *options,
-               unsigned char *sha1)
-{
-       if (commit->parents)
-               diff_tree_sha1(commit->parents->item->object.sha1,
-                              commit->object.sha1, "", options);
-       else
-               diff_root_tree_sha1(commit->object.sha1, "", options);
-       diffcore_std(options);
-       return diff_flush_patch_id(options, sha1);
-}
-
-static void get_patch_ids(struct rev_info *rev, struct diff_options *options, const char *prefix)
+static void get_patch_ids(struct rev_info *rev, struct patch_ids *ids, const char *prefix)
 {
        struct rev_info check_rev;
        struct commit *commit;
        struct object *o1, *o2;
        unsigned flags1, flags2;
-       unsigned char sha1[20];
 
        if (rev->pending.nr != 2)
                die("Need exactly one range.");
@@ -364,10 +382,7 @@ static void get_patch_ids(struct rev_info *rev, struct diff_options *options, co
        if ((flags1 & UNINTERESTING) == (flags2 & UNINTERESTING))
                die("Not a range.");
 
-       diff_setup(options);
-       options->recursive = 1;
-       if (diff_setup_done(options) < 0)
-               die("diff_setup_done failed");
+       init_patch_ids(ids);
 
        /* given a range a..b get all patch ids for b..a */
        init_revisions(&check_rev, prefix);
@@ -382,8 +397,7 @@ static void get_patch_ids(struct rev_info *rev, struct diff_options *options, co
                if (commit->parents && commit->parents->next)
                        continue;
 
-               if (!get_patch_id(commit, options, sha1))
-                       created_object(sha1, xcalloc(1, sizeof(struct object)));
+               add_commit_patch_id(commit, ids);
        }
 
        /* reset for next revision walk */
@@ -417,10 +431,11 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
        int numbered = 0;
        int start_number = -1;
        int keep_subject = 0;
+       int subject_prefix = 0;
        int ignore_if_in_upstream = 0;
        int thread = 0;
        const char *in_reply_to = NULL;
-       struct diff_options patch_id_opts;
+       struct patch_ids ids;
        char *add_signoff = NULL;
        char message_id[1024];
        char ref_message_id[1024];
@@ -509,8 +524,10 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
                        if (i == argc)
                                die("Need a Message-Id for --in-reply-to");
                        in_reply_to = argv[i];
-               }
-               else if (!prefixcmp(argv[i], "--suffix="))
+               } else if (!prefixcmp(argv[i], "--subject-prefix=")) {
+                       subject_prefix = 1;
+                       rev.subject_prefix = argv[i] + 17;
+               } else if (!prefixcmp(argv[i], "--suffix="))
                        fmt_patch_suffix = argv[i] + 9;
                else
                        argv[j++] = argv[i];
@@ -521,6 +538,8 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
                start_number = 1;
        if (numbered && keep_subject)
                die ("-n and -k are mutually exclusive.");
+       if (keep_subject && subject_prefix)
+               die ("--subject-prefix and -k are mutually exclusive.");
 
        argc = setup_revisions(argc, argv, &rev, "HEAD");
        if (argc > 1)
@@ -554,22 +573,19 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
        }
 
        if (ignore_if_in_upstream)
-               get_patch_ids(&rev, &patch_id_opts, prefix);
+               get_patch_ids(&rev, &ids, prefix);
 
        if (!use_stdout)
                realstdout = fdopen(dup(1), "w");
 
        prepare_revision_walk(&rev);
        while ((commit = get_revision(&rev)) != NULL) {
-               unsigned char sha1[20];
-
                /* ignore merges */
                if (commit->parents && commit->parents->next)
                        continue;
 
                if (ignore_if_in_upstream &&
-                               !get_patch_id(commit, &patch_id_opts, sha1) &&
-                               lookup_object(sha1))
+                               has_commit_patch_id(commit, &ids))
                        continue;
 
                nr++;
@@ -624,6 +640,8 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
                        fclose(stdout);
        }
        free(list);
+       if (ignore_if_in_upstream)
+               free_patch_ids(&ids);
        return 0;
 }
 
@@ -646,7 +664,7 @@ static const char cherry_usage[] =
 int cmd_cherry(int argc, const char **argv, const char *prefix)
 {
        struct rev_info revs;
-       struct diff_options patch_id_opts;
+       struct patch_ids ids;
        struct commit *commit;
        struct commit_list *list = NULL;
        const char *upstream;
@@ -692,7 +710,7 @@ int cmd_cherry(int argc, const char **argv, const char *prefix)
                        return 0;
        }
 
-       get_patch_ids(&revs, &patch_id_opts, prefix);
+       get_patch_ids(&revs, &ids, prefix);
 
        if (limit && add_pending_commit(limit, &revs, UNINTERESTING))
                die("Unknown commit %s", limit);
@@ -708,12 +726,10 @@ int cmd_cherry(int argc, const char **argv, const char *prefix)
        }
 
        while (list) {
-               unsigned char sha1[20];
                char sign = '+';
 
                commit = list->item;
-               if (!get_patch_id(commit, &patch_id_opts, sha1) &&
-                   lookup_object(sha1))
+               if (has_commit_patch_id(commit, &ids))
                        sign = '-';
 
                if (verbose) {
@@ -731,5 +747,6 @@ int cmd_cherry(int argc, const char **argv, const char *prefix)
                list = list->next;
        }
 
+       free_patch_ids(&ids);
        return 0;
 }
index 4e1d5af634a1280288d7c8110571f1136343bf3e..f7c066b24b7a6a728fd2f0bf4a92a31fb4a695dd 100644 (file)
@@ -89,20 +89,38 @@ static void show_dir_entry(const char *tag, struct dir_entry *ent)
 static void show_other_files(struct dir_struct *dir)
 {
        int i;
+
+
+       /*
+        * Skip matching and unmerged entries for the paths,
+        * since we want just "others".
+        *
+        * (Matching entries are normally pruned during
+        * the directory tree walk, but will show up for
+        * gitlinks because we don't necessarily have
+        * dir->show_other_directories set to suppress
+        * them).
+        */
        for (i = 0; i < dir->nr; i++) {
-               /* We should not have a matching entry, but we
-                * may have an unmerged entry for this path.
-                */
                struct dir_entry *ent = dir->entries[i];
-               int pos = cache_name_pos(ent->name, ent->len);
+               int len, pos;
                struct cache_entry *ce;
+
+               /*
+                * Remove the '/' at the end that directory
+                * walking adds for directory entries.
+                */
+               len = ent->len;
+               if (len && ent->name[len-1] == '/')
+                       len--;
+               pos = cache_name_pos(ent->name, len);
                if (0 <= pos)
-                       die("bug in show-other-files");
+                       continue;       /* exact match */
                pos = -pos - 1;
                if (pos < active_nr) { 
                        ce = active_cache[pos];
-                       if (ce_namelen(ce) == ent->len &&
-                           !memcmp(ce->name, ent->name, ent->len))
+                       if (ce_namelen(ce) == len &&
+                           !memcmp(ce->name, ent->name, len))
                                continue; /* Yup, this one exists unmerged */
                }
                show_dir_entry(tag_other, ent);
@@ -216,7 +234,7 @@ static void show_files(struct dir_struct *dir, const char *prefix)
 
                if (baselen)
                        path = base = prefix;
-               read_directory(dir, path, base, baselen);
+               read_directory(dir, path, base, baselen, pathspec);
                if (show_others)
                        show_other_files(dir);
                if (show_killed)
index 6472610ac2fecb8096ecab8fe29331a6fd6c009b..1cb4dca277b511315d3b914239c57621fc60bcf3 100644 (file)
@@ -6,6 +6,7 @@
 #include "cache.h"
 #include "blob.h"
 #include "tree.h"
+#include "commit.h"
 #include "quote.h"
 #include "builtin.h"
 
@@ -59,7 +60,24 @@ static int show_tree(const unsigned char *sha1, const char *base, int baselen,
        int retval = 0;
        const char *type = blob_type;
 
-       if (S_ISDIR(mode)) {
+       if (S_ISDIRLNK(mode)) {
+               /*
+                * Maybe we want to have some recursive version here?
+                *
+                * Something like:
+                *
+               if (show_subprojects(base, baselen, pathname)) {
+                       if (fork()) {
+                               chdir(base);
+                               exec ls-tree;
+                       }
+                       waitpid();
+               }
+                *
+                * ..or similar..
+                */
+               type = commit_type;
+       } else if (S_ISDIR(mode)) {
                if (show_recursive(base, baselen, pathname)) {
                        retval = READ_TREE_RECURSIVE;
                        if (!(ls_options & LS_SHOW_TREES))
index 737af350b873e90c787cb49960236fc19b62a3bf..3563216acaebba668f465895fe0563e5d7113fef 100644 (file)
@@ -77,7 +77,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
 
        git_config(git_default_config);
 
-       newfd = hold_lock_file_for_update(&lock_file, get_index_file(), 1);
+       newfd = hold_locked_index(&lock_file, 1);
        if (read_cache() < 0)
                die("index file corrupt");
 
@@ -273,7 +273,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
 
                for (i = 0; i < added.nr; i++) {
                        const char *path = added.items[i].path;
-                       add_file_to_index(path, verbose);
+                       add_file_to_cache(path, verbose);
                }
 
                for (i = 0; i < deleted.nr; i++) {
@@ -285,7 +285,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
                if (active_cache_changed) {
                        if (write_cache(newfd, active_cache, active_nr) ||
                            close(newfd) ||
-                           commit_lock_file(&lock_file))
+                           commit_locked_index(&lock_file))
                                die("Unable to write new index file");
                }
        }
index b5f9648e809a3ef7f381239470c031208ade4a34..966f843e4357d45b8c9acf96617bdacd8dc1b133 100644 (file)
@@ -12,6 +12,7 @@
 #include "diff.h"
 #include "revision.h"
 #include "list-objects.h"
+#include "progress.h"
 
 static const char pack_usage[] = "\
 git-pack-objects [{ -q | --progress | --all-progress }] \n\
@@ -22,28 +23,26 @@ git-pack-objects [{ -q | --progress | --all-progress }] \n\
 
 struct object_entry {
        unsigned char sha1[20];
+       uint32_t crc32;         /* crc of raw pack data for this object */
+       off_t offset;           /* offset into the final pack file */
        unsigned long size;     /* uncompressed size */
-       off_t offset;   /* offset into the final pack file;
-                                * nonzero if already written.
-                                */
-       unsigned int depth;     /* delta depth */
-       unsigned int delta_limit;       /* base adjustment for in-pack delta */
        unsigned int hash;      /* name hint hash */
-       enum object_type type;
-       enum object_type in_pack_type;  /* could be delta */
-       unsigned long delta_size;       /* delta data size (uncompressed) */
-#define in_pack_header_size delta_size /* only when reusing pack data */
-       struct object_entry *delta;     /* delta base object */
+       unsigned int depth;     /* delta depth */
        struct packed_git *in_pack;     /* already in pack */
        off_t in_pack_offset;
+       struct object_entry *delta;     /* delta base object */
        struct object_entry *delta_child; /* deltified objects who bases me */
        struct object_entry *delta_sibling; /* other deltified objects who
                                             * uses the same base as me
                                             */
-       int preferred_base;     /* we do not pack this, but is encouraged to
-                                * be used as the base objectto delta huge
-                                * objects against.
-                                */
+       unsigned long delta_size;       /* delta data size (uncompressed) */
+       enum object_type type;
+       enum object_type in_pack_type;  /* could be delta */
+       unsigned char in_pack_header_size;
+       unsigned char preferred_base; /* we do not pack this, but is available
+                                      * to be used as the base objectto delta
+                                      * objects against.
+                                      */
 };
 
 /*
@@ -51,36 +50,28 @@ struct object_entry {
  * expanded).  nr_objects & nr_alloc controls this array.  They are stored
  * in the order we see -- typically rev-list --objects order that gives us
  * nice "minimum seek" order.
- *
- * sorted-by-sha ans sorted-by-type are arrays of pointers that point at
- * elements in the objects array.  The former is used to build the pack
- * index (lists object names in the ascending order to help offset lookup),
- * and the latter is used to group similar things together by try_delta()
- * heuristics.
  */
+static struct object_entry *objects;
+static uint32_t nr_objects, nr_alloc, nr_result;
 
-static unsigned char object_list_sha1[20];
 static int non_empty;
 static int no_reuse_delta;
 static int local;
 static int incremental;
 static int allow_ofs_delta;
-
-static struct object_entry **sorted_by_sha, **sorted_by_type;
-static struct object_entry *objects;
-static uint32_t nr_objects, nr_alloc, nr_result;
-static const char *base_name;
+static const char *pack_tmp_name, *idx_tmp_name;
+static char tmpname[PATH_MAX];
 static unsigned char pack_file_sha1[20];
 static int progress = 1;
-static volatile sig_atomic_t progress_update;
 static int window = 10;
+static int depth = 50;
 static int pack_to_stdout;
 static int num_preferred_base;
+static struct progress progress_state;
 
 /*
  * The object names in objects array are hashed with this hashtable,
- * to help looking up the entry by object name.  Binary search from
- * sorted_by_sha is also possible but this was easier to code and faster.
+ * to help looking up the entry by object name.
  * This hashtable is built after all the objects are seen.
  */
 static int *object_ix;
@@ -164,17 +155,37 @@ static int cmp_offset(const void *a_, const void *b_)
 static void prepare_pack_revindex(struct pack_revindex *rix)
 {
        struct packed_git *p = rix->p;
-       int num_ent = num_packed_objects(p);
+       int num_ent = p->num_objects;
        int i;
        const char *index = p->index_data;
 
-       index += 4 * 256;
        rix->revindex = xmalloc(sizeof(*rix->revindex) * (num_ent + 1));
-       for (i = 0; i < num_ent; i++) {
-               uint32_t hl = *((uint32_t *)(index + 24 * i));
-               rix->revindex[i].offset = ntohl(hl);
-               rix->revindex[i].nr = i;
+       index += 4 * 256;
+
+       if (p->index_version > 1) {
+               const uint32_t *off_32 =
+                       (uint32_t *)(index + 8 + p->num_objects * (20 + 4));
+               const uint32_t *off_64 = off_32 + p->num_objects;
+               for (i = 0; i < num_ent; i++) {
+                       uint32_t off = ntohl(*off_32++);
+                       if (!(off & 0x80000000)) {
+                               rix->revindex[i].offset = off;
+                       } else {
+                               rix->revindex[i].offset =
+                                       ((uint64_t)ntohl(*off_64++)) << 32;
+                               rix->revindex[i].offset |=
+                                       ntohl(*off_64++);
+                       }
+                       rix->revindex[i].nr = i;
+               }
+       } else {
+               for (i = 0; i < num_ent; i++) {
+                       uint32_t hl = *((uint32_t *)(index + 24 * i));
+                       rix->revindex[i].offset = ntohl(hl);
+                       rix->revindex[i].nr = i;
+               }
        }
+
        /* This knows the pack format -- the 20-byte trailer
         * follows immediately after the last object data.
         */
@@ -198,7 +209,7 @@ static struct revindex_entry * find_packed_object(struct packed_git *p,
                prepare_pack_revindex(rix);
        revindex = rix->revindex;
        lo = 0;
-       hi = num_packed_objects(p) + 1;
+       hi = p->num_objects + 1;
        do {
                int mi = (lo + hi) / 2;
                if (revindex[mi].offset == ofs) {
@@ -212,17 +223,11 @@ static struct revindex_entry * find_packed_object(struct packed_git *p,
        die("internal error: pack revindex corrupt");
 }
 
-static off_t find_packed_object_size(struct packed_git *p, off_t ofs)
-{
-       struct revindex_entry *entry = find_packed_object(p, ofs);
-       return entry[1].offset - ofs;
-}
-
 static const unsigned char *find_packed_object_name(struct packed_git *p,
                                                    off_t ofs)
 {
        struct revindex_entry *entry = find_packed_object(p, ofs);
-       return ((unsigned char *)p->index_data) + 4 * 256 + 24 * entry->nr + 4;
+       return nth_packed_object_sha1(p, entry->nr);
 }
 
 static void *delta_against(void *buf, unsigned long size, struct object_entry *entry)
@@ -300,6 +305,28 @@ static int check_pack_inflate(struct packed_git *p,
                stream.total_in == len) ? 0 : -1;
 }
 
+static int check_pack_crc(struct packed_git *p, struct pack_window **w_curs,
+                         off_t offset, off_t len, unsigned int nr)
+{
+       const uint32_t *index_crc;
+       uint32_t data_crc = crc32(0, Z_NULL, 0);
+
+       do {
+               unsigned int avail;
+               void *data = use_pack(p, w_curs, offset, &avail);
+               if (avail > len)
+                       avail = len;
+               data_crc = crc32(data_crc, data, avail);
+               offset += avail;
+               len -= avail;
+       } while (len);
+
+       index_crc = p->index_data;
+       index_crc += 2 + 256 + p->num_objects * (20/4) + nr;
+
+       return data_crc != ntohl(*index_crc);
+}
+
 static void copy_pack_data(struct sha1file *f,
                struct packed_git *p,
                struct pack_window **w_curs,
@@ -369,7 +396,7 @@ static int revalidate_loose_object(struct object_entry *entry,
        return check_loose_inflate(map, mapsize, size);
 }
 
-static off_t write_object(struct sha1file *f,
+static unsigned long write_object(struct sha1file *f,
                                  struct object_entry *entry)
 {
        unsigned long size;
@@ -381,6 +408,9 @@ static off_t write_object(struct sha1file *f,
        enum object_type obj_type;
        int to_reuse = 0;
 
+       if (!pack_to_stdout)
+               crc32_begin(f);
+
        obj_type = entry->type;
        if (! entry->in_pack)
                to_reuse = 0;   /* can't reuse what we don't have */
@@ -461,6 +491,7 @@ static off_t write_object(struct sha1file *f,
        else {
                struct packed_git *p = entry->in_pack;
                struct pack_window *w_curs = NULL;
+               struct revindex_entry *revidx;
                off_t offset;
 
                if (entry->delta) {
@@ -483,12 +514,17 @@ static off_t write_object(struct sha1file *f,
                        hdrlen += 20;
                }
 
-               offset = entry->in_pack_offset + entry->in_pack_header_size;
-               datalen = find_packed_object_size(p, entry->in_pack_offset)
-                               - entry->in_pack_header_size;
-               if (!pack_to_stdout && check_pack_inflate(p, &w_curs,
-                               offset, datalen, entry->size))
-                       die("corrupt delta in pack %s", sha1_to_hex(entry->sha1));
+               offset = entry->in_pack_offset;
+               revidx = find_packed_object(p, offset);
+               datalen = revidx[1].offset - offset;
+               if (!pack_to_stdout && p->index_version > 1 &&
+                   check_pack_crc(p, &w_curs, offset, datalen, revidx->nr))
+                       die("bad packed object CRC for %s", sha1_to_hex(entry->sha1));
+               offset += entry->in_pack_header_size;
+               datalen -= entry->in_pack_header_size;
+               if (!pack_to_stdout && p->index_version == 1 &&
+                   check_pack_inflate(p, &w_curs, offset, datalen, entry->size))
+                       die("corrupt packed object for %s", sha1_to_hex(entry->sha1));
                copy_pack_data(f, p, &w_curs, offset, datalen);
                unuse_pack(&w_curs);
                reused++;
@@ -496,6 +532,8 @@ static off_t write_object(struct sha1file *f,
        if (entry->delta)
                written_delta++;
        written++;
+       if (!pack_to_stdout)
+               entry->crc32 = crc32_end(f);
        return hdrlen + datalen;
 }
 
@@ -503,36 +541,52 @@ static off_t write_one(struct sha1file *f,
                               struct object_entry *e,
                               off_t offset)
 {
+       unsigned long size;
+
+       /* offset is non zero if object is written already. */
        if (e->offset || e->preferred_base)
-               /* offset starts from header size and cannot be zero
-                * if it is written already.
-                */
                return offset;
-       /* if we are deltified, write out its base object first. */
+
+       /* if we are deltified, write out base object first. */
        if (e->delta)
                offset = write_one(f, e->delta, offset);
+
        e->offset = offset;
-       return offset + write_object(f, e);
+       size = write_object(f, e);
+
+       /* make sure off_t is sufficiently large not to wrap */
+       if (offset > offset + size)
+               die("pack too large for current definition of off_t");
+       return offset + size;
+}
+
+static int open_object_dir_tmp(const char *path)
+{
+    snprintf(tmpname, sizeof(tmpname), "%s/%s", get_object_directory(), path);
+    return mkstemp(tmpname);
 }
 
-static void write_pack_file(void)
+static off_t write_pack_file(void)
 {
        uint32_t i;
        struct sha1file *f;
-       off_t offset;
+       off_t offset, last_obj_offset = 0;
        struct pack_header hdr;
-       unsigned last_percent = 999;
        int do_progress = progress;
 
-       if (!base_name) {
+       if (pack_to_stdout) {
                f = sha1fd(1, "<stdout>");
                do_progress >>= 1;
+       } else {
+               int fd = open_object_dir_tmp("tmp_pack_XXXXXX");
+               if (fd < 0)
+                       die("unable to create %s: %s\n", tmpname, strerror(errno));
+               pack_tmp_name = xstrdup(tmpname);
+               f = sha1fd(fd, pack_tmp_name);
        }
-       else
-               f = sha1create("%s-%s.%s", base_name,
-                              sha1_to_hex(object_list_sha1), "pack");
+
        if (do_progress)
-               fprintf(stderr, "Writing %u objects.\n", nr_result);
+               start_progress(&progress_state, "Writing %u objects...", "", nr_result);
 
        hdr.hdr_signature = htonl(PACK_SIGNATURE);
        hdr.hdr_version = htonl(PACK_VERSION);
@@ -542,33 +596,70 @@ static void write_pack_file(void)
        if (!nr_result)
                goto done;
        for (i = 0; i < nr_objects; i++) {
+               last_obj_offset = offset;
                offset = write_one(f, objects + i, offset);
-               if (do_progress) {
-                       unsigned percent = written * 100 / nr_result;
-                       if (progress_update || percent != last_percent) {
-                               fprintf(stderr, "%4u%% (%u/%u) done\r",
-                                       percent, written, nr_result);
-                               progress_update = 0;
-                               last_percent = percent;
-                       }
-               }
+               if (do_progress)
+                       display_progress(&progress_state, written);
        }
        if (do_progress)
-               fputc('\n', stderr);
+               stop_progress(&progress_state);
  done:
        if (written != nr_result)
                die("wrote %u objects while expecting %u", written, nr_result);
        sha1close(f, pack_file_sha1, 1);
+
+       return last_obj_offset;
 }
 
-static void write_index_file(void)
+static int sha1_sort(const void *_a, const void *_b)
 {
-       uint32_t i;
-       struct sha1file *f = sha1create("%s-%s.%s", base_name,
-                                       sha1_to_hex(object_list_sha1), "idx");
-       struct object_entry **list = sorted_by_sha;
-       struct object_entry **last = list + nr_result;
+       const struct object_entry *a = *(struct object_entry **)_a;
+       const struct object_entry *b = *(struct object_entry **)_b;
+       return hashcmp(a->sha1, b->sha1);
+}
+
+static uint32_t index_default_version = 1;
+static uint32_t index_off32_limit = 0x7fffffff;
+
+static void write_index_file(off_t last_obj_offset, unsigned char *sha1)
+{
+       struct sha1file *f;
+       struct object_entry **sorted_by_sha, **list, **last;
        uint32_t array[256];
+       uint32_t i, index_version;
+       SHA_CTX ctx;
+
+       int fd = open_object_dir_tmp("tmp_idx_XXXXXX");
+       if (fd < 0)
+               die("unable to create %s: %s\n", tmpname, strerror(errno));
+       idx_tmp_name = xstrdup(tmpname);
+       f = sha1fd(fd, idx_tmp_name);
+
+       if (nr_result) {
+               uint32_t j = 0;
+               sorted_by_sha =
+                       xcalloc(nr_result, sizeof(struct object_entry *));
+               for (i = 0; i < nr_objects; i++)
+                       if (!objects[i].preferred_base)
+                               sorted_by_sha[j++] = objects + i;
+               if (j != nr_result)
+                       die("listed %u objects while expecting %u", j, nr_result);
+               qsort(sorted_by_sha, nr_result, sizeof(*sorted_by_sha), sha1_sort);
+               list = sorted_by_sha;
+               last = sorted_by_sha + nr_result;
+       } else
+               sorted_by_sha = list = last = NULL;
+
+       /* if last object's offset is >= 2^31 we should use index V2 */
+       index_version = (last_obj_offset >> 31) ? 2 : index_default_version;
+
+       /* index versions 2 and above need a header */
+       if (index_version >= 2) {
+               struct pack_idx_header hdr;
+               hdr.idx_signature = htonl(PACK_IDX_SIGNATURE);
+               hdr.idx_version = htonl(index_version);
+               sha1write(f, &hdr, sizeof(hdr));
+       }
 
        /*
         * Write the first-level table (the list is sorted,
@@ -588,18 +679,61 @@ static void write_index_file(void)
        }
        sha1write(f, array, 256 * 4);
 
-       /*
-        * Write the actual SHA1 entries..
-        */
+       /* Compute the SHA1 hash of sorted object names. */
+       SHA1_Init(&ctx);
+
+       /* Write the actual SHA1 entries. */
        list = sorted_by_sha;
        for (i = 0; i < nr_result; i++) {
                struct object_entry *entry = *list++;
-               uint32_t offset = htonl(entry->offset);
-               sha1write(f, &offset, 4);
+               if (index_version < 2) {
+                       uint32_t offset = htonl(entry->offset);
+                       sha1write(f, &offset, 4);
+               }
                sha1write(f, entry->sha1, 20);
+               SHA1_Update(&ctx, entry->sha1, 20);
        }
+
+       if (index_version >= 2) {
+               unsigned int nr_large_offset = 0;
+
+               /* write the crc32 table */
+               list = sorted_by_sha;
+               for (i = 0; i < nr_objects; i++) {
+                       struct object_entry *entry = *list++;
+                       uint32_t crc32_val = htonl(entry->crc32);
+                       sha1write(f, &crc32_val, 4);
+               }
+
+               /* write the 32-bit offset table */
+               list = sorted_by_sha;
+               for (i = 0; i < nr_objects; i++) {
+                       struct object_entry *entry = *list++;
+                       uint32_t offset = (entry->offset <= index_off32_limit) ?
+                               entry->offset : (0x80000000 | nr_large_offset++);
+                       offset = htonl(offset);
+                       sha1write(f, &offset, 4);
+               }
+
+               /* write the large offset table */
+               list = sorted_by_sha;
+               while (nr_large_offset) {
+                       struct object_entry *entry = *list++;
+                       uint64_t offset = entry->offset;
+                       if (offset > index_off32_limit) {
+                               uint32_t split[2];
+                               split[0]        = htonl(offset >> 32);
+                               split[1] = htonl(offset & 0xffffffff);
+                               sha1write(f, split, 8);
+                               nr_large_offset--;
+                       }
+               }
+       }
+
        sha1write(f, pack_file_sha1, 20);
        sha1close(f, NULL, 1);
+       free(sorted_by_sha);
+       SHA1_Final(sha1, &ctx);
 }
 
 static int locate_object_entry_hash(const unsigned char *sha1)
@@ -667,67 +801,70 @@ static unsigned name_hash(const char *name)
        return hash;
 }
 
-static int add_object_entry(const unsigned char *sha1, unsigned hash, int exclude)
+static int add_object_entry(const unsigned char *sha1, enum object_type type,
+                           unsigned hash, int exclude)
 {
-       uint32_t idx = nr_objects;
        struct object_entry *entry;
-       struct packed_git *p;
+       struct packed_git *p, *found_pack = NULL;
        off_t found_offset = 0;
-       struct packed_git *found_pack = NULL;
-       int ix, status = 0;
-
-       if (!exclude) {
-               for (p = packed_git; p; p = p->next) {
-                       off_t offset = find_pack_entry_one(sha1, p);
-                       if (offset) {
-                               if (incremental)
-                                       return 0;
-                               if (local && !p->pack_local)
-                                       return 0;
-                               if (!found_pack) {
-                                       found_offset = offset;
-                                       found_pack = p;
-                               }
+       int ix;
+
+       ix = nr_objects ? locate_object_entry_hash(sha1) : -1;
+       if (ix >= 0) {
+               if (exclude) {
+                       entry = objects + object_ix[ix] - 1;
+                       if (!entry->preferred_base)
+                               nr_result--;
+                       entry->preferred_base = 1;
+               }
+               return 0;
+       }
+
+       for (p = packed_git; p; p = p->next) {
+               off_t offset = find_pack_entry_one(sha1, p);
+               if (offset) {
+                       if (!found_pack) {
+                               found_offset = offset;
+                               found_pack = p;
                        }
+                       if (exclude)
+                               break;
+                       if (incremental)
+                               return 0;
+                       if (local && !p->pack_local)
+                               return 0;
                }
        }
-       if ((entry = locate_object_entry(sha1)) != NULL)
-               goto already_added;
 
-       if (idx >= nr_alloc) {
-               nr_alloc = (idx + 1024) * 3 / 2;
+       if (nr_objects >= nr_alloc) {
+               nr_alloc = (nr_alloc  + 1024) * 3 / 2;
                objects = xrealloc(objects, nr_alloc * sizeof(*entry));
        }
-       entry = objects + idx;
-       nr_objects = idx + 1;
+
+       entry = objects + nr_objects++;
        memset(entry, 0, sizeof(*entry));
        hashcpy(entry->sha1, sha1);
        entry->hash = hash;
+       if (type)
+               entry->type = type;
+       if (exclude)
+               entry->preferred_base = 1;
+       else
+               nr_result++;
+       if (found_pack) {
+               entry->in_pack = found_pack;
+               entry->in_pack_offset = found_offset;
+       }
 
        if (object_ix_hashsz * 3 <= nr_objects * 4)
                rehash_objects();
-       else {
-               ix = locate_object_entry_hash(entry->sha1);
-               if (0 <= ix)
-                       die("internal error in object hashing.");
-               object_ix[-1 - ix] = idx + 1;
-       }
-       status = 1;
+       else
+               object_ix[-1 - ix] = nr_objects;
 
- already_added:
-       if (progress_update) {
-               fprintf(stderr, "Counting objects...%u\r", nr_objects);
-               progress_update = 0;
-       }
-       if (exclude)
-               entry->preferred_base = 1;
-       else {
-               if (found_pack) {
-                       entry->in_pack = found_pack;
-                       entry->in_pack_offset = found_offset;
-               }
-       }
-       return status;
+       if (progress)
+               display_progress(&progress_state, nr_objects);
+
+       return 1;
 }
 
 struct pbase_tree_cache {
@@ -849,22 +986,23 @@ static void add_pbase_object(struct tree_desc *tree,
                             const char *fullname)
 {
        struct name_entry entry;
+       int cmp;
 
        while (tree_entry(tree,&entry)) {
-               unsigned long size;
-               enum object_type type;
-
-               if (tree_entry_len(entry.path, entry.sha1) != cmplen ||
-                   memcmp(entry.path, name, cmplen) ||
-                   !has_sha1_file(entry.sha1) ||
-                   (type = sha1_object_info(entry.sha1, &size)) < 0)
+               cmp = tree_entry_len(entry.path, entry.sha1) != cmplen ? 1 :
+                     memcmp(name, entry.path, cmplen);
+               if (cmp > 0)
                        continue;
+               if (cmp < 0)
+                       return;
                if (name[cmplen] != '/') {
                        unsigned hash = name_hash(fullname);
-                       add_object_entry(entry.sha1, hash, 1);
+                       add_object_entry(entry.sha1,
+                                        S_ISDIR(entry.mode) ? OBJ_TREE : OBJ_BLOB,
+                                        hash, 1);
                        return;
                }
-               if (type == OBJ_TREE) {
+               if (S_ISDIR(entry.mode)) {
                        struct tree_desc sub;
                        struct pbase_tree_cache *tree;
                        const char *down = name+cmplen+1;
@@ -924,15 +1062,15 @@ static int check_pbase_path(unsigned hash)
 static void add_preferred_base_object(const char *name, unsigned hash)
 {
        struct pbase_tree *it;
-       int cmplen = name_cmp_len(name);
+       int cmplen;
 
-       if (check_pbase_path(hash))
+       if (!num_preferred_base || check_pbase_path(hash))
                return;
 
+       cmplen = name_cmp_len(name);
        for (it = pbase_tree; it; it = it->next) {
                if (cmplen == 0) {
-                       hash = name_hash("");
-                       add_object_entry(it->pcache.sha1, hash, 1);
+                       add_object_entry(it->pcache.sha1, OBJ_TREE, 0, 1);
                }
                else {
                        struct tree_desc tree;
@@ -974,87 +1112,105 @@ static void add_preferred_base(unsigned char *sha1)
 
 static void check_object(struct object_entry *entry)
 {
-       if (entry->in_pack && !entry->preferred_base) {
+       if (entry->in_pack) {
                struct packed_git *p = entry->in_pack;
                struct pack_window *w_curs = NULL;
-               unsigned long size, used;
+               const unsigned char *base_ref = NULL;
+               struct object_entry *base_entry;
+               unsigned long used, used_0;
                unsigned int avail;
-               unsigned char *buf;
-               struct object_entry *base_entry = NULL;
+               off_t ofs;
+               unsigned char *buf, c;
 
                buf = use_pack(p, &w_curs, entry->in_pack_offset, &avail);
 
-               /* We want in_pack_type even if we do not reuse delta.
+               /*
+                * We want in_pack_type even if we do not reuse delta.
                 * There is no point not reusing non-delta representations.
                 */
                used = unpack_object_header_gently(buf, avail,
-                                                  &entry->in_pack_type, &size);
+                                                  &entry->in_pack_type,
+                                                  &entry->size);
 
-               /* Check if it is delta, and the base is also an object
-                * we are going to pack.  If so we will reuse the existing
-                * delta.
+               /*
+                * Determine if this is a delta and if so whether we can
+                * reuse it or not.  Otherwise let's find out as cheaply as
+                * possible what the actual type and size for this object is.
                 */
-               if (!no_reuse_delta) {
-                       unsigned char c;
-                       const unsigned char *base_name;
-                       off_t ofs;
-                       unsigned long used_0;
-                       /* there is at least 20 bytes left in the pack */
-                       switch (entry->in_pack_type) {
-                       case OBJ_REF_DELTA:
-                               base_name = use_pack(p, &w_curs,
-                                       entry->in_pack_offset + used, NULL);
-                               used += 20;
-                               break;
-                       case OBJ_OFS_DELTA:
-                               buf = use_pack(p, &w_curs,
-                                       entry->in_pack_offset + used, NULL);
-                               used_0 = 0;
-                               c = buf[used_0++];
-                               ofs = c & 127;
-                               while (c & 128) {
-                                       ofs += 1;
-                                       if (!ofs || ofs & ~(~0UL >> 7))
-                                               die("delta base offset overflow in pack for %s",
-                                                   sha1_to_hex(entry->sha1));
-                                       c = buf[used_0++];
-                                       ofs = (ofs << 7) + (c & 127);
-                               }
-                               if (ofs >= entry->in_pack_offset)
-                                       die("delta base offset out of bound for %s",
+               switch (entry->in_pack_type) {
+               default:
+                       /* Not a delta hence we've already got all we need. */
+                       entry->type = entry->in_pack_type;
+                       entry->in_pack_header_size = used;
+                       unuse_pack(&w_curs);
+                       return;
+               case OBJ_REF_DELTA:
+                       if (!no_reuse_delta && !entry->preferred_base)
+                               base_ref = use_pack(p, &w_curs,
+                                               entry->in_pack_offset + used, NULL);
+                       entry->in_pack_header_size = used + 20;
+                       break;
+               case OBJ_OFS_DELTA:
+                       buf = use_pack(p, &w_curs,
+                                      entry->in_pack_offset + used, NULL);
+                       used_0 = 0;
+                       c = buf[used_0++];
+                       ofs = c & 127;
+                       while (c & 128) {
+                               ofs += 1;
+                               if (!ofs || MSB(ofs, 7))
+                                       die("delta base offset overflow in pack for %s",
                                            sha1_to_hex(entry->sha1));
-                               ofs = entry->in_pack_offset - ofs;
-                               base_name = find_packed_object_name(p, ofs);
-                               used += used_0;
-                               break;
-                       default:
-                               base_name = NULL;
+                               c = buf[used_0++];
+                               ofs = (ofs << 7) + (c & 127);
                        }
-                       if (base_name)
-                               base_entry = locate_object_entry(base_name);
+                       if (ofs >= entry->in_pack_offset)
+                               die("delta base offset out of bound for %s",
+                                   sha1_to_hex(entry->sha1));
+                       ofs = entry->in_pack_offset - ofs;
+                       if (!no_reuse_delta && !entry->preferred_base)
+                               base_ref = find_packed_object_name(p, ofs);
+                       entry->in_pack_header_size = used + used_0;
+                       break;
                }
-               unuse_pack(&w_curs);
-               entry->in_pack_header_size = used;
 
-               if (base_entry) {
-
-                       /* Depth value does not matter - find_deltas()
-                        * will never consider reused delta as the
-                        * base object to deltify other objects
-                        * against, in order to avoid circular deltas.
+               if (base_ref && (base_entry = locate_object_entry(base_ref))) {
+                       /*
+                        * If base_ref was set above that means we wish to
+                        * reuse delta data, and we even found that base
+                        * in the list of objects we want to pack. Goodie!
+                        *
+                        * Depth value does not matter - find_deltas() will
+                        * never consider reused delta as the base object to
+                        * deltify other objects against, in order to avoid
+                        * circular deltas.
                         */
-
-                       /* uncompressed size of the delta data */
-                       entry->size = size;
-                       entry->delta = base_entry;
                        entry->type = entry->in_pack_type;
-
+                       entry->delta = base_entry;
                        entry->delta_sibling = base_entry->delta_child;
                        base_entry->delta_child = entry;
+                       unuse_pack(&w_curs);
+                       return;
+               }
 
+               if (entry->type) {
+                       /*
+                        * This must be a delta and we already know what the
+                        * final object type is.  Let's extract the actual
+                        * object size from the delta header.
+                        */
+                       entry->size = get_size_from_delta(p, &w_curs,
+                                       entry->in_pack_offset + entry->in_pack_header_size);
+                       unuse_pack(&w_curs);
                        return;
                }
-               /* Otherwise we would do the usual */
+
+               /*
+                * No choice but to fall back to the recursive delta walk
+                * with sha1_object_info() to find about the object type
+                * at this point...
+                */
+               unuse_pack(&w_curs);
        }
 
        entry->type = sha1_object_info(entry->sha1, &entry->size);
@@ -1063,94 +1219,44 @@ static void check_object(struct object_entry *entry)
                    sha1_to_hex(entry->sha1));
 }
 
-static unsigned int check_delta_limit(struct object_entry *me, unsigned int n)
+static int pack_offset_sort(const void *_a, const void *_b)
 {
-       struct object_entry *child = me->delta_child;
-       unsigned int m = n;
-       while (child) {
-               unsigned int c = check_delta_limit(child, n + 1);
-               if (m < c)
-                       m = c;
-               child = child->delta_sibling;
-       }
-       return m;
-}
-
-static void get_object_details(void)
-{
-       uint32_t i;
-       struct object_entry *entry;
-
-       prepare_pack_ix();
-       for (i = 0, entry = objects; i < nr_objects; i++, entry++)
-               check_object(entry);
-
-       if (nr_objects == nr_result) {
-               /*
-                * Depth of objects that depend on the entry -- this
-                * is subtracted from depth-max to break too deep
-                * delta chain because of delta data reusing.
-                * However, we loosen this restriction when we know we
-                * are creating a thin pack -- it will have to be
-                * expanded on the other end anyway, so do not
-                * artificially cut the delta chain and let it go as
-                * deep as it wants.
-                */
-               for (i = 0, entry = objects; i < nr_objects; i++, entry++)
-                       if (!entry->delta && entry->delta_child)
-                               entry->delta_limit =
-                                       check_delta_limit(entry, 1);
-       }
-}
+       const struct object_entry *a = *(struct object_entry **)_a;
+       const struct object_entry *b = *(struct object_entry **)_b;
 
-typedef int (*entry_sort_t)(const struct object_entry *, const struct object_entry *);
+       /* avoid filesystem trashing with loose objects */
+       if (!a->in_pack && !b->in_pack)
+               return hashcmp(a->sha1, b->sha1);
 
-static entry_sort_t current_sort;
-
-static int sort_comparator(const void *_a, const void *_b)
-{
-       struct object_entry *a = *(struct object_entry **)_a;
-       struct object_entry *b = *(struct object_entry **)_b;
-       return current_sort(a,b);
+       if (a->in_pack < b->in_pack)
+               return -1;
+       if (a->in_pack > b->in_pack)
+               return 1;
+       return a->in_pack_offset < b->in_pack_offset ? -1 :
+                       (a->in_pack_offset > b->in_pack_offset);
 }
 
-static struct object_entry **create_sorted_list(entry_sort_t sort)
+static void get_object_details(void)
 {
-       struct object_entry **list = xmalloc(nr_objects * sizeof(struct object_entry *));
        uint32_t i;
+       struct object_entry **sorted_by_offset;
 
+       sorted_by_offset = xcalloc(nr_objects, sizeof(struct object_entry *));
        for (i = 0; i < nr_objects; i++)
-               list[i] = objects + i;
-       current_sort = sort;
-       qsort(list, nr_objects, sizeof(struct object_entry *), sort_comparator);
-       return list;
-}
+               sorted_by_offset[i] = objects + i;
+       qsort(sorted_by_offset, nr_objects, sizeof(*sorted_by_offset), pack_offset_sort);
 
-static int sha1_sort(const struct object_entry *a, const struct object_entry *b)
-{
-       return hashcmp(a->sha1, b->sha1);
+       prepare_pack_ix();
+       for (i = 0; i < nr_objects; i++)
+               check_object(sorted_by_offset[i]);
+       free(sorted_by_offset);
 }
 
-static struct object_entry **create_final_object_list(void)
+static int type_size_sort(const void *_a, const void *_b)
 {
-       struct object_entry **list;
-       uint32_t i, j;
-
-       for (i = nr_result = 0; i < nr_objects; i++)
-               if (!objects[i].preferred_base)
-                       nr_result++;
-       list = xmalloc(nr_result * sizeof(struct object_entry *));
-       for (i = j = 0; i < nr_objects; i++) {
-               if (!objects[i].preferred_base)
-                       list[j++] = objects + i;
-       }
-       current_sort = sha1_sort;
-       qsort(list, nr_result, sizeof(struct object_entry *), sort_comparator);
-       return list;
-}
+       const struct object_entry *a = *(struct object_entry **)_a;
+       const struct object_entry *b = *(struct object_entry **)_b;
 
-static int type_size_sort(const struct object_entry *a, const struct object_entry *b)
-{
        if (a->type < b->type)
                return -1;
        if (a->type > b->type)
@@ -1167,7 +1273,7 @@ static int type_size_sort(const struct object_entry *a, const struct object_entr
                return -1;
        if (a->size > b->size)
                return 1;
-       return a < b ? -1 : (a > b);
+       return a > b ? -1 : (a < b);  /* newest last */
 }
 
 struct unpacked {
@@ -1213,16 +1319,7 @@ static int try_delta(struct unpacked *trg, struct unpacked *src,
            trg_entry->in_pack_type != OBJ_OFS_DELTA)
                return 0;
 
-       /*
-        * If the current object is at pack edge, take the depth the
-        * objects that depend on the current object into account --
-        * otherwise they would become too deep.
-        */
-       if (trg_entry->delta_child) {
-               if (max_depth <= trg_entry->delta_limit)
-                       return 0;
-               max_depth -= trg_entry->delta_limit;
-       }
+       /* Let's not bust the allowed depth. */
        if (src_entry->depth >= max_depth)
                return 0;
 
@@ -1269,9 +1366,17 @@ static int try_delta(struct unpacked *trg, struct unpacked *src,
        return 1;
 }
 
-static void progress_interval(int signum)
+static unsigned int check_delta_limit(struct object_entry *me, unsigned int n)
 {
-       progress_update = 1;
+       struct object_entry *child = me->delta_child;
+       unsigned int m = n;
+       while (child) {
+               unsigned int c = check_delta_limit(child, n + 1);
+               if (m < c)
+                       m = c;
+               child = child->delta_sibling;
+       }
+       return m;
 }
 
 static void find_deltas(struct object_entry **list, int window, int depth)
@@ -1279,14 +1384,14 @@ static void find_deltas(struct object_entry **list, int window, int depth)
        uint32_t i = nr_objects, idx = 0, processed = 0;
        unsigned int array_size = window * sizeof(struct unpacked);
        struct unpacked *array;
-       unsigned last_percent = 999;
+       int max_depth;
 
        if (!nr_objects)
                return;
        array = xmalloc(array_size);
        memset(array, 0, array_size);
        if (progress)
-               fprintf(stderr, "Deltifying %u objects.\n", nr_result);
+               start_progress(&progress_state, "Deltifying %u objects...", "", nr_result);
 
        do {
                struct object_entry *entry = list[--i];
@@ -1296,15 +1401,8 @@ static void find_deltas(struct object_entry **list, int window, int depth)
                if (!entry->preferred_base)
                        processed++;
 
-               if (progress) {
-                       unsigned percent = processed * 100 / nr_result;
-                       if (percent != last_percent || progress_update) {
-                               fprintf(stderr, "%4u%% (%u/%u) done\r",
-                                       percent, processed, nr_result);
-                               progress_update = 0;
-                               last_percent = percent;
-                       }
-               }
+               if (progress)
+                       display_progress(&progress_state, processed);
 
                if (entry->delta)
                        /* This happens if we decided to reuse existing
@@ -1320,6 +1418,18 @@ static void find_deltas(struct object_entry **list, int window, int depth)
                n->data = NULL;
                n->entry = entry;
 
+               /*
+                * If the current object is at pack edge, take the depth the
+                * objects that depend on the current object into account
+                * otherwise they would become too deep.
+                */
+               max_depth = depth;
+               if (entry->delta_child) {
+                       max_depth -= check_delta_limit(entry, 0);
+                       if (max_depth <= 0)
+                               goto next;
+               }
+
                j = window;
                while (--j > 0) {
                        uint32_t other_idx = idx + j;
@@ -1329,9 +1439,10 @@ static void find_deltas(struct object_entry **list, int window, int depth)
                        m = array + other_idx;
                        if (!m->entry)
                                break;
-                       if (try_delta(n, m, depth) < 0)
+                       if (try_delta(n, m, max_depth) < 0)
                                break;
                }
+
                /* if we made n a delta, and if n is already at max
                 * depth, leaving it in the window is pointless.  we
                 * should evict it first.
@@ -1339,13 +1450,14 @@ static void find_deltas(struct object_entry **list, int window, int depth)
                if (entry->delta && depth <= entry->depth)
                        continue;
 
+               next:
                idx++;
                if (idx >= window)
                        idx = 0;
        } while (i > 0);
 
        if (progress)
-               fputc('\n', stderr);
+               stop_progress(&progress_state);
 
        for (i = 0; i < window; ++i) {
                free_delta_index(array[i].index);
@@ -1356,81 +1468,20 @@ static void find_deltas(struct object_entry **list, int window, int depth)
 
 static void prepare_pack(int window, int depth)
 {
-       get_object_details();
-       sorted_by_type = create_sorted_list(type_size_sort);
-       if (window && depth)
-               find_deltas(sorted_by_type, window+1, depth);
-}
-
-static int reuse_cached_pack(unsigned char *sha1)
-{
-       static const char cache[] = "pack-cache/pack-%s.%s";
-       char *cached_pack, *cached_idx;
-       int ifd, ofd, ifd_ix = -1;
-
-       cached_pack = git_path(cache, sha1_to_hex(sha1), "pack");
-       ifd = open(cached_pack, O_RDONLY);
-       if (ifd < 0)
-               return 0;
-
-       if (!pack_to_stdout) {
-               cached_idx = git_path(cache, sha1_to_hex(sha1), "idx");
-               ifd_ix = open(cached_idx, O_RDONLY);
-               if (ifd_ix < 0) {
-                       close(ifd);
-                       return 0;
-               }
-       }
-
-       if (progress)
-               fprintf(stderr, "Reusing %u objects pack %s\n", nr_objects,
-                       sha1_to_hex(sha1));
+       struct object_entry **delta_list;
+       uint32_t i;
 
-       if (pack_to_stdout) {
-               if (copy_fd(ifd, 1))
-                       exit(1);
-               close(ifd);
-       }
-       else {
-               char name[PATH_MAX];
-               snprintf(name, sizeof(name),
-                        "%s-%s.%s", base_name, sha1_to_hex(sha1), "pack");
-               ofd = open(name, O_CREAT | O_EXCL | O_WRONLY, 0666);
-               if (ofd < 0)
-                       die("unable to open %s (%s)", name, strerror(errno));
-               if (copy_fd(ifd, ofd))
-                       exit(1);
-               close(ifd);
-
-               snprintf(name, sizeof(name),
-                        "%s-%s.%s", base_name, sha1_to_hex(sha1), "idx");
-               ofd = open(name, O_CREAT | O_EXCL | O_WRONLY, 0666);
-               if (ofd < 0)
-                       die("unable to open %s (%s)", name, strerror(errno));
-               if (copy_fd(ifd_ix, ofd))
-                       exit(1);
-               close(ifd_ix);
-               puts(sha1_to_hex(sha1));
-       }
+       get_object_details();
 
-       return 1;
-}
+       if (!window || !depth)
+               return;
 
-static void setup_progress_signal(void)
-{
-       struct sigaction sa;
-       struct itimerval v;
-
-       memset(&sa, 0, sizeof(sa));
-       sa.sa_handler = progress_interval;
-       sigemptyset(&sa.sa_mask);
-       sa.sa_flags = SA_RESTART;
-       sigaction(SIGALRM, &sa, NULL);
-
-       v.it_interval.tv_sec = 1;
-       v.it_interval.tv_usec = 0;
-       v.it_value = v.it_interval;
-       setitimer(ITIMER_REAL, &v, NULL);
+       delta_list = xmalloc(nr_objects * sizeof(*delta_list));
+       for (i = 0; i < nr_objects; i++)
+               delta_list[i] = objects + i;
+       qsort(delta_list, nr_objects, sizeof(*delta_list), type_size_sort);
+       find_deltas(delta_list, window+1, depth);
+       free(delta_list);
 }
 
 static int git_pack_config(const char *k, const char *v)
@@ -1439,6 +1490,10 @@ static int git_pack_config(const char *k, const char *v)
                window = git_config_int(k, v);
                return 0;
        }
+       if(!strcmp(k, "pack.depth")) {
+               depth = git_config_int(k, v);
+               return 0;
+       }
        return git_default_config(k, v);
 }
 
@@ -1471,22 +1526,20 @@ static void read_object_list_from_stdin(void)
 
                hash = name_hash(line+41);
                add_preferred_base_object(line+41, hash);
-               add_object_entry(sha1, hash, 0);
+               add_object_entry(sha1, 0, hash, 0);
        }
 }
 
 static void show_commit(struct commit *commit)
 {
-       unsigned hash = name_hash("");
-       add_preferred_base_object("", hash);
-       add_object_entry(commit->object.sha1, hash, 0);
+       add_object_entry(commit->object.sha1, OBJ_COMMIT, 0, 0);
 }
 
 static void show_object(struct object_array_entry *p)
 {
        unsigned hash = name_hash(p->name);
        add_preferred_base_object(p->name, hash);
-       add_object_entry(p->item->sha1, hash, 0);
+       add_object_entry(p->item->sha1, p->item->type, hash, 0);
 }
 
 static void show_edge(struct commit *commit)
@@ -1527,14 +1580,20 @@ static void get_object_list(int ac, const char **av)
        traverse_commit_list(&revs, show_commit, show_object);
 }
 
+static int adjust_perm(const char *path, mode_t mode)
+{
+       if (chmod(path, mode))
+               return -1;
+       return adjust_shared_perm(path);
+}
+
 int cmd_pack_objects(int argc, const char **argv, const char *prefix)
 {
-       SHA_CTX ctx;
-       int depth = 10;
-       struct object_entry **list;
        int use_internal_rev_list = 0;
        int thin = 0;
        uint32_t i;
+       off_t last_obj_offset;
+       const char *base_name = NULL;
        const char **rp_av;
        int rp_ac_alloc = 64;
        int rp_ac;
@@ -1627,6 +1686,17 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
                        rp_av[1] = "--objects-edge";
                        continue;
                }
+               if (!prefixcmp(arg, "--index-version=")) {
+                       char *c;
+                       index_default_version = strtoul(arg + 16, &c, 10);
+                       if (index_default_version > 2)
+                               die("bad %s", arg);
+                       if (*c == ',')
+                               index_off32_limit = strtoul(c+1, &c, 0);
+                       if (*c || index_off32_limit & 0x80000000)
+                               die("bad %s", arg);
+                       continue;
+               }
                usage(pack_usage);
        }
 
@@ -1654,51 +1724,52 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
 
        prepare_packed_git();
 
-       if (progress) {
-               fprintf(stderr, "Generating pack...\n");
-               setup_progress_signal();
-       }
-
+       if (progress)
+               start_progress(&progress_state, "Generating pack...",
+                              "Counting objects: ", 0);
        if (!use_internal_rev_list)
                read_object_list_from_stdin();
        else {
                rp_av[rp_ac] = NULL;
                get_object_list(rp_ac, rp_av);
        }
-
-       if (progress)
+       if (progress) {
+               stop_progress(&progress_state);
                fprintf(stderr, "Done counting %u objects.\n", nr_objects);
-       sorted_by_sha = create_final_object_list();
+       }
+
        if (non_empty && !nr_result)
                return 0;
-
-       SHA1_Init(&ctx);
-       list = sorted_by_sha;
-       for (i = 0; i < nr_result; i++) {
-               struct object_entry *entry = *list++;
-               SHA1_Update(&ctx, entry->sha1, 20);
-       }
-       SHA1_Final(object_list_sha1, &ctx);
        if (progress && (nr_objects != nr_result))
                fprintf(stderr, "Result has %u objects.\n", nr_result);
-
-       if (reuse_cached_pack(object_list_sha1))
-               ;
-       else {
-               if (nr_result)
-                       prepare_pack(window, depth);
-               if (progress == 1 && pack_to_stdout) {
-                       /* the other end usually displays progress itself */
-                       struct itimerval v = {{0,},};
-                       setitimer(ITIMER_REAL, &v, NULL);
-                       signal(SIGALRM, SIG_IGN );
-                       progress_update = 0;
-               }
-               write_pack_file();
-               if (!pack_to_stdout) {
-                       write_index_file();
-                       puts(sha1_to_hex(object_list_sha1));
-               }
+       if (nr_result)
+               prepare_pack(window, depth);
+       last_obj_offset = write_pack_file();
+       if (!pack_to_stdout) {
+               unsigned char object_list_sha1[20];
+               mode_t mode = umask(0);
+
+               umask(mode);
+               mode = 0444 & ~mode;
+
+               write_index_file(last_obj_offset, object_list_sha1);
+               snprintf(tmpname, sizeof(tmpname), "%s-%s.pack",
+                        base_name, sha1_to_hex(object_list_sha1));
+               if (adjust_perm(pack_tmp_name, mode))
+                       die("unable to make temporary pack file readable: %s",
+                           strerror(errno));
+               if (rename(pack_tmp_name, tmpname))
+                       die("unable to rename temporary pack file: %s",
+                           strerror(errno));
+               snprintf(tmpname, sizeof(tmpname), "%s-%s.idx",
+                        base_name, sha1_to_hex(object_list_sha1));
+               if (adjust_perm(idx_tmp_name, mode))
+                       die("unable to make temporary index file readable: %s",
+                           strerror(errno));
+               if (rename(idx_tmp_name, tmpname))
+                       die("unable to rename temporary index file: %s",
+                           strerror(errno));
+               puts(sha1_to_hex(object_list_sha1));
        }
        if (progress)
                fprintf(stderr, "Total %u (delta %u), reused %u (delta %u)\n",
index 70b1168fa677fc889543e87b2a3f964b175375c6..cb78401c946eac9019c2e4e953c3b87ef9e214f3 100644 (file)
@@ -297,7 +297,7 @@ static int read_config(const char *repo, const char *uri[MAX_URI])
 static int do_push(const char *repo)
 {
        const char *uri[MAX_URI];
-       int i, n;
+       int i, n, errs;
        int common_argc;
        const char **argv;
        int argc;
@@ -317,6 +317,7 @@ static int do_push(const char *repo)
                argv[argc++] = receivepack;
        common_argc = argc;
 
+       errs = 0;
        for (i = 0; i < n; i++) {
                int err;
                int dest_argc = common_argc;
@@ -339,21 +340,23 @@ static int do_push(const char *repo)
                err = run_command_v_opt(argv, RUN_GIT_CMD);
                if (!err)
                        continue;
+
+               error("failed to push to '%s'", uri[i]);
                switch (err) {
                case -ERR_RUN_COMMAND_FORK:
-                       die("unable to fork for %s", sender);
+                       error("unable to fork for %s", sender);
                case -ERR_RUN_COMMAND_EXEC:
-                       die("unable to exec %s", sender);
+                       error("unable to exec %s", sender);
+                       break;
                case -ERR_RUN_COMMAND_WAITPID:
                case -ERR_RUN_COMMAND_WAITPID_WRONG_PID:
                case -ERR_RUN_COMMAND_WAITPID_SIGNAL:
                case -ERR_RUN_COMMAND_WAITPID_NOEXIT:
-                       die("%s died with strange error", sender);
-               default:
-                       return -err;
+                       error("%s died with strange error", sender);
                }
+               errs++;
        }
-       return 0;
+       return !!errs;
 }
 
 int cmd_push(int argc, const char **argv, const char *prefix)
index 793eae0a5f4cc3d21788a3642f156410ce1ad89d..316fb0f8dae022b35a89b71c94a22331a77a500a 100644 (file)
@@ -84,7 +84,7 @@ static void prime_cache_tree(void)
 
 }
 
-static const char read_tree_usage[] = "git-read-tree (<sha> | [[-m [--aggressive] | --reset | --prefix=<prefix>] [-u | -i]] [--exclude-per-directory=<gitignore>] <sha1> [<sha2> [<sha3>]])";
+static const char read_tree_usage[] = "git-read-tree (<sha> | [[-m [--aggressive] | --reset | --prefix=<prefix>] [-u | -i]] [--exclude-per-directory=<gitignore>] [--index-output=<file>] <sha1> [<sha2> [<sha3>]])";
 
 static struct lock_file lock_file;
 
@@ -100,7 +100,7 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix)
        setup_git_directory();
        git_config(git_default_config);
 
-       newfd = hold_lock_file_for_update(&lock_file, get_index_file(), 1);
+       newfd = hold_locked_index(&lock_file, 1);
 
        git_config(git_default_config);
 
@@ -128,6 +128,11 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix)
                        continue;
                }
 
+               if (!prefixcmp(arg, "--index-output=")) {
+                       set_alternate_index_output(arg + 15);
+                       continue;
+               }
+
                /* "--prefix=<subdirectory>/" means keep the current index
                 *  entries and put the entries from the tree under the
                 * given subdirectory.
@@ -228,6 +233,7 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix)
                if (0 <= pos)
                        die("file '%.*s' already exists.",
                                        pfxlen-1, opts.prefix);
+               opts.pos = -1 - pos;
        }
 
        if (opts.merge) {
@@ -267,7 +273,7 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix)
        }
 
        if (write_cache(newfd, active_cache, active_nr) ||
-           close(newfd) || commit_lock_file(&lock_file))
+           close(newfd) || commit_locked_index(&lock_file))
                die("unable to write new index file");
        return 0;
 }
index b86e7ca8b18c7d11886dd3a1b4ca0eba95af264a..ebf53f5944f1a53c5336f69e2ef3105ce50d0823 100644 (file)
@@ -37,7 +37,8 @@ static const char rev_list_usage[] =
 "    --abbrev-commit\n"
 "    --left-right\n"
 "  special purpose:\n"
-"    --bisect"
+"    --bisect\n"
+"    --bisect-vars"
 ;
 
 static struct rev_info revs;
@@ -94,7 +95,7 @@ static void show_commit(struct commit *commit)
                static char pretty_header[16384];
                pretty_print_commit(revs.commit_format, commit, ~0,
                                    pretty_header, sizeof(pretty_header),
-                                   revs.abbrev, NULL, NULL, revs.relative_date);
+                                   revs.abbrev, NULL, NULL, revs.date_mode);
                printf("%s%c", pretty_header, hdr_termination);
        }
        fflush(stdout);
@@ -112,6 +113,10 @@ static void show_object(struct object_array_entry *p)
         * confuse downstream git-pack-objects very badly.
         */
        const char *ep = strchr(p->name, '\n');
+
+       if (p->item->type == OBJ_BLOB && !has_sha1_file(p->item->sha1))
+               die("missing blob object '%s'", sha1_to_hex(p->item->sha1));
+
        if (ep) {
                printf("%s %.*s\n", sha1_to_hex(p->item->sha1),
                       (int) (ep - p->name),
@@ -169,38 +174,273 @@ static void clear_distance(struct commit_list *list)
        }
 }
 
-static struct commit_list *find_bisection(struct commit_list *list)
+#define DEBUG_BISECT 0
+
+static inline int weight(struct commit_list *elem)
 {
-       int nr, closest;
-       struct commit_list *p, *best;
+       return *((int*)(elem->item->util));
+}
 
-       nr = 0;
-       p = list;
-       while (p) {
-               if (!revs.prune_fn || (p->item->object.flags & TREECHANGE))
-                       nr++;
-               p = p->next;
+static inline void weight_set(struct commit_list *elem, int weight)
+{
+       *((int*)(elem->item->util)) = weight;
+}
+
+static int count_interesting_parents(struct commit *commit)
+{
+       struct commit_list *p;
+       int count;
+
+       for (count = 0, p = commit->parents; p; p = p->next) {
+               if (p->item->object.flags & UNINTERESTING)
+                       continue;
+               count++;
        }
-       closest = -1;
-       best = list;
+       return count;
+}
+
+static inline int halfway(struct commit_list *p, int distance, int nr)
+{
+       /*
+        * Don't short-cut something we are not going to return!
+        */
+       if (revs.prune_fn && !(p->item->object.flags & TREECHANGE))
+               return 0;
+       if (DEBUG_BISECT)
+               return 0;
+       /*
+        * 2 and 3 are halfway of 5.
+        * 3 is halfway of 6 but 2 and 4 are not.
+        */
+       distance *= 2;
+       switch (distance - nr) {
+       case -1: case 0: case 1:
+               return 1;
+       default:
+               return 0;
+       }
+}
+
+#if !DEBUG_BISECT
+#define show_list(a,b,c,d) do { ; } while (0)
+#else
+static void show_list(const char *debug, int counted, int nr,
+                     struct commit_list *list)
+{
+       struct commit_list *p;
+
+       fprintf(stderr, "%s (%d/%d)\n", debug, counted, nr);
 
        for (p = list; p; p = p->next) {
-               int distance;
+               struct commit_list *pp;
+               struct commit *commit = p->item;
+               unsigned flags = commit->object.flags;
+               enum object_type type;
+               unsigned long size;
+               char *buf = read_sha1_file(commit->object.sha1, &type, &size);
+               char *ep, *sp;
+
+               fprintf(stderr, "%c%c%c ",
+                       (flags & TREECHANGE) ? 'T' : ' ',
+                       (flags & UNINTERESTING) ? 'U' : ' ',
+                       (flags & COUNTED) ? 'C' : ' ');
+               if (commit->util)
+                       fprintf(stderr, "%3d", weight(p));
+               else
+                       fprintf(stderr, "---");
+               fprintf(stderr, " %.*s", 8, sha1_to_hex(commit->object.sha1));
+               for (pp = commit->parents; pp; pp = pp->next)
+                       fprintf(stderr, " %.*s", 8,
+                               sha1_to_hex(pp->item->object.sha1));
+
+               sp = strstr(buf, "\n\n");
+               if (sp) {
+                       sp += 2;
+                       for (ep = sp; *ep && *ep != '\n'; ep++)
+                               ;
+                       fprintf(stderr, " %.*s", (int)(ep - sp), sp);
+               }
+               fprintf(stderr, "\n");
+       }
+}
+#endif /* DEBUG_BISECT */
+
+/*
+ * zero or positive weight is the number of interesting commits it can
+ * reach, including itself.  Especially, weight = 0 means it does not
+ * reach any tree-changing commits (e.g. just above uninteresting one
+ * but traversal is with pathspec).
+ *
+ * weight = -1 means it has one parent and its distance is yet to
+ * be computed.
+ *
+ * weight = -2 means it has more than one parent and its distance is
+ * unknown.  After running count_distance() first, they will get zero
+ * or positive distance.
+ */
+
+static struct commit_list *find_bisection(struct commit_list *list,
+                                         int *reaches, int *all)
+{
+       int n, nr, on_list, counted, distance;
+       struct commit_list *p, *best, *next, *last;
+       int *weights;
 
-               if (revs.prune_fn && !(p->item->object.flags & TREECHANGE))
+       show_list("bisection 2 entry", 0, 0, list);
+
+       /*
+        * Count the number of total and tree-changing items on the
+        * list, while reversing the list.
+        */
+       for (nr = on_list = 0, last = NULL, p = list;
+            p;
+            p = next) {
+               unsigned flags = p->item->object.flags;
+
+               next = p->next;
+               if (flags & UNINTERESTING)
                        continue;
+               p->next = last;
+               last = p;
+               if (!revs.prune_fn || (flags & TREECHANGE))
+                       nr++;
+               on_list++;
+       }
+       list = last;
+       show_list("bisection 2 sorted", 0, nr, list);
+
+       *all = nr;
+       weights = xcalloc(on_list, sizeof(int*));
+       counted = 0;
+
+       for (n = 0, p = list; p; p = p->next) {
+               struct commit *commit = p->item;
+               unsigned flags = commit->object.flags;
+
+               p->item->util = &weights[n++];
+               switch (count_interesting_parents(commit)) {
+               case 0:
+                       if (!revs.prune_fn || (flags & TREECHANGE)) {
+                               weight_set(p, 1);
+                               counted++;
+                               show_list("bisection 2 count one",
+                                         counted, nr, list);
+                       }
+                       /*
+                        * otherwise, it is known not to reach any
+                        * tree-changing commit and gets weight 0.
+                        */
+                       break;
+               case 1:
+                       weight_set(p, -1);
+                       break;
+               default:
+                       weight_set(p, -2);
+                       break;
+               }
+       }
 
+       show_list("bisection 2 initialize", counted, nr, list);
+
+       /*
+        * If you have only one parent in the resulting set
+        * then you can reach one commit more than that parent
+        * can reach.  So we do not have to run the expensive
+        * count_distance() for single strand of pearls.
+        *
+        * However, if you have more than one parents, you cannot
+        * just add their distance and one for yourself, since
+        * they usually reach the same ancestor and you would
+        * end up counting them twice that way.
+        *
+        * So we will first count distance of merges the usual
+        * way, and then fill the blanks using cheaper algorithm.
+        */
+       for (p = list; p; p = p->next) {
+               if (p->item->object.flags & UNINTERESTING)
+                       continue;
+               n = weight(p);
+               if (n != -2)
+                       continue;
                distance = count_distance(p);
                clear_distance(list);
+               weight_set(p, distance);
+
+               /* Does it happen to be at exactly half-way? */
+               if (halfway(p, distance, nr)) {
+                       p->next = NULL;
+                       *reaches = distance;
+                       free(weights);
+                       return p;
+               }
+               counted++;
+       }
+
+       show_list("bisection 2 count_distance", counted, nr, list);
+
+       while (counted < nr) {
+               for (p = list; p; p = p->next) {
+                       struct commit_list *q;
+                       unsigned flags = p->item->object.flags;
+
+                       if (0 <= weight(p))
+                               continue;
+                       for (q = p->item->parents; q; q = q->next) {
+                               if (q->item->object.flags & UNINTERESTING)
+                                       continue;
+                               if (0 <= weight(q))
+                                       break;
+                       }
+                       if (!q)
+                               continue;
+
+                       /*
+                        * weight for p is unknown but q is known.
+                        * add one for p itself if p is to be counted,
+                        * otherwise inherit it from q directly.
+                        */
+                       if (!revs.prune_fn || (flags & TREECHANGE)) {
+                               weight_set(p, weight(q)+1);
+                               counted++;
+                               show_list("bisection 2 count one",
+                                         counted, nr, list);
+                       }
+                       else
+                               weight_set(p, weight(q));
+
+                       /* Does it happen to be at exactly half-way? */
+                       distance = weight(p);
+                       if (halfway(p, distance, nr)) {
+                               p->next = NULL;
+                               *reaches = distance;
+                               free(weights);
+                               return p;
+                       }
+               }
+       }
+
+       show_list("bisection 2 counted all", counted, nr, list);
+
+       /* Then find the best one */
+       counted = -1;
+       best = list;
+       for (p = list; p; p = p->next) {
+               unsigned flags = p->item->object.flags;
+
+               if (revs.prune_fn && !(flags & TREECHANGE))
+                       continue;
+               distance = weight(p);
                if (nr - distance < distance)
                        distance = nr - distance;
-               if (distance > closest) {
+               if (distance > counted) {
                        best = p;
-                       closest = distance;
+                       counted = distance;
+                       *reaches = weight(p);
                }
        }
        if (best)
                best->next = NULL;
+       free(weights);
        return best;
 }
 
@@ -226,6 +466,7 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix)
        struct commit_list *list;
        int i;
        int read_from_stdin = 0;
+       int bisect_show_vars = 0;
 
        git_config(git_default_config);
        init_revisions(&revs, prefix);
@@ -248,6 +489,11 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix)
                        bisect_list = 1;
                        continue;
                }
+               if (!strcmp(arg, "--bisect-vars")) {
+                       bisect_list = 1;
+                       bisect_show_vars = 1;
+                       continue;
+               }
                if (!strcmp(arg, "--stdin")) {
                        if (read_from_stdin++)
                                die("--stdin given twice?");
@@ -286,8 +532,39 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix)
        if (revs.tree_objects)
                mark_edges_uninteresting(revs.commits, &revs, show_edge);
 
-       if (bisect_list)
-               revs.commits = find_bisection(revs.commits);
+       if (bisect_list) {
+               int reaches = reaches, all = all;
+
+               revs.commits = find_bisection(revs.commits, &reaches, &all);
+               if (bisect_show_vars) {
+                       int cnt;
+                       if (!revs.commits)
+                               return 1;
+                       /*
+                        * revs.commits can reach "reaches" commits among
+                        * "all" commits.  If it is good, then there are
+                        * (all-reaches) commits left to be bisected.
+                        * On the other hand, if it is bad, then the set
+                        * to bisect is "reaches".
+                        * A bisect set of size N has (N-1) commits further
+                        * to test, as we already know one bad one.
+                        */
+                       cnt = all-reaches;
+                       if (cnt < reaches)
+                               cnt = reaches;
+                       printf("bisect_rev=%s\n"
+                              "bisect_nr=%d\n"
+                              "bisect_good=%d\n"
+                              "bisect_bad=%d\n"
+                              "bisect_all=%d\n",
+                              sha1_to_hex(revs.commits->item->object.sha1),
+                              cnt - 1,
+                              all - reaches - 1,
+                              reaches - 1,
+                              all);
+                       return 0;
+               }
+       }
 
        traverse_commit_list(&revs, show_commit, show_object);
 
index 4ba0ee63ab4ca5e77aa350d1956f1b3312945f5d..ea2f15b977a8b62189755ae89e3ee704b45afae8 100644 (file)
@@ -133,7 +133,7 @@ static void add_to_msg(const char *string)
 {
        int len = strlen(string);
        if (write_in_full(msg_fd, string, len) < 0)
-               die ("Could not write to .msg");
+               die ("Could not write to MERGE_MSG");
 }
 
 static void add_message_to_msg(const char *message)
@@ -237,6 +237,7 @@ static int revert_or_cherry_pick(int argc, const char **argv)
        int i;
        char *oneline, *reencoded_message = NULL;
        const char *message, *encoding;
+       const char *defmsg = xstrdup(git_path("MERGE_MSG"));
 
        git_config(git_default_config);
        me = action == REVERT ? "revert" : "cherry-pick";
@@ -280,7 +281,7 @@ static int revert_or_cherry_pick(int argc, const char **argv)
         * reverse of it if we are revert.
         */
 
-       msg_fd = hold_lock_file_for_update(&msg_file, ".msg", 1);
+       msg_fd = hold_lock_file_for_update(&msg_file, defmsg, 1);
 
        encoding = get_encoding(message);
        if (!encoding)
@@ -330,7 +331,6 @@ static int revert_or_cherry_pick(int argc, const char **argv)
                                sha1_to_hex(head), "HEAD",
                                sha1_to_hex(next->object.sha1), oneline) ||
                        write_tree(head, 0, NULL)) {
-               const char *target = git_path("MERGE_MSG");
                add_to_msg("\nConflicts:\n\n");
                read_cache();
                for (i = 0; i < active_nr;) {
@@ -345,10 +345,7 @@ static int revert_or_cherry_pick(int argc, const char **argv)
                        }
                }
                if (close(msg_fd) || commit_lock_file(&msg_file) < 0)
-                       die ("Error wrapping up .msg");
-               unlink(target);
-               if (rename(".msg", target))
-                       die ("Could not move .msg to %s", target);
+                       die ("Error wrapping up %s", defmsg);
                fprintf(stderr, "Automatic %s failed.  "
                        "After resolving the conflicts,\n"
                        "mark the corrected paths with 'git-add <paths>'\n"
@@ -362,7 +359,7 @@ static int revert_or_cherry_pick(int argc, const char **argv)
                exit(1);
        }
        if (close(msg_fd) || commit_lock_file(&msg_file) < 0)
-               die ("Error wrapping up .msg");
+               die ("Error wrapping up %s", defmsg);
        fprintf(stderr, "Finished one %s.\n", me);
 
        /*
@@ -376,11 +373,9 @@ static int revert_or_cherry_pick(int argc, const char **argv)
 
        if (!no_commit) {
                if (edit)
-                       return execl_git_cmd("commit", "-n", "-F", ".msg",
-                               "-e", NULL);
+                       return execl_git_cmd("commit", "-n", NULL);
                else
-                       return execl_git_cmd("commit", "-n", "-F", ".msg",
-                               NULL);
+                       return execl_git_cmd("commit", "-n", "-F", defmsg, NULL);
        }
        if (reencoded_message)
                free(reencoded_message);
index bf42003a7e79f96bf4964b116ddd3ee79350e682..4a0bd93c8b3b644fb86ce05686b09d79b180bffc 100644 (file)
@@ -10,7 +10,7 @@
 #include "tree-walk.h"
 
 static const char builtin_rm_usage[] =
-"git-rm [-f] [-n] [-r] [--cached] [--] <file>...";
+"git-rm [-f] [-n] [-r] [--cached] [--ignore-unmatch] [--quiet] [--] <file>...";
 
 static struct {
        int nr, alloc;
@@ -104,13 +104,14 @@ static struct lock_file lock_file;
 int cmd_rm(int argc, const char **argv, const char *prefix)
 {
        int i, newfd;
-       int show_only = 0, force = 0, index_only = 0, recursive = 0;
+       int show_only = 0, force = 0, index_only = 0, recursive = 0, quiet = 0;
+       int ignore_unmatch = 0;
        const char **pathspec;
        char *seen;
 
        git_config(git_default_config);
 
-       newfd = hold_lock_file_for_update(&lock_file, get_index_file(), 1);
+       newfd = hold_locked_index(&lock_file, 1);
 
        if (read_cache() < 0)
                die("index file corrupt");
@@ -132,6 +133,10 @@ int cmd_rm(int argc, const char **argv, const char *prefix)
                        force = 1;
                else if (!strcmp(arg, "-r"))
                        recursive = 1;
+               else if (!strcmp(arg, "--quiet"))
+                       quiet = 1;
+               else if (!strcmp(arg, "--ignore-unmatch"))
+                       ignore_unmatch = 1;
                else
                        usage(builtin_rm_usage);
        }
@@ -153,14 +158,24 @@ int cmd_rm(int argc, const char **argv, const char *prefix)
 
        if (pathspec) {
                const char *match;
+               int seen_any = 0;
                for (i = 0; (match = pathspec[i]) != NULL ; i++) {
-                       if (!seen[i])
-                               die("pathspec '%s' did not match any files",
-                                   match);
+                       if (!seen[i]) {
+                               if (!ignore_unmatch) {
+                                       die("pathspec '%s' did not match any files",
+                                           match);
+                               }
+                       }
+                       else {
+                               seen_any = 1;
+                       }
                        if (!recursive && seen[i] == MATCHED_RECURSIVELY)
                                die("not removing '%s' recursively without -r",
                                    *match ? match : ".");
                }
+
+               if (! seen_any)
+                       exit(0);
        }
 
        /*
@@ -168,7 +183,7 @@ int cmd_rm(int argc, const char **argv, const char *prefix)
         * must match; but the file can already been removed, since
         * this sequence is a natural "novice" way:
         *
-        *      rm F; git fm F
+        *      rm F; git rm F
         *
         * Further, if HEAD commit exists, "diff-index --cached" must
         * report no changes unless forced.
@@ -187,7 +202,8 @@ int cmd_rm(int argc, const char **argv, const char *prefix)
         */
        for (i = 0; i < list.nr; i++) {
                const char *path = list.name[i];
-               printf("rm '%s'\n", path);
+               if (!quiet)
+                       printf("rm '%s'\n", path);
 
                if (remove_file_from_cache(path))
                        die("git-rm: unable to remove %s", path);
@@ -220,7 +236,7 @@ int cmd_rm(int argc, const char **argv, const char *prefix)
 
        if (active_cache_changed) {
                if (write_cache(newfd, active_cache, active_nr) ||
-                   close(newfd) || commit_lock_file(&lock_file))
+                   close(newfd) || commit_locked_index(&lock_file))
                        die("Unable to write new index file");
        }
 
index 29343aefc843c4dd22095f559262bc6b5e381440..8d3f742d432c966e5afe03e8d0628eb92a73920c 100644 (file)
@@ -4,6 +4,8 @@
 #include "diff.h"
 #include "path-list.h"
 #include "revision.h"
+#include "utf8.h"
+#include "mailmap.h"
 
 static const char shortlog_usage[] =
 "git-shortlog [-n] [-s] [<commit-id>... ]";
@@ -25,83 +27,6 @@ static int compare_by_number(const void *a1, const void *a2)
 
 static struct path_list mailmap = {NULL, 0, 0, 0};
 
-static int read_mailmap(const char *filename)
-{
-       char buffer[1024];
-       FILE *f = fopen(filename, "r");
-
-       if (f == NULL)
-               return 1;
-       while (fgets(buffer, sizeof(buffer), f) != NULL) {
-               char *end_of_name, *left_bracket, *right_bracket;
-               char *name, *email;
-               int i;
-               if (buffer[0] == '#') {
-                       static const char abbrev[] = "# repo-abbrev:";
-                       int abblen = sizeof(abbrev) - 1;
-                       int len = strlen(buffer);
-
-                       if (len && buffer[len - 1] == '\n')
-                               buffer[--len] = 0;
-                       if (!strncmp(buffer, abbrev, abblen)) {
-                               char *cp;
-
-                               if (common_repo_prefix)
-                                       free(common_repo_prefix);
-                               common_repo_prefix = xmalloc(len);
-
-                               for (cp = buffer + abblen; isspace(*cp); cp++)
-                                       ; /* nothing */
-                               strcpy(common_repo_prefix, cp);
-                       }
-                       continue;
-               }
-               if ((left_bracket = strchr(buffer, '<')) == NULL)
-                       continue;
-               if ((right_bracket = strchr(left_bracket + 1, '>')) == NULL)
-                       continue;
-               if (right_bracket == left_bracket + 1)
-                       continue;
-               for (end_of_name = left_bracket; end_of_name != buffer
-                               && isspace(end_of_name[-1]); end_of_name--)
-                       /* keep on looking */
-               if (end_of_name == buffer)
-                       continue;
-               name = xmalloc(end_of_name - buffer + 1);
-               strlcpy(name, buffer, end_of_name - buffer + 1);
-               email = xmalloc(right_bracket - left_bracket);
-               for (i = 0; i < right_bracket - left_bracket - 1; i++)
-                       email[i] = tolower(left_bracket[i + 1]);
-               email[right_bracket - left_bracket - 1] = '\0';
-               path_list_insert(email, &mailmap)->util = name;
-       }
-       fclose(f);
-       return 0;
-}
-
-static int map_email(char *email, char *name, int maxlen)
-{
-       char *p;
-       struct path_list_item *item;
-
-       /* autocomplete common developers */
-       p = strchr(email, '>');
-       if (!p)
-               return 0;
-
-       *p = '\0';
-       /* downcase the email address */
-       for (p = email; *p; p++)
-               *p = tolower(*p);
-       item = path_list_lookup(email, &mailmap);
-       if (item != NULL) {
-               const char *realname = (const char *)item->util;
-               strncpy(name, realname, maxlen);
-               return 1;
-       }
-       return 0;
-}
-
 static void insert_author_oneline(struct path_list *list,
                const char *author, int authorlen,
                const char *oneline, int onelinelen)
@@ -183,7 +108,7 @@ static void read_from_stdin(struct path_list *list)
                                (bob = strchr(buffer + 7, '<')) != NULL) {
                        char buffer2[1024], offset = 0;
 
-                       if (map_email(bob + 1, buffer, sizeof(buffer)))
+                       if (map_email(&mailmap, bob + 1, buffer, sizeof(buffer)))
                                bob = buffer + strlen(buffer);
                        else {
                                offset = 8;
@@ -237,7 +162,7 @@ static void get_from_rev(struct rev_info *rev, struct path_list *list)
                                        die("Invalid commit buffer: %s",
                                            sha1_to_hex(commit->object.sha1));
 
-                               if (map_email(bracket + 1, scratch,
+                               if (map_email(&mailmap, bracket + 1, scratch,
                                                        sizeof(scratch))) {
                                        author = scratch;
                                        authorlen = strlen(scratch);
@@ -276,11 +201,64 @@ static void get_from_rev(struct rev_info *rev, struct path_list *list)
 
 }
 
+static int parse_uint(char const **arg, int comma)
+{
+       unsigned long ul;
+       int ret;
+       char *endp;
+
+       ul = strtoul(*arg, &endp, 10);
+       if (endp != *arg && *endp && *endp != comma)
+               return -1;
+       ret = (int) ul;
+       if (ret != ul)
+               return -1;
+       *arg = endp;
+       if (**arg)
+               (*arg)++;
+       return ret;
+}
+
+static const char wrap_arg_usage[] = "-w[<width>[,<indent1>[,<indent2>]]]";
+#define DEFAULT_WRAPLEN 76
+#define DEFAULT_INDENT1 6
+#define DEFAULT_INDENT2 9
+
+static void parse_wrap_args(const char *arg, int *in1, int *in2, int *wrap)
+{
+       arg += 2; /* skip -w */
+
+       *wrap = parse_uint(&arg, ',');
+       if (*wrap < 0)
+               die(wrap_arg_usage);
+       *in1 = parse_uint(&arg, ',');
+       if (*in1 < 0)
+               die(wrap_arg_usage);
+       *in2 = parse_uint(&arg, '\0');
+       if (*in2 < 0)
+               die(wrap_arg_usage);
+
+       if (!*wrap)
+               *wrap = DEFAULT_WRAPLEN;
+       if (!*in1)
+               *in1 = DEFAULT_INDENT1;
+       if (!*in2)
+               *in2 = DEFAULT_INDENT2;
+       if (*wrap &&
+           ((*in1 && *wrap <= *in1) ||
+            (*in2 && *wrap <= *in2)))
+               die(wrap_arg_usage);
+}
+
 int cmd_shortlog(int argc, const char **argv, const char *prefix)
 {
        struct rev_info rev;
        struct path_list list = { NULL, 0, 0, 1 };
        int i, j, sort_by_number = 0, summary = 0;
+       int wrap_lines = 0;
+       int wrap = DEFAULT_WRAPLEN;
+       int in1 = DEFAULT_INDENT1;
+       int in2 = DEFAULT_INDENT2;
 
        /* since -n is a shadowed rev argument, parse our args first */
        while (argc > 1) {
@@ -289,6 +267,10 @@ int cmd_shortlog(int argc, const char **argv, const char *prefix)
                else if (!strcmp(argv[1], "-s") ||
                                !strcmp(argv[1], "--summary"))
                        summary = 1;
+               else if (!prefixcmp(argv[1], "-w")) {
+                       wrap_lines = 1;
+                       parse_wrap_args(argv[1], &in1, &in2, &wrap);
+               }
                else if (!strcmp(argv[1], "-h") || !strcmp(argv[1], "--help"))
                        usage(shortlog_usage);
                else
@@ -301,8 +283,7 @@ int cmd_shortlog(int argc, const char **argv, const char *prefix)
        if (argc > 1)
                die ("unrecognized argument: %s", argv[1]);
 
-       if (!access(".mailmap", R_OK))
-               read_mailmap(".mailmap");
+       read_mailmap(&mailmap, ".mailmap", &common_repo_prefix);
 
        if (rev.pending.nr == 0) {
                if (isatty(0))
@@ -323,9 +304,18 @@ int cmd_shortlog(int argc, const char **argv, const char *prefix)
                        printf("%s: %d\n", list.items[i].path, onelines->nr);
                } else {
                        printf("%s (%d):\n", list.items[i].path, onelines->nr);
-                       for (j = onelines->nr - 1; j >= 0; j--)
-                               printf("      %s\n", onelines->items[j].path);
-                       printf("\n");
+                       for (j = onelines->nr - 1; j >= 0; j--) {
+                               const char *msg = onelines->items[j].path;
+
+                               if (wrap_lines) {
+                                       int col = print_wrapped_text(msg, in1, in2, wrap);
+                                       if (col != wrap)
+                                               putchar('\n');
+                               }
+                               else
+                                       printf("      %s\n", msg);
+                       }
+                       putchar('\n');
                }
 
                onelines->strdup_paths = 1;
index 3956c5633448a5c29c60cad370ec7da6a8bfeb64..a6ff62fd8c66f075550e01718acf56d90b44d4bb 100644 (file)
@@ -7,13 +7,15 @@
 #include "commit.h"
 #include "tag.h"
 #include "tree.h"
+#include "progress.h"
 
 static int dry_run, quiet, recover, has_errors;
 static const char unpack_usage[] = "git-unpack-objects [-n] [-q] [-r] < pack-file";
 
 /* We always read in 4kB chunks. */
 static unsigned char buffer[4096];
-static unsigned long offset, len, consumed_bytes;
+static unsigned int offset, len;
+static off_t consumed_bytes;
 static SHA_CTX ctx;
 
 /*
@@ -32,7 +34,7 @@ static void *fill(int min)
                offset = 0;
        }
        do {
-               int ret = xread(0, buffer + len, sizeof(buffer) - len);
+               ssize_t ret = xread(0, buffer + len, sizeof(buffer) - len);
                if (ret <= 0) {
                        if (!ret)
                                die("early EOF");
@@ -49,6 +51,10 @@ static void use(int bytes)
                die("used more bytes than were available");
        len -= bytes;
        offset += bytes;
+
+       /* make sure off_t is sufficiently large not to wrap */
+       if (consumed_bytes > consumed_bytes + bytes)
+               die("pack too large for current definition of off_t");
        consumed_bytes += bytes;
 }
 
@@ -88,17 +94,17 @@ static void *get_data(unsigned long size)
 
 struct delta_info {
        unsigned char base_sha1[20];
-       unsigned long base_offset;
+       unsigned nr;
+       off_t base_offset;
        unsigned long size;
        void *delta;
-       unsigned nr;
        struct delta_info *next;
 };
 
 static struct delta_info *delta_list;
 
 static void add_delta_to_list(unsigned nr, unsigned const char *base_sha1,
-                             unsigned long base_offset,
+                             off_t base_offset,
                              void *delta, unsigned long size)
 {
        struct delta_info *info = xmalloc(sizeof(*info));
@@ -113,7 +119,7 @@ static void add_delta_to_list(unsigned nr, unsigned const char *base_sha1,
 }
 
 struct obj_info {
-       unsigned long offset;
+       off_t offset;
        unsigned char sha1[20];
 };
 
@@ -200,7 +206,7 @@ static void unpack_delta_entry(enum object_type type, unsigned long delta_size,
        } else {
                unsigned base_found = 0;
                unsigned char *pack, c;
-               unsigned long base_offset;
+               off_t base_offset;
                unsigned lo, mid, hi;
 
                pack = fill(1);
@@ -209,7 +215,7 @@ static void unpack_delta_entry(enum object_type type, unsigned long delta_size,
                base_offset = c & 127;
                while (c & 128) {
                        base_offset += 1;
-                       if (!base_offset || base_offset & ~(~0UL >> 7))
+                       if (!base_offset || MSB(base_offset, 7))
                                die("offset value overflow for delta base object");
                        pack = fill(1);
                        c = *pack;
@@ -259,7 +265,7 @@ static void unpack_delta_entry(enum object_type type, unsigned long delta_size,
        free(base);
 }
 
-static void unpack_one(unsigned nr, unsigned total)
+static void unpack_one(unsigned nr)
 {
        unsigned shift;
        unsigned char *pack, c;
@@ -281,20 +287,7 @@ static void unpack_one(unsigned nr, unsigned total)
                size += (c & 0x7f) << shift;
                shift += 7;
        }
-       if (!quiet) {
-               static unsigned long last_sec;
-               static unsigned last_percent;
-               struct timeval now;
-               unsigned percentage = ((nr+1) * 100) / total;
-
-               gettimeofday(&now, NULL);
-               if (percentage != last_percent || now.tv_sec != last_sec) {
-                       last_sec = now.tv_sec;
-                       last_percent = percentage;
-                       fprintf(stderr, "%4u%% (%u/%u) done\r",
-                                       percentage, (nr+1), total);
-               }
-       }
+
        switch (type) {
        case OBJ_COMMIT:
        case OBJ_TREE:
@@ -318,6 +311,7 @@ static void unpack_one(unsigned nr, unsigned total)
 static void unpack_all(void)
 {
        int i;
+       struct progress progress;
        struct pack_header *hdr = fill(sizeof(struct pack_header));
        unsigned nr_objects = ntohl(hdr->hdr_entries);
 
@@ -325,12 +319,19 @@ static void unpack_all(void)
                die("bad pack file");
        if (!pack_version_ok(hdr->hdr_version))
                die("unknown pack file version %d", ntohl(hdr->hdr_version));
-       fprintf(stderr, "Unpacking %d objects\n", nr_objects);
+       use(sizeof(struct pack_header));
 
+       if (!quiet)
+               start_progress(&progress, "Unpacking %u objects...", "", nr_objects);
        obj_list = xmalloc(nr_objects * sizeof(*obj_list));
-       use(sizeof(struct pack_header));
-       for (i = 0; i < nr_objects; i++)
-               unpack_one(i, nr_objects);
+       for (i = 0; i < nr_objects; i++) {
+               unpack_one(i);
+               if (!quiet)
+                       display_progress(&progress, i + 1);
+       }
+       if (!quiet)
+               stop_progress(&progress);
+
        if (delta_list)
                die("unresolved deltas left after unpacking");
 }
@@ -399,7 +400,5 @@ int cmd_unpack_objects(int argc, const char **argv, const char *prefix)
        }
 
        /* All done */
-       if (!quiet)
-               fprintf(stderr, "\n");
        return has_errors;
 }
index 8659800eec055913327192747b3c342e4da462b8..8f9899178b1755ee0acd4a34ee185a4f54fb4219 100644 (file)
@@ -9,6 +9,7 @@
 #include "cache-tree.h"
 #include "tree-walk.h"
 #include "builtin.h"
+#include "refs.h"
 
 /*
  * Default to not allowing changes to the list of files. The
@@ -60,78 +61,153 @@ static int mark_valid(const char *path)
        return -1;
 }
 
-static int add_file_to_cache(const char *path)
+static int remove_one_path(const char *path)
 {
-       int size, namelen, option, status;
-       struct cache_entry *ce;
-       struct stat st;
-
-       status = lstat(path, &st);
-
-       /* We probably want to do this in remove_file_from_cache() and
-        * add_cache_entry() instead...
-        */
-       cache_tree_invalidate_path(active_cache_tree, path);
+       if (!allow_remove)
+               return error("%s: does not exist and --remove not passed", path);
+       if (remove_file_from_cache(path))
+               return error("%s: cannot remove from the index", path);
+       return 0;
+}
 
-       if (status < 0 || S_ISDIR(st.st_mode)) {
-               /* When we used to have "path" and now we want to add
-                * "path/file", we need a way to remove "path" before
-                * being able to add "path/file".  However,
-                * "git-update-index --remove path" would not work.
-                * --force-remove can be used but this is more user
-                * friendly, especially since we can do the opposite
-                * case just fine without --force-remove.
-                */
-               if (status == 0 || (errno == ENOENT || errno == ENOTDIR)) {
-                       if (allow_remove) {
-                               if (remove_file_from_cache(path))
-                                       return error("%s: cannot remove from the index",
-                                                    path);
-                               else
-                                       return 0;
-                       } else if (status < 0) {
-                               return error("%s: does not exist and --remove not passed",
-                                            path);
-                       }
-               }
-               if (0 == status)
-                       return error("%s: is a directory - add files inside instead",
-                                    path);
-               else
-                       return error("lstat(\"%s\"): %s", path,
-                                    strerror(errno));
-       }
+/*
+ * Handle a path that couldn't be lstat'ed. It's either:
+ *  - missing file (ENOENT or ENOTDIR). That's ok if we're
+ *    supposed to be removing it and the removal actually
+ *    succeeds.
+ *  - permission error. That's never ok.
+ */
+static int process_lstat_error(const char *path, int err)
+{
+       if (err == ENOENT || err == ENOTDIR)
+               return remove_one_path(path);
+       return error("lstat(\"%s\"): %s", path, strerror(errno));
+}
 
-       namelen = strlen(path);
-       size = cache_entry_size(namelen);
-       ce = xcalloc(1, size);
-       memcpy(ce->name, path, namelen);
-       ce->ce_flags = htons(namelen);
-       fill_stat_cache_info(ce, &st);
-
-       if (trust_executable_bit && has_symlinks)
-               ce->ce_mode = create_ce_mode(st.st_mode);
-       else {
-               /* If there is an existing entry, pick the mode bits and type
-                * from it, otherwise assume unexecutable regular file.
-                */
-               struct cache_entry *ent;
-               int pos = cache_name_pos(path, namelen);
+static int add_one_path(struct cache_entry *old, const char *path, int len, struct stat *st)
+{
+       int option, size = cache_entry_size(len);
+       struct cache_entry *ce = xcalloc(1, size);
 
-               ent = (0 <= pos) ? active_cache[pos] : NULL;
-               ce->ce_mode = ce_mode_from_stat(ent, st.st_mode);
-       }
+       memcpy(ce->name, path, len);
+       ce->ce_flags = htons(len);
+       fill_stat_cache_info(ce, st);
+       ce->ce_mode = ce_mode_from_stat(old, st->st_mode);
 
-       if (index_path(ce->sha1, path, &st, !info_only))
+       if (index_path(ce->sha1, path, st, !info_only))
                return -1;
        option = allow_add ? ADD_CACHE_OK_TO_ADD : 0;
        option |= allow_replace ? ADD_CACHE_OK_TO_REPLACE : 0;
        if (add_cache_entry(ce, option))
-               return error("%s: cannot add to the index - missing --add option?",
-                            path);
+               return error("%s: cannot add to the index - missing --add option?", path);
        return 0;
 }
 
+/*
+ * Handle a path that was a directory. Four cases:
+ *
+ *  - it's already a gitlink in the index, and we keep it that
+ *    way, and update it if we can (if we cannot find the HEAD,
+ *    we're going to keep it unchanged in the index!)
+ *
+ *  - it's a *file* in the index, in which case it should be
+ *    removed as a file if removal is allowed, since it doesn't
+ *    exist as such any more. If removal isn't allowed, it's
+ *    an error.
+ *
+ *    (NOTE! This is old and arguably fairly strange behaviour.
+ *    We might want to make this an error unconditionally, and
+ *    use "--force-remove" if you actually want to force removal).
+ *
+ *  - it used to exist as a subdirectory (ie multiple files with
+ *    this particular prefix) in the index, in which case it's wrong
+ *    to try to update it as a directory.
+ *
+ *  - it doesn't exist at all in the index, but it is a valid
+ *    git directory, and it should be *added* as a gitlink.
+ */
+static int process_directory(const char *path, int len, struct stat *st)
+{
+       unsigned char sha1[20];
+       int pos = cache_name_pos(path, len);
+
+       /* Exact match: file or existing gitlink */
+       if (pos >= 0) {
+               struct cache_entry *ce = active_cache[pos];
+               if (S_ISDIRLNK(ntohl(ce->ce_mode))) {
+
+                       /* Do nothing to the index if there is no HEAD! */
+                       if (resolve_gitlink_ref(path, "HEAD", sha1) < 0)
+                               return 0;
+
+                       return add_one_path(ce, path, len, st);
+               }
+               /* Should this be an unconditional error? */
+               return remove_one_path(path);
+       }
+
+       /* Inexact match: is there perhaps a subdirectory match? */
+       pos = -pos-1;
+       while (pos < active_nr) {
+               struct cache_entry *ce = active_cache[pos++];
+
+               if (strncmp(ce->name, path, len))
+                       break;
+               if (ce->name[len] > '/')
+                       break;
+               if (ce->name[len] < '/')
+                       continue;
+
+               /* Subdirectory match - error out */
+               return error("%s: is a directory - add individual files instead", path);
+       }
+
+       /* No match - should we add it as a gitlink? */
+       if (!resolve_gitlink_ref(path, "HEAD", sha1))
+               return add_one_path(NULL, path, len, st);
+
+       /* Error out. */
+       return error("%s: is a directory - add files inside instead", path);
+}
+
+/*
+ * Process a regular file
+ */
+static int process_file(const char *path, int len, struct stat *st)
+{
+       int pos = cache_name_pos(path, len);
+       struct cache_entry *ce = pos < 0 ? NULL : active_cache[pos];
+
+       if (ce && S_ISDIRLNK(ntohl(ce->ce_mode)))
+               return error("%s is already a gitlink, not replacing", path);
+
+       return add_one_path(ce, path, len, st);
+}
+
+static int process_path(const char *path)
+{
+       int len;
+       struct stat st;
+
+       /* We probably want to do this in remove_file_from_cache() and
+        * add_cache_entry() instead...
+        */
+       cache_tree_invalidate_path(active_cache_tree, path);
+
+       /*
+        * First things first: get the stat information, to decide
+        * what to do about the pathname!
+        */
+       if (lstat(path, &st) < 0)
+               return process_lstat_error(path, errno);
+
+       len = strlen(path);
+       if (S_ISDIR(st.st_mode))
+               return process_directory(path, len, &st);
+
+       return process_file(path, len, &st);
+}
+
 static int add_cacheinfo(unsigned int mode, const unsigned char *sha1,
                         const char *path, int stage)
 {
@@ -210,8 +286,8 @@ static void update_one(const char *path, const char *prefix, int prefix_length)
                report("remove '%s'", path);
                goto free_return;
        }
-       if (add_file_to_cache(p))
-               die("Unable to process file %s", path);
+       if (process_path(p))
+               die("Unable to process path %s", path);
        report("add '%s'", path);
  free_return:
        if (p < path || p > path + strlen(path))
@@ -499,7 +575,7 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)
        /* We can't free this memory, it becomes part of a linked list parsed atexit() */
        lock_file = xcalloc(1, sizeof(struct lock_file));
 
-       newfd = hold_lock_file_for_update(lock_file, get_index_file(), 0);
+       newfd = hold_locked_index(lock_file, 0);
        if (newfd < 0)
                lock_error = errno;
 
@@ -665,7 +741,7 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)
                            get_index_file(), strerror(lock_error));
                }
                if (write_cache(newfd, active_cache, active_nr) ||
-                   close(newfd) || commit_lock_file(lock_file))
+                   close(newfd) || commit_locked_index(lock_file))
                        die("Unable to write new index file");
        }
 
index a1894814f7356e5689416560aa6cda868583ce2b..391de53972ebf77d2e08f1b405969e065bd8b371 100644 (file)
@@ -18,7 +18,7 @@ int write_tree(unsigned char *sha1, int missing_ok, const char *prefix)
        /* We can't free this memory, it becomes part of a linked list parsed atexit() */
        struct lock_file *lock_file = xcalloc(1, sizeof(struct lock_file));
 
-       newfd = hold_lock_file_for_update(lock_file, get_index_file(), 0);
+       newfd = hold_locked_index(lock_file, 1);
 
        entries = read_cache();
        if (entries < 0)
index af203e9e367b1dc1abb012234b5a8ae5e09f1629..d3f3a7496e1c1adbe3f8bae36603fafee374c8c5 100644 (file)
--- a/builtin.h
+++ b/builtin.h
@@ -22,6 +22,7 @@ extern int cmd_branch(int argc, const char **argv, const char *prefix);
 extern int cmd_bundle(int argc, const char **argv, const char *prefix);
 extern int cmd_cat_file(int argc, const char **argv, const char *prefix);
 extern int cmd_checkout_index(int argc, const char **argv, const char *prefix);
+extern int cmd_check_attr(int argc, const char **argv, const char *prefix);
 extern int cmd_check_ref_format(int argc, const char **argv, const char *prefix);
 extern int cmd_cherry(int argc, const char **argv, const char *prefix);
 extern int cmd_cherry_pick(int argc, const char **argv, const char *prefix);
index 9b73c8669a0946c3bcbf1de777e9acd4cd34bcae..6369cc7c536ba7b82a6afcb191628beefe889b72 100644 (file)
@@ -326,7 +326,7 @@ static int update_one(struct cache_tree *it,
                        mode = ntohl(ce->ce_mode);
                        entlen = pathlen - baselen;
                }
-               if (!missing_ok && !has_sha1_file(sha1))
+               if (mode != S_IFDIRLNK && !missing_ok && !has_sha1_file(sha1))
                        return error("invalid object %s", sha1_to_hex(sha1));
 
                if (!ce->ce_mode)
diff --git a/cache.h b/cache.h
index 7cedda684f81e5b7bc111c2322ef1f4b2fc97d90..4204bc168c11fc7f8764e7d92e5935d2dc30c3bd 100644 (file)
--- a/cache.h
+++ b/cache.h
 #define DTYPE(de)      DT_UNKNOWN
 #endif
 
+/* unknown mode (impossible combination S_IFIFO|S_IFCHR) */
+#define S_IFINVALID     0030000
+
+/*
+ * A "directory link" is a link to another git directory.
+ *
+ * The value 0160000 is not normally a valid mode, and
+ * also just happens to be S_IFDIR + S_IFLNK
+ *
+ * NOTE! We *really* shouldn't depend on the S_IFxxx macros
+ * always having the same values everywhere. We should use
+ * our internal git values for these things, and then we can
+ * translate that to the OS-specific value. It just so
+ * happens that everybody shares the same bit representation
+ * in the UNIX world (and apparently wider too..)
+ */
+#define S_IFDIRLNK     0160000
+#define S_ISDIRLNK(m)  (((m) & S_IFMT) == S_IFDIRLNK)
+
 /*
  * Intensive research over the course of many years has shown that
  * port 9418 is totally unused by anything else. Or
@@ -104,6 +123,8 @@ static inline unsigned int create_ce_mode(unsigned int mode)
 {
        if (S_ISLNK(mode))
                return htonl(S_IFLNK);
+       if (S_ISDIR(mode) || S_ISDIRLNK(mode))
+               return htonl(S_IFDIRLNK);
        return htonl(S_IFREG | ce_permissions(mode));
 }
 static inline unsigned int ce_mode_from_stat(struct cache_entry *ce, unsigned int mode)
@@ -121,14 +142,41 @@ static inline unsigned int ce_mode_from_stat(struct cache_entry *ce, unsigned in
 }
 #define canon_mode(mode) \
        (S_ISREG(mode) ? (S_IFREG | ce_permissions(mode)) : \
-       S_ISLNK(mode) ? S_IFLNK : S_IFDIR)
+       S_ISLNK(mode) ? S_IFLNK : S_ISDIR(mode) ? S_IFDIR : S_IFDIRLNK)
 
 #define cache_entry_size(len) ((offsetof(struct cache_entry,name) + (len) + 8) & ~7)
 
-extern struct cache_entry **active_cache;
-extern unsigned int active_nr, active_alloc, active_cache_changed;
-extern struct cache_tree *active_cache_tree;
-extern int cache_errno;
+struct index_state {
+       struct cache_entry **cache;
+       unsigned int cache_nr, cache_alloc, cache_changed;
+       struct cache_tree *cache_tree;
+       time_t timestamp;
+       void *mmap;
+       size_t mmap_size;
+};
+
+extern struct index_state the_index;
+
+#ifndef NO_THE_INDEX_COMPATIBILITY_MACROS
+#define active_cache (the_index.cache)
+#define active_nr (the_index.cache_nr)
+#define active_alloc (the_index.cache_alloc)
+#define active_cache_changed (the_index.cache_changed)
+#define active_cache_tree (the_index.cache_tree)
+
+#define read_cache() read_index(&the_index)
+#define read_cache_from(path) read_index_from(&the_index, (path))
+#define write_cache(newfd, cache, entries) write_index(&the_index, (newfd))
+#define discard_cache() discard_index(&the_index)
+#define cache_name_pos(name, namelen) index_name_pos(&the_index,(name),(namelen))
+#define add_cache_entry(ce, option) add_index_entry(&the_index, (ce), (option))
+#define remove_cache_entry_at(pos) remove_index_entry_at(&the_index, (pos))
+#define remove_file_from_cache(path) remove_file_from_index(&the_index, (path))
+#define add_file_to_cache(path, verbose) add_file_to_index(&the_index, (path), (verbose))
+#define refresh_cache(flags) refresh_index(&the_index, flags)
+#define ce_match_stat(ce, st, really) ie_match_stat(&the_index, (ce), (st), (really))
+#define ce_modified(ce, st, really) ie_modified(&the_index, (ce), (st), (really))
+#endif
 
 enum object_type {
        OBJ_BAD = -1,
@@ -152,6 +200,9 @@ enum object_type {
 #define CONFIG_ENVIRONMENT "GIT_CONFIG"
 #define CONFIG_LOCAL_ENVIRONMENT "GIT_CONFIG_LOCAL"
 #define EXEC_PATH_ENVIRONMENT "GIT_EXEC_PATH"
+#define GITATTRIBUTES_FILE ".gitattributes"
+#define INFOATTRIBUTES_FILE "info/attributes"
+#define ATTRIBUTE_MACRO_PREFIX "[attr]"
 
 extern int is_bare_repository_cfg;
 extern int is_bare_repository(void);
@@ -175,23 +226,23 @@ extern void verify_non_filename(const char *prefix, const char *name);
 #define alloc_nr(x) (((x)+16)*3/2)
 
 /* Initialize and use the cache information */
-extern int read_cache(void);
-extern int read_cache_from(const char *path);
-extern int write_cache(int newfd, struct cache_entry **cache, int entries);
-extern int discard_cache(void);
+extern int read_index(struct index_state *);
+extern int read_index_from(struct index_state *, const char *path);
+extern int write_index(struct index_state *, int newfd);
+extern int discard_index(struct index_state *);
 extern int verify_path(const char *path);
-extern int cache_name_pos(const char *name, int namelen);
+extern int index_name_pos(struct index_state *, const char *name, int namelen);
 #define ADD_CACHE_OK_TO_ADD 1          /* Ok to add */
 #define ADD_CACHE_OK_TO_REPLACE 2      /* Ok to replace file/directory */
 #define ADD_CACHE_SKIP_DFCHECK 4       /* Ok to skip DF conflict checks */
-extern int add_cache_entry(struct cache_entry *ce, int option);
+extern int add_index_entry(struct index_state *, struct cache_entry *ce, int option);
 extern struct cache_entry *refresh_cache_entry(struct cache_entry *ce, int really);
-extern int remove_cache_entry_at(int pos);
-extern int remove_file_from_cache(const char *path);
-extern int add_file_to_index(const char *path, int verbose);
+extern int remove_index_entry_at(struct index_state *, int pos);
+extern int remove_file_from_index(struct index_state *, const char *path);
+extern int add_file_to_index(struct index_state *, const char *path, int verbose);
 extern int ce_same_name(struct cache_entry *a, struct cache_entry *b);
-extern int ce_match_stat(struct cache_entry *ce, struct stat *st, int);
-extern int ce_modified(struct cache_entry *ce, struct stat *st, int);
+extern int ie_match_stat(struct index_state *, struct cache_entry *, struct stat *, int);
+extern int ie_modified(struct index_state *, struct cache_entry *, struct stat *, int);
 extern int ce_path_match(const struct cache_entry *ce, const char **pathspec);
 extern int index_fd(unsigned char *sha1, int fd, struct stat *st, int write_object, enum object_type type, const char *path);
 extern int read_pipe(int fd, char** return_buf, unsigned long* return_size);
@@ -203,15 +254,21 @@ extern void fill_stat_cache_info(struct cache_entry *ce, struct stat *st);
 #define REFRESH_UNMERGED       0x0002  /* allow unmerged */
 #define REFRESH_QUIET          0x0004  /* be quiet about it */
 #define REFRESH_IGNORE_MISSING 0x0008  /* ignore non-existent */
-extern int refresh_cache(unsigned int flags);
+extern int refresh_index(struct index_state *, unsigned int flags);
 
 struct lock_file {
        struct lock_file *next;
+       pid_t owner;
        char on_list;
        char filename[PATH_MAX];
 };
 extern int hold_lock_file_for_update(struct lock_file *, const char *path, int);
 extern int commit_lock_file(struct lock_file *);
+
+extern int hold_locked_index(struct lock_file *, int);
+extern int commit_locked_index(struct lock_file *);
+extern void set_alternate_index_output(const char *);
+
 extern void rollback_lock_file(struct lock_file *);
 extern int delete_ref(const char *, const unsigned char *sha1);
 
@@ -313,6 +370,7 @@ static inline unsigned int hexval(unsigned int c)
 #define DEFAULT_ABBREV 7
 
 extern int get_sha1(const char *str, unsigned char *sha1);
+extern int get_sha1_with_mode(const char *str, unsigned char *sha1, unsigned *mode);
 extern int get_sha1_hex(const char *hex, unsigned char *sha1);
 extern char *sha1_to_hex(const unsigned char *sha1);   /* static buffer result! */
 extern int read_ref(const char *filename, unsigned char *sha1);
@@ -331,7 +389,7 @@ extern void *read_object_with_reference(const unsigned char *sha1,
                                        unsigned long *size,
                                        unsigned char *sha1_ret);
 
-enum date_mode { DATE_NORMAL = 0, DATE_RELATIVE, DATE_SHORT };
+enum date_mode { DATE_NORMAL = 0, DATE_RELATIVE, DATE_SHORT, DATE_LOCAL };
 const char *show_date(unsigned long time, int timezone, enum date_mode mode);
 const char *show_rfc2822_date(unsigned long time, int timezone);
 int parse_date(const char *date, char *buf, int bufsize);
@@ -351,7 +409,8 @@ struct checkout {
                 refresh_cache:1;
 };
 
-extern int checkout_entry(struct cache_entry *ce, struct checkout *state, char *topath);
+extern int checkout_entry(struct cache_entry *ce, const struct checkout *state, char *topath);
+extern int has_symlink_leading_path(const char *name, char *last_symlink);
 
 extern struct alternate_object_database {
        struct alternate_object_database *next;
@@ -372,11 +431,12 @@ struct pack_window {
 extern struct packed_git {
        struct packed_git *next;
        struct pack_window *windows;
-       const void *index_data;
-       off_t index_size;
        off_t pack_size;
-       time_t mtime;
+       const void *index_data;
+       size_t index_size;
+       uint32_t num_objects;
        int index_version;
+       time_t mtime;
        int pack_fd;
        int pack_local;
        unsigned char sha1[20];
@@ -427,11 +487,11 @@ extern void pack_report(void);
 extern unsigned char* use_pack(struct packed_git *, struct pack_window **, off_t, unsigned int *);
 extern void unuse_pack(struct pack_window **);
 extern struct packed_git *add_packed_git(const char *, int, int);
-extern uint32_t num_packed_objects(const struct packed_git *p);
-extern int nth_packed_object_sha1(const struct packed_git *, uint32_t, unsigned char*);
+extern const unsigned char *nth_packed_object_sha1(const struct packed_git *, uint32_t);
 extern off_t find_pack_entry_one(const unsigned char *, struct packed_git *);
 extern void *unpack_entry(struct packed_git *, off_t, enum object_type *, unsigned long *);
 extern unsigned long unpack_object_header_gently(const unsigned char *buf, unsigned long len, enum object_type *type, unsigned long *sizep);
+extern unsigned long get_size_from_delta(struct packed_git *, struct pack_window **, off_t);
 extern const char *packed_object_info_detail(struct packed_git *, off_t, unsigned long *, unsigned long *, unsigned int *, unsigned char *);
 
 /* Dumb servers support */
@@ -472,14 +532,11 @@ int decode_85(char *dst, const char *line, int linelen);
 void encode_85(char *buf, const unsigned char *data, int bytes);
 
 /* alloc.c */
-struct blob;
-struct tree;
-struct commit;
-struct tag;
-extern struct blob *alloc_blob_node(void);
-extern struct tree *alloc_tree_node(void);
-extern struct commit *alloc_commit_node(void);
-extern struct tag *alloc_tag_node(void);
+extern void *alloc_blob_node(void);
+extern void *alloc_tree_node(void);
+extern void *alloc_commit_node(void);
+extern void *alloc_tag_node(void);
+extern void *alloc_object_node(void);
 extern void alloc_report(void);
 
 /* trace.c */
@@ -489,7 +546,11 @@ extern void trace_printf(const char *format, ...);
 extern void trace_argv_printf(const char **argv, int count, const char *format, ...);
 
 /* convert.c */
-extern int convert_to_git(const char *path, char **bufp, unsigned long *sizep);
-extern int convert_to_working_tree(const char *path, char **bufp, unsigned long *sizep);
+extern char *convert_to_git(const char *path, const char *src, unsigned long *sizep);
+extern char *convert_to_working_tree(const char *path, const char *src, unsigned long *sizep);
+extern void *convert_sha1_file(const char *path, const unsigned char *sha1, unsigned int mode, enum object_type *type, unsigned long *size);
+
+/* match-trees.c */
+void shift_tree(const unsigned char *, const unsigned char *, unsigned char *, int);
 
 #endif /* CACHE_H */
index 3a9b32f6b8882f3adb91e5833c205635657a98b7..ea3ca5f950561a92fdd3be1a4ee4bbd726656118 100644 (file)
@@ -714,7 +714,7 @@ static void show_patch_diff(struct combine_diff_path *elem, int num_parent,
                        result_size = len;
                        result = xmalloc(len + 1);
                        while (sz < len) {
-                               int done = xread(fd, result+sz, len-sz);
+                               ssize_t done = xread(fd, result+sz, len-sz);
                                if (done == 0)
                                        break;
                                if (done < 0)
@@ -943,6 +943,7 @@ void diff_tree_combined(const unsigned char *sha1,
        diffopts = *opt;
        diffopts.output_format = DIFF_FORMAT_NO_OUTPUT;
        diffopts.recursive = 1;
+       diffopts.allow_external = 0;
 
        show_log_first = !!rev->loginfo && !rev->no_commit_id;
        needsep = 0;
index 43b767ce5342624f98603e83ba3ec263ea8a7dda..bee066fa3227c876022424bd0e6238c1053e1231 100644 (file)
--- a/commit.c
+++ b/commit.c
@@ -4,6 +4,8 @@
 #include "pkt-line.h"
 #include "utf8.h"
 #include "interpolate.h"
+#include "diff.h"
+#include "revision.h"
 
 int save_commit_buffer = 1;
 
@@ -96,12 +98,8 @@ struct commit *lookup_commit_reference(const unsigned char *sha1)
 struct commit *lookup_commit(const unsigned char *sha1)
 {
        struct object *obj = lookup_object(sha1);
-       if (!obj) {
-               struct commit *ret = alloc_commit_node();
-               created_object(sha1, &ret->object);
-               ret->object.type = OBJ_COMMIT;
-               return ret;
-       }
+       if (!obj)
+               return create_object(sha1, OBJ_COMMIT, alloc_commit_node());
        if (!obj->type)
                obj->type = OBJ_COMMIT;
        return check_commit(obj, sha1, 0);
@@ -528,7 +526,7 @@ static int add_rfc2047(char *buf, const char *line, int len,
 }
 
 static int add_user_info(const char *what, enum cmit_fmt fmt, char *buf,
-                        const char *line, int relative_date,
+                        const char *line, enum date_mode dmode,
                         const char *encoding)
 {
        char *date;
@@ -571,7 +569,7 @@ static int add_user_info(const char *what, enum cmit_fmt fmt, char *buf,
        switch (fmt) {
        case CMIT_FMT_MEDIUM:
                ret += sprintf(buf + ret, "Date:   %s\n",
-                              show_date(time, tz, relative_date));
+                              show_date(time, tz, dmode));
                break;
        case CMIT_FMT_EMAIL:
                ret += sprintf(buf + ret, "Date: %s\n",
@@ -579,7 +577,7 @@ static int add_user_info(const char *what, enum cmit_fmt fmt, char *buf,
                break;
        case CMIT_FMT_FULLER:
                ret += sprintf(buf + ret, "%sDate: %s\n", what,
-                              show_date(time, tz, relative_date));
+                              show_date(time, tz, dmode));
                break;
        default:
                /* notin' */
@@ -802,7 +800,8 @@ static long format_commit_message(const struct commit *commit,
                { "%Cgreen" },  /* green */
                { "%Cblue" },   /* blue */
                { "%Creset" },  /* reset color */
-               { "%n" }        /* newline */
+               { "%n" },       /* newline */
+               { "%m" },       /* left/right/bottom */
        };
        enum interp_index {
                IHASH = 0, IHASH_ABBREV,
@@ -818,14 +817,15 @@ static long format_commit_message(const struct commit *commit,
                ISUBJECT,
                IBODY,
                IRED, IGREEN, IBLUE, IRESET_COLOR,
-               INEWLINE
+               INEWLINE,
+               ILEFT_RIGHT,
        };
        struct commit_list *p;
        char parents[1024];
        int i;
        enum { HEADER, SUBJECT, BODY } state;
 
-       if (INEWLINE + 1 != ARRAY_SIZE(table))
+       if (ILEFT_RIGHT + 1 != ARRAY_SIZE(table))
                die("invalid interp table!");
 
        /* these are independent of the commit */
@@ -846,6 +846,12 @@ static long format_commit_message(const struct commit *commit,
        interp_set_entry(table, ITREE_ABBREV,
                        find_unique_abbrev(commit->tree->object.sha1,
                                DEFAULT_ABBREV));
+       interp_set_entry(table, ILEFT_RIGHT,
+                        (commit->object.flags & BOUNDARY)
+                        ? "-"
+                        : (commit->object.flags & SYMMETRIC_LEFT)
+                        ? "<"
+                        : ">");
 
        parents[1] = 0;
        for (i = 0, p = commit->parents;
@@ -907,7 +913,7 @@ unsigned long pretty_print_commit(enum cmit_fmt fmt,
                                  char *buf, unsigned long space,
                                  int abbrev, const char *subject,
                                  const char *after_subject,
-                                 int relative_date)
+                                 enum date_mode dmode)
 {
        int hdr = 1, body = 0, seen_title = 0;
        unsigned long offset = 0;
@@ -1011,14 +1017,14 @@ unsigned long pretty_print_commit(enum cmit_fmt fmt,
                                offset += add_user_info("Author", fmt,
                                                        buf + offset,
                                                        line + 7,
-                                                       relative_date,
+                                                       dmode,
                                                        encoding);
                        if (!memcmp(line, "committer ", 10) &&
                            (fmt == CMIT_FMT_FULL || fmt == CMIT_FMT_FULLER))
                                offset += add_user_info("Commit", fmt,
                                                        buf + offset,
                                                        line + 10,
-                                                       relative_date,
+                                                       dmode,
                                                        encoding);
                        continue;
                }
index 83507a07e4cbaab81f130891a64b78539d1beede..86e8dca0c9dbac4d990d959296145805319b477a 100644 (file)
--- a/commit.h
+++ b/commit.h
@@ -3,6 +3,7 @@
 
 #include "object.h"
 #include "tree.h"
+#include "decorate.h"
 
 struct commit_list {
        struct commit *item;
@@ -21,6 +22,13 @@ struct commit {
 extern int save_commit_buffer;
 extern const char *commit_type;
 
+/* While we can decorate any object with a name, it's only used for commits.. */
+extern struct decoration name_decoration;
+struct name_decoration {
+       struct name_decoration *next;
+       char name[1];
+};
+
 struct commit *lookup_commit(const unsigned char *sha1);
 struct commit *lookup_commit_reference(const unsigned char *sha1);
 struct commit *lookup_commit_reference_gently(const unsigned char *sha1,
@@ -53,7 +61,7 @@ enum cmit_fmt {
 };
 
 extern enum cmit_fmt get_commit_format(const char *arg);
-extern unsigned long pretty_print_commit(enum cmit_fmt fmt, const struct commit *, unsigned long len, char *buf, unsigned long space, int abbrev, const char *subject, const char *after_subject, int relative_date);
+extern unsigned long pretty_print_commit(enum cmit_fmt fmt, const struct commit *, unsigned long len, char *buf, unsigned long space, int abbrev, const char *subject, const char *after_subject, enum date_mode dmode);
 
 /** Removes the first commit from a list sorted by date, and adds all
  * of its parents.
index 9a578405d856c3633e7137a24964b7b80fd96485..eb9d7a55496492fa021e8262f052c7f46b60ea20 100644 (file)
@@ -6,6 +6,7 @@ CFLAGS = @CFLAGS@
 AR = @AR@
 TAR = @TAR@
 #INSTALL = @INSTALL@           # needs install-sh or install.sh in sources
+TCLTK_PATH = @TCLTK_PATH@
 
 prefix = @prefix@
 exec_prefix = @exec_prefix@
index 3a8e778defcf1f0e1285664785c69d9213f99516..50d2b85ace7d79ba1b8c576b54c6ef1f22ddf91f 100644 (file)
@@ -75,6 +75,14 @@ GIT_ARG_SET_PATH(shell)
 # Define PERL_PATH to provide path to Perl.
 GIT_ARG_SET_PATH(perl)
 #
+# Declare the with-tcltk/without-tcltk options.
+AC_ARG_WITH(tcltk,
+AS_HELP_STRING([--with-tcltk],[use Tcl/Tk GUI (default is YES)])
+AS_HELP_STRING([],[ARG is the full path to the Tcl/Tk interpreter.])
+AS_HELP_STRING([],[Bare --with-tcltk will make the GUI part only if])
+AS_HELP_STRING([],[Tcl/Tk interpreter will be found in a system.]),\
+GIT_PARSE_WITH(tcltk))
+#
 
 
 ## Checks for programs.
@@ -84,6 +92,22 @@ AC_PROG_CC([cc gcc])
 #AC_PROG_INSTALL               # needs install-sh or install.sh in sources
 AC_CHECK_TOOL(AR, ar, :)
 AC_CHECK_PROGS(TAR, [gtar tar])
+# TCLTK_PATH will be set to some value if we want Tcl/Tk
+# or will be empty otherwise.
+if test -z "$NO_TCLTK"; then
+  if test "$with_tcltk" = ""; then
+  # No Tcl/Tk switches given. Do not check for Tcl/Tk, use bare 'wish'.
+    TCLTK_PATH=wish
+    AC_SUBST(TCLTK_PATH)
+  elif test "$with_tcltk" = "yes"; then
+  # Tcl/Tk check requested.
+    AC_CHECK_PROGS(TCLTK_PATH, [wish], )
+  else
+    AC_MSG_RESULT([Using Tcl/Tk interpreter $with_tcltk])
+    TCLTK_PATH="$with_tcltk"
+    AC_SUBST(TCLTK_PATH)
+  fi
+fi
 
 ## Checks for libraries.
 AC_MSG_NOTICE([CHECKS for libraries])
index 7c03403484f3a52c9588aa3bfc58dea4f394dabe..46356e8a274a20c4d01f43c60caf98c2e7e1b76a 100755 (executable)
@@ -790,6 +790,7 @@ _git_config ()
                core.legacyHeaders
                core.packedGitWindowSize
                core.packedGitLimit
+               clean.requireForce
                color.branch
                color.branch.current
                color.branch.local
index 64ad50b3274d00199f5b3fc724eb5746b8eef480..bb671d561ebc9af51bb9a5d52017e71fd81881e9 100644 (file)
@@ -8,8 +8,8 @@
 ;; License:    GPL
 ;; Keywords:   git, version control, release management
 ;;
-;; Compatibility: Emacs21
-
+;; Compatibility: Emacs21, Emacs22 and EmacsCVS
+;;                Git 1.5 and up
 
 ;; This file is *NOT* part of GNU Emacs.
 ;; This file is distributed under the same terms as GNU Emacs.
@@ -61,8 +61,9 @@
 
 ;;; Compatibility:
 ;;
-;; It requires GNU Emacs 21.  If you'are using Emacs 20, try
-;; changing this:
+;; It requires GNU Emacs 21 or later and Git 1.5.0 and up
+;;
+;; If you'are using Emacs 20, try changing this:
 ;;
 ;;            (overlay-put ovl 'face (list :background
 ;;                                         (cdr (assq 'color (cddddr info)))))
 ;;
 ;;; Code:
 
-(require 'cl)                        ; to use `push', `pop'
-
-(defun color-scale (l)
-  (let* ((colors ())
-         r g b)
-    (setq r l)
-    (while r
-      (setq g l)
-      (while g
-        (setq b l)
-        (while b
-          (push (concat "#" (car r) (car g) (car b)) colors)
-          (pop b))
-        (pop g))
-      (pop r))
-    colors))
+(eval-when-compile (require 'cl))                            ; to use `push', `pop'
+
+
+(defun git-blame-color-scale (&rest elements)
+  "Given a list, returns a list of triples formed with each
+elements of the list.
+
+a b => bbb bba bab baa abb aba aaa aab"
+  (let (result)
+    (dolist (a elements)
+      (dolist (b elements)
+        (dolist (c elements)
+          (setq result (cons (format "#%s%s%s" a b c) result)))))
+    result))
+
+;; (git-blame-color-scale "0c" "04" "24" "1c" "2c" "34" "14" "3c") =>
+;; ("#3c3c3c" "#3c3c14" "#3c3c34" "#3c3c2c" "#3c3c1c" "#3c3c24"
+;; "#3c3c04" "#3c3c0c" "#3c143c" "#3c1414" "#3c1434" "#3c142c" ...)
+
+(defmacro git-blame-random-pop (l)
+  "Select a random element from L and returns it. Also remove
+selected element from l."
+  ;; only works on lists with unique elements
+  `(let ((e (elt ,l (random (length ,l)))))
+     (setq ,l (remove e ,l))
+     e))
 
 (defvar git-blame-dark-colors
-  (color-scale '("0c" "04" "24" "1c" "2c" "34" "14" "3c")))
+  (git-blame-color-scale "0c" "04" "24" "1c" "2c" "34" "14" "3c")
+  "*List of colors (format #RGB) to use in a dark environment.
+
+To check out the list, evaluate (list-colors-display git-blame-dark-colors).")
 
 (defvar git-blame-light-colors
-  (color-scale '("c4" "d4" "cc" "dc" "f4" "e4" "fc" "ec")))
+  (git-blame-color-scale "c4" "d4" "cc" "dc" "f4" "e4" "fc" "ec")
+  "*List of colors (format #RGB) to use in a light environment.
+
+To check out the list, evaluate (list-colors-display git-blame-light-colors).")
 
-(defvar git-blame-ancient-color "dark green")
+(defvar git-blame-colors '()
+  "Colors used by git-blame. The list is built once when activating git-blame
+minor mode.")
+
+(defvar git-blame-ancient-color "dark green"
+  "*Color to be used for ancient commit.")
 
 (defvar git-blame-autoupdate t
   "*Automatically update the blame display while editing")
   "A queue of update requests")
 (make-variable-buffer-local 'git-blame-update-queue)
 
+;; FIXME: docstrings
+(defvar git-blame-file nil)
+(defvar git-blame-current nil)
+
 (defvar git-blame-mode nil)
 (make-variable-buffer-local 'git-blame-mode)
-(unless (assq 'git-blame-mode minor-mode-alist)
-  (setq minor-mode-alist
-       (cons (list 'git-blame-mode " blame")
-             minor-mode-alist)))
+
+(defvar git-blame-mode-line-string " blame"
+  "String to display on the mode line when git-blame is active.")
+
+(or (assq 'git-blame-mode minor-mode-alist)
+    (setq minor-mode-alist
+         (cons '(git-blame-mode git-blame-mode-line-string) minor-mode-alist)))
 
 ;;;###autoload
 (defun git-blame-mode (&optional arg)
-  "Minor mode for displaying Git blame"
+  "Toggle minor mode for displaying Git blame
+
+With prefix ARG, turn the mode on if ARG is positive."
   (interactive "P")
-  (if arg
-      (setq git-blame-mode (eq arg 1))
-    (setq git-blame-mode (not git-blame-mode)))
+  (cond
+   ((null arg)
+    (if git-blame-mode (git-blame-mode-off) (git-blame-mode-on)))
+   ((> (prefix-numeric-value arg) 0) (git-blame-mode-on))
+   (t (git-blame-mode-off))))
+
+(defun git-blame-mode-on ()
+  "Turn on git-blame mode.
+
+See also function `git-blame-mode'."
   (make-local-variable 'git-blame-colors)
   (if git-blame-autoupdate
       (add-hook 'after-change-functions 'git-blame-after-change nil t)
     (remove-hook 'after-change-functions 'git-blame-after-change t))
   (git-blame-cleanup)
-  (if git-blame-mode
-      (progn
-        (let ((bgmode (cdr (assoc 'background-mode (frame-parameters)))))
-          (if (eq bgmode 'dark)
-              (setq git-blame-colors git-blame-dark-colors)
-            (setq git-blame-colors git-blame-light-colors)))
-        (setq git-blame-cache (make-hash-table :test 'equal))
-        (git-blame-run))
-    (cancel-timer git-blame-idle-timer)))
+  (let ((bgmode (cdr (assoc 'background-mode (frame-parameters)))))
+    (if (eq bgmode 'dark)
+       (setq git-blame-colors git-blame-dark-colors)
+      (setq git-blame-colors git-blame-light-colors)))
+  (setq git-blame-cache (make-hash-table :test 'equal))
+  (setq git-blame-mode t)
+  (git-blame-run))
+
+(defun git-blame-mode-off ()
+  "Turn off git-blame mode.
+
+See also function `git-blame-mode'."
+  (git-blame-cleanup)
+  (if git-blame-idle-timer (cancel-timer git-blame-idle-timer))
+  (setq git-blame-mode nil))
 
 ;;;###autoload
 (defun git-reblame ()
   "Recalculate all blame information in the current buffer"
-  (unless git-blame-mode
-    (error "git-blame is not active"))
   (interactive)
+  (unless git-blame-mode
+    (error "Git-blame is not active"))
+
   (git-blame-cleanup)
   (git-blame-run))
 
         (t
          nil)))
 
-
 (defun git-blame-new-commit (hash src-line res-line num-lines)
   (save-excursion
     (set-buffer git-blame-file)
           (inhibit-point-motion-hooks t)
           (inhibit-modification-hooks t))
       (when (not info)
-        (let ((color (pop git-blame-colors)))
-          (unless color
-            (setq color git-blame-ancient-color))
+       ;; Assign a random color to each new commit info
+       ;; Take care not to select the same color multiple times
+       (let ((color (if git-blame-colors
+                        (git-blame-random-pop git-blame-colors)
+                      git-blame-ancient-color)))
           (setq info (list hash src-line res-line num-lines
                            (git-describe-commit hash)
                            (cons 'color color))))
index 2f9995ea3979350c7cd3883dfc6364e90b8d9bb2..f60017948f4eb45f6aa7fe0fc60ca1507cec98f2 100644 (file)
@@ -345,9 +345,15 @@ and returns the process output as a string."
   (let ((str (git-call-process-env-string nil "symbolic-ref" ref)))
     (and str (car (split-string str "\n")))))
 
-(defun git-update-ref (ref val &optional oldval)
+(defun git-update-ref (ref newval &optional oldval reason)
   "Update a reference by calling git-update-ref."
-  (apply #'git-call-process-env nil nil "update-ref" ref val (if oldval (list oldval))))
+  (let ((args (and oldval (list oldval))))
+    (push newval args)
+    (push ref args)
+    (when reason
+     (push reason args)
+     (push "-m" args))
+    (eq 0 (apply #'git-call-process-env nil nil "update-ref" args))))
 
 (defun git-read-tree (tree &optional index-file)
   "Read a tree into the index file."
@@ -364,8 +370,10 @@ and returns the process output as a string."
   "Call git-commit-tree with buffer as input and return the resulting commit SHA1."
   (let ((author-name (git-get-committer-name))
         (author-email (git-get-committer-email))
+        (subject "commit (initial): ")
         author-date log-start log-end args coding-system-for-write)
     (when head
+      (setq subject "commit: ")
       (push "-p" args)
       (push head args))
     (with-current-buffer buffer
@@ -384,22 +392,29 @@ and returns the process output as a string."
             (goto-char (point-min))
             (while (re-search-forward "^Parent: +\\([0-9a-f]+\\)" nil t)
               (unless (string-equal head (match-string 1))
+                (setq subject "commit (merge): ")
                 (push "-p" args)
                 (push (match-string 1) args))))
         (setq log-start (point-min)))
       (setq log-end (point-max))
+      (goto-char log-start)
+      (when (re-search-forward ".*$" nil t)
+        (setq subject (concat subject (match-string 0))))
       (setq coding-system-for-write buffer-file-coding-system))
-    (git-get-string-sha1
-     (with-output-to-string
-       (with-current-buffer standard-output
-         (let ((env `(("GIT_AUTHOR_NAME" . ,author-name)
-                      ("GIT_AUTHOR_EMAIL" . ,author-email)
-                      ("GIT_COMMITTER_NAME" . ,(git-get-committer-name))
-                      ("GIT_COMMITTER_EMAIL" . ,(git-get-committer-email)))))
-           (when author-date (push `("GIT_AUTHOR_DATE" . ,author-date) env))
-           (apply #'git-run-command-region
-                  buffer log-start log-end env
-                  "commit-tree" tree (nreverse args))))))))
+    (let ((commit
+           (git-get-string-sha1
+            (with-output-to-string
+              (with-current-buffer standard-output
+                (let ((env `(("GIT_AUTHOR_NAME" . ,author-name)
+                             ("GIT_AUTHOR_EMAIL" . ,author-email)
+                             ("GIT_COMMITTER_NAME" . ,(git-get-committer-name))
+                             ("GIT_COMMITTER_EMAIL" . ,(git-get-committer-email)))))
+                  (when author-date (push `("GIT_AUTHOR_DATE" . ,author-date) env))
+                  (apply #'git-run-command-region
+                         buffer log-start log-end env
+                         "commit-tree" tree (nreverse args))))))))
+      (and (git-update-ref "HEAD" commit head subject)
+           commit))))
 
 (defun git-empty-db-p ()
   "Check if the git db is empty (no commit done yet)."
@@ -662,7 +677,6 @@ and returns the process output as a string."
                       (if (or (not (string-equal tree head-tree))
                               (yes-or-no-p "The tree was not modified, do you really want to perform an empty commit? "))
                           (let ((commit (git-commit-tree buffer tree head)))
-                            (git-update-ref "HEAD" commit head)
                             (condition-case nil (delete-file ".git/MERGE_HEAD") (error nil))
                             (condition-case nil (delete-file ".git/MERGE_MSG") (error nil))
                             (with-current-buffer buffer (erase-buffer))
index 5bfd205225a65bb4909cb44c305fb6235518d52a..23aeb257b9557cb146586868084ce1b20d7e7ac8 100755 (executable)
@@ -84,7 +84,12 @@ foreach my $tar_file (@ARGV)
                }
                print FI "\n";
 
-               my $path = "$prefix$name";
+               my $path;
+               if ($prefix) {
+                       $path = "$prefix/$name";
+               } else {
+                       $path = "$name";
+               }
                $files{$path} = [$next_mark++, $mode];
 
                $commit_time = $mtime if $mtime > $commit_time;
index 521b2fcd32da4103d0c916af0ae44fbe53ed282a..2d80e2bad2e6f322d7ff7e9f03a6897a11f74231 100755 (executable)
@@ -10,7 +10,8 @@ GUI browser for git repository
 This program is based on bzrk by Scott James Remnant <scott@ubuntu.com>
 """
 __copyright__ = "Copyright (C) 2006 Hewlett-Packard Development Company, L.P."
-__author__    = "Aneesh Kumar K.V <aneesh.kumar@hp.com>"
+__copyright__ = "Copyright (C) 2007 Aneesh Kumar K.V <aneesh.kumar@gmail.com"
+__author__    = "Aneesh Kumar K.V <aneesh.kumar@gmail.com>"
 
 
 import sys
@@ -24,6 +25,7 @@ import gobject
 import cairo
 import math
 import string
+import fcntl
 
 try:
     import gtksourceview
@@ -337,6 +339,186 @@ class Commit:
                fp.close()
                return diff
 
+class AnnotateWindow:
+       """Annotate window.
+       This object represents and manages a single window containing the
+       annotate information of the file
+       """
+
+       def __init__(self):
+               self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
+               self.window.set_border_width(0)
+               self.window.set_title("Git repository browser annotation window")
+
+               # Use two thirds of the screen by default
+               screen = self.window.get_screen()
+               monitor = screen.get_monitor_geometry(0)
+               width = int(monitor.width * 0.66)
+               height = int(monitor.height * 0.66)
+               self.window.set_default_size(width, height)
+
+       def add_file_data(self, filename, commit_sha1, line_num):
+               fp = os.popen("git cat-file blob " + commit_sha1 +":"+filename)
+               i = 1;
+               for line in fp.readlines():
+                       line = string.rstrip(line)
+                       self.model.append(None, ["HEAD", filename, line, i])
+                       i = i+1
+               fp.close()
+
+               # now set the cursor position
+               self.treeview.set_cursor(line_num-1)
+               self.treeview.grab_focus()
+
+       def _treeview_cursor_cb(self, *args):
+               """Callback for when the treeview cursor changes."""
+               (path, col) = self.treeview.get_cursor()
+               commit_sha1 = self.model[path][0]
+               commit_msg = ""
+               fp = os.popen("git cat-file commit " + commit_sha1)
+               for line in fp.readlines():
+                       commit_msg =  commit_msg + line
+               fp.close()
+
+               self.commit_buffer.set_text(commit_msg)
+
+       def _treeview_row_activated(self, *args):
+               """Callback for when the treeview row gets selected."""
+               (path, col) = self.treeview.get_cursor()
+               commit_sha1 = self.model[path][0]
+               filename    = self.model[path][1]
+               line_num    = self.model[path][3]
+
+               window = AnnotateWindow();
+               fp = os.popen("git rev-parse "+ commit_sha1 + "~1")
+               commit_sha1 = string.strip(fp.readline())
+               fp.close()
+               window.annotate(filename, commit_sha1, line_num)
+
+       def data_ready(self, source, condition):
+               while (1):
+                       try :
+                               buffer = source.read(8192)
+                       except:
+                               # resource temporary not available
+                               return True
+
+                       if (len(buffer) == 0):
+                               gobject.source_remove(self.io_watch_tag)
+                               source.close()
+                               return False
+
+                       for buff in buffer.split("\n"):
+                               annotate_line = re.compile('^([0-9a-f]{40}) (.+) (.+) (.+)$')
+                               m = annotate_line.match(buff)
+                               if not m:
+                                       annotate_line = re.compile('^(filename) (.+)$')
+                                       m = annotate_line.match(buff)
+                                       if not m:
+                                               continue
+                                       filename = m.group(2)
+                               else:
+                                       self.commit_sha1 = m.group(1)
+                                       self.source_line = int(m.group(2))
+                                       self.result_line = int(m.group(3))
+                                       self.count          = int(m.group(4))
+                                       #set the details only when we have the file name
+                                       continue
+
+                               while (self.count > 0):
+                                       # set at result_line + count-1 the sha1 as commit_sha1
+                                       self.count = self.count - 1
+                                       iter = self.model.iter_nth_child(None, self.result_line + self.count-1)
+                                       self.model.set(iter, 0, self.commit_sha1, 1, filename, 3, self.source_line)
+
+
+       def annotate(self, filename, commit_sha1, line_num):
+               # verify the commit_sha1 specified has this filename
+
+               fp = os.popen("git ls-tree "+ commit_sha1 + " -- " + filename)
+               line = string.strip(fp.readline())
+               if line == '':
+                       # pop up the message the file is not there as a part of the commit
+                       fp.close()
+                       dialog = gtk.MessageDialog(parent=None, flags=0,
+                                       type=gtk.MESSAGE_WARNING, buttons=gtk.BUTTONS_CLOSE,
+                                       message_format=None)
+                       dialog.set_markup("The file %s is not present in the parent commit %s" % (filename, commit_sha1))
+                       dialog.run()
+                       dialog.destroy()
+                       return
+
+               fp.close()
+
+               vpan = gtk.VPaned();
+               self.window.add(vpan);
+               vpan.show()
+
+               scrollwin = gtk.ScrolledWindow()
+               scrollwin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
+               scrollwin.set_shadow_type(gtk.SHADOW_IN)
+               vpan.pack1(scrollwin, True, True);
+               scrollwin.show()
+
+               self.model = gtk.TreeStore(str, str, str, int)
+               self.treeview = gtk.TreeView(self.model)
+               self.treeview.set_rules_hint(True)
+               self.treeview.set_search_column(0)
+               self.treeview.connect("cursor-changed", self._treeview_cursor_cb)
+               self.treeview.connect("row-activated", self._treeview_row_activated)
+               scrollwin.add(self.treeview)
+               self.treeview.show()
+
+               cell = gtk.CellRendererText()
+               cell.set_property("width-chars", 10)
+               cell.set_property("ellipsize", pango.ELLIPSIZE_END)
+               column = gtk.TreeViewColumn("Commit")
+               column.set_resizable(True)
+               column.pack_start(cell, expand=True)
+               column.add_attribute(cell, "text", 0)
+               self.treeview.append_column(column)
+
+               cell = gtk.CellRendererText()
+               cell.set_property("width-chars", 20)
+               cell.set_property("ellipsize", pango.ELLIPSIZE_END)
+               column = gtk.TreeViewColumn("File Name")
+               column.set_resizable(True)
+               column.pack_start(cell, expand=True)
+               column.add_attribute(cell, "text", 1)
+               self.treeview.append_column(column)
+
+               cell = gtk.CellRendererText()
+               cell.set_property("width-chars", 20)
+               cell.set_property("ellipsize", pango.ELLIPSIZE_END)
+               column = gtk.TreeViewColumn("Data")
+               column.set_resizable(True)
+               column.pack_start(cell, expand=True)
+               column.add_attribute(cell, "text", 2)
+               self.treeview.append_column(column)
+
+               # The commit message window
+               scrollwin = gtk.ScrolledWindow()
+               scrollwin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
+               scrollwin.set_shadow_type(gtk.SHADOW_IN)
+               vpan.pack2(scrollwin, True, True);
+               scrollwin.show()
+
+               commit_text = gtk.TextView()
+               self.commit_buffer = gtk.TextBuffer()
+               commit_text.set_buffer(self.commit_buffer)
+               scrollwin.add(commit_text)
+               commit_text.show()
+
+               self.window.show()
+
+               self.add_file_data(filename, commit_sha1, line_num)
+
+               fp = os.popen("git blame --incremental -- " + filename + " " + commit_sha1)
+               flags = fcntl.fcntl(fp.fileno(), fcntl.F_GETFL)
+               fcntl.fcntl(fp.fileno(), fcntl.F_SETFL, flags | os.O_NONBLOCK)
+               self.io_watch_tag = gobject.io_add_watch(fp, gobject.IO_IN, self.data_ready)
+
+
 class DiffWindow:
        """Diff window.
        This object represents and manages a single window containing the
@@ -355,6 +537,7 @@ class DiffWindow:
                height = int(monitor.height * 0.66)
                self.window.set_default_size(width, height)
 
+
                self.construct()
 
        def construct(self):
@@ -371,10 +554,12 @@ class DiffWindow:
                vbox.pack_start(menu_bar, expand=False, fill=True)
                menu_bar.show()
 
+               hpan = gtk.HPaned()
+
                scrollwin = gtk.ScrolledWindow()
                scrollwin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
                scrollwin.set_shadow_type(gtk.SHADOW_IN)
-               vbox.pack_start(scrollwin, expand=True, fill=True)
+               hpan.pack1(scrollwin, True, True)
                scrollwin.show()
 
                if have_gtksourceview:
@@ -388,11 +573,77 @@ class DiffWindow:
                        self.buffer = gtk.TextBuffer()
                        sourceview = gtk.TextView(self.buffer)
 
+
                sourceview.set_editable(False)
                sourceview.modify_font(pango.FontDescription("Monospace"))
                scrollwin.add(sourceview)
                sourceview.show()
 
+               # The file hierarchy: a scrollable treeview
+               scrollwin = gtk.ScrolledWindow()
+               scrollwin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
+               scrollwin.set_shadow_type(gtk.SHADOW_IN)
+               scrollwin.set_size_request(20, -1)
+               hpan.pack2(scrollwin, True, True)
+               scrollwin.show()
+
+               self.model = gtk.TreeStore(str, str, str)
+               self.treeview = gtk.TreeView(self.model)
+               self.treeview.set_search_column(1)
+               self.treeview.connect("cursor-changed", self._treeview_clicked)
+               scrollwin.add(self.treeview)
+               self.treeview.show()
+
+               cell = gtk.CellRendererText()
+               cell.set_property("width-chars", 20)
+               column = gtk.TreeViewColumn("Select to annotate")
+               column.pack_start(cell, expand=True)
+               column.add_attribute(cell, "text", 0)
+               self.treeview.append_column(column)
+
+               vbox.pack_start(hpan, expand=True, fill=True)
+               hpan.show()
+
+       def _treeview_clicked(self, *args):
+               """Callback for when the treeview cursor changes."""
+               (path, col) = self.treeview.get_cursor()
+               specific_file = self.model[path][1]
+               commit_sha1 =  self.model[path][2]
+               if specific_file ==  None :
+                       return
+               elif specific_file ==  "" :
+                       specific_file =  None
+
+               window = AnnotateWindow();
+               window.annotate(specific_file, commit_sha1, 1)
+
+
+       def commit_files(self, commit_sha1, parent_sha1):
+               self.model.clear()
+               add  = self.model.append(None, [ "Added", None, None])
+               dele = self.model.append(None, [ "Deleted", None, None])
+               mod  = self.model.append(None, [ "Modified", None, None])
+               diff_tree = re.compile('^(:.{6}) (.{6}) (.{40}) (.{40}) (A|D|M)\s(.+)$')
+               fp = os.popen("git diff-tree -r --no-commit-id " + parent_sha1 + " " + commit_sha1)
+               while 1:
+                       line = string.strip(fp.readline())
+                       if line == '':
+                               break
+                       m = diff_tree.match(line)
+                       if not m:
+                               continue
+
+                       attr = m.group(5)
+                       filename = m.group(6)
+                       if attr == "A":
+                               self.model.append(add,  [filename, filename, commit_sha1])
+                       elif attr == "D":
+                               self.model.append(dele, [filename, filename, commit_sha1])
+                       elif attr == "M":
+                               self.model.append(mod,  [filename, filename, commit_sha1])
+               fp.close()
+
+               self.treeview.expand_all()
 
        def set_diff(self, commit_sha1, parent_sha1, encoding):
                """Set the differences showed by this window.
@@ -406,6 +657,7 @@ class DiffWindow:
                fp = os.popen("git diff-tree -p " + parent_sha1 + " " + commit_sha1)
                self.buffer.set_text(unicode(fp.read(), encoding).encode('utf-8'))
                fp.close()
+               self.commit_files(commit_sha1, parent_sha1)
                self.window.show()
 
        def save_menu_response(self, widget, string):
@@ -425,7 +677,7 @@ class DiffWindow:
 class GitView:
        """ This is the main class
        """
-       version = "0.8"
+       version = "0.9"
 
        def __init__(self, with_diff=0):
                self.with_diff = with_diff
@@ -590,7 +842,7 @@ class GitView:
                dialog = gtk.AboutDialog()
                dialog.set_name("Gitview")
                dialog.set_version(GitView.version)
-               dialog.set_authors(["Aneesh Kumar K.V <aneesh.kumar@hp.com>"])
+               dialog.set_authors(["Aneesh Kumar K.V <aneesh.kumar@gmail.com>"])
                dialog.set_website("http://www.kernel.org/pub/software/scm/git/")
                dialog.set_copyright("Use and distribute under the terms of the GNU General Public License")
                dialog.set_wrap_license(True)
index 65160153eef88b041bc9fbdc9353575e96b3250b..d1bef9125bca0997e0e714b3f89e9c2df307d598 100644 (file)
@@ -302,7 +302,7 @@ generate_update_branch_email()
        # List all of the revisions that were removed by this update, in a fast forward
        # update, this list will be empty, because rev-list O ^N is empty.  For a non
        # fast forward, O ^N is the list of removed revisions
-       fastforward=""
+       fast_forward=""
        rev=""
        for rev in $(git rev-list $newrev..$oldrev)
        do
@@ -327,36 +327,67 @@ generate_update_branch_email()
        if [ -z "$fastforward" ]; then
                echo "      from  $oldrev ($oldrev_type)"
        else
+               #  1. Existing revisions were removed.  In this case newrev is a
+               #     subset of oldrev - this is the reverse of a fast-forward,
+               #     a rewind
+               #  2. New revisions were added on top of an old revision, this is
+               #     a rewind and addition.
+
+               # (1) certainly happened, (2) possibly.  When (2) hasn't happened,
+               # we set a flag to indicate that no log printout is required.
+
                echo ""
-               echo "This update added new revisions after undoing old revisions.  That is to"
-               echo "say, the old revision is not a strict subset of the new revision.  This"
-               echo "situation occurs when you --force push a change and generate a"
-               echo "repository containing something like this:"
-               echo ""
-               echo " * -- * -- B -- O -- O -- O ($oldrev)"
-               echo "            \\"
-               echo "             N -- N -- N ($newrev)"
-               echo ""
-               echo "When this happens we assume that you've already had alert emails for all"
-               echo "of the O revisions, and so we here report only the revisions in the N"
-               echo "branch from the common base, B."
+
+               # Find the common ancestor of the old and new revisions and compare
+               # it with newrev
+               baserev=$(git merge-base $oldrev $newrev)
+               rewind_only=""
+               if [ "$baserev" = "$newrev" ]; then
+                       echo "This update discarded existing revisions and left the branch pointing at"
+                       echo "a previous point in the repository history."
+                       echo ""
+                       echo " * -- * -- N ($newrev)"
+                       echo "            \\"
+                       echo "             O -- O -- O ($oldrev)"
+                       echo ""
+                       echo "The removed revisions are not necessarilly gone - if another reference"
+                       echo "still refers to them they will stay in the repository."
+                       rewind_only=1
+               else
+                       echo "This update added new revisions after undoing existing revisions.  That is"
+                       echo "to say, the old revision is not a strict subset of the new revision.  This"
+                       echo "situation occurs when you --force push a change and generate a repository"
+                       echo "containing something like this:"
+                       echo ""
+                       echo " * -- * -- B -- O -- O -- O ($oldrev)"
+                       echo "            \\"
+                       echo "             N -- N -- N ($newrev)"
+                       echo ""
+                       echo "When this happens we assume that you've already had alert emails for all"
+                       echo "of the O revisions, and so we here report only the revisions in the N"
+                       echo "branch from the common base, B."
+               fi
        fi
 
        echo ""
-       echo "Those revisions listed above that are new to this repository have"
-       echo "not appeared on any other notification email; so we list those"
-       echo "revisions in full, below."
+       if [ -z "$rewind_only" ]; then
+               echo "Those revisions listed above that are new to this repository have"
+               echo "not appeared on any other notification email; so we list those"
+               echo "revisions in full, below."
 
-       echo ""
-       echo $LOGBEGIN
-       git rev-parse --not --branches | grep -v $(git rev-parse $refname) |
-       git rev-list --pretty --stdin $oldrev..$newrev
+               echo ""
+               echo $LOGBEGIN
+               git rev-parse --not --branches | grep -v $(git rev-parse $refname) |
+               git rev-list --pretty --stdin $oldrev..$newrev
 
-       # XXX: Need a way of detecting whether git rev-list actually outputted
-       # anything, so that we can issue a "no new revisions added by this
-       # update" message
+               # XXX: Need a way of detecting whether git rev-list actually outputted
+               # anything, so that we can issue a "no new revisions added by this
+               # update" message
 
-       echo $LOGEND
+               echo $LOGEND
+       else
+               echo "No new revisions were added by this update."
+       fi
 
        # The diffstat is shown from the old revision to the new revision.  This
        # is to show the truth of what happened in this change.  There's no point
@@ -556,7 +587,7 @@ if [ -z "$GIT_DIR" ]; then
        exit 1
 fi
 
-projectdesc=$(sed -e '1p' "$GIT_DIR/description")
+projectdesc=$(sed -ne '1p' "$GIT_DIR/description")
 # Check if the description is unchanged from it's default, and shorten it to a
 # more manageable length if it is
 if expr "$projectdesc" : "Unnamed repository.*$" >/dev/null
diff --git a/contrib/hooks/update-paranoid b/contrib/hooks/update-paranoid
new file mode 100644 (file)
index 0000000..5ee1835
--- /dev/null
@@ -0,0 +1,284 @@
+#!/usr/bin/perl
+
+use strict;
+use File::Spec;
+
+$ENV{PATH}     = '/opt/git/bin';
+my $acl_git    = '/vcs/acls.git';
+my $acl_branch = 'refs/heads/master';
+my $debug      = 0;
+
+=doc
+Invoked as: update refname old-sha1 new-sha1
+
+This script is run by git-receive-pack once for each ref that the
+client is trying to modify.  If we exit with a non-zero exit value
+then the update for that particular ref is denied, but updates for
+other refs in the same run of receive-pack may still be allowed.
+
+We are run after the objects have been uploaded, but before the
+ref is actually modified.  We take advantage of that fact when we
+look for "new" commits and tags (the new objects won't show up in
+`rev-list --all`).
+
+This script loads and parses the content of the config file
+"users/$this_user.acl" from the $acl_branch commit of $acl_git ODB.
+The acl file is a git-config style file, but uses a slightly more
+restricted syntax as the Perl parser contained within this script
+is not nearly as permissive as git-config.
+
+Example:
+
+  [user]
+    committer = John Doe <john.doe@example.com>
+    committer = John R. Doe <john.doe@example.com>
+
+  [repository "acls"]
+    allow = heads/master
+    allow = CDUR for heads/jd/
+    allow = C    for ^tags/v\\d+$
+
+For all new commit or tag objects the committer (or tagger) line
+within the object must exactly match one of the user.committer
+values listed in the acl file ("HEAD:users/$this_user.acl").
+
+For a branch to be modified an allow line within the matching
+repository section must be matched for both the refname and the
+opcode.
+
+Repository sections are matched on the basename of the repository
+(after removing the .git suffix).
+
+The opcode abbrevations are:
+
+  C: create new ref
+  D: delete existing ref
+  U: fast-forward existing ref (no commit loss)
+  R: rewind/rebase existing ref (commit loss)
+
+if no opcodes are listed before the "for" keyword then "U" (for
+fast-forward update only) is assumed as this is the most common
+usage.
+
+Refnames are matched by always assuming a prefix of "refs/".
+This hook forbids pushing or deleting anything not under "refs/".
+
+Refnames that start with ^ are Perl regular expressions, and the ^
+is kept as part of the regexp.  \\ is needed to get just one \, so
+\\d expands to \d in Perl.  The 3rd allow line above is an example.
+
+Refnames that don't start with ^ but that end with / are prefix
+matches (2nd allow line above); all other refnames are strict
+equality matches (1st allow line).
+
+Anything pushed to "heads/" (ok, really "refs/heads/") must be
+a commit.  Tags are not permitted here.
+
+Anything pushed to "tags/" (err, really "refs/tags/") must be an
+annotated tag.  Commits, blobs, trees, etc. are not permitted here.
+Annotated tag signatures aren't checked, nor are they required.
+
+The special subrepository of 'info/new-commit-check' can
+be created and used to allow users to push new commits and
+tags from another local repository to this one, even if they
+aren't the committer/tagger of those objects.  In a nut shell
+the info/new-commit-check directory is a Git repository whose
+objects/info/alternates file lists this repository and all other
+possible sources, and whose refs subdirectory contains symlinks
+to this repository's refs subdirectory, and to all other possible
+sources refs subdirectories.  Yes, this means that you cannot
+use packed-refs in those repositories as they won't be resolved
+correctly.
+
+=cut
+
+my $git_dir = $ENV{GIT_DIR};
+my $new_commit_check = "$git_dir/info/new-commit-check";
+my $ref = $ARGV[0];
+my $old = $ARGV[1];
+my $new = $ARGV[2];
+my $new_type;
+my ($this_user) = getpwuid $<; # REAL_USER_ID
+my $repository_name;
+my %user_committer;
+my @allow_rules;
+
+sub deny ($) {
+       print STDERR "-Deny-    $_[0]\n" if $debug;
+       print STDERR "\ndenied: $_[0]\n\n";
+       exit 1;
+}
+
+sub grant ($) {
+       print STDERR "-Grant-   $_[0]\n" if $debug;
+       exit 0;
+}
+
+sub info ($) {
+       print STDERR "-Info-    $_[0]\n" if $debug;
+}
+
+sub parse_config ($$) {
+       my ($data, $fn) = @_;
+       info "Loading $fn";
+       open(I,'-|','git',"--git-dir=$acl_git",'cat-file','blob',$fn);
+       my $section = '';
+       while (<I>) {
+               chomp;
+               if (/^\s*$/ || /^\s*#/) {
+               } elsif (/^\[([a-z]+)\]$/i) {
+                       $section = $1;
+               } elsif (/^\[([a-z]+)\s+"(.*)"\]$/i) {
+                       $section = "$1.$2";
+               } elsif (/^\s*([a-z][a-z0-9]+)\s*=\s*(.*?)\s*$/i) {
+                       push @{$data->{"$section.$1"}}, $2;
+               } else {
+                       deny "bad config file line $. in $fn";
+               }
+       }
+       close I;
+}
+
+sub all_new_committers () {
+       local $ENV{GIT_DIR} = $git_dir;
+       $ENV{GIT_DIR} = $new_commit_check if -d $new_commit_check;
+
+       info "Getting committers of new commits.";
+       my %used;
+       open(T,'-|','git','rev-list','--pretty=raw',$new,'--not','--all');
+       while (<T>) {
+               next unless s/^committer //;
+               chop;
+               s/>.*$/>/;
+               info "Found $_." unless $used{$_}++;
+       }
+       close T;
+       info "No new commits." unless %used;
+       keys %used;
+}
+
+sub all_new_taggers () {
+       my %exists;
+       open(T,'-|','git','for-each-ref','--format=%(objectname)','refs/tags');
+       while (<T>) {
+               chop;
+               $exists{$_} = 1;
+       }
+       close T;
+
+       info "Getting taggers of new tags.";
+       my %used;
+       my $obj = $new;
+       my $obj_type = $new_type;
+       while ($obj_type eq 'tag') {
+               last if $exists{$obj};
+               $obj_type = '';
+               open(T,'-|','git','cat-file','tag',$obj);
+               while (<T>) {
+                       chop;
+                       if (/^object ([a-z0-9]{40})$/) {
+                               $obj = $1;
+                       } elsif (/^type (.+)$/) {
+                               $obj_type = $1;
+                       } elsif (s/^tagger //) {
+                               s/>.*$/>/;
+                               info "Found $_." unless $used{$_}++;
+                               last;
+                       }
+               }
+               close T;
+       }
+       info "No new tags." unless %used;
+       keys %used;
+}
+
+sub check_committers (@) {
+       my @bad;
+       foreach (@_) { push @bad, $_ unless $user_committer{$_}; }
+       if (@bad) {
+               print STDERR "\n";
+               print STDERR "You are not $_.\n" foreach (sort @bad);
+               deny "You cannot push changes not committed by you.";
+       }
+}
+
+sub git_value (@) {
+       open(T,'-|','git',@_); local $_ = <T>; chop; close T;
+       $_;
+}
+
+deny "No GIT_DIR inherited from caller" unless $git_dir;
+deny "Need a ref name" unless $ref;
+deny "Refusing funny ref $ref" unless $ref =~ s,^refs/,,;
+deny "Bad old value $old" unless $old =~ /^[a-z0-9]{40}$/;
+deny "Bad new value $new" unless $new =~ /^[a-z0-9]{40}$/;
+deny "Cannot determine who you are." unless $this_user;
+
+$repository_name = File::Spec->rel2abs($git_dir);
+$repository_name =~ m,/([^/]+)(?:\.git|/\.git)$,;
+$repository_name = $1;
+info "Updating in '$repository_name'.";
+
+my $op;
+if    ($old =~ /^0{40}$/) { $op = 'C'; }
+elsif ($new =~ /^0{40}$/) { $op = 'D'; }
+else                      { $op = 'R'; }
+
+# This is really an update (fast-forward) if the
+# merge base of $old and $new is $old.
+#
+$op = 'U' if ($op eq 'R'
+       && $ref =~ m,^heads/,
+       && $old eq git_value('merge-base',$old,$new));
+
+# Load the user's ACL file.
+{
+       my %data = ('user.committer' => []);
+       parse_config(\%data, "$acl_branch:users/$this_user.acl");
+       %user_committer = map {$_ => $_} @{$data{'user.committer'}};
+       my $rules = $data{"repository.$repository_name.allow"} || [];
+       foreach (@$rules) {
+               if (/^([CDRU ]+)\s+for\s+([^\s]+)$/) {
+                       my $ops = $1;
+                       my $ref = $2;
+                       $ops =~ s/ //g;
+                       $ref =~ s/\\\\/\\/g;
+                       push @allow_rules, [$ops, $ref];
+               } elsif (/^for\s+([^\s]+)$/) {
+                       # Mentioned, but nothing granted?
+               } elsif (/^[^\s]+$/) {
+                       s/\\\\/\\/g;
+                       push @allow_rules, ['U', $_];
+               }
+       }
+}
+
+if ($op ne 'D') {
+       $new_type = git_value('cat-file','-t',$new);
+
+       if ($ref =~ m,^heads/,) {
+               deny "$ref must be a commit." unless $new_type eq 'commit';
+       } elsif ($ref =~ m,^tags/,) {
+               deny "$ref must be an annotated tag." unless $new_type eq 'tag';
+       }
+
+       check_committers (all_new_committers);
+       check_committers (all_new_taggers) if $new_type eq 'tag';
+}
+
+info "$this_user wants $op for $ref";
+foreach my $acl_entry (@allow_rules) {
+       my ($acl_ops, $acl_n) = @$acl_entry;
+       next unless $acl_ops =~ /^[CDRU]+$/; # Uhh.... shouldn't happen.
+       next unless $acl_n;
+       next unless $op =~ /^[$acl_ops]$/;
+
+       grant "Allowed by: $acl_ops for $acl_n"
+       if (
+          ($acl_n eq $ref)
+       || ($acl_n =~ m,/$, && substr($ref,0,length $acl_n) eq $acl_n)
+       || ($acl_n =~ m,^\^, && $ref =~ m:$acl_n:)
+       );
+}
+close A;
+deny "You are not permitted to $op $ref";
index 898bfe3eb219618e746afbe02eb4a8756eccc6aa..4b26b1a9b9979c2a7753afdad107c73f3e1aae4c 100644 (file)
--- a/convert.c
+++ b/convert.c
@@ -1,4 +1,7 @@
 #include "cache.h"
+#include "attr.h"
+#include "run-command.h"
+
 /*
  * convert.c - convert a file when checking it out and checking it in.
  *
@@ -8,6 +11,11 @@
  * translation when the "auto_crlf" option is set.
  */
 
+#define CRLF_GUESS     (-1)
+#define CRLF_BINARY    0
+#define CRLF_TEXT      1
+#define CRLF_INPUT     2
+
 struct text_stat {
        /* CR, LF and CRLF counts */
        unsigned cr, lf, crlf;
@@ -72,115 +80,590 @@ static int is_binary(unsigned long size, struct text_stat *stats)
        return 0;
 }
 
-int convert_to_git(const char *path, char **bufp, unsigned long *sizep)
+static char *crlf_to_git(const char *path, const char *src, unsigned long *sizep, int action)
 {
-       char *buffer, *nbuf;
+       char *buffer, *dst;
        unsigned long size, nsize;
        struct text_stat stats;
 
-       /*
-        * FIXME! Other pluggable conversions should go here,
-        * based on filename patterns. Right now we just do the
-        * stupid auto-CRLF one.
-        */
-       if (!auto_crlf)
-               return 0;
+       if ((action == CRLF_BINARY) || !auto_crlf)
+               return NULL;
 
        size = *sizep;
        if (!size)
-               return 0;
-       buffer = *bufp;
+               return NULL;
 
-       gather_stats(buffer, size, &stats);
+       gather_stats(src, size, &stats);
 
        /* No CR? Nothing to convert, regardless. */
        if (!stats.cr)
-               return 0;
+               return NULL;
 
-       /*
-        * We're currently not going to even try to convert stuff
-        * that has bare CR characters. Does anybody do that crazy
-        * stuff?
-        */
-       if (stats.cr != stats.crlf)
-               return 0;
+       if (action == CRLF_GUESS) {
+               /*
+                * We're currently not going to even try to convert stuff
+                * that has bare CR characters. Does anybody do that crazy
+                * stuff?
+                */
+               if (stats.cr != stats.crlf)
+                       return NULL;
 
-       /*
-        * And add some heuristics for binary vs text, of course...
-        */
-       if (is_binary(size, &stats))
-               return 0;
+               /*
+                * And add some heuristics for binary vs text, of course...
+                */
+               if (is_binary(size, &stats))
+                       return NULL;
+       }
 
        /*
-        * Ok, allocate a new buffer, fill it in, and return true
-        * to let the caller know that we switched buffers on it.
+        * Ok, allocate a new buffer, fill it in, and return it
+        * to let the caller know that we switched buffers.
         */
        nsize = size - stats.crlf;
-       nbuf = xmalloc(nsize);
-       *bufp = nbuf;
+       buffer = xmalloc(nsize);
        *sizep = nsize;
-       do {
-               unsigned char c = *buffer++;
-               if (c != '\r')
-                       *nbuf++ = c;
-       } while (--size);
 
-       return 1;
+       dst = buffer;
+       if (action == CRLF_GUESS) {
+               /*
+                * If we guessed, we already know we rejected a file with
+                * lone CR, and we can strip a CR without looking at what
+                * follow it.
+                */
+               do {
+                       unsigned char c = *src++;
+                       if (c != '\r')
+                               *dst++ = c;
+               } while (--size);
+       } else {
+               do {
+                       unsigned char c = *src++;
+                       if (! (c == '\r' && (1 < size && *src == '\n')))
+                               *dst++ = c;
+               } while (--size);
+       }
+
+       return buffer;
 }
 
-int convert_to_working_tree(const char *path, char **bufp, unsigned long *sizep)
+static char *crlf_to_worktree(const char *path, const char *src, unsigned long *sizep, int action)
 {
-       char *buffer, *nbuf;
+       char *buffer, *dst;
        unsigned long size, nsize;
        struct text_stat stats;
        unsigned char last;
 
-       /*
-        * FIXME! Other pluggable conversions should go here,
-        * based on filename patterns. Right now we just do the
-        * stupid auto-CRLF one.
-        */
-       if (auto_crlf <= 0)
-               return 0;
+       if ((action == CRLF_BINARY) || (action == CRLF_INPUT) ||
+           auto_crlf <= 0)
+               return NULL;
 
        size = *sizep;
        if (!size)
-               return 0;
-       buffer = *bufp;
+               return NULL;
 
-       gather_stats(buffer, size, &stats);
+       gather_stats(src, size, &stats);
 
        /* No LF? Nothing to convert, regardless. */
        if (!stats.lf)
-               return 0;
+               return NULL;
 
        /* Was it already in CRLF format? */
        if (stats.lf == stats.crlf)
-               return 0;
+               return NULL;
 
-       /* If we have any bare CR characters, we're not going to touch it */
-       if (stats.cr != stats.crlf)
-               return 0;
+       if (action == CRLF_GUESS) {
+               /* If we have any bare CR characters, we're not going to touch it */
+               if (stats.cr != stats.crlf)
+                       return NULL;
 
-       if (is_binary(size, &stats))
-               return 0;
+               if (is_binary(size, &stats))
+                       return NULL;
+       }
 
        /*
-        * Ok, allocate a new buffer, fill it in, and return true
-        * to let the caller know that we switched buffers on it.
+        * Ok, allocate a new buffer, fill it in, and return it
+        * to let the caller know that we switched buffers.
         */
        nsize = size + stats.lf - stats.crlf;
-       nbuf = xmalloc(nsize);
-       *bufp = nbuf;
+       buffer = xmalloc(nsize);
        *sizep = nsize;
        last = 0;
+
+       dst = buffer;
        do {
-               unsigned char c = *buffer++;
+               unsigned char c = *src++;
                if (c == '\n' && last != '\r')
-                       *nbuf++ = '\r';
-               *nbuf++ = c;
+                       *dst++ = '\r';
+               *dst++ = c;
                last = c;
        } while (--size);
 
-       return 1;
+       return buffer;
+}
+
+static int filter_buffer(const char *path, const char *src,
+                        unsigned long size, const char *cmd)
+{
+       /*
+        * Spawn cmd and feed the buffer contents through its stdin.
+        */
+       struct child_process child_process;
+       int pipe_feed[2];
+       int write_err, status;
+
+       memset(&child_process, 0, sizeof(child_process));
+
+       if (pipe(pipe_feed) < 0) {
+               error("cannot create pipe to run external filter %s", cmd);
+               return 1;
+       }
+
+       child_process.pid = fork();
+       if (child_process.pid < 0) {
+               error("cannot fork to run external filter %s", cmd);
+               close(pipe_feed[0]);
+               close(pipe_feed[1]);
+               return 1;
+       }
+       if (!child_process.pid) {
+               dup2(pipe_feed[0], 0);
+               close(pipe_feed[0]);
+               close(pipe_feed[1]);
+               execlp("sh", "sh", "-c", cmd, NULL);
+               return 1;
+       }
+       close(pipe_feed[0]);
+
+       write_err = (write_in_full(pipe_feed[1], src, size) < 0);
+       if (close(pipe_feed[1]))
+               write_err = 1;
+       if (write_err)
+               error("cannot feed the input to external filter %s", cmd);
+
+       status = finish_command(&child_process);
+       if (status)
+               error("external filter %s failed %d", cmd, -status);
+       return (write_err || status);
+}
+
+static char *apply_filter(const char *path, const char *src,
+                         unsigned long *sizep, const char *cmd)
+{
+       /*
+        * Create a pipeline to have the command filter the buffer's
+        * contents.
+        *
+        * (child --> cmd) --> us
+        */
+       const int SLOP = 4096;
+       int pipe_feed[2];
+       int status;
+       char *dst;
+       unsigned long dstsize, dstalloc;
+       struct child_process child_process;
+
+       if (!cmd)
+               return NULL;
+
+       memset(&child_process, 0, sizeof(child_process));
+
+       if (pipe(pipe_feed) < 0) {
+               error("cannot create pipe to run external filter %s", cmd);
+               return NULL;
+       }
+
+       fflush(NULL);
+       child_process.pid = fork();
+       if (child_process.pid < 0) {
+               error("cannot fork to run external filter %s", cmd);
+               close(pipe_feed[0]);
+               close(pipe_feed[1]);
+               return NULL;
+       }
+       if (!child_process.pid) {
+               dup2(pipe_feed[1], 1);
+               close(pipe_feed[0]);
+               close(pipe_feed[1]);
+               exit(filter_buffer(path, src, *sizep, cmd));
+       }
+       close(pipe_feed[1]);
+
+       dstalloc = *sizep;
+       dst = xmalloc(dstalloc);
+       dstsize = 0;
+
+       while (1) {
+               ssize_t numread = xread(pipe_feed[0], dst + dstsize,
+                                       dstalloc - dstsize);
+
+               if (numread <= 0) {
+                       if (!numread)
+                               break;
+                       error("read from external filter %s failed", cmd);
+                       free(dst);
+                       dst = NULL;
+                       break;
+               }
+               dstsize += numread;
+               if (dstalloc <= dstsize + SLOP) {
+                       dstalloc = dstsize + SLOP;
+                       dst = xrealloc(dst, dstalloc);
+               }
+       }
+       if (close(pipe_feed[0])) {
+               error("read from external filter %s failed", cmd);
+               free(dst);
+               dst = NULL;
+       }
+
+       status = finish_command(&child_process);
+       if (status) {
+               error("external filter %s failed %d", cmd, -status);
+               free(dst);
+               dst = NULL;
+       }
+
+       if (dst)
+               *sizep = dstsize;
+       return dst;
+}
+
+static struct convert_driver {
+       const char *name;
+       struct convert_driver *next;
+       char *smudge;
+       char *clean;
+} *user_convert, **user_convert_tail;
+
+static int read_convert_config(const char *var, const char *value)
+{
+       const char *ep, *name;
+       int namelen;
+       struct convert_driver *drv;
+
+       /*
+        * External conversion drivers are configured using
+        * "filter.<name>.variable".
+        */
+       if (prefixcmp(var, "filter.") || (ep = strrchr(var, '.')) == var + 6)
+               return 0;
+       name = var + 7;
+       namelen = ep - name;
+       for (drv = user_convert; drv; drv = drv->next)
+               if (!strncmp(drv->name, name, namelen) && !drv->name[namelen])
+                       break;
+       if (!drv) {
+               char *namebuf;
+               drv = xcalloc(1, sizeof(struct convert_driver));
+               namebuf = xmalloc(namelen + 1);
+               memcpy(namebuf, name, namelen);
+               namebuf[namelen] = 0;
+               drv->name = namebuf;
+               drv->next = NULL;
+               *user_convert_tail = drv;
+               user_convert_tail = &(drv->next);
+       }
+
+       ep++;
+
+       /*
+        * filter.<name>.smudge and filter.<name>.clean specifies
+        * the command line:
+        *
+        *      command-line
+        *
+        * The command-line will not be interpolated in any way.
+        */
+
+       if (!strcmp("smudge", ep)) {
+               if (!value)
+                       return error("%s: lacks value", var);
+               drv->smudge = strdup(value);
+               return 0;
+       }
+
+       if (!strcmp("clean", ep)) {
+               if (!value)
+                       return error("%s: lacks value", var);
+               drv->clean = strdup(value);
+               return 0;
+       }
+       return 0;
+}
+
+static void setup_convert_check(struct git_attr_check *check)
+{
+       static struct git_attr *attr_crlf;
+       static struct git_attr *attr_ident;
+       static struct git_attr *attr_filter;
+
+       if (!attr_crlf) {
+               attr_crlf = git_attr("crlf", 4);
+               attr_ident = git_attr("ident", 5);
+               attr_filter = git_attr("filter", 6);
+               user_convert_tail = &user_convert;
+               git_config(read_convert_config);
+       }
+       check[0].attr = attr_crlf;
+       check[1].attr = attr_ident;
+       check[2].attr = attr_filter;
+}
+
+static int count_ident(const char *cp, unsigned long size)
+{
+       /*
+        * "$Id: 0000000000000000000000000000000000000000 $" <=> "$Id$"
+        */
+       int cnt = 0;
+       char ch;
+
+       while (size) {
+               ch = *cp++;
+               size--;
+               if (ch != '$')
+                       continue;
+               if (size < 3)
+                       break;
+               if (memcmp("Id", cp, 2))
+                       continue;
+               ch = cp[2];
+               cp += 3;
+               size -= 3;
+               if (ch == '$')
+                       cnt++; /* $Id$ */
+               if (ch != ':')
+                       continue;
+
+               /*
+                * "$Id: ... "; scan up to the closing dollar sign and discard.
+                */
+               while (size) {
+                       ch = *cp++;
+                       size--;
+                       if (ch == '$') {
+                               cnt++;
+                               break;
+                       }
+               }
+       }
+       return cnt;
+}
+
+static char *ident_to_git(const char *path, const char *src, unsigned long *sizep, int ident)
+{
+       int cnt;
+       unsigned long size;
+       char *dst, *buf;
+
+       if (!ident)
+               return NULL;
+       size = *sizep;
+       cnt = count_ident(src, size);
+       if (!cnt)
+               return NULL;
+       buf = xmalloc(size);
+
+       for (dst = buf; size; size--) {
+               char ch = *src++;
+               *dst++ = ch;
+               if ((ch == '$') && (3 <= size) &&
+                   !memcmp("Id:", src, 3)) {
+                       unsigned long rem = size - 3;
+                       const char *cp = src + 3;
+                       do {
+                               ch = *cp++;
+                               if (ch == '$')
+                                       break;
+                               rem--;
+                       } while (rem);
+                       if (!rem)
+                               continue;
+                       memcpy(dst, "Id$", 3);
+                       dst += 3;
+                       size -= (cp - src);
+                       src = cp;
+               }
+       }
+
+       *sizep = dst - buf;
+       return buf;
+}
+
+static char *ident_to_worktree(const char *path, const char *src, unsigned long *sizep, int ident)
+{
+       int cnt;
+       unsigned long size;
+       char *dst, *buf;
+       unsigned char sha1[20];
+
+       if (!ident)
+               return NULL;
+
+       size = *sizep;
+       cnt = count_ident(src, size);
+       if (!cnt)
+               return NULL;
+
+       hash_sha1_file(src, size, "blob", sha1);
+       buf = xmalloc(size + cnt * 43);
+
+       for (dst = buf; size; size--) {
+               const char *cp;
+               char ch = *src++;
+               *dst++ = ch;
+               if ((ch != '$') || (size < 3) || memcmp("Id", src, 2))
+                       continue;
+
+               if (src[2] == ':') {
+                       /* discard up to but not including the closing $ */
+                       unsigned long rem = size - 3;
+                       cp = src + 3;
+                       do {
+                               ch = *cp++;
+                               if (ch == '$')
+                                       break;
+                               rem--;
+                       } while (rem);
+                       if (!rem)
+                               continue;
+                       size -= (cp - src);
+               } else if (src[2] == '$')
+                       cp = src + 2;
+               else
+                       continue;
+
+               memcpy(dst, "Id: ", 4);
+               dst += 4;
+               memcpy(dst, sha1_to_hex(sha1), 40);
+               dst += 40;
+               *dst++ = ' ';
+               size -= (cp - src);
+               src = cp;
+               *dst++ = *src++;
+               size--;
+       }
+
+       *sizep = dst - buf;
+       return buf;
+}
+
+static int git_path_check_crlf(const char *path, struct git_attr_check *check)
+{
+       const char *value = check->value;
+
+       if (ATTR_TRUE(value))
+               return CRLF_TEXT;
+       else if (ATTR_FALSE(value))
+               return CRLF_BINARY;
+       else if (ATTR_UNSET(value))
+               ;
+       else if (!strcmp(value, "input"))
+               return CRLF_INPUT;
+       return CRLF_GUESS;
+}
+
+static struct convert_driver *git_path_check_convert(const char *path,
+                                            struct git_attr_check *check)
+{
+       const char *value = check->value;
+       struct convert_driver *drv;
+
+       if (ATTR_TRUE(value) || ATTR_FALSE(value) || ATTR_UNSET(value))
+               return NULL;
+       for (drv = user_convert; drv; drv = drv->next)
+               if (!strcmp(value, drv->name))
+                       return drv;
+       return NULL;
+}
+
+static int git_path_check_ident(const char *path, struct git_attr_check *check)
+{
+       const char *value = check->value;
+
+       return !!ATTR_TRUE(value);
+}
+
+char *convert_to_git(const char *path, const char *src, unsigned long *sizep)
+{
+       struct git_attr_check check[3];
+       int crlf = CRLF_GUESS;
+       int ident = 0;
+       char *filter = NULL;
+       char *buf, *buf2;
+
+       setup_convert_check(check);
+       if (!git_checkattr(path, ARRAY_SIZE(check), check)) {
+               struct convert_driver *drv;
+               crlf = git_path_check_crlf(path, check + 0);
+               ident = git_path_check_ident(path, check + 1);
+               drv = git_path_check_convert(path, check + 2);
+               if (drv && drv->clean)
+                       filter = drv->clean;
+       }
+
+       buf = apply_filter(path, src, sizep, filter);
+
+       buf2 = crlf_to_git(path, buf ? buf : src, sizep, crlf);
+       if (buf2) {
+               free(buf);
+               buf = buf2;
+       }
+
+       buf2 = ident_to_git(path, buf ? buf : src, sizep, ident);
+       if (buf2) {
+               free(buf);
+               buf = buf2;
+       }
+
+       return buf;
+}
+
+char *convert_to_working_tree(const char *path, const char *src, unsigned long *sizep)
+{
+       struct git_attr_check check[3];
+       int crlf = CRLF_GUESS;
+       int ident = 0;
+       char *filter = NULL;
+       char *buf, *buf2;
+
+       setup_convert_check(check);
+       if (!git_checkattr(path, ARRAY_SIZE(check), check)) {
+               struct convert_driver *drv;
+               crlf = git_path_check_crlf(path, check + 0);
+               ident = git_path_check_ident(path, check + 1);
+               drv = git_path_check_convert(path, check + 2);
+               if (drv && drv->smudge)
+                       filter = drv->smudge;
+       }
+
+       buf = ident_to_worktree(path, src, sizep, ident);
+
+       buf2 = crlf_to_worktree(path, buf ? buf : src, sizep, crlf);
+       if (buf2) {
+               free(buf);
+               buf = buf2;
+       }
+
+       buf2 = apply_filter(path, buf ? buf : src, sizep, filter);
+       if (buf2) {
+               free(buf);
+               buf = buf2;
+       }
+
+       return buf;
+}
+
+void *convert_sha1_file(const char *path, const unsigned char *sha1,
+                        unsigned int mode, enum object_type *type,
+                        unsigned long *size)
+{
+       void *buffer = read_sha1_file(sha1, type, size);
+       if (S_ISREG(mode) && buffer) {
+               void *converted = convert_to_working_tree(path, buffer, size);
+               if (converted) {
+                       free(buffer);
+                       buffer = converted;
+               }
+       }
+       return buffer;
 }
diff --git a/copy.c b/copy.c
index 08a3d388a4c312fca18558b2b8e5dcf027c7afaf..d340bb253ec35af379c29e71f384e15d6822fb9a 100644 (file)
--- a/copy.c
+++ b/copy.c
@@ -3,10 +3,9 @@
 int copy_fd(int ifd, int ofd)
 {
        while (1) {
-               int len;
                char buffer[8192];
                char *buf = buffer;
-               len = xread(ifd, buffer, sizeof(buffer));
+               ssize_t len = xread(ifd, buffer, sizeof(buffer));
                if (!len)
                        break;
                if (len < 0) {
index b7174c6c056c5a8f2a800ecbcb3cdf304c0bfc3f..7c806ada48d0fd58c091f9415fc8bb2f61bdd2e6 100644 (file)
@@ -49,6 +49,8 @@ int sha1close(struct sha1file *f, unsigned char *result, int update)
 
 int sha1write(struct sha1file *f, void *buf, unsigned int count)
 {
+       if (f->do_crc)
+               f->crc32 = crc32(f->crc32, buf, count);
        while (count) {
                unsigned offset = f->offset;
                unsigned left = sizeof(f->buffer) - offset;
@@ -91,6 +93,7 @@ struct sha1file *sha1create(const char *fmt, ...)
        f->fd = fd;
        f->error = 0;
        f->offset = 0;
+       f->do_crc = 0;
        SHA1_Init(&f->ctx);
        return f;
 }
@@ -111,6 +114,7 @@ struct sha1file *sha1fd(int fd, const char *name)
        f->fd = fd;
        f->error = 0;
        f->offset = 0;
+       f->do_crc = 0;
        SHA1_Init(&f->ctx);
        return f;
 }
@@ -143,4 +147,14 @@ int sha1write_compressed(struct sha1file *f, void *in, unsigned int size)
        return size;
 }
 
+void crc32_begin(struct sha1file *f)
+{
+       f->crc32 = crc32(0, Z_NULL, 0);
+       f->do_crc = 1;
+}
 
+uint32_t crc32_end(struct sha1file *f)
+{
+       f->do_crc = 0;
+       return f->crc32;
+}
index 3ad1a992a758fc9339baa2cecc17ac14af359f1b..7e1339189dcdc6e271fad5df7ad427954642ae9e 100644 (file)
@@ -7,6 +7,8 @@ struct sha1file {
        unsigned int offset, namelen;
        SHA_CTX ctx;
        char name[PATH_MAX];
+       int do_crc;
+       uint32_t crc32;
        unsigned char buffer[8192];
 };
 
@@ -15,5 +17,7 @@ extern struct sha1file *sha1create(const char *fmt, ...) __attribute__((format (
 extern int sha1close(struct sha1file *, unsigned char *, int);
 extern int sha1write(struct sha1file *, void *, unsigned int);
 extern int sha1write_compressed(struct sha1file *, void *, unsigned int);
+extern void crc32_begin(struct sha1file *);
+extern uint32_t crc32_end(struct sha1file *);
 
 #endif
diff --git a/date.c b/date.c
index 0ceccbe03401faa67836577b9bdbe139fe025dd5..a9b59a289e7b22f34958ccc7b80b02a01cf793c6 100644 (file)
--- a/date.c
+++ b/date.c
@@ -55,6 +55,32 @@ static struct tm *time_to_tm(unsigned long time, int tz)
        return gmtime(&t);
 }
 
+/*
+ * What value of "tz" was in effect back then at "time" in the
+ * local timezone?
+ */
+static int local_tzoffset(unsigned long time)
+{
+       time_t t, t_local;
+       struct tm tm;
+       int offset, eastwest;
+
+       t = time;
+       localtime_r(&t, &tm);
+       t_local = my_mktime(&tm);
+
+       if (t_local < t) {
+               eastwest = -1;
+               offset = t - t_local;
+       } else {
+               eastwest = 1;
+               offset = t_local - t;
+       }
+       offset /= 60; /* in minutes */
+       offset = (offset % 60) + ((offset / 60) * 100);
+       return offset * eastwest;
+}
+
 const char *show_date(unsigned long time, int tz, enum date_mode mode)
 {
        struct tm *tm;
@@ -102,6 +128,9 @@ const char *show_date(unsigned long time, int tz, enum date_mode mode)
                /* Else fall back on absolute format.. */
        }
 
+       if (mode == DATE_LOCAL)
+               tz = local_tzoffset(time);
+
        tm = time_to_tm(time, tz);
        if (!tm)
                return NULL;
@@ -109,12 +138,14 @@ const char *show_date(unsigned long time, int tz, enum date_mode mode)
                sprintf(timebuf, "%04d-%02d-%02d", tm->tm_year + 1900,
                                tm->tm_mon + 1, tm->tm_mday);
        else
-               sprintf(timebuf, "%.3s %.3s %d %02d:%02d:%02d %d %+05d",
+               sprintf(timebuf, "%.3s %.3s %d %02d:%02d:%02d %d%c%+05d",
                                weekday_names[tm->tm_wday],
                                month_names[tm->tm_mon],
                                tm->tm_mday,
                                tm->tm_hour, tm->tm_min, tm->tm_sec,
-                               tm->tm_year + 1900, tz);
+                               tm->tm_year + 1900,
+                               (mode == DATE_LOCAL) ? 0 : ' ',
+                               tz);
        return timebuf;
 }
 
diff --git a/decorate.c b/decorate.c
new file mode 100644 (file)
index 0000000..23f6b00
--- /dev/null
@@ -0,0 +1,88 @@
+/*
+ * decorate.c - decorate a git object with some arbitrary
+ * data.
+ */
+#include "cache.h"
+#include "object.h"
+#include "decorate.h"
+
+static unsigned int hash_obj(struct object *obj, unsigned int n)
+{
+       unsigned int hash = *(unsigned int *)obj->sha1;
+       return hash % n;
+}
+
+static void *insert_decoration(struct decoration *n, struct object *base, void *decoration)
+{
+       int size = n->size;
+       struct object_decoration *hash = n->hash;
+       int j = hash_obj(base, size);
+
+       while (hash[j].base) {
+               if (hash[j].base == base) {
+                       void *old = hash[j].decoration;
+                       hash[j].decoration = decoration;
+                       return old;
+               }
+               if (++j >= size)
+                       j = 0;
+       }
+       hash[j].base = base;
+       hash[j].decoration = decoration;
+       n->nr++;
+       return NULL;
+}
+
+static void grow_decoration(struct decoration *n)
+{
+       int i;
+       int old_size = n->size;
+       struct object_decoration *old_hash;
+
+       old_size = n->size;
+       old_hash = n->hash;
+
+       n->size = (old_size + 1000) * 3 / 2;
+       n->hash = xcalloc(n->size, sizeof(struct object_decoration));
+       n->nr = 0;
+
+       for (i = 0; i < old_size; i++) {
+               struct object *base = old_hash[i].base;
+               void *decoration = old_hash[i].decoration;
+
+               if (!base)
+                       continue;
+               insert_decoration(n, base, decoration);
+       }
+       free(old_hash);
+}
+
+/* Add a decoration pointer, return any old one */
+void *add_decoration(struct decoration *n, struct object *obj, void *decoration)
+{
+       int nr = n->nr + 1;
+
+       if (nr > n->size * 2 / 3)
+               grow_decoration(n);
+       return insert_decoration(n, obj, decoration);
+}
+
+/* Lookup a decoration pointer */
+void *lookup_decoration(struct decoration *n, struct object *obj)
+{
+       int j;
+
+       /* nothing to lookup */
+       if (!n->size)
+               return NULL;
+       j = hash_obj(obj, n->size);
+       for (;;) {
+               struct object_decoration *ref = n->hash + j;
+               if (ref->base == obj)
+                       return ref->decoration;
+               if (!ref->base)
+                       return NULL;
+               if (++j == n->size)
+                       j = 0;
+       }
+}
diff --git a/decorate.h b/decorate.h
new file mode 100644 (file)
index 0000000..1fa4ad9
--- /dev/null
@@ -0,0 +1,18 @@
+#ifndef DECORATE_H
+#define DECORATE_H
+
+struct object_decoration {
+       struct object *base;
+       void *decoration;
+};
+
+struct decoration {
+       const char *name;
+       unsigned int size, nr;
+       struct object_decoration *hash;
+};
+
+extern void *add_decoration(struct decoration *n, struct object *obj, void *decoration);
+extern void *lookup_decoration(struct decoration *n, struct object *obj);
+
+#endif
index 7531e20c784c44c0b5d3ecb2057638874a09ce6c..07f4e8106a51384d2236b182438472884c300da6 100644 (file)
@@ -373,7 +373,7 @@ int run_diff_files(struct rev_info *revs, int silent_on_removed)
                                        continue;
                        }
                        else
-                               dpath->mode = canon_mode(st.st_mode);
+                               dpath->mode = ntohl(ce_mode_from_stat(ce, st.st_mode));
 
                        while (i < entries) {
                                struct cache_entry *nce = active_cache[i];
@@ -390,8 +390,7 @@ int run_diff_files(struct rev_info *revs, int silent_on_removed)
                                        int mode = ntohl(nce->ce_mode);
                                        num_compare_stages++;
                                        hashcpy(dpath->parent[stage-2].sha1, nce->sha1);
-                                       dpath->parent[stage-2].mode =
-                                               canon_mode(mode);
+                                       dpath->parent[stage-2].mode = ntohl(ce_mode_from_stat(nce, mode));
                                        dpath->parent[stage-2].status =
                                                DIFF_STATUS_MODIFIED;
                                }
@@ -440,15 +439,7 @@ int run_diff_files(struct rev_info *revs, int silent_on_removed)
                if (!changed && !revs->diffopt.find_copies_harder)
                        continue;
                oldmode = ntohl(ce->ce_mode);
-
-               newmode = canon_mode(st.st_mode);
-               if (!trust_executable_bit &&
-                   S_ISREG(newmode) && S_ISREG(oldmode) &&
-                   ((newmode ^ oldmode) == 0111))
-                       newmode = oldmode;
-               else if (!has_symlinks &&
-                   S_ISREG(newmode) && S_ISLNK(oldmode))
-                       newmode = oldmode;
+               newmode = ntohl(ce_mode_from_stat(ce, st.st_mode));
                diff_change(&revs->diffopt, oldmode, newmode,
                            ce->sha1, (changed ? null_sha1 : ce->sha1),
                            ce->name, NULL);
diff --git a/diff.c b/diff.c
index b28933f870d7dd11596fd3715280e1488ac2430f..33297aa8a7fd8f1c7f4711a9807b0b497d3de2ae 100644 (file)
--- a/diff.c
+++ b/diff.c
@@ -8,6 +8,7 @@
 #include "delta.h"
 #include "xdiff-interface.h"
 #include "color.h"
+#include "attr.h"
 
 #ifdef NO_FAST_WORKING_DIRECTORY
 #define FAST_WORKING_DIRECTORY 0
@@ -15,8 +16,6 @@
 #define FAST_WORKING_DIRECTORY 1
 #endif
 
-static int use_size_cache;
-
 static int diff_detect_rename_default;
 static int diff_rename_limit_default = -1;
 static int diff_use_color_default;
@@ -51,6 +50,49 @@ static int parse_diff_color_slot(const char *var, int ofs)
        die("bad config variable '%s'", var);
 }
 
+static struct ll_diff_driver {
+       const char *name;
+       struct ll_diff_driver *next;
+       char *cmd;
+} *user_diff, **user_diff_tail;
+
+/*
+ * Currently there is only "diff.<drivername>.command" variable;
+ * because there are "diff.color.<slot>" variables, we are parsing
+ * this in a bit convoluted way to allow low level diff driver
+ * called "color".
+ */
+static int parse_lldiff_command(const char *var, const char *ep, const char *value)
+{
+       const char *name;
+       int namelen;
+       struct ll_diff_driver *drv;
+
+       name = var + 5;
+       namelen = ep - name;
+       for (drv = user_diff; drv; drv = drv->next)
+               if (!strncmp(drv->name, name, namelen) && !drv->name[namelen])
+                       break;
+       if (!drv) {
+               char *namebuf;
+               drv = xcalloc(1, sizeof(struct ll_diff_driver));
+               namebuf = xmalloc(namelen + 1);
+               memcpy(namebuf, name, namelen);
+               namebuf[namelen] = 0;
+               drv->name = namebuf;
+               drv->next = NULL;
+               if (!user_diff_tail)
+                       user_diff_tail = &user_diff;
+               *user_diff_tail = drv;
+               user_diff_tail = &(drv->next);
+       }
+
+       if (!value)
+               return error("%s: lacks value", var);
+       drv->cmd = strdup(value);
+       return 0;
+}
+
 /*
  * These are to give UI layer defaults.
  * The core-level commands such as git-diff-files should
@@ -77,11 +119,18 @@ int git_diff_ui_config(const char *var, const char *value)
                        diff_detect_rename_default = DIFF_DETECT_RENAME;
                return 0;
        }
+       if (!prefixcmp(var, "diff.")) {
+               const char *ep = strrchr(var, '.');
+
+               if (ep != var + 4 && !strcmp(ep, ".command"))
+                       return parse_lldiff_command(var, ep, value);
+       }
        if (!prefixcmp(var, "diff.color.") || !prefixcmp(var, "color.diff.")) {
                int slot = parse_diff_color_slot(var, 11);
                color_parse(value, var, diff_colors[slot]);
                return 0;
        }
+
        return git_default_config(var, value);
 }
 
@@ -811,7 +860,12 @@ static void show_stats(struct diffstat_t* data, struct diff_options *options)
 
                if (data->files[i]->is_binary) {
                        show_name(prefix, name, len, reset, set);
-                       printf("  Bin\n");
+                       printf("  Bin ");
+                       printf("%s%d%s", del_c, deleted, reset);
+                       printf(" -> ");
+                       printf("%s%d%s", add_c, added, reset);
+                       printf(" bytes");
+                       printf("\n");
                        goto free_diffstat_file;
                }
                else if (data->files[i]->is_unmerged) {
@@ -1046,13 +1100,39 @@ static void emit_binary_diff(mmfile_t *one, mmfile_t *two)
        emit_binary_diff_body(two, one);
 }
 
+static void setup_diff_attr_check(struct git_attr_check *check)
+{
+       static struct git_attr *attr_diff;
+
+       if (!attr_diff)
+               attr_diff = git_attr("diff", 4);
+       check->attr = attr_diff;
+}
+
 #define FIRST_FEW_BYTES 8000
-static int mmfile_is_binary(mmfile_t *mf)
+static int file_is_binary(struct diff_filespec *one)
 {
-       long sz = mf->size;
+       unsigned long sz;
+       struct git_attr_check attr_diff_check;
+
+       setup_diff_attr_check(&attr_diff_check);
+       if (!git_checkattr(one->path, 1, &attr_diff_check)) {
+               const char *value = attr_diff_check.value;
+               if (ATTR_TRUE(value))
+                       return 0;
+               else if (ATTR_FALSE(value))
+                       return 1;
+       }
+
+       if (!one->data) {
+               if (!DIFF_FILE_VALID(one))
+                       return 0;
+               diff_populate_filespec(one, 0);
+       }
+       sz = one->size;
        if (FIRST_FEW_BYTES < sz)
                sz = FIRST_FEW_BYTES;
-       return !!memchr(mf->ptr, 0, sz);
+       return !!memchr(one->data, 0, sz);
 }
 
 static void builtin_diff(const char *name_a,
@@ -1109,7 +1189,7 @@ static void builtin_diff(const char *name_a,
        if (fill_mmfile(&mf1, one) < 0 || fill_mmfile(&mf2, two) < 0)
                die("unable to read files to diff");
 
-       if (!o->text && (mmfile_is_binary(&mf1) || mmfile_is_binary(&mf2))) {
+       if (!o->text && (file_is_binary(one) || file_is_binary(two))) {
                /* Quite common confusing case */
                if (mf1.size == mf2.size &&
                    !memcmp(mf1.ptr, mf2.ptr, mf1.size))
@@ -1154,6 +1234,8 @@ static void builtin_diff(const char *name_a,
        }
 
  free_ab_and_return:
+       diff_free_filespec_data(one);
+       diff_free_filespec_data(two);
        free(a_one);
        free(b_two);
        return;
@@ -1180,14 +1262,16 @@ static void builtin_diffstat(const char *name_a, const char *name_b,
                diff_populate_filespec(two, 0);
                data->deleted = count_lines(one->data, one->size);
                data->added = count_lines(two->data, two->size);
-               return;
+               goto free_and_return;
        }
        if (fill_mmfile(&mf1, one) < 0 || fill_mmfile(&mf2, two) < 0)
                die("unable to read files to diff");
 
-       if (mmfile_is_binary(&mf1) || mmfile_is_binary(&mf2))
+       if (file_is_binary(one) || file_is_binary(two)) {
                data->is_binary = 1;
-       else {
+               data->added = mf2.size;
+               data->deleted = mf1.size;
+       } else {
                /* Crazy xdl interfaces.. */
                xpparam_t xpp;
                xdemitconf_t xecfg;
@@ -1200,6 +1284,10 @@ static void builtin_diffstat(const char *name_a, const char *name_b,
                ecb.priv = diffstat;
                xdl_diff(&mf1, &mf2, &xpp, &xecfg, &ecb);
        }
+
+ free_and_return:
+       diff_free_filespec_data(one);
+       diff_free_filespec_data(two);
 }
 
 static void builtin_checkdiff(const char *name_a, const char *name_b,
@@ -1221,8 +1309,8 @@ static void builtin_checkdiff(const char *name_a, const char *name_b,
        if (fill_mmfile(&mf1, one) < 0 || fill_mmfile(&mf2, two) < 0)
                die("unable to read files to diff");
 
-       if (mmfile_is_binary(&mf2))
-               return;
+       if (file_is_binary(two))
+               goto free_and_return;
        else {
                /* Crazy xdl interfaces.. */
                xpparam_t xpp;
@@ -1236,6 +1324,9 @@ static void builtin_checkdiff(const char *name_a, const char *name_b,
                ecb.priv = &data;
                xdl_diff(&mf1, &mf2, &xpp, &xecfg, &ecb);
        }
+ free_and_return:
+       diff_free_filespec_data(one);
+       diff_free_filespec_data(two);
 }
 
 struct diff_filespec *alloc_filespec(const char *path)
@@ -1315,61 +1406,12 @@ static int reuse_worktree_file(const char *name, const unsigned char *sha1, int
        return 1;
 }
 
-static struct sha1_size_cache {
-       unsigned char sha1[20];
-       unsigned long size;
-} **sha1_size_cache;
-static int sha1_size_cache_nr, sha1_size_cache_alloc;
-
-static struct sha1_size_cache *locate_size_cache(unsigned char *sha1,
-                                                int find_only,
-                                                unsigned long size)
-{
-       int first, last;
-       struct sha1_size_cache *e;
-
-       first = 0;
-       last = sha1_size_cache_nr;
-       while (last > first) {
-               int cmp, next = (last + first) >> 1;
-               e = sha1_size_cache[next];
-               cmp = hashcmp(e->sha1, sha1);
-               if (!cmp)
-                       return e;
-               if (cmp < 0) {
-                       last = next;
-                       continue;
-               }
-               first = next+1;
-       }
-       /* not found */
-       if (find_only)
-               return NULL;
-       /* insert to make it at "first" */
-       if (sha1_size_cache_alloc <= sha1_size_cache_nr) {
-               sha1_size_cache_alloc = alloc_nr(sha1_size_cache_alloc);
-               sha1_size_cache = xrealloc(sha1_size_cache,
-                                          sha1_size_cache_alloc *
-                                          sizeof(*sha1_size_cache));
-       }
-       sha1_size_cache_nr++;
-       if (first < sha1_size_cache_nr)
-               memmove(sha1_size_cache + first + 1, sha1_size_cache + first,
-                       (sha1_size_cache_nr - first - 1) *
-                       sizeof(*sha1_size_cache));
-       e = xmalloc(sizeof(struct sha1_size_cache));
-       sha1_size_cache[first] = e;
-       hashcpy(e->sha1, sha1);
-       e->size = size;
-       return e;
-}
-
 static int populate_from_stdin(struct diff_filespec *s)
 {
 #define INCREMENT 1024
        char *buf;
        unsigned long size;
-       int got;
+       ssize_t got;
 
        size = 0;
        buf = NULL;
@@ -1390,6 +1432,22 @@ static int populate_from_stdin(struct diff_filespec *s)
        return 0;
 }
 
+static int diff_populate_gitlink(struct diff_filespec *s, int size_only)
+{
+       int len;
+       char *data = xmalloc(100);
+       len = snprintf(data, 100,
+               "Subproject commit %s\n", sha1_to_hex(s->sha1));
+       s->data = data;
+       s->size = len;
+       s->should_free = 1;
+       if (size_only) {
+               s->data = NULL;
+               free(data);
+       }
+       return 0;
+}
+
 /*
  * While doing rename detection and pickaxe operation, we may need to
  * grab the data for the blob (or file) for our own in-core comparison.
@@ -1403,11 +1461,15 @@ int diff_populate_filespec(struct diff_filespec *s, int size_only)
        if (S_ISDIR(s->mode))
                return -1;
 
-       if (!use_size_cache)
-               size_only = 0;
-
        if (s->data)
-               return err;
+               return 0;
+
+       if (size_only && 0 < s->size)
+               return 0;
+
+       if (S_ISDIRLNK(s->mode))
+               return diff_populate_gitlink(s, size_only);
+
        if (!s->sha1_valid ||
            reuse_worktree_file(s->path, s->sha1, 0)) {
                struct stat st;
@@ -1454,9 +1516,9 @@ int diff_populate_filespec(struct diff_filespec *s, int size_only)
                /*
                 * Convert from working tree format to canonical git format
                 */
-               buf = s->data;
                size = s->size;
-               if (convert_to_git(s->path, &buf, &size)) {
+               buf = convert_to_git(s->path, s->data, &size);
+               if (buf) {
                        munmap(s->data, s->size);
                        s->should_munmap = 0;
                        s->data = buf;
@@ -1466,19 +1528,8 @@ int diff_populate_filespec(struct diff_filespec *s, int size_only)
        }
        else {
                enum object_type type;
-               struct sha1_size_cache *e;
-
-               if (size_only && use_size_cache &&
-                   (e = locate_size_cache(s->sha1, 1, 0)) != NULL) {
-                       s->size = e->size;
-                       return 0;
-               }
-
-               if (size_only) {
+               if (size_only)
                        type = sha1_object_info(s->sha1, &s->size);
-                       if (use_size_cache && 0 < type)
-                               locate_size_cache(s->sha1, 0, s->size);
-               }
                else {
                        s->data = read_sha1_file(s->sha1, &type, &s->size);
                        s->should_free = 1;
@@ -1493,8 +1544,11 @@ void diff_free_filespec_data(struct diff_filespec *s)
                free(s->data);
        else if (s->should_munmap)
                munmap(s->data, s->size);
-       s->should_free = s->should_munmap = 0;
-       s->data = NULL;
+
+       if (s->should_free || s->should_munmap) {
+               s->should_free = s->should_munmap = 0;
+               s->data = NULL;
+       }
        free(s->cnt_data);
        s->cnt_data = NULL;
 }
@@ -1694,6 +1748,30 @@ static void run_external_diff(const char *pgm,
        }
 }
 
+static const char *external_diff_attr(const char *name)
+{
+       struct git_attr_check attr_diff_check;
+
+       setup_diff_attr_check(&attr_diff_check);
+       if (!git_checkattr(name, 1, &attr_diff_check)) {
+               const char *value = attr_diff_check.value;
+               if (!ATTR_TRUE(value) &&
+                   !ATTR_FALSE(value) &&
+                   !ATTR_UNSET(value)) {
+                       struct ll_diff_driver *drv;
+
+                       if (!user_diff_tail) {
+                               user_diff_tail = &user_diff;
+                               git_config(git_diff_ui_config);
+                       }
+                       for (drv = user_diff; drv; drv = drv->next)
+                               if (!strcmp(drv->name, value))
+                                       return drv->cmd;
+               }
+       }
+       return NULL;
+}
+
 static void run_diff_cmd(const char *pgm,
                         const char *name,
                         const char *other,
@@ -1703,6 +1781,14 @@ static void run_diff_cmd(const char *pgm,
                         struct diff_options *o,
                         int complete_rewrite)
 {
+       if (!o->allow_external)
+               pgm = NULL;
+       else {
+               const char *cmd = external_diff_attr(name);
+               if (cmd)
+                       pgm = cmd;
+       }
+
        if (pgm) {
                run_external_diff(pgm, name, other, one, two, xfrm_msg,
                                  complete_rewrite);
@@ -1799,8 +1885,8 @@ static void run_diff(struct diff_filepair *p, struct diff_options *o)
 
                if (o->binary) {
                        mmfile_t mf;
-                       if ((!fill_mmfile(&mf, one) && mmfile_is_binary(&mf)) ||
-                           (!fill_mmfile(&mf, two) && mmfile_is_binary(&mf)))
+                       if ((!fill_mmfile(&mf, one) && file_is_binary(one)) ||
+                           (!fill_mmfile(&mf, two) && file_is_binary(two)))
                                abbrev = 40;
                }
                len += snprintf(msg + len, sizeof(msg) - len,
@@ -1954,8 +2040,6 @@ int diff_setup_done(struct diff_options *options)
                         */
                        read_cache();
        }
-       if (options->setup & DIFF_SETUP_USE_SIZE_CACHE)
-               use_size_cache = 1;
        if (options->abbrev <= 0 || 40 < options->abbrev)
                options->abbrev = 40; /* full */
 
@@ -2695,7 +2779,7 @@ static int diff_get_patch_id(struct diff_options *options, unsigned char *sha1)
                        return error("unable to read files to diff");
 
                /* Maybe hash p->two? into the patch id? */
-               if (mmfile_is_binary(&mf2))
+               if (file_is_binary(p->two))
                        continue;
 
                len1 = remove_space(p->one->path, strlen(p->one->path));
diff --git a/diff.h b/diff.h
index a0d2ce13994c1a8751bf7b207671e95c5bc5db97..63738c1dd4c71cb1beacaffea40bf51377a137ea 100644 (file)
--- a/diff.h
+++ b/diff.h
@@ -59,6 +59,7 @@ struct diff_options {
                 color_diff_words:1,
                 has_changes:1,
                 quiet:1,
+                allow_external:1,
                 exit_with_status:1;
        int context;
        int break_opt;
index 286919e71428049eda1449ba71ac81667ebc3f7d..c4a77d71da5602b68c541c2487fcbecfdfff2944 100644 (file)
@@ -44,6 +44,7 @@ static unsigned int contains(struct diff_filespec *one,
                        }
                }
        }
+       diff_free_filespec_data(one);
        return cnt;
 }
 
index 79030412dbf883147f35678505ad147dc9c240a8..93c40d9e04f35897b1ac2ad578db0c0972aacbc1 100644 (file)
@@ -329,6 +329,7 @@ void diffcore_rename(struct diff_options *options)
                        m->dst = i;
                        m->score = estimate_similarity(one, two,
                                                       minimum_score);
+                       diff_free_filespec_data(one);
                }
                /* We do not need the text anymore */
                diff_free_filespec_data(two);
index 1ea80671e30500f95fc1b648ccc6d5143ac0ac52..7b9294eab2c1cb9f7cb03307c90203344d97e3f6 100644 (file)
@@ -1,8 +1,8 @@
 /*
  * Copyright (C) 2005 Junio C Hamano
  */
-#ifndef _DIFFCORE_H_
-#define _DIFFCORE_H_
+#ifndef DIFFCORE_H
+#define DIFFCORE_H
 
 /* This header file is internal between diff.c and its diff transformers
  * (e.g. diffcore-rename, diffcore-pickaxe).  Never include this header
diff --git a/dir.c b/dir.c
index 602282bd1b198a7a0cef0707c738b4eda0d3ccef..11fab7f4bf9b93be33795dfd5c1a3c55c8a8079d 100644 (file)
--- a/dir.c
+++ b/dir.c
@@ -7,6 +7,16 @@
  */
 #include "cache.h"
 #include "dir.h"
+#include "refs.h"
+
+struct path_simplify {
+       int len;
+       const char *path;
+};
+
+static int read_directory_recursive(struct dir_struct *dir,
+       const char *path, const char *base, int baselen,
+       int check_only, const struct path_simplify *simplify);
 
 int common_prefix(const char **pathspec)
 {
@@ -282,15 +292,136 @@ struct dir_entry *dir_add_name(struct dir_struct *dir, const char *pathname, int
        return ent;
 }
 
-static int dir_exists(const char *dirname, int len)
+enum exist_status {
+       index_nonexistent = 0,
+       index_directory,
+       index_gitdir,
+};
+
+/*
+ * The index sorts alphabetically by entry name, which
+ * means that a gitlink sorts as '\0' at the end, while
+ * a directory (which is defined not as an entry, but as
+ * the files it contains) will sort with the '/' at the
+ * end.
+ */
+static enum exist_status directory_exists_in_index(const char *dirname, int len)
 {
        int pos = cache_name_pos(dirname, len);
-       if (pos >= 0)
+       if (pos < 0)
+               pos = -pos-1;
+       while (pos < active_nr) {
+               struct cache_entry *ce = active_cache[pos++];
+               unsigned char endchar;
+
+               if (strncmp(ce->name, dirname, len))
+                       break;
+               endchar = ce->name[len];
+               if (endchar > '/')
+                       break;
+               if (endchar == '/')
+                       return index_directory;
+               if (!endchar && S_ISDIRLNK(ntohl(ce->ce_mode)))
+                       return index_gitdir;
+       }
+       return index_nonexistent;
+}
+
+/*
+ * When we find a directory when traversing the filesystem, we
+ * have three distinct cases:
+ *
+ *  - ignore it
+ *  - see it as a directory
+ *  - recurse into it
+ *
+ * and which one we choose depends on a combination of existing
+ * git index contents and the flags passed into the directory
+ * traversal routine.
+ *
+ * Case 1: If we *already* have entries in the index under that
+ * directory name, we always recurse into the directory to see
+ * all the files.
+ *
+ * Case 2: If we *already* have that directory name as a gitlink,
+ * we always continue to see it as a gitlink, regardless of whether
+ * there is an actual git directory there or not (it might not
+ * be checked out as a subproject!)
+ *
+ * Case 3: if we didn't have it in the index previously, we
+ * have a few sub-cases:
+ *
+ *  (a) if "show_other_directories" is true, we show it as
+ *      just a directory, unless "hide_empty_directories" is
+ *      also true and the directory is empty, in which case
+ *      we just ignore it entirely.
+ *  (b) if it looks like a git directory, and we don't have
+ *      'no_dirlinks' set we treat it as a gitlink, and show it
+ *      as a directory.
+ *  (c) otherwise, we recurse into it.
+ */
+enum directory_treatment {
+       show_directory,
+       ignore_directory,
+       recurse_into_directory,
+};
+
+static enum directory_treatment treat_directory(struct dir_struct *dir,
+       const char *dirname, int len,
+       const struct path_simplify *simplify)
+{
+       /* The "len-1" is to strip the final '/' */
+       switch (directory_exists_in_index(dirname, len-1)) {
+       case index_directory:
+               return recurse_into_directory;
+
+       case index_gitdir:
+               if (dir->show_other_directories)
+                       return ignore_directory;
+               return show_directory;
+
+       case index_nonexistent:
+               if (dir->show_other_directories)
+                       break;
+               if (!dir->no_dirlinks) {
+                       unsigned char sha1[20];
+                       if (resolve_gitlink_ref(dirname, "HEAD", sha1) == 0)
+                               return show_directory;
+               }
+               return recurse_into_directory;
+       }
+
+       /* This is the "show_other_directories" case */
+       if (!dir->hide_empty_directories)
+               return show_directory;
+       if (!read_directory_recursive(dir, dirname, dirname, len, 1, simplify))
+               return ignore_directory;
+       return show_directory;
+}
+
+/*
+ * This is an inexact early pruning of any recursive directory
+ * reading - if the path cannot possibly be in the pathspec,
+ * return true, and we'll skip it early.
+ */
+static int simplify_away(const char *path, int pathlen, const struct path_simplify *simplify)
+{
+       if (simplify) {
+               for (;;) {
+                       const char *match = simplify->path;
+                       int len = simplify->len;
+
+                       if (!match)
+                               break;
+                       if (len > pathlen)
+                               len = pathlen;
+                       if (!memcmp(path, match, len))
+                               return 0;
+                       simplify++;
+               }
                return 1;
-       pos = -pos-1;
-       if (pos >= active_nr) /* can't */
-               return 0;
-       return !strncmp(active_cache[pos]->name, dirname, len);
+       }
+       return 0;
 }
 
 /*
@@ -302,7 +433,7 @@ static int dir_exists(const char *dirname, int len)
  * Also, we ignore the name ".git" (even if it is not a directory).
  * That likely will not change.
  */
-static int read_directory_recursive(struct dir_struct *dir, const char *path, const char *base, int baselen, int check_only)
+static int read_directory_recursive(struct dir_struct *dir, const char *path, const char *base, int baselen, int check_only, const struct path_simplify *simplify)
 {
        DIR *fdir = opendir(path);
        int contents = 0;
@@ -317,6 +448,7 @@ static int read_directory_recursive(struct dir_struct *dir, const char *path, co
 
                while ((de = readdir(fdir)) != NULL) {
                        int len;
+                       int exclude;
 
                        if ((de->d_name[0] == '.') &&
                            (de->d_name[1] == 0 ||
@@ -324,8 +456,15 @@ static int read_directory_recursive(struct dir_struct *dir, const char *path, co
                             !strcmp(de->d_name + 1, "git")))
                                continue;
                        len = strlen(de->d_name);
+                       /* Ignore overly long pathnames! */
+                       if (len + baselen + 8 > sizeof(fullname))
+                               continue;
                        memcpy(fullname + baselen, de->d_name, len+1);
-                       if (excluded(dir, fullname) != dir->show_ignored) {
+                       if (simplify_away(fullname, baselen + len, simplify))
+                               continue;
+
+                       exclude = excluded(dir, fullname);
+                       if (exclude != dir->show_ignored) {
                                if (!dir->show_ignored || DTYPE(de) != DT_DIR) {
                                        continue;
                                }
@@ -346,19 +485,19 @@ static int read_directory_recursive(struct dir_struct *dir, const char *path, co
                        case DT_DIR:
                                memcpy(fullname + baselen + len, "/", 2);
                                len++;
-                               if (dir->show_other_directories &&
-                                   !dir_exists(fullname, baselen + len)) {
-                                       if (dir->hide_empty_directories &&
-                                           !read_directory_recursive(dir,
-                                                   fullname, fullname,
-                                                   baselen + len, 1))
+                               switch (treat_directory(dir, fullname, baselen + len, simplify)) {
+                               case show_directory:
+                                       if (exclude != dir->show_ignored)
                                                continue;
                                        break;
+                               case recurse_into_directory:
+                                       contents += read_directory_recursive(dir,
+                                               fullname, fullname, baselen + len, 0, simplify);
+                                       continue;
+                               case ignore_directory:
+                                       continue;
                                }
-
-                               contents += read_directory_recursive(dir,
-                                       fullname, fullname, baselen + len, 0);
-                               continue;
+                               break;
                        case DT_REG:
                        case DT_LNK:
                                break;
@@ -387,8 +526,61 @@ static int cmp_name(const void *p1, const void *p2)
                                  e2->name, e2->len);
 }
 
-int read_directory(struct dir_struct *dir, const char *path, const char *base, int baselen)
+/*
+ * Return the length of the "simple" part of a path match limiter.
+ */
+static int simple_length(const char *match)
+{
+       const char special[256] = {
+               [0] = 1, ['?'] = 1,
+               ['\\'] = 1, ['*'] = 1,
+               ['['] = 1
+       };
+       int len = -1;
+
+       for (;;) {
+               unsigned char c = *match++;
+               len++;
+               if (special[c])
+                       return len;
+       }
+}
+
+static struct path_simplify *create_simplify(const char **pathspec)
+{
+       int nr, alloc = 0;
+       struct path_simplify *simplify = NULL;
+
+       if (!pathspec)
+               return NULL;
+
+       for (nr = 0 ; ; nr++) {
+               const char *match;
+               if (nr >= alloc) {
+                       alloc = alloc_nr(alloc);
+                       simplify = xrealloc(simplify, alloc * sizeof(*simplify));
+               }
+               match = *pathspec++;
+               if (!match)
+                       break;
+               simplify[nr].path = match;
+               simplify[nr].len = simple_length(match);
+       }
+       simplify[nr].path = NULL;
+       simplify[nr].len = 0;
+       return simplify;
+}
+
+static void free_simplify(struct path_simplify *simplify)
 {
+       if (simplify)
+               free(simplify);
+}
+
+int read_directory(struct dir_struct *dir, const char *path, const char *base, int baselen, const char **pathspec)
+{
+       struct path_simplify *simplify = create_simplify(pathspec);
+
        /*
         * Make sure to do the per-directory exclude for all the
         * directories leading up to our base.
@@ -415,7 +607,8 @@ int read_directory(struct dir_struct *dir, const char *path, const char *base, i
                }
        }
 
-       read_directory_recursive(dir, path, base, baselen, 0);
+       read_directory_recursive(dir, path, base, baselen, 0, simplify);
+       free_simplify(simplify);
        qsort(dir->entries, dir->nr, sizeof(struct dir_entry *), cmp_name);
        return dir->nr;
 }
diff --git a/dir.h b/dir.h
index 7233d65bbd393f1d34d75538dd0e39e4a86383f2..817c674da1e017cffea9dddae672f9125aca8475 100644 (file)
--- a/dir.h
+++ b/dir.h
@@ -33,7 +33,8 @@ struct dir_struct {
        int nr, alloc;
        unsigned int show_ignored:1,
                     show_other_directories:1,
-                    hide_empty_directories:1;
+                    hide_empty_directories:1,
+                    no_dirlinks:1;
        struct dir_entry **entries;
 
        /* Exclude info */
@@ -48,7 +49,7 @@ extern int common_prefix(const char **pathspec);
 #define MATCHED_EXACTLY 3
 extern int match_pathspec(const char **pathspec, const char *name, int namelen, int prefix, char *seen);
 
-extern int read_directory(struct dir_struct *, const char *path, const char *base, int baselen);
+extern int read_directory(struct dir_struct *, const char *path, const char *base, int baselen, const char **pathspec);
 extern int push_exclude_per_directory(struct dir_struct *, const char *, int);
 extern void pop_exclude_per_directory(struct dir_struct *, int);
 
diff --git a/entry.c b/entry.c
index d72f811580ad10e792e38b40fe79bf4af3868846..82bf7259a7d1d34a92a9bf214bccd9771748c582 100644 (file)
--- a/entry.c
+++ b/entry.c
@@ -1,7 +1,7 @@
 #include "cache.h"
 #include "blob.h"
 
-static void create_directories(const char *path, struct checkout *state)
+static void create_directories(const char *path, const struct checkout *state)
 {
        int len = strlen(path);
        char *buf = xmalloc(len + 1);
@@ -33,7 +33,7 @@ static void remove_subtree(const char *path)
        char *name;
        
        if (!dir)
-               die("cannot opendir %s", path);
+               die("cannot opendir %s (%s)", path, strerror(errno));
        strcpy(pathbuf, path);
        name = pathbuf + strlen(path);
        *name++ = '/';
@@ -45,15 +45,15 @@ static void remove_subtree(const char *path)
                        continue;
                strcpy(name, de->d_name);
                if (lstat(pathbuf, &st))
-                       die("cannot lstat %s", pathbuf);
+                       die("cannot lstat %s (%s)", pathbuf, strerror(errno));
                if (S_ISDIR(st.st_mode))
                        remove_subtree(pathbuf);
                else if (unlink(pathbuf))
-                       die("cannot unlink %s", pathbuf);
+                       die("cannot unlink %s (%s)", pathbuf, strerror(errno));
        }
        closedir(dir);
        if (rmdir(path))
-               die("cannot rmdir %s", path);
+               die("cannot rmdir %s (%s)", path, strerror(errno));
 }
 
 static int create_file(const char *path, unsigned int mode)
@@ -62,26 +62,33 @@ static int create_file(const char *path, unsigned int mode)
        return open(path, O_WRONLY | O_CREAT | O_EXCL, mode);
 }
 
-static int write_entry(struct cache_entry *ce, char *path, struct checkout *state, int to_tempfile)
+static void *read_blob_entry(struct cache_entry *ce, const char *path, unsigned long *size)
 {
-       int fd;
-       void *new;
-       unsigned long size;
-       long wrote;
        enum object_type type;
+       void *new = read_sha1_file(ce->sha1, &type, size);
 
-       new = read_sha1_file(ce->sha1, &type, &size);
-       if (!new || type != OBJ_BLOB) {
-               if (new)
-                       free(new);
-               return error("git-checkout-index: unable to read sha1 file of %s (%s)",
-                       path, sha1_to_hex(ce->sha1));
+       if (new) {
+               if (type == OBJ_BLOB)
+                       return new;
+               free(new);
        }
+       return NULL;
+}
+
+static int write_entry(struct cache_entry *ce, char *path, const struct checkout *state, int to_tempfile)
+{
+       int fd;
+       long wrote;
+
        switch (ntohl(ce->ce_mode) & S_IFMT) {
-               char *buf;
-               unsigned long nsize;
+               char *buf, *new;
+               unsigned long size;
 
        case S_IFREG:
+               new = read_blob_entry(ce, path, &size);
+               if (!new)
+                       return error("git-checkout-index: unable to read sha1 file of %s (%s)",
+                               path, sha1_to_hex(ce->sha1));
                if (to_tempfile) {
                        strcpy(path, ".merge_file_XXXXXX");
                        fd = mkstemp(path);
@@ -96,12 +103,10 @@ static int write_entry(struct cache_entry *ce, char *path, struct checkout *stat
                /*
                 * Convert from git internal format to working tree format
                 */
-               buf = new;
-               nsize = size;
-               if (convert_to_working_tree(ce->name, &buf, &nsize)) {
+               buf = convert_to_working_tree(ce->name, new, &size);
+               if (buf) {
                        free(new);
                        new = buf;
-                       size = nsize;
                }
 
                wrote = write_in_full(fd, new, size);
@@ -111,6 +116,10 @@ static int write_entry(struct cache_entry *ce, char *path, struct checkout *stat
                        return error("git-checkout-index: unable to write file %s", path);
                break;
        case S_IFLNK:
+               new = read_blob_entry(ce, path, &size);
+               if (!new)
+                       return error("git-checkout-index: unable to read sha1 file of %s (%s)",
+                               path, sha1_to_hex(ce->sha1));
                if (to_tempfile || !has_symlinks) {
                        if (to_tempfile) {
                                strcpy(path, ".merge_link_XXXXXX");
@@ -136,8 +145,13 @@ static int write_entry(struct cache_entry *ce, char *path, struct checkout *stat
                                                 "symlink %s (%s)", path, strerror(errno));
                }
                break;
+       case S_IFDIRLNK:
+               if (to_tempfile)
+                       return error("git-checkout-index: cannot create temporary subproject %s", path);
+               if (mkdir(path, 0777) < 0)
+                       return error("git-checkout-index: cannot create subproject directory %s", path);
+               break;
        default:
-               free(new);
                return error("git-checkout-index: unknown file mode for %s", path);
        }
 
@@ -149,7 +163,7 @@ static int write_entry(struct cache_entry *ce, char *path, struct checkout *stat
        return 0;
 }
 
-int checkout_entry(struct cache_entry *ce, struct checkout *state, char *topath)
+int checkout_entry(struct cache_entry *ce, const struct checkout *state, char *topath)
 {
        static char path[PATH_MAX + 1];
        struct stat st;
@@ -179,6 +193,9 @@ int checkout_entry(struct cache_entry *ce, struct checkout *state, char *topath)
                 */
                unlink(path);
                if (S_ISDIR(st.st_mode)) {
+                       /* If it is a gitlink, leave it alone! */
+                       if (S_ISDIRLNK(ntohl(ce->ce_mode)))
+                               return 0;
                        if (!state->force)
                                return error("%s is a directory", path);
                        remove_subtree(path);
index 989621ff4ecea463c340cb05eb035be0f7cb94a5..849a8395a0c03ba2976fe4802eee8150fdec5816 100644 (file)
@@ -1,5 +1,5 @@
-#ifndef __GIT_EXEC_CMD_H_
-#define __GIT_EXEC_CMD_H_
+#ifndef GIT_EXEC_CMD_H
+#define GIT_EXEC_CMD_H
 
 extern void git_set_exec_path(const char *exec_path);
 extern const char* git_exec_path(void);
@@ -7,4 +7,4 @@ extern int execv_git_cmd(const char **argv); /* NULL terminated */
 extern int execl_git_cmd(const char *cmd, ...);
 
 
-#endif /* __GIT_EXEC_CMD_H_ */
+#endif /* GIT_EXEC_CMD_H */
index 6c43a0d37f781294a8844ea52c9ce9728396f456..3a2d5ed8e667af06f97d2559b3cffb8a01f94565 100644 (file)
@@ -651,42 +651,6 @@ static void start_packfile(void)
        all_packs[pack_id] = p;
 }
 
-static void fixup_header_footer(void)
-{
-       static const int buf_sz = 128 * 1024;
-       int pack_fd = pack_data->pack_fd;
-       SHA_CTX c;
-       struct pack_header hdr;
-       char *buf;
-
-       if (lseek(pack_fd, 0, SEEK_SET) != 0)
-               die("Failed seeking to start: %s", strerror(errno));
-       if (read_in_full(pack_fd, &hdr, sizeof(hdr)) != sizeof(hdr))
-               die("Unable to reread header of %s", pack_data->pack_name);
-       if (lseek(pack_fd, 0, SEEK_SET) != 0)
-               die("Failed seeking to start: %s", strerror(errno));
-       hdr.hdr_entries = htonl(object_count);
-       write_or_die(pack_fd, &hdr, sizeof(hdr));
-
-       SHA1_Init(&c);
-       SHA1_Update(&c, &hdr, sizeof(hdr));
-
-       buf = xmalloc(buf_sz);
-       for (;;) {
-               size_t n = xread(pack_fd, buf, buf_sz);
-               if (!n)
-                       break;
-               if (n < 0)
-                       die("Failed to checksum %s", pack_data->pack_name);
-               SHA1_Update(&c, buf, n);
-       }
-       free(buf);
-
-       SHA1_Final(pack_data->sha1, &c);
-       write_or_die(pack_fd, pack_data->sha1, sizeof(pack_data->sha1));
-       close(pack_fd);
-}
-
 static int oecmp (const void *a_, const void *b_)
 {
        struct object_entry *a = *((struct object_entry**)a_);
@@ -802,7 +766,9 @@ static void end_packfile(void)
                struct branch *b;
                struct tag *t;
 
-               fixup_header_footer();
+               fixup_pack_header_footer(pack_data->pack_fd, pack_data->sha1,
+                                   pack_data->pack_name, object_count);
+               close(pack_data->pack_fd);
                idx_name = keep_pack(create_index());
 
                /* Register the packfile with core git's machinary. */
@@ -904,6 +870,12 @@ static int store_object(
        if (e->offset) {
                duplicate_count_by_type[type]++;
                return 1;
+       } else if (find_sha1_pack(sha1, packed_git)) {
+               e->type = type;
+               e->pack_id = MAX_PACK_ID;
+               e->offset = 1; /* just not zero! */
+               duplicate_count_by_type[type]++;
+               return 1;
        }
 
        if (last && last->data && last->depth < max_depth) {
@@ -2023,6 +1995,7 @@ static void import_marks(const char *input_file)
                        e = insert_object(sha1);
                        e->type = type;
                        e->pack_id = MAX_PACK_ID;
+                       e->offset = 1; /* just not zero! */
                }
                insert_mark(mark, e);
        }
@@ -2088,6 +2061,7 @@ int main(int argc, const char **argv)
        if (i != argc)
                usage(fast_import_usage);
 
+       prepare_packed_git();
        start_packfile();
        for (;;) {
                read_next_command();
index 11313a7949909f61f47ccc061cfc6390b225c630..1cd456173dc386528cbbbca35e327badf97a25e2 100755 (executable)
@@ -1,15 +1,24 @@
 #!/bin/sh
 
 USAGE='[start|bad|good|next|reset|visualize|replay|log|run]'
-LONG_USAGE='git bisect start [<pathspec>]      reset bisect state and start bisection.
-git bisect bad [<rev>]         mark <rev> a known-bad revision.
-git bisect good [<rev>...]     mark <rev>... known-good revisions.
-git bisect next                        find next bisection to test and check it out.
-git bisect reset [<branch>]    finish bisection search and go back to branch.
-git bisect visualize            show bisect status in gitk.
-git bisect replay <logfile>    replay bisection log.
-git bisect log                 show bisect log.
-git bisect run <cmd>...        use <cmd>... to automatically bisect.'
+LONG_USAGE='git bisect start [<bad> [<good>...]] [--] [<pathspec>...]
+        reset bisect state and start bisection.
+git bisect bad [<rev>]
+        mark <rev> a known-bad revision.
+git bisect good [<rev>...]
+        mark <rev>... known-good revisions.
+git bisect next
+        find next bisection to test and check it out.
+git bisect reset [<branch>]
+        finish bisection search and go back to branch.
+git bisect visualize
+        show bisect status in gitk.
+git bisect replay <logfile>
+        replay bisection log.
+git bisect log
+        show bisect log.
+git bisect run <cmd>...
+        use <cmd>... to automatically bisect.'
 
 . git-sh-setup
 require_work_tree
@@ -70,14 +79,45 @@ bisect_start() {
        #
        # Get rid of any old bisect state
        #
-       rm -f "$GIT_DIR/refs/heads/bisect"
-       rm -rf "$GIT_DIR/refs/bisect/"
+       bisect_clean_state
        mkdir "$GIT_DIR/refs/bisect"
-       {
-           printf "git-bisect start"
-           sq "$@"
-       } >"$GIT_DIR/BISECT_LOG"
+
+       #
+       # Check for one bad and then some good revisions.
+       #
+       has_double_dash=0
+       for arg; do
+           case "$arg" in --) has_double_dash=1; break ;; esac
+       done
+       orig_args=$(sq "$@")
+       bad_seen=0
+       while [ $# -gt 0 ]; do
+           arg="$1"
+           case "$arg" in
+           --)
+               shift
+               break
+               ;;
+           *)
+               rev=$(git-rev-parse --verify "$arg^{commit}" 2>/dev/null) || {
+                   test $has_double_dash -eq 1 &&
+                       die "'$arg' does not appear to be a valid revision"
+                   break
+               }
+               if [ $bad_seen -eq 0 ]; then
+                   bad_seen=1
+                   bisect_write_bad "$rev"
+               else
+                   bisect_write_good "$rev"
+               fi
+               shift
+               ;;
+           esac
+        done
+
        sq "$@" >"$GIT_DIR/BISECT_NAMES"
+       echo "git-bisect start$orig_args" >>"$GIT_DIR/BISECT_LOG"
+       bisect_auto_next
 }
 
 bisect_bad() {
@@ -90,12 +130,17 @@ bisect_bad() {
        *)
                usage ;;
        esac || exit
-       echo "$rev" >"$GIT_DIR/refs/bisect/bad"
-       echo "# bad: "$(git-show-branch $rev) >>"$GIT_DIR/BISECT_LOG"
+       bisect_write_bad "$rev"
        echo "git-bisect bad $rev" >>"$GIT_DIR/BISECT_LOG"
        bisect_auto_next
 }
 
+bisect_write_bad() {
+       rev="$1"
+       echo "$rev" >"$GIT_DIR/refs/bisect/bad"
+       echo "# bad: "$(git-show-branch $rev) >>"$GIT_DIR/BISECT_LOG"
+}
+
 bisect_good() {
        bisect_autostart
         case "$#" in
@@ -106,35 +151,54 @@ bisect_good() {
        for rev in $revs
        do
                rev=$(git-rev-parse --verify "$rev^{commit}") || exit
-               echo "$rev" >"$GIT_DIR/refs/bisect/good-$rev"
-               echo "# good: "$(git-show-branch $rev) >>"$GIT_DIR/BISECT_LOG"
+               bisect_write_good "$rev"
                echo "git-bisect good $rev" >>"$GIT_DIR/BISECT_LOG"
+
        done
        bisect_auto_next
 }
 
+bisect_write_good() {
+       rev="$1"
+       echo "$rev" >"$GIT_DIR/refs/bisect/good-$rev"
+       echo "# good: "$(git-show-branch $rev) >>"$GIT_DIR/BISECT_LOG"
+}
+
 bisect_next_check() {
-       next_ok=no
-        test -f "$GIT_DIR/refs/bisect/bad" &&
-       case "$(cd "$GIT_DIR" && echo refs/bisect/good-*)" in
-       refs/bisect/good-\*) ;;
-       *) next_ok=yes ;;
-       esac
-       case "$next_ok,$1" in
-       no,) false ;;
-       no,fail)
-           THEN=''
-           test -d "$GIT_DIR/refs/bisect" || {
-               echo >&2 'You need to start by "git bisect start".'
-               THEN='then '
-           }
-           echo >&2 'You '$THEN'need to give me at least one good' \
-               'and one bad revisions.'
-           echo >&2 '(You can use "git bisect bad" and' \
-               '"git bisect good" for that.)'
-           exit 1 ;;
+       missing_good= missing_bad=
+       git show-ref -q --verify refs/bisect/bad || missing_bad=t
+       test -n "$(git for-each-ref "refs/bisect/good-*")" || missing_good=t
+
+       case "$missing_good,$missing_bad,$1" in
+       ,,*)
+               : have both good and bad - ok
+               ;;
+       *,)
+               # do not have both but not asked to fail - just report.
+               false
+               ;;
+       t,,good)
+               # have bad but not good.  we could bisect although
+               # this is less optimum.
+               echo >&2 'Warning: bisecting only with a bad commit.'
+               if test -t 0
+               then
+                       printf >&2 'Are you sure [Y/n]? '
+                       case "$(read yesno)" in [Nn]*) exit 1 ;; esac
+               fi
+               : bisect without good...
+               ;;
        *)
-           true ;;
+               THEN=''
+               test -d "$GIT_DIR/refs/bisect" || {
+                       echo >&2 'You need to start by "git bisect start".'
+                       THEN='then '
+               }
+               echo >&2 'You '$THEN'need to give me at least one good' \
+                       'and one bad revisions.'
+               echo >&2 '(You can use "git bisect bad" and' \
+                       '"git bisect good" for that.)'
+               exit 1 ;;
        esac
 }
 
@@ -145,27 +209,32 @@ bisect_auto_next() {
 bisect_next() {
         case "$#" in 0) ;; *) usage ;; esac
        bisect_autostart
-       bisect_next_check fail
+       bisect_next_check good
+
        bad=$(git-rev-parse --verify refs/bisect/bad) &&
-       good=$(git-rev-parse --sq --revs-only --not \
-               $(cd "$GIT_DIR" && ls refs/bisect/good-*)) &&
-       rev=$(eval "git-rev-list --bisect $good $bad -- $(cat "$GIT_DIR/BISECT_NAMES")") || exit
-       if [ -z "$rev" ]; then
-           echo "$bad was both good and bad"
-           exit 1
+       good=$(git for-each-ref --format='^%(objectname)' \
+               "refs/bisect/good-*" | tr '[\012]' ' ') &&
+       eval="git-rev-list --bisect-vars $good $bad --" &&
+       eval="$eval $(cat "$GIT_DIR/BISECT_NAMES")" &&
+       eval=$(eval "$eval") &&
+       eval "$eval" || exit
+
+       if [ -z "$bisect_rev" ]; then
+               echo "$bad was both good and bad"
+               exit 1
        fi
-       if [ "$rev" = "$bad" ]; then
-           echo "$rev is first bad commit"
-           git-diff-tree --pretty $rev
-           exit 0
+       if [ "$bisect_rev" = "$bad" ]; then
+               echo "$bisect_rev is first bad commit"
+               git-diff-tree --pretty $bisect_rev
+               exit 0
        fi
-       nr=$(eval "git-rev-list $rev $good -- $(cat $GIT_DIR/BISECT_NAMES)" | wc -l) || exit
-       echo "Bisecting: $nr revisions left to test after this"
-       echo "$rev" > "$GIT_DIR/refs/heads/new-bisect"
+
+       echo "Bisecting: $bisect_nr revisions left to test after this"
+       echo "$bisect_rev" >"$GIT_DIR/refs/heads/new-bisect"
        git checkout -q new-bisect || exit
        mv "$GIT_DIR/refs/heads/new-bisect" "$GIT_DIR/refs/heads/bisect" &&
        GIT_DIR="$GIT_DIR" git-symbolic-ref HEAD refs/heads/bisect
-       git-show-branch "$rev"
+       git-show-branch "$bisect_rev"
 }
 
 bisect_visualize() {
@@ -190,14 +259,19 @@ bisect_reset() {
            usage ;;
        esac
        if git checkout "$branch"; then
-               rm -fr "$GIT_DIR/refs/bisect"
-               rm -f "$GIT_DIR/refs/heads/bisect" "$GIT_DIR/head-name"
-               rm -f "$GIT_DIR/BISECT_LOG"
-               rm -f "$GIT_DIR/BISECT_NAMES"
-               rm -f "$GIT_DIR/BISECT_RUN"
+               rm -f "$GIT_DIR/head-name"
+               bisect_clean_state
        fi
 }
 
+bisect_clean_state() {
+       rm -fr "$GIT_DIR/refs/bisect"
+       rm -f "$GIT_DIR/refs/heads/bisect"
+       rm -f "$GIT_DIR/BISECT_LOG"
+       rm -f "$GIT_DIR/BISECT_NAMES"
+       rm -f "$GIT_DIR/BISECT_RUN"
+}
+
 bisect_replay () {
        test -r "$1" || {
                echo >&2 "cannot read $1 for replaying"
index deb0a9a3c733ed889158d05b7cae4d174917553d..ed7c2c5f6aab338c844329e3ae3d9c7ce003680f 100755 (executable)
@@ -17,6 +17,7 @@ newbranch=
 newbranch_log=
 merge=
 quiet=
+v=-v
 LF='
 '
 while [ "$#" != "0" ]; do
@@ -47,6 +48,7 @@ while [ "$#" != "0" ]; do
                ;;
        "-q")
                quiet=1
+               v=
                ;;
        --)
                break
@@ -197,7 +199,7 @@ fi
 
 if [ "$force" ]
 then
-    git-read-tree --reset -u $new
+    git-read-tree $v --reset -u $new
 else
     git-update-index --refresh >/dev/null
     merge_error=$(git-read-tree -m -u --exclude-per-directory=.gitignore $old $new 2>&1) || (
@@ -210,7 +212,7 @@ else
        # Match the index to the working tree, and do a three-way.
        git diff-files --name-only | git update-index --remove --stdin &&
        work=`git write-tree` &&
-       git read-tree --reset -u $new || exit
+       git read-tree $v --reset -u $new || exit
 
        eval GITHEAD_$new='${new_name:-${branch:-$new}}' &&
        eval GITHEAD_$work=local &&
@@ -221,7 +223,7 @@ else
        # this is not a real merge before committing, but just carrying
        # the working tree changes along.
        unmerged=`git ls-files -u`
-       git read-tree --reset $new
+       git read-tree $v --reset $new
        case "$unmerged" in
        '')     ;;
        *)
index db177a7886b6407b4c4ad7b778a1ae99471355ac..299309d97169e6d9a52a8175dd0b8f6d54c5b369 100755 (executable)
@@ -3,9 +3,10 @@
 # Copyright (c) 2005-2006 Pavel Roskin
 #
 
-USAGE="[-d] [-n] [-q] [-x | -X] [--] <paths>..."
+USAGE="[-d] [-f] [-n] [-q] [-x | -X] [--] <paths>..."
 LONG_USAGE='Clean untracked files from the working directory
        -d      remove directories as well
+       -f      override clean.requireForce and clean anyway
        -n      don'\''t remove anything, just show what would be done
        -q      be quiet, only report errors
        -x      remove ignored files as well
@@ -19,6 +20,7 @@ require_work_tree
 ignored=
 ignoredonly=
 cleandir=
+disabled="`git-config --bool clean.requireForce`"
 rmf="rm -f --"
 rmrf="rm -rf --"
 rm_refuse="echo Not removing"
@@ -30,7 +32,11 @@ do
        -d)
                cleandir=1
                ;;
+       -f)
+               disabled=
+               ;;
        -n)
+               disabled=
                rmf="echo Would remove"
                rmrf="echo Would remove"
                rm_refuse="echo Would not remove"
@@ -58,6 +64,11 @@ do
        shift
 done
 
+if [ "$disabled" = true ]; then
+       echo "clean.requireForce set and -n or -f not given; refusing to clean"
+       exit 1
+fi
+
 case "$ignored,$ignoredonly" in
        1,1) usage;;
 esac
index 292cf967e3cbc77e7b49eb7252a756194e917418..f28fc242241d3f0f5c88b287da0c59417667a013 100755 (executable)
@@ -370,8 +370,8 @@ t,)
                # the same way.
                if test -z "$initial_commit"
                then
-                       cp "$THIS_INDEX" "$TMP_INDEX"
-                       GIT_INDEX_FILE="$TMP_INDEX" git-read-tree -i -m HEAD
+                       GIT_INDEX_FILE="$THIS_INDEX" \
+                       git-read-tree --index-output="$TMP_INDEX" -i -m HEAD
                else
                        rm -f "$TMP_INDEX"
                fi || exit
@@ -649,8 +649,9 @@ then
        fi
        if test -z "$quiet"
        then
+               commit=`git-diff-tree --always --shortstat --pretty="format:%h: %s"\
+                      --summary --root HEAD --`
                echo "Created${initial_commit:+ initial} commit $commit"
-               git-diff-tree --shortstat --summary --root --no-commit-id HEAD --
        fi
 fi
 
index 7ed8b88b1f3cef2562693bbf6d68e2e4cef9465b..6bd8987b2774774fbbd3747a2b571b66ad78727b 100644 (file)
 
 #define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0]))
 
+#ifdef __GNUC__
+#define TYPEOF(x) (__typeof__(x))
+#else
+#define TYPEOF(x)
+#endif
+
+#define MSB(x, bits) ((x) & TYPEOF(x)(~0ULL << (sizeof(x) * 8 - (bits))))
+
 /* Approximation of the length of the decimal representation of this type. */
 #define decimal_length(x)      ((int)(sizeof(x) * 2.56 + 0.5) + 1)
 
index 6ed471918df25006314f3f2c6177bdd018757068..d6ae99b8c00c278f6e4b0190dfb892b857f27103 100755 (executable)
@@ -160,36 +160,51 @@ foreach my $p (@afiles) {
     }
 }
 
+# ... check dirs,
 foreach my $d (@dirs) {
     if (-e $d) {
        $dirty = 1;
        warn "$d exists and is not a directory!\n";
     }
 }
-foreach my $f (@afiles) {
-    # This should return only one value
-    if ($f =~ m,(.*)/[^/]*$,) {
-       my $p = $1;
-       next if (grep { $_ eq $p } @dirs);
+
+# ... query status of all files that we have a directory for and parse output of 'cvs status' to %cvsstat.
+my @canstatusfiles;
+foreach my $f (@files) {
+    my $path = dirname $f;
+    next if (grep { $_ eq $path } @dirs);
+    push @canstatusfiles, $f;
+}
+
+my %cvsstat;
+if (@canstatusfiles) {
+    my @cvsoutput;
+    @cvsoutput= safe_pipe_capture(@cvs, 'status', @canstatusfiles);
+    my $matchcount = 0;
+    foreach my $l (@cvsoutput) {
+        chomp $l;
+        if ( $l =~ /^File:/ and  $l =~ /Status: (.*)$/ ) {
+            $cvsstat{$canstatusfiles[$matchcount]} = $1;
+            $matchcount++;
+        }
     }
-    my @status = grep(m/^File/,  safe_pipe_capture(@cvs, '-q', 'status' ,$f));
-    if (@status > 1) { warn 'Strange! cvs status returned more than one line?'};
-    if (-d dirname $f and $status[0] !~ m/Status: Unknown$/
-       and $status[0] !~ m/^File: no file /) {
+}
+
+# ... validate new files,
+foreach my $f (@afiles) {
+    if (defined ($cvsstat{$f}) and $cvsstat{$f} ne "Unknown") {
        $dirty = 1;
        warn "File $f is already known in your CVS checkout -- perhaps it has been added by another user. Or this may indicate that it exists on a different branch. If this is the case, use -f to force the merge.\n";
-       warn "Status was: $status[0]\n";
+       warn "Status was: $cvsstat{$f}\n";
     }
 }
-
+# ... validate known files.
 foreach my $f (@files) {
     next if grep { $_ eq $f } @afiles;
     # TODO:we need to handle removed in cvs
-    my @status = grep(m/^File/,  safe_pipe_capture(@cvs, '-q', 'status' ,$f));
-    if (@status > 1) { warn 'Strange! cvs status returned more than one line?'};
-    unless ($status[0] =~ m/Status: Up-to-date$/) {
+    unless (defined ($cvsstat{$f}) and $cvsstat{$f} eq "Up-to-date") {
        $dirty = 1;
-       warn "File $f not up to date in your CVS checkout!\n";
+       warn "File $f not up to date but has status '$cvsstat{$f}' in your CVS checkout!\n";
     }
 }
 if ($dirty) {
index 3501c1bc3a7fb1482ef3eec10c9fc91bf4d19e4d..ac88625fc2733b7de9f73bcdfc6bc466de44ac4a 100755 (executable)
@@ -91,7 +91,9 @@ $log->debug("Temporary directory is '$TEMP_DIR'");
 # if we are called with a pserver argument,
 # deal with the authentication cat before entering the
 # main loop
+$state->{method} = 'ext';
 if (@ARGV && $ARGV[0] eq 'pserver') {
+    $state->{method} = 'pserver';
     my $line = <STDIN>; chomp $line;
     unless( $line eq 'BEGIN AUTH REQUEST') {
        die "E Do not understand $line - expecting BEGIN AUTH REQUEST\n";
@@ -103,6 +105,7 @@ if (@ARGV && $ARGV[0] eq 'pserver') {
     unless ($line eq 'anonymous') {
        print "E Only anonymous user allowed via pserver\n";
        print "I HATE YOU\n";
+       exit;
     }
     $line = <STDIN>; chomp $line;    # validate the password?
     $line = <STDIN>; chomp $line;
@@ -181,11 +184,18 @@ sub req_Root
     }
     foreach my $line ( @gitvars )
     {
-        next unless ( $line =~ /^(.*?)\.(.*?)=(.*)$/ );
-        $cfg->{$1}{$2} = $3;
+        next unless ( $line =~ /^(gitcvs)\.(?:(ext|pserver)\.)?([\w-]+)=(.*)$/ );
+        unless ($2) {
+            $cfg->{$1}{$3} = $4;
+        } else {
+            $cfg->{$1}{$2}{$3} = $4;
+        }
     }
 
-    unless ( defined ( $cfg->{gitcvs}{enabled} ) and $cfg->{gitcvs}{enabled} =~ /^\s*(1|true|yes)\s*$/i )
+    unless ( ($cfg->{gitcvs}{$state->{method}}{enabled}
+             and $cfg->{gitcvs}{$state->{method}}{enabled} =~ /^\s*(1|true|yes)\s*$/i)
+            or ($cfg->{gitcvs}{enabled}
+             and $cfg->{gitcvs}{enabled} =~ /^\s*(1|true|yes)\s*$/i) )
     {
         print "E GITCVS emulation needs to be enabled on this repo\n";
         print "E the repo config file needs a [gitcvs] section added, and the parameter 'enabled' set to 1\n";
@@ -194,9 +204,10 @@ sub req_Root
         return 0;
     }
 
-    if ( defined ( $cfg->{gitcvs}{logfile} ) )
+    my $logfile = $cfg->{gitcvs}{$state->{method}}{logfile} || $cfg->{gitcvs}{logfile};
+    if ( $logfile )
     {
-        $log->setfile($cfg->{gitcvs}{logfile});
+        $log->setfile($logfile);
     } else {
         $log->nofile();
     }
@@ -350,12 +361,52 @@ sub req_add
 
     argsplit("add");
 
+    my $updater = GITCVS::updater->new($state->{CVSROOT}, $state->{module}, $log);
+    $updater->update();
+
+    argsfromdir($updater);
+
     my $addcount = 0;
 
     foreach my $filename ( @{$state->{args}} )
     {
         $filename = filecleanup($filename);
 
+        my $meta = $updater->getmeta($filename);
+        my $wrev = revparse($filename);
+
+        if ($wrev && $meta && ($wrev < 0))
+        {
+            # previously removed file, add back
+            $log->info("added file $filename was previously removed, send 1.$meta->{revision}");
+
+            print "MT +updated\n";
+            print "MT text U \n";
+            print "MT fname $filename\n";
+            print "MT newline\n";
+            print "MT -updated\n";
+
+            unless ( $state->{globaloptions}{-n} )
+            {
+                my ( $filepart, $dirpart ) = filenamesplit($filename,1);
+
+                print "Created $dirpart\n";
+                print $state->{CVSROOT} . "/$state->{module}/$filename\n";
+
+                # this is an "entries" line
+                my $kopts = kopts_from_path($filepart);
+                $log->debug("/$filepart/1.$meta->{revision}//$kopts/");
+                print "/$filepart/1.$meta->{revision}//$kopts/\n";
+                # permissions
+                $log->debug("SEND : u=$meta->{mode},g=$meta->{mode},o=$meta->{mode}");
+                print "u=$meta->{mode},g=$meta->{mode},o=$meta->{mode}\n";
+                # transmit file
+                transmitfile($meta->{filehash});
+            }
+
+            next;
+        }
+
         unless ( defined ( $state->{entries}{$filename}{modified_filename} ) )
         {
             print "E cvs add: nothing known about `$filename'\n";
@@ -1027,7 +1078,7 @@ sub req_ci
 
     $log->info("req_ci : " . ( defined($data) ? $data : "[NULL]" ));
 
-    if ( @ARGV && $ARGV[0] eq 'pserver')
+    if ( $state->{method} eq 'pserver')
     {
         print "error 1 pserver access cannot commit\n";
         exit;
@@ -2132,25 +2183,40 @@ sub new
 
     bless $self, $class;
 
-    $self->{dbdir} = $config . "/";
-    die "Database dir '$self->{dbdir}' isn't a directory" unless ( defined($self->{dbdir}) and -d $self->{dbdir} );
-
     $self->{module} = $module;
-    $self->{file} = $self->{dbdir} . "/gitcvs.$module.sqlite";
-
     $self->{git_path} = $config . "/";
 
     $self->{log} = $log;
 
     die "Git repo '$self->{git_path}' doesn't exist" unless ( -d $self->{git_path} );
 
-    $self->{dbh} = DBI->connect("dbi:SQLite:dbname=" . $self->{file},"","");
+    $self->{dbdriver} = $cfg->{gitcvs}{$state->{method}}{dbdriver} ||
+        $cfg->{gitcvs}{dbdriver} || "SQLite";
+    $self->{dbname} = $cfg->{gitcvs}{$state->{method}}{dbname} ||
+        $cfg->{gitcvs}{dbname} || "%Ggitcvs.%m.sqlite";
+    $self->{dbuser} = $cfg->{gitcvs}{$state->{method}}{dbuser} ||
+        $cfg->{gitcvs}{dbuser} || "";
+    $self->{dbpass} = $cfg->{gitcvs}{$state->{method}}{dbpass} ||
+        $cfg->{gitcvs}{dbpass} || "";
+    my %mapping = ( m => $module,
+                    a => $state->{method},
+                    u => getlogin || getpwuid($<) || $<,
+                    G => $self->{git_path},
+                    g => mangle_dirname($self->{git_path}),
+                    );
+    $self->{dbname} =~ s/%([mauGg])/$mapping{$1}/eg;
+    $self->{dbuser} =~ s/%([mauGg])/$mapping{$1}/eg;
+
+    die "Invalid char ':' in dbdriver" if $self->{dbdriver} =~ /:/;
+    die "Invalid char ';' in dbname" if $self->{dbname} =~ /;/;
+    $self->{dbh} = DBI->connect("dbi:$self->{dbdriver}:dbname=$self->{dbname}",
+                                $self->{dbuser},
+                                $self->{dbpass});
+    die "Error connecting to database\n" unless defined $self->{dbh};
 
     $self->{tables} = {};
-    foreach my $table ( $self->{dbh}->tables )
+    foreach my $table ( keys %{$self->{dbh}->table_info(undef,undef,undef,'TABLE')->fetchall_hashref('TABLE_NAME')} )
     {
-        $table =~ s/^"//;
-        $table =~ s/"$//;
         $self->{tables}{$table} = 1;
     }
 
@@ -2848,5 +2914,19 @@ sub safe_pipe_capture {
     return wantarray ? @output : join('',@output);
 }
 
+=head2 mangle_dirname
+
+create a string from a directory name that is suitable to use as
+part of a filename, mainly by converting all chars except \w.- to _
+
+=cut
+sub mangle_dirname {
+    my $dirname = shift;
+    return unless defined $dirname;
+
+    $dirname =~ s/[^\w.-]/_/g;
+
+    return $dirname;
+}
 
 1;
index fd70696b7479ad08eedbb44b75654f07247cc37b..0e05cf1195737d2c7afc4b9447d7b4105908bf77 100755 (executable)
@@ -26,6 +26,7 @@ keep=
 shallow_depth=
 no_progress=
 test -t 1 || no_progress=--no-progress
+quiet=
 while case "$#" in 0) break ;; esac
 do
        case "$1" in
@@ -56,6 +57,9 @@ do
        --update-head-o|--update-head-ok)
                update_head_ok=t
                ;;
+       -q|--q|--qu|--qui|--quie|--quiet)
+               quiet=--quiet
+               ;;
        -v|--verbose)
                verbose=Yes
                ;;
@@ -173,9 +177,34 @@ fetch_all_at_once () {
            git-bundle unbundle "$remote" $rref ||
            echo failed "$remote"
        else
-         git-fetch-pack --thin $exec $keep $shallow_depth $no_progress \
-               "$remote" $rref ||
-         echo failed "$remote"
+               if      test -d "$remote" &&
+
+                       # The remote might be our alternate.  With
+                       # this optimization we will bypass fetch-pack
+                       # altogether, which means we cannot be doing
+                       # the shallow stuff at all.
+                       test ! -f "$GIT_DIR/shallow" &&
+                       test -z "$shallow_depth" &&
+
+                       # See if all of what we are going to fetch are
+                       # connected to our repository's tips, in which
+                       # case we do not have to do any fetch.
+                       theirs=$(echo "$ls_remote_result" | \
+                               git-fetch--tool -s pick-rref "$rref" "-") &&
+
+                       # This will barf when $theirs reach an object that
+                       # we do not have in our repository.  Otherwise,
+                       # we already have everything the fetch would bring in.
+                       git-rev-list --objects $theirs --not --all \
+                               >/dev/null 2>/dev/null
+               then
+                       echo "$ls_remote_result" | \
+                               git-fetch--tool pick-rref "$rref" "-"
+               else
+                       git-fetch-pack --thin $exec $keep $shallow_depth \
+                               $quiet $no_progress "$remote" $rref ||
+                       echo failed "$remote"
+               fi
        fi
       ) |
       (
@@ -235,20 +264,13 @@ fetch_per_ref () {
          fi
 
          # Find $remote_name from ls-remote output.
-         head=$(
-               IFS='   '
-               echo "$ls_remote_result" |
-               while read sha1 name
-               do
-                       test "z$name" = "z$remote_name" || continue
-                       echo "$sha1"
-                       break
-               done
-         )
+         head=$(echo "$ls_remote_result" | \
+               git-fetch--tool -s pick-rref "$remote_name" "-")
          expr "z$head" : "z$_x40\$" >/dev/null ||
                die "No such ref $remote_name at $remote"
          echo >&2 "Fetching $remote_name from $remote using $proto"
-         git-http-fetch -v -a "$head" "$remote" || exit
+         case "$quiet" in '') v=-v ;; *) v= ;; esac
+         git-http-fetch $v -a "$head" "$remote" || exit
          ;;
       rsync://*)
          test -n "$shallow_depth" &&
@@ -257,8 +279,9 @@ fetch_per_ref () {
          rsync -L -q "$remote/$remote_name" "$TMP_HEAD" || exit 1
          head=$(git-rev-parse --verify TMP_HEAD)
          rm -f "$TMP_HEAD"
+         case "$quiet" in '') v=-v ;; *) v= ;; esac
          test "$rsync_slurped_objects" || {
-             rsync -av --ignore-existing --exclude info \
+             rsync -a $v --ignore-existing --exclude info \
                  "$remote/objects/" "$GIT_OBJECT_DIRECTORY/" || exit
 
              # Look at objects/info/alternates for rsync -- http will
index c714d382e87b5c18609f5934890c8b135a720262..020b86deae9ee5e258ac42b2b44c8baae7015938 100644 (file)
@@ -1,3 +1,5 @@
 GIT-VERSION-FILE
+GIT-GUI-VARS
 git-citool
 git-gui
+lib/tclIndex
index 2741c1e14c4f38b33d10c1679b48c4fc5d9c5587..25647c8060d8e30ef48d972e487dc9b280266b88 100755 (executable)
@@ -1,7 +1,7 @@
 #!/bin/sh
 
 GVF=GIT-VERSION-FILE
-DEF_VER=0.6.GITGUI
+DEF_VER=0.7.GITGUI
 
 LF='
 '
index b82789ead6255b33be0f1ed2029a91611cea3072..ee564219c0f911025b9bf850539b87edae2d13c4 100644 (file)
@@ -10,6 +10,8 @@ GIT-VERSION-FILE: .FORCE-GIT-VERSION-FILE
 SCRIPT_SH = git-gui.sh
 GITGUI_BUILT_INS = git-citool
 ALL_PROGRAMS = $(GITGUI_BUILT_INS) $(patsubst %.sh,%,$(SCRIPT_SH))
+ALL_LIBFILES = $(wildcard lib/*.tcl)
+PRELOAD_FILES = lib/class.tcl
 
 ifndef SHELL_PATH
        SHELL_PATH = /bin/sh
@@ -19,6 +21,10 @@ ifndef gitexecdir
        gitexecdir := $(shell git --exec-path)
 endif
 
+ifndef sharedir
+       sharedir := $(dir $(gitexecdir))/share
+endif
+
 ifndef INSTALL
        INSTALL = install
 endif
@@ -26,8 +32,13 @@ endif
 ifndef V
        QUIET_GEN      = @echo '   ' GEN $@;
        QUIET_BUILT_IN = @echo '   ' BUILTIN $@;
+       QUIET_INDEX    = @echo '   ' INDEX $(dir $@);
+       QUIET_2DEVNULL = 2>/dev/null
 endif
 
+TCL_PATH   ?= tclsh
+TCLTK_PATH ?= wish
+
 ifeq ($(findstring $(MAKEFLAGS),s),s)
 QUIET_GEN =
 QUIET_BUILT_IN =
@@ -36,11 +47,18 @@ endif
 DESTDIR_SQ = $(subst ','\'',$(DESTDIR))
 gitexecdir_SQ = $(subst ','\'',$(gitexecdir))
 SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH))
+TCL_PATH_SQ = $(subst ','\'',$(TCL_PATH))
+TCLTK_PATH_SQ = $(subst ','\'',$(TCLTK_PATH))
+
+libdir   ?= $(sharedir)/git-gui/lib
+libdir_SQ = $(subst ','\'',$(libdir))
 
 $(patsubst %.sh,%,$(SCRIPT_SH)) : % : %.sh
        $(QUIET_GEN)rm -f $@ $@+ && \
        sed -e '1s|#!.*/sh|#!$(SHELL_PATH_SQ)|' \
+               -e 's|^exec wish "$$0"|exec $(subst |,'\|',$(TCLTK_PATH_SQ)) "$$0"|' \
                -e 's/@@GITGUI_VERSION@@/$(GITGUI_VERSION)/g' \
+               -e 's|@@GITGUI_LIBDIR@@|$(libdir_SQ)|' \
                $@.sh >$@+ && \
        chmod +x $@+ && \
        mv $@+ $@
@@ -48,22 +66,56 @@ $(patsubst %.sh,%,$(SCRIPT_SH)) : % : %.sh
 $(GITGUI_BUILT_INS): git-gui
        $(QUIET_BUILT_IN)rm -f $@ && ln git-gui $@
 
+lib/tclIndex: $(ALL_LIBFILES)
+       $(QUIET_INDEX)if echo \
+         $(foreach p,$(PRELOAD_FILES),source $p\;) \
+         auto_mkindex lib '*.tcl' \
+       | $(TCL_PATH) $(QUIET_2DEVNULL); then : ok; \
+       else \
+        echo 1>&2 "    * $(TCL_PATH) failed; using unoptimized loading"; \
+        rm -f $@ ; \
+        echo '# Autogenerated by git-gui Makefile' >$@ && \
+        echo >>$@ && \
+        $(foreach p,$(PRELOAD_FILES) $(ALL_LIBFILES),echo '$(subst lib/,,$p)' >>$@ &&) \
+        echo >>$@ ; \
+       fi
+
 # These can record GITGUI_VERSION
-$(patsubst %.sh,%,$(SCRIPT_SH)): GIT-VERSION-FILE
+$(patsubst %.sh,%,$(SCRIPT_SH)): GIT-VERSION-FILE GIT-GUI-VARS
+lib/tclIndex: GIT-GUI-VARS
+
+TRACK_VARS = \
+       $(subst ','\'',SHELL_PATH='$(SHELL_PATH_SQ)') \
+       $(subst ','\'',TCL_PATH='$(TCL_PATH_SQ)') \
+       $(subst ','\'',TCLTK_PATH='$(TCLTK_PATH_SQ)') \
+       $(subst ','\'',libdir='$(libdir_SQ)') \
+#end TRACK_VARS
+
+GIT-GUI-VARS: .FORCE-GIT-GUI-VARS
+       @VARS='$(TRACK_VARS)'; \
+       if test x"$$VARS" != x"`cat $@ 2>/dev/null`" ; then \
+               echo 1>&2 "    * new locations or Tcl/Tk interpreter"; \
+               echo 1>$@ "$$VARS"; \
+       fi
 
-all:: $(ALL_PROGRAMS)
+all:: $(ALL_PROGRAMS) lib/tclIndex
 
 install: all
        $(INSTALL) -d -m755 '$(DESTDIR_SQ)$(gitexecdir_SQ)'
        $(INSTALL) git-gui '$(DESTDIR_SQ)$(gitexecdir_SQ)'
        $(foreach p,$(GITGUI_BUILT_INS), rm -f '$(DESTDIR_SQ)$(gitexecdir_SQ)/$p' && ln '$(DESTDIR_SQ)$(gitexecdir_SQ)/git-gui' '$(DESTDIR_SQ)$(gitexecdir_SQ)/$p' ;)
+       $(INSTALL) -d -m755 '$(DESTDIR_SQ)$(libdir_SQ)'
+       $(INSTALL) -m644 lib/tclIndex '$(DESTDIR_SQ)$(libdir_SQ)'
+       $(foreach p,$(ALL_LIBFILES), $(INSTALL) -m644 $p '$(DESTDIR_SQ)$(libdir_SQ)' ;)
 
 dist-version:
        @mkdir -p $(TARDIR)
        @echo $(GITGUI_VERSION) > $(TARDIR)/version
 
 clean::
-       rm -f $(ALL_PROGRAMS) GIT-VERSION-FILE
+       rm -f $(ALL_PROGRAMS) lib/tclIndex
+       rm -f GIT-VERSION-FILE GIT-GUI-VARS
 
 .PHONY: all install dist-version clean
 .PHONY: .FORCE-GIT-VERSION-FILE
+.PHONY: .FORCE-GIT-GUI-VARS
index ae881336dac0807d1f4a026b649c75febdc1b8b3..0a471a5c7d4a2126f1f3322424a643796e1de715 100755 (executable)
@@ -20,6 +20,57 @@ You should have received a copy of the GNU General Public License
 along with this program; if not, write to the Free Software
 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA}
 
+######################################################################
+##
+## configure our library
+
+set oguilib {@@GITGUI_LIBDIR@@}
+if {[string match @@* $oguilib]} {
+       set oguilib [file join [file dirname [file normalize $argv0]] lib]
+}
+set idx [file join $oguilib tclIndex]
+catch {
+       set fd [open $idx r]
+       if {[gets $fd] eq {# Autogenerated by git-gui Makefile}} {
+               set idx [list]
+               while {[gets $fd n] >= 0} {
+                       if {$n ne {} && ![string match #* $n]} {
+                               lappend idx $n
+                       }
+               }
+       } else {
+               set idx {}
+       }
+       close $fd
+}
+if {$idx ne {}} {
+       set loaded [list]
+       foreach p $idx {
+               if {[lsearch -exact $loaded $p] >= 0} continue
+               puts $p
+               source [file join $oguilib $p]
+               lappend loaded $p
+       }
+       unset loaded p
+} else {
+       set auto_path [concat [list $oguilib] $auto_path]
+}
+unset -nocomplain fd idx
+
+if {![catch {set _verbose $env(GITGUI_VERBOSE)}]} {
+       unset _verbose
+       rename auto_load real__auto_load
+       proc auto_load {name args} {
+               puts stderr "auto_load $name"
+               return [uplevel 1 real__auto_load $name $args]
+       }
+       rename source real__source
+       proc source {name} {
+               puts stderr "source    $name"
+               uplevel 1 real__source $name
+       }
+}
+
 ######################################################################
 ##
 ## read only globals
@@ -179,56 +230,6 @@ proc load_config {include_global} {
        }
 }
 
-proc save_config {} {
-       global default_config font_descs
-       global repo_config global_config
-       global repo_config_new global_config_new
-
-       foreach option $font_descs {
-               set name [lindex $option 0]
-               set font [lindex $option 1]
-               font configure $font \
-                       -family $global_config_new(gui.$font^^family) \
-                       -size $global_config_new(gui.$font^^size)
-               font configure ${font}bold \
-                       -family $global_config_new(gui.$font^^family) \
-                       -size $global_config_new(gui.$font^^size)
-               set global_config_new(gui.$name) [font configure $font]
-               unset global_config_new(gui.$font^^family)
-               unset global_config_new(gui.$font^^size)
-       }
-
-       foreach name [array names default_config] {
-               set value $global_config_new($name)
-               if {$value ne $global_config($name)} {
-                       if {$value eq $default_config($name)} {
-                               catch {git config --global --unset $name}
-                       } else {
-                               regsub -all "\[{}\]" $value {"} value
-                               git config --global $name $value
-                       }
-                       set global_config($name) $value
-                       if {$value eq $repo_config($name)} {
-                               catch {git config --unset $name}
-                               set repo_config($name) $value
-                       }
-               }
-       }
-
-       foreach name [array names default_config] {
-               set value $repo_config_new($name)
-               if {$value ne $repo_config($name)} {
-                       if {$value eq $global_config($name)} {
-                               catch {git config --unset $name}
-                       } else {
-                               regsub -all "\[{}\]" $value {"} value
-                               git config $name $value
-                       }
-                       set repo_config($name) $value
-               }
-       }
-}
-
 ######################################################################
 ##
 ## handy utils
@@ -237,62 +238,13 @@ proc git {args} {
        return [eval exec git $args]
 }
 
-proc error_popup {msg} {
-       set title [appname]
-       if {[reponame] ne {}} {
-               append title " ([reponame])"
-       }
-       set cmd [list tk_messageBox \
-               -icon error \
-               -type ok \
-               -title "$title: error" \
-               -message $msg]
-       if {[winfo ismapped .]} {
-               lappend cmd -parent .
-       }
-       eval $cmd
-}
-
-proc warn_popup {msg} {
-       set title [appname]
-       if {[reponame] ne {}} {
-               append title " ([reponame])"
-       }
-       set cmd [list tk_messageBox \
-               -icon warning \
-               -type ok \
-               -title "$title: warning" \
-               -message $msg]
-       if {[winfo ismapped .]} {
-               lappend cmd -parent .
-       }
-       eval $cmd
-}
-
-proc info_popup {msg {parent .}} {
-       set title [appname]
-       if {[reponame] ne {}} {
-               append title " ([reponame])"
-       }
-       tk_messageBox \
-               -parent $parent \
-               -icon info \
-               -type ok \
-               -title $title \
-               -message $msg
-}
-
-proc ask_popup {msg} {
-       set title [appname]
-       if {[reponame] ne {}} {
-               append title " ([reponame])"
-       }
-       return [tk_messageBox \
-               -parent . \
-               -icon question \
-               -type yesno \
-               -title $title \
-               -message $msg]
+auto_load tk_optionMenu
+rename tk_optionMenu real__tkOptionMenu
+proc tk_optionMenu {w varName args} {
+       set m [eval real__tkOptionMenu $w $varName $args]
+       $m configure -font font_ui
+       $w configure -font font_ui
+       return $m
 }
 
 ######################################################################
@@ -336,8 +288,14 @@ unset -nocomplain v _junk act_maj act_min req_maj req_min
 ##
 ## repository setup
 
-if {   [catch {set _gitdir $env(GIT_DIR)}]
-       && [catch {set _gitdir [git rev-parse --git-dir]} err]} {
+if {[catch {
+               set _gitdir $env(GIT_DIR)
+               set _prefix {}
+               }]
+       && [catch {
+               set _gitdir [git rev-parse --git-dir]
+               set _prefix [git rev-parse --show-prefix]
+       } err]} {
        catch {wm withdraw .}
        error_popup "Cannot find the git directory:\n\n$err"
        exit 1
@@ -655,7 +613,7 @@ proc read_ls_others {fd after} {
 }
 
 proc rescan_done {fd buf after} {
-       global rescan_active
+       global rescan_active current_diff_path
        global file_states repo_config
        upvar $buf to_clear
 
@@ -667,7 +625,7 @@ proc rescan_done {fd buf after} {
        prune_selection
        unlock_index
        display_all_files
-       reshow_diff
+       if {$current_diff_path ne {}} reshow_diff
        uplevel #0 $after
 }
 
@@ -683,3042 +641,195 @@ proc prune_selection {} {
 
 ######################################################################
 ##
-## diff
-
-proc clear_diff {} {
-       global ui_diff current_diff_path current_diff_header
-       global ui_index ui_workdir
-
-       $ui_diff conf -state normal
-       $ui_diff delete 0.0 end
-       $ui_diff conf -state disabled
-
-       set current_diff_path {}
-       set current_diff_header {}
+## ui helpers
 
-       $ui_index tag remove in_diff 0.0 end
-       $ui_workdir tag remove in_diff 0.0 end
-}
+proc mapicon {w state path} {
+       global all_icons
 
-proc reshow_diff {} {
-       global ui_status_value file_states file_lists
-       global current_diff_path current_diff_side
-
-       set p $current_diff_path
-       if {$p eq {}} {
-               # No diff is being shown.
-       } elseif {$current_diff_side eq {}
-               || [catch {set s $file_states($p)}]
-               || [lsearch -sorted -exact $file_lists($current_diff_side) $p] == -1} {
-               clear_diff
-       } else {
-               show_diff $p $current_diff_side
+       if {[catch {set r $all_icons($state$w)}]} {
+               puts "error: no icon for $w state={$state} $path"
+               return file_plain
        }
+       return $r
 }
 
-proc handle_empty_diff {} {
-       global current_diff_path file_states file_lists
-
-       set path $current_diff_path
-       set s $file_states($path)
-       if {[lindex $s 0] ne {_M}} return
-
-       info_popup "No differences detected.
-
-[short_path $path] has no changes.
+proc mapdesc {state path} {
+       global all_descs
 
-The modification date of this file was updated
-by another application, but the content within
-the file was not changed.
+       if {[catch {set r $all_descs($state)}]} {
+               puts "error: no desc for state={$state} $path"
+               return $state
+       }
+       return $r
+}
 
-A rescan will be automatically started to find
-other files which may have the same state."
+proc escape_path {path} {
+       regsub -all {\\} $path "\\\\" path
+       regsub -all "\n" $path "\\n" path
+       return $path
+}
 
-       clear_diff
-       display_file $path __
-       rescan {set ui_status_value {Ready.}} 0
+proc short_path {path} {
+       return [escape_path [lindex [file split $path] end]]
 }
 
-proc show_diff {path w {lno {}}} {
-       global file_states file_lists
-       global is_3way_diff diff_active repo_config
-       global ui_diff ui_status_value ui_index ui_workdir
-       global current_diff_path current_diff_side current_diff_header
+set next_icon_id 0
+set null_sha1 [string repeat 0 40]
 
-       if {$diff_active || ![lock_index read]} return
+proc merge_state {path new_state {head_info {}} {index_info {}}} {
+       global file_states next_icon_id null_sha1
 
-       clear_diff
-       if {$lno == {}} {
-               set lno [lsearch -sorted -exact $file_lists($w) $path]
-               if {$lno >= 0} {
-                       incr lno
-               }
-       }
-       if {$lno >= 1} {
-               $w tag add in_diff $lno.0 [expr {$lno + 1}].0
-       }
+       set s0 [string index $new_state 0]
+       set s1 [string index $new_state 1]
 
-       set s $file_states($path)
-       set m [lindex $s 0]
-       set is_3way_diff 0
-       set diff_active 1
-       set current_diff_path $path
-       set current_diff_side $w
-       set current_diff_header {}
-       set ui_status_value "Loading diff of [escape_path $path]..."
-
-       # - Git won't give us the diff, there's nothing to compare to!
-       #
-       if {$m eq {_O}} {
-               set max_sz [expr {128 * 1024}]
-               if {[catch {
-                               set fd [open $path r]
-                               set content [read $fd $max_sz]
-                               close $fd
-                               set sz [file size $path]
-                       } err ]} {
-                       set diff_active 0
-                       unlock_index
-                       set ui_status_value "Unable to display [escape_path $path]"
-                       error_popup "Error loading file:\n\n$err"
-                       return
-               }
-               $ui_diff conf -state normal
-               if {![catch {set type [exec file $path]}]} {
-                       set n [string length $path]
-                       if {[string equal -length $n $path $type]} {
-                               set type [string range $type $n end]
-                               regsub {^:?\s*} $type {} type
-                       }
-                       $ui_diff insert end "* $type\n" d_@
-               }
-               if {[string first "\0" $content] != -1} {
-                       $ui_diff insert end \
-                               "* Binary file (not showing content)." \
-                               d_@
-               } else {
-                       if {$sz > $max_sz} {
-                               $ui_diff insert end \
-"* Untracked file is $sz bytes.
-* Showing only first $max_sz bytes.
-" d_@
-                       }
-                       $ui_diff insert end $content
-                       if {$sz > $max_sz} {
-                               $ui_diff insert end "
-* Untracked file clipped here by [appname].
-* To see the entire file, use an external editor.
-" d_@
-                       }
-               }
-               $ui_diff conf -state disabled
-               set diff_active 0
-               unlock_index
-               set ui_status_value {Ready.}
-               return
+       if {[catch {set info $file_states($path)}]} {
+               set state __
+               set icon n[incr next_icon_id]
+       } else {
+               set state [lindex $info 0]
+               set icon [lindex $info 1]
+               if {$head_info eq {}}  {set head_info  [lindex $info 2]}
+               if {$index_info eq {}} {set index_info [lindex $info 3]}
        }
 
-       set cmd [list | git]
-       if {$w eq $ui_index} {
-               lappend cmd diff-index
-               lappend cmd --cached
-       } elseif {$w eq $ui_workdir} {
-               if {[string index $m 0] eq {U}} {
-                       lappend cmd diff
-               } else {
-                       lappend cmd diff-files
-               }
-       }
+       if     {$s0 eq {?}} {set s0 [string index $state 0]} \
+       elseif {$s0 eq {_}} {set s0 _}
 
-       lappend cmd -p
-       lappend cmd --no-color
-       if {$repo_config(gui.diffcontext) > 0} {
-               lappend cmd "-U$repo_config(gui.diffcontext)"
-       }
-       if {$w eq $ui_index} {
-               lappend cmd [PARENT]
-       }
-       lappend cmd --
-       lappend cmd $path
-
-       if {[catch {set fd [open $cmd r]} err]} {
-               set diff_active 0
-               unlock_index
-               set ui_status_value "Unable to display [escape_path $path]"
-               error_popup "Error loading diff:\n\n$err"
-               return
+       if     {$s1 eq {?}} {set s1 [string index $state 1]} \
+       elseif {$s1 eq {_}} {set s1 _}
+
+       if {$s0 eq {A} && $s1 eq {_} && $head_info eq {}} {
+               set head_info [list 0 $null_sha1]
+       } elseif {$s0 ne {_} && [string index $state 0] eq {_}
+               && $head_info eq {}} {
+               set head_info $index_info
        }
 
-       fconfigure $fd \
-               -blocking 0 \
-               -encoding binary \
-               -translation binary
-       fileevent $fd readable [list read_diff $fd]
+       set file_states($path) [list $s0$s1 $icon \
+               $head_info $index_info \
+               ]
+       return $state
 }
 
-proc read_diff {fd} {
-       global ui_diff ui_status_value diff_active
-       global is_3way_diff current_diff_header
-
-       $ui_diff conf -state normal
-       while {[gets $fd line] >= 0} {
-               # -- Cleanup uninteresting diff header lines.
-               #
-               if {   [string match {diff --git *}      $line]
-                       || [string match {diff --cc *}       $line]
-                       || [string match {diff --combined *} $line]
-                       || [string match {--- *}             $line]
-                       || [string match {+++ *}             $line]} {
-                       append current_diff_header $line "\n"
-                       continue
-               }
-               if {[string match {index *} $line]} continue
-               if {$line eq {deleted file mode 120000}} {
-                       set line "deleted symlink"
-               }
-
-               # -- Automatically detect if this is a 3 way diff.
-               #
-               if {[string match {@@@ *} $line]} {set is_3way_diff 1}
-
-               if {[string match {mode *} $line]
-                       || [string match {new file *} $line]
-                       || [string match {deleted file *} $line]
-                       || [string match {Binary files * and * differ} $line]
-                       || $line eq {\ No newline at end of file}
-                       || [regexp {^\* Unmerged path } $line]} {
-                       set tags {}
-               } elseif {$is_3way_diff} {
-                       set op [string range $line 0 1]
-                       switch -- $op {
-                       {  } {set tags {}}
-                       {@@} {set tags d_@}
-                       { +} {set tags d_s+}
-                       { -} {set tags d_s-}
-                       {+ } {set tags d_+s}
-                       {- } {set tags d_-s}
-                       {--} {set tags d_--}
-                       {++} {
-                               if {[regexp {^\+\+([<>]{7} |={7})} $line _g op]} {
-                                       set line [string replace $line 0 1 {  }]
-                                       set tags d$op
-                               } else {
-                                       set tags d_++
-                               }
-                       }
-                       default {
-                               puts "error: Unhandled 3 way diff marker: {$op}"
-                               set tags {}
-                       }
-                       }
-               } else {
-                       set op [string index $line 0]
-                       switch -- $op {
-                       { } {set tags {}}
-                       {@} {set tags d_@}
-                       {-} {set tags d_-}
-                       {+} {
-                               if {[regexp {^\+([<>]{7} |={7})} $line _g op]} {
-                                       set line [string replace $line 0 0 { }]
-                                       set tags d$op
-                               } else {
-                                       set tags d_+
-                               }
-                       }
-                       default {
-                               puts "error: Unhandled 2 way diff marker: {$op}"
-                               set tags {}
-                       }
-                       }
-               }
-               $ui_diff insert end $line $tags
-               if {[string index $line end] eq "\r"} {
-                       $ui_diff tag add d_cr {end - 2c}
-               }
-               $ui_diff insert end "\n" $tags
-       }
-       $ui_diff conf -state disabled
-
-       if {[eof $fd]} {
-               close $fd
-               set diff_active 0
-               unlock_index
-               set ui_status_value {Ready.}
+proc display_file_helper {w path icon_name old_m new_m} {
+       global file_lists
 
-               if {[$ui_diff index end] eq {2.0}} {
-                       handle_empty_diff
+       if {$new_m eq {_}} {
+               set lno [lsearch -sorted -exact $file_lists($w) $path]
+               if {$lno >= 0} {
+                       set file_lists($w) [lreplace $file_lists($w) $lno $lno]
+                       incr lno
+                       $w conf -state normal
+                       $w delete $lno.0 [expr {$lno + 1}].0
+                       $w conf -state disabled
                }
+       } elseif {$old_m eq {_} && $new_m ne {_}} {
+               lappend file_lists($w) $path
+               set file_lists($w) [lsort -unique $file_lists($w)]
+               set lno [lsearch -sorted -exact $file_lists($w) $path]
+               incr lno
+               $w conf -state normal
+               $w image create $lno.0 \
+                       -align center -padx 5 -pady 1 \
+                       -name $icon_name \
+                       -image [mapicon $w $new_m $path]
+               $w insert $lno.1 "[escape_path $path]\n"
+               $w conf -state disabled
+       } elseif {$old_m ne $new_m} {
+               $w conf -state normal
+               $w image conf $icon_name -image [mapicon $w $new_m $path]
+               $w conf -state disabled
        }
 }
 
-proc apply_hunk {x y} {
-       global current_diff_path current_diff_header current_diff_side
-       global ui_diff ui_index file_states
-
-       if {$current_diff_path eq {} || $current_diff_header eq {}} return
-       if {![lock_index apply_hunk]} return
-
-       set apply_cmd {git apply --cached --whitespace=nowarn}
-       set mi [lindex $file_states($current_diff_path) 0]
-       if {$current_diff_side eq $ui_index} {
-               set mode unstage
-               lappend apply_cmd --reverse
-               if {[string index $mi 0] ne {M}} {
-                       unlock_index
-                       return
-               }
-       } else {
-               set mode stage
-               if {[string index $mi 1] ne {M}} {
-                       unlock_index
-                       return
-               }
-       }
+proc display_file {path state} {
+       global file_states selected_paths
+       global ui_index ui_workdir
 
-       set s_lno [lindex [split [$ui_diff index @$x,$y] .] 0]
-       set s_lno [$ui_diff search -backwards -regexp ^@@ $s_lno.0 0.0]
-       if {$s_lno eq {}} {
-               unlock_index
-               return
-       }
+       set old_m [merge_state $path $state]
+       set s $file_states($path)
+       set new_m [lindex $s 0]
+       set icon_name [lindex $s 1]
 
-       set e_lno [$ui_diff search -forwards -regexp ^@@ "$s_lno + 1 lines" end]
-       if {$e_lno eq {}} {
-               set e_lno end
+       set o [string index $old_m 0]
+       set n [string index $new_m 0]
+       if {$o eq {U}} {
+               set o _
        }
-
-       if {[catch {
-               set p [open "| $apply_cmd" w]
-               fconfigure $p -translation binary -encoding binary
-               puts -nonewline $p $current_diff_header
-               puts -nonewline $p [$ui_diff get $s_lno $e_lno]
-               close $p} err]} {
-               error_popup "Failed to $mode selected hunk.\n\n$err"
-               unlock_index
-               return
+       if {$n eq {U}} {
+               set n _
        }
+       display_file_helper     $ui_index $path $icon_name $o $n
 
-       $ui_diff conf -state normal
-       $ui_diff delete $s_lno $e_lno
-       $ui_diff conf -state disabled
-
-       if {[$ui_diff get 1.0 end] eq "\n"} {
-               set o _
+       if {[string index $old_m 0] eq {U}} {
+               set o U
        } else {
-               set o ?
+               set o [string index $old_m 1]
        }
-
-       if {$current_diff_side eq $ui_index} {
-               set mi ${o}M
-       } elseif {[string index $mi 0] eq {_}} {
-               set mi M$o
+       if {[string index $new_m 0] eq {U}} {
+               set n U
        } else {
-               set mi ?$o
+               set n [string index $new_m 1]
        }
-       unlock_index
-       display_file $current_diff_path $mi
-       if {$o eq {_}} {
-               clear_diff
+       display_file_helper     $ui_workdir $path $icon_name $o $n
+
+       if {$new_m eq {__}} {
+               unset file_states($path)
+               catch {unset selected_paths($path)}
        }
 }
 
-######################################################################
-##
-## commit
-
-proc load_last_commit {} {
-       global HEAD PARENT MERGE_HEAD commit_type ui_comm
-       global repo_config
-
-       if {[llength $PARENT] == 0} {
-               error_popup {There is nothing to amend.
+proc display_all_files_helper {w path icon_name m} {
+       global file_lists
 
-You are about to create the initial commit.
-There is no commit before this to amend.
+       lappend file_lists($w) $path
+       set lno [expr {[lindex [split [$w index end] .] 0] - 1}]
+       $w image create end \
+               -align center -padx 5 -pady 1 \
+               -name $icon_name \
+               -image [mapicon $w $m $path]
+       $w insert end "[escape_path $path]\n"
 }
-               return
-       }
 
-       repository_state curType curHEAD curMERGE_HEAD
-       if {$curType eq {merge}} {
-               error_popup {Cannot amend while merging.
+proc display_all_files {} {
+       global ui_index ui_workdir
+       global file_states file_lists
+       global last_clicked
 
-You are currently in the middle of a merge that
-has not been fully completed.  You cannot amend
-the prior commit unless you first abort the
-current merge activity.
-}
-               return
-       }
+       $ui_index conf -state normal
+       $ui_workdir conf -state normal
 
-       set msg {}
-       set parents [list]
-       if {[catch {
-                       set fd [open "| git cat-file commit $curHEAD" r]
-                       fconfigure $fd -encoding binary -translation lf
-                       if {[catch {set enc $repo_config(i18n.commitencoding)}]} {
-                               set enc utf-8
-                       }
-                       while {[gets $fd line] > 0} {
-                               if {[string match {parent *} $line]} {
-                                       lappend parents [string range $line 7 end]
-                               } elseif {[string match {encoding *} $line]} {
-                                       set enc [string tolower [string range $line 9 end]]
-                               }
-                       }
-                       fconfigure $fd -encoding $enc
-                       set msg [string trim [read $fd]]
-                       close $fd
-               } err]} {
-               error_popup "Error loading commit data for amend:\n\n$err"
-               return
-       }
-
-       set HEAD $curHEAD
-       set PARENT $parents
-       set MERGE_HEAD [list]
-       switch -- [llength $parents] {
-       0       {set commit_type amend-initial}
-       1       {set commit_type amend}
-       default {set commit_type amend-merge}
-       }
-
-       $ui_comm delete 0.0 end
-       $ui_comm insert end $msg
-       $ui_comm edit reset
-       $ui_comm edit modified false
-       rescan {set ui_status_value {Ready.}}
-}
-
-proc create_new_commit {} {
-       global commit_type ui_comm
-
-       set commit_type normal
-       $ui_comm delete 0.0 end
-       $ui_comm edit reset
-       $ui_comm edit modified false
-       rescan {set ui_status_value {Ready.}}
-}
-
-set GIT_COMMITTER_IDENT {}
-
-proc committer_ident {} {
-       global GIT_COMMITTER_IDENT
-
-       if {$GIT_COMMITTER_IDENT eq {}} {
-               if {[catch {set me [git var GIT_COMMITTER_IDENT]} err]} {
-                       error_popup "Unable to obtain your identity:\n\n$err"
-                       return {}
-               }
-               if {![regexp {^(.*) [0-9]+ [-+0-9]+$} \
-                       $me me GIT_COMMITTER_IDENT]} {
-                       error_popup "Invalid GIT_COMMITTER_IDENT:\n\n$me"
-                       return {}
-               }
-       }
-
-       return $GIT_COMMITTER_IDENT
-}
-
-proc commit_tree {} {
-       global HEAD commit_type file_states ui_comm repo_config
-       global ui_status_value pch_error
-
-       if {[committer_ident] eq {}} return
-       if {![lock_index update]} return
-
-       # -- Our in memory state should match the repository.
-       #
-       repository_state curType curHEAD curMERGE_HEAD
-       if {[string match amend* $commit_type]
-               && $curType eq {normal}
-               && $curHEAD eq $HEAD} {
-       } elseif {$commit_type ne $curType || $HEAD ne $curHEAD} {
-               info_popup {Last scanned state does not match repository state.
-
-Another Git program has modified this repository
-since the last scan.  A rescan must be performed
-before another commit can be created.
-
-The rescan will be automatically started now.
-}
-               unlock_index
-               rescan {set ui_status_value {Ready.}}
-               return
-       }
-
-       # -- At least one file should differ in the index.
-       #
-       set files_ready 0
-       foreach path [array names file_states] {
-               switch -glob -- [lindex $file_states($path) 0] {
-               _? {continue}
-               A? -
-               D? -
-               M? {set files_ready 1}
-               U? {
-                       error_popup "Unmerged files cannot be committed.
-
-File [short_path $path] has merge conflicts.
-You must resolve them and add the file before committing.
-"
-                       unlock_index
-                       return
-               }
-               default {
-                       error_popup "Unknown file state [lindex $s 0] detected.
-
-File [short_path $path] cannot be committed by this program.
-"
-               }
-               }
-       }
-       if {!$files_ready && ![string match *merge $curType]} {
-               info_popup {No changes to commit.
-
-You must add at least 1 file before you can commit.
-}
-               unlock_index
-               return
-       }
-
-       # -- A message is required.
-       #
-       set msg [string trim [$ui_comm get 1.0 end]]
-       regsub -all -line {[ \t\r]+$} $msg {} msg
-       if {$msg eq {}} {
-               error_popup {Please supply a commit message.
-
-A good commit message has the following format:
-
-- First line: Describe in one sentance what you did.
-- Second line: Blank
-- Remaining lines: Describe why this change is good.
-}
-               unlock_index
-               return
-       }
-
-       # -- Run the pre-commit hook.
-       #
-       set pchook [gitdir hooks pre-commit]
-
-       # On Cygwin [file executable] might lie so we need to ask
-       # the shell if the hook is executable.  Yes that's annoying.
-       #
-       if {[is_Cygwin] && [file isfile $pchook]} {
-               set pchook [list sh -c [concat \
-                       "if test -x \"$pchook\";" \
-                       "then exec \"$pchook\" 2>&1;" \
-                       "fi"]]
-       } elseif {[file executable $pchook]} {
-               set pchook [list $pchook |& cat]
-       } else {
-               commit_writetree $curHEAD $msg
-               return
-       }
-
-       set ui_status_value {Calling pre-commit hook...}
-       set pch_error {}
-       set fd_ph [open "| $pchook" r]
-       fconfigure $fd_ph -blocking 0 -translation binary
-       fileevent $fd_ph readable \
-               [list commit_prehook_wait $fd_ph $curHEAD $msg]
-}
-
-proc commit_prehook_wait {fd_ph curHEAD msg} {
-       global pch_error ui_status_value
-
-       append pch_error [read $fd_ph]
-       fconfigure $fd_ph -blocking 1
-       if {[eof $fd_ph]} {
-               if {[catch {close $fd_ph}]} {
-                       set ui_status_value {Commit declined by pre-commit hook.}
-                       hook_failed_popup pre-commit $pch_error
-                       unlock_index
-               } else {
-                       commit_writetree $curHEAD $msg
-               }
-               set pch_error {}
-               return
-       }
-       fconfigure $fd_ph -blocking 0
-}
-
-proc commit_writetree {curHEAD msg} {
-       global ui_status_value
-
-       set ui_status_value {Committing changes...}
-       set fd_wt [open "| git write-tree" r]
-       fileevent $fd_wt readable \
-               [list commit_committree $fd_wt $curHEAD $msg]
-}
-
-proc commit_committree {fd_wt curHEAD msg} {
-       global HEAD PARENT MERGE_HEAD commit_type
-       global all_heads current_branch
-       global ui_status_value ui_comm selected_commit_type
-       global file_states selected_paths rescan_active
-       global repo_config
-
-       gets $fd_wt tree_id
-       if {$tree_id eq {} || [catch {close $fd_wt} err]} {
-               error_popup "write-tree failed:\n\n$err"
-               set ui_status_value {Commit failed.}
-               unlock_index
-               return
-       }
-
-       # -- Verify this wasn't an empty change.
-       #
-       if {$commit_type eq {normal}} {
-               set old_tree [git rev-parse "$PARENT^{tree}"]
-               if {$tree_id eq $old_tree} {
-                       info_popup {No changes to commit.
-
-No files were modified by this commit and it
-was not a merge commit.
-
-A rescan will be automatically started now.
-}
-                       unlock_index
-                       rescan {set ui_status_value {No changes to commit.}}
-                       return
-               }
-       }
-
-       # -- Build the message.
-       #
-       set msg_p [gitdir COMMIT_EDITMSG]
-       set msg_wt [open $msg_p w]
-       if {[catch {set enc $repo_config(i18n.commitencoding)}]} {
-               set enc utf-8
-       }
-       fconfigure $msg_wt -encoding $enc -translation binary
-       puts -nonewline $msg_wt $msg
-       close $msg_wt
-
-       # -- Create the commit.
-       #
-       set cmd [list git commit-tree $tree_id]
-       foreach p [concat $PARENT $MERGE_HEAD] {
-               lappend cmd -p $p
-       }
-       lappend cmd <$msg_p
-       if {[catch {set cmt_id [eval exec $cmd]} err]} {
-               error_popup "commit-tree failed:\n\n$err"
-               set ui_status_value {Commit failed.}
-               unlock_index
-               return
-       }
-
-       # -- Update the HEAD ref.
-       #
-       set reflogm commit
-       if {$commit_type ne {normal}} {
-               append reflogm " ($commit_type)"
-       }
-       set i [string first "\n" $msg]
-       if {$i >= 0} {
-               append reflogm {: } [string range $msg 0 [expr {$i - 1}]]
-       } else {
-               append reflogm {: } $msg
-       }
-       set cmd [list git update-ref -m $reflogm HEAD $cmt_id $curHEAD]
-       if {[catch {eval exec $cmd} err]} {
-               error_popup "update-ref failed:\n\n$err"
-               set ui_status_value {Commit failed.}
-               unlock_index
-               return
-       }
-
-       # -- Cleanup after ourselves.
-       #
-       catch {file delete $msg_p}
-       catch {file delete [gitdir MERGE_HEAD]}
-       catch {file delete [gitdir MERGE_MSG]}
-       catch {file delete [gitdir SQUASH_MSG]}
-       catch {file delete [gitdir GITGUI_MSG]}
-
-       # -- Let rerere do its thing.
-       #
-       if {[file isdirectory [gitdir rr-cache]]} {
-               catch {git rerere}
-       }
-
-       # -- Run the post-commit hook.
-       #
-       set pchook [gitdir hooks post-commit]
-       if {[is_Cygwin] && [file isfile $pchook]} {
-               set pchook [list sh -c [concat \
-                       "if test -x \"$pchook\";" \
-                       "then exec \"$pchook\";" \
-                       "fi"]]
-       } elseif {![file executable $pchook]} {
-               set pchook {}
-       }
-       if {$pchook ne {}} {
-               catch {exec $pchook &}
-       }
-
-       $ui_comm delete 0.0 end
-       $ui_comm edit reset
-       $ui_comm edit modified false
-
-       if {[is_enabled singlecommit]} do_quit
-
-       # -- Make sure our current branch exists.
-       #
-       if {$commit_type eq {initial}} {
-               lappend all_heads $current_branch
-               set all_heads [lsort -unique $all_heads]
-               populate_branch_menu
-       }
+       $ui_index delete 0.0 end
+       $ui_workdir delete 0.0 end
+       set last_clicked {}
 
-       # -- Update in memory status
-       #
-       set selected_commit_type new
-       set commit_type normal
-       set HEAD $cmt_id
-       set PARENT $cmt_id
-       set MERGE_HEAD [list]
+       set file_lists($ui_index) [list]
+       set file_lists($ui_workdir) [list]
 
-       foreach path [array names file_states] {
+       foreach path [lsort [array names file_states]] {
                set s $file_states($path)
                set m [lindex $s 0]
-               switch -glob -- $m {
-               _O -
-               _M -
-               _D {continue}
-               __ -
-               A_ -
-               M_ -
-               D_ {
-                       unset file_states($path)
-                       catch {unset selected_paths($path)}
-               }
-               DO {
-                       set file_states($path) [list _O [lindex $s 1] {} {}]
-               }
-               AM -
-               AD -
-               MM -
-               MD {
-                       set file_states($path) [list \
-                               _[string index $m 1] \
-                               [lindex $s 1] \
-                               [lindex $s 3] \
-                               {}]
-               }
-               }
-       }
-
-       display_all_files
-       unlock_index
-       reshow_diff
-       set ui_status_value \
-               "Changes committed as [string range $cmt_id 0 7]."
-}
-
-######################################################################
-##
-## fetch push
-
-proc fetch_from {remote} {
-       set w [new_console \
-               "fetch $remote" \
-               "Fetching new changes from $remote"]
-       set cmd [list git fetch]
-       lappend cmd $remote
-       console_exec $w $cmd console_done
-}
-
-proc push_to {remote} {
-       set w [new_console \
-               "push $remote" \
-               "Pushing changes to $remote"]
-       set cmd [list git push]
-       lappend cmd -v
-       lappend cmd $remote
-       console_exec $w $cmd console_done
-}
-
-######################################################################
-##
-## ui helpers
-
-proc mapicon {w state path} {
-       global all_icons
-
-       if {[catch {set r $all_icons($state$w)}]} {
-               puts "error: no icon for $w state={$state} $path"
-               return file_plain
-       }
-       return $r
-}
-
-proc mapdesc {state path} {
-       global all_descs
-
-       if {[catch {set r $all_descs($state)}]} {
-               puts "error: no desc for state={$state} $path"
-               return $state
-       }
-       return $r
-}
-
-proc escape_path {path} {
-       regsub -all {\\} $path "\\\\" path
-       regsub -all "\n" $path "\\n" path
-       return $path
-}
-
-proc short_path {path} {
-       return [escape_path [lindex [file split $path] end]]
-}
-
-set next_icon_id 0
-set null_sha1 [string repeat 0 40]
-
-proc merge_state {path new_state {head_info {}} {index_info {}}} {
-       global file_states next_icon_id null_sha1
-
-       set s0 [string index $new_state 0]
-       set s1 [string index $new_state 1]
-
-       if {[catch {set info $file_states($path)}]} {
-               set state __
-               set icon n[incr next_icon_id]
-       } else {
-               set state [lindex $info 0]
-               set icon [lindex $info 1]
-               if {$head_info eq {}}  {set head_info  [lindex $info 2]}
-               if {$index_info eq {}} {set index_info [lindex $info 3]}
-       }
-
-       if     {$s0 eq {?}} {set s0 [string index $state 0]} \
-       elseif {$s0 eq {_}} {set s0 _}
-
-       if     {$s1 eq {?}} {set s1 [string index $state 1]} \
-       elseif {$s1 eq {_}} {set s1 _}
-
-       if {$s0 eq {A} && $s1 eq {_} && $head_info eq {}} {
-               set head_info [list 0 $null_sha1]
-       } elseif {$s0 ne {_} && [string index $state 0] eq {_}
-               && $head_info eq {}} {
-               set head_info $index_info
-       }
-
-       set file_states($path) [list $s0$s1 $icon \
-               $head_info $index_info \
-               ]
-       return $state
-}
-
-proc display_file_helper {w path icon_name old_m new_m} {
-       global file_lists
-
-       if {$new_m eq {_}} {
-               set lno [lsearch -sorted -exact $file_lists($w) $path]
-               if {$lno >= 0} {
-                       set file_lists($w) [lreplace $file_lists($w) $lno $lno]
-                       incr lno
-                       $w conf -state normal
-                       $w delete $lno.0 [expr {$lno + 1}].0
-                       $w conf -state disabled
-               }
-       } elseif {$old_m eq {_} && $new_m ne {_}} {
-               lappend file_lists($w) $path
-               set file_lists($w) [lsort -unique $file_lists($w)]
-               set lno [lsearch -sorted -exact $file_lists($w) $path]
-               incr lno
-               $w conf -state normal
-               $w image create $lno.0 \
-                       -align center -padx 5 -pady 1 \
-                       -name $icon_name \
-                       -image [mapicon $w $new_m $path]
-               $w insert $lno.1 "[escape_path $path]\n"
-               $w conf -state disabled
-       } elseif {$old_m ne $new_m} {
-               $w conf -state normal
-               $w image conf $icon_name -image [mapicon $w $new_m $path]
-               $w conf -state disabled
-       }
-}
-
-proc display_file {path state} {
-       global file_states selected_paths
-       global ui_index ui_workdir
-
-       set old_m [merge_state $path $state]
-       set s $file_states($path)
-       set new_m [lindex $s 0]
-       set icon_name [lindex $s 1]
-
-       set o [string index $old_m 0]
-       set n [string index $new_m 0]
-       if {$o eq {U}} {
-               set o _
-       }
-       if {$n eq {U}} {
-               set n _
-       }
-       display_file_helper     $ui_index $path $icon_name $o $n
-
-       if {[string index $old_m 0] eq {U}} {
-               set o U
-       } else {
-               set o [string index $old_m 1]
-       }
-       if {[string index $new_m 0] eq {U}} {
-               set n U
-       } else {
-               set n [string index $new_m 1]
-       }
-       display_file_helper     $ui_workdir $path $icon_name $o $n
-
-       if {$new_m eq {__}} {
-               unset file_states($path)
-               catch {unset selected_paths($path)}
-       }
-}
-
-proc display_all_files_helper {w path icon_name m} {
-       global file_lists
-
-       lappend file_lists($w) $path
-       set lno [expr {[lindex [split [$w index end] .] 0] - 1}]
-       $w image create end \
-               -align center -padx 5 -pady 1 \
-               -name $icon_name \
-               -image [mapicon $w $m $path]
-       $w insert end "[escape_path $path]\n"
-}
-
-proc display_all_files {} {
-       global ui_index ui_workdir
-       global file_states file_lists
-       global last_clicked
-
-       $ui_index conf -state normal
-       $ui_workdir conf -state normal
-
-       $ui_index delete 0.0 end
-       $ui_workdir delete 0.0 end
-       set last_clicked {}
-
-       set file_lists($ui_index) [list]
-       set file_lists($ui_workdir) [list]
-
-       foreach path [lsort [array names file_states]] {
-               set s $file_states($path)
-               set m [lindex $s 0]
-               set icon_name [lindex $s 1]
-
-               set s [string index $m 0]
-               if {$s ne {U} && $s ne {_}} {
-                       display_all_files_helper $ui_index $path \
-                               $icon_name $s
-               }
-
-               if {[string index $m 0] eq {U}} {
-                       set s U
-               } else {
-                       set s [string index $m 1]
-               }
-               if {$s ne {_}} {
-                       display_all_files_helper $ui_workdir $path \
-                               $icon_name $s
-               }
-       }
-
-       $ui_index conf -state disabled
-       $ui_workdir conf -state disabled
-}
-
-proc update_indexinfo {msg pathList after} {
-       global update_index_cp ui_status_value
-
-       if {![lock_index update]} return
-
-       set update_index_cp 0
-       set pathList [lsort $pathList]
-       set totalCnt [llength $pathList]
-       set batch [expr {int($totalCnt * .01) + 1}]
-       if {$batch > 25} {set batch 25}
-
-       set ui_status_value [format \
-               "$msg... %i/%i files (%.2f%%)" \
-               $update_index_cp \
-               $totalCnt \
-               0.0]
-       set fd [open "| git update-index -z --index-info" w]
-       fconfigure $fd \
-               -blocking 0 \
-               -buffering full \
-               -buffersize 512 \
-               -encoding binary \
-               -translation binary
-       fileevent $fd writable [list \
-               write_update_indexinfo \
-               $fd \
-               $pathList \
-               $totalCnt \
-               $batch \
-               $msg \
-               $after \
-               ]
-}
-
-proc write_update_indexinfo {fd pathList totalCnt batch msg after} {
-       global update_index_cp ui_status_value
-       global file_states current_diff_path
-
-       if {$update_index_cp >= $totalCnt} {
-               close $fd
-               unlock_index
-               uplevel #0 $after
-               return
-       }
-
-       for {set i $batch} \
-               {$update_index_cp < $totalCnt && $i > 0} \
-               {incr i -1} {
-               set path [lindex $pathList $update_index_cp]
-               incr update_index_cp
-
-               set s $file_states($path)
-               switch -glob -- [lindex $s 0] {
-               A? {set new _O}
-               M? {set new _M}
-               D_ {set new _D}
-               D? {set new _?}
-               ?? {continue}
-               }
-               set info [lindex $s 2]
-               if {$info eq {}} continue
-
-               puts -nonewline $fd "$info\t[encoding convertto $path]\0"
-               display_file $path $new
-       }
-
-       set ui_status_value [format \
-               "$msg... %i/%i files (%.2f%%)" \
-               $update_index_cp \
-               $totalCnt \
-               [expr {100.0 * $update_index_cp / $totalCnt}]]
-}
-
-proc update_index {msg pathList after} {
-       global update_index_cp ui_status_value
-
-       if {![lock_index update]} return
-
-       set update_index_cp 0
-       set pathList [lsort $pathList]
-       set totalCnt [llength $pathList]
-       set batch [expr {int($totalCnt * .01) + 1}]
-       if {$batch > 25} {set batch 25}
-
-       set ui_status_value [format \
-               "$msg... %i/%i files (%.2f%%)" \
-               $update_index_cp \
-               $totalCnt \
-               0.0]
-       set fd [open "| git update-index --add --remove -z --stdin" w]
-       fconfigure $fd \
-               -blocking 0 \
-               -buffering full \
-               -buffersize 512 \
-               -encoding binary \
-               -translation binary
-       fileevent $fd writable [list \
-               write_update_index \
-               $fd \
-               $pathList \
-               $totalCnt \
-               $batch \
-               $msg \
-               $after \
-               ]
-}
-
-proc write_update_index {fd pathList totalCnt batch msg after} {
-       global update_index_cp ui_status_value
-       global file_states current_diff_path
-
-       if {$update_index_cp >= $totalCnt} {
-               close $fd
-               unlock_index
-               uplevel #0 $after
-               return
-       }
-
-       for {set i $batch} \
-               {$update_index_cp < $totalCnt && $i > 0} \
-               {incr i -1} {
-               set path [lindex $pathList $update_index_cp]
-               incr update_index_cp
-
-               switch -glob -- [lindex $file_states($path) 0] {
-               AD {set new __}
-               ?D {set new D_}
-               _O -
-               AM {set new A_}
-               U? {
-                       if {[file exists $path]} {
-                               set new M_
-                       } else {
-                               set new D_
-                       }
-               }
-               ?M {set new M_}
-               ?? {continue}
-               }
-               puts -nonewline $fd "[encoding convertto $path]\0"
-               display_file $path $new
-       }
-
-       set ui_status_value [format \
-               "$msg... %i/%i files (%.2f%%)" \
-               $update_index_cp \
-               $totalCnt \
-               [expr {100.0 * $update_index_cp / $totalCnt}]]
-}
-
-proc checkout_index {msg pathList after} {
-       global update_index_cp ui_status_value
-
-       if {![lock_index update]} return
-
-       set update_index_cp 0
-       set pathList [lsort $pathList]
-       set totalCnt [llength $pathList]
-       set batch [expr {int($totalCnt * .01) + 1}]
-       if {$batch > 25} {set batch 25}
-
-       set ui_status_value [format \
-               "$msg... %i/%i files (%.2f%%)" \
-               $update_index_cp \
-               $totalCnt \
-               0.0]
-       set cmd [list git checkout-index]
-       lappend cmd --index
-       lappend cmd --quiet
-       lappend cmd --force
-       lappend cmd -z
-       lappend cmd --stdin
-       set fd [open "| $cmd " w]
-       fconfigure $fd \
-               -blocking 0 \
-               -buffering full \
-               -buffersize 512 \
-               -encoding binary \
-               -translation binary
-       fileevent $fd writable [list \
-               write_checkout_index \
-               $fd \
-               $pathList \
-               $totalCnt \
-               $batch \
-               $msg \
-               $after \
-               ]
-}
-
-proc write_checkout_index {fd pathList totalCnt batch msg after} {
-       global update_index_cp ui_status_value
-       global file_states current_diff_path
-
-       if {$update_index_cp >= $totalCnt} {
-               close $fd
-               unlock_index
-               uplevel #0 $after
-               return
-       }
-
-       for {set i $batch} \
-               {$update_index_cp < $totalCnt && $i > 0} \
-               {incr i -1} {
-               set path [lindex $pathList $update_index_cp]
-               incr update_index_cp
-               switch -glob -- [lindex $file_states($path) 0] {
-               U? {continue}
-               ?M -
-               ?D {
-                       puts -nonewline $fd "[encoding convertto $path]\0"
-                       display_file $path ?_
-               }
-               }
-       }
-
-       set ui_status_value [format \
-               "$msg... %i/%i files (%.2f%%)" \
-               $update_index_cp \
-               $totalCnt \
-               [expr {100.0 * $update_index_cp / $totalCnt}]]
-}
-
-######################################################################
-##
-## branch management
-
-proc is_tracking_branch {name} {
-       global tracking_branches
-
-       if {![catch {set info $tracking_branches($name)}]} {
-               return 1
-       }
-       foreach t [array names tracking_branches] {
-               if {[string match {*/\*} $t] && [string match $t $name]} {
-                       return 1
-               }
-       }
-       return 0
-}
-
-proc load_all_heads {} {
-       global all_heads
-
-       set all_heads [list]
-       set fd [open "| git for-each-ref --format=%(refname) refs/heads" r]
-       while {[gets $fd line] > 0} {
-               if {[is_tracking_branch $line]} continue
-               if {![regsub ^refs/heads/ $line {} name]} continue
-               lappend all_heads $name
-       }
-       close $fd
-
-       set all_heads [lsort $all_heads]
-}
-
-proc populate_branch_menu {} {
-       global all_heads disable_on_lock
-
-       set m .mbar.branch
-       set last [$m index last]
-       for {set i 0} {$i <= $last} {incr i} {
-               if {[$m type $i] eq {separator}} {
-                       $m delete $i last
-                       set new_dol [list]
-                       foreach a $disable_on_lock {
-                               if {[lindex $a 0] ne $m || [lindex $a 2] < $i} {
-                                       lappend new_dol $a
-                               }
-                       }
-                       set disable_on_lock $new_dol
-                       break
-               }
-       }
-
-       if {$all_heads ne {}} {
-               $m add separator
-       }
-       foreach b $all_heads {
-               $m add radiobutton \
-                       -label $b \
-                       -command [list switch_branch $b] \
-                       -variable current_branch \
-                       -value $b \
-                       -font font_ui
-               lappend disable_on_lock \
-                       [list $m entryconf [$m index last] -state]
-       }
-}
-
-proc all_tracking_branches {} {
-       global tracking_branches
-
-       set all_trackings {}
-       set cmd {}
-       foreach name [array names tracking_branches] {
-               if {[regsub {/\*$} $name {} name]} {
-                       lappend cmd $name
-               } else {
-                       regsub ^refs/(heads|remotes)/ $name {} name
-                       lappend all_trackings $name
-               }
-       }
-
-       if {$cmd ne {}} {
-               set fd [open "| git for-each-ref --format=%(refname) $cmd" r]
-               while {[gets $fd name] > 0} {
-                       regsub ^refs/(heads|remotes)/ $name {} name
-                       lappend all_trackings $name
-               }
-               close $fd
-       }
-
-       return [lsort -unique $all_trackings]
-}
-
-proc load_all_tags {} {
-       set all_tags [list]
-       set fd [open "| git for-each-ref --format=%(refname) refs/tags" r]
-       while {[gets $fd line] > 0} {
-               if {![regsub ^refs/tags/ $line {} name]} continue
-               lappend all_tags $name
-       }
-       close $fd
-
-       return [lsort $all_tags]
-}
-
-proc do_create_branch_action {w} {
-       global all_heads null_sha1 repo_config
-       global create_branch_checkout create_branch_revtype
-       global create_branch_head create_branch_trackinghead
-       global create_branch_name create_branch_revexp
-       global create_branch_tag
-
-       set newbranch $create_branch_name
-       if {$newbranch eq {}
-               || $newbranch eq $repo_config(gui.newbranchtemplate)} {
-               tk_messageBox \
-                       -icon error \
-                       -type ok \
-                       -title [wm title $w] \
-                       -parent $w \
-                       -message "Please supply a branch name."
-               focus $w.desc.name_t
-               return
-       }
-       if {![catch {git show-ref --verify -- "refs/heads/$newbranch"}]} {
-               tk_messageBox \
-                       -icon error \
-                       -type ok \
-                       -title [wm title $w] \
-                       -parent $w \
-                       -message "Branch '$newbranch' already exists."
-               focus $w.desc.name_t
-               return
-       }
-       if {[catch {git check-ref-format "heads/$newbranch"}]} {
-               tk_messageBox \
-                       -icon error \
-                       -type ok \
-                       -title [wm title $w] \
-                       -parent $w \
-                       -message "We do not like '$newbranch' as a branch name."
-               focus $w.desc.name_t
-               return
-       }
-
-       set rev {}
-       switch -- $create_branch_revtype {
-       head {set rev $create_branch_head}
-       tracking {set rev $create_branch_trackinghead}
-       tag {set rev $create_branch_tag}
-       expression {set rev $create_branch_revexp}
-       }
-       if {[catch {set cmt [git rev-parse --verify "${rev}^0"]}]} {
-               tk_messageBox \
-                       -icon error \
-                       -type ok \
-                       -title [wm title $w] \
-                       -parent $w \
-                       -message "Invalid starting revision: $rev"
-               return
-       }
-       set cmd [list git update-ref]
-       lappend cmd -m
-       lappend cmd "branch: Created from $rev"
-       lappend cmd "refs/heads/$newbranch"
-       lappend cmd $cmt
-       lappend cmd $null_sha1
-       if {[catch {eval exec $cmd} err]} {
-               tk_messageBox \
-                       -icon error \
-                       -type ok \
-                       -title [wm title $w] \
-                       -parent $w \
-                       -message "Failed to create '$newbranch'.\n\n$err"
-               return
-       }
-
-       lappend all_heads $newbranch
-       set all_heads [lsort $all_heads]
-       populate_branch_menu
-       destroy $w
-       if {$create_branch_checkout} {
-               switch_branch $newbranch
-       }
-}
-
-proc radio_selector {varname value args} {
-       upvar #0 $varname var
-       set var $value
-}
-
-trace add variable create_branch_head write \
-       [list radio_selector create_branch_revtype head]
-trace add variable create_branch_trackinghead write \
-       [list radio_selector create_branch_revtype tracking]
-trace add variable create_branch_tag write \
-       [list radio_selector create_branch_revtype tag]
-
-trace add variable delete_branch_head write \
-       [list radio_selector delete_branch_checktype head]
-trace add variable delete_branch_trackinghead write \
-       [list radio_selector delete_branch_checktype tracking]
-
-proc do_create_branch {} {
-       global all_heads current_branch repo_config
-       global create_branch_checkout create_branch_revtype
-       global create_branch_head create_branch_trackinghead
-       global create_branch_name create_branch_revexp
-       global create_branch_tag
-
-       set w .branch_editor
-       toplevel $w
-       wm geometry $w "+[winfo rootx .]+[winfo rooty .]"
-
-       label $w.header -text {Create New Branch} \
-               -font font_uibold
-       pack $w.header -side top -fill x
-
-       frame $w.buttons
-       button $w.buttons.create -text Create \
-               -font font_ui \
-               -default active \
-               -command [list do_create_branch_action $w]
-       pack $w.buttons.create -side right
-       button $w.buttons.cancel -text {Cancel} \
-               -font font_ui \
-               -command [list destroy $w]
-       pack $w.buttons.cancel -side right -padx 5
-       pack $w.buttons -side bottom -fill x -pady 10 -padx 10
-
-       labelframe $w.desc \
-               -text {Branch Description} \
-               -font font_ui
-       label $w.desc.name_l -text {Name:} -font font_ui
-       entry $w.desc.name_t \
-               -borderwidth 1 \
-               -relief sunken \
-               -width 40 \
-               -textvariable create_branch_name \
-               -font font_ui \
-               -validate key \
-               -validatecommand {
-                       if {%d == 1 && [regexp {[~^:?*\[\0- ]} %S]} {return 0}
-                       return 1
-               }
-       grid $w.desc.name_l $w.desc.name_t -sticky we -padx {0 5}
-       grid columnconfigure $w.desc 1 -weight 1
-       pack $w.desc -anchor nw -fill x -pady 5 -padx 5
-
-       labelframe $w.from \
-               -text {Starting Revision} \
-               -font font_ui
-       radiobutton $w.from.head_r \
-               -text {Local Branch:} \
-               -value head \
-               -variable create_branch_revtype \
-               -font font_ui
-       eval tk_optionMenu $w.from.head_m create_branch_head $all_heads
-       grid $w.from.head_r $w.from.head_m -sticky w
-       set all_trackings [all_tracking_branches]
-       if {$all_trackings ne {}} {
-               set create_branch_trackinghead [lindex $all_trackings 0]
-               radiobutton $w.from.tracking_r \
-                       -text {Tracking Branch:} \
-                       -value tracking \
-                       -variable create_branch_revtype \
-                       -font font_ui
-               eval tk_optionMenu $w.from.tracking_m \
-                       create_branch_trackinghead \
-                       $all_trackings
-               grid $w.from.tracking_r $w.from.tracking_m -sticky w
-       }
-       set all_tags [load_all_tags]
-       if {$all_tags ne {}} {
-               set create_branch_tag [lindex $all_tags 0]
-               radiobutton $w.from.tag_r \
-                       -text {Tag:} \
-                       -value tag \
-                       -variable create_branch_revtype \
-                       -font font_ui
-               eval tk_optionMenu $w.from.tag_m \
-                       create_branch_tag \
-                       $all_tags
-               grid $w.from.tag_r $w.from.tag_m -sticky w
-       }
-       radiobutton $w.from.exp_r \
-               -text {Revision Expression:} \
-               -value expression \
-               -variable create_branch_revtype \
-               -font font_ui
-       entry $w.from.exp_t \
-               -borderwidth 1 \
-               -relief sunken \
-               -width 50 \
-               -textvariable create_branch_revexp \
-               -font font_ui \
-               -validate key \
-               -validatecommand {
-                       if {%d == 1 && [regexp {\s} %S]} {return 0}
-                       if {%d == 1 && [string length %S] > 0} {
-                               set create_branch_revtype expression
-                       }
-                       return 1
-               }
-       grid $w.from.exp_r $w.from.exp_t -sticky we -padx {0 5}
-       grid columnconfigure $w.from 1 -weight 1
-       pack $w.from -anchor nw -fill x -pady 5 -padx 5
-
-       labelframe $w.postActions \
-               -text {Post Creation Actions} \
-               -font font_ui
-       checkbutton $w.postActions.checkout \
-               -text {Checkout after creation} \
-               -variable create_branch_checkout \
-               -font font_ui
-       pack $w.postActions.checkout -anchor nw
-       pack $w.postActions -anchor nw -fill x -pady 5 -padx 5
-
-       set create_branch_checkout 1
-       set create_branch_head $current_branch
-       set create_branch_revtype head
-       set create_branch_name $repo_config(gui.newbranchtemplate)
-       set create_branch_revexp {}
-
-       bind $w <Visibility> "
-               grab $w
-               $w.desc.name_t icursor end
-               focus $w.desc.name_t
-       "
-       bind $w <Key-Escape> "destroy $w"
-       bind $w <Key-Return> "do_create_branch_action $w;break"
-       wm title $w "[appname] ([reponame]): Create Branch"
-       tkwait window $w
-}
-
-proc do_delete_branch_action {w} {
-       global all_heads
-       global delete_branch_checktype delete_branch_head delete_branch_trackinghead
-
-       set check_rev {}
-       switch -- $delete_branch_checktype {
-       head {set check_rev $delete_branch_head}
-       tracking {set check_rev $delete_branch_trackinghead}
-       always {set check_rev {:none}}
-       }
-       if {$check_rev eq {:none}} {
-               set check_cmt {}
-       } elseif {[catch {set check_cmt [git rev-parse --verify "${check_rev}^0"]}]} {
-               tk_messageBox \
-                       -icon error \
-                       -type ok \
-                       -title [wm title $w] \
-                       -parent $w \
-                       -message "Invalid check revision: $check_rev"
-               return
-       }
-
-       set to_delete [list]
-       set not_merged [list]
-       foreach i [$w.list.l curselection] {
-               set b [$w.list.l get $i]
-               if {[catch {set o [git rev-parse --verify $b]}]} continue
-               if {$check_cmt ne {}} {
-                       if {$b eq $check_rev} continue
-                       if {[catch {set m [git merge-base $o $check_cmt]}]} continue
-                       if {$o ne $m} {
-                               lappend not_merged $b
-                               continue
-                       }
-               }
-               lappend to_delete [list $b $o]
-       }
-       if {$not_merged ne {}} {
-               set msg "The following branches are not completely merged into $check_rev:
-
- - [join $not_merged "\n - "]"
-               tk_messageBox \
-                       -icon info \
-                       -type ok \
-                       -title [wm title $w] \
-                       -parent $w \
-                       -message $msg
-       }
-       if {$to_delete eq {}} return
-       if {$delete_branch_checktype eq {always}} {
-               set msg {Recovering deleted branches is difficult.
-
-Delete the selected branches?}
-               if {[tk_messageBox \
-                       -icon warning \
-                       -type yesno \
-                       -title [wm title $w] \
-                       -parent $w \
-                       -message $msg] ne yes} {
-                       return
-               }
-       }
-
-       set failed {}
-       foreach i $to_delete {
-               set b [lindex $i 0]
-               set o [lindex $i 1]
-               if {[catch {git update-ref -d "refs/heads/$b" $o} err]} {
-                       append failed " - $b: $err\n"
-               } else {
-                       set x [lsearch -sorted -exact $all_heads $b]
-                       if {$x >= 0} {
-                               set all_heads [lreplace $all_heads $x $x]
-                       }
-               }
-       }
-
-       if {$failed ne {}} {
-               tk_messageBox \
-                       -icon error \
-                       -type ok \
-                       -title [wm title $w] \
-                       -parent $w \
-                       -message "Failed to delete branches:\n$failed"
-       }
-
-       set all_heads [lsort $all_heads]
-       populate_branch_menu
-       destroy $w
-}
-
-proc do_delete_branch {} {
-       global all_heads tracking_branches current_branch
-       global delete_branch_checktype delete_branch_head delete_branch_trackinghead
-
-       set w .branch_editor
-       toplevel $w
-       wm geometry $w "+[winfo rootx .]+[winfo rooty .]"
-
-       label $w.header -text {Delete Local Branch} \
-               -font font_uibold
-       pack $w.header -side top -fill x
-
-       frame $w.buttons
-       button $w.buttons.create -text Delete \
-               -font font_ui \
-               -command [list do_delete_branch_action $w]
-       pack $w.buttons.create -side right
-       button $w.buttons.cancel -text {Cancel} \
-               -font font_ui \
-               -command [list destroy $w]
-       pack $w.buttons.cancel -side right -padx 5
-       pack $w.buttons -side bottom -fill x -pady 10 -padx 10
-
-       labelframe $w.list \
-               -text {Local Branches} \
-               -font font_ui
-       listbox $w.list.l \
-               -height 10 \
-               -width 70 \
-               -selectmode extended \
-               -yscrollcommand [list $w.list.sby set] \
-               -font font_ui
-       foreach h $all_heads {
-               if {$h ne $current_branch} {
-                       $w.list.l insert end $h
-               }
-       }
-       scrollbar $w.list.sby -command [list $w.list.l yview]
-       pack $w.list.sby -side right -fill y
-       pack $w.list.l -side left -fill both -expand 1
-       pack $w.list -fill both -expand 1 -pady 5 -padx 5
-
-       labelframe $w.validate \
-               -text {Delete Only If} \
-               -font font_ui
-       radiobutton $w.validate.head_r \
-               -text {Merged Into Local Branch:} \
-               -value head \
-               -variable delete_branch_checktype \
-               -font font_ui
-       eval tk_optionMenu $w.validate.head_m delete_branch_head $all_heads
-       grid $w.validate.head_r $w.validate.head_m -sticky w
-       set all_trackings [all_tracking_branches]
-       if {$all_trackings ne {}} {
-               set delete_branch_trackinghead [lindex $all_trackings 0]
-               radiobutton $w.validate.tracking_r \
-                       -text {Merged Into Tracking Branch:} \
-                       -value tracking \
-                       -variable delete_branch_checktype \
-                       -font font_ui
-               eval tk_optionMenu $w.validate.tracking_m \
-                       delete_branch_trackinghead \
-                       $all_trackings
-               grid $w.validate.tracking_r $w.validate.tracking_m -sticky w
-       }
-       radiobutton $w.validate.always_r \
-               -text {Always (Do not perform merge checks)} \
-               -value always \
-               -variable delete_branch_checktype \
-               -font font_ui
-       grid $w.validate.always_r -columnspan 2 -sticky w
-       grid columnconfigure $w.validate 1 -weight 1
-       pack $w.validate -anchor nw -fill x -pady 5 -padx 5
-
-       set delete_branch_head $current_branch
-       set delete_branch_checktype head
-
-       bind $w <Visibility> "grab $w; focus $w"
-       bind $w <Key-Escape> "destroy $w"
-       wm title $w "[appname] ([reponame]): Delete Branch"
-       tkwait window $w
-}
-
-proc switch_branch {new_branch} {
-       global HEAD commit_type current_branch repo_config
-
-       if {![lock_index switch]} return
-
-       # -- Our in memory state should match the repository.
-       #
-       repository_state curType curHEAD curMERGE_HEAD
-       if {[string match amend* $commit_type]
-               && $curType eq {normal}
-               && $curHEAD eq $HEAD} {
-       } elseif {$commit_type ne $curType || $HEAD ne $curHEAD} {
-               info_popup {Last scanned state does not match repository state.
-
-Another Git program has modified this repository
-since the last scan.  A rescan must be performed
-before the current branch can be changed.
-
-The rescan will be automatically started now.
-}
-               unlock_index
-               rescan {set ui_status_value {Ready.}}
-               return
-       }
-
-       # -- Don't do a pointless switch.
-       #
-       if {$current_branch eq $new_branch} {
-               unlock_index
-               return
-       }
-
-       if {$repo_config(gui.trustmtime) eq {true}} {
-               switch_branch_stage2 {} $new_branch
-       } else {
-               set ui_status_value {Refreshing file status...}
-               set cmd [list git update-index]
-               lappend cmd -q
-               lappend cmd --unmerged
-               lappend cmd --ignore-missing
-               lappend cmd --refresh
-               set fd_rf [open "| $cmd" r]
-               fconfigure $fd_rf -blocking 0 -translation binary
-               fileevent $fd_rf readable \
-                       [list switch_branch_stage2 $fd_rf $new_branch]
-       }
-}
-
-proc switch_branch_stage2 {fd_rf new_branch} {
-       global ui_status_value HEAD
-
-       if {$fd_rf ne {}} {
-               read $fd_rf
-               if {![eof $fd_rf]} return
-               close $fd_rf
-       }
-
-       set ui_status_value "Updating working directory to '$new_branch'..."
-       set cmd [list git read-tree]
-       lappend cmd -m
-       lappend cmd -u
-       lappend cmd --exclude-per-directory=.gitignore
-       lappend cmd $HEAD
-       lappend cmd $new_branch
-       set fd_rt [open "| $cmd" r]
-       fconfigure $fd_rt -blocking 0 -translation binary
-       fileevent $fd_rt readable \
-               [list switch_branch_readtree_wait $fd_rt $new_branch]
-}
-
-proc switch_branch_readtree_wait {fd_rt new_branch} {
-       global selected_commit_type commit_type HEAD MERGE_HEAD PARENT
-       global current_branch
-       global ui_comm ui_status_value
-
-       # -- We never get interesting output on stdout; only stderr.
-       #
-       read $fd_rt
-       fconfigure $fd_rt -blocking 1
-       if {![eof $fd_rt]} {
-               fconfigure $fd_rt -blocking 0
-               return
-       }
-
-       # -- The working directory wasn't in sync with the index and
-       #    we'd have to overwrite something to make the switch. A
-       #    merge is required.
-       #
-       if {[catch {close $fd_rt} err]} {
-               regsub {^fatal: } $err {} err
-               warn_popup "File level merge required.
-
-$err
-
-Staying on branch '$current_branch'."
-               set ui_status_value "Aborted checkout of '$new_branch' (file level merging is required)."
-               unlock_index
-               return
-       }
-
-       # -- Update the symbolic ref.  Core git doesn't even check for failure
-       #    here, it Just Works(tm).  If it doesn't we are in some really ugly
-       #    state that is difficult to recover from within git-gui.
-       #
-       if {[catch {git symbolic-ref HEAD "refs/heads/$new_branch"} err]} {
-               error_popup "Failed to set current branch.
-
-This working directory is only partially switched.
-We successfully updated your files, but failed to
-update an internal Git file.
-
-This should not have occurred.  [appname] will now
-close and give up.
-
-$err"
-               do_quit
-               return
-       }
-
-       # -- Update our repository state.  If we were previously in amend mode
-       #    we need to toss the current buffer and do a full rescan to update
-       #    our file lists.  If we weren't in amend mode our file lists are
-       #    accurate and we can avoid the rescan.
-       #
-       unlock_index
-       set selected_commit_type new
-       if {[string match amend* $commit_type]} {
-               $ui_comm delete 0.0 end
-               $ui_comm edit reset
-               $ui_comm edit modified false
-               rescan {set ui_status_value "Checked out branch '$current_branch'."}
-       } else {
-               repository_state commit_type HEAD MERGE_HEAD
-               set PARENT $HEAD
-               set ui_status_value "Checked out branch '$current_branch'."
-       }
-}
-
-######################################################################
-##
-## remote management
-
-proc load_all_remotes {} {
-       global repo_config
-       global all_remotes tracking_branches
-
-       set all_remotes [list]
-       array unset tracking_branches
-
-       set rm_dir [gitdir remotes]
-       if {[file isdirectory $rm_dir]} {
-               set all_remotes [glob \
-                       -types f \
-                       -tails \
-                       -nocomplain \
-                       -directory $rm_dir *]
-
-               foreach name $all_remotes {
-                       catch {
-                               set fd [open [file join $rm_dir $name] r]
-                               while {[gets $fd line] >= 0} {
-                                       if {![regexp {^Pull:[   ]*([^:]+):(.+)$} \
-                                               $line line src dst]} continue
-                                       if {![regexp ^refs/ $dst]} {
-                                               set dst "refs/heads/$dst"
-                                       }
-                                       set tracking_branches($dst) [list $name $src]
-                               }
-                               close $fd
-                       }
-               }
-       }
-
-       foreach line [array names repo_config remote.*.url] {
-               if {![regexp ^remote\.(.*)\.url\$ $line line name]} continue
-               lappend all_remotes $name
-
-               if {[catch {set fl $repo_config(remote.$name.fetch)}]} {
-                       set fl {}
-               }
-               foreach line $fl {
-                       if {![regexp {^([^:]+):(.+)$} $line line src dst]} continue
-                       if {![regexp ^refs/ $dst]} {
-                               set dst "refs/heads/$dst"
-                       }
-                       set tracking_branches($dst) [list $name $src]
-               }
-       }
-
-       set all_remotes [lsort -unique $all_remotes]
-}
-
-proc populate_fetch_menu {} {
-       global all_remotes repo_config
-
-       set m .mbar.fetch
-       foreach r $all_remotes {
-               set enable 0
-               if {![catch {set a $repo_config(remote.$r.url)}]} {
-                       if {![catch {set a $repo_config(remote.$r.fetch)}]} {
-                               set enable 1
-                       }
-               } else {
-                       catch {
-                               set fd [open [gitdir remotes $r] r]
-                               while {[gets $fd n] >= 0} {
-                                       if {[regexp {^Pull:[ \t]*([^:]+):} $n]} {
-                                               set enable 1
-                                               break
-                                       }
-                               }
-                               close $fd
-                       }
-               }
-
-               if {$enable} {
-                       $m add command \
-                               -label "Fetch from $r..." \
-                               -command [list fetch_from $r] \
-                               -font font_ui
-               }
-       }
-}
-
-proc populate_push_menu {} {
-       global all_remotes repo_config
-
-       set m .mbar.push
-       set fast_count 0
-       foreach r $all_remotes {
-               set enable 0
-               if {![catch {set a $repo_config(remote.$r.url)}]} {
-                       if {![catch {set a $repo_config(remote.$r.push)}]} {
-                               set enable 1
-                       }
-               } else {
-                       catch {
-                               set fd [open [gitdir remotes $r] r]
-                               while {[gets $fd n] >= 0} {
-                                       if {[regexp {^Push:[ \t]*([^:]+):} $n]} {
-                                               set enable 1
-                                               break
-                                       }
-                               }
-                               close $fd
-                       }
-               }
-
-               if {$enable} {
-                       if {!$fast_count} {
-                               $m add separator
-                       }
-                       $m add command \
-                               -label "Push to $r..." \
-                               -command [list push_to $r] \
-                               -font font_ui
-                       incr fast_count
-               }
-       }
-}
-
-proc start_push_anywhere_action {w} {
-       global push_urltype push_remote push_url push_thin push_tags
-
-       set r_url {}
-       switch -- $push_urltype {
-       remote {set r_url $push_remote}
-       url {set r_url $push_url}
-       }
-       if {$r_url eq {}} return
-
-       set cmd [list git push]
-       lappend cmd -v
-       if {$push_thin} {
-               lappend cmd --thin
-       }
-       if {$push_tags} {
-               lappend cmd --tags
-       }
-       lappend cmd $r_url
-       set cnt 0
-       foreach i [$w.source.l curselection] {
-               set b [$w.source.l get $i]
-               lappend cmd "refs/heads/$b:refs/heads/$b"
-               incr cnt
-       }
-       if {$cnt == 0} {
-               return
-       } elseif {$cnt == 1} {
-               set unit branch
-       } else {
-               set unit branches
-       }
-
-       set cons [new_console "push $r_url" "Pushing $cnt $unit to $r_url"]
-       console_exec $cons $cmd console_done
-       destroy $w
-}
-
-trace add variable push_remote write \
-       [list radio_selector push_urltype remote]
-
-proc do_push_anywhere {} {
-       global all_heads all_remotes current_branch
-       global push_urltype push_remote push_url push_thin push_tags
-
-       set w .push_setup
-       toplevel $w
-       wm geometry $w "+[winfo rootx .]+[winfo rooty .]"
-
-       label $w.header -text {Push Branches} -font font_uibold
-       pack $w.header -side top -fill x
-
-       frame $w.buttons
-       button $w.buttons.create -text Push \
-               -font font_ui \
-               -command [list start_push_anywhere_action $w]
-       pack $w.buttons.create -side right
-       button $w.buttons.cancel -text {Cancel} \
-               -font font_ui \
-               -command [list destroy $w]
-       pack $w.buttons.cancel -side right -padx 5
-       pack $w.buttons -side bottom -fill x -pady 10 -padx 10
-
-       labelframe $w.source \
-               -text {Source Branches} \
-               -font font_ui
-       listbox $w.source.l \
-               -height 10 \
-               -width 70 \
-               -selectmode extended \
-               -yscrollcommand [list $w.source.sby set] \
-               -font font_ui
-       foreach h $all_heads {
-               $w.source.l insert end $h
-               if {$h eq $current_branch} {
-                       $w.source.l select set end
-               }
-       }
-       scrollbar $w.source.sby -command [list $w.source.l yview]
-       pack $w.source.sby -side right -fill y
-       pack $w.source.l -side left -fill both -expand 1
-       pack $w.source -fill both -expand 1 -pady 5 -padx 5
-
-       labelframe $w.dest \
-               -text {Destination Repository} \
-               -font font_ui
-       if {$all_remotes ne {}} {
-               radiobutton $w.dest.remote_r \
-                       -text {Remote:} \
-                       -value remote \
-                       -variable push_urltype \
-                       -font font_ui
-               eval tk_optionMenu $w.dest.remote_m push_remote $all_remotes
-               grid $w.dest.remote_r $w.dest.remote_m -sticky w
-               if {[lsearch -sorted -exact $all_remotes origin] != -1} {
-                       set push_remote origin
-               } else {
-                       set push_remote [lindex $all_remotes 0]
-               }
-               set push_urltype remote
-       } else {
-               set push_urltype url
-       }
-       radiobutton $w.dest.url_r \
-               -text {Arbitrary URL:} \
-               -value url \
-               -variable push_urltype \
-               -font font_ui
-       entry $w.dest.url_t \
-               -borderwidth 1 \
-               -relief sunken \
-               -width 50 \
-               -textvariable push_url \
-               -font font_ui \
-               -validate key \
-               -validatecommand {
-                       if {%d == 1 && [regexp {\s} %S]} {return 0}
-                       if {%d == 1 && [string length %S] > 0} {
-                               set push_urltype url
-                       }
-                       return 1
-               }
-       grid $w.dest.url_r $w.dest.url_t -sticky we -padx {0 5}
-       grid columnconfigure $w.dest 1 -weight 1
-       pack $w.dest -anchor nw -fill x -pady 5 -padx 5
-
-       labelframe $w.options \
-               -text {Transfer Options} \
-               -font font_ui
-       checkbutton $w.options.thin \
-               -text {Use thin pack (for slow network connections)} \
-               -variable push_thin \
-               -font font_ui
-       grid $w.options.thin -columnspan 2 -sticky w
-       checkbutton $w.options.tags \
-               -text {Include tags} \
-               -variable push_tags \
-               -font font_ui
-       grid $w.options.tags -columnspan 2 -sticky w
-       grid columnconfigure $w.options 1 -weight 1
-       pack $w.options -anchor nw -fill x -pady 5 -padx 5
-
-       set push_url {}
-       set push_thin 0
-       set push_tags 0
-
-       bind $w <Visibility> "grab $w"
-       bind $w <Key-Escape> "destroy $w"
-       wm title $w "[appname] ([reponame]): Push"
-       tkwait window $w
-}
-
-######################################################################
-##
-## merge
-
-proc can_merge {} {
-       global HEAD commit_type file_states
-
-       if {[string match amend* $commit_type]} {
-               info_popup {Cannot merge while amending.
-
-You must finish amending this commit before
-starting any type of merge.
-}
-               return 0
-       }
-
-       if {[committer_ident] eq {}} {return 0}
-       if {![lock_index merge]} {return 0}
-
-       # -- Our in memory state should match the repository.
-       #
-       repository_state curType curHEAD curMERGE_HEAD
-       if {$commit_type ne $curType || $HEAD ne $curHEAD} {
-               info_popup {Last scanned state does not match repository state.
-
-Another Git program has modified this repository
-since the last scan.  A rescan must be performed
-before a merge can be performed.
-
-The rescan will be automatically started now.
-}
-               unlock_index
-               rescan {set ui_status_value {Ready.}}
-               return 0
-       }
-
-       foreach path [array names file_states] {
-               switch -glob -- [lindex $file_states($path) 0] {
-               _O {
-                       continue; # and pray it works!
-               }
-               U? {
-                       error_popup "You are in the middle of a conflicted merge.
-
-File [short_path $path] has merge conflicts.
-
-You must resolve them, add the file, and commit to
-complete the current merge.  Only then can you
-begin another merge.
-"
-                       unlock_index
-                       return 0
-               }
-               ?? {
-                       error_popup "You are in the middle of a change.
-
-File [short_path $path] is modified.
-
-You should complete the current commit before
-starting a merge.  Doing so will help you abort
-a failed merge, should the need arise.
-"
-                       unlock_index
-                       return 0
-               }
-               }
-       }
-
-       return 1
-}
-
-proc visualize_local_merge {w} {
-       set revs {}
-       foreach i [$w.source.l curselection] {
-               lappend revs [$w.source.l get $i]
-       }
-       if {$revs eq {}} return
-       lappend revs --not HEAD
-       do_gitk $revs
-}
-
-proc start_local_merge_action {w} {
-       global HEAD ui_status_value current_branch
-
-       set cmd [list git merge]
-       set names {}
-       set revcnt 0
-       foreach i [$w.source.l curselection] {
-               set b [$w.source.l get $i]
-               lappend cmd $b
-               lappend names $b
-               incr revcnt
-       }
-
-       if {$revcnt == 0} {
-               return
-       } elseif {$revcnt == 1} {
-               set unit branch
-       } elseif {$revcnt <= 15} {
-               set unit branches
-       } else {
-               tk_messageBox \
-                       -icon error \
-                       -type ok \
-                       -title [wm title $w] \
-                       -parent $w \
-                       -message "Too many branches selected.
-
-You have requested to merge $revcnt branches
-in an octopus merge.  This exceeds Git's
-internal limit of 15 branches per merge.
-
-Please select fewer branches.  To merge more
-than 15 branches, merge the branches in batches.
-"
-               return
-       }
-
-       set msg "Merging $current_branch, [join $names {, }]"
-       set ui_status_value "$msg..."
-       set cons [new_console "Merge" $msg]
-       console_exec $cons $cmd [list finish_merge $revcnt]
-       bind $w <Destroy> {}
-       destroy $w
-}
-
-proc finish_merge {revcnt w ok} {
-       console_done $w $ok
-       if {$ok} {
-               set msg {Merge completed successfully.}
-       } else {
-               if {$revcnt != 1} {
-                       info_popup "Octopus merge failed.
-
-Your merge of $revcnt branches has failed.
-
-There are file-level conflicts between the
-branches which must be resolved manually.
-
-The working directory will now be reset.
-
-You can attempt this merge again
-by merging only one branch at a time." $w
-
-                       set fd [open "| git read-tree --reset -u HEAD" r]
-                       fconfigure $fd -blocking 0 -translation binary
-                       fileevent $fd readable [list reset_hard_wait $fd]
-                       set ui_status_value {Aborting... please wait...}
-                       return
-               }
-
-               set msg {Merge failed.  Conflict resolution is required.}
-       }
-       unlock_index
-       rescan [list set ui_status_value $msg]
-}
-
-proc do_local_merge {} {
-       global current_branch
-
-       if {![can_merge]} return
-
-       set w .merge_setup
-       toplevel $w
-       wm geometry $w "+[winfo rootx .]+[winfo rooty .]"
-
-       label $w.header \
-               -text "Merge Into $current_branch" \
-               -font font_uibold
-       pack $w.header -side top -fill x
-
-       frame $w.buttons
-       button $w.buttons.visualize -text Visualize \
-               -font font_ui \
-               -command [list visualize_local_merge $w]
-       pack $w.buttons.visualize -side left
-       button $w.buttons.create -text Merge \
-               -font font_ui \
-               -command [list start_local_merge_action $w]
-       pack $w.buttons.create -side right
-       button $w.buttons.cancel -text {Cancel} \
-               -font font_ui \
-               -command [list destroy $w]
-       pack $w.buttons.cancel -side right -padx 5
-       pack $w.buttons -side bottom -fill x -pady 10 -padx 10
-
-       labelframe $w.source \
-               -text {Source Branches} \
-               -font font_ui
-       listbox $w.source.l \
-               -height 10 \
-               -width 70 \
-               -selectmode extended \
-               -yscrollcommand [list $w.source.sby set] \
-               -font font_ui
-       scrollbar $w.source.sby -command [list $w.source.l yview]
-       pack $w.source.sby -side right -fill y
-       pack $w.source.l -side left -fill both -expand 1
-       pack $w.source -fill both -expand 1 -pady 5 -padx 5
-
-       set cmd [list git for-each-ref]
-       lappend cmd {--format=%(objectname) %(*objectname) %(refname)}
-       lappend cmd refs/heads
-       lappend cmd refs/remotes
-       lappend cmd refs/tags
-       set fr_fd [open "| $cmd" r]
-       fconfigure $fr_fd -translation binary
-       while {[gets $fr_fd line] > 0} {
-               set line [split $line { }]
-               set sha1([lindex $line 0]) [lindex $line 2]
-               set sha1([lindex $line 1]) [lindex $line 2]
-       }
-       close $fr_fd
-
-       set to_show {}
-       set fr_fd [open "| git rev-list --all --not HEAD"]
-       while {[gets $fr_fd line] > 0} {
-               if {[catch {set ref $sha1($line)}]} continue
-               regsub ^refs/(heads|remotes|tags)/ $ref {} ref
-               lappend to_show $ref
-       }
-       close $fr_fd
-
-       foreach ref [lsort -unique $to_show] {
-               $w.source.l insert end $ref
-       }
-
-       bind $w <Visibility> "grab $w"
-       bind $w <Key-Escape> "unlock_index;destroy $w"
-       bind $w <Destroy> unlock_index
-       wm title $w "[appname] ([reponame]): Merge"
-       tkwait window $w
-}
-
-proc do_reset_hard {} {
-       global HEAD commit_type file_states
-
-       if {[string match amend* $commit_type]} {
-               info_popup {Cannot abort while amending.
-
-You must finish amending this commit.
-}
-               return
-       }
-
-       if {![lock_index abort]} return
-
-       if {[string match *merge* $commit_type]} {
-               set op merge
-       } else {
-               set op commit
-       }
-
-       if {[ask_popup "Abort $op?
-
-Aborting the current $op will cause
-*ALL* uncommitted changes to be lost.
-
-Continue with aborting the current $op?"] eq {yes}} {
-               set fd [open "| git read-tree --reset -u HEAD" r]
-               fconfigure $fd -blocking 0 -translation binary
-               fileevent $fd readable [list reset_hard_wait $fd]
-               set ui_status_value {Aborting... please wait...}
-       } else {
-               unlock_index
-       }
-}
-
-proc reset_hard_wait {fd} {
-       global ui_comm
-
-       read $fd
-       if {[eof $fd]} {
-               close $fd
-               unlock_index
-
-               $ui_comm delete 0.0 end
-               $ui_comm edit modified false
-
-               catch {file delete [gitdir MERGE_HEAD]}
-               catch {file delete [gitdir rr-cache MERGE_RR]}
-               catch {file delete [gitdir SQUASH_MSG]}
-               catch {file delete [gitdir MERGE_MSG]}
-               catch {file delete [gitdir GITGUI_MSG]}
-
-               rescan {set ui_status_value {Abort completed.  Ready.}}
-       }
-}
-
-######################################################################
-##
-## browser
-
-set next_browser_id 0
-
-proc new_browser {commit} {
-       global next_browser_id cursor_ptr M1B
-       global browser_commit browser_status browser_stack browser_path browser_busy
-
-       if {[winfo ismapped .]} {
-               set w .browser[incr next_browser_id]
-               set tl $w
-               toplevel $w
-       } else {
-               set w {}
-               set tl .
-       }
-       set w_list $w.list.l
-       set browser_commit($w_list) $commit
-       set browser_status($w_list) {Starting...}
-       set browser_stack($w_list) {}
-       set browser_path($w_list) $browser_commit($w_list):
-       set browser_busy($w_list) 1
-
-       label $w.path -textvariable browser_path($w_list) \
-               -anchor w \
-               -justify left \
-               -borderwidth 1 \
-               -relief sunken \
-               -font font_uibold
-       pack $w.path -anchor w -side top -fill x
-
-       frame $w.list
-       text $w_list -background white -borderwidth 0 \
-               -cursor $cursor_ptr \
-               -state disabled \
-               -wrap none \
-               -height 20 \
-               -width 70 \
-               -xscrollcommand [list $w.list.sbx set] \
-               -yscrollcommand [list $w.list.sby set] \
-               -font font_ui
-       $w_list tag conf in_sel \
-               -background [$w_list cget -foreground] \
-               -foreground [$w_list cget -background]
-       scrollbar $w.list.sbx -orient h -command [list $w_list xview]
-       scrollbar $w.list.sby -orient v -command [list $w_list yview]
-       pack $w.list.sbx -side bottom -fill x
-       pack $w.list.sby -side right -fill y
-       pack $w_list -side left -fill both -expand 1
-       pack $w.list -side top -fill both -expand 1
-
-       label $w.status -textvariable browser_status($w_list) \
-               -anchor w \
-               -justify left \
-               -borderwidth 1 \
-               -relief sunken \
-               -font font_ui
-       pack $w.status -anchor w -side bottom -fill x
-
-       bind $w_list <Button-1>        "browser_click 0 $w_list @%x,%y;break"
-       bind $w_list <Double-Button-1> "browser_click 1 $w_list @%x,%y;break"
-       bind $w_list <$M1B-Up>         "browser_parent $w_list;break"
-       bind $w_list <$M1B-Left>       "browser_parent $w_list;break"
-       bind $w_list <Up>              "browser_move -1 $w_list;break"
-       bind $w_list <Down>            "browser_move 1 $w_list;break"
-       bind $w_list <$M1B-Right>      "browser_enter $w_list;break"
-       bind $w_list <Return>          "browser_enter $w_list;break"
-       bind $w_list <Prior>           "browser_page -1 $w_list;break"
-       bind $w_list <Next>            "browser_page 1 $w_list;break"
-       bind $w_list <Left>            break
-       bind $w_list <Right>           break
-
-       bind $tl <Visibility> "focus $w"
-       bind $tl <Destroy> "
-               array unset browser_buffer $w_list
-               array unset browser_files $w_list
-               array unset browser_status $w_list
-               array unset browser_stack $w_list
-               array unset browser_path $w_list
-               array unset browser_commit $w_list
-               array unset browser_busy $w_list
-       "
-       wm title $tl "[appname] ([reponame]): File Browser"
-       ls_tree $w_list $browser_commit($w_list) {}
-}
-
-proc browser_move {dir w} {
-       global browser_files browser_busy
-
-       if {$browser_busy($w)} return
-       set lno [lindex [split [$w index in_sel.first] .] 0]
-       incr lno $dir
-       if {[lindex $browser_files($w) [expr {$lno - 1}]] ne {}} {
-               $w tag remove in_sel 0.0 end
-               $w tag add in_sel $lno.0 [expr {$lno + 1}].0
-               $w see $lno.0
-       }
-}
-
-proc browser_page {dir w} {
-       global browser_files browser_busy
-
-       if {$browser_busy($w)} return
-       $w yview scroll $dir pages
-       set lno [expr {int(
-                 [lindex [$w yview] 0]
-               * [llength $browser_files($w)]
-               + 1)}]
-       if {[lindex $browser_files($w) [expr {$lno - 1}]] ne {}} {
-               $w tag remove in_sel 0.0 end
-               $w tag add in_sel $lno.0 [expr {$lno + 1}].0
-               $w see $lno.0
-       }
-}
-
-proc browser_parent {w} {
-       global browser_files browser_status browser_path
-       global browser_stack browser_busy
-
-       if {$browser_busy($w)} return
-       set info [lindex $browser_files($w) 0]
-       if {[lindex $info 0] eq {parent}} {
-               set parent [lindex $browser_stack($w) end-1]
-               set browser_stack($w) [lrange $browser_stack($w) 0 end-2]
-               if {$browser_stack($w) eq {}} {
-                       regsub {:.*$} $browser_path($w) {:} browser_path($w)
-               } else {
-                       regsub {/[^/]+$} $browser_path($w) {} browser_path($w)
-               }
-               set browser_status($w) "Loading $browser_path($w)..."
-               ls_tree $w [lindex $parent 0] [lindex $parent 1]
-       }
-}
-
-proc browser_enter {w} {
-       global browser_files browser_status browser_path
-       global browser_commit browser_stack browser_busy
-
-       if {$browser_busy($w)} return
-       set lno [lindex [split [$w index in_sel.first] .] 0]
-       set info [lindex $browser_files($w) [expr {$lno - 1}]]
-       if {$info ne {}} {
-               switch -- [lindex $info 0] {
-               parent {
-                       browser_parent $w
-               }
-               tree {
-                       set name [lindex $info 2]
-                       set escn [escape_path $name]
-                       set browser_status($w) "Loading $escn..."
-                       append browser_path($w) $escn
-                       ls_tree $w [lindex $info 1] $name
-               }
-               blob {
-                       set name [lindex $info 2]
-                       set p {}
-                       foreach n $browser_stack($w) {
-                               append p [lindex $n 1]
-                       }
-                       append p $name
-                       show_blame $browser_commit($w) $p
-               }
-               }
-       }
-}
-
-proc browser_click {was_double_click w pos} {
-       global browser_files browser_busy
-
-       if {$browser_busy($w)} return
-       set lno [lindex [split [$w index $pos] .] 0]
-       focus $w
-
-       if {[lindex $browser_files($w) [expr {$lno - 1}]] ne {}} {
-               $w tag remove in_sel 0.0 end
-               $w tag add in_sel $lno.0 [expr {$lno + 1}].0
-               if {$was_double_click} {
-                       browser_enter $w
-               }
-       }
-}
-
-proc ls_tree {w tree_id name} {
-       global browser_buffer browser_files browser_stack browser_busy
-
-       set browser_buffer($w) {}
-       set browser_files($w) {}
-       set browser_busy($w) 1
-
-       $w conf -state normal
-       $w tag remove in_sel 0.0 end
-       $w delete 0.0 end
-       if {$browser_stack($w) ne {}} {
-               $w image create end \
-                       -align center -padx 5 -pady 1 \
-                       -name icon0 \
-                       -image file_uplevel
-               $w insert end {[Up To Parent]}
-               lappend browser_files($w) parent
-       }
-       lappend browser_stack($w) [list $tree_id $name]
-       $w conf -state disabled
-
-       set cmd [list git ls-tree -z $tree_id]
-       set fd [open "| $cmd" r]
-       fconfigure $fd -blocking 0 -translation binary -encoding binary
-       fileevent $fd readable [list read_ls_tree $fd $w]
-}
-
-proc read_ls_tree {fd w} {
-       global browser_buffer browser_files browser_status browser_busy
-
-       if {![winfo exists $w]} {
-               catch {close $fd}
-               return
-       }
-
-       append browser_buffer($w) [read $fd]
-       set pck [split $browser_buffer($w) "\0"]
-       set browser_buffer($w) [lindex $pck end]
-
-       set n [llength $browser_files($w)]
-       $w conf -state normal
-       foreach p [lrange $pck 0 end-1] {
-               set info [split $p "\t"]
-               set path [lindex $info 1]
-               set info [split [lindex $info 0] { }]
-               set type [lindex $info 1]
-               set object [lindex $info 2]
-
-               switch -- $type {
-               blob {
-                       set image file_mod
-               }
-               tree {
-                       set image file_dir
-                       append path /
-               }
-               default {
-                       set image file_question
-               }
-               }
-
-               if {$n > 0} {$w insert end "\n"}
-               $w image create end \
-                       -align center -padx 5 -pady 1 \
-                       -name icon[incr n] \
-                       -image $image
-               $w insert end [escape_path $path]
-               lappend browser_files($w) [list $type $object $path]
-       }
-       $w conf -state disabled
-
-       if {[eof $fd]} {
-               close $fd
-               set browser_status($w) Ready.
-               set browser_busy($w) 0
-               array unset browser_buffer $w
-               if {$n > 0} {
-                       $w tag add in_sel 1.0 2.0
-                       focus -force $w
-               }
-       }
-}
-
-proc show_blame {commit path} {
-       global next_browser_id blame_status blame_data
-
-       if {[winfo ismapped .]} {
-               set w .browser[incr next_browser_id]
-               set tl $w
-               toplevel $w
-       } else {
-               set w {}
-               set tl .
-       }
-       set blame_status($w) {Loading current file content...}
-
-       label $w.path -text "$commit:$path" \
-               -anchor w \
-               -justify left \
-               -borderwidth 1 \
-               -relief sunken \
-               -font font_uibold
-       pack $w.path -side top -fill x
-
-       frame $w.out
-       text $w.out.loaded_t \
-               -background white -borderwidth 0 \
-               -state disabled \
-               -wrap none \
-               -height 40 \
-               -width 1 \
-               -font font_diff
-       $w.out.loaded_t tag conf annotated -background grey
-
-       text $w.out.linenumber_t \
-               -background white -borderwidth 0 \
-               -state disabled \
-               -wrap none \
-               -height 40 \
-               -width 5 \
-               -font font_diff
-       $w.out.linenumber_t tag conf linenumber -justify right
-
-       text $w.out.file_t \
-               -background white -borderwidth 0 \
-               -state disabled \
-               -wrap none \
-               -height 40 \
-               -width 80 \
-               -xscrollcommand [list $w.out.sbx set] \
-               -font font_diff
-
-       scrollbar $w.out.sbx -orient h -command [list $w.out.file_t xview]
-       scrollbar $w.out.sby -orient v \
-               -command [list scrollbar2many [list \
-               $w.out.loaded_t \
-               $w.out.linenumber_t \
-               $w.out.file_t \
-               ] yview]
-       grid \
-               $w.out.linenumber_t \
-               $w.out.loaded_t \
-               $w.out.file_t \
-               $w.out.sby \
-               -sticky nsew
-       grid conf $w.out.sbx -column 2 -sticky we
-       grid columnconfigure $w.out 2 -weight 1
-       grid rowconfigure $w.out 0 -weight 1
-       pack $w.out -fill both -expand 1
-
-       label $w.status -textvariable blame_status($w) \
-               -anchor w \
-               -justify left \
-               -borderwidth 1 \
-               -relief sunken \
-               -font font_ui
-       pack $w.status -side bottom -fill x
-
-       frame $w.cm
-       text $w.cm.t \
-               -background white -borderwidth 0 \
-               -state disabled \
-               -wrap none \
-               -height 10 \
-               -width 80 \
-               -xscrollcommand [list $w.cm.sbx set] \
-               -yscrollcommand [list $w.cm.sby set] \
-               -font font_diff
-       scrollbar $w.cm.sbx -orient h -command [list $w.cm.t xview]
-       scrollbar $w.cm.sby -orient v -command [list $w.cm.t yview]
-       pack $w.cm.sby -side right -fill y
-       pack $w.cm.sbx -side bottom -fill x
-       pack $w.cm.t -expand 1 -fill both
-       pack $w.cm -side bottom -fill x
-
-       menu $w.ctxm -tearoff 0
-       $w.ctxm add command -label "Copy Commit" \
-               -font font_ui \
-               -command "blame_copycommit $w \$cursorW @\$cursorX,\$cursorY"
-
-       foreach i [list \
-               $w.out.loaded_t \
-               $w.out.linenumber_t \
-               $w.out.file_t] {
-               $i tag conf in_sel \
-                       -background [$i cget -foreground] \
-                       -foreground [$i cget -background]
-               $i conf -yscrollcommand \
-                       [list many2scrollbar [list \
-                       $w.out.loaded_t \
-                       $w.out.linenumber_t \
-                       $w.out.file_t \
-                       ] yview $w.out.sby]
-               bind $i <Button-1> "
-                       blame_click {$w} \\
-                               $w.cm.t \\
-                               $w.out.linenumber_t \\
-                               $w.out.file_t \\
-                               $i @%x,%y
-                       focus $i
-               "
-               bind_button3 $i "
-                       set cursorX %x
-                       set cursorY %y
-                       set cursorW %W
-                       tk_popup $w.ctxm %X %Y
-               "
-       }
-
-       bind $w.cm.t <Button-1> "focus $w.cm.t"
-       bind $tl <Visibility> "focus $tl"
-       bind $tl <Destroy> "
-               array unset blame_status {$w}
-               array unset blame_data $w,*
-       "
-       wm title $tl "[appname] ([reponame]): File Viewer"
-
-       set blame_data($w,commit_count) 0
-       set blame_data($w,commit_list) {}
-       set blame_data($w,total_lines) 0
-       set blame_data($w,blame_lines) 0
-       set blame_data($w,highlight_commit) {}
-       set blame_data($w,highlight_line) -1
-
-       set cmd [list git cat-file blob "$commit:$path"]
-       set fd [open "| $cmd" r]
-       fconfigure $fd -blocking 0 -translation lf -encoding binary
-       fileevent $fd readable [list read_blame_catfile \
-               $fd $w $commit $path \
-               $w.cm.t $w.out.loaded_t $w.out.linenumber_t $w.out.file_t]
-}
-
-proc read_blame_catfile {fd w commit path w_cmit w_load w_line w_file} {
-       global blame_status blame_data
-
-       if {![winfo exists $w_file]} {
-               catch {close $fd}
-               return
-       }
-
-       set n $blame_data($w,total_lines)
-       $w_load conf -state normal
-       $w_line conf -state normal
-       $w_file conf -state normal
-       while {[gets $fd line] >= 0} {
-               regsub "\r\$" $line {} line
-               incr n
-               $w_load insert end "\n"
-               $w_line insert end "$n\n" linenumber
-               $w_file insert end "$line\n"
-       }
-       $w_load conf -state disabled
-       $w_line conf -state disabled
-       $w_file conf -state disabled
-       set blame_data($w,total_lines) $n
-
-       if {[eof $fd]} {
-               close $fd
-               blame_incremental_status $w
-               set cmd [list git blame -M -C --incremental]
-               lappend cmd $commit -- $path
-               set fd [open "| $cmd" r]
-               fconfigure $fd -blocking 0 -translation lf -encoding binary
-               fileevent $fd readable [list read_blame_incremental $fd $w \
-                       $w_load $w_cmit $w_line $w_file]
-       }
-}
-
-proc read_blame_incremental {fd w w_load w_cmit w_line w_file} {
-       global blame_status blame_data
-
-       if {![winfo exists $w_file]} {
-               catch {close $fd}
-               return
-       }
-
-       while {[gets $fd line] >= 0} {
-               if {[regexp {^([a-z0-9]{40}) (\d+) (\d+) (\d+)$} $line line \
-                       cmit original_line final_line line_count]} {
-                       set blame_data($w,commit) $cmit
-                       set blame_data($w,original_line) $original_line
-                       set blame_data($w,final_line) $final_line
-                       set blame_data($w,line_count) $line_count
-
-                       if {[catch {set g $blame_data($w,$cmit,order)}]} {
-                               $w_line tag conf g$cmit
-                               $w_file tag conf g$cmit
-                               $w_line tag raise in_sel
-                               $w_file tag raise in_sel
-                               $w_file tag raise sel
-                               set blame_data($w,$cmit,order) $blame_data($w,commit_count)
-                               incr blame_data($w,commit_count)
-                               lappend blame_data($w,commit_list) $cmit
-                       }
-               } elseif {[string match {filename *} $line]} {
-                       set file [string range $line 9 end]
-                       set n $blame_data($w,line_count)
-                       set lno $blame_data($w,final_line)
-                       set cmit $blame_data($w,commit)
-
-                       while {$n > 0} {
-                               if {[catch {set g g$blame_data($w,line$lno,commit)}]} {
-                                       $w_load tag add annotated $lno.0 "$lno.0 lineend + 1c"
-                               } else {
-                                       $w_line tag remove g$g $lno.0 "$lno.0 lineend + 1c"
-                                       $w_file tag remove g$g $lno.0 "$lno.0 lineend + 1c"
-                               }
-
-                               set blame_data($w,line$lno,commit) $cmit
-                               set blame_data($w,line$lno,file) $file
-                               $w_line tag add g$cmit $lno.0 "$lno.0 lineend + 1c"
-                               $w_file tag add g$cmit $lno.0 "$lno.0 lineend + 1c"
-
-                               if {$blame_data($w,highlight_line) == -1} {
-                                       if {[lindex [$w_file yview] 0] == 0} {
-                                               $w_file see $lno.0
-                                               blame_showcommit $w $w_cmit $w_line $w_file $lno
-                                       }
-                               } elseif {$blame_data($w,highlight_line) == $lno} {
-                                       blame_showcommit $w $w_cmit $w_line $w_file $lno
-                               }
-
-                               incr n -1
-                               incr lno
-                               incr blame_data($w,blame_lines)
-                       }
-
-                       set hc $blame_data($w,highlight_commit)
-                       if {$hc ne {}
-                               && [expr {$blame_data($w,$hc,order) + 1}]
-                                       == $blame_data($w,$cmit,order)} {
-                               blame_showcommit $w $w_cmit $w_line $w_file \
-                                       $blame_data($w,highlight_line)
-                       }
-               } elseif {[regexp {^([a-z-]+) (.*)$} $line line header data]} {
-                       set blame_data($w,$blame_data($w,commit),$header) $data
-               }
-       }
-
-       if {[eof $fd]} {
-               close $fd
-               set blame_status($w) {Annotation complete.}
-       } else {
-               blame_incremental_status $w
-       }
-}
-
-proc blame_incremental_status {w} {
-       global blame_status blame_data
-
-       set have  $blame_data($w,blame_lines)
-       set total $blame_data($w,total_lines)
-       set pdone 0
-       if {$total} {set pdone [expr {100 * $have / $total}]}
-
-       set blame_status($w) [format \
-               "Loading annotations... %i of %i lines annotated (%2i%%)" \
-               $have $total $pdone]
-}
-
-proc blame_click {w w_cmit w_line w_file cur_w pos} {
-       set lno [lindex [split [$cur_w index $pos] .] 0]
-       if {$lno eq {}} return
-
-       $w_line tag remove in_sel 0.0 end
-       $w_file tag remove in_sel 0.0 end
-       $w_line tag add in_sel $lno.0 "$lno.0 + 1 line"
-       $w_file tag add in_sel $lno.0 "$lno.0 + 1 line"
-
-       blame_showcommit $w $w_cmit $w_line $w_file $lno
-}
-
-set blame_colors {
-       #ff4040
-       #ff40ff
-       #4040ff
-}
+               set icon_name [lindex $s 1]
 
-proc blame_showcommit {w w_cmit w_line w_file lno} {
-       global blame_colors blame_data repo_config
-
-       set cmit $blame_data($w,highlight_commit)
-       if {$cmit ne {}} {
-               set idx $blame_data($w,$cmit,order)
-               set i 0
-               foreach c $blame_colors {
-                       set h [lindex $blame_data($w,commit_list) [expr {$idx - 1 + $i}]]
-                       $w_line tag conf g$h -background white
-                       $w_file tag conf g$h -background white
-                       incr i
+               set s [string index $m 0]
+               if {$s ne {U} && $s ne {_}} {
+                       display_all_files_helper $ui_index $path \
+                               $icon_name $s
                }
-       }
 
-       $w_cmit conf -state normal
-       $w_cmit delete 0.0 end
-       if {[catch {set cmit $blame_data($w,line$lno,commit)}]} {
-               set cmit {}
-               $w_cmit insert end "Loading annotation..."
-       } else {
-               set idx $blame_data($w,$cmit,order)
-               set i 0
-               foreach c $blame_colors {
-                       set h [lindex $blame_data($w,commit_list) [expr {$idx - 1 + $i}]]
-                       $w_line tag conf g$h -background $c
-                       $w_file tag conf g$h -background $c
-                       incr i
+               if {[string index $m 0] eq {U}} {
+                       set s U
+               } else {
+                       set s [string index $m 1]
                }
-
-               if {[catch {set msg $blame_data($w,$cmit,message)}]} {
-                       set msg {}
-                       catch {
-                               set fd [open "| git cat-file commit $cmit" r]
-                               fconfigure $fd -encoding binary -translation lf
-                               if {[catch {set enc $repo_config(i18n.commitencoding)}]} {
-                                       set enc utf-8
-                               }
-                               while {[gets $fd line] > 0} {
-                                       if {[string match {encoding *} $line]} {
-                                               set enc [string tolower [string range $line 9 end]]
-                                       }
-                               }
-                               fconfigure $fd -encoding $enc
-                               set msg [string trim [read $fd]]
-                               close $fd
-                       }
-                       set blame_data($w,$cmit,message) $msg
+               if {$s ne {_}} {
+                       display_all_files_helper $ui_workdir $path \
+                               $icon_name $s
                }
-
-               set author_name {}
-               set author_email {}
-               set author_time {}
-               catch {set author_name $blame_data($w,$cmit,author)}
-               catch {set author_email $blame_data($w,$cmit,author-mail)}
-               catch {set author_time [clock format $blame_data($w,$cmit,author-time)]}
-
-               set committer_name {}
-               set committer_email {}
-               set committer_time {}
-               catch {set committer_name $blame_data($w,$cmit,committer)}
-               catch {set committer_email $blame_data($w,$cmit,committer-mail)}
-               catch {set committer_time [clock format $blame_data($w,$cmit,committer-time)]}
-
-               $w_cmit insert end "commit $cmit\n"
-               $w_cmit insert end "Author: $author_name $author_email $author_time\n"
-               $w_cmit insert end "Committer: $committer_name $committer_email $committer_time\n"
-               $w_cmit insert end "Original File: [escape_path $blame_data($w,line$lno,file)]\n"
-               $w_cmit insert end "\n"
-               $w_cmit insert end $msg
        }
-       $w_cmit conf -state disabled
-
-       set blame_data($w,highlight_line) $lno
-       set blame_data($w,highlight_commit) $cmit
-}
 
-proc blame_copycommit {w i pos} {
-       global blame_data
-       set lno [lindex [split [$i index $pos] .] 0]
-       if {![catch {set commit $blame_data($w,line$lno,commit)}]} {
-               clipboard clear
-               clipboard append \
-                       -format STRING \
-                       -type STRING \
-                       -- $commit
-       }
+       $ui_index conf -state disabled
+       $ui_workdir conf -state disabled
 }
 
 ######################################################################
@@ -3895,233 +1006,6 @@ proc incr_font_size {font {amt 1}} {
        font configure ${font}bold -size $sz
 }
 
-proc hook_failed_popup {hook msg} {
-       set w .hookfail
-       toplevel $w
-
-       frame $w.m
-       label $w.m.l1 -text "$hook hook failed:" \
-               -anchor w \
-               -justify left \
-               -font font_uibold
-       text $w.m.t \
-               -background white -borderwidth 1 \
-               -relief sunken \
-               -width 80 -height 10 \
-               -font font_diff \
-               -yscrollcommand [list $w.m.sby set]
-       label $w.m.l2 \
-               -text {You must correct the above errors before committing.} \
-               -anchor w \
-               -justify left \
-               -font font_uibold
-       scrollbar $w.m.sby -command [list $w.m.t yview]
-       pack $w.m.l1 -side top -fill x
-       pack $w.m.l2 -side bottom -fill x
-       pack $w.m.sby -side right -fill y
-       pack $w.m.t -side left -fill both -expand 1
-       pack $w.m -side top -fill both -expand 1 -padx 5 -pady 10
-
-       $w.m.t insert 1.0 $msg
-       $w.m.t conf -state disabled
-
-       button $w.ok -text OK \
-               -width 15 \
-               -font font_ui \
-               -command "destroy $w"
-       pack $w.ok -side bottom -anchor e -pady 10 -padx 10
-
-       bind $w <Visibility> "grab $w; focus $w"
-       bind $w <Key-Return> "destroy $w"
-       wm title $w "[appname] ([reponame]): error"
-       tkwait window $w
-}
-
-set next_console_id 0
-
-proc new_console {short_title long_title} {
-       global next_console_id console_data
-       set w .console[incr next_console_id]
-       set console_data($w) [list $short_title $long_title]
-       return [console_init $w]
-}
-
-proc console_init {w} {
-       global console_cr console_data M1B
-
-       set console_cr($w) 1.0
-       toplevel $w
-       frame $w.m
-       label $w.m.l1 -text "[lindex $console_data($w) 1]:" \
-               -anchor w \
-               -justify left \
-               -font font_uibold
-       text $w.m.t \
-               -background white -borderwidth 1 \
-               -relief sunken \
-               -width 80 -height 10 \
-               -font font_diff \
-               -state disabled \
-               -yscrollcommand [list $w.m.sby set]
-       label $w.m.s -text {Working... please wait...} \
-               -anchor w \
-               -justify left \
-               -font font_uibold
-       scrollbar $w.m.sby -command [list $w.m.t yview]
-       pack $w.m.l1 -side top -fill x
-       pack $w.m.s -side bottom -fill x
-       pack $w.m.sby -side right -fill y
-       pack $w.m.t -side left -fill both -expand 1
-       pack $w.m -side top -fill both -expand 1 -padx 5 -pady 10
-
-       menu $w.ctxm -tearoff 0
-       $w.ctxm add command -label "Copy" \
-               -font font_ui \
-               -command "tk_textCopy $w.m.t"
-       $w.ctxm add command -label "Select All" \
-               -font font_ui \
-               -command "focus $w.m.t;$w.m.t tag add sel 0.0 end"
-       $w.ctxm add command -label "Copy All" \
-               -font font_ui \
-               -command "
-                       $w.m.t tag add sel 0.0 end
-                       tk_textCopy $w.m.t
-                       $w.m.t tag remove sel 0.0 end
-               "
-
-       button $w.ok -text {Close} \
-               -font font_ui \
-               -state disabled \
-               -command "destroy $w"
-       pack $w.ok -side bottom -anchor e -pady 10 -padx 10
-
-       bind_button3 $w.m.t "tk_popup $w.ctxm %X %Y"
-       bind $w.m.t <$M1B-Key-a> "$w.m.t tag add sel 0.0 end;break"
-       bind $w.m.t <$M1B-Key-A> "$w.m.t tag add sel 0.0 end;break"
-       bind $w <Visibility> "focus $w"
-       wm title $w "[appname] ([reponame]): [lindex $console_data($w) 0]"
-       return $w
-}
-
-proc console_exec {w cmd after} {
-       # -- Cygwin's Tcl tosses the enviroment when we exec our child.
-       #    But most users need that so we have to relogin. :-(
-       #
-       if {[is_Cygwin]} {
-               set cmd [list sh --login -c "cd \"[pwd]\" && [join $cmd { }]"]
-       }
-
-       # -- Tcl won't let us redirect both stdout and stderr to
-       #    the same pipe.  So pass it through cat...
-       #
-       set cmd [concat | $cmd |& cat]
-
-       set fd_f [open $cmd r]
-       fconfigure $fd_f -blocking 0 -translation binary
-       fileevent $fd_f readable [list console_read $w $fd_f $after]
-}
-
-proc console_read {w fd after} {
-       global console_cr
-
-       set buf [read $fd]
-       if {$buf ne {}} {
-               if {![winfo exists $w]} {console_init $w}
-               $w.m.t conf -state normal
-               set c 0
-               set n [string length $buf]
-               while {$c < $n} {
-                       set cr [string first "\r" $buf $c]
-                       set lf [string first "\n" $buf $c]
-                       if {$cr < 0} {set cr [expr {$n + 1}]}
-                       if {$lf < 0} {set lf [expr {$n + 1}]}
-
-                       if {$lf < $cr} {
-                               $w.m.t insert end [string range $buf $c $lf]
-                               set console_cr($w) [$w.m.t index {end -1c}]
-                               set c $lf
-                               incr c
-                       } else {
-                               $w.m.t delete $console_cr($w) end
-                               $w.m.t insert end "\n"
-                               $w.m.t insert end [string range $buf $c $cr]
-                               set c $cr
-                               incr c
-                       }
-               }
-               $w.m.t conf -state disabled
-               $w.m.t see end
-       }
-
-       fconfigure $fd -blocking 1
-       if {[eof $fd]} {
-               if {[catch {close $fd}]} {
-                       set ok 0
-               } else {
-                       set ok 1
-               }
-               uplevel #0 $after $w $ok
-               return
-       }
-       fconfigure $fd -blocking 0
-}
-
-proc console_chain {cmdlist w {ok 1}} {
-       if {$ok} {
-               if {[llength $cmdlist] == 0} {
-                       console_done $w $ok
-                       return
-               }
-
-               set cmd [lindex $cmdlist 0]
-               set cmdlist [lrange $cmdlist 1 end]
-
-               if {[lindex $cmd 0] eq {console_exec}} {
-                       console_exec $w \
-                               [lindex $cmd 1] \
-                               [list console_chain $cmdlist]
-               } else {
-                       uplevel #0 $cmd $cmdlist $w $ok
-               }
-       } else {
-               console_done $w $ok
-       }
-}
-
-proc console_done {args} {
-       global console_cr console_data
-
-       switch -- [llength $args] {
-       2 {
-               set w [lindex $args 0]
-               set ok [lindex $args 1]
-       }
-       3 {
-               set w [lindex $args 1]
-               set ok [lindex $args 2]
-       }
-       default {
-               error "wrong number of args: console_done ?ignored? w ok"
-       }
-       }
-
-       if {$ok} {
-               if {[winfo exists $w]} {
-                       $w.m.s conf -background green -text {Success}
-                       $w.ok conf -state normal
-               }
-       } else {
-               if {![winfo exists $w]} {
-                       console_init $w
-               }
-               $w.m.s conf -background red -text {Error: Command Failed}
-               $w.ok conf -state normal
-       }
-
-       array unset console_cr $w
-       array unset console_data $w
-}
-
 ######################################################################
 ##
 ## ui commands
@@ -4153,94 +1037,6 @@ proc do_gitk {revs} {
        }
 }
 
-proc do_stats {} {
-       set fd [open "| git count-objects -v" r]
-       while {[gets $fd line] > 0} {
-               if {[regexp {^([^:]+): (\d+)$} $line _ name value]} {
-                       set stats($name) $value
-               }
-       }
-       close $fd
-
-       set packed_sz 0
-       foreach p [glob -directory [gitdir objects pack] \
-               -type f \
-               -nocomplain -- *] {
-               incr packed_sz [file size $p]
-       }
-       if {$packed_sz > 0} {
-               set stats(size-pack) [expr {$packed_sz / 1024}]
-       }
-
-       set w .stats_view
-       toplevel $w
-       wm geometry $w "+[winfo rootx .]+[winfo rooty .]"
-
-       label $w.header -text {Database Statistics} \
-               -font font_uibold
-       pack $w.header -side top -fill x
-
-       frame $w.buttons -border 1
-       button $w.buttons.close -text Close \
-               -font font_ui \
-               -command [list destroy $w]
-       button $w.buttons.gc -text {Compress Database} \
-               -font font_ui \
-               -command "destroy $w;do_gc"
-       pack $w.buttons.close -side right
-       pack $w.buttons.gc -side left
-       pack $w.buttons -side bottom -fill x -pady 10 -padx 10
-
-       frame $w.stat -borderwidth 1 -relief solid
-       foreach s {
-               {count           {Number of loose objects}}
-               {size            {Disk space used by loose objects} { KiB}}
-               {in-pack         {Number of packed objects}}
-               {packs           {Number of packs}}
-               {size-pack       {Disk space used by packed objects} { KiB}}
-               {prune-packable  {Packed objects waiting for pruning}}
-               {garbage         {Garbage files}}
-               } {
-               set name [lindex $s 0]
-               set label [lindex $s 1]
-               if {[catch {set value $stats($name)}]} continue
-               if {[llength $s] > 2} {
-                       set value "$value[lindex $s 2]"
-               }
-
-               label $w.stat.l_$name -text "$label:" -anchor w -font font_ui
-               label $w.stat.v_$name -text $value -anchor w -font font_ui
-               grid $w.stat.l_$name $w.stat.v_$name -sticky we -padx {0 5}
-       }
-       pack $w.stat -pady 10 -padx 10
-
-       bind $w <Visibility> "grab $w; focus $w"
-       bind $w <Key-Escape> [list destroy $w]
-       bind $w <Key-Return> [list destroy $w]
-       wm title $w "[appname] ([reponame]): Database Statistics"
-       tkwait window $w
-}
-
-proc do_gc {} {
-       set w [new_console {gc} {Compressing the object database}]
-       console_chain {
-               {console_exec {git pack-refs --prune}}
-               {console_exec {git reflog expire --all}}
-               {console_exec {git repack -a -d -l}}
-               {console_exec {git rerere gc}}
-       } $w
-}
-
-proc do_fsck_objects {} {
-       set w [new_console {fsck-objects} \
-               {Verifying the object database with fsck-objects}]
-       set cmd [list git fsck-objects]
-       lappend cmd --full
-       lappend cmd --cache
-       lappend cmd --strict
-       console_exec $w $cmd console_done
-}
-
 set is_quitting 0
 
 proc do_quit {} {
@@ -4288,598 +1084,10 @@ proc do_rescan {} {
        rescan {set ui_status_value {Ready.}}
 }
 
-proc unstage_helper {txt paths} {
-       global file_states current_diff_path
-
-       if {![lock_index begin-update]} return
-
-       set pathList [list]
-       set after {}
-       foreach path $paths {
-               switch -glob -- [lindex $file_states($path) 0] {
-               A? -
-               M? -
-               D? {
-                       lappend pathList $path
-                       if {$path eq $current_diff_path} {
-                               set after {reshow_diff;}
-                       }
-               }
-               }
-       }
-       if {$pathList eq {}} {
-               unlock_index
-       } else {
-               update_indexinfo \
-                       $txt \
-                       $pathList \
-                       [concat $after {set ui_status_value {Ready.}}]
-       }
-}
-
-proc do_unstage_selection {} {
-       global current_diff_path selected_paths
-
-       if {[array size selected_paths] > 0} {
-               unstage_helper \
-                       {Unstaging selected files from commit} \
-                       [array names selected_paths]
-       } elseif {$current_diff_path ne {}} {
-               unstage_helper \
-                       "Unstaging [short_path $current_diff_path] from commit" \
-                       [list $current_diff_path]
-       }
-}
-
-proc add_helper {txt paths} {
-       global file_states current_diff_path
-
-       if {![lock_index begin-update]} return
-
-       set pathList [list]
-       set after {}
-       foreach path $paths {
-               switch -glob -- [lindex $file_states($path) 0] {
-               _O -
-               ?M -
-               ?D -
-               U? {
-                       lappend pathList $path
-                       if {$path eq $current_diff_path} {
-                               set after {reshow_diff;}
-                       }
-               }
-               }
-       }
-       if {$pathList eq {}} {
-               unlock_index
-       } else {
-               update_index \
-                       $txt \
-                       $pathList \
-                       [concat $after {set ui_status_value {Ready to commit.}}]
-       }
-}
-
-proc do_add_selection {} {
-       global current_diff_path selected_paths
-
-       if {[array size selected_paths] > 0} {
-               add_helper \
-                       {Adding selected files} \
-                       [array names selected_paths]
-       } elseif {$current_diff_path ne {}} {
-               add_helper \
-                       "Adding [short_path $current_diff_path]" \
-                       [list $current_diff_path]
-       }
-}
-
-proc do_add_all {} {
-       global file_states
-
-       set paths [list]
-       foreach path [array names file_states] {
-               switch -glob -- [lindex $file_states($path) 0] {
-               U? {continue}
-               ?M -
-               ?D {lappend paths $path}
-               }
-       }
-       add_helper {Adding all changed files} $paths
-}
-
-proc revert_helper {txt paths} {
-       global file_states current_diff_path
-
-       if {![lock_index begin-update]} return
-
-       set pathList [list]
-       set after {}
-       foreach path $paths {
-               switch -glob -- [lindex $file_states($path) 0] {
-               U? {continue}
-               ?M -
-               ?D {
-                       lappend pathList $path
-                       if {$path eq $current_diff_path} {
-                               set after {reshow_diff;}
-                       }
-               }
-               }
-       }
-
-       set n [llength $pathList]
-       if {$n == 0} {
-               unlock_index
-               return
-       } elseif {$n == 1} {
-               set s "[short_path [lindex $pathList]]"
-       } else {
-               set s "these $n files"
-       }
-
-       set reply [tk_dialog \
-               .confirm_revert \
-               "[appname] ([reponame])" \
-               "Revert changes in $s?
-
-Any unadded changes will be permanently lost by the revert." \
-               question \
-               1 \
-               {Do Nothing} \
-               {Revert Changes} \
-               ]
-       if {$reply == 1} {
-               checkout_index \
-                       $txt \
-                       $pathList \
-                       [concat $after {set ui_status_value {Ready.}}]
-       } else {
-               unlock_index
-       }
-}
-
-proc do_revert_selection {} {
-       global current_diff_path selected_paths
-
-       if {[array size selected_paths] > 0} {
-               revert_helper \
-                       {Reverting selected files} \
-                       [array names selected_paths]
-       } elseif {$current_diff_path ne {}} {
-               revert_helper \
-                       "Reverting [short_path $current_diff_path]" \
-                       [list $current_diff_path]
-       }
-}
-
-proc do_signoff {} {
-       global ui_comm
-
-       set me [committer_ident]
-       if {$me eq {}} return
-
-       set sob "Signed-off-by: $me"
-       set last [$ui_comm get {end -1c linestart} {end -1c}]
-       if {$last ne $sob} {
-               $ui_comm edit separator
-               if {$last ne {}
-                       && ![regexp {^[A-Z][A-Za-z]*-[A-Za-z-]+: *} $last]} {
-                       $ui_comm insert end "\n"
-               }
-               $ui_comm insert end "\n$sob"
-               $ui_comm edit separator
-               $ui_comm see end
-       }
-}
-
-proc do_select_commit_type {} {
-       global commit_type selected_commit_type
-
-       if {$selected_commit_type eq {new}
-               && [string match amend* $commit_type]} {
-               create_new_commit
-       } elseif {$selected_commit_type eq {amend}
-               && ![string match amend* $commit_type]} {
-               load_last_commit
-
-               # The amend request was rejected...
-               #
-               if {![string match amend* $commit_type]} {
-                       set selected_commit_type new
-               }
-       }
-}
-
 proc do_commit {} {
        commit_tree
 }
 
-proc do_about {} {
-       global appvers copyright
-       global tcl_patchLevel tk_patchLevel
-
-       set w .about_dialog
-       toplevel $w
-       wm geometry $w "+[winfo rootx .]+[winfo rooty .]"
-
-       label $w.header -text "About [appname]" \
-               -font font_uibold
-       pack $w.header -side top -fill x
-
-       frame $w.buttons
-       button $w.buttons.close -text {Close} \
-               -font font_ui \
-               -command [list destroy $w]
-       pack $w.buttons.close -side right
-       pack $w.buttons -side bottom -fill x -pady 10 -padx 10
-
-       label $w.desc \
-               -text "git-gui - a graphical user interface for Git.
-$copyright" \
-               -padx 5 -pady 5 \
-               -justify left \
-               -anchor w \
-               -borderwidth 1 \
-               -relief solid \
-               -font font_ui
-       pack $w.desc -side top -fill x -padx 5 -pady 5
-
-       set v {}
-       append v "git-gui version $appvers\n"
-       append v "[git version]\n"
-       append v "\n"
-       if {$tcl_patchLevel eq $tk_patchLevel} {
-               append v "Tcl/Tk version $tcl_patchLevel"
-       } else {
-               append v "Tcl version $tcl_patchLevel"
-               append v ", Tk version $tk_patchLevel"
-       }
-
-       label $w.vers \
-               -text $v \
-               -padx 5 -pady 5 \
-               -justify left \
-               -anchor w \
-               -borderwidth 1 \
-               -relief solid \
-               -font font_ui
-       pack $w.vers -side top -fill x -padx 5 -pady 5
-
-       menu $w.ctxm -tearoff 0
-       $w.ctxm add command \
-               -label {Copy} \
-               -font font_ui \
-               -command "
-               clipboard clear
-               clipboard append -format STRING -type STRING -- \[$w.vers cget -text\]
-       "
-
-       bind $w <Visibility> "grab $w; focus $w"
-       bind $w <Key-Escape> "destroy $w"
-       bind_button3 $w.vers "tk_popup $w.ctxm %X %Y; grab $w; focus $w"
-       wm title $w "About [appname]"
-       tkwait window $w
-}
-
-proc do_options {} {
-       global repo_config global_config font_descs
-       global repo_config_new global_config_new
-
-       array unset repo_config_new
-       array unset global_config_new
-       foreach name [array names repo_config] {
-               set repo_config_new($name) $repo_config($name)
-       }
-       load_config 1
-       foreach name [array names repo_config] {
-               switch -- $name {
-               gui.diffcontext {continue}
-               }
-               set repo_config_new($name) $repo_config($name)
-       }
-       foreach name [array names global_config] {
-               set global_config_new($name) $global_config($name)
-       }
-
-       set w .options_editor
-       toplevel $w
-       wm geometry $w "+[winfo rootx .]+[winfo rooty .]"
-
-       label $w.header -text "Options" \
-               -font font_uibold
-       pack $w.header -side top -fill x
-
-       frame $w.buttons
-       button $w.buttons.restore -text {Restore Defaults} \
-               -font font_ui \
-               -command do_restore_defaults
-       pack $w.buttons.restore -side left
-       button $w.buttons.save -text Save \
-               -font font_ui \
-               -command [list do_save_config $w]
-       pack $w.buttons.save -side right
-       button $w.buttons.cancel -text {Cancel} \
-               -font font_ui \
-               -command [list destroy $w]
-       pack $w.buttons.cancel -side right -padx 5
-       pack $w.buttons -side bottom -fill x -pady 10 -padx 10
-
-       labelframe $w.repo -text "[reponame] Repository" \
-               -font font_ui
-       labelframe $w.global -text {Global (All Repositories)} \
-               -font font_ui
-       pack $w.repo -side left -fill both -expand 1 -pady 5 -padx 5
-       pack $w.global -side right -fill both -expand 1 -pady 5 -padx 5
-
-       set optid 0
-       foreach option {
-               {t user.name {User Name}}
-               {t user.email {Email Address}}
-
-               {b merge.summary {Summarize Merge Commits}}
-               {i-1..5 merge.verbosity {Merge Verbosity}}
-
-               {b gui.trustmtime  {Trust File Modification Timestamps}}
-               {i-1..99 gui.diffcontext {Number of Diff Context Lines}}
-               {t gui.newbranchtemplate {New Branch Name Template}}
-               } {
-               set type [lindex $option 0]
-               set name [lindex $option 1]
-               set text [lindex $option 2]
-               incr optid
-               foreach f {repo global} {
-                       switch -glob -- $type {
-                       b {
-                               checkbutton $w.$f.$optid -text $text \
-                                       -variable ${f}_config_new($name) \
-                                       -onvalue true \
-                                       -offvalue false \
-                                       -font font_ui
-                               pack $w.$f.$optid -side top -anchor w
-                       }
-                       i-* {
-                               regexp -- {-(\d+)\.\.(\d+)$} $type _junk min max
-                               frame $w.$f.$optid
-                               label $w.$f.$optid.l -text "$text:" -font font_ui
-                               pack $w.$f.$optid.l -side left -anchor w -fill x
-                               spinbox $w.$f.$optid.v \
-                                       -textvariable ${f}_config_new($name) \
-                                       -from $min \
-                                       -to $max \
-                                       -increment 1 \
-                                       -width [expr {1 + [string length $max]}] \
-                                       -font font_ui
-                               bind $w.$f.$optid.v <FocusIn> {%W selection range 0 end}
-                               pack $w.$f.$optid.v -side right -anchor e -padx 5
-                               pack $w.$f.$optid -side top -anchor w -fill x
-                       }
-                       t {
-                               frame $w.$f.$optid
-                               label $w.$f.$optid.l -text "$text:" -font font_ui
-                               entry $w.$f.$optid.v \
-                                       -borderwidth 1 \
-                                       -relief sunken \
-                                       -width 20 \
-                                       -textvariable ${f}_config_new($name) \
-                                       -font font_ui
-                               pack $w.$f.$optid.l -side left -anchor w
-                               pack $w.$f.$optid.v -side left -anchor w \
-                                       -fill x -expand 1 \
-                                       -padx 5
-                               pack $w.$f.$optid -side top -anchor w -fill x
-                       }
-                       }
-               }
-       }
-
-       set all_fonts [lsort [font families]]
-       foreach option $font_descs {
-               set name [lindex $option 0]
-               set font [lindex $option 1]
-               set text [lindex $option 2]
-
-               set global_config_new(gui.$font^^family) \
-                       [font configure $font -family]
-               set global_config_new(gui.$font^^size) \
-                       [font configure $font -size]
-
-               frame $w.global.$name
-               label $w.global.$name.l -text "$text:" -font font_ui
-               pack $w.global.$name.l -side left -anchor w -fill x
-               eval tk_optionMenu $w.global.$name.family \
-                       global_config_new(gui.$font^^family) \
-                       $all_fonts
-               spinbox $w.global.$name.size \
-                       -textvariable global_config_new(gui.$font^^size) \
-                       -from 2 -to 80 -increment 1 \
-                       -width 3 \
-                       -font font_ui
-               bind $w.global.$name.size <FocusIn> {%W selection range 0 end}
-               pack $w.global.$name.size -side right -anchor e
-               pack $w.global.$name.family -side right -anchor e
-               pack $w.global.$name -side top -anchor w -fill x
-       }
-
-       bind $w <Visibility> "grab $w; focus $w"
-       bind $w <Key-Escape> "destroy $w"
-       wm title $w "[appname] ([reponame]): Options"
-       tkwait window $w
-}
-
-proc do_restore_defaults {} {
-       global font_descs default_config repo_config
-       global repo_config_new global_config_new
-
-       foreach name [array names default_config] {
-               set repo_config_new($name) $default_config($name)
-               set global_config_new($name) $default_config($name)
-       }
-
-       foreach option $font_descs {
-               set name [lindex $option 0]
-               set repo_config(gui.$name) $default_config(gui.$name)
-       }
-       apply_config
-
-       foreach option $font_descs {
-               set name [lindex $option 0]
-               set font [lindex $option 1]
-               set global_config_new(gui.$font^^family) \
-                       [font configure $font -family]
-               set global_config_new(gui.$font^^size) \
-                       [font configure $font -size]
-       }
-}
-
-proc do_save_config {w} {
-       if {[catch {save_config} err]} {
-               error_popup "Failed to completely save options:\n\n$err"
-       }
-       reshow_diff
-       destroy $w
-}
-
-proc do_windows_shortcut {} {
-       global argv0
-
-       set fn [tk_getSaveFile \
-               -parent . \
-               -title "[appname] ([reponame]): Create Desktop Icon" \
-               -initialfile "Git [reponame].bat"]
-       if {$fn != {}} {
-               if {[catch {
-                               set fd [open $fn w]
-                               puts $fd "@ECHO Entering [reponame]"
-                               puts $fd "@ECHO Starting git-gui... please wait..."
-                               puts $fd "@SET PATH=[file normalize [gitexec]];%PATH%"
-                               puts $fd "@SET GIT_DIR=[file normalize [gitdir]]"
-                               puts -nonewline $fd "@\"[info nameofexecutable]\""
-                               puts $fd " \"[file normalize $argv0]\""
-                               close $fd
-                       } err]} {
-                       error_popup "Cannot write script:\n\n$err"
-               }
-       }
-}
-
-proc do_cygwin_shortcut {} {
-       global argv0
-
-       if {[catch {
-               set desktop [exec cygpath \
-                       --windows \
-                       --absolute \
-                       --long-name \
-                       --desktop]
-               }]} {
-                       set desktop .
-       }
-       set fn [tk_getSaveFile \
-               -parent . \
-               -title "[appname] ([reponame]): Create Desktop Icon" \
-               -initialdir $desktop \
-               -initialfile "Git [reponame].bat"]
-       if {$fn != {}} {
-               if {[catch {
-                               set fd [open $fn w]
-                               set sh [exec cygpath \
-                                       --windows \
-                                       --absolute \
-                                       /bin/sh]
-                               set me [exec cygpath \
-                                       --unix \
-                                       --absolute \
-                                       $argv0]
-                               set gd [exec cygpath \
-                                       --unix \
-                                       --absolute \
-                                       [gitdir]]
-                               set gw [exec cygpath \
-                                       --windows \
-                                       --absolute \
-                                       [file dirname [gitdir]]]
-                               regsub -all ' $me "'\\''" me
-                               regsub -all ' $gd "'\\''" gd
-                               puts $fd "@ECHO Entering $gw"
-                               puts $fd "@ECHO Starting git-gui... please wait..."
-                               puts -nonewline $fd "@\"$sh\" --login -c \""
-                               puts -nonewline $fd "GIT_DIR='$gd'"
-                               puts -nonewline $fd " '$me'"
-                               puts $fd "&\""
-                               close $fd
-                       } err]} {
-                       error_popup "Cannot write script:\n\n$err"
-               }
-       }
-}
-
-proc do_macosx_app {} {
-       global argv0 env
-
-       set fn [tk_getSaveFile \
-               -parent . \
-               -title "[appname] ([reponame]): Create Desktop Icon" \
-               -initialdir [file join $env(HOME) Desktop] \
-               -initialfile "Git [reponame].app"]
-       if {$fn != {}} {
-               if {[catch {
-                               set Contents [file join $fn Contents]
-                               set MacOS [file join $Contents MacOS]
-                               set exe [file join $MacOS git-gui]
-
-                               file mkdir $MacOS
-
-                               set fd [open [file join $Contents Info.plist] w]
-                               puts $fd {<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
-<plist version="1.0">
-<dict>
-       <key>CFBundleDevelopmentRegion</key>
-       <string>English</string>
-       <key>CFBundleExecutable</key>
-       <string>git-gui</string>
-       <key>CFBundleIdentifier</key>
-       <string>org.spearce.git-gui</string>
-       <key>CFBundleInfoDictionaryVersion</key>
-       <string>6.0</string>
-       <key>CFBundlePackageType</key>
-       <string>APPL</string>
-       <key>CFBundleSignature</key>
-       <string>????</string>
-       <key>CFBundleVersion</key>
-       <string>1.0</string>
-       <key>NSPrincipalClass</key>
-       <string>NSApplication</string>
-</dict>
-</plist>}
-                               close $fd
-
-                               set fd [open $exe w]
-                               set gd [file normalize [gitdir]]
-                               set ep [file normalize [gitexec]]
-                               regsub -all ' $gd "'\\''" gd
-                               regsub -all ' $ep "'\\''" ep
-                               puts $fd "#!/bin/sh"
-                               foreach name [array names env] {
-                                       if {[string match GIT_* $name]} {
-                                               regsub -all ' $env($name) "'\\''" v
-                                               puts $fd "export $name='$v'"
-                                       }
-                               }
-                               puts $fd "export PATH='$ep':\$PATH"
-                               puts $fd "export GIT_DIR='$gd'"
-                               puts $fd "exec [file normalize $argv0]"
-                               close $fd
-
-                               file attributes $exe -permissions u+x,g+x,o+x
-                       } err]} {
-                       error_popup "Cannot write icon:\n\n$err"
-               }
-       }
-}
-
 proc toggle_or_diff {w x y} {
        global file_states file_lists current_diff_path ui_index ui_workdir
        global last_clicked selected_paths
@@ -4991,6 +1199,13 @@ catch {
 font create font_uibold
 font create font_diffbold
 
+foreach class {Button Checkbutton Entry Label
+               Labelframe Listbox Menu Message
+               Radiobutton Text} {
+       option add *$class.font font_ui
+}
+unset class
+
 if {[is_Windows]} {
        set M1B Control
        set M1T Ctrl
@@ -5106,93 +1321,76 @@ menu .mbar.repository
 
 .mbar.repository add command \
        -label {Browse Current Branch} \
-       -command {new_browser $current_branch} \
-       -font font_ui
+       -command {browser::new $current_branch}
 trace add variable current_branch write ".mbar.repository entryconf [.mbar.repository index last] -label \"Browse \$current_branch\" ;#"
 .mbar.repository add separator
 
 .mbar.repository add command \
        -label {Visualize Current Branch} \
-       -command {do_gitk $current_branch} \
-       -font font_ui
+       -command {do_gitk $current_branch}
 trace add variable current_branch write ".mbar.repository entryconf [.mbar.repository index last] -label \"Visualize \$current_branch\" ;#"
 .mbar.repository add command \
        -label {Visualize All Branches} \
-       -command {do_gitk --all} \
-       -font font_ui
+       -command {do_gitk --all}
 .mbar.repository add separator
 
 if {[is_enabled multicommit]} {
        .mbar.repository add command -label {Database Statistics} \
-               -command do_stats \
-               -font font_ui
+               -command do_stats
 
        .mbar.repository add command -label {Compress Database} \
-               -command do_gc \
-               -font font_ui
+               -command do_gc
 
        .mbar.repository add command -label {Verify Database} \
-               -command do_fsck_objects \
-               -font font_ui
+               -command do_fsck_objects
 
        .mbar.repository add separator
 
        if {[is_Cygwin]} {
                .mbar.repository add command \
                        -label {Create Desktop Icon} \
-                       -command do_cygwin_shortcut \
-                       -font font_ui
+                       -command do_cygwin_shortcut
        } elseif {[is_Windows]} {
                .mbar.repository add command \
                        -label {Create Desktop Icon} \
-                       -command do_windows_shortcut \
-                       -font font_ui
+                       -command do_windows_shortcut
        } elseif {[is_MacOSX]} {
                .mbar.repository add command \
                        -label {Create Desktop Icon} \
-                       -command do_macosx_app \
-                       -font font_ui
+                       -command do_macosx_app
        }
 }
 
 .mbar.repository add command -label Quit \
        -command do_quit \
-       -accelerator $M1T-Q \
-       -font font_ui
+       -accelerator $M1T-Q
 
 # -- Edit Menu
 #
 menu .mbar.edit
 .mbar.edit add command -label Undo \
        -command {catch {[focus] edit undo}} \
-       -accelerator $M1T-Z \
-       -font font_ui
+       -accelerator $M1T-Z
 .mbar.edit add command -label Redo \
        -command {catch {[focus] edit redo}} \
-       -accelerator $M1T-Y \
-       -font font_ui
+       -accelerator $M1T-Y
 .mbar.edit add separator
 .mbar.edit add command -label Cut \
        -command {catch {tk_textCut [focus]}} \
-       -accelerator $M1T-X \
-       -font font_ui
+       -accelerator $M1T-X
 .mbar.edit add command -label Copy \
        -command {catch {tk_textCopy [focus]}} \
-       -accelerator $M1T-C \
-       -font font_ui
+       -accelerator $M1T-C
 .mbar.edit add command -label Paste \
        -command {catch {tk_textPaste [focus]; [focus] see insert}} \
-       -accelerator $M1T-V \
-       -font font_ui
+       -accelerator $M1T-V
 .mbar.edit add command -label Delete \
        -command {catch {[focus] delete sel.first sel.last}} \
-       -accelerator Del \
-       -font font_ui
+       -accelerator Del
 .mbar.edit add separator
 .mbar.edit add command -label {Select All} \
        -command {catch {[focus] tag add sel 0.0 end}} \
-       -accelerator $M1T-A \
-       -font font_ui
+       -accelerator $M1T-A
 
 # -- Branch Menu
 #
@@ -5201,20 +1399,17 @@ if {[is_enabled branch]} {
 
        .mbar.branch add command -label {Create...} \
                -command do_create_branch \
-               -accelerator $M1T-N \
-               -font font_ui
+               -accelerator $M1T-N
        lappend disable_on_lock [list .mbar.branch entryconf \
                [.mbar.branch index last] -state]
 
        .mbar.branch add command -label {Delete...} \
-               -command do_delete_branch \
-               -font font_ui
+               -command do_delete_branch
        lappend disable_on_lock [list .mbar.branch entryconf \
                [.mbar.branch index last] -state]
 
        .mbar.branch add command -label {Reset...} \
-               -command do_reset_hard \
-               -font font_ui
+               -command merge::reset_hard
        lappend disable_on_lock [list .mbar.branch entryconf \
                [.mbar.branch index last] -state]
 }
@@ -5228,8 +1423,7 @@ if {[is_enabled multicommit] || [is_enabled singlecommit]} {
                -label {New Commit} \
                -command do_select_commit_type \
                -variable selected_commit_type \
-               -value new \
-               -font font_ui
+               -value new
        lappend disable_on_lock \
                [list .mbar.commit entryconf [.mbar.commit index last] -state]
 
@@ -5237,8 +1431,7 @@ if {[is_enabled multicommit] || [is_enabled singlecommit]} {
                -label {Amend Last Commit} \
                -command do_select_commit_type \
                -variable selected_commit_type \
-               -value amend \
-               -font font_ui
+               -value amend
        lappend disable_on_lock \
                [list .mbar.commit entryconf [.mbar.commit index last] -state]
 
@@ -5246,33 +1439,28 @@ if {[is_enabled multicommit] || [is_enabled singlecommit]} {
 
        .mbar.commit add command -label Rescan \
                -command do_rescan \
-               -accelerator F5 \
-               -font font_ui
+               -accelerator F5
        lappend disable_on_lock \
                [list .mbar.commit entryconf [.mbar.commit index last] -state]
 
        .mbar.commit add command -label {Add To Commit} \
-               -command do_add_selection \
-               -font font_ui
+               -command do_add_selection
        lappend disable_on_lock \
                [list .mbar.commit entryconf [.mbar.commit index last] -state]
 
        .mbar.commit add command -label {Add Existing To Commit} \
                -command do_add_all \
-               -accelerator $M1T-I \
-               -font font_ui
+               -accelerator $M1T-I
        lappend disable_on_lock \
                [list .mbar.commit entryconf [.mbar.commit index last] -state]
 
        .mbar.commit add command -label {Unstage From Commit} \
-               -command do_unstage_selection \
-               -font font_ui
+               -command do_unstage_selection
        lappend disable_on_lock \
                [list .mbar.commit entryconf [.mbar.commit index last] -state]
 
        .mbar.commit add command -label {Revert Changes} \
-               -command do_revert_selection \
-               -font font_ui
+               -command do_revert_selection
        lappend disable_on_lock \
                [list .mbar.commit entryconf [.mbar.commit index last] -state]
 
@@ -5280,13 +1468,11 @@ if {[is_enabled multicommit] || [is_enabled singlecommit]} {
 
        .mbar.commit add command -label {Sign Off} \
                -command do_signoff \
-               -accelerator $M1T-S \
-               -font font_ui
+               -accelerator $M1T-S
 
        .mbar.commit add command -label Commit \
                -command do_commit \
-               -accelerator $M1T-Return \
-               -font font_ui
+               -accelerator $M1T-Return
        lappend disable_on_lock \
                [list .mbar.commit entryconf [.mbar.commit index last] -state]
 }
@@ -5296,13 +1482,11 @@ if {[is_enabled multicommit] || [is_enabled singlecommit]} {
 if {[is_enabled branch]} {
        menu .mbar.merge
        .mbar.merge add command -label {Local Merge...} \
-               -command do_local_merge \
-               -font font_ui
+               -command merge::dialog
        lappend disable_on_lock \
                [list .mbar.merge entryconf [.mbar.merge index last] -state]
        .mbar.merge add command -label {Abort Merge...} \
-               -command do_reset_hard \
-               -font font_ui
+               -command merge::reset_hard
        lappend disable_on_lock \
                [list .mbar.merge entryconf [.mbar.merge index last] -state]
 
@@ -5315,8 +1499,7 @@ if {[is_enabled transport]} {
 
        menu .mbar.push
        .mbar.push add command -label {Push...} \
-               -command do_push_anywhere \
-               -font font_ui
+               -command do_push_anywhere
 }
 
 if {[is_MacOSX]} {
@@ -5326,18 +1509,15 @@ if {[is_MacOSX]} {
        menu .mbar.apple
 
        .mbar.apple add command -label "About [appname]" \
-               -command do_about \
-               -font font_ui
+               -command do_about
        .mbar.apple add command -label "Options..." \
-               -command do_options \
-               -font font_ui
+               -command do_options
 } else {
        # -- Edit Menu
        #
        .mbar.edit add separator
        .mbar.edit add command -label {Options...} \
-               -command do_options \
-               -font font_ui
+               -command do_options
 
        # -- Tools Menu
        #
@@ -5363,8 +1543,7 @@ if {[is_MacOSX]} {
        .mbar add cascade -label Tools -menu .mbar.tools
        menu .mbar.tools
        .mbar.tools add command -label "Migrate" \
-               -command do_miga \
-               -font font_ui
+               -command do_miga
        lappend disable_on_lock \
                [list .mbar.tools entryconf [.mbar.tools index last] -state]
        }
@@ -5377,8 +1556,7 @@ menu .mbar.help
 
 if {![is_MacOSX]} {
        .mbar.help add command -label "About [appname]" \
-               -command do_about \
-               -font font_ui
+               -command do_about
 }
 
 set browser {}
@@ -5415,8 +1593,7 @@ if {[file isfile $doc_path]} {
 
 if {$browser ne {}} {
        .mbar.help add command -label {Online Documentation} \
-               -command [list exec $browser $doc_url &] \
-               -font font_ui
+               -command [list exec $browser $doc_url &]
 }
 unset browser doc_path doc_url
 
@@ -5428,25 +1605,67 @@ bind all <$M1B-Key-Q> do_quit
 bind all <$M1B-Key-w> {destroy [winfo toplevel %W]}
 bind all <$M1B-Key-W> {destroy [winfo toplevel %W]}
 
+set subcommand_args {}
+proc usage {} {
+       puts stderr "usage: $::argv0 $::subcommand $::subcommand_args"
+       exit 1
+}
+
 # -- Not a normal commit type invocation?  Do that instead!
 #
 switch -- $subcommand {
 browser {
-       if {[llength $argv] != 1} {
-               puts stderr "usage: $argv0 browser commit"
-               exit 1
+       set subcommand_args {rev?}
+       switch [llength $argv] {
+       0 {
+               set current_branch [git symbolic-ref HEAD]
+               regsub ^refs/((heads|tags|remotes)/)? \
+                       $current_branch {} current_branch
+       }
+       1 {
+               set current_branch [lindex $argv 0]
+       }
+       default usage
        }
-       set current_branch [lindex $argv 0]
-       new_browser $current_branch
+       browser::new $current_branch
        return
 }
 blame {
-       if {[llength $argv] != 2} {
-               puts stderr "usage: $argv0 blame commit path"
-               exit 1
+       set subcommand_args {rev? path?}
+       set head {}
+       set path {}
+       set is_path 0
+       foreach a $argv {
+               if {$is_path || [file exists $_prefix$a]} {
+                       if {$path ne {}} usage
+                       set path $_prefix$a
+                       break
+               } elseif {$a eq {--}} {
+                       if {$path ne {}} {
+                               if {$head ne {}} usage
+                               set head $path
+                               set path {}
+                       }
+                       set is_path 1
+               } elseif {$head eq {}} {
+                       if {$head ne {}} usage
+                       set head $a
+               } else {
+                       usage
+               }
+       }
+       unset is_path
+
+       if {$head eq {}} {
+               set current_branch [git symbolic-ref HEAD]
+               regsub ^refs/((heads|tags|remotes)/)? \
+                       $current_branch {} current_branch
+       } else {
+               set current_branch $head
        }
-       set current_branch [lindex $argv 0]
-       show_blame $current_branch [lindex $argv 1]
+
+       if {$path eq {}} usage
+       blame::new $head $path
        return
 }
 citool -
@@ -5475,13 +1694,11 @@ frame .branch \
 label .branch.l1 \
        -text {Current Branch:} \
        -anchor w \
-       -justify left \
-       -font font_ui
+       -justify left
 label .branch.cb \
        -textvariable current_branch \
        -anchor w \
-       -justify left \
-       -font font_ui
+       -justify left
 pack .branch.l1 -side left
 pack .branch.cb -side left -fill x
 pack .branch -side top -fill x
@@ -5496,13 +1713,11 @@ pack .vpane -anchor n -side top -fill both -expand 1
 # -- Index File List
 #
 frame .vpane.files.index -height 100 -width 200
-label .vpane.files.index.title -text {Changes To Be Committed} \
-       -background green \
-       -font font_ui
+label .vpane.files.index.title -text {Staged Changes (Will Be Committed)} \
+       -background green
 text $ui_index -background white -borderwidth 0 \
        -width 20 -height 10 \
        -wrap none \
-       -font font_ui \
        -cursor $cursor_ptr \
        -xscrollcommand {.vpane.files.index.sx set} \
        -yscrollcommand {.vpane.files.index.sy set} \
@@ -5518,13 +1733,11 @@ pack $ui_index -side left -fill both -expand 1
 # -- Working Directory File List
 #
 frame .vpane.files.workdir -height 100 -width 200
-label .vpane.files.workdir.title -text {Changed But Not Updated} \
-       -background red \
-       -font font_ui
+label .vpane.files.workdir.title -text {Unstaged Changes (Will Not Be Committed)} \
+       -background red
 text $ui_workdir -background white -borderwidth 0 \
        -width 20 -height 10 \
        -wrap none \
-       -font font_ui \
        -cursor $cursor_ptr \
        -xscrollcommand {.vpane.files.workdir.sx set} \
        -yscrollcommand {.vpane.files.workdir.sy set} \
@@ -5559,33 +1772,28 @@ pack .vpane.lower.diff -side bottom -fill both -expand 1
 frame .vpane.lower.commarea.buttons
 label .vpane.lower.commarea.buttons.l -text {} \
        -anchor w \
-       -justify left \
-       -font font_ui
+       -justify left
 pack .vpane.lower.commarea.buttons.l -side top -fill x
 pack .vpane.lower.commarea.buttons -side left -fill y
 
 button .vpane.lower.commarea.buttons.rescan -text {Rescan} \
-       -command do_rescan \
-       -font font_ui
+       -command do_rescan
 pack .vpane.lower.commarea.buttons.rescan -side top -fill x
 lappend disable_on_lock \
        {.vpane.lower.commarea.buttons.rescan conf -state}
 
 button .vpane.lower.commarea.buttons.incall -text {Add Existing} \
-       -command do_add_all \
-       -font font_ui
+       -command do_add_all
 pack .vpane.lower.commarea.buttons.incall -side top -fill x
 lappend disable_on_lock \
        {.vpane.lower.commarea.buttons.incall conf -state}
 
 button .vpane.lower.commarea.buttons.signoff -text {Sign Off} \
-       -command do_signoff \
-       -font font_ui
+       -command do_signoff
 pack .vpane.lower.commarea.buttons.signoff -side top -fill x
 
 button .vpane.lower.commarea.buttons.commit -text {Commit} \
-       -command do_commit \
-       -font font_ui
+       -command do_commit
 pack .vpane.lower.commarea.buttons.commit -side top -fill x
 lappend disable_on_lock \
        {.vpane.lower.commarea.buttons.commit conf -state}
@@ -5600,22 +1808,19 @@ radiobutton .vpane.lower.commarea.buffer.header.new \
        -text {New Commit} \
        -command do_select_commit_type \
        -variable selected_commit_type \
-       -value new \
-       -font font_ui
+       -value new
 lappend disable_on_lock \
        [list .vpane.lower.commarea.buffer.header.new conf -state]
 radiobutton .vpane.lower.commarea.buffer.header.amend \
        -text {Amend Last Commit} \
        -command do_select_commit_type \
        -variable selected_commit_type \
-       -value amend \
-       -font font_ui
+       -value amend
 lappend disable_on_lock \
        [list .vpane.lower.commarea.buffer.header.amend conf -state]
 label $ui_coml \
        -anchor w \
-       -justify left \
-       -font font_ui
+       -justify left
 proc trace_commit_type {varname args} {
        global ui_coml commit_type
        switch -glob -- $commit_type {
@@ -5654,28 +1859,22 @@ set ctxm .vpane.lower.commarea.buffer.ctxm
 menu $ctxm -tearoff 0
 $ctxm add command \
        -label {Cut} \
-       -font font_ui \
        -command {tk_textCut $ui_comm}
 $ctxm add command \
        -label {Copy} \
-       -font font_ui \
        -command {tk_textCopy $ui_comm}
 $ctxm add command \
        -label {Paste} \
-       -font font_ui \
        -command {tk_textPaste $ui_comm}
 $ctxm add command \
        -label {Delete} \
-       -font font_ui \
        -command {$ui_comm delete sel.first sel.last}
 $ctxm add separator
 $ctxm add command \
        -label {Select All} \
-       -font font_ui \
        -command {focus $ui_comm;$ui_comm tag add sel 0.0 end}
 $ctxm add command \
        -label {Copy All} \
-       -font font_ui \
        -command {
                $ui_comm tag add sel 0.0 end
                tk_textCopy $ui_comm
@@ -5684,7 +1883,6 @@ $ctxm add command \
 $ctxm add separator
 $ctxm add command \
        -label {Sign Off} \
-       -font font_ui \
        -command do_signoff
 bind_button3 $ui_comm "tk_popup $ctxm %X %Y"
 
@@ -5719,18 +1917,15 @@ label .vpane.lower.diff.header.status \
        -background orange \
        -width $max_status_desc \
        -anchor w \
-       -justify left \
-       -font font_ui
+       -justify left
 label .vpane.lower.diff.header.file \
        -background orange \
        -anchor w \
-       -justify left \
-       -font font_ui
+       -justify left
 label .vpane.lower.diff.header.path \
        -background orange \
        -anchor w \
-       -justify left \
-       -font font_ui
+       -justify left
 pack .vpane.lower.diff.header.status -side left
 pack .vpane.lower.diff.header.file -side left
 pack .vpane.lower.diff.header.path -fill x
@@ -5738,7 +1933,6 @@ set ctxm .vpane.lower.diff.header.ctxm
 menu $ctxm -tearoff 0
 $ctxm add command \
        -label {Copy} \
-       -font font_ui \
        -command {
                clipboard clear
                clipboard append \
@@ -5807,22 +2001,18 @@ set ctxm .vpane.lower.diff.body.ctxm
 menu $ctxm -tearoff 0
 $ctxm add command \
        -label {Refresh} \
-       -font font_ui \
        -command reshow_diff
 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
 $ctxm add command \
        -label {Copy} \
-       -font font_ui \
        -command {tk_textCopy $ui_diff}
 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
 $ctxm add command \
        -label {Select All} \
-       -font font_ui \
        -command {focus $ui_diff;$ui_diff tag add sel 0.0 end}
 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
 $ctxm add command \
        -label {Copy All} \
-       -font font_ui \
        -command {
                $ui_diff tag add sel 0.0 end
                tk_textCopy $ui_diff
@@ -5832,25 +2022,21 @@ lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
 $ctxm add separator
 $ctxm add command \
        -label {Apply/Reverse Hunk} \
-       -font font_ui \
        -command {apply_hunk $cursorX $cursorY}
 set ui_diff_applyhunk [$ctxm index last]
 lappend diff_actions [list $ctxm entryconf $ui_diff_applyhunk -state]
 $ctxm add separator
 $ctxm add command \
        -label {Decrease Font Size} \
-       -font font_ui \
        -command {incr_font_size font_diff -1}
 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
 $ctxm add command \
        -label {Increase Font Size} \
-       -font font_ui \
        -command {incr_font_size font_diff 1}
 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
 $ctxm add separator
 $ctxm add command \
        -label {Show Less Context} \
-       -font font_ui \
        -command {if {$repo_config(gui.diffcontext) >= 2} {
                incr repo_config(gui.diffcontext) -1
                reshow_diff
@@ -5858,7 +2044,6 @@ $ctxm add command \
 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
 $ctxm add command \
        -label {Show More Context} \
-       -font font_ui \
        -command {
                incr repo_config(gui.diffcontext)
                reshow_diff
@@ -5866,7 +2051,6 @@ $ctxm add command \
 lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
 $ctxm add separator
 $ctxm add command -label {Options...} \
-       -font font_ui \
        -command do_options
 bind_button3 $ui_diff "
        set cursorX %x
@@ -5886,8 +2070,7 @@ label .status -textvariable ui_status_value \
        -anchor w \
        -justify left \
        -borderwidth 1 \
-       -relief sunken \
-       -font font_ui
+       -relief sunken
 pack .status -anchor w -side bottom -fill x
 
 # -- Load geometry
@@ -5930,6 +2113,12 @@ bind $ui_diff <Key-Up>     {catch {%W yview scroll -1 units};break}
 bind $ui_diff <Key-Down>   {catch {%W yview scroll  1 units};break}
 bind $ui_diff <Key-Left>   {catch {%W xview scroll -1 units};break}
 bind $ui_diff <Key-Right>  {catch {%W xview scroll  1 units};break}
+bind $ui_diff <Key-k>         {catch {%W yview scroll -1 units};break}
+bind $ui_diff <Key-j>         {catch {%W yview scroll  1 units};break}
+bind $ui_diff <Key-h>         {catch {%W xview scroll -1 units};break}
+bind $ui_diff <Key-l>         {catch {%W xview scroll  1 units};break}
+bind $ui_diff <Control-Key-b> {catch {%W yview scroll -1 pages};break}
+bind $ui_diff <Control-Key-f> {catch {%W yview scroll  1 pages};break}
 bind $ui_diff <Button-1>   {focus %W}
 
 if {[is_enabled branch]} {
@@ -5955,7 +2144,7 @@ unset i
 set file_lists($ui_index) [list]
 set file_lists($ui_workdir) [list]
 
-wm title . "[appname] ([file normalize [file dirname [gitdir]]])"
+wm title . "[appname] ([reponame]) [file normalize [file dirname [gitdir]]]"
 focus -force $ui_comm
 
 # -- Warn the user about environmental problems.  Cygwin's Tcl
@@ -6034,9 +2223,7 @@ if {[is_enabled multicommit]} {
                if {[ask_popup \
                        "This repository currently has $objects_current loose objects.
 
-To maintain optimal performance it is strongly
-recommended that you compress the database
-when more than $object_limit loose objects exist.
+To maintain optimal performance it is strongly recommended that you compress the database when more than $object_limit loose objects exist.
 
 Compress the database now?"] eq yes} {
                        do_gc
diff --git a/git-gui/lib/blame.tcl b/git-gui/lib/blame.tcl
new file mode 100644 (file)
index 0000000..8b032d9
--- /dev/null
@@ -0,0 +1,416 @@
+# git-gui blame viewer
+# Copyright (C) 2006, 2007 Shawn Pearce
+
+class blame {
+
+field commit  ; # input commit to blame
+field path    ; # input filename to view in $commit
+
+field w
+field w_line
+field w_load
+field w_file
+field w_cmit
+field status
+
+field highlight_line   -1 ; # current line selected
+field highlight_commit {} ; # sha1 of commit selected
+
+field total_lines       0  ; # total length of file
+field blame_lines       0  ; # number of lines computed
+field commit_count      0  ; # number of commits in $commit_list
+field commit_list      {}  ; # list of commit sha1 in receipt order
+field order                ; # array commit -> receipt order
+field header               ; # array commit,key -> header field
+field line_commit          ; # array line -> sha1 commit
+field line_file            ; # array line -> file name
+
+field r_commit      ; # commit currently being parsed
+field r_orig_line   ; # original line number
+field r_final_line  ; # final line number
+field r_line_count  ; # lines in this region
+
+constructor new {i_commit i_path} {
+       set commit $i_commit
+       set path   $i_path
+
+       make_toplevel top w
+       wm title $top "[appname] ([reponame]): File Viewer"
+       set status "Loading $commit:$path..."
+
+       label $w.path -text "$commit:$path" \
+               -anchor w \
+               -justify left \
+               -borderwidth 1 \
+               -relief sunken \
+               -font font_uibold
+       pack $w.path -side top -fill x
+
+       frame $w.out
+       text $w.out.loaded_t \
+               -background white -borderwidth 0 \
+               -state disabled \
+               -wrap none \
+               -height 40 \
+               -width 1 \
+               -font font_diff
+       $w.out.loaded_t tag conf annotated -background grey
+
+       text $w.out.linenumber_t \
+               -background white -borderwidth 0 \
+               -state disabled \
+               -wrap none \
+               -height 40 \
+               -width 5 \
+               -font font_diff
+       $w.out.linenumber_t tag conf linenumber -justify right
+
+       text $w.out.file_t \
+               -background white -borderwidth 0 \
+               -state disabled \
+               -wrap none \
+               -height 40 \
+               -width 80 \
+               -xscrollcommand [list $w.out.sbx set] \
+               -font font_diff
+
+       scrollbar $w.out.sbx -orient h -command [list $w.out.file_t xview]
+       scrollbar $w.out.sby -orient v \
+               -command [list scrollbar2many [list \
+               $w.out.loaded_t \
+               $w.out.linenumber_t \
+               $w.out.file_t \
+               ] yview]
+       grid \
+               $w.out.linenumber_t \
+               $w.out.loaded_t \
+               $w.out.file_t \
+               $w.out.sby \
+               -sticky nsew
+       grid conf $w.out.sbx -column 2 -sticky we
+       grid columnconfigure $w.out 2 -weight 1
+       grid rowconfigure $w.out 0 -weight 1
+       pack $w.out -fill both -expand 1
+
+       label $w.status \
+               -textvariable @status \
+               -anchor w \
+               -justify left \
+               -borderwidth 1 \
+               -relief sunken
+       pack $w.status -side bottom -fill x
+
+       frame $w.cm
+       text $w.cm.t \
+               -background white -borderwidth 0 \
+               -state disabled \
+               -wrap none \
+               -height 10 \
+               -width 80 \
+               -xscrollcommand [list $w.cm.sbx set] \
+               -yscrollcommand [list $w.cm.sby set] \
+               -font font_diff
+       scrollbar $w.cm.sbx -orient h -command [list $w.cm.t xview]
+       scrollbar $w.cm.sby -orient v -command [list $w.cm.t yview]
+       pack $w.cm.sby -side right -fill y
+       pack $w.cm.sbx -side bottom -fill x
+       pack $w.cm.t -expand 1 -fill both
+       pack $w.cm -side bottom -fill x
+
+       menu $w.ctxm -tearoff 0
+       $w.ctxm add command \
+               -label "Copy Commit" \
+               -command [cb _copycommit]
+
+       set w_line $w.out.linenumber_t
+       set w_load $w.out.loaded_t
+       set w_file $w.out.file_t
+       set w_cmit $w.cm.t
+
+       foreach i [list \
+               $w.out.loaded_t \
+               $w.out.linenumber_t \
+               $w.out.file_t] {
+               $i tag conf in_sel \
+                       -background [$i cget -foreground] \
+                       -foreground [$i cget -background]
+               $i conf -yscrollcommand \
+                       [list many2scrollbar [list \
+                       $w.out.loaded_t \
+                       $w.out.linenumber_t \
+                       $w.out.file_t \
+                       ] yview $w.out.sby]
+               bind $i <Button-1> "[cb _click $i @%x,%y]; focus $i"
+               bind_button3 $i "
+                       set cursorX %x
+                       set cursorY %y
+                       set cursorW %W
+                       tk_popup $w.ctxm %X %Y
+               "
+       }
+
+       foreach i [list \
+               $w.out.loaded_t \
+               $w.out.linenumber_t \
+               $w.out.file_t \
+               $w.cm.t] {
+               bind $i <Key-Up>        {catch {%W yview scroll -1 units};break}
+               bind $i <Key-Down>      {catch {%W yview scroll  1 units};break}
+               bind $i <Key-Left>      {catch {%W xview scroll -1 units};break}
+               bind $i <Key-Right>     {catch {%W xview scroll  1 units};break}
+               bind $i <Key-k>         {catch {%W yview scroll -1 units};break}
+               bind $i <Key-j>         {catch {%W yview scroll  1 units};break}
+               bind $i <Key-h>         {catch {%W xview scroll -1 units};break}
+               bind $i <Key-l>         {catch {%W xview scroll  1 units};break}
+               bind $i <Control-Key-b> {catch {%W yview scroll -1 pages};break}
+               bind $i <Control-Key-f> {catch {%W yview scroll  1 pages};break}
+       }
+
+       bind $w.cm.t <Button-1> [list focus $w.cm.t]
+       bind $top <Visibility> [list focus $top]
+       bind $top <Destroy> [list delete_this $this]
+
+       if {$commit eq {}} {
+               set fd [open $path r]
+       } else {
+               set cmd [list git cat-file blob "$commit:$path"]
+               set fd [open "| $cmd" r]
+       }
+       fconfigure $fd -blocking 0 -translation lf -encoding binary
+       fileevent $fd readable [cb _read_file $fd]
+}
+
+method _read_file {fd} {
+       $w_load conf -state normal
+       $w_line conf -state normal
+       $w_file conf -state normal
+       while {[gets $fd line] >= 0} {
+               regsub "\r\$" $line {} line
+               incr total_lines
+               $w_load insert end "\n"
+               $w_line insert end "$total_lines\n" linenumber
+               $w_file insert end "$line\n"
+       }
+       $w_load conf -state disabled
+       $w_line conf -state disabled
+       $w_file conf -state disabled
+
+       if {[eof $fd]} {
+               close $fd
+               _status $this
+               set cmd [list git blame -M -C --incremental]
+               if {$commit eq {}} {
+                       lappend cmd --contents $path
+               } else {
+                       lappend cmd $commit
+               }
+               lappend cmd -- $path
+               set fd [open "| $cmd" r]
+               fconfigure $fd -blocking 0 -translation lf -encoding binary
+               fileevent $fd readable [cb _read_blame $fd]
+       }
+} ifdeleted { catch {close $fd} }
+
+method _read_blame {fd} {
+       while {[gets $fd line] >= 0} {
+               if {[regexp {^([a-z0-9]{40}) (\d+) (\d+) (\d+)$} $line line \
+                       cmit original_line final_line line_count]} {
+                       set r_commit     $cmit
+                       set r_orig_line  $original_line
+                       set r_final_line $final_line
+                       set r_line_count $line_count
+
+                       if {[catch {set g $order($cmit)}]} {
+                               $w_line tag conf g$cmit
+                               $w_file tag conf g$cmit
+                               $w_line tag raise in_sel
+                               $w_file tag raise in_sel
+                               $w_file tag raise sel
+                               set order($cmit) $commit_count
+                               incr commit_count
+                               lappend commit_list $cmit
+                       }
+               } elseif {[string match {filename *} $line]} {
+                       set file [string range $line 9 end]
+                       set n    $r_line_count
+                       set lno  $r_final_line
+                       set cmit $r_commit
+
+                       while {$n > 0} {
+                               set lno_e "$lno.0 lineend + 1c"
+                               if {[catch {set g g$line_commit($lno)}]} {
+                                       $w_load tag add annotated $lno.0 $lno_e
+                               } else {
+                                       $w_line tag remove g$g $lno.0 $lno_e
+                                       $w_file tag remove g$g $lno.0 $lno_e
+                               }
+
+                               set line_commit($lno) $cmit
+                               set line_file($lno)   $file
+                               $w_line tag add g$cmit $lno.0 $lno_e
+                               $w_file tag add g$cmit $lno.0 $lno_e
+
+                               if {$highlight_line == -1} {
+                                       if {[lindex [$w_file yview] 0] == 0} {
+                                               $w_file see $lno.0
+                                               _showcommit $this $lno
+                                       }
+                               } elseif {$highlight_line == $lno} {
+                                       _showcommit $this $lno
+                               }
+
+                               incr n -1
+                               incr lno
+                               incr blame_lines
+                       }
+
+                       set hc $highlight_commit
+                       if {$hc ne {}
+                               && [expr {$order($hc) + 1}] == $order($cmit)} {
+                               _showcommit $this $highlight_line
+                       }
+               } elseif {[regexp {^([a-z-]+) (.*)$} $line line key data]} {
+                       set header($r_commit,$key) $data
+               }
+       }
+
+       if {[eof $fd]} {
+               close $fd
+               set status {Annotation complete.}
+       } else {
+               _status $this
+       }
+} ifdeleted { catch {close $fd} }
+
+method _status {} {
+       set have  $blame_lines
+       set total $total_lines
+       set pdone 0
+       if {$total} {set pdone [expr {100 * $have / $total}]}
+
+       set status [format \
+               "Loading annotations... %i of %i lines annotated (%2i%%)" \
+               $have $total $pdone]
+}
+
+method _click {cur_w pos} {
+       set lno [lindex [split [$cur_w index $pos] .] 0]
+       if {$lno eq {}} return
+
+       set lno_e "$lno.0 + 1 line"
+       $w_line tag remove in_sel 0.0 end
+       $w_file tag remove in_sel 0.0 end
+       $w_line tag add in_sel $lno.0 $lno_e
+       $w_file tag add in_sel $lno.0 $lno_e
+
+       _showcommit $this $lno
+}
+
+variable blame_colors {
+       #ff4040
+       #ff40ff
+       #4040ff
+}
+
+method _showcommit {lno} {
+       global repo_config
+       variable blame_colors
+
+       if {$highlight_commit ne {}} {
+               set idx $order($highlight_commit)
+               set i 0
+               foreach c $blame_colors {
+                       set h [lindex $commit_list [expr {$idx - 1 + $i}]]
+                       $w_line tag conf g$h -background white
+                       $w_file tag conf g$h -background white
+                       incr i
+               }
+       }
+
+       $w_cmit conf -state normal
+       $w_cmit delete 0.0 end
+       if {[catch {set cmit $line_commit($lno)}]} {
+               set cmit {}
+               $w_cmit insert end "Loading annotation..."
+       } else {
+               set idx $order($cmit)
+               set i 0
+               foreach c $blame_colors {
+                       set h [lindex $commit_list [expr {$idx - 1 + $i}]]
+                       $w_line tag conf g$h -background $c
+                       $w_file tag conf g$h -background $c
+                       incr i
+               }
+
+               set author_name {}
+               set author_email {}
+               set author_time {}
+               catch {set author_name $header($cmit,author)}
+               catch {set author_email $header($cmit,author-mail)}
+               catch {set author_time [clock format \
+                       $header($cmit,author-time) \
+                       -format {%Y-%m-%d %H:%M:%S}
+               ]}
+
+               set committer_name {}
+               set committer_email {}
+               set committer_time {}
+               catch {set committer_name $header($cmit,committer)}
+               catch {set committer_email $header($cmit,committer-mail)}
+               catch {set committer_time [clock format \
+                       $header($cmit,committer-time) \
+                       -format {%Y-%m-%d %H:%M:%S}
+               ]}
+
+               if {[catch {set msg $header($cmit,message)}]} {
+                       set msg {}
+                       catch {
+                               set fd [open "| git cat-file commit $cmit" r]
+                               fconfigure $fd -encoding binary -translation lf
+                               if {[catch {set enc $repo_config(i18n.commitencoding)}]} {
+                                       set enc utf-8
+                               }
+                               while {[gets $fd line] > 0} {
+                                       if {[string match {encoding *} $line]} {
+                                               set enc [string tolower [string range $line 9 end]]
+                                       }
+                               }
+                               set msg [encoding convertfrom $enc [read $fd]]
+                               set msg [string trim $msg]
+                               close $fd
+
+                               set author_name [encoding convertfrom $enc $author_name]
+                               set committer_name [encoding convertfrom $enc $committer_name]
+
+                               set header($cmit,author) $author_name
+                               set header($cmit,committer) $committer_name
+                       }
+                       set header($cmit,message) $msg
+               }
+
+               $w_cmit insert end "commit $cmit
+Author: $author_name $author_email  $author_time
+Committer: $committer_name $committer_email  $committer_time
+Original File: [escape_path $line_file($lno)]
+
+$msg"
+       }
+       $w_cmit conf -state disabled
+
+       set highlight_line $lno
+       set highlight_commit $cmit
+}
+
+method _copycommit {} {
+       set pos @$::cursorX,$::cursorY
+       set lno [lindex [split [$::cursorW index $pos] .] 0]
+       if {![catch {set commit $line_commit($lno)}]} {
+               clipboard clear
+               clipboard append \
+                       -format STRING \
+                       -type STRING \
+                       -- $commit
+       }
+}
+
+}
diff --git a/git-gui/lib/branch.tcl b/git-gui/lib/branch.tcl
new file mode 100644 (file)
index 0000000..caaee5c
--- /dev/null
@@ -0,0 +1,572 @@
+# git-gui branch (create/delete) support
+# Copyright (C) 2006, 2007 Shawn Pearce
+
+proc load_all_heads {} {
+       global all_heads
+
+       set all_heads [list]
+       set fd [open "| git for-each-ref --format=%(refname) refs/heads" r]
+       while {[gets $fd line] > 0} {
+               if {[is_tracking_branch $line]} continue
+               if {![regsub ^refs/heads/ $line {} name]} continue
+               lappend all_heads $name
+       }
+       close $fd
+
+       set all_heads [lsort $all_heads]
+}
+
+proc load_all_tags {} {
+       set all_tags [list]
+       set fd [open "| git for-each-ref --format=%(refname) refs/tags" r]
+       while {[gets $fd line] > 0} {
+               if {![regsub ^refs/tags/ $line {} name]} continue
+               lappend all_tags $name
+       }
+       close $fd
+
+       return [lsort $all_tags]
+}
+
+proc populate_branch_menu {} {
+       global all_heads disable_on_lock
+
+       set m .mbar.branch
+       set last [$m index last]
+       for {set i 0} {$i <= $last} {incr i} {
+               if {[$m type $i] eq {separator}} {
+                       $m delete $i last
+                       set new_dol [list]
+                       foreach a $disable_on_lock {
+                               if {[lindex $a 0] ne $m || [lindex $a 2] < $i} {
+                                       lappend new_dol $a
+                               }
+                       }
+                       set disable_on_lock $new_dol
+                       break
+               }
+       }
+
+       if {$all_heads ne {}} {
+               $m add separator
+       }
+       foreach b $all_heads {
+               $m add radiobutton \
+                       -label $b \
+                       -command [list switch_branch $b] \
+                       -variable current_branch \
+                       -value $b
+               lappend disable_on_lock \
+                       [list $m entryconf [$m index last] -state]
+       }
+}
+
+proc do_create_branch_action {w} {
+       global all_heads null_sha1 repo_config
+       global create_branch_checkout create_branch_revtype
+       global create_branch_head create_branch_trackinghead
+       global create_branch_name create_branch_revexp
+       global create_branch_tag
+
+       set newbranch $create_branch_name
+       if {$newbranch eq {}
+               || $newbranch eq $repo_config(gui.newbranchtemplate)} {
+               tk_messageBox \
+                       -icon error \
+                       -type ok \
+                       -title [wm title $w] \
+                       -parent $w \
+                       -message "Please supply a branch name."
+               focus $w.desc.name_t
+               return
+       }
+       if {![catch {git show-ref --verify -- "refs/heads/$newbranch"}]} {
+               tk_messageBox \
+                       -icon error \
+                       -type ok \
+                       -title [wm title $w] \
+                       -parent $w \
+                       -message "Branch '$newbranch' already exists."
+               focus $w.desc.name_t
+               return
+       }
+       if {[catch {git check-ref-format "heads/$newbranch"}]} {
+               tk_messageBox \
+                       -icon error \
+                       -type ok \
+                       -title [wm title $w] \
+                       -parent $w \
+                       -message "We do not like '$newbranch' as a branch name."
+               focus $w.desc.name_t
+               return
+       }
+
+       set rev {}
+       switch -- $create_branch_revtype {
+       head {set rev $create_branch_head}
+       tracking {set rev $create_branch_trackinghead}
+       tag {set rev $create_branch_tag}
+       expression {set rev $create_branch_revexp}
+       }
+       if {[catch {set cmt [git rev-parse --verify "${rev}^0"]}]} {
+               tk_messageBox \
+                       -icon error \
+                       -type ok \
+                       -title [wm title $w] \
+                       -parent $w \
+                       -message "Invalid starting revision: $rev"
+               return
+       }
+       if {[catch {
+                       git update-ref \
+                               -m "branch: Created from $rev" \
+                               "refs/heads/$newbranch" \
+                               $cmt \
+                               $null_sha1
+               } err]} {
+               tk_messageBox \
+                       -icon error \
+                       -type ok \
+                       -title [wm title $w] \
+                       -parent $w \
+                       -message "Failed to create '$newbranch'.\n\n$err"
+               return
+       }
+
+       lappend all_heads $newbranch
+       set all_heads [lsort $all_heads]
+       populate_branch_menu
+       destroy $w
+       if {$create_branch_checkout} {
+               switch_branch $newbranch
+       }
+}
+
+proc radio_selector {varname value args} {
+       upvar #0 $varname var
+       set var $value
+}
+
+trace add variable create_branch_head write \
+       [list radio_selector create_branch_revtype head]
+trace add variable create_branch_trackinghead write \
+       [list radio_selector create_branch_revtype tracking]
+trace add variable create_branch_tag write \
+       [list radio_selector create_branch_revtype tag]
+
+trace add variable delete_branch_head write \
+       [list radio_selector delete_branch_checktype head]
+trace add variable delete_branch_trackinghead write \
+       [list radio_selector delete_branch_checktype tracking]
+
+proc do_create_branch {} {
+       global all_heads current_branch repo_config
+       global create_branch_checkout create_branch_revtype
+       global create_branch_head create_branch_trackinghead
+       global create_branch_name create_branch_revexp
+       global create_branch_tag
+
+       set w .branch_editor
+       toplevel $w
+       wm geometry $w "+[winfo rootx .]+[winfo rooty .]"
+
+       label $w.header -text {Create New Branch} \
+               -font font_uibold
+       pack $w.header -side top -fill x
+
+       frame $w.buttons
+       button $w.buttons.create -text Create \
+               -default active \
+               -command [list do_create_branch_action $w]
+       pack $w.buttons.create -side right
+       button $w.buttons.cancel -text {Cancel} \
+               -command [list destroy $w]
+       pack $w.buttons.cancel -side right -padx 5
+       pack $w.buttons -side bottom -fill x -pady 10 -padx 10
+
+       labelframe $w.desc -text {Branch Description}
+       label $w.desc.name_l -text {Name:}
+       entry $w.desc.name_t \
+               -borderwidth 1 \
+               -relief sunken \
+               -width 40 \
+               -textvariable create_branch_name \
+               -validate key \
+               -validatecommand {
+                       if {%d == 1 && [regexp {[~^:?*\[\0- ]} %S]} {return 0}
+                       return 1
+               }
+       grid $w.desc.name_l $w.desc.name_t -sticky we -padx {0 5}
+       grid columnconfigure $w.desc 1 -weight 1
+       pack $w.desc -anchor nw -fill x -pady 5 -padx 5
+
+       labelframe $w.from -text {Starting Revision}
+       radiobutton $w.from.head_r \
+               -text {Local Branch:} \
+               -value head \
+               -variable create_branch_revtype
+       eval tk_optionMenu $w.from.head_m create_branch_head $all_heads
+       grid $w.from.head_r $w.from.head_m -sticky w
+       set all_trackings [all_tracking_branches]
+       if {$all_trackings ne {}} {
+               set create_branch_trackinghead [lindex $all_trackings 0]
+               radiobutton $w.from.tracking_r \
+                       -text {Tracking Branch:} \
+                       -value tracking \
+                       -variable create_branch_revtype
+               eval tk_optionMenu $w.from.tracking_m \
+                       create_branch_trackinghead \
+                       $all_trackings
+               grid $w.from.tracking_r $w.from.tracking_m -sticky w
+       }
+       set all_tags [load_all_tags]
+       if {$all_tags ne {}} {
+               set create_branch_tag [lindex $all_tags 0]
+               radiobutton $w.from.tag_r \
+                       -text {Tag:} \
+                       -value tag \
+                       -variable create_branch_revtype
+               eval tk_optionMenu $w.from.tag_m create_branch_tag $all_tags
+               grid $w.from.tag_r $w.from.tag_m -sticky w
+       }
+       radiobutton $w.from.exp_r \
+               -text {Revision Expression:} \
+               -value expression \
+               -variable create_branch_revtype
+       entry $w.from.exp_t \
+               -borderwidth 1 \
+               -relief sunken \
+               -width 50 \
+               -textvariable create_branch_revexp \
+               -validate key \
+               -validatecommand {
+                       if {%d == 1 && [regexp {\s} %S]} {return 0}
+                       if {%d == 1 && [string length %S] > 0} {
+                               set create_branch_revtype expression
+                       }
+                       return 1
+               }
+       grid $w.from.exp_r $w.from.exp_t -sticky we -padx {0 5}
+       grid columnconfigure $w.from 1 -weight 1
+       pack $w.from -anchor nw -fill x -pady 5 -padx 5
+
+       labelframe $w.postActions -text {Post Creation Actions}
+       checkbutton $w.postActions.checkout \
+               -text {Checkout after creation} \
+               -variable create_branch_checkout
+       pack $w.postActions.checkout -anchor nw
+       pack $w.postActions -anchor nw -fill x -pady 5 -padx 5
+
+       set create_branch_checkout 1
+       set create_branch_head $current_branch
+       set create_branch_revtype head
+       set create_branch_name $repo_config(gui.newbranchtemplate)
+       set create_branch_revexp {}
+
+       bind $w <Visibility> "
+               grab $w
+               $w.desc.name_t icursor end
+               focus $w.desc.name_t
+       "
+       bind $w <Key-Escape> "destroy $w"
+       bind $w <Key-Return> "do_create_branch_action $w;break"
+       wm title $w "[appname] ([reponame]): Create Branch"
+       tkwait window $w
+}
+
+proc do_delete_branch_action {w} {
+       global all_heads
+       global delete_branch_checktype delete_branch_head delete_branch_trackinghead
+
+       set check_rev {}
+       switch -- $delete_branch_checktype {
+       head {set check_rev $delete_branch_head}
+       tracking {set check_rev $delete_branch_trackinghead}
+       always {set check_rev {:none}}
+       }
+       if {$check_rev eq {:none}} {
+               set check_cmt {}
+       } elseif {[catch {set check_cmt [git rev-parse --verify "${check_rev}^0"]}]} {
+               tk_messageBox \
+                       -icon error \
+                       -type ok \
+                       -title [wm title $w] \
+                       -parent $w \
+                       -message "Invalid check revision: $check_rev"
+               return
+       }
+
+       set to_delete [list]
+       set not_merged [list]
+       foreach i [$w.list.l curselection] {
+               set b [$w.list.l get $i]
+               if {[catch {set o [git rev-parse --verify $b]}]} continue
+               if {$check_cmt ne {}} {
+                       if {$b eq $check_rev} continue
+                       if {[catch {set m [git merge-base $o $check_cmt]}]} continue
+                       if {$o ne $m} {
+                               lappend not_merged $b
+                               continue
+                       }
+               }
+               lappend to_delete [list $b $o]
+       }
+       if {$not_merged ne {}} {
+               set msg "The following branches are not completely merged into $check_rev:
+
+ - [join $not_merged "\n - "]"
+               tk_messageBox \
+                       -icon info \
+                       -type ok \
+                       -title [wm title $w] \
+                       -parent $w \
+                       -message $msg
+       }
+       if {$to_delete eq {}} return
+       if {$delete_branch_checktype eq {always}} {
+               set msg {Recovering deleted branches is difficult.
+
+Delete the selected branches?}
+               if {[tk_messageBox \
+                       -icon warning \
+                       -type yesno \
+                       -title [wm title $w] \
+                       -parent $w \
+                       -message $msg] ne yes} {
+                       return
+               }
+       }
+
+       set failed {}
+       foreach i $to_delete {
+               set b [lindex $i 0]
+               set o [lindex $i 1]
+               if {[catch {git update-ref -d "refs/heads/$b" $o} err]} {
+                       append failed " - $b: $err\n"
+               } else {
+                       set x [lsearch -sorted -exact $all_heads $b]
+                       if {$x >= 0} {
+                               set all_heads [lreplace $all_heads $x $x]
+                       }
+               }
+       }
+
+       if {$failed ne {}} {
+               tk_messageBox \
+                       -icon error \
+                       -type ok \
+                       -title [wm title $w] \
+                       -parent $w \
+                       -message "Failed to delete branches:\n$failed"
+       }
+
+       set all_heads [lsort $all_heads]
+       populate_branch_menu
+       destroy $w
+}
+
+proc do_delete_branch {} {
+       global all_heads tracking_branches current_branch
+       global delete_branch_checktype delete_branch_head delete_branch_trackinghead
+
+       set w .branch_editor
+       toplevel $w
+       wm geometry $w "+[winfo rootx .]+[winfo rooty .]"
+
+       label $w.header -text {Delete Local Branch} \
+               -font font_uibold
+       pack $w.header -side top -fill x
+
+       frame $w.buttons
+       button $w.buttons.create -text Delete \
+               -command [list do_delete_branch_action $w]
+       pack $w.buttons.create -side right
+       button $w.buttons.cancel -text {Cancel} \
+               -command [list destroy $w]
+       pack $w.buttons.cancel -side right -padx 5
+       pack $w.buttons -side bottom -fill x -pady 10 -padx 10
+
+       labelframe $w.list -text {Local Branches}
+       listbox $w.list.l \
+               -height 10 \
+               -width 70 \
+               -selectmode extended \
+               -yscrollcommand [list $w.list.sby set]
+       foreach h $all_heads {
+               if {$h ne $current_branch} {
+                       $w.list.l insert end $h
+               }
+       }
+       scrollbar $w.list.sby -command [list $w.list.l yview]
+       pack $w.list.sby -side right -fill y
+       pack $w.list.l -side left -fill both -expand 1
+       pack $w.list -fill both -expand 1 -pady 5 -padx 5
+
+       labelframe $w.validate -text {Delete Only If}
+       radiobutton $w.validate.head_r \
+               -text {Merged Into Local Branch:} \
+               -value head \
+               -variable delete_branch_checktype
+       eval tk_optionMenu $w.validate.head_m delete_branch_head $all_heads
+       grid $w.validate.head_r $w.validate.head_m -sticky w
+       set all_trackings [all_tracking_branches]
+       if {$all_trackings ne {}} {
+               set delete_branch_trackinghead [lindex $all_trackings 0]
+               radiobutton $w.validate.tracking_r \
+                       -text {Merged Into Tracking Branch:} \
+                       -value tracking \
+                       -variable delete_branch_checktype
+               eval tk_optionMenu $w.validate.tracking_m \
+                       delete_branch_trackinghead \
+                       $all_trackings
+               grid $w.validate.tracking_r $w.validate.tracking_m -sticky w
+       }
+       radiobutton $w.validate.always_r \
+               -text {Always (Do not perform merge checks)} \
+               -value always \
+               -variable delete_branch_checktype
+       grid $w.validate.always_r -columnspan 2 -sticky w
+       grid columnconfigure $w.validate 1 -weight 1
+       pack $w.validate -anchor nw -fill x -pady 5 -padx 5
+
+       set delete_branch_head $current_branch
+       set delete_branch_checktype head
+
+       bind $w <Visibility> "grab $w; focus $w"
+       bind $w <Key-Escape> "destroy $w"
+       wm title $w "[appname] ([reponame]): Delete Branch"
+       tkwait window $w
+}
+
+proc switch_branch {new_branch} {
+       global HEAD commit_type current_branch repo_config
+
+       if {![lock_index switch]} return
+
+       # -- Our in memory state should match the repository.
+       #
+       repository_state curType curHEAD curMERGE_HEAD
+       if {[string match amend* $commit_type]
+               && $curType eq {normal}
+               && $curHEAD eq $HEAD} {
+       } elseif {$commit_type ne $curType || $HEAD ne $curHEAD} {
+               info_popup {Last scanned state does not match repository state.
+
+Another Git program has modified this repository since the last scan.  A rescan must be performed before the current branch can be changed.
+
+The rescan will be automatically started now.
+}
+               unlock_index
+               rescan {set ui_status_value {Ready.}}
+               return
+       }
+
+       # -- Don't do a pointless switch.
+       #
+       if {$current_branch eq $new_branch} {
+               unlock_index
+               return
+       }
+
+       if {$repo_config(gui.trustmtime) eq {true}} {
+               switch_branch_stage2 {} $new_branch
+       } else {
+               set ui_status_value {Refreshing file status...}
+               set cmd [list git update-index]
+               lappend cmd -q
+               lappend cmd --unmerged
+               lappend cmd --ignore-missing
+               lappend cmd --refresh
+               set fd_rf [open "| $cmd" r]
+               fconfigure $fd_rf -blocking 0 -translation binary
+               fileevent $fd_rf readable \
+                       [list switch_branch_stage2 $fd_rf $new_branch]
+       }
+}
+
+proc switch_branch_stage2 {fd_rf new_branch} {
+       global ui_status_value HEAD
+
+       if {$fd_rf ne {}} {
+               read $fd_rf
+               if {![eof $fd_rf]} return
+               close $fd_rf
+       }
+
+       set ui_status_value "Updating working directory to '$new_branch'..."
+       set cmd [list git read-tree]
+       lappend cmd -m
+       lappend cmd -u
+       lappend cmd --exclude-per-directory=.gitignore
+       lappend cmd $HEAD
+       lappend cmd $new_branch
+       set fd_rt [open "| $cmd" r]
+       fconfigure $fd_rt -blocking 0 -translation binary
+       fileevent $fd_rt readable \
+               [list switch_branch_readtree_wait $fd_rt $new_branch]
+}
+
+proc switch_branch_readtree_wait {fd_rt new_branch} {
+       global selected_commit_type commit_type HEAD MERGE_HEAD PARENT
+       global current_branch
+       global ui_comm ui_status_value
+
+       # -- We never get interesting output on stdout; only stderr.
+       #
+       read $fd_rt
+       fconfigure $fd_rt -blocking 1
+       if {![eof $fd_rt]} {
+               fconfigure $fd_rt -blocking 0
+               return
+       }
+
+       # -- The working directory wasn't in sync with the index and
+       #    we'd have to overwrite something to make the switch. A
+       #    merge is required.
+       #
+       if {[catch {close $fd_rt} err]} {
+               regsub {^fatal: } $err {} err
+               warn_popup "File level merge required.
+
+$err
+
+Staying on branch '$current_branch'."
+               set ui_status_value "Aborted checkout of '$new_branch' (file level merging is required)."
+               unlock_index
+               return
+       }
+
+       # -- Update the symbolic ref.  Core git doesn't even check for failure
+       #    here, it Just Works(tm).  If it doesn't we are in some really ugly
+       #    state that is difficult to recover from within git-gui.
+       #
+       if {[catch {git symbolic-ref HEAD "refs/heads/$new_branch"} err]} {
+               error_popup "Failed to set current branch.
+
+This working directory is only partially switched.  We successfully updated your files, but failed to update an internal Git file.
+
+This should not have occurred.  [appname] will now close and give up.
+
+$err"
+               do_quit
+               return
+       }
+
+       # -- Update our repository state.  If we were previously in amend mode
+       #    we need to toss the current buffer and do a full rescan to update
+       #    our file lists.  If we weren't in amend mode our file lists are
+       #    accurate and we can avoid the rescan.
+       #
+       unlock_index
+       set selected_commit_type new
+       if {[string match amend* $commit_type]} {
+               $ui_comm delete 0.0 end
+               $ui_comm edit reset
+               $ui_comm edit modified false
+               rescan {set ui_status_value "Checked out branch '$current_branch'."}
+       } else {
+               repository_state commit_type HEAD MERGE_HEAD
+               set PARENT $HEAD
+               set ui_status_value "Checked out branch '$current_branch'."
+       }
+}
diff --git a/git-gui/lib/browser.tcl b/git-gui/lib/browser.tcl
new file mode 100644 (file)
index 0000000..fd86b11
--- /dev/null
@@ -0,0 +1,239 @@
+# git-gui tree browser
+# Copyright (C) 2006, 2007 Shawn Pearce
+
+class browser {
+
+field w
+field browser_commit
+field browser_path
+field browser_files  {}
+field browser_status {Starting...}
+field browser_stack  {}
+field browser_busy   1
+
+constructor new {commit} {
+       global cursor_ptr M1B
+       make_toplevel top w
+       wm title $top "[appname] ([reponame]): File Browser"
+
+       set browser_commit $commit
+       set browser_path $browser_commit:
+
+       label $w.path \
+               -textvariable @browser_path \
+               -anchor w \
+               -justify left \
+               -borderwidth 1 \
+               -relief sunken \
+               -font font_uibold
+       pack $w.path -anchor w -side top -fill x
+
+       frame $w.list
+       set w_list $w.list.l
+       text $w_list -background white -borderwidth 0 \
+               -cursor $cursor_ptr \
+               -state disabled \
+               -wrap none \
+               -height 20 \
+               -width 70 \
+               -xscrollcommand [list $w.list.sbx set] \
+               -yscrollcommand [list $w.list.sby set]
+       $w_list tag conf in_sel \
+               -background [$w_list cget -foreground] \
+               -foreground [$w_list cget -background]
+       scrollbar $w.list.sbx -orient h -command [list $w_list xview]
+       scrollbar $w.list.sby -orient v -command [list $w_list yview]
+       pack $w.list.sbx -side bottom -fill x
+       pack $w.list.sby -side right -fill y
+       pack $w_list -side left -fill both -expand 1
+       pack $w.list -side top -fill both -expand 1
+
+       label $w.status \
+               -textvariable @browser_status \
+               -anchor w \
+               -justify left \
+               -borderwidth 1 \
+               -relief sunken
+       pack $w.status -anchor w -side bottom -fill x
+
+       bind $w_list <Button-1>        "[cb _click 0 @%x,%y];break"
+       bind $w_list <Double-Button-1> "[cb _click 1 @%x,%y];break"
+       bind $w_list <$M1B-Up>         "[cb _parent]        ;break"
+       bind $w_list <$M1B-Left>       "[cb _parent]        ;break"
+       bind $w_list <Up>              "[cb _move -1]       ;break"
+       bind $w_list <Down>            "[cb _move  1]       ;break"
+       bind $w_list <$M1B-Right>      "[cb _enter]         ;break"
+       bind $w_list <Return>          "[cb _enter]         ;break"
+       bind $w_list <Prior>           "[cb _page -1]       ;break"
+       bind $w_list <Next>            "[cb _page  1]       ;break"
+       bind $w_list <Left>            break
+       bind $w_list <Right>           break
+
+       bind $w_list <Visibility> [list focus $w_list]
+       bind $w_list <Destroy> [list delete_this $this]
+       set w $w_list
+       _ls $this $browser_commit
+       return $this
+}
+
+method _move {dir} {
+       if {$browser_busy} return
+       set lno [lindex [split [$w index in_sel.first] .] 0]
+       incr lno $dir
+       if {[lindex $browser_files [expr {$lno - 1}]] ne {}} {
+               $w tag remove in_sel 0.0 end
+               $w tag add in_sel $lno.0 [expr {$lno + 1}].0
+               $w see $lno.0
+       }
+}
+
+method _page {dir} {
+       if {$browser_busy} return
+       $w yview scroll $dir pages
+       set lno [expr {int(
+                 [lindex [$w yview] 0]
+               * [llength $browser_files]
+               + 1)}]
+       if {[lindex $browser_files [expr {$lno - 1}]] ne {}} {
+               $w tag remove in_sel 0.0 end
+               $w tag add in_sel $lno.0 [expr {$lno + 1}].0
+               $w see $lno.0
+       }
+}
+
+method _parent {} {
+       if {$browser_busy} return
+       set info [lindex $browser_files 0]
+       if {[lindex $info 0] eq {parent}} {
+               set parent [lindex $browser_stack end-1]
+               set browser_stack [lrange $browser_stack 0 end-2]
+               if {$browser_stack eq {}} {
+                       regsub {:.*$} $browser_path {:} browser_path
+               } else {
+                       regsub {/[^/]+$} $browser_path {} browser_path
+               }
+               set browser_status "Loading $browser_path..."
+               _ls $this [lindex $parent 0] [lindex $parent 1]
+       }
+}
+
+method _enter {} {
+       if {$browser_busy} return
+       set lno [lindex [split [$w index in_sel.first] .] 0]
+       set info [lindex $browser_files [expr {$lno - 1}]]
+       if {$info ne {}} {
+               switch -- [lindex $info 0] {
+               parent {
+                       _parent $this
+               }
+               tree {
+                       set name [lindex $info 2]
+                       set escn [escape_path $name]
+                       set browser_status "Loading $escn..."
+                       append browser_path $escn
+                       _ls $this [lindex $info 1] $name
+               }
+               blob {
+                       set name [lindex $info 2]
+                       set p {}
+                       foreach n $browser_stack {
+                               append p [lindex $n 1]
+                       }
+                       append p $name
+                       blame::new $browser_commit $p
+               }
+               }
+       }
+}
+
+method _click {was_double_click pos} {
+       if {$browser_busy} return
+       set lno [lindex [split [$w index $pos] .] 0]
+       focus $w
+
+       if {[lindex $browser_files [expr {$lno - 1}]] ne {}} {
+               $w tag remove in_sel 0.0 end
+               $w tag add in_sel $lno.0 [expr {$lno + 1}].0
+               if {$was_double_click} {
+                       _enter $this
+               }
+       }
+}
+
+method _ls {tree_id {name {}}} {
+       set browser_buffer {}
+       set browser_files {}
+       set browser_busy 1
+
+       $w conf -state normal
+       $w tag remove in_sel 0.0 end
+       $w delete 0.0 end
+       if {$browser_stack ne {}} {
+               $w image create end \
+                       -align center -padx 5 -pady 1 \
+                       -name icon0 \
+                       -image file_uplevel
+               $w insert end {[Up To Parent]}
+               lappend browser_files parent
+       }
+       lappend browser_stack [list $tree_id $name]
+       $w conf -state disabled
+
+       set cmd [list git ls-tree -z $tree_id]
+       set fd [open "| $cmd" r]
+       fconfigure $fd -blocking 0 -translation binary -encoding binary
+       fileevent $fd readable [cb _read $fd]
+}
+
+method _read {fd} {
+       append browser_buffer [read $fd]
+       set pck [split $browser_buffer "\0"]
+       set browser_buffer [lindex $pck end]
+
+       set n [llength $browser_files]
+       $w conf -state normal
+       foreach p [lrange $pck 0 end-1] {
+               set info [split $p "\t"]
+               set path [lindex $info 1]
+               set info [split [lindex $info 0] { }]
+               set type [lindex $info 1]
+               set object [lindex $info 2]
+
+               switch -- $type {
+               blob {
+                       set image file_mod
+               }
+               tree {
+                       set image file_dir
+                       append path /
+               }
+               default {
+                       set image file_question
+               }
+               }
+
+               if {$n > 0} {$w insert end "\n"}
+               $w image create end \
+                       -align center -padx 5 -pady 1 \
+                       -name icon[incr n] \
+                       -image $image
+               $w insert end [escape_path $path]
+               lappend browser_files [list $type $object $path]
+       }
+       $w conf -state disabled
+
+       if {[eof $fd]} {
+               close $fd
+               set browser_status Ready.
+               set browser_busy 0
+               unset browser_buffer
+               if {$n > 0} {
+                       $w tag add in_sel 1.0 2.0
+                       focus -force $w
+               }
+       }
+} ifdeleted {
+       catch {close $fd}
+}
+
+}
diff --git a/git-gui/lib/class.tcl b/git-gui/lib/class.tcl
new file mode 100644 (file)
index 0000000..88b0565
--- /dev/null
@@ -0,0 +1,154 @@
+# git-gui simple class/object fake-alike
+# Copyright (C) 2007 Shawn Pearce
+
+proc class {class body} {
+       if {[namespace exists $class]} {
+               error "class $class already declared"
+       }
+       namespace eval $class {
+               variable __nextid     0
+               variable __sealed     0
+               variable __field_list {}
+               variable __field_array
+
+               proc cb {name args} {
+                       upvar this this
+                       set args [linsert $args 0 $name $this]
+                       return [uplevel [list namespace code $args]]
+               }
+       }
+       namespace eval $class $body
+}
+
+proc field {name args} {
+       set class [uplevel {namespace current}]
+       variable ${class}::__sealed
+       variable ${class}::__field_array
+
+       switch [llength $args] {
+       0 { set new [list $name] }
+       1 { set new [list $name [lindex $args 0]] }
+       default { error "wrong # args: field name value?" }
+       }
+
+       if {$__sealed} {
+               error "class $class is sealed (cannot add new fields)"
+       }
+
+       if {[catch {set old $__field_array($name)}]} {
+               variable ${class}::__field_list
+               lappend __field_list $new
+               set __field_array($name) 1
+       } else {
+               error "field $name already declared"
+       }
+}
+
+proc constructor {name params body} {
+       set class [uplevel {namespace current}]
+       set ${class}::__sealed 1
+       variable ${class}::__field_list
+       set mbodyc {}
+
+       append mbodyc {set this } $class
+       append mbodyc {::__o[incr } $class {::__nextid]} \;
+       append mbodyc {namespace eval $this {}} \;
+
+       if {$__field_list ne {}} {
+               append mbodyc {upvar #0}
+               foreach n $__field_list {
+                       set n [lindex $n 0]
+                       append mbodyc { ${this}::} $n { } $n
+                       regsub -all @$n\\M $body "\${this}::$n" body
+               }
+               append mbodyc \;
+               foreach n $__field_list {
+                       if {[llength $n] == 2} {
+                               append mbodyc \
+                               {set } [lindex $n 0] { } [list [lindex $n 1]] \;
+                       }
+               }
+       }
+       append mbodyc $body
+       namespace eval $class [list proc $name $params $mbodyc]
+}
+
+proc method {name params body {deleted {}} {del_body {}}} {
+       set class [uplevel {namespace current}]
+       set ${class}::__sealed 1
+       variable ${class}::__field_list
+       set params [linsert $params 0 this]
+       set mbodyc {}
+
+       switch $deleted {
+       {} {}
+       ifdeleted {
+               append mbodyc {if {![namespace exists $this]} }
+               append mbodyc \{ $del_body \; return \} \;
+       }
+       default {
+               error "wrong # args: method name args body (ifdeleted body)?"
+       }
+       }
+
+       set decl {}
+       foreach n $__field_list {
+               set n [lindex $n 0]
+               if {[regexp -- $n\\M $body]} {
+                       if {   [regexp -all -- $n\\M $body] == 1
+                               && [regexp -all -- \\\$$n\\M $body] == 1
+                               && [regexp -all -- \\\$$n\\( $body] == 0} {
+                               regsub -all \\\$$n\\M $body "\[set \${this}::$n\]" body
+                       } else {
+                               append decl { ${this}::} $n { } $n
+                               regsub -all @$n\\M $body "\${this}::$n" body
+                       }
+               }
+       }
+       if {$decl ne {}} {
+               append mbodyc {upvar #0} $decl \;
+       }
+       append mbodyc $body
+       namespace eval $class [list proc $name $params $mbodyc]
+}
+
+proc delete_this {{t {}}} {
+       if {$t eq {}} {
+               upvar this this
+               set t $this
+       }
+       if {[namespace exists $t]} {namespace delete $t}
+}
+
+proc make_toplevel {t w} {
+       upvar $t top $w pfx
+       if {[winfo ismapped .]} {
+               upvar this this
+               regsub -all {::} $this {__} w
+               set top .$w
+               set pfx $top
+               toplevel $top
+       } else {
+               set top .
+               set pfx {}
+       }
+}
+
+
+## auto_mkindex support for class/constructor/method
+##
+auto_mkindex_parser::command class {name body} {
+       variable parser
+       variable contextStack
+       set contextStack [linsert $contextStack 0 $name]
+       $parser eval [list _%@namespace eval $name] $body
+       set contextStack [lrange $contextStack 1 end]
+}
+auto_mkindex_parser::command constructor {name args} {
+       variable index
+       variable scriptFile
+       append index [list set auto_index([fullname $name])] \
+               [format { [list source [file join $dir %s]]} \
+               [file split $scriptFile]] "\n"
+}
+
diff --git a/git-gui/lib/commit.tcl b/git-gui/lib/commit.tcl
new file mode 100644 (file)
index 0000000..f9791f6
--- /dev/null
@@ -0,0 +1,410 @@
+# git-gui misc. commit reading/writing support
+# Copyright (C) 2006, 2007 Shawn Pearce
+
+proc load_last_commit {} {
+       global HEAD PARENT MERGE_HEAD commit_type ui_comm
+       global repo_config
+
+       if {[llength $PARENT] == 0} {
+               error_popup {There is nothing to amend.
+
+You are about to create the initial commit.  There is no commit before this to amend.
+}
+               return
+       }
+
+       repository_state curType curHEAD curMERGE_HEAD
+       if {$curType eq {merge}} {
+               error_popup {Cannot amend while merging.
+
+You are currently in the middle of a merge that has not been fully completed.  You cannot amend the prior commit unless you first abort the current merge activity.
+}
+               return
+       }
+
+       set msg {}
+       set parents [list]
+       if {[catch {
+                       set fd [open "| git cat-file commit $curHEAD" r]
+                       fconfigure $fd -encoding binary -translation lf
+                       if {[catch {set enc $repo_config(i18n.commitencoding)}]} {
+                               set enc utf-8
+                       }
+                       while {[gets $fd line] > 0} {
+                               if {[string match {parent *} $line]} {
+                                       lappend parents [string range $line 7 end]
+                               } elseif {[string match {encoding *} $line]} {
+                                       set enc [string tolower [string range $line 9 end]]
+                               }
+                       }
+                       set msg [encoding convertfrom $enc [read $fd]]
+                       set msg [string trim $msg]
+                       close $fd
+               } err]} {
+               error_popup "Error loading commit data for amend:\n\n$err"
+               return
+       }
+
+       set HEAD $curHEAD
+       set PARENT $parents
+       set MERGE_HEAD [list]
+       switch -- [llength $parents] {
+       0       {set commit_type amend-initial}
+       1       {set commit_type amend}
+       default {set commit_type amend-merge}
+       }
+
+       $ui_comm delete 0.0 end
+       $ui_comm insert end $msg
+       $ui_comm edit reset
+       $ui_comm edit modified false
+       rescan {set ui_status_value {Ready.}}
+}
+
+set GIT_COMMITTER_IDENT {}
+
+proc committer_ident {} {
+       global GIT_COMMITTER_IDENT
+
+       if {$GIT_COMMITTER_IDENT eq {}} {
+               if {[catch {set me [git var GIT_COMMITTER_IDENT]} err]} {
+                       error_popup "Unable to obtain your identity:\n\n$err"
+                       return {}
+               }
+               if {![regexp {^(.*) [0-9]+ [-+0-9]+$} \
+                       $me me GIT_COMMITTER_IDENT]} {
+                       error_popup "Invalid GIT_COMMITTER_IDENT:\n\n$me"
+                       return {}
+               }
+       }
+
+       return $GIT_COMMITTER_IDENT
+}
+
+proc do_signoff {} {
+       global ui_comm
+
+       set me [committer_ident]
+       if {$me eq {}} return
+
+       set sob "Signed-off-by: $me"
+       set last [$ui_comm get {end -1c linestart} {end -1c}]
+       if {$last ne $sob} {
+               $ui_comm edit separator
+               if {$last ne {}
+                       && ![regexp {^[A-Z][A-Za-z]*-[A-Za-z-]+: *} $last]} {
+                       $ui_comm insert end "\n"
+               }
+               $ui_comm insert end "\n$sob"
+               $ui_comm edit separator
+               $ui_comm see end
+       }
+}
+
+proc create_new_commit {} {
+       global commit_type ui_comm
+
+       set commit_type normal
+       $ui_comm delete 0.0 end
+       $ui_comm edit reset
+       $ui_comm edit modified false
+       rescan {set ui_status_value {Ready.}}
+}
+
+proc commit_tree {} {
+       global HEAD commit_type file_states ui_comm repo_config
+       global ui_status_value pch_error
+
+       if {[committer_ident] eq {}} return
+       if {![lock_index update]} return
+
+       # -- Our in memory state should match the repository.
+       #
+       repository_state curType curHEAD curMERGE_HEAD
+       if {[string match amend* $commit_type]
+               && $curType eq {normal}
+               && $curHEAD eq $HEAD} {
+       } elseif {$commit_type ne $curType || $HEAD ne $curHEAD} {
+               info_popup {Last scanned state does not match repository state.
+
+Another Git program has modified this repository since the last scan.  A rescan must be performed before another commit can be created.
+
+The rescan will be automatically started now.
+}
+               unlock_index
+               rescan {set ui_status_value {Ready.}}
+               return
+       }
+
+       # -- At least one file should differ in the index.
+       #
+       set files_ready 0
+       foreach path [array names file_states] {
+               switch -glob -- [lindex $file_states($path) 0] {
+               _? {continue}
+               A? -
+               D? -
+               M? {set files_ready 1}
+               U? {
+                       error_popup "Unmerged files cannot be committed.
+
+File [short_path $path] has merge conflicts.  You must resolve them and add the file before committing.
+"
+                       unlock_index
+                       return
+               }
+               default {
+                       error_popup "Unknown file state [lindex $s 0] detected.
+
+File [short_path $path] cannot be committed by this program.
+"
+               }
+               }
+       }
+       if {!$files_ready && ![string match *merge $curType]} {
+               info_popup {No changes to commit.
+
+You must add at least 1 file before you can commit.
+}
+               unlock_index
+               return
+       }
+
+       # -- A message is required.
+       #
+       set msg [string trim [$ui_comm get 1.0 end]]
+       regsub -all -line {[ \t\r]+$} $msg {} msg
+       if {$msg eq {}} {
+               error_popup {Please supply a commit message.
+
+A good commit message has the following format:
+
+- First line: Describe in one sentance what you did.
+- Second line: Blank
+- Remaining lines: Describe why this change is good.
+}
+               unlock_index
+               return
+       }
+
+       # -- Run the pre-commit hook.
+       #
+       set pchook [gitdir hooks pre-commit]
+
+       # On Cygwin [file executable] might lie so we need to ask
+       # the shell if the hook is executable.  Yes that's annoying.
+       #
+       if {[is_Cygwin] && [file isfile $pchook]} {
+               set pchook [list sh -c [concat \
+                       "if test -x \"$pchook\";" \
+                       "then exec \"$pchook\" 2>&1;" \
+                       "fi"]]
+       } elseif {[file executable $pchook]} {
+               set pchook [list $pchook |& cat]
+       } else {
+               commit_writetree $curHEAD $msg
+               return
+       }
+
+       set ui_status_value {Calling pre-commit hook...}
+       set pch_error {}
+       set fd_ph [open "| $pchook" r]
+       fconfigure $fd_ph -blocking 0 -translation binary
+       fileevent $fd_ph readable \
+               [list commit_prehook_wait $fd_ph $curHEAD $msg]
+}
+
+proc commit_prehook_wait {fd_ph curHEAD msg} {
+       global pch_error ui_status_value
+
+       append pch_error [read $fd_ph]
+       fconfigure $fd_ph -blocking 1
+       if {[eof $fd_ph]} {
+               if {[catch {close $fd_ph}]} {
+                       set ui_status_value {Commit declined by pre-commit hook.}
+                       hook_failed_popup pre-commit $pch_error
+                       unlock_index
+               } else {
+                       commit_writetree $curHEAD $msg
+               }
+               set pch_error {}
+               return
+       }
+       fconfigure $fd_ph -blocking 0
+}
+
+proc commit_writetree {curHEAD msg} {
+       global ui_status_value
+
+       set ui_status_value {Committing changes...}
+       set fd_wt [open "| git write-tree" r]
+       fileevent $fd_wt readable \
+               [list commit_committree $fd_wt $curHEAD $msg]
+}
+
+proc commit_committree {fd_wt curHEAD msg} {
+       global HEAD PARENT MERGE_HEAD commit_type
+       global all_heads current_branch
+       global ui_status_value ui_comm selected_commit_type
+       global file_states selected_paths rescan_active
+       global repo_config
+
+       gets $fd_wt tree_id
+       if {$tree_id eq {} || [catch {close $fd_wt} err]} {
+               error_popup "write-tree failed:\n\n$err"
+               set ui_status_value {Commit failed.}
+               unlock_index
+               return
+       }
+
+       # -- Verify this wasn't an empty change.
+       #
+       if {$commit_type eq {normal}} {
+               set old_tree [git rev-parse "$PARENT^{tree}"]
+               if {$tree_id eq $old_tree} {
+                       info_popup {No changes to commit.
+
+No files were modified by this commit and it was not a merge commit.
+
+A rescan will be automatically started now.
+}
+                       unlock_index
+                       rescan {set ui_status_value {No changes to commit.}}
+                       return
+               }
+       }
+
+       # -- Build the message.
+       #
+       set msg_p [gitdir COMMIT_EDITMSG]
+       set msg_wt [open $msg_p w]
+       if {[catch {set enc $repo_config(i18n.commitencoding)}]} {
+               set enc utf-8
+       }
+       fconfigure $msg_wt -encoding binary -translation binary
+       puts -nonewline $msg_wt [encoding convertto $enc $msg]
+       close $msg_wt
+
+       # -- Create the commit.
+       #
+       set cmd [list commit-tree $tree_id]
+       foreach p [concat $PARENT $MERGE_HEAD] {
+               lappend cmd -p $p
+       }
+       lappend cmd <$msg_p
+       if {[catch {set cmt_id [eval git $cmd]} err]} {
+               error_popup "commit-tree failed:\n\n$err"
+               set ui_status_value {Commit failed.}
+               unlock_index
+               return
+       }
+
+       # -- Update the HEAD ref.
+       #
+       set reflogm commit
+       if {$commit_type ne {normal}} {
+               append reflogm " ($commit_type)"
+       }
+       set i [string first "\n" $msg]
+       if {$i >= 0} {
+               set subject [string range $msg 0 [expr {$i - 1}]]
+       } else {
+               set subject $msg
+       }
+       append reflogm {: } $subject
+       if {[catch {
+                       git update-ref -m $reflogm HEAD $cmt_id $curHEAD
+               } err]} {
+               error_popup "update-ref failed:\n\n$err"
+               set ui_status_value {Commit failed.}
+               unlock_index
+               return
+       }
+
+       # -- Cleanup after ourselves.
+       #
+       catch {file delete $msg_p}
+       catch {file delete [gitdir MERGE_HEAD]}
+       catch {file delete [gitdir MERGE_MSG]}
+       catch {file delete [gitdir SQUASH_MSG]}
+       catch {file delete [gitdir GITGUI_MSG]}
+
+       # -- Let rerere do its thing.
+       #
+       if {[file isdirectory [gitdir rr-cache]]} {
+               catch {git rerere}
+       }
+
+       # -- Run the post-commit hook.
+       #
+       set pchook [gitdir hooks post-commit]
+       if {[is_Cygwin] && [file isfile $pchook]} {
+               set pchook [list sh -c [concat \
+                       "if test -x \"$pchook\";" \
+                       "then exec \"$pchook\";" \
+                       "fi"]]
+       } elseif {![file executable $pchook]} {
+               set pchook {}
+       }
+       if {$pchook ne {}} {
+               catch {exec $pchook &}
+       }
+
+       $ui_comm delete 0.0 end
+       $ui_comm edit reset
+       $ui_comm edit modified false
+
+       if {[is_enabled singlecommit]} do_quit
+
+       # -- Make sure our current branch exists.
+       #
+       if {$commit_type eq {initial}} {
+               lappend all_heads $current_branch
+               set all_heads [lsort -unique $all_heads]
+               populate_branch_menu
+       }
+
+       # -- Update in memory status
+       #
+       set selected_commit_type new
+       set commit_type normal
+       set HEAD $cmt_id
+       set PARENT $cmt_id
+       set MERGE_HEAD [list]
+
+       foreach path [array names file_states] {
+               set s $file_states($path)
+               set m [lindex $s 0]
+               switch -glob -- $m {
+               _O -
+               _M -
+               _D {continue}
+               __ -
+               A_ -
+               M_ -
+               D_ {
+                       unset file_states($path)
+                       catch {unset selected_paths($path)}
+               }
+               DO {
+                       set file_states($path) [list _O [lindex $s 1] {} {}]
+               }
+               AM -
+               AD -
+               MM -
+               MD {
+                       set file_states($path) [list \
+                               _[string index $m 1] \
+                               [lindex $s 1] \
+                               [lindex $s 3] \
+                               {}]
+               }
+               }
+       }
+
+       display_all_files
+       unlock_index
+       reshow_diff
+       set ui_status_value \
+               "Created commit [string range $cmt_id 0 7]: $subject"
+}
diff --git a/git-gui/lib/console.tcl b/git-gui/lib/console.tcl
new file mode 100644 (file)
index 0000000..8c112f3
--- /dev/null
@@ -0,0 +1,176 @@
+# git-gui console support
+# Copyright (C) 2006, 2007 Shawn Pearce
+
+class console {
+
+field t_short
+field t_long
+field w
+field console_cr
+
+constructor new {short_title long_title} {
+       set t_short $short_title
+       set t_long $long_title
+       _init $this
+       return $this
+}
+
+method _init {} {
+       global M1B
+       make_toplevel top w
+       wm title $top "[appname] ([reponame]): $t_short"
+       set console_cr 1.0
+
+       frame $w.m
+       label $w.m.l1 \
+               -textvariable @t_long  \
+               -anchor w \
+               -justify left \
+               -font font_uibold
+       text $w.m.t \
+               -background white -borderwidth 1 \
+               -relief sunken \
+               -width 80 -height 10 \
+               -font font_diff \
+               -state disabled \
+               -yscrollcommand [list $w.m.sby set]
+       label $w.m.s -text {Working... please wait...} \
+               -anchor w \
+               -justify left \
+               -font font_uibold
+       scrollbar $w.m.sby -command [list $w.m.t yview]
+       pack $w.m.l1 -side top -fill x
+       pack $w.m.s -side bottom -fill x
+       pack $w.m.sby -side right -fill y
+       pack $w.m.t -side left -fill both -expand 1
+       pack $w.m -side top -fill both -expand 1 -padx 5 -pady 10
+
+       menu $w.ctxm -tearoff 0
+       $w.ctxm add command -label "Copy" \
+               -command "tk_textCopy $w.m.t"
+       $w.ctxm add command -label "Select All" \
+               -command "focus $w.m.t;$w.m.t tag add sel 0.0 end"
+       $w.ctxm add command -label "Copy All" \
+               -command "
+                       $w.m.t tag add sel 0.0 end
+                       tk_textCopy $w.m.t
+                       $w.m.t tag remove sel 0.0 end
+               "
+
+       button $w.ok -text {Close} \
+               -state disabled \
+               -command "destroy $w"
+       pack $w.ok -side bottom -anchor e -pady 10 -padx 10
+
+       bind_button3 $w.m.t "tk_popup $w.ctxm %X %Y"
+       bind $w.m.t <$M1B-Key-a> "$w.m.t tag add sel 0.0 end;break"
+       bind $w.m.t <$M1B-Key-A> "$w.m.t tag add sel 0.0 end;break"
+       bind $w <Visibility> "focus $w"
+}
+
+method exec {cmd {after {}}} {
+       # -- Cygwin's Tcl tosses the enviroment when we exec our child.
+       #    But most users need that so we have to relogin. :-(
+       #
+       if {[is_Cygwin]} {
+               set cmd [list sh --login -c "cd \"[pwd]\" && [join $cmd { }]"]
+       }
+
+       # -- Tcl won't let us redirect both stdout and stderr to
+       #    the same pipe.  So pass it through cat...
+       #
+       set cmd [concat | $cmd |& cat]
+
+       set fd_f [open $cmd r]
+       fconfigure $fd_f -blocking 0 -translation binary
+       fileevent $fd_f readable [cb _read $fd_f $after]
+}
+
+method _read {fd after} {
+       set buf [read $fd]
+       if {$buf ne {}} {
+               if {![winfo exists $w.m.t]} {_init $this}
+               $w.m.t conf -state normal
+               set c 0
+               set n [string length $buf]
+               while {$c < $n} {
+                       set cr [string first "\r" $buf $c]
+                       set lf [string first "\n" $buf $c]
+                       if {$cr < 0} {set cr [expr {$n + 1}]}
+                       if {$lf < 0} {set lf [expr {$n + 1}]}
+
+                       if {$lf < $cr} {
+                               $w.m.t insert end [string range $buf $c $lf]
+                               set console_cr [$w.m.t index {end -1c}]
+                               set c $lf
+                               incr c
+                       } else {
+                               $w.m.t delete $console_cr end
+                               $w.m.t insert end "\n"
+                               $w.m.t insert end [string range $buf $c $cr]
+                               set c $cr
+                               incr c
+                       }
+               }
+               $w.m.t conf -state disabled
+               $w.m.t see end
+       }
+
+       fconfigure $fd -blocking 1
+       if {[eof $fd]} {
+               if {[catch {close $fd}]} {
+                       set ok 0
+               } else {
+                       set ok 1
+               }
+               if {$after ne {}} {
+                       uplevel #0 $after $ok
+               } else {
+                       done $this $ok
+               }
+               return
+       }
+       fconfigure $fd -blocking 0
+}
+
+method chain {cmdlist {ok 1}} {
+       if {$ok} {
+               if {[llength $cmdlist] == 0} {
+                       done $this $ok
+                       return
+               }
+
+               set cmd [lindex $cmdlist 0]
+               set cmdlist [lrange $cmdlist 1 end]
+
+               if {[lindex $cmd 0] eq {exec}} {
+                       exec $this \
+                               [lrange $cmd 1 end] \
+                               [cb chain $cmdlist]
+               } else {
+                       uplevel #0 $cmd [cb chain $cmdlist]
+               }
+       } else {
+               done $this $ok
+       }
+}
+
+method done {ok} {
+       if {$ok} {
+               if {[winfo exists $w.m.s]} {
+                       $w.m.s conf -background green -text {Success}
+                       $w.ok conf -state normal
+                       focus $w.ok
+               }
+       } else {
+               if {![winfo exists $w.m.s]} {
+                       _init $this
+               }
+               $w.m.s conf -background red -text {Error: Command Failed}
+               $w.ok conf -state normal
+               focus $w.ok
+       }
+       delete_this
+}
+
+}
diff --git a/git-gui/lib/database.tcl b/git-gui/lib/database.tcl
new file mode 100644 (file)
index 0000000..43e4a28
--- /dev/null
@@ -0,0 +1,89 @@
+# git-gui object database management support
+# Copyright (C) 2006, 2007 Shawn Pearce
+
+proc do_stats {} {
+       set fd [open "| git count-objects -v" r]
+       while {[gets $fd line] > 0} {
+               if {[regexp {^([^:]+): (\d+)$} $line _ name value]} {
+                       set stats($name) $value
+               }
+       }
+       close $fd
+
+       set packed_sz 0
+       foreach p [glob -directory [gitdir objects pack] \
+               -type f \
+               -nocomplain -- *] {
+               incr packed_sz [file size $p]
+       }
+       if {$packed_sz > 0} {
+               set stats(size-pack) [expr {$packed_sz / 1024}]
+       }
+
+       set w .stats_view
+       toplevel $w
+       wm geometry $w "+[winfo rootx .]+[winfo rooty .]"
+
+       label $w.header -text {Database Statistics}
+       pack $w.header -side top -fill x
+
+       frame $w.buttons -border 1
+       button $w.buttons.close -text Close \
+               -default active \
+               -command [list destroy $w]
+       button $w.buttons.gc -text {Compress Database} \
+               -default normal \
+               -command "destroy $w;do_gc"
+       pack $w.buttons.close -side right
+       pack $w.buttons.gc -side left
+       pack $w.buttons -side bottom -fill x -pady 10 -padx 10
+
+       frame $w.stat -borderwidth 1 -relief solid
+       foreach s {
+               {count           {Number of loose objects}}
+               {size            {Disk space used by loose objects} { KiB}}
+               {in-pack         {Number of packed objects}}
+               {packs           {Number of packs}}
+               {size-pack       {Disk space used by packed objects} { KiB}}
+               {prune-packable  {Packed objects waiting for pruning}}
+               {garbage         {Garbage files}}
+               } {
+               set name [lindex $s 0]
+               set label [lindex $s 1]
+               if {[catch {set value $stats($name)}]} continue
+               if {[llength $s] > 2} {
+                       set value "$value[lindex $s 2]"
+               }
+
+               label $w.stat.l_$name -text "$label:" -anchor w
+               label $w.stat.v_$name -text $value -anchor w
+               grid $w.stat.l_$name $w.stat.v_$name -sticky we -padx {0 5}
+       }
+       pack $w.stat -pady 10 -padx 10
+
+       bind $w <Visibility> "grab $w; focus $w.buttons.close"
+       bind $w <Key-Escape> [list destroy $w]
+       bind $w <Key-Return> [list destroy $w]
+       wm title $w "[appname] ([reponame]): Database Statistics"
+       tkwait window $w
+}
+
+proc do_gc {} {
+       set w [console::new {gc} {Compressing the object database}]
+       console::chain $w {
+               {exec git pack-refs --prune}
+               {exec git reflog expire --all}
+               {exec git repack -a -d -l}
+               {exec git rerere gc}
+       }
+}
+
+proc do_fsck_objects {} {
+       set w [console::new {fsck-objects} \
+               {Verifying the object database with fsck-objects}]
+       set cmd [list git fsck-objects]
+       lappend cmd --full
+       lappend cmd --cache
+       lappend cmd --strict
+       console::exec $w $cmd
+}
diff --git a/git-gui/lib/diff.tcl b/git-gui/lib/diff.tcl
new file mode 100644 (file)
index 0000000..7e715a6
--- /dev/null
@@ -0,0 +1,336 @@
+# git-gui diff viewer
+# Copyright (C) 2006, 2007 Shawn Pearce
+
+proc clear_diff {} {
+       global ui_diff current_diff_path current_diff_header
+       global ui_index ui_workdir
+
+       $ui_diff conf -state normal
+       $ui_diff delete 0.0 end
+       $ui_diff conf -state disabled
+
+       set current_diff_path {}
+       set current_diff_header {}
+
+       $ui_index tag remove in_diff 0.0 end
+       $ui_workdir tag remove in_diff 0.0 end
+}
+
+proc reshow_diff {} {
+       global ui_status_value file_states file_lists
+       global current_diff_path current_diff_side
+
+       set p $current_diff_path
+       if {$p eq {}} {
+               # No diff is being shown.
+       } elseif {$current_diff_side eq {}
+               || [catch {set s $file_states($p)}]
+               || [lsearch -sorted -exact $file_lists($current_diff_side) $p] == -1} {
+               clear_diff
+       } else {
+               show_diff $p $current_diff_side
+       }
+}
+
+proc handle_empty_diff {} {
+       global current_diff_path file_states file_lists
+
+       set path $current_diff_path
+       set s $file_states($path)
+       if {[lindex $s 0] ne {_M}} return
+
+       info_popup "No differences detected.
+
+[short_path $path] has no changes.
+
+The modification date of this file was updated by another application, but the content within the file was not changed.
+
+A rescan will be automatically started to find other files which may have the same state."
+
+       clear_diff
+       display_file $path __
+       rescan {set ui_status_value {Ready.}} 0
+}
+
+proc show_diff {path w {lno {}}} {
+       global file_states file_lists
+       global is_3way_diff diff_active repo_config
+       global ui_diff ui_status_value ui_index ui_workdir
+       global current_diff_path current_diff_side current_diff_header
+
+       if {$diff_active || ![lock_index read]} return
+
+       clear_diff
+       if {$lno == {}} {
+               set lno [lsearch -sorted -exact $file_lists($w) $path]
+               if {$lno >= 0} {
+                       incr lno
+               }
+       }
+       if {$lno >= 1} {
+               $w tag add in_diff $lno.0 [expr {$lno + 1}].0
+       }
+
+       set s $file_states($path)
+       set m [lindex $s 0]
+       set is_3way_diff 0
+       set diff_active 1
+       set current_diff_path $path
+       set current_diff_side $w
+       set current_diff_header {}
+       set ui_status_value "Loading diff of [escape_path $path]..."
+
+       # - Git won't give us the diff, there's nothing to compare to!
+       #
+       if {$m eq {_O}} {
+               set max_sz [expr {128 * 1024}]
+               if {[catch {
+                               set fd [open $path r]
+                               set content [read $fd $max_sz]
+                               close $fd
+                               set sz [file size $path]
+                       } err ]} {
+                       set diff_active 0
+                       unlock_index
+                       set ui_status_value "Unable to display [escape_path $path]"
+                       error_popup "Error loading file:\n\n$err"
+                       return
+               }
+               $ui_diff conf -state normal
+               if {![catch {set type [exec file $path]}]} {
+                       set n [string length $path]
+                       if {[string equal -length $n $path $type]} {
+                               set type [string range $type $n end]
+                               regsub {^:?\s*} $type {} type
+                       }
+                       $ui_diff insert end "* $type\n" d_@
+               }
+               if {[string first "\0" $content] != -1} {
+                       $ui_diff insert end \
+                               "* Binary file (not showing content)." \
+                               d_@
+               } else {
+                       if {$sz > $max_sz} {
+                               $ui_diff insert end \
+"* Untracked file is $sz bytes.
+* Showing only first $max_sz bytes.
+" d_@
+                       }
+                       $ui_diff insert end $content
+                       if {$sz > $max_sz} {
+                               $ui_diff insert end "
+* Untracked file clipped here by [appname].
+* To see the entire file, use an external editor.
+" d_@
+                       }
+               }
+               $ui_diff conf -state disabled
+               set diff_active 0
+               unlock_index
+               set ui_status_value {Ready.}
+               return
+       }
+
+       set cmd [list | git]
+       if {$w eq $ui_index} {
+               lappend cmd diff-index
+               lappend cmd --cached
+       } elseif {$w eq $ui_workdir} {
+               if {[string index $m 0] eq {U}} {
+                       lappend cmd diff
+               } else {
+                       lappend cmd diff-files
+               }
+       }
+
+       lappend cmd -p
+       lappend cmd --no-color
+       if {$repo_config(gui.diffcontext) > 0} {
+               lappend cmd "-U$repo_config(gui.diffcontext)"
+       }
+       if {$w eq $ui_index} {
+               lappend cmd [PARENT]
+       }
+       lappend cmd --
+       lappend cmd $path
+
+       if {[catch {set fd [open $cmd r]} err]} {
+               set diff_active 0
+               unlock_index
+               set ui_status_value "Unable to display [escape_path $path]"
+               error_popup "Error loading diff:\n\n$err"
+               return
+       }
+
+       fconfigure $fd \
+               -blocking 0 \
+               -encoding binary \
+               -translation binary
+       fileevent $fd readable [list read_diff $fd]
+}
+
+proc read_diff {fd} {
+       global ui_diff ui_status_value diff_active
+       global is_3way_diff current_diff_header
+
+       $ui_diff conf -state normal
+       while {[gets $fd line] >= 0} {
+               # -- Cleanup uninteresting diff header lines.
+               #
+               if {   [string match {diff --git *}      $line]
+                       || [string match {diff --cc *}       $line]
+                       || [string match {diff --combined *} $line]
+                       || [string match {--- *}             $line]
+                       || [string match {+++ *}             $line]} {
+                       append current_diff_header $line "\n"
+                       continue
+               }
+               if {[string match {index *} $line]} continue
+               if {$line eq {deleted file mode 120000}} {
+                       set line "deleted symlink"
+               }
+
+               # -- Automatically detect if this is a 3 way diff.
+               #
+               if {[string match {@@@ *} $line]} {set is_3way_diff 1}
+
+               if {[string match {mode *} $line]
+                       || [string match {new file *} $line]
+                       || [string match {deleted file *} $line]
+                       || [string match {Binary files * and * differ} $line]
+                       || $line eq {\ No newline at end of file}
+                       || [regexp {^\* Unmerged path } $line]} {
+                       set tags {}
+               } elseif {$is_3way_diff} {
+                       set op [string range $line 0 1]
+                       switch -- $op {
+                       {  } {set tags {}}
+                       {@@} {set tags d_@}
+                       { +} {set tags d_s+}
+                       { -} {set tags d_s-}
+                       {+ } {set tags d_+s}
+                       {- } {set tags d_-s}
+                       {--} {set tags d_--}
+                       {++} {
+                               if {[regexp {^\+\+([<>]{7} |={7})} $line _g op]} {
+                                       set line [string replace $line 0 1 {  }]
+                                       set tags d$op
+                               } else {
+                                       set tags d_++
+                               }
+                       }
+                       default {
+                               puts "error: Unhandled 3 way diff marker: {$op}"
+                               set tags {}
+                       }
+                       }
+               } else {
+                       set op [string index $line 0]
+                       switch -- $op {
+                       { } {set tags {}}
+                       {@} {set tags d_@}
+                       {-} {set tags d_-}
+                       {+} {
+                               if {[regexp {^\+([<>]{7} |={7})} $line _g op]} {
+                                       set line [string replace $line 0 0 { }]
+                                       set tags d$op
+                               } else {
+                                       set tags d_+
+                               }
+                       }
+                       default {
+                               puts "error: Unhandled 2 way diff marker: {$op}"
+                               set tags {}
+                       }
+                       }
+               }
+               $ui_diff insert end $line $tags
+               if {[string index $line end] eq "\r"} {
+                       $ui_diff tag add d_cr {end - 2c}
+               }
+               $ui_diff insert end "\n" $tags
+       }
+       $ui_diff conf -state disabled
+
+       if {[eof $fd]} {
+               close $fd
+               set diff_active 0
+               unlock_index
+               set ui_status_value {Ready.}
+
+               if {[$ui_diff index end] eq {2.0}} {
+                       handle_empty_diff
+               }
+       }
+}
+
+proc apply_hunk {x y} {
+       global current_diff_path current_diff_header current_diff_side
+       global ui_diff ui_index file_states
+
+       if {$current_diff_path eq {} || $current_diff_header eq {}} return
+       if {![lock_index apply_hunk]} return
+
+       set apply_cmd {git apply --cached --whitespace=nowarn}
+       set mi [lindex $file_states($current_diff_path) 0]
+       if {$current_diff_side eq $ui_index} {
+               set mode unstage
+               lappend apply_cmd --reverse
+               if {[string index $mi 0] ne {M}} {
+                       unlock_index
+                       return
+               }
+       } else {
+               set mode stage
+               if {[string index $mi 1] ne {M}} {
+                       unlock_index
+                       return
+               }
+       }
+
+       set s_lno [lindex [split [$ui_diff index @$x,$y] .] 0]
+       set s_lno [$ui_diff search -backwards -regexp ^@@ $s_lno.0 0.0]
+       if {$s_lno eq {}} {
+               unlock_index
+               return
+       }
+
+       set e_lno [$ui_diff search -forwards -regexp ^@@ "$s_lno + 1 lines" end]
+       if {$e_lno eq {}} {
+               set e_lno end
+       }
+
+       if {[catch {
+               set p [open "| $apply_cmd" w]
+               fconfigure $p -translation binary -encoding binary
+               puts -nonewline $p $current_diff_header
+               puts -nonewline $p [$ui_diff get $s_lno $e_lno]
+               close $p} err]} {
+               error_popup "Failed to $mode selected hunk.\n\n$err"
+               unlock_index
+               return
+       }
+
+       $ui_diff conf -state normal
+       $ui_diff delete $s_lno $e_lno
+       $ui_diff conf -state disabled
+
+       if {[$ui_diff get 1.0 end] eq "\n"} {
+               set o _
+       } else {
+               set o ?
+       }
+
+       if {$current_diff_side eq $ui_index} {
+               set mi ${o}M
+       } elseif {[string index $mi 0] eq {_}} {
+               set mi M$o
+       } else {
+               set mi ?$o
+       }
+       unlock_index
+       display_file $current_diff_path $mi
+       if {$o eq {_}} {
+               clear_diff
+       }
+}
diff --git a/git-gui/lib/error.tcl b/git-gui/lib/error.tcl
new file mode 100644 (file)
index 0000000..d0253ae
--- /dev/null
@@ -0,0 +1,101 @@
+# git-gui branch (create/delete) support
+# Copyright (C) 2006, 2007 Shawn Pearce
+
+proc error_popup {msg} {
+       set title [appname]
+       if {[reponame] ne {}} {
+               append title " ([reponame])"
+       }
+       set cmd [list tk_messageBox \
+               -icon error \
+               -type ok \
+               -title "$title: error" \
+               -message $msg]
+       if {[winfo ismapped .]} {
+               lappend cmd -parent .
+       }
+       eval $cmd
+}
+
+proc warn_popup {msg} {
+       set title [appname]
+       if {[reponame] ne {}} {
+               append title " ([reponame])"
+       }
+       set cmd [list tk_messageBox \
+               -icon warning \
+               -type ok \
+               -title "$title: warning" \
+               -message $msg]
+       if {[winfo ismapped .]} {
+               lappend cmd -parent .
+       }
+       eval $cmd
+}
+
+proc info_popup {msg {parent .}} {
+       set title [appname]
+       if {[reponame] ne {}} {
+               append title " ([reponame])"
+       }
+       tk_messageBox \
+               -parent $parent \
+               -icon info \
+               -type ok \
+               -title $title \
+               -message $msg
+}
+
+proc ask_popup {msg} {
+       set title [appname]
+       if {[reponame] ne {}} {
+               append title " ([reponame])"
+       }
+       return [tk_messageBox \
+               -parent . \
+               -icon question \
+               -type yesno \
+               -title $title \
+               -message $msg]
+}
+
+proc hook_failed_popup {hook msg} {
+       set w .hookfail
+       toplevel $w
+
+       frame $w.m
+       label $w.m.l1 -text "$hook hook failed:" \
+               -anchor w \
+               -justify left \
+               -font font_uibold
+       text $w.m.t \
+               -background white -borderwidth 1 \
+               -relief sunken \
+               -width 80 -height 10 \
+               -font font_diff \
+               -yscrollcommand [list $w.m.sby set]
+       label $w.m.l2 \
+               -text {You must correct the above errors before committing.} \
+               -anchor w \
+               -justify left \
+               -font font_uibold
+       scrollbar $w.m.sby -command [list $w.m.t yview]
+       pack $w.m.l1 -side top -fill x
+       pack $w.m.l2 -side bottom -fill x
+       pack $w.m.sby -side right -fill y
+       pack $w.m.t -side left -fill both -expand 1
+       pack $w.m -side top -fill both -expand 1 -padx 5 -pady 10
+
+       $w.m.t insert 1.0 $msg
+       $w.m.t conf -state disabled
+
+       button $w.ok -text OK \
+               -width 15 \
+               -command "destroy $w"
+       pack $w.ok -side bottom -anchor e -pady 10 -padx 10
+
+       bind $w <Visibility> "grab $w; focus $w"
+       bind $w <Key-Return> "destroy $w"
+       wm title $w "[appname] ([reponame]): error"
+       tkwait window $w
+}
diff --git a/git-gui/lib/index.tcl b/git-gui/lib/index.tcl
new file mode 100644 (file)
index 0000000..4274285
--- /dev/null
@@ -0,0 +1,409 @@
+# git-gui index (add/remove) support
+# Copyright (C) 2006, 2007 Shawn Pearce
+
+proc update_indexinfo {msg pathList after} {
+       global update_index_cp ui_status_value
+
+       if {![lock_index update]} return
+
+       set update_index_cp 0
+       set pathList [lsort $pathList]
+       set totalCnt [llength $pathList]
+       set batch [expr {int($totalCnt * .01) + 1}]
+       if {$batch > 25} {set batch 25}
+
+       set ui_status_value [format \
+               "$msg... %i/%i files (%.2f%%)" \
+               $update_index_cp \
+               $totalCnt \
+               0.0]
+       set fd [open "| git update-index -z --index-info" w]
+       fconfigure $fd \
+               -blocking 0 \
+               -buffering full \
+               -buffersize 512 \
+               -encoding binary \
+               -translation binary
+       fileevent $fd writable [list \
+               write_update_indexinfo \
+               $fd \
+               $pathList \
+               $totalCnt \
+               $batch \
+               $msg \
+               $after \
+               ]
+}
+
+proc write_update_indexinfo {fd pathList totalCnt batch msg after} {
+       global update_index_cp ui_status_value
+       global file_states current_diff_path
+
+       if {$update_index_cp >= $totalCnt} {
+               close $fd
+               unlock_index
+               uplevel #0 $after
+               return
+       }
+
+       for {set i $batch} \
+               {$update_index_cp < $totalCnt && $i > 0} \
+               {incr i -1} {
+               set path [lindex $pathList $update_index_cp]
+               incr update_index_cp
+
+               set s $file_states($path)
+               switch -glob -- [lindex $s 0] {
+               A? {set new _O}
+               M? {set new _M}
+               D_ {set new _D}
+               D? {set new _?}
+               ?? {continue}
+               }
+               set info [lindex $s 2]
+               if {$info eq {}} continue
+
+               puts -nonewline $fd "$info\t[encoding convertto $path]\0"
+               display_file $path $new
+       }
+
+       set ui_status_value [format \
+               "$msg... %i/%i files (%.2f%%)" \
+               $update_index_cp \
+               $totalCnt \
+               [expr {100.0 * $update_index_cp / $totalCnt}]]
+}
+
+proc update_index {msg pathList after} {
+       global update_index_cp ui_status_value
+
+       if {![lock_index update]} return
+
+       set update_index_cp 0
+       set pathList [lsort $pathList]
+       set totalCnt [llength $pathList]
+       set batch [expr {int($totalCnt * .01) + 1}]
+       if {$batch > 25} {set batch 25}
+
+       set ui_status_value [format \
+               "$msg... %i/%i files (%.2f%%)" \
+               $update_index_cp \
+               $totalCnt \
+               0.0]
+       set fd [open "| git update-index --add --remove -z --stdin" w]
+       fconfigure $fd \
+               -blocking 0 \
+               -buffering full \
+               -buffersize 512 \
+               -encoding binary \
+               -translation binary
+       fileevent $fd writable [list \
+               write_update_index \
+               $fd \
+               $pathList \
+               $totalCnt \
+               $batch \
+               $msg \
+               $after \
+               ]
+}
+
+proc write_update_index {fd pathList totalCnt batch msg after} {
+       global update_index_cp ui_status_value
+       global file_states current_diff_path
+
+       if {$update_index_cp >= $totalCnt} {
+               close $fd
+               unlock_index
+               uplevel #0 $after
+               return
+       }
+
+       for {set i $batch} \
+               {$update_index_cp < $totalCnt && $i > 0} \
+               {incr i -1} {
+               set path [lindex $pathList $update_index_cp]
+               incr update_index_cp
+
+               switch -glob -- [lindex $file_states($path) 0] {
+               AD {set new __}
+               ?D {set new D_}
+               _O -
+               AM {set new A_}
+               U? {
+                       if {[file exists $path]} {
+                               set new M_
+                       } else {
+                               set new D_
+                       }
+               }
+               ?M {set new M_}
+               ?? {continue}
+               }
+               puts -nonewline $fd "[encoding convertto $path]\0"
+               display_file $path $new
+       }
+
+       set ui_status_value [format \
+               "$msg... %i/%i files (%.2f%%)" \
+               $update_index_cp \
+               $totalCnt \
+               [expr {100.0 * $update_index_cp / $totalCnt}]]
+}
+
+proc checkout_index {msg pathList after} {
+       global update_index_cp ui_status_value
+
+       if {![lock_index update]} return
+
+       set update_index_cp 0
+       set pathList [lsort $pathList]
+       set totalCnt [llength $pathList]
+       set batch [expr {int($totalCnt * .01) + 1}]
+       if {$batch > 25} {set batch 25}
+
+       set ui_status_value [format \
+               "$msg... %i/%i files (%.2f%%)" \
+               $update_index_cp \
+               $totalCnt \
+               0.0]
+       set cmd [list git checkout-index]
+       lappend cmd --index
+       lappend cmd --quiet
+       lappend cmd --force
+       lappend cmd -z
+       lappend cmd --stdin
+       set fd [open "| $cmd " w]
+       fconfigure $fd \
+               -blocking 0 \
+               -buffering full \
+               -buffersize 512 \
+               -encoding binary \
+               -translation binary
+       fileevent $fd writable [list \
+               write_checkout_index \
+               $fd \
+               $pathList \
+               $totalCnt \
+               $batch \
+               $msg \
+               $after \
+               ]
+}
+
+proc write_checkout_index {fd pathList totalCnt batch msg after} {
+       global update_index_cp ui_status_value
+       global file_states current_diff_path
+
+       if {$update_index_cp >= $totalCnt} {
+               close $fd
+               unlock_index
+               uplevel #0 $after
+               return
+       }
+
+       for {set i $batch} \
+               {$update_index_cp < $totalCnt && $i > 0} \
+               {incr i -1} {
+               set path [lindex $pathList $update_index_cp]
+               incr update_index_cp
+               switch -glob -- [lindex $file_states($path) 0] {
+               U? {continue}
+               ?M -
+               ?D {
+                       puts -nonewline $fd "[encoding convertto $path]\0"
+                       display_file $path ?_
+               }
+               }
+       }
+
+       set ui_status_value [format \
+               "$msg... %i/%i files (%.2f%%)" \
+               $update_index_cp \
+               $totalCnt \
+               [expr {100.0 * $update_index_cp / $totalCnt}]]
+}
+
+proc unstage_helper {txt paths} {
+       global file_states current_diff_path
+
+       if {![lock_index begin-update]} return
+
+       set pathList [list]
+       set after {}
+       foreach path $paths {
+               switch -glob -- [lindex $file_states($path) 0] {
+               A? -
+               M? -
+               D? {
+                       lappend pathList $path
+                       if {$path eq $current_diff_path} {
+                               set after {reshow_diff;}
+                       }
+               }
+               }
+       }
+       if {$pathList eq {}} {
+               unlock_index
+       } else {
+               update_indexinfo \
+                       $txt \
+                       $pathList \
+                       [concat $after {set ui_status_value {Ready.}}]
+       }
+}
+
+proc do_unstage_selection {} {
+       global current_diff_path selected_paths
+
+       if {[array size selected_paths] > 0} {
+               unstage_helper \
+                       {Unstaging selected files from commit} \
+                       [array names selected_paths]
+       } elseif {$current_diff_path ne {}} {
+               unstage_helper \
+                       "Unstaging [short_path $current_diff_path] from commit" \
+                       [list $current_diff_path]
+       }
+}
+
+proc add_helper {txt paths} {
+       global file_states current_diff_path
+
+       if {![lock_index begin-update]} return
+
+       set pathList [list]
+       set after {}
+       foreach path $paths {
+               switch -glob -- [lindex $file_states($path) 0] {
+               _O -
+               ?M -
+               ?D -
+               U? {
+                       lappend pathList $path
+                       if {$path eq $current_diff_path} {
+                               set after {reshow_diff;}
+                       }
+               }
+               }
+       }
+       if {$pathList eq {}} {
+               unlock_index
+       } else {
+               update_index \
+                       $txt \
+                       $pathList \
+                       [concat $after {set ui_status_value {Ready to commit.}}]
+       }
+}
+
+proc do_add_selection {} {
+       global current_diff_path selected_paths
+
+       if {[array size selected_paths] > 0} {
+               add_helper \
+                       {Adding selected files} \
+                       [array names selected_paths]
+       } elseif {$current_diff_path ne {}} {
+               add_helper \
+                       "Adding [short_path $current_diff_path]" \
+                       [list $current_diff_path]
+       }
+}
+
+proc do_add_all {} {
+       global file_states
+
+       set paths [list]
+       foreach path [array names file_states] {
+               switch -glob -- [lindex $file_states($path) 0] {
+               U? {continue}
+               ?M -
+               ?D {lappend paths $path}
+               }
+       }
+       add_helper {Adding all changed files} $paths
+}
+
+proc revert_helper {txt paths} {
+       global file_states current_diff_path
+
+       if {![lock_index begin-update]} return
+
+       set pathList [list]
+       set after {}
+       foreach path $paths {
+               switch -glob -- [lindex $file_states($path) 0] {
+               U? {continue}
+               ?M -
+               ?D {
+                       lappend pathList $path
+                       if {$path eq $current_diff_path} {
+                               set after {reshow_diff;}
+                       }
+               }
+               }
+       }
+
+       set n [llength $pathList]
+       if {$n == 0} {
+               unlock_index
+               return
+       } elseif {$n == 1} {
+               set s "[short_path [lindex $pathList]]"
+       } else {
+               set s "these $n files"
+       }
+
+       set reply [tk_dialog \
+               .confirm_revert \
+               "[appname] ([reponame])" \
+               "Revert changes in $s?
+
+Any unadded changes will be permanently lost by the revert." \
+               question \
+               1 \
+               {Do Nothing} \
+               {Revert Changes} \
+               ]
+       if {$reply == 1} {
+               checkout_index \
+                       $txt \
+                       $pathList \
+                       [concat $after {set ui_status_value {Ready.}}]
+       } else {
+               unlock_index
+       }
+}
+
+proc do_revert_selection {} {
+       global current_diff_path selected_paths
+
+       if {[array size selected_paths] > 0} {
+               revert_helper \
+                       {Reverting selected files} \
+                       [array names selected_paths]
+       } elseif {$current_diff_path ne {}} {
+               revert_helper \
+                       "Reverting [short_path $current_diff_path]" \
+                       [list $current_diff_path]
+       }
+}
+
+proc do_select_commit_type {} {
+       global commit_type selected_commit_type
+
+       if {$selected_commit_type eq {new}
+               && [string match amend* $commit_type]} {
+               create_new_commit
+       } elseif {$selected_commit_type eq {amend}
+               && ![string match amend* $commit_type]} {
+               load_last_commit
+
+               # The amend request was rejected...
+               #
+               if {![string match amend* $commit_type]} {
+                       set selected_commit_type new
+               }
+       }
+}
diff --git a/git-gui/lib/merge.tcl b/git-gui/lib/merge.tcl
new file mode 100644 (file)
index 0000000..24ed24b
--- /dev/null
@@ -0,0 +1,312 @@
+# git-gui branch merge support
+# Copyright (C) 2006, 2007 Shawn Pearce
+
+namespace eval merge {
+
+proc _can_merge {} {
+       global HEAD commit_type file_states
+
+       if {[string match amend* $commit_type]} {
+               info_popup {Cannot merge while amending.
+
+You must finish amending this commit before starting any type of merge.
+}
+               return 0
+       }
+
+       if {[committer_ident] eq {}} {return 0}
+       if {![lock_index merge]} {return 0}
+
+       # -- Our in memory state should match the repository.
+       #
+       repository_state curType curHEAD curMERGE_HEAD
+       if {$commit_type ne $curType || $HEAD ne $curHEAD} {
+               info_popup {Last scanned state does not match repository state.
+
+Another Git program has modified this repository since the last scan.  A rescan must be performed before a merge can be performed.
+
+The rescan will be automatically started now.
+}
+               unlock_index
+               rescan {set ui_status_value {Ready.}}
+               return 0
+       }
+
+       foreach path [array names file_states] {
+               switch -glob -- [lindex $file_states($path) 0] {
+               _O {
+                       continue; # and pray it works!
+               }
+               U? {
+                       error_popup "You are in the middle of a conflicted merge.
+
+File [short_path $path] has merge conflicts.
+
+You must resolve them, add the file, and commit to complete the current merge.  Only then can you begin another merge.
+"
+                       unlock_index
+                       return 0
+               }
+               ?? {
+                       error_popup "You are in the middle of a change.
+
+File [short_path $path] is modified.
+
+You should complete the current commit before starting a merge.  Doing so will help you abort a failed merge, should the need arise.
+"
+                       unlock_index
+                       return 0
+               }
+               }
+       }
+
+       return 1
+}
+
+proc _refs {w list} {
+       set r {}
+       foreach i [$w.source.l curselection] {
+               lappend r [lindex [lindex $list $i] 0]
+       }
+       return $r
+}
+
+proc _visualize {w list} {
+       set revs [_refs $w $list]
+       if {$revs eq {}} return
+       lappend revs --not HEAD
+       do_gitk $revs
+}
+
+proc _start {w list} {
+       global HEAD ui_status_value current_branch
+
+       set cmd [list git merge]
+       set names [_refs $w $list]
+       set revcnt [llength $names]
+       append cmd { } $names
+
+       if {$revcnt == 0} {
+               return
+       } elseif {$revcnt == 1} {
+               set unit branch
+       } elseif {$revcnt <= 15} {
+               set unit branches
+
+               if {[tk_dialog \
+               $w.confirm_octopus \
+               [wm title $w] \
+               "Use octopus merge strategy?
+
+You are merging $revcnt branches at once.  This requires using the octopus merge driver, which may not succeed if there are file-level conflicts.
+" \
+               question \
+               0 \
+               {Cancel} \
+               {Use octopus} \
+               ] != 1} return
+       } else {
+               tk_messageBox \
+                       -icon error \
+                       -type ok \
+                       -title [wm title $w] \
+                       -parent $w \
+                       -message "Too many branches selected.
+
+You have requested to merge $revcnt branches in an octopus merge.  This exceeds Git's internal limit of 15 branches per merge.
+
+Please select fewer branches.  To merge more than 15 branches, merge the branches in batches.
+"
+               return
+       }
+
+       set msg "Merging $current_branch, [join $names {, }]"
+       set ui_status_value "$msg..."
+       set cons [console::new "Merge" $msg]
+       console::exec $cons $cmd \
+               [namespace code [list _finish $revcnt $cons]]
+       bind $w <Destroy> {}
+       destroy $w
+}
+
+proc _finish {revcnt w ok} {
+       console::done $w $ok
+       if {$ok} {
+               set msg {Merge completed successfully.}
+       } else {
+               if {$revcnt != 1} {
+                       info_popup "Octopus merge failed.
+
+Your merge of $revcnt branches has failed.
+
+There are file-level conflicts between the branches which must be resolved manually.
+
+The working directory will now be reset.
+
+You can attempt this merge again by merging only one branch at a time." $w
+
+                       set fd [open "| git read-tree --reset -u HEAD" r]
+                       fconfigure $fd -blocking 0 -translation binary
+                       fileevent $fd readable \
+                               [namespace code [list _reset_wait $fd]]
+                       set ui_status_value {Aborting... please wait...}
+                       return
+               }
+
+               set msg {Merge failed.  Conflict resolution is required.}
+       }
+       unlock_index
+       rescan [list set ui_status_value $msg]
+}
+
+proc dialog {} {
+       global current_branch
+       global M1B
+
+       if {![_can_merge]} return
+
+       set fmt {list %(objectname) %(*objectname) %(refname) %(subject)}
+       set cmd [list git for-each-ref --tcl --format=$fmt]
+       lappend cmd refs/heads
+       lappend cmd refs/remotes
+       lappend cmd refs/tags
+       set fr_fd [open "| $cmd" r]
+       fconfigure $fr_fd -translation binary
+       while {[gets $fr_fd line] > 0} {
+               set line [eval $line]
+               set ref [lindex $line 2]
+               regsub ^refs/(heads|remotes|tags)/ $ref {} ref
+               set subj($ref) [lindex $line 3]
+               lappend sha1([lindex $line 0]) $ref
+               if {[lindex $line 1] ne {}} {
+                       lappend sha1([lindex $line 1]) $ref
+               }
+       }
+       close $fr_fd
+
+       set to_show {}
+       set fr_fd [open "| git rev-list --all --not HEAD"]
+       while {[gets $fr_fd line] > 0} {
+               if {[catch {set ref $sha1($line)}]} continue
+               foreach n $ref {
+                       lappend to_show [list $n $line]
+               }
+       }
+       close $fr_fd
+       set to_show [lsort -unique $to_show]
+
+       set w .merge_setup
+       toplevel $w
+       wm geometry $w "+[winfo rootx .]+[winfo rooty .]"
+
+       set _visualize [namespace code [list _visualize $w $to_show]]
+       set _start [namespace code [list _start $w $to_show]]
+
+       label $w.header \
+               -text "Merge Into $current_branch" \
+               -font font_uibold
+       pack $w.header -side top -fill x
+
+       frame $w.buttons
+       button $w.buttons.visualize -text Visualize -command $_visualize
+       pack $w.buttons.visualize -side left
+       button $w.buttons.create -text Merge -command $_start
+       pack $w.buttons.create -side right
+       button $w.buttons.cancel -text {Cancel} -command [list destroy $w]
+       pack $w.buttons.cancel -side right -padx 5
+       pack $w.buttons -side bottom -fill x -pady 10 -padx 10
+
+       labelframe $w.source -text {Source Branches}
+       listbox $w.source.l \
+               -height 10 \
+               -width 70 \
+               -font font_diff \
+               -selectmode extended \
+               -yscrollcommand [list $w.source.sby set]
+       scrollbar $w.source.sby -command [list $w.source.l yview]
+       pack $w.source.sby -side right -fill y
+       pack $w.source.l -side left -fill both -expand 1
+       pack $w.source -fill both -expand 1 -pady 5 -padx 5
+
+       foreach ref $to_show {
+               set n [lindex $ref 0]
+               if {[string length $n] > 20} {
+                       set n "[string range $n 0 16]..."
+               }
+               $w.source.l insert end [format {%s %-20s %s} \
+                       [string range [lindex $ref 1] 0 5] \
+                       $n \
+                       $subj([lindex $ref 0])]
+       }
+
+       bind $w.source.l <Key-K> [list event generate %W <Shift-Key-Up>]
+       bind $w.source.l <Key-J> [list event generate %W <Shift-Key-Down>]
+       bind $w.source.l <Key-k> [list event generate %W <Key-Up>]
+       bind $w.source.l <Key-j> [list event generate %W <Key-Down>]
+       bind $w.source.l <Key-h> [list event generate %W <Key-Left>]
+       bind $w.source.l <Key-l> [list event generate %W <Key-Right>]
+       bind $w.source.l <Key-v> $_visualize
+
+       bind $w <$M1B-Key-Return> $_start
+       bind $w <Visibility> "grab $w; focus $w.source.l"
+       bind $w <Key-Escape> "unlock_index;destroy $w"
+       bind $w <Destroy> unlock_index
+       wm title $w "[appname] ([reponame]): Merge"
+       tkwait window $w
+}
+
+proc reset_hard {} {
+       global HEAD commit_type file_states
+
+       if {[string match amend* $commit_type]} {
+               info_popup {Cannot abort while amending.
+
+You must finish amending this commit.
+}
+               return
+       }
+
+       if {![lock_index abort]} return
+
+       if {[string match *merge* $commit_type]} {
+               set op merge
+       } else {
+               set op commit
+       }
+
+       if {[ask_popup "Abort $op?
+
+Aborting the current $op will cause *ALL* uncommitted changes to be lost.
+
+Continue with aborting the current $op?"] eq {yes}} {
+               set fd [open "| git read-tree --reset -u HEAD" r]
+               fconfigure $fd -blocking 0 -translation binary
+               fileevent $fd readable [namespace code [list _reset_wait $fd]]
+               set ui_status_value {Aborting... please wait...}
+       } else {
+               unlock_index
+       }
+}
+
+proc _reset_wait {fd} {
+       global ui_comm
+
+       read $fd
+       if {[eof $fd]} {
+               close $fd
+               unlock_index
+
+               $ui_comm delete 0.0 end
+               $ui_comm edit modified false
+
+               catch {file delete [gitdir MERGE_HEAD]}
+               catch {file delete [gitdir rr-cache MERGE_RR]}
+               catch {file delete [gitdir SQUASH_MSG]}
+               catch {file delete [gitdir MERGE_MSG]}
+               catch {file delete [gitdir GITGUI_MSG]}
+
+               rescan {set ui_status_value {Abort completed.  Ready.}}
+       }
+}
+
+}
diff --git a/git-gui/lib/option.tcl b/git-gui/lib/option.tcl
new file mode 100644 (file)
index 0000000..17fcc65
--- /dev/null
@@ -0,0 +1,290 @@
+# git-gui options editor
+# Copyright (C) 2006, 2007 Shawn Pearce
+
+proc save_config {} {
+       global default_config font_descs
+       global repo_config global_config
+       global repo_config_new global_config_new
+
+       foreach option $font_descs {
+               set name [lindex $option 0]
+               set font [lindex $option 1]
+               font configure $font \
+                       -family $global_config_new(gui.$font^^family) \
+                       -size $global_config_new(gui.$font^^size)
+               font configure ${font}bold \
+                       -family $global_config_new(gui.$font^^family) \
+                       -size $global_config_new(gui.$font^^size)
+               set global_config_new(gui.$name) [font configure $font]
+               unset global_config_new(gui.$font^^family)
+               unset global_config_new(gui.$font^^size)
+       }
+
+       foreach name [array names default_config] {
+               set value $global_config_new($name)
+               if {$value ne $global_config($name)} {
+                       if {$value eq $default_config($name)} {
+                               catch {git config --global --unset $name}
+                       } else {
+                               regsub -all "\[{}\]" $value {"} value
+                               git config --global $name $value
+                       }
+                       set global_config($name) $value
+                       if {$value eq $repo_config($name)} {
+                               catch {git config --unset $name}
+                               set repo_config($name) $value
+                       }
+               }
+       }
+
+       foreach name [array names default_config] {
+               set value $repo_config_new($name)
+               if {$value ne $repo_config($name)} {
+                       if {$value eq $global_config($name)} {
+                               catch {git config --unset $name}
+                       } else {
+                               regsub -all "\[{}\]" $value {"} value
+                               git config $name $value
+                       }
+                       set repo_config($name) $value
+               }
+       }
+}
+
+proc do_about {} {
+       global appvers copyright
+       global tcl_patchLevel tk_patchLevel
+
+       set w .about_dialog
+       toplevel $w
+       wm geometry $w "+[winfo rootx .]+[winfo rooty .]"
+
+       label $w.header -text "About [appname]" \
+               -font font_uibold
+       pack $w.header -side top -fill x
+
+       frame $w.buttons
+       button $w.buttons.close -text {Close} \
+               -default active \
+               -command [list destroy $w]
+       pack $w.buttons.close -side right
+       pack $w.buttons -side bottom -fill x -pady 10 -padx 10
+
+       label $w.desc \
+               -text "git-gui - a graphical user interface for Git.
+$copyright" \
+               -padx 5 -pady 5 \
+               -justify left \
+               -anchor w \
+               -borderwidth 1 \
+               -relief solid
+       pack $w.desc -side top -fill x -padx 5 -pady 5
+
+       set v {}
+       append v "git-gui version $appvers\n"
+       append v "[git version]\n"
+       append v "\n"
+       if {$tcl_patchLevel eq $tk_patchLevel} {
+               append v "Tcl/Tk version $tcl_patchLevel"
+       } else {
+               append v "Tcl version $tcl_patchLevel"
+               append v ", Tk version $tk_patchLevel"
+       }
+
+       label $w.vers \
+               -text $v \
+               -padx 5 -pady 5 \
+               -justify left \
+               -anchor w \
+               -borderwidth 1 \
+               -relief solid
+       pack $w.vers -side top -fill x -padx 5 -pady 5
+
+       menu $w.ctxm -tearoff 0
+       $w.ctxm add command \
+               -label {Copy} \
+               -command "
+               clipboard clear
+               clipboard append -format STRING -type STRING -- \[$w.vers cget -text\]
+       "
+
+       bind $w <Visibility> "grab $w; focus $w.buttons.close"
+       bind $w <Key-Escape> "destroy $w"
+       bind $w <Key-Return> "destroy $w"
+       bind_button3 $w.vers "tk_popup $w.ctxm %X %Y; grab $w; focus $w"
+       wm title $w "About [appname]"
+       tkwait window $w
+}
+
+proc do_options {} {
+       global repo_config global_config font_descs
+       global repo_config_new global_config_new
+
+       array unset repo_config_new
+       array unset global_config_new
+       foreach name [array names repo_config] {
+               set repo_config_new($name) $repo_config($name)
+       }
+       load_config 1
+       foreach name [array names repo_config] {
+               switch -- $name {
+               gui.diffcontext {continue}
+               }
+               set repo_config_new($name) $repo_config($name)
+       }
+       foreach name [array names global_config] {
+               set global_config_new($name) $global_config($name)
+       }
+
+       set w .options_editor
+       toplevel $w
+       wm geometry $w "+[winfo rootx .]+[winfo rooty .]"
+
+       label $w.header -text "Options" \
+               -font font_uibold
+       pack $w.header -side top -fill x
+
+       frame $w.buttons
+       button $w.buttons.restore -text {Restore Defaults} \
+               -default normal \
+               -command do_restore_defaults
+       pack $w.buttons.restore -side left
+       button $w.buttons.save -text Save \
+               -default active \
+               -command [list do_save_config $w]
+       pack $w.buttons.save -side right
+       button $w.buttons.cancel -text {Cancel} \
+               -default normal \
+               -command [list destroy $w]
+       pack $w.buttons.cancel -side right -padx 5
+       pack $w.buttons -side bottom -fill x -pady 10 -padx 10
+
+       labelframe $w.repo -text "[reponame] Repository"
+       labelframe $w.global -text {Global (All Repositories)}
+       pack $w.repo -side left -fill both -expand 1 -pady 5 -padx 5
+       pack $w.global -side right -fill both -expand 1 -pady 5 -padx 5
+
+       set optid 0
+       foreach option {
+               {t user.name {User Name}}
+               {t user.email {Email Address}}
+
+               {b merge.summary {Summarize Merge Commits}}
+               {i-1..5 merge.verbosity {Merge Verbosity}}
+
+               {b gui.trustmtime  {Trust File Modification Timestamps}}
+               {i-1..99 gui.diffcontext {Number of Diff Context Lines}}
+               {t gui.newbranchtemplate {New Branch Name Template}}
+               } {
+               set type [lindex $option 0]
+               set name [lindex $option 1]
+               set text [lindex $option 2]
+               incr optid
+               foreach f {repo global} {
+                       switch -glob -- $type {
+                       b {
+                               checkbutton $w.$f.$optid -text $text \
+                                       -variable ${f}_config_new($name) \
+                                       -onvalue true \
+                                       -offvalue false
+                               pack $w.$f.$optid -side top -anchor w
+                       }
+                       i-* {
+                               regexp -- {-(\d+)\.\.(\d+)$} $type _junk min max
+                               frame $w.$f.$optid
+                               label $w.$f.$optid.l -text "$text:"
+                               pack $w.$f.$optid.l -side left -anchor w -fill x
+                               spinbox $w.$f.$optid.v \
+                                       -textvariable ${f}_config_new($name) \
+                                       -from $min \
+                                       -to $max \
+                                       -increment 1 \
+                                       -width [expr {1 + [string length $max]}]
+                               bind $w.$f.$optid.v <FocusIn> {%W selection range 0 end}
+                               pack $w.$f.$optid.v -side right -anchor e -padx 5
+                               pack $w.$f.$optid -side top -anchor w -fill x
+                       }
+                       t {
+                               frame $w.$f.$optid
+                               label $w.$f.$optid.l -text "$text:"
+                               entry $w.$f.$optid.v \
+                                       -borderwidth 1 \
+                                       -relief sunken \
+                                       -width 20 \
+                                       -textvariable ${f}_config_new($name)
+                               pack $w.$f.$optid.l -side left -anchor w
+                               pack $w.$f.$optid.v -side left -anchor w \
+                                       -fill x -expand 1 \
+                                       -padx 5
+                               pack $w.$f.$optid -side top -anchor w -fill x
+                       }
+                       }
+               }
+       }
+
+       set all_fonts [lsort [font families]]
+       foreach option $font_descs {
+               set name [lindex $option 0]
+               set font [lindex $option 1]
+               set text [lindex $option 2]
+
+               set global_config_new(gui.$font^^family) \
+                       [font configure $font -family]
+               set global_config_new(gui.$font^^size) \
+                       [font configure $font -size]
+
+               frame $w.global.$name
+               label $w.global.$name.l -text "$text:"
+               pack $w.global.$name.l -side left -anchor w -fill x
+               eval tk_optionMenu $w.global.$name.family \
+                       global_config_new(gui.$font^^family) \
+                       $all_fonts
+               spinbox $w.global.$name.size \
+                       -textvariable global_config_new(gui.$font^^size) \
+                       -from 2 -to 80 -increment 1 \
+                       -width 3
+               bind $w.global.$name.size <FocusIn> {%W selection range 0 end}
+               pack $w.global.$name.size -side right -anchor e
+               pack $w.global.$name.family -side right -anchor e
+               pack $w.global.$name -side top -anchor w -fill x
+       }
+
+       bind $w <Visibility> "grab $w; focus $w.buttons.save"
+       bind $w <Key-Escape> "destroy $w"
+       bind $w <Key-Return> [list do_save_config $w]
+       wm title $w "[appname] ([reponame]): Options"
+       tkwait window $w
+}
+
+proc do_restore_defaults {} {
+       global font_descs default_config repo_config
+       global repo_config_new global_config_new
+
+       foreach name [array names default_config] {
+               set repo_config_new($name) $default_config($name)
+               set global_config_new($name) $default_config($name)
+       }
+
+       foreach option $font_descs {
+               set name [lindex $option 0]
+               set repo_config(gui.$name) $default_config(gui.$name)
+       }
+       apply_config
+
+       foreach option $font_descs {
+               set name [lindex $option 0]
+               set font [lindex $option 1]
+               set global_config_new(gui.$font^^family) \
+                       [font configure $font -family]
+               set global_config_new(gui.$font^^size) \
+                       [font configure $font -size]
+       }
+}
+
+proc do_save_config {w} {
+       if {[catch {save_config} err]} {
+               error_popup "Failed to completely save options:\n\n$err"
+       }
+       reshow_diff
+       destroy $w
+}
diff --git a/git-gui/lib/remote.tcl b/git-gui/lib/remote.tcl
new file mode 100644 (file)
index 0000000..99f353e
--- /dev/null
@@ -0,0 +1,159 @@
+# git-gui remote management
+# Copyright (C) 2006, 2007 Shawn Pearce
+
+proc is_tracking_branch {name} {
+       global tracking_branches
+
+       if {![catch {set info $tracking_branches($name)}]} {
+               return 1
+       }
+       foreach t [array names tracking_branches] {
+               if {[string match {*/\*} $t] && [string match $t $name]} {
+                       return 1
+               }
+       }
+       return 0
+}
+
+proc all_tracking_branches {} {
+       global tracking_branches
+
+       set all_trackings {}
+       set cmd {}
+       foreach name [array names tracking_branches] {
+               if {[regsub {/\*$} $name {} name]} {
+                       lappend cmd $name
+               } else {
+                       regsub ^refs/(heads|remotes)/ $name {} name
+                       lappend all_trackings $name
+               }
+       }
+
+       if {$cmd ne {}} {
+               set fd [open "| git for-each-ref --format=%(refname) $cmd" r]
+               while {[gets $fd name] > 0} {
+                       regsub ^refs/(heads|remotes)/ $name {} name
+                       lappend all_trackings $name
+               }
+               close $fd
+       }
+
+       return [lsort -unique $all_trackings]
+}
+
+proc load_all_remotes {} {
+       global repo_config
+       global all_remotes tracking_branches
+
+       set all_remotes [list]
+       array unset tracking_branches
+
+       set rm_dir [gitdir remotes]
+       if {[file isdirectory $rm_dir]} {
+               set all_remotes [glob \
+                       -types f \
+                       -tails \
+                       -nocomplain \
+                       -directory $rm_dir *]
+
+               foreach name $all_remotes {
+                       catch {
+                               set fd [open [file join $rm_dir $name] r]
+                               while {[gets $fd line] >= 0} {
+                                       if {![regexp {^Pull:[   ]*([^:]+):(.+)$} \
+                                               $line line src dst]} continue
+                                       if {![regexp ^refs/ $dst]} {
+                                               set dst "refs/heads/$dst"
+                                       }
+                                       set tracking_branches($dst) [list $name $src]
+                               }
+                               close $fd
+                       }
+               }
+       }
+
+       foreach line [array names repo_config remote.*.url] {
+               if {![regexp ^remote\.(.*)\.url\$ $line line name]} continue
+               lappend all_remotes $name
+
+               if {[catch {set fl $repo_config(remote.$name.fetch)}]} {
+                       set fl {}
+               }
+               foreach line $fl {
+                       if {![regexp {^([^:]+):(.+)$} $line line src dst]} continue
+                       if {![regexp ^refs/ $dst]} {
+                               set dst "refs/heads/$dst"
+                       }
+                       set tracking_branches($dst) [list $name $src]
+               }
+       }
+
+       set all_remotes [lsort -unique $all_remotes]
+}
+
+proc populate_fetch_menu {} {
+       global all_remotes repo_config
+
+       set m .mbar.fetch
+       foreach r $all_remotes {
+               set enable 0
+               if {![catch {set a $repo_config(remote.$r.url)}]} {
+                       if {![catch {set a $repo_config(remote.$r.fetch)}]} {
+                               set enable 1
+                       }
+               } else {
+                       catch {
+                               set fd [open [gitdir remotes $r] r]
+                               while {[gets $fd n] >= 0} {
+                                       if {[regexp {^Pull:[ \t]*([^:]+):} $n]} {
+                                               set enable 1
+                                               break
+                                       }
+                               }
+                               close $fd
+                       }
+               }
+
+               if {$enable} {
+                       $m add command \
+                               -label "Fetch from $r..." \
+                               -command [list fetch_from $r]
+               }
+       }
+}
+
+proc populate_push_menu {} {
+       global all_remotes repo_config
+
+       set m .mbar.push
+       set fast_count 0
+       foreach r $all_remotes {
+               set enable 0
+               if {![catch {set a $repo_config(remote.$r.url)}]} {
+                       if {![catch {set a $repo_config(remote.$r.push)}]} {
+                               set enable 1
+                       }
+               } else {
+                       catch {
+                               set fd [open [gitdir remotes $r] r]
+                               while {[gets $fd n] >= 0} {
+                                       if {[regexp {^Push:[ \t]*([^:]+):} $n]} {
+                                               set enable 1
+                                               break
+                                       }
+                               }
+                               close $fd
+                       }
+               }
+
+               if {$enable} {
+                       if {!$fast_count} {
+                               $m add separator
+                       }
+                       $m add command \
+                               -label "Push to $r..." \
+                               -command [list push_to $r]
+                       incr fast_count
+               }
+       }
+}
diff --git a/git-gui/lib/shortcut.tcl b/git-gui/lib/shortcut.tcl
new file mode 100644 (file)
index 0000000..ebf72e4
--- /dev/null
@@ -0,0 +1,141 @@
+# git-gui desktop icon creators
+# Copyright (C) 2006, 2007 Shawn Pearce
+
+proc do_windows_shortcut {} {
+       global argv0
+
+       set fn [tk_getSaveFile \
+               -parent . \
+               -title "[appname] ([reponame]): Create Desktop Icon" \
+               -initialfile "Git [reponame].bat"]
+       if {$fn != {}} {
+               if {[catch {
+                               set fd [open $fn w]
+                               puts $fd "@ECHO Entering [reponame]"
+                               puts $fd "@ECHO Starting git-gui... please wait..."
+                               puts $fd "@SET PATH=[file normalize [gitexec]];%PATH%"
+                               puts $fd "@SET GIT_DIR=[file normalize [gitdir]]"
+                               puts -nonewline $fd "@\"[info nameofexecutable]\""
+                               puts $fd " \"[file normalize $argv0]\""
+                               close $fd
+                       } err]} {
+                       error_popup "Cannot write script:\n\n$err"
+               }
+       }
+}
+
+proc do_cygwin_shortcut {} {
+       global argv0
+
+       if {[catch {
+               set desktop [exec cygpath \
+                       --windows \
+                       --absolute \
+                       --long-name \
+                       --desktop]
+               }]} {
+                       set desktop .
+       }
+       set fn [tk_getSaveFile \
+               -parent . \
+               -title "[appname] ([reponame]): Create Desktop Icon" \
+               -initialdir $desktop \
+               -initialfile "Git [reponame].bat"]
+       if {$fn != {}} {
+               if {[catch {
+                               set fd [open $fn w]
+                               set sh [exec cygpath \
+                                       --windows \
+                                       --absolute \
+                                       /bin/sh]
+                               set me [exec cygpath \
+                                       --unix \
+                                       --absolute \
+                                       $argv0]
+                               set gd [exec cygpath \
+                                       --unix \
+                                       --absolute \
+                                       [gitdir]]
+                               set gw [exec cygpath \
+                                       --windows \
+                                       --absolute \
+                                       [file dirname [gitdir]]]
+                               regsub -all ' $me "'\\''" me
+                               regsub -all ' $gd "'\\''" gd
+                               puts $fd "@ECHO Entering $gw"
+                               puts $fd "@ECHO Starting git-gui... please wait..."
+                               puts -nonewline $fd "@\"$sh\" --login -c \""
+                               puts -nonewline $fd "GIT_DIR='$gd'"
+                               puts -nonewline $fd " '$me'"
+                               puts $fd "&\""
+                               close $fd
+                       } err]} {
+                       error_popup "Cannot write script:\n\n$err"
+               }
+       }
+}
+
+proc do_macosx_app {} {
+       global argv0 env
+
+       set fn [tk_getSaveFile \
+               -parent . \
+               -title "[appname] ([reponame]): Create Desktop Icon" \
+               -initialdir [file join $env(HOME) Desktop] \
+               -initialfile "Git [reponame].app"]
+       if {$fn != {}} {
+               if {[catch {
+                               set Contents [file join $fn Contents]
+                               set MacOS [file join $Contents MacOS]
+                               set exe [file join $MacOS git-gui]
+
+                               file mkdir $MacOS
+
+                               set fd [open [file join $Contents Info.plist] w]
+                               puts $fd {<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+       <key>CFBundleDevelopmentRegion</key>
+       <string>English</string>
+       <key>CFBundleExecutable</key>
+       <string>git-gui</string>
+       <key>CFBundleIdentifier</key>
+       <string>org.spearce.git-gui</string>
+       <key>CFBundleInfoDictionaryVersion</key>
+       <string>6.0</string>
+       <key>CFBundlePackageType</key>
+       <string>APPL</string>
+       <key>CFBundleSignature</key>
+       <string>????</string>
+       <key>CFBundleVersion</key>
+       <string>1.0</string>
+       <key>NSPrincipalClass</key>
+       <string>NSApplication</string>
+</dict>
+</plist>}
+                               close $fd
+
+                               set fd [open $exe w]
+                               set gd [file normalize [gitdir]]
+                               set ep [file normalize [gitexec]]
+                               regsub -all ' $gd "'\\''" gd
+                               regsub -all ' $ep "'\\''" ep
+                               puts $fd "#!/bin/sh"
+                               foreach name [array names env] {
+                                       if {[string match GIT_* $name]} {
+                                               regsub -all ' $env($name) "'\\''" v
+                                               puts $fd "export $name='$v'"
+                                       }
+                               }
+                               puts $fd "export PATH='$ep':\$PATH"
+                               puts $fd "export GIT_DIR='$gd'"
+                               puts $fd "exec [file normalize $argv0]"
+                               close $fd
+
+                               file attributes $exe -permissions u+x,g+x,o+x
+                       } err]} {
+                       error_popup "Cannot write icon:\n\n$err"
+               }
+       }
+}
diff --git a/git-gui/lib/transport.tcl b/git-gui/lib/transport.tcl
new file mode 100644 (file)
index 0000000..c0e7d20
--- /dev/null
@@ -0,0 +1,164 @@
+# git-gui transport (fetch/push) support
+# Copyright (C) 2006, 2007 Shawn Pearce
+
+proc fetch_from {remote} {
+       set w [console::new \
+               "fetch $remote" \
+               "Fetching new changes from $remote"]
+       set cmd [list git fetch]
+       lappend cmd $remote
+       console::exec $w $cmd
+}
+
+proc push_to {remote} {
+       set w [console::new \
+               "push $remote" \
+               "Pushing changes to $remote"]
+       set cmd [list git push]
+       lappend cmd -v
+       lappend cmd $remote
+       console::exec $w $cmd
+}
+
+proc start_push_anywhere_action {w} {
+       global push_urltype push_remote push_url push_thin push_tags
+
+       set r_url {}
+       switch -- $push_urltype {
+       remote {set r_url $push_remote}
+       url {set r_url $push_url}
+       }
+       if {$r_url eq {}} return
+
+       set cmd [list git push]
+       lappend cmd -v
+       if {$push_thin} {
+               lappend cmd --thin
+       }
+       if {$push_tags} {
+               lappend cmd --tags
+       }
+       lappend cmd $r_url
+       set cnt 0
+       foreach i [$w.source.l curselection] {
+               set b [$w.source.l get $i]
+               lappend cmd "refs/heads/$b:refs/heads/$b"
+               incr cnt
+       }
+       if {$cnt == 0} {
+               return
+       } elseif {$cnt == 1} {
+               set unit branch
+       } else {
+               set unit branches
+       }
+
+       set cons [console::new \
+               "push $r_url" \
+               "Pushing $cnt $unit to $r_url"]
+       console::exec $cons $cmd
+       destroy $w
+}
+
+trace add variable push_remote write \
+       [list radio_selector push_urltype remote]
+
+proc do_push_anywhere {} {
+       global all_heads all_remotes current_branch
+       global push_urltype push_remote push_url push_thin push_tags
+
+       set w .push_setup
+       toplevel $w
+       wm geometry $w "+[winfo rootx .]+[winfo rooty .]"
+
+       label $w.header -text {Push Branches} -font font_uibold
+       pack $w.header -side top -fill x
+
+       frame $w.buttons
+       button $w.buttons.create -text Push \
+               -default active \
+               -command [list start_push_anywhere_action $w]
+       pack $w.buttons.create -side right
+       button $w.buttons.cancel -text {Cancel} \
+               -default normal \
+               -command [list destroy $w]
+       pack $w.buttons.cancel -side right -padx 5
+       pack $w.buttons -side bottom -fill x -pady 10 -padx 10
+
+       labelframe $w.source -text {Source Branches}
+       listbox $w.source.l \
+               -height 10 \
+               -width 70 \
+               -selectmode extended \
+               -yscrollcommand [list $w.source.sby set]
+       foreach h $all_heads {
+               $w.source.l insert end $h
+               if {$h eq $current_branch} {
+                       $w.source.l select set end
+               }
+       }
+       scrollbar $w.source.sby -command [list $w.source.l yview]
+       pack $w.source.sby -side right -fill y
+       pack $w.source.l -side left -fill both -expand 1
+       pack $w.source -fill both -expand 1 -pady 5 -padx 5
+
+       labelframe $w.dest -text {Destination Repository}
+       if {$all_remotes ne {}} {
+               radiobutton $w.dest.remote_r \
+                       -text {Remote:} \
+                       -value remote \
+                       -variable push_urltype
+               eval tk_optionMenu $w.dest.remote_m push_remote $all_remotes
+               grid $w.dest.remote_r $w.dest.remote_m -sticky w
+               if {[lsearch -sorted -exact $all_remotes origin] != -1} {
+                       set push_remote origin
+               } else {
+                       set push_remote [lindex $all_remotes 0]
+               }
+               set push_urltype remote
+       } else {
+               set push_urltype url
+       }
+       radiobutton $w.dest.url_r \
+               -text {Arbitrary URL:} \
+               -value url \
+               -variable push_urltype
+       entry $w.dest.url_t \
+               -borderwidth 1 \
+               -relief sunken \
+               -width 50 \
+               -textvariable push_url \
+               -validate key \
+               -validatecommand {
+                       if {%d == 1 && [regexp {\s} %S]} {return 0}
+                       if {%d == 1 && [string length %S] > 0} {
+                               set push_urltype url
+                       }
+                       return 1
+               }
+       grid $w.dest.url_r $w.dest.url_t -sticky we -padx {0 5}
+       grid columnconfigure $w.dest 1 -weight 1
+       pack $w.dest -anchor nw -fill x -pady 5 -padx 5
+
+       labelframe $w.options -text {Transfer Options}
+       checkbutton $w.options.thin \
+               -text {Use thin pack (for slow network connections)} \
+               -variable push_thin
+       grid $w.options.thin -columnspan 2 -sticky w
+       checkbutton $w.options.tags \
+               -text {Include tags} \
+               -variable push_tags
+       grid $w.options.tags -columnspan 2 -sticky w
+       grid columnconfigure $w.options 1 -weight 1
+       pack $w.options -anchor nw -fill x -pady 5 -padx 5
+
+       set push_url {}
+       set push_thin 0
+       set push_tags 0
+
+       bind $w <Visibility> "grab $w; focus $w.buttons.create"
+       bind $w <Key-Escape> "destroy $w"
+       bind $w <Key-Return> [list start_push_anywhere_action $w]
+       wm title $w "[appname] ([reponame]): Push"
+       tkwait window $w
+}
index 9360804711d6b81983e53df67c86ddc8082e2ab4..58570dff137adfdeb72ebf3f088af7379eded522 100755 (executable)
@@ -12,7 +12,7 @@ fi
 laf="$GIT_DIR/lost-found"
 rm -fr "$laf" && mkdir -p "$laf/commit" "$laf/other" || exit
 
-git fsck --full |
+git fsck --full --no-reflogs |
 while read dangling type sha1
 do
        case "$dangling" in
index fa4589173f426d6172883c47479c52b8700cafa8..7ebbce4bdbaf243a7a5612c024216b8ccf8eae44 100755 (executable)
@@ -16,10 +16,10 @@ test -z "$(git ls-files -u)" ||
 LF='
 '
 
-all_strategies='recur recursive octopus resolve stupid ours'
+all_strategies='recur recursive octopus resolve stupid ours subtree'
 default_twohead_strategies='recursive'
 default_octopus_strategies='octopus'
-no_trivial_merge_strategies='ours'
+no_trivial_merge_strategies='ours subtree'
 use_strategies=
 
 index_merge=t
index 52013fe76dba73e19244fadf63f91677ce8e6a40..576379912793c9740620892cd3ee1cf7d2f18cfe 100755 (executable)
@@ -297,9 +297,9 @@ sub update_remote {
        } elsif ($name eq 'default') {
                undef @remotes;
                for (sort keys %$remote) {
-                       my $do_fetch = $git->config_boolean("remote." . $_ .
+                       my $do_fetch = $git->config_bool("remote." . $_ .
                                                    ".skipDefaultUpdate");
-                       if (!defined($do_fetch) || $do_fetch ne "true") {
+                       unless ($do_fetch) {
                                push @remotes, $_;
                        }
                }
index 4eacc3a059ca3d90305cd1f28b91460b56e07167..ba577d4ce1b0aaf5777667410dc9cca6c999df1d 100755 (executable)
@@ -9,25 +9,48 @@ LONG_USAGE='Summarizes the changes since <commit> to the standard output,
 and includes <url> in the message generated.'
 SUBDIRECTORY_OK='Yes'
 . git-sh-setup
+. git-parse-remote
 
-revision=$1
+base=$1
 url=$2
 head=${3-HEAD}
 
-[ "$revision" ] || usage
+[ "$base" ] || usage
 [ "$url" ] || usage
 
-baserev=`git-rev-parse --verify "$revision"^0` &&
+baserev=`git-rev-parse --verify "$base"^0` &&
 headrev=`git-rev-parse --verify "$head"^0` || exit
 
+merge_base=`git merge-base $baserev $headrev` ||
+die "fatal: No commits in common between $base and $head"
+
+url="`get_remote_url "$url"`"
+branch=`git peek-remote "$url" \
+       | sed -n -e "/^$headrev refs.heads./{
+               s/^.*   refs.heads.//
+               p
+               q
+       }"`
+if [ -z "$branch" ]; then
+       echo "warn: No branch of $url is at:" >&2
+       git log --max-count=1 --pretty='format:warn:   %h: %s' $headrev >&2
+       echo "warn: Are you sure you pushed $head there?" >&2
+       echo >&2
+       echo >&2
+       branch=..BRANCH.NOT.VERIFIED..
+       status=1
+fi
+
+PAGER=
+export PAGER
 echo "The following changes since commit $baserev:"
-git log --max-count=1 --pretty=short "$baserev" |
-git-shortlog | sed -e 's/^\(.\)/  \1/'
+git shortlog --max-count=1 $baserev | sed -e 's/^\(.\)/  \1/'
 
-echo "are found in the git repository at:" 
+echo "are available in the git repository at:"
 echo
-echo "  $url"
+echo "  $url $branch"
 echo
 
-git log  $baserev..$headrev | git-shortlog ;
-git diff -M --stat --summary $baserev..$headrev
+git shortlog ^$baserev $headrev
+git diff -M --stat --summary $merge_base $headrev
+exit $status
index fee6d98d9cba708828c36a2d2a29dbec8662c36b..a172d7ce25b40f192b0036d29a8a60e62649159c 100755 (executable)
@@ -71,7 +71,7 @@ then
                die "Cannot do a soft reset in the middle of a merge."
        fi
 else
-       git-read-tree --reset $update "$rev" || exit
+       git-read-tree -v --reset $update "$rev" || exit
 fi
 
 # Any resets update HEAD to the head being switched to.
index e60d8777f0a14234c1b695b08e37ae173dd48d26..eb876f88ddec315991c2634a976eca8e5b99a1ba 100755 (executable)
@@ -154,8 +154,8 @@ if ($@) {
        $term = new FakeTerm "$@: going non-interactive";
 }
 
-my $def_chain = $repo->config_boolean('sendemail.chainreplyto');
-if ($def_chain and $def_chain eq 'false') {
+my $def_chain = $repo->config_bool('sendemail.chainreplyto');
+if (defined $def_chain and not $def_chain) {
     $chain_reply_to = 0;
 }
 
@@ -462,10 +462,13 @@ sub send_message
        }
 
        my $cc = join(", ", unique_email_list(@cc));
+       my $ccline = "";
+       if ($cc ne '') {
+               $ccline = "\nCc: $cc";
+       }
        $from = sanitize_address_rfc822($from);
        my $header = "From: $from
-To: $to
-Cc: $cc
+To: $to${ccline}
 Subject: $subject
 Date: $date
 Message-Id: $message_id
diff --git a/git.c b/git.c
index 7def319e609454cd2389d2fc60caeaa5831d339e..f20090721aa799a8dfd780291c8a83bd834b45b5 100644 (file)
--- a/git.c
+++ b/git.c
@@ -234,6 +234,7 @@ static void handle_internal_command(int argc, const char **argv, char **envp)
                { "cat-file", cmd_cat_file, RUN_SETUP },
                { "checkout-index", cmd_checkout_index, RUN_SETUP },
                { "check-ref-format", cmd_check_ref_format },
+               { "check-attr", cmd_check_attr, RUN_SETUP | NOT_BARE },
                { "cherry", cmd_cherry, RUN_SETUP },
                { "cherry-pick", cmd_cherry_pick, RUN_SETUP | NOT_BARE },
                { "commit-tree", cmd_commit_tree, RUN_SETUP },
index 2170424599e2013ac806d0c021fa4709ee6d7244..3a45eb876157d9979f51fe88ecc5d5ae4a93ff1c 100644 (file)
@@ -1,4 +1,7 @@
 # Pass --without docs to rpmbuild if you don't want the documentation
+
+%define python_path /usr/bin/python
+
 Name:          git
 Version:       @@VERSION@@
 Release:       1%{?dist}
@@ -9,7 +12,7 @@ URL:           http://kernel.org/pub/software/scm/git/
 Source:        http://kernel.org/pub/software/scm/git/%{name}-%{version}.tar.gz
 BuildRequires: zlib-devel >= 1.2, openssl-devel, curl-devel, expat-devel  %{!?_without_docs:, xmlto, asciidoc > 6.0.3}
 BuildRoot:     %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n)
-Requires:      git-core, git-svn, git-cvs, git-arch, git-email, gitk, git-gui, perl-Git
+Requires:      git-core, git-svn, git-cvs, git-arch, git-email, gitk, git-gui, git-p4, perl-Git
 
 %description
 Git is a fast, scalable, distributed revision control system with an
@@ -50,6 +53,13 @@ Requires:       git-core = %{version}-%{release}, tla
 %description arch
 Git tools for importing Arch repositories.
 
+%package p4
+Summary:        Git tools for importing Perforce repositories
+Group:          Development/Tools
+Requires:       git-core = %{version}-%{release}, python
+%description p4
+Git tools for importing Perforce repositories.
+
 %package email
 Summary:        Git tools for sending email
 Group:          Development/Tools
@@ -85,25 +95,25 @@ Perl interface to Git
 %setup -q
 
 %build
-make %{_smp_mflags} CFLAGS="$RPM_OPT_FLAGS" WITH_OWN_SUBPROCESS_PY=YesPlease \
+make %{_smp_mflags} CFLAGS="$RPM_OPT_FLAGS" WITH_P4IMPORT=YesPlease \
      ETC_GITCONFIG=/etc/gitconfig \
-     prefix=%{_prefix} all %{!?_without_docs: doc}
+     prefix=%{_prefix} PYTHON_PATH=%{python_path} all %{!?_without_docs: doc}
 
 %install
 rm -rf $RPM_BUILD_ROOT
 make %{_smp_mflags} CFLAGS="$RPM_OPT_FLAGS" DESTDIR=$RPM_BUILD_ROOT \
-     WITH_OWN_SUBPROCESS_PY=YesPlease \
+     WITH_P4IMPORT=YesPlease prefix=%{_prefix} mandir=%{_mandir} \
      ETC_GITCONFIG=/etc/gitconfig \
-     prefix=%{_prefix} mandir=%{_mandir} INSTALLDIRS=vendor \
-     install %{!?_without_docs: install-doc}
+     PYTHON_PATH=%{python_path} \
+     INSTALLDIRS=vendor install %{!?_without_docs: install-doc}
 find $RPM_BUILD_ROOT -type f -name .packlist -exec rm -f {} ';'
 find $RPM_BUILD_ROOT -type f -name '*.bs' -empty -exec rm -f {} ';'
 find $RPM_BUILD_ROOT -type f -name perllocal.pod -exec rm -f {} ';'
 
-(find $RPM_BUILD_ROOT%{_bindir} -type f | grep -vE "archimport|svn|cvs|email|gitk|git-gui|git-citool" | sed -e s@^$RPM_BUILD_ROOT@@)               > bin-man-doc-files
+(find $RPM_BUILD_ROOT%{_bindir} -type f | grep -vE "p4import|archimport|svn|cvs|email|gitk|git-gui|git-citool" | sed -e s@^$RPM_BUILD_ROOT@@)               > bin-man-doc-files
 (find $RPM_BUILD_ROOT%{perl_vendorlib} -type f | sed -e s@^$RPM_BUILD_ROOT@@) >> perl-files
 %if %{!?_without_docs:1}0
-(find $RPM_BUILD_ROOT%{_mandir} $RPM_BUILD_ROOT/Documentation -type f | grep -vE "archimport|svn|git-cvs|email|gitk|git-gui|git-citool" | sed -e s@^$RPM_BUILD_ROOT@@ -e 's/$/*/' ) >> bin-man-doc-files
+(find $RPM_BUILD_ROOT%{_mandir} $RPM_BUILD_ROOT/Documentation -type f | grep -vE "p4import|archimport|svn|git-cvs|email|gitk|git-gui|git-citool" | sed -e s@^$RPM_BUILD_ROOT@@ -e 's/$/*/' ) >> bin-man-doc-files
 %else
 rm -rf $RPM_BUILD_ROOT%{_mandir}
 %endif
@@ -135,6 +145,13 @@ rm -rf $RPM_BUILD_ROOT
 %{!?_without_docs: %{_mandir}/man1/git-archimport.1*}
 %{!?_without_docs: %doc Documentation/git-archimport.html }
 
+%files p4
+%defattr(-,root,root)
+%doc Documentation/git-p4import.txt
+%{_bindir}/git-p4import
+%{!?_without_docs: %{_mandir}/man1/git-p4import.1*}
+%{!?_without_docs: %doc Documentation/git-p4import.html }
+
 %files email
 %defattr(-,root,root)
 %doc Documentation/*email*.txt
@@ -146,6 +163,7 @@ rm -rf $RPM_BUILD_ROOT
 %defattr(-,root,root)
 %{_bindir}/git-gui
 %{_bindir}/git-citool
+%{_datadir}/git-gui/
 # Not Yet...
 # %{!?_without_docs: %{_mandir}/man1/git-gui.1}
 # %{!?_without_docs: %doc Documentation/git-gui.html}
@@ -177,6 +195,9 @@ rm -rf $RPM_BUILD_ROOT
 * Tue May 8 2007 Quy Tonthat <qtonthat@gmail.com>
 - Added howto files
 
+* Tue Mar 27 2007 Eygene Ryabinkin <rea-git@codelabs.ru>
+- Added the git-p4 package: Perforce import stuff.
+
 * Mon Feb 13 2007 Nicolas Pitre <nico@cam.org>
 - Update core package description (Git isn't as stupid as it used to be)
 
index 5e402924040d55aff5fb831a737d108d1e68a0bb..9f0822fab3d10fd5a83234f761847d7818c6da0a 100644 (file)
@@ -1,6 +1,6 @@
 body {
        font-family: sans-serif;
-       font-size: 12px;
+       font-size: small;
        border: solid #d9d8d1;
        border-width: 1px;
        margin: 10px;
@@ -31,7 +31,7 @@ img.logo {
 div.page_header {
        height: 25px;
        padding: 8px;
-       font-size: 18px;
+       font-size: 150%;
        font-weight: bold;
        background-color: #d9d8d1;
 }
@@ -113,7 +113,7 @@ span.signoff {
 
 div.log_link {
        padding: 0px 8px;
-       font-size: 10px;
+       font-size: 70%;
        font-family: sans-serif;
        font-style: normal;
        position: relative;
@@ -181,19 +181,36 @@ table.diff_tree {
        font-family: monospace;
 }
 
+table.combined.diff_tree td {
+       padding-right: 24px;
+}
+
+table.combined.diff_tree td.link {
+       padding: 0px 2px;
+}
+
+table.combined.diff_tree td.nochange a {
+       color: #6666ff;
+}
+
+table.combined.diff_tree td.nochange a:hover,
+table.combined.diff_tree td.nochange a:visited {
+       color: #d06666;
+}
+
 table.blame {
        border-collapse: collapse;
 }
 
 table.blame td {
        padding: 0px 5px;
-       font-size: 12px;
+       font-size: 100%;
        vertical-align: top;
 }
 
 th {
        padding: 2px 5px;
-       font-size: 12px;
+       font-size: 100%;
        text-align: left;
 }
 
@@ -215,14 +232,14 @@ tr.dark:hover {
 
 td {
        padding: 2px 5px;
-       font-size: 12px;
+       font-size: 100%;
        vertical-align: top;
 }
 
 td.link, td.selflink {
        padding: 2px 5px;
        font-family: sans-serif;
-       font-size: 10px;
+       font-size: 70%;
 }
 
 td.selflink {
@@ -387,6 +404,10 @@ div.diff.incomplete {
        color: #cccccc;
 }
 
+div.diff.nodifferences {
+       font-weight: bold;
+       color: #600000;
+}
 
 div.index_include {
        border: solid #d9d8d1;
@@ -395,7 +416,7 @@ div.index_include {
 }
 
 div.search {
-       font-size: 12px;
+       font-size: 100%;
        font-weight: normal;
        margin: 4px 8px;
        position: absolute;
@@ -423,7 +444,7 @@ a.rss_logo {
        background-color: #ff6600;
        font-weight: bold;
        font-family: sans-serif;
-       font-size: 10px;
+       font-size: 70%;
        text-align: center;
        text-decoration: none;
 }
@@ -434,7 +455,7 @@ a.rss_logo:hover {
 
 span.refs span {
        padding: 0px 4px;
-       font-size: 10px;
+       font-size: 70%;
        font-weight: normal;
        border: 1px solid;
        background-color: #ffaaff;
@@ -463,3 +484,7 @@ span.atnight {
 span.match {
        color: #e00000;
 }
+
+div.binary {
+       font-style: italic;
+}
index 7b520f5fc1f292d66a1958fbe09b44debc8bab7b..5c7011a37b2e4dab8c38de54a3e531d2fa16ddbe 100755 (executable)
@@ -19,7 +19,7 @@ use File::Basename qw(basename);
 binmode STDOUT, ':utf8';
 
 BEGIN {
-       CGI->compile() if $ENV{MOD_PERL};
+       CGI->compile() if $ENV{'MOD_PERL'};
 }
 
 our $cgi = new CGI;
@@ -71,6 +71,10 @@ our $logo_label = "git homepage";
 # source of projects list
 our $projects_list = "++GITWEB_LIST++";
 
+# default order of projects list
+# valid values are none, project, descr, owner, and age
+our $default_projects_order = "project";
+
 # show repository only if this file exists
 # (only effective if this variable evaluates to true)
 our $export_ok = "++GITWEB_EXPORT_OK++";
@@ -142,6 +146,19 @@ our %feature = (
                'override' => 0,
                'default' => [1]},
 
+       # Enable grep search, which will list the files in currently selected
+       # tree containing the given string. Enabled by default. This can be
+       # potentially CPU-intensive, of course.
+
+       # To enable system wide have in $GITWEB_CONFIG
+       # $feature{'grep'}{'default'} = [1];
+       # To have project specific config enable override in $GITWEB_CONFIG
+       # $feature{'grep'}{'override'} = 1;
+       # and in project config gitweb.grep = 0|1;
+       'grep' => {
+               'override' => 0,
+               'default' => [1]},
+
        # Enable the pickaxe search, which will list the commits that modified
        # a given string in a file. This can be practical and quite faster
        # alternative to 'blame', but still potentially CPU-intensive.
@@ -180,8 +197,8 @@ our %feature = (
        # projects matching $projname/*.git will not be shown in the main
        # projects list, instead a '+' mark will be added to $projname
        # there and a 'forks' view will be enabled for the project, listing
-       # all the forks. This feature is supported only if project list
-       # is taken from a directory, not file.
+       # all the forks. If project list is taken from a file, forks have
+       # to be listed after the main project.
 
        # To enable system wide have in $GITWEB_CONFIG
        # $feature{'forks'}{'default'} = [1];
@@ -241,6 +258,18 @@ sub gitweb_have_snapshot {
        return $have_snapshot;
 }
 
+sub feature_grep {
+       my ($val) = git_get_project_config('grep', '--bool');
+
+       if ($val eq 'true') {
+               return (1);
+       } elsif ($val eq 'false') {
+               return (0);
+       }
+
+       return ($_[0]);
+}
+
 sub feature_pickaxe {
        my ($val) = git_get_project_config('pickaxe', '--bool');
 
@@ -360,22 +389,23 @@ if (defined $page) {
        }
 }
 
+our $searchtype = $cgi->param('st');
+if (defined $searchtype) {
+       if ($searchtype =~ m/[^a-z]/) {
+               die_error(undef, "Invalid searchtype parameter");
+       }
+}
+
 our $searchtext = $cgi->param('s');
+our $search_regexp;
 if (defined $searchtext) {
-       if ($searchtext =~ m/[^a-zA-Z0-9_\.\/\-\+\:\@ ]/) {
+       if ($searchtype ne 'grep' and $searchtype ne 'pickaxe' and $searchtext =~ m/[^a-zA-Z0-9_\.\/\-\+\:\@ ]/) {
                die_error(undef, "Invalid search parameter");
        }
        if (length($searchtext) < 2) {
                die_error(undef, "At least two characters are required for search parameter");
        }
-       $searchtext = quotemeta $searchtext;
-}
-
-our $searchtype = $cgi->param('st');
-if (defined $searchtype) {
-       if ($searchtype =~ m/[^a-z]/) {
-               die_error(undef, "Invalid searchtype parameter");
-       }
+       $search_regexp = quotemeta $searchtext;
 }
 
 # now read PATH_INFO and use it as alternative to parameters
@@ -458,10 +488,16 @@ my %actions = (
        "project_index" => \&git_project_index,
 );
 
-if (defined $project) {
-       $action ||= 'summary';
-} else {
-       $action ||= 'project_list';
+if (!defined $action) {
+       if (defined $hash) {
+               $action = git_get_type($hash);
+       } elsif (defined $hash_base && defined $file_name) {
+               $action = git_get_type("$hash_base:$file_name");
+       } elsif (defined $project) {
+               $action = 'summary';
+       } else {
+               $action = 'project_list';
+       }
 }
 if (!defined($actions{$action})) {
        die_error(undef, "Unknown action");
@@ -722,7 +758,9 @@ sub chop_str {
 sub age_class {
        my $age = shift;
 
-       if ($age < 60*60*2) {
+       if (!defined $age) {
+               return "noage";
+       } elsif ($age < 60*60*2) {
                return "age0";
        } elsif ($age < 60*60*24*2) {
                return "age1";
@@ -897,19 +935,34 @@ sub format_subject_html {
 sub format_diff_line {
        my $line = shift;
        my ($from, $to) = @_;
-       my $char = substr($line, 0, 1);
        my $diff_class = "";
 
        chomp $line;
 
-       if ($char eq '+') {
-               $diff_class = " add";
-       } elsif ($char eq "-") {
-               $diff_class = " rem";
-       } elsif ($char eq "@") {
-               $diff_class = " chunk_header";
-       } elsif ($char eq "\\") {
-               $diff_class = " incomplete";
+       if ($from && $to && ref($from->{'href'}) eq "ARRAY") {
+               # combined diff
+               my $prefix = substr($line, 0, scalar @{$from->{'href'}});
+               if ($line =~ m/^\@{3}/) {
+                       $diff_class = " chunk_header";
+               } elsif ($line =~ m/^\\/) {
+                       $diff_class = " incomplete";
+               } elsif ($prefix =~ tr/+/+/) {
+                       $diff_class = " add";
+               } elsif ($prefix =~ tr/-/-/) {
+                       $diff_class = " rem";
+               }
+       } else {
+               # assume ordinary diff
+               my $char = substr($line, 0, 1);
+               if ($char eq '+') {
+                       $diff_class = " add";
+               } elsif ($char eq '-') {
+                       $diff_class = " rem";
+               } elsif ($char eq '@') {
+                       $diff_class = " chunk_header";
+               } elsif ($char eq "\\") {
+                       $diff_class = " incomplete";
+               }
        }
        $line = untabify($line);
        if ($from && $to && $line =~ m/^\@{2} /) {
@@ -930,6 +983,39 @@ sub format_diff_line {
                $line = "<span class=\"chunk_info\">@@ $from_text $to_text @@</span>" .
                        "<span class=\"section\">" . esc_html($section, -nbsp=>1) . "</span>";
                return "<div class=\"diff$diff_class\">$line</div>\n";
+       } elsif ($from && $to && $line =~ m/^\@{3}/) {
+               my ($prefix, $ranges, $section) = $line =~ m/^(\@+) (.*?) \@+(.*)$/;
+               my (@from_text, @from_start, @from_nlines, $to_text, $to_start, $to_nlines);
+
+               @from_text = split(' ', $ranges);
+               for (my $i = 0; $i < @from_text; ++$i) {
+                       ($from_start[$i], $from_nlines[$i]) =
+                               (split(',', substr($from_text[$i], 1)), 0);
+               }
+
+               $to_text   = pop @from_text;
+               $to_start  = pop @from_start;
+               $to_nlines = pop @from_nlines;
+
+               $line = "<span class=\"chunk_info\">$prefix ";
+               for (my $i = 0; $i < @from_text; ++$i) {
+                       if ($from->{'href'}[$i]) {
+                               $line .= $cgi->a({-href=>"$from->{'href'}[$i]#l$from_start[$i]",
+                                                 -class=>"list"}, $from_text[$i]);
+                       } else {
+                               $line .= $from_text[$i];
+                       }
+                       $line .= " ";
+               }
+               if ($to->{'href'}) {
+                       $line .= $cgi->a({-href=>"$to->{'href'}#l$to_start",
+                                         -class=>"list"}, $to_text);
+               } else {
+                       $line .= $to_text;
+               }
+               $line .= " $prefix</span>" .
+                        "<span class=\"section\">" . esc_html($section, -nbsp=>1) . "</span>";
+               return "<div class=\"diff$diff_class\">$line</div>\n";
        }
        return "<div class=\"diff$diff_class\">" . esc_html($line, -nbsp=>1) . "</div>\n";
 }
@@ -1006,6 +1092,11 @@ sub git_get_hash_by_path {
        my $line = <$fd>;
        close $fd or return undef;
 
+       if (!defined $line) {
+               # there is no tree or hash given by $path at $base
+               return undef;
+       }
+
        #'100644 blob 0fa3f3a66fb6a137f6ec2c19351ed4d807070ffa  panic.c'
        $line =~ m/^([0-9]+) (.+) ([0-9a-fA-F]{40})\t/;
        if (defined $type && $type ne $2) {
@@ -1015,6 +1106,30 @@ sub git_get_hash_by_path {
        return $3;
 }
 
+# get path of entry with given hash at given tree-ish (ref)
+# used to get 'from' filename for combined diff (merge commit) for renames
+sub git_get_path_by_hash {
+       my $base = shift || return;
+       my $hash = shift || return;
+
+       local $/ = "\0";
+
+       open my $fd, "-|", git_cmd(), "ls-tree", '-r', '-t', '-z', $base
+               or return undef;
+       while (my $line = <$fd>) {
+               chomp $line;
+
+               #'040000 tree 595596a6a9117ddba9fe379b6b012b558bac8423  gitweb'
+               #'100644 blob e02e90f0429be0d2a69b76571101f20b8f75530f  gitweb/README'
+               if ($line =~ m/(?:[0-9]+) (?:.+) $hash\t(.+)$/) {
+                       close $fd;
+                       return $1;
+               }
+       }
+       close $fd;
+       return undef;
+}
+
 ## ......................................................................
 ## git utility functions, directly accessing git repository
 
@@ -1024,7 +1139,9 @@ sub git_get_project_description {
        open my $fd, "$projectroot/$path/description" or return undef;
        my $descr = <$fd>;
        close $fd;
-       chomp $descr;
+       if (defined $descr) {
+               chomp $descr;
+       }
        return $descr;
 }
 
@@ -1045,6 +1162,8 @@ sub git_get_projects_list {
        $filter ||= '';
        $filter =~ s/\.git$//;
 
+       my ($check_forks) = gitweb_check_feature('forks');
+
        if (-d $projects_list) {
                # search in directory
                my $dir = $projects_list . ($filter ? "/$filter" : '');
@@ -1052,8 +1171,6 @@ sub git_get_projects_list {
                $dir =~ s!/+$!!;
                my $pfxlen = length("$dir");
 
-               my ($check_forks) = gitweb_check_feature('forks');
-
                File::Find::find({
                        follow_fast => 1, # follow symbolic links
                        dangling_symlinks => 0, # ignore dangling symlinks, silently
@@ -1079,7 +1196,9 @@ sub git_get_projects_list {
                # 'git%2Fgit.git Linus+Torvalds'
                # 'libs%2Fklibc%2Fklibc.git H.+Peter+Anvin'
                # 'linux%2Fhotplug%2Fudev.git Greg+Kroah-Hartman'
+               my %paths;
                open my ($fd), $projects_list or return;
+       PROJECT:
                while (my $line = <$fd>) {
                        chomp $line;
                        my ($path, $owner) = split ' ', $line;
@@ -1092,11 +1211,27 @@ sub git_get_projects_list {
                                # looking for forks;
                                my $pfx = substr($path, 0, length($filter));
                                if ($pfx ne $filter) {
-                                       next;
+                                       next PROJECT;
                                }
                                my $sfx = substr($path, length($filter));
                                if ($sfx !~ /^\/.*\.git$/) {
-                                       next;
+                                       next PROJECT;
+                               }
+                       } elsif ($check_forks) {
+                       PATH:
+                               foreach my $filter (keys %paths) {
+                                       # looking for forks;
+                                       my $pfx = substr($path, 0, length($filter));
+                                       if ($pfx ne $filter) {
+                                               next PATH;
+                                       }
+                                       my $sfx = substr($path, length($filter));
+                                       if ($sfx !~ /^\/.*\.git$/) {
+                                               next PATH;
+                                       }
+                                       # is a fork, don't include it in
+                                       # the list
+                                       next PROJECT;
                                }
                        }
                        if (check_export_ok("$projectroot/$path")) {
@@ -1104,12 +1239,13 @@ sub git_get_projects_list {
                                        path => $path,
                                        owner => decode_utf8($owner),
                                };
-                               push @list, $pr
+                               push @list, $pr;
+                               (my $forks_path = $path) =~ s/\.git$//;
+                               $paths{$forks_path}++;
                        }
                }
                close $fd;
        }
-       @list = sort {$a->{'path'} cmp $b->{'path'}} @list;
        return @list;
 }
 
@@ -1156,7 +1292,8 @@ sub git_get_last_activity {
             'refs/heads') or return;
        my $most_recent = <$fd>;
        close $fd or return;
-       if ($most_recent =~ / (\d+) [-+][01]\d\d\d$/) {
+       if (defined $most_recent &&
+           $most_recent =~ / (\d+) [-+][01]\d\d\d$/) {
                my $timestamp = $1;
                my $age = time - $timestamp;
                return ($age, age_string($age));
@@ -1279,8 +1416,12 @@ sub parse_commit_text {
 
        pop @commit_lines; # Remove '\0'
 
+       if (! @commit_lines) {
+               return;
+       }
+
        my $header = shift @commit_lines;
-       if (!($header =~ m/^[0-9a-fA-F]{40}/)) {
+       if ($header !~ m/^[0-9a-fA-F]{40}/) {
                return;
        }
        ($co{'id'}, my @parents) = split ' ', $header;
@@ -1476,6 +1617,17 @@ sub parse_difftree_raw_line {
                        $res{'file'} = unquote($7);
                }
        }
+       # '::100755 100755 100755 60e79ca1b01bc8b057abe17ddab484699a7f5fdb 94067cc5f73388f33722d52ae02f44692bc07490 94067cc5f73388f33722d52ae02f44692bc07490 MR git-gui/git-gui.sh'
+       # combined diff (for merge commit)
+       elsif ($line =~ s/^(::+)((?:[0-7]{6} )+)((?:[0-9a-fA-F]{40} )+)([a-zA-Z]+)\t(.*)$//) {
+               $res{'nparents'}  = length($1);
+               $res{'from_mode'} = [ split(' ', $2) ];
+               $res{'to_mode'} = pop @{$res{'from_mode'}};
+               $res{'from_id'} = [ split(' ', $3) ];
+               $res{'to_id'} = pop @{$res{'from_id'}};
+               $res{'status'} = [ split('', $4) ];
+               $res{'to_file'} = unquote($5);
+       }
        # 'c512b523472485aef4fff9e57b229d9d243c967f'
        elsif ($line =~ m/^([0-9a-fA-F]{40})$/) {
                $res{'commit'} = $1;
@@ -1776,6 +1928,8 @@ EOF
                }
                print "\n";
        }
+       print "</div>\n";
+
        my ($have_search) = gitweb_check_feature('search');
        if ((defined $project) && ($have_search)) {
                if (!defined $searchtext) {
@@ -1798,14 +1952,13 @@ EOF
                      $cgi->hidden(-name => "a") . "\n" .
                      $cgi->hidden(-name => "h") . "\n" .
                      $cgi->popup_menu(-name => 'st', -default => 'commit',
-                                      -values => ['commit', 'author', 'committer', 'pickaxe']) .
+                                      -values => ['commit', 'grep', 'author', 'committer', 'pickaxe']) .
                      $cgi->sup($cgi->a({-href => href(action=>"search_help")}, "?")) .
                      " search:\n",
                      $cgi->textfield(-name => "s", -value => $searchtext) . "\n" .
                      "</div>" .
                      $cgi->end_form() . "\n";
        }
-       print "</div>\n";
 }
 
 sub git_footer_html {
@@ -1868,16 +2021,16 @@ sub git_print_page_nav {
        my %arg = map { $_ => {action=>$_} } @navs;
        if (defined $head) {
                for (qw(commit commitdiff)) {
-                       $arg{$_}{hash} = $head;
+                       $arg{$_}{'hash'} = $head;
                }
                if ($current =~ m/^(tree | log | shortlog | commit | commitdiff | search)$/x) {
                        for (qw(shortlog log)) {
-                               $arg{$_}{hash} = $head;
+                               $arg{$_}{'hash'} = $head;
                        }
                }
        }
-       $arg{tree}{hash} = $treehead if defined $treehead;
-       $arg{tree}{hash_base} = $treebase if defined $treebase;
+       $arg{'tree'}{'hash'} = $treehead if defined $treehead;
+       $arg{'tree'}{'hash_base'} = $treebase if defined $treebase;
 
        print "<div class=\"page_nav\">\n" .
                (join " | ",
@@ -1925,9 +2078,9 @@ sub git_print_header_div {
        my ($action, $title, $hash, $hash_base) = @_;
        my %args = ();
 
-       $args{action} = $action;
-       $args{hash} = $hash if $hash;
-       $args{hash_base} = $hash_base if $hash_base;
+       $args{'action'} = $action;
+       $args{'hash'} = $hash if $hash;
+       $args{'hash_base'} = $hash_base if $hash_base;
 
        print "<div class=\"header\">\n" .
              $cgi->a({-href => href(%args), -class => "title"},
@@ -2179,8 +2332,42 @@ sub git_print_tree_entry {
 ## ......................................................................
 ## functions printing large fragments of HTML
 
+sub fill_from_file_info {
+       my ($diff, @parents) = @_;
+
+       $diff->{'from_file'} = [ ];
+       $diff->{'from_file'}[$diff->{'nparents'} - 1] = undef;
+       for (my $i = 0; $i < $diff->{'nparents'}; $i++) {
+               if ($diff->{'status'}[$i] eq 'R' ||
+                   $diff->{'status'}[$i] eq 'C') {
+                       $diff->{'from_file'}[$i] =
+                               git_get_path_by_hash($parents[$i], $diff->{'from_id'}[$i]);
+               }
+       }
+
+       return $diff;
+}
+
+# parameters can be strings, or references to arrays of strings
+sub from_ids_eq {
+       my ($a, $b) = @_;
+
+       if (ref($a) eq "ARRAY" && ref($b) eq "ARRAY" && @$a == @$b) {
+               for (my $i = 0; $i < @$a; ++$i) {
+                       return 0 unless ($a->[$i] eq $b->[$i]);
+               }
+               return 1;
+       } elsif (!ref($a) && !ref($b)) {
+               return $a eq $b;
+       } else {
+               return 0;
+       }
+}
+
+
 sub git_difftree_body {
-       my ($difftree, $hash, $parent) = @_;
+       my ($difftree, $hash, @parents) = @_;
+       my ($parent) = $parents[0];
        my ($have_blame) = gitweb_check_feature('blame');
        print "<div class=\"list_head\">\n";
        if ($#{$difftree} > 10) {
@@ -2188,11 +2375,19 @@ sub git_difftree_body {
        }
        print "</div>\n";
 
-       print "<table class=\"diff_tree\">\n";
+       print "<table class=\"" .
+             (@parents > 1 ? "combined " : "") .
+             "diff_tree\">\n";
        my $alternate = 1;
        my $patchno = 0;
        foreach my $line (@{$difftree}) {
-               my %diff = parse_difftree_raw_line($line);
+               my $diff;
+               if (ref($line) eq "HASH") {
+                       # pre-parsed (or generated by hand)
+                       $diff = $line;
+               } else {
+                       $diff = parse_difftree_raw_line($line);
+               }
 
                if ($alternate) {
                        print "<tr class=\"dark\">\n";
@@ -2201,31 +2396,120 @@ sub git_difftree_body {
                }
                $alternate ^= 1;
 
+               if (exists $diff->{'nparents'}) { # combined diff
+
+                       fill_from_file_info($diff, @parents)
+                               unless exists $diff->{'from_file'};
+
+                       if ($diff->{'to_id'} ne ('0' x 40)) {
+                               # file exists in the result (child) commit
+                               print "<td>" .
+                                     $cgi->a({-href => href(action=>"blob", hash=>$diff->{'to_id'},
+                                                            file_name=>$diff->{'to_file'},
+                                                            hash_base=>$hash),
+                                             -class => "list"}, esc_path($diff->{'to_file'})) .
+                                     "</td>\n";
+                       } else {
+                               print "<td>" .
+                                     esc_path($diff->{'to_file'}) .
+                                     "</td>\n";
+                       }
+
+                       if ($action eq 'commitdiff') {
+                               # link to patch
+                               $patchno++;
+                               print "<td class=\"link\">" .
+                                     $cgi->a({-href => "#patch$patchno"}, "patch") .
+                                     " | " .
+                                     "</td>\n";
+                       }
+
+                       my $has_history = 0;
+                       my $not_deleted = 0;
+                       for (my $i = 0; $i < $diff->{'nparents'}; $i++) {
+                               my $hash_parent = $parents[$i];
+                               my $from_hash = $diff->{'from_id'}[$i];
+                               my $from_path = $diff->{'from_file'}[$i];
+                               my $status = $diff->{'status'}[$i];
+
+                               $has_history ||= ($status ne 'A');
+                               $not_deleted ||= ($status ne 'D');
+
+                               if ($status eq 'A') {
+                                       print "<td  class=\"link\" align=\"right\"> | </td>\n";
+                               } elsif ($status eq 'D') {
+                                       print "<td class=\"link\">" .
+                                             $cgi->a({-href => href(action=>"blob",
+                                                                    hash_base=>$hash,
+                                                                    hash=>$from_hash,
+                                                                    file_name=>$from_path)},
+                                                     "blob" . ($i+1)) .
+                                             " | </td>\n";
+                               } else {
+                                       if ($diff->{'to_id'} eq $from_hash) {
+                                               print "<td class=\"link nochange\">";
+                                       } else {
+                                               print "<td class=\"link\">";
+                                       }
+                                       print $cgi->a({-href => href(action=>"blobdiff",
+                                                                    hash=>$diff->{'to_id'},
+                                                                    hash_parent=>$from_hash,
+                                                                    hash_base=>$hash,
+                                                                    hash_parent_base=>$hash_parent,
+                                                                    file_name=>$diff->{'to_file'},
+                                                                    file_parent=>$from_path)},
+                                                     "diff" . ($i+1)) .
+                                             " | </td>\n";
+                               }
+                       }
+
+                       print "<td class=\"link\">";
+                       if ($not_deleted) {
+                               print $cgi->a({-href => href(action=>"blob",
+                                                            hash=>$diff->{'to_id'},
+                                                            file_name=>$diff->{'to_file'},
+                                                            hash_base=>$hash)},
+                                             "blob");
+                               print " | " if ($has_history);
+                       }
+                       if ($has_history) {
+                               print $cgi->a({-href => href(action=>"history",
+                                                            file_name=>$diff->{'to_file'},
+                                                            hash_base=>$hash)},
+                                             "history");
+                       }
+                       print "</td>\n";
+
+                       print "</tr>\n";
+                       next; # instead of 'else' clause, to avoid extra indent
+               }
+               # else ordinary diff
+
                my ($to_mode_oct, $to_mode_str, $to_file_type);
                my ($from_mode_oct, $from_mode_str, $from_file_type);
-               if ($diff{'to_mode'} ne ('0' x 6)) {
-                       $to_mode_oct = oct $diff{'to_mode'};
+               if ($diff->{'to_mode'} ne ('0' x 6)) {
+                       $to_mode_oct = oct $diff->{'to_mode'};
                        if (S_ISREG($to_mode_oct)) { # only for regular file
                                $to_mode_str = sprintf("%04o", $to_mode_oct & 0777); # permission bits
                        }
-                       $to_file_type = file_type($diff{'to_mode'});
+                       $to_file_type = file_type($diff->{'to_mode'});
                }
-               if ($diff{'from_mode'} ne ('0' x 6)) {
-                       $from_mode_oct = oct $diff{'from_mode'};
+               if ($diff->{'from_mode'} ne ('0' x 6)) {
+                       $from_mode_oct = oct $diff->{'from_mode'};
                        if (S_ISREG($to_mode_oct)) { # only for regular file
                                $from_mode_str = sprintf("%04o", $from_mode_oct & 0777); # permission bits
                        }
-                       $from_file_type = file_type($diff{'from_mode'});
+                       $from_file_type = file_type($diff->{'from_mode'});
                }
 
-               if ($diff{'status'} eq "A") { # created
+               if ($diff->{'status'} eq "A") { # created
                        my $mode_chng = "<span class=\"file_status new\">[new $to_file_type";
                        $mode_chng   .= " with mode: $to_mode_str" if $to_mode_str;
                        $mode_chng   .= "]</span>";
                        print "<td>";
-                       print $cgi->a({-href => href(action=>"blob", hash=>$diff{'to_id'},
-                                                    hash_base=>$hash, file_name=>$diff{'file'}),
-                                     -class => "list"}, esc_path($diff{'file'}));
+                       print $cgi->a({-href => href(action=>"blob", hash=>$diff->{'to_id'},
+                                                    hash_base=>$hash, file_name=>$diff->{'file'}),
+                                     -class => "list"}, esc_path($diff->{'file'}));
                        print "</td>\n";
                        print "<td>$mode_chng</td>\n";
                        print "<td class=\"link\">";
@@ -2235,17 +2519,17 @@ sub git_difftree_body {
                                print $cgi->a({-href => "#patch$patchno"}, "patch");
                                print " | ";
                        }
-                       print $cgi->a({-href => href(action=>"blob", hash=>$diff{'to_id'},
-                                                    hash_base=>$hash, file_name=>$diff{'file'})},
+                       print $cgi->a({-href => href(action=>"blob", hash=>$diff->{'to_id'},
+                                                    hash_base=>$hash, file_name=>$diff->{'file'})},
                                      "blob");
                        print "</td>\n";
 
-               } elsif ($diff{'status'} eq "D") { # deleted
+               } elsif ($diff->{'status'} eq "D") { # deleted
                        my $mode_chng = "<span class=\"file_status deleted\">[deleted $from_file_type]</span>";
                        print "<td>";
-                       print $cgi->a({-href => href(action=>"blob", hash=>$diff{'from_id'},
-                                                    hash_base=>$parent, file_name=>$diff{'file'}),
-                                      -class => "list"}, esc_path($diff{'file'}));
+                       print $cgi->a({-href => href(action=>"blob", hash=>$diff->{'from_id'},
+                                                    hash_base=>$parent, file_name=>$diff->{'file'}),
+                                      -class => "list"}, esc_path($diff->{'file'}));
                        print "</td>\n";
                        print "<td>$mode_chng</td>\n";
                        print "<td class=\"link\">";
@@ -2255,22 +2539,22 @@ sub git_difftree_body {
                                print $cgi->a({-href => "#patch$patchno"}, "patch");
                                print " | ";
                        }
-                       print $cgi->a({-href => href(action=>"blob", hash=>$diff{'from_id'},
-                                                    hash_base=>$parent, file_name=>$diff{'file'})},
+                       print $cgi->a({-href => href(action=>"blob", hash=>$diff->{'from_id'},
+                                                    hash_base=>$parent, file_name=>$diff->{'file'})},
                                      "blob") . " | ";
                        if ($have_blame) {
                                print $cgi->a({-href => href(action=>"blame", hash_base=>$parent,
-                                                            file_name=>$diff{'file'})},
+                                                            file_name=>$diff->{'file'})},
                                              "blame") . " | ";
                        }
                        print $cgi->a({-href => href(action=>"history", hash_base=>$parent,
-                                                    file_name=>$diff{'file'})},
+                                                    file_name=>$diff->{'file'})},
                                      "history");
                        print "</td>\n";
 
-               } elsif ($diff{'status'} eq "M" || $diff{'status'} eq "T") { # modified, or type changed
+               } elsif ($diff->{'status'} eq "M" || $diff->{'status'} eq "T") { # modified, or type changed
                        my $mode_chnge = "";
-                       if ($diff{'from_mode'} != $diff{'to_mode'}) {
+                       if ($diff->{'from_mode'} != $diff->{'to_mode'}) {
                                $mode_chnge = "<span class=\"file_status mode_chnge\">[changed";
                                if ($from_file_type ne $to_file_type) {
                                        $mode_chnge .= " from $from_file_type to $to_file_type";
@@ -2285,9 +2569,9 @@ sub git_difftree_body {
                                $mode_chnge .= "]</span>\n";
                        }
                        print "<td>";
-                       print $cgi->a({-href => href(action=>"blob", hash=>$diff{'to_id'},
-                                                    hash_base=>$hash, file_name=>$diff{'file'}),
-                                     -class => "list"}, esc_path($diff{'file'}));
+                       print $cgi->a({-href => href(action=>"blob", hash=>$diff->{'to_id'},
+                                                    hash_base=>$hash, file_name=>$diff->{'file'}),
+                                     -class => "list"}, esc_path($diff->{'file'}));
                        print "</td>\n";
                        print "<td>$mode_chnge</td>\n";
                        print "<td class=\"link\">";
@@ -2296,70 +2580,70 @@ sub git_difftree_body {
                                $patchno++;
                                print $cgi->a({-href => "#patch$patchno"}, "patch") .
                                      " | ";
-                       } elsif ($diff{'to_id'} ne $diff{'from_id'}) {
+                       } elsif ($diff->{'to_id'} ne $diff->{'from_id'}) {
                                # "commit" view and modified file (not onlu mode changed)
                                print $cgi->a({-href => href(action=>"blobdiff",
-                                                            hash=>$diff{'to_id'}, hash_parent=>$diff{'from_id'},
+                                                            hash=>$diff->{'to_id'}, hash_parent=>$diff->{'from_id'},
                                                             hash_base=>$hash, hash_parent_base=>$parent,
-                                                            file_name=>$diff{'file'})},
+                                                            file_name=>$diff->{'file'})},
                                              "diff") .
                                      " | ";
                        }
-                       print $cgi->a({-href => href(action=>"blob", hash=>$diff{'to_id'},
-                                                    hash_base=>$hash, file_name=>$diff{'file'})},
+                       print $cgi->a({-href => href(action=>"blob", hash=>$diff->{'to_id'},
+                                                    hash_base=>$hash, file_name=>$diff->{'file'})},
                                       "blob") . " | ";
                        if ($have_blame) {
                                print $cgi->a({-href => href(action=>"blame", hash_base=>$hash,
-                                                            file_name=>$diff{'file'})},
+                                                            file_name=>$diff->{'file'})},
                                              "blame") . " | ";
                        }
                        print $cgi->a({-href => href(action=>"history", hash_base=>$hash,
-                                                    file_name=>$diff{'file'})},
+                                                    file_name=>$diff->{'file'})},
                                      "history");
                        print "</td>\n";
 
-               } elsif ($diff{'status'} eq "R" || $diff{'status'} eq "C") { # renamed or copied
+               } elsif ($diff->{'status'} eq "R" || $diff->{'status'} eq "C") { # renamed or copied
                        my %status_name = ('R' => 'moved', 'C' => 'copied');
-                       my $nstatus = $status_name{$diff{'status'}};
+                       my $nstatus = $status_name{$diff->{'status'}};
                        my $mode_chng = "";
-                       if ($diff{'from_mode'} != $diff{'to_mode'}) {
+                       if ($diff->{'from_mode'} != $diff->{'to_mode'}) {
                                # mode also for directories, so we cannot use $to_mode_str
                                $mode_chng = sprintf(", mode: %04o", $to_mode_oct & 0777);
                        }
                        print "<td>" .
                              $cgi->a({-href => href(action=>"blob", hash_base=>$hash,
-                                                    hash=>$diff{'to_id'}, file_name=>$diff{'to_file'}),
-                                     -class => "list"}, esc_path($diff{'to_file'})) . "</td>\n" .
+                                                    hash=>$diff->{'to_id'}, file_name=>$diff->{'to_file'}),
+                                     -class => "list"}, esc_path($diff->{'to_file'})) . "</td>\n" .
                              "<td><span class=\"file_status $nstatus\">[$nstatus from " .
                              $cgi->a({-href => href(action=>"blob", hash_base=>$parent,
-                                                    hash=>$diff{'from_id'}, file_name=>$diff{'from_file'}),
-                                     -class => "list"}, esc_path($diff{'from_file'})) .
-                             " with " . (int $diff{'similarity'}) . "% similarity$mode_chng]</span></td>\n" .
+                                                    hash=>$diff->{'from_id'}, file_name=>$diff->{'from_file'}),
+                                     -class => "list"}, esc_path($diff->{'from_file'})) .
+                             " with " . (int $diff->{'similarity'}) . "% similarity$mode_chng]</span></td>\n" .
                              "<td class=\"link\">";
                        if ($action eq 'commitdiff') {
                                # link to patch
                                $patchno++;
                                print $cgi->a({-href => "#patch$patchno"}, "patch") .
                                      " | ";
-                       } elsif ($diff{'to_id'} ne $diff{'from_id'}) {
+                       } elsif ($diff->{'to_id'} ne $diff->{'from_id'}) {
                                # "commit" view and modified file (not only pure rename or copy)
                                print $cgi->a({-href => href(action=>"blobdiff",
-                                                            hash=>$diff{'to_id'}, hash_parent=>$diff{'from_id'},
+                                                            hash=>$diff->{'to_id'}, hash_parent=>$diff->{'from_id'},
                                                             hash_base=>$hash, hash_parent_base=>$parent,
-                                                            file_name=>$diff{'to_file'}, file_parent=>$diff{'from_file'})},
+                                                            file_name=>$diff->{'to_file'}, file_parent=>$diff->{'from_file'})},
                                              "diff") .
                                      " | ";
                        }
-                       print $cgi->a({-href => href(action=>"blob", hash=>$diff{'to_id'},
-                                                    hash_base=>$parent, file_name=>$diff{'to_file'})},
+                       print $cgi->a({-href => href(action=>"blob", hash=>$diff->{'to_id'},
+                                                    hash_base=>$parent, file_name=>$diff->{'to_file'})},
                                      "blob") . " | ";
                        if ($have_blame) {
                                print $cgi->a({-href => href(action=>"blame", hash_base=>$hash,
-                                                            file_name=>$diff{'to_file'})},
+                                                            file_name=>$diff->{'to_file'})},
                                              "blame") . " | ";
                        }
                        print $cgi->a({-href => href(action=>"history", hash_base=>$hash,
-                                                   file_name=>$diff{'to_file'})},
+                                                   file_name=>$diff->{'to_file'})},
                                      "history");
                        print "</td>\n";
 
@@ -2370,9 +2654,11 @@ sub git_difftree_body {
 }
 
 sub git_patchset_body {
-       my ($fd, $difftree, $hash, $hash_parent) = @_;
+       my ($fd, $difftree, $hash, @hash_parents) = @_;
+       my ($hash_parent) = $hash_parents[0];
 
        my $patch_idx = 0;
+       my $patch_number = 0;
        my $patch_line;
        my $diffinfo;
        my (%from, %to);
@@ -2394,6 +2680,7 @@ sub git_patchset_body {
                # git diff header
                #assert($patch_line =~ m/^diff /) if DEBUG;
                #assert($patch_line !~ m!$/$!) if DEBUG; # is chomp-ed
+               $patch_number++;
                push @diff_header, $patch_line;
 
                # extended diff header
@@ -2406,6 +2693,9 @@ sub git_patchset_body {
                        if ($patch_line =~ m/^index ([0-9a-fA-F]{40})..([0-9a-fA-F]{40})/) {
                                $from_id = $1;
                                $to_id   = $2;
+                       } elsif ($patch_line =~ m/^index ((?:[0-9a-fA-F]{40},)+[0-9a-fA-F]{40})..([0-9a-fA-F]{40})/) {
+                               $from_id = [ split(',', $1) ];
+                               $to_id   = $2;
                        }
 
                        push @diff_header, $patch_line;
@@ -2415,9 +2705,10 @@ sub git_patchset_body {
                # check if current patch belong to current raw line
                # and parse raw git-diff line if needed
                if (defined $diffinfo &&
-                   $diffinfo->{'from_id'} eq $from_id &&
-                   $diffinfo->{'to_id'}   eq $to_id) {
-                       # this is split patch
+                   defined $from_id && defined $to_id &&
+                   from_ids_eq($diffinfo->{'from_id'}, $from_id) &&
+                   $diffinfo->{'to_id'} eq $to_id) {
+                       # this is continuation of a split patch
                        print "<div class=\"patch cont\">\n";
                } else {
                        # advance raw git-diff output if needed
@@ -2430,16 +2721,36 @@ sub git_patchset_body {
                        } else {
                                $diffinfo = parse_difftree_raw_line($difftree->[$patch_idx]);
                        }
-                       $from{'file'} = $diffinfo->{'from_file'} || $diffinfo->{'file'};
-                       $to{'file'}   = $diffinfo->{'to_file'}   || $diffinfo->{'file'};
-                       if ($diffinfo->{'status'} ne "A") { # not new (added) file
-                               $from{'href'} = href(action=>"blob", hash_base=>$hash_parent,
-                                                    hash=>$diffinfo->{'from_id'},
-                                                    file_name=>$from{'file'});
+                       if ($diffinfo->{'nparents'}) {
+                               # combined diff
+                               $from{'file'} = [];
+                               $from{'href'} = [];
+                               fill_from_file_info($diffinfo, @hash_parents)
+                                       unless exists $diffinfo->{'from_file'};
+                               for (my $i = 0; $i < $diffinfo->{'nparents'}; $i++) {
+                                       $from{'file'}[$i] = $diffinfo->{'from_file'}[$i] || $diffinfo->{'to_file'};
+                                       if ($diffinfo->{'status'}[$i] ne "A") { # not new (added) file
+                                               $from{'href'}[$i] = href(action=>"blob",
+                                                                        hash_base=>$hash_parents[$i],
+                                                                        hash=>$diffinfo->{'from_id'}[$i],
+                                                                        file_name=>$from{'file'}[$i]);
+                                       } else {
+                                               $from{'href'}[$i] = undef;
+                                       }
+                               }
                        } else {
-                               delete $from{'href'};
+                               $from{'file'} = $diffinfo->{'from_file'} || $diffinfo->{'file'};
+                               if ($diffinfo->{'status'} ne "A") { # not new (added) file
+                                       $from{'href'} = href(action=>"blob", hash_base=>$hash_parent,
+                                                            hash=>$diffinfo->{'from_id'},
+                                                            file_name=>$from{'file'});
+                               } else {
+                                       delete $from{'href'};
+                               }
                        }
-                       if ($diffinfo->{'status'} ne "D") { # not deleted file
+
+                       $to{'file'} = $diffinfo->{'to_file'} || $diffinfo->{'file'};
+                       if ($diffinfo->{'to_id'} ne ('0' x 40)) { # file exists in result
                                $to{'href'} = href(action=>"blob", hash_base=>$hash,
                                                   hash=>$diffinfo->{'to_id'},
                                                   file_name=>$to{'file'});
@@ -2453,19 +2764,34 @@ sub git_patchset_body {
 
                # print "git diff" header
                $patch_line = shift @diff_header;
-               $patch_line =~ s!^(diff (.*?) )"?a/.*$!$1!;
-               if ($from{'href'}) {
-                       $patch_line .= $cgi->a({-href => $from{'href'}, -class => "path"},
-                                              'a/' . esc_path($from{'file'}));
-               } else { # file was added
-                       $patch_line .= 'a/' . esc_path($from{'file'});
-               }
-               $patch_line .= ' ';
-               if ($to{'href'}) {
-                       $patch_line .= $cgi->a({-href => $to{'href'}, -class => "path"},
-                                              'b/' . esc_path($to{'file'}));
-               } else { # file was deleted
-                       $patch_line .= 'b/' . esc_path($to{'file'});
+               if ($diffinfo->{'nparents'}) {
+
+                       # combined diff
+                       $patch_line =~ s!^(diff (.*?) )"?.*$!$1!;
+                       if ($to{'href'}) {
+                               $patch_line .= $cgi->a({-href => $to{'href'}, -class => "path"},
+                                                      esc_path($to{'file'}));
+                       } else { # file was deleted
+                               $patch_line .= esc_path($to{'file'});
+                       }
+
+               } else {
+
+                       $patch_line =~ s!^(diff (.*?) )"?a/.*$!$1!;
+                       if ($from{'href'}) {
+                               $patch_line .= $cgi->a({-href => $from{'href'}, -class => "path"},
+                                                      'a/' . esc_path($from{'file'}));
+                       } else { # file was added
+                               $patch_line .= 'a/' . esc_path($from{'file'});
+                       }
+                       $patch_line .= ' ';
+                       if ($to{'href'}) {
+                               $patch_line .= $cgi->a({-href => $to{'href'}, -class => "path"},
+                                                      'b/' . esc_path($to{'file'}));
+                       } else { # file was deleted
+                               $patch_line .= 'b/' . esc_path($to{'file'});
+                       }
+
                }
                print "<div class=\"diff header\">$patch_line</div>\n";
 
@@ -2482,14 +2808,37 @@ sub git_patchset_body {
                                $patch_line .= $cgi->a({-href=>$to{'href'}, -class=>"path"},
                                                       esc_path($to{'file'}));
                        }
-                       # match <mode>
+                       # match single <mode>
                        if ($patch_line =~ m/\s(\d{6})$/) {
                                $patch_line .= '<span class="info"> (' .
                                               file_type_long($1) .
                                               ')</span>';
                        }
                        # match <hash>
-                       if ($patch_line =~ m/^index/) {
+                       if ($patch_line =~ m/^index [0-9a-fA-F]{40},[0-9a-fA-F]{40}/) {
+                               # can match only for combined diff
+                               $patch_line = 'index ';
+                               for (my $i = 0; $i < $diffinfo->{'nparents'}; $i++) {
+                                       if ($from{'href'}[$i]) {
+                                               $patch_line .= $cgi->a({-href=>$from{'href'}[$i],
+                                                                       -class=>"hash"},
+                                                                      substr($diffinfo->{'from_id'}[$i],0,7));
+                                       } else {
+                                               $patch_line .= '0' x 7;
+                                       }
+                                       # separator
+                                       $patch_line .= ',' if ($i < $diffinfo->{'nparents'} - 1);
+                               }
+                               $patch_line .= '..';
+                               if ($to{'href'}) {
+                                       $patch_line .= $cgi->a({-href=>$to{'href'}, -class=>"hash"},
+                                                              substr($diffinfo->{'to_id'},0,7));
+                               } else {
+                                       $patch_line .= '0' x 7;
+                               }
+
+                       } elsif ($patch_line =~ m/^index [0-9a-fA-F]{40}..[0-9a-fA-F]{40}/) {
+                               # can match only for ordinary diff
                                my ($from_link, $to_link);
                                if ($from{'href'}) {
                                        $from_link = $cgi->a({-href=>$from{'href'}, -class=>"hash"},
@@ -2525,7 +2874,8 @@ sub git_patchset_body {
                }
                next PATCH if ($patch_line =~ m/^diff /);
                #assert($patch_line =~ m/^---/) if DEBUG;
-               if ($from{'href'} && $patch_line =~ m!^--- "?a/!) {
+               if (!$diffinfo->{'nparents'} && # not from-file line for combined diff
+                   $from{'href'} && $patch_line =~ m!^--- "?a/!) {
                        $patch_line = '--- a/' .
                                      $cgi->a({-href=>$from{'href'}, -class=>"path"},
                                              esc_path($from{'file'}));
@@ -2557,6 +2907,14 @@ sub git_patchset_body {
                print "</div>\n"; # class="patch"
        }
 
+       if ($patch_number == 0) {
+               if (@hash_parents > 1) {
+                       print "<div class=\"diff nodifferences\">Trivial merge</div>\n";
+               } else {
+                       print "<div class=\"diff nodifferences\">No differences found</div>\n";
+               }
+       }
+
        print "</div>\n"; # class="patchset"
 }
 
@@ -2596,7 +2954,7 @@ sub git_project_list_body {
                push @projects, $pr;
        }
 
-       $order ||= "project";
+       $order ||= $default_projects_order;
        $from = 0 unless defined $from;
        $to = $#projects if (!defined $to || $#projects < $to);
 
@@ -2669,7 +3027,7 @@ sub git_project_list_body {
                                        esc_html($pr->{'descr'})) . "</td>\n" .
                      "<td><i>" . chop_str($pr->{'owner'}, 15) . "</i></td>\n";
                print "<td class=\"". age_class($pr->{'age'}) . "\">" .
-                     $pr->{'age_string'} . "</td>\n" .
+                     (defined $pr->{'age_string'} ? $pr->{'age_string'} : "No commits") . "</td>\n" .
                      "<td class=\"link\">" .
                      $cgi->a({-href => href(project=>$pr->{'path'}, action=>"summary")}, "summary")   . " | " .
                      $cgi->a({-href => href(project=>$pr->{'path'}, action=>"shortlog")}, "shortlog") . " | " .
@@ -2923,7 +3281,7 @@ sub git_search_grep_body {
                               esc_html(chop_str($co{'title'}, 50)) . "<br/>");
                my $comment = $co{'comment'};
                foreach my $line (@$comment) {
-                       if ($line =~ m/^(.*)($searchtext)(.*)$/i) {
+                       if ($line =~ m/^(.*)($search_regexp)(.*)$/i) {
                                my $lead = esc_html($1) || "";
                                $lead = chop_str($lead, 30, 10);
                                my $match = esc_html($2) || "";
@@ -2955,7 +3313,7 @@ sub git_search_grep_body {
 
 sub git_project_list {
        my $order = $cgi->param('o');
-       if (defined $order && $order !~ m/project|descr|owner|age/) {
+       if (defined $order && $order !~ m/none|project|descr|owner|age/) {
                die_error(undef, "Unknown order parameter");
        }
 
@@ -2978,7 +3336,7 @@ sub git_project_list {
 
 sub git_forks {
        my $order = $cgi->param('o');
-       if (defined $order && $order !~ m/project|descr|owner|age/) {
+       if (defined $order && $order !~ m/none|project|descr|owner|age/) {
                die_error(undef, "Unknown order parameter");
        }
 
@@ -3021,7 +3379,7 @@ sub git_project_index {
 sub git_summary {
        my $descr = git_get_project_description($project) || "none";
        my %co = parse_commit("HEAD");
-       my %cd = parse_date($co{'committer_epoch'}, $co{'committer_tz'});
+       my %cd = %co ? parse_date($co{'committer_epoch'}, $co{'committer_tz'}) : ();
        my $head = $co{'id'};
 
        my $owner = git_get_project_owner($project);
@@ -3044,8 +3402,11 @@ sub git_summary {
        print "<div class=\"title\">&nbsp;</div>\n";
        print "<table cellspacing=\"0\">\n" .
              "<tr><td>description</td><td>" . esc_html($descr) . "</td></tr>\n" .
-             "<tr><td>owner</td><td>$owner</td></tr>\n" .
-             "<tr><td>last change</td><td>$cd{'rfc2822'}</td></tr>\n";
+             "<tr><td>owner</td><td>$owner</td></tr>\n";
+       if (defined $cd{'rfc2822'}) {
+               print "<tr><td>last change</td><td>$cd{'rfc2822'}</td></tr>\n";
+       }
+
        # use per project git URL list in $projectroot/$project/cloneurl
        # or make project git URL from git base URL and project name
        my $url_tag = "URL";
@@ -3068,11 +3429,13 @@ sub git_summary {
 
        # we need to request one more than 16 (0..15) to check if
        # those 16 are all
-       my @commitlist = parse_commits($head, 17);
-       git_print_header_div('shortlog');
-       git_shortlog_body(\@commitlist, 0, 15, $refs,
-                         $#commitlist <=  15 ? undef :
-                         $cgi->a({-href => href(action=>"shortlog")}, "..."));
+       my @commitlist = $head ? parse_commits($head, 17) : ();
+       if (@commitlist) {
+               git_print_header_div('shortlog');
+               git_shortlog_body(\@commitlist, 0, 15, $refs,
+                                 $#commitlist <=  15 ? undef :
+                                 $cgi->a({-href => href(action=>"shortlog")}, "..."));
+       }
 
        if (@taglist) {
                git_print_header_div('tags');
@@ -3093,7 +3456,7 @@ sub git_summary {
                git_project_list_body(\@forklist, undef, 0, 15,
                                      $#forklist <= 15 ? undef :
                                      $cgi->a({-href => href(action=>"forks")}, "..."),
-                                     'noheader');
+                                     'noheader');
        }
 
        git_footer_html();
@@ -3104,6 +3467,11 @@ sub git_tag {
        git_header_html();
        git_print_page_nav('','', $head,undef,$head);
        my %tag = parse_tag($hash);
+
+       if (! %tag) {
+               die_error(undef, "Unknown tag object");
+       }
+
        git_print_header_div('commit', esc_html($tag{'name'}), $hash);
        print "<div class=\"title_text\">\n" .
              "<table cellspacing=\"0\">\n" .
@@ -3200,7 +3568,7 @@ HTML
                my $rev = substr($full_rev, 0, 8);
                my $author = $meta->{'author'};
                my %date = parse_date($meta->{'author-time'},
-                                     $meta->{'author-tz'});
+                                     $meta->{'author-tz'});
                my $date = $date{'iso-tz'};
                if ($group_size) {
                        $current_color = ++$current_color % $num_colors;
@@ -3212,9 +3580,9 @@ HTML
                        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($rev));
+                                                    hash=>$full_rev,
+                                                    file_name=>$file_name)},
+                                     esc_html($rev));
                        print "</td>\n";
                }
                open (my $dd, "-|", git_cmd(), "rev-parse", "$full_rev^")
@@ -3223,13 +3591,13 @@ HTML
                close $dd;
                chomp($parent_commit);
                my $blamed = href(action => 'blame',
-                                 file_name => $meta->{'filename'},
-                                 hash_base => $parent_commit);
+                                 file_name => $meta->{'filename'},
+                                 hash_base => $parent_commit);
                print "<td class=\"linenr\">";
                print $cgi->a({ -href => "$blamed#l$orig_lineno",
-                               -id => "l$lineno",
-                               -class => "linenr" },
-                             esc_html($lineno));
+                               -id => "l$lineno",
+                               -class => "linenr" },
+                             esc_html($lineno));
                print "</td>";
                print "<td class=\"pre\">" . esc_html($data) . "</td>\n";
                print "</tr>\n";
@@ -3619,7 +3987,7 @@ sub git_snapshot {
        my $name = $project;
        $name =~ s/\047/\047\\\047\047/g;
        open my $fd, "-|",
-       "$git archive --format=tar --prefix=\'$name\'/ $hash | $command"
+               "$git archive --format=tar --prefix=\'$name\'/ $hash | $command"
                or die_error(undef, "Execute git-tar-tree failed");
        binmode STDOUT, ':raw';
        print <$fd>;
@@ -3728,14 +4096,13 @@ sub git_commit {
                $parent = "--root";
        }
        my @difftree;
-       if (@$parents <= 1) {
-               # difftree output is not printed for merges
-               open my $fd, "-|", git_cmd(), "diff-tree", '-r', "--no-commit-id",
-                       @diff_opts, $parent, $hash, "--"
-                               or die_error(undef, "Open git-diff-tree failed");
-               @difftree = map { chomp; $_ } <$fd>;
-               close $fd or die_error(undef, "Reading git-diff-tree failed");
-       }
+       open my $fd, "-|", git_cmd(), "diff-tree", '-r', "--no-commit-id",
+               @diff_opts,
+               (@$parents <= 1 ? $parent : '-c'),
+               $hash, "--"
+               or die_error(undef, "Open git-diff-tree failed");
+       @difftree = map { chomp; $_ } <$fd>;
+       close $fd or die_error(undef, "Reading git-diff-tree failed");
 
        # non-textual hash id's can be cached
        my $expires;
@@ -3813,10 +4180,7 @@ sub git_commit {
        git_print_log($co{'comment'});
        print "</div>\n";
 
-       if (@$parents <= 1) {
-               # do not output difftree/whatchanged for merges
-               git_difftree_body(\@difftree, $hash, $parent);
-       }
+       git_difftree_body(\@difftree, $hash, @$parents);
 
        git_footer_html();
 }
@@ -4093,8 +4457,10 @@ sub git_commitdiff {
                }
        }
 
+       my $hash_parent_param = $hash_parent;
        if (!defined $hash_parent) {
-               $hash_parent = $co{'parent'} || '--root';
+               $hash_parent_param =
+                       @{$co{'parents'}} > 1 ? '-c' : $co{'parent'} || '--root';
        }
 
        # read commitdiff
@@ -4103,19 +4469,19 @@ sub git_commitdiff {
        if ($format eq 'html') {
                open $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts,
                        "--no-commit-id", "--patch-with-raw", "--full-index",
-                       $hash_parent, $hash, "--"
+                       $hash_parent_param, $hash, "--"
                        or die_error(undef, "Open git-diff-tree failed");
 
                while (my $line = <$fd>) {
                        chomp $line;
                        # empty line ends raw part of diff-tree output
                        last unless $line;
-                       push @difftree, $line;
+                       push @difftree, scalar parse_difftree_raw_line($line);
                }
 
        } elsif ($format eq 'plain') {
                open $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts,
-                       '-p', $hash_parent, $hash, "--"
+                       '-p', $hash_parent_param, $hash, "--"
                        or die_error(undef, "Open git-diff-tree failed");
 
        } else {
@@ -4171,10 +4537,10 @@ TEXT
 
        # write patch
        if ($format eq 'html') {
-               git_difftree_body(\@difftree, $hash, $hash_parent);
+               git_difftree_body(\@difftree, $hash, $hash_parent || @{$co{'parents'}});
                print "<br/>\n";
 
-               git_patchset_body($fd, \@difftree, $hash, $hash_parent);
+               git_patchset_body($fd, \@difftree, $hash, $hash_parent || @{$co{'parents'}});
                close $fd;
                print "</div>\n"; # class="page_body"
                git_footer_html();
@@ -4285,6 +4651,12 @@ sub git_search {
                        die_error('403 Permission denied', "Permission denied");
                }
        }
+       if ($searchtype eq 'grep') {
+               my ($have_grep) = gitweb_check_feature('grep');
+               if (!$have_grep) {
+                       die_error('403 Permission denied', "Permission denied");
+               }
+       }
 
        git_header_html();
 
@@ -4297,20 +4669,20 @@ sub git_search {
                } elsif ($searchtype eq 'committer') {
                        $greptype = "--committer=";
                }
-               $greptype .= $searchtext;
+               $greptype .= $search_regexp;
                my @commitlist = parse_commits($hash, 101, (100 * $page), $greptype);
 
                my $paging_nav = '';
                if ($page > 0) {
                        $paging_nav .=
                                $cgi->a({-href => href(action=>"search", hash=>$hash,
-                                                      searchtext=>$searchtext, searchtype=>$searchtype)},
-                                       "first");
+                                                      searchtext=>$searchtext, searchtype=>$searchtype)},
+                                       "first");
                        $paging_nav .= " &sdot; " .
                                $cgi->a({-href => href(action=>"search", hash=>$hash,
-                                                      searchtext=>$searchtext, searchtype=>$searchtype,
-                                                      page=>$page-1),
-                                        -accesskey => "p", -title => "Alt-p"}, "prev");
+                                                      searchtext=>$searchtext, searchtype=>$searchtype,
+                                                      page=>$page-1),
+                                        -accesskey => "p", -title => "Alt-p"}, "prev");
                } else {
                        $paging_nav .= "first";
                        $paging_nav .= " &sdot; prev";
@@ -4318,9 +4690,9 @@ sub git_search {
                if ($#commitlist >= 100) {
                        $paging_nav .= " &sdot; " .
                                $cgi->a({-href => href(action=>"search", hash=>$hash,
-                                                      searchtext=>$searchtext, searchtype=>$searchtype,
-                                                      page=>$page+1),
-                                        -accesskey => "n", -title => "Alt-n"}, "next");
+                                                      searchtext=>$searchtext, searchtype=>$searchtype,
+                                                      page=>$page+1),
+                                        -accesskey => "n", -title => "Alt-n"}, "next");
                } else {
                        $paging_nav .= " &sdot; next";
                }
@@ -4328,9 +4700,9 @@ sub git_search {
                if ($#commitlist >= 100) {
                        $next_link =
                                $cgi->a({-href => href(action=>"search", hash=>$hash,
-                                                      searchtext=>$searchtext, searchtype=>$searchtype,
-                                                      page=>$page+1),
-                                        -accesskey => "n", -title => "Alt-n"}, "next");
+                                                      searchtext=>$searchtext, searchtype=>$searchtype,
+                                                      page=>$page+1),
+                                        -accesskey => "n", -title => "Alt-n"}, "next");
                }
 
                git_print_page_nav('','', $hash,$co{'tree'},$hash, $paging_nav);
@@ -4346,8 +4718,10 @@ sub git_search {
                my $alternate = 1;
                $/ = "\n";
                my $git_command = git_cmd_str();
+               my $searchqtext = $searchtext;
+               $searchqtext =~ s/'/'\\''/;
                open my $fd, "-|", "$git_command rev-list $hash | " .
-                       "$git_command diff-tree -r --stdin -S\'$searchtext\'";
+                       "$git_command diff-tree -r --stdin -S\'$searchqtext\'";
                undef %co;
                my @files;
                while (my $line = <$fd>) {
@@ -4401,6 +4775,73 @@ sub git_search {
 
                print "</table>\n";
        }
+
+       if ($searchtype eq 'grep') {
+               git_print_page_nav('','', $hash,$co{'tree'},$hash);
+               git_print_header_div('commit', esc_html($co{'title'}), $hash);
+
+               print "<table cellspacing=\"0\">\n";
+               my $alternate = 1;
+               my $matches = 0;
+               $/ = "\n";
+               open my $fd, "-|", git_cmd(), 'grep', '-n', '-i', '-E', $searchtext, $co{'tree'};
+               my $lastfile = '';
+               while (my $line = <$fd>) {
+                       chomp $line;
+                       my ($file, $lno, $ltext, $binary);
+                       last if ($matches++ > 1000);
+                       if ($line =~ /^Binary file (.+) matches$/) {
+                               $file = $1;
+                               $binary = 1;
+                       } else {
+                               (undef, $file, $lno, $ltext) = split(/:/, $line, 4);
+                       }
+                       if ($file ne $lastfile) {
+                               $lastfile and print "</td></tr>\n";
+                               if ($alternate++) {
+                                       print "<tr class=\"dark\">\n";
+                               } else {
+                                       print "<tr class=\"light\">\n";
+                               }
+                               print "<td class=\"list\">".
+                                       $cgi->a({-href => href(action=>"blob", hash=>$co{'hash'},
+                                                              file_name=>"$file"),
+                                               -class => "list"}, esc_path($file));
+                               print "</td><td>\n";
+                               $lastfile = $file;
+                       }
+                       if ($binary) {
+                               print "<div class=\"binary\">Binary file</div>\n";
+                       } else {
+                               $ltext = untabify($ltext);
+                               if ($ltext =~ m/^(.*)($searchtext)(.*)$/i) {
+                                       $ltext = esc_html($1, -nbsp=>1);
+                                       $ltext .= '<span class="match">';
+                                       $ltext .= esc_html($2, -nbsp=>1);
+                                       $ltext .= '</span>';
+                                       $ltext .= esc_html($3, -nbsp=>1);
+                               } else {
+                                       $ltext = esc_html($ltext, -nbsp=>1);
+                               }
+                               print "<div class=\"pre\">" .
+                                       $cgi->a({-href => href(action=>"blob", hash=>$co{'hash'},
+                                                              file_name=>"$file").'#l'.$lno,
+                                               -class => "linenr"}, sprintf('%4i', $lno))
+                                       . ' ' .  $ltext . "</div>\n";
+                       }
+               }
+               if ($lastfile) {
+                       print "</td></tr>\n";
+                       if ($matches > 1000) {
+                               print "<div class=\"diff nodifferences\">Too many matches, listing trimmed</div>\n";
+                       }
+               } else {
+                       print "<div class=\"diff nodifferences\">No matches found</div>\n";
+               }
+               close $fd;
+
+               print "</table>\n";
+       }
        git_footer_html();
 }
 
@@ -4411,6 +4852,20 @@ sub git_search_help {
 <dl>
 <dt><b>commit</b></dt>
 <dd>The commit messages and authorship information will be scanned for the given string.</dd>
+EOT
+       my ($have_grep) = gitweb_check_feature('grep');
+       if ($have_grep) {
+               print <<EOT;
+<dt><b>grep</b></dt>
+<dd>All files in the currently selected tree (HEAD unless you are explicitly browsing
+    a different one) are searched for the given
+<a href="http://en.wikipedia.org/wiki/Regular_expression">regular expression</a>
+(POSIX extended) and the matches are listed. On large
+trees, this search can take a while and put some strain on the server, so please use it with
+some consideration.</dd>
+EOT
+       }
+       print <<EOT;
 <dt><b>author</b></dt>
 <dd>Name and e-mail of the change author and date of birth of the patch will be scanned for the given string.</dd>
 <dt><b>committer</b></dt>
@@ -4585,7 +5040,8 @@ XML
 
                # get list of changed files
                open my $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts,
-                       $co{'parent'}, $co{'id'}, "--", (defined $file_name ? $file_name : ())
+                       $co{'parent'} || "--root",
+                       $co{'id'}, "--", (defined $file_name ? $file_name : ())
                        or next;
                my @difftree = map { chomp; $_ } <$fd>;
                close $fd
diff --git a/ident.c b/ident.c
index bb03bddd34f2471ed130c499affc369758d4bfd9..69a04b827d7805d9b29153e31e5489edb587690a 100644 (file)
--- a/ident.c
+++ b/ident.c
@@ -9,10 +9,10 @@
 
 static char git_default_date[50];
 
-static void copy_gecos(struct passwd *w, char *name, int sz)
+static void copy_gecos(const struct passwd *w, char *name, size_t sz)
 {
        char *src, *dst;
-       int len, nlen;
+       size_t len, nlen;
 
        nlen = strlen(w->pw_name);
 
@@ -43,13 +43,13 @@ static void copy_gecos(struct passwd *w, char *name, int sz)
 
 }
 
-static void copy_email(struct passwd *pw)
+static void copy_email(const struct passwd *pw)
 {
        /*
         * Make up a fake email address
         * (name + '@' + hostname [+ '.' + domainname])
         */
-       int len = strlen(pw->pw_name);
+       size_t len = strlen(pw->pw_name);
        if (len > sizeof(git_default_email)/2)
                die("Your sysadmin must hate you!");
        memcpy(git_default_email, pw->pw_name, len);
@@ -95,9 +95,9 @@ static void setup_ident(void)
                datestamp(git_default_date, sizeof(git_default_date));
 }
 
-static int add_raw(char *buf, int size, int offset, const char *str)
+static int add_raw(char *buf, size_t size, int offset, const char *str)
 {
-       int len = strlen(str);
+       size_t len = strlen(str);
        if (offset + len > size)
                return size;
        memcpy(buf + offset, str, len);
@@ -131,9 +131,9 @@ static int crud(unsigned char c)
  * Copy over a string to the destination, but avoid special
  * characters ('\n', '<' and '>') and remove crud at the end
  */
-static int copy(char *buf, int size, int offset, const char *src)
+static int copy(char *buf, size_t size, int offset, const char *src)
 {
-       int i, len;
+       size_t i, len;
        unsigned char c;
 
        /* Remove crud from the beginning.. */
@@ -195,6 +195,8 @@ const char *fmt_ident(const char *name, const char *email,
        setup_ident();
        if (!name)
                name = git_default_name;
+       if (!email)
+               email = getenv("EMAIL");
        if (!email)
                email = git_default_email;
 
index 84df2fabb7f91d30f7617dd07202ed916c5f8eb1..4283a4acdaf28a8edc2f595e6137cc396f4988dc 100644 (file)
@@ -224,7 +224,7 @@ socket_perror( const char *func, Socket_t *sock, int ret )
 static int
 socket_read( Socket_t *sock, char *buf, int len )
 {
-       int n = xread( sock->fd, buf, len );
+       ssize_t n = xread( sock->fd, buf, len );
        if (n <= 0) {
                socket_perror( "read", sock, n );
                close( sock->fd );
index 3c768fbc631387b59bbbae3423b65b6a311a702b..58c4a9c41dd7a05b86d40e6eeee33ba0a3fb6c4f 100644 (file)
@@ -6,15 +6,17 @@
 #include "commit.h"
 #include "tag.h"
 #include "tree.h"
+#include "progress.h"
 
 static const char index_pack_usage[] =
 "git-index-pack [-v] [-o <index-file>] [{ ---keep | --keep=<msg> }] { <pack-file> | --stdin [--fix-thin] [<pack-file>] }";
 
 struct object_entry
 {
-       unsigned long offset;
+       off_t offset;
        unsigned long size;
        unsigned int hdr_size;
+       uint32_t crc32;
        enum object_type type;
        enum object_type real_type;
        unsigned char sha1[20];
@@ -22,7 +24,7 @@ struct object_entry
 
 union delta_base {
        unsigned char sha1[20];
-       unsigned long offset;
+       off_t offset;
 };
 
 /*
@@ -46,45 +48,14 @@ static int nr_resolved_deltas;
 static int from_stdin;
 static int verbose;
 
-static volatile sig_atomic_t progress_update;
-
-static void progress_interval(int signum)
-{
-       progress_update = 1;
-}
-
-static void setup_progress_signal(void)
-{
-       struct sigaction sa;
-       struct itimerval v;
-
-       memset(&sa, 0, sizeof(sa));
-       sa.sa_handler = progress_interval;
-       sigemptyset(&sa.sa_mask);
-       sa.sa_flags = SA_RESTART;
-       sigaction(SIGALRM, &sa, NULL);
-
-       v.it_interval.tv_sec = 1;
-       v.it_interval.tv_usec = 0;
-       v.it_value = v.it_interval;
-       setitimer(ITIMER_REAL, &v, NULL);
-
-}
-
-static unsigned display_progress(unsigned n, unsigned total, unsigned last_pc)
-{
-       unsigned percent = n * 100 / total;
-       if (percent != last_pc || progress_update) {
-               fprintf(stderr, "%4u%% (%u/%u) done\r", percent, n, total);
-               progress_update = 0;
-       }
-       return percent;
-}
+static struct progress progress;
 
 /* We always read in 4kB chunks. */
 static unsigned char input_buffer[4096];
-static unsigned long input_offset, input_len, consumed_bytes;
+static unsigned int input_offset, input_len;
+static off_t consumed_bytes;
 static SHA_CTX input_ctx;
+static uint32_t input_crc32;
 static int input_fd, output_fd, pack_fd;
 
 /* Discard current buffer used content. */
@@ -111,7 +82,7 @@ static void *fill(int min)
                die("cannot fill %d bytes", min);
        flush();
        do {
-               int ret = xread(input_fd, input_buffer + input_len,
+               ssize_t ret = xread(input_fd, input_buffer + input_len,
                                sizeof(input_buffer) - input_len);
                if (ret <= 0) {
                        if (!ret)
@@ -127,8 +98,13 @@ static void use(int bytes)
 {
        if (bytes > input_len)
                die("used more bytes than were available");
+       input_crc32 = crc32(input_crc32, input_buffer + input_offset, bytes);
        input_len -= bytes;
        input_offset += bytes;
+
+       /* make sure off_t is sufficiently large not to wrap */
+       if (consumed_bytes > consumed_bytes + bytes)
+               die("pack too large for current definition of off_t");
        consumed_bytes += bytes;
 }
 
@@ -216,10 +192,13 @@ static void *unpack_entry_data(unsigned long offset, unsigned long size)
 static void *unpack_raw_entry(struct object_entry *obj, union delta_base *delta_base)
 {
        unsigned char *p, c;
-       unsigned long size, base_offset;
+       unsigned long size;
+       off_t base_offset;
        unsigned shift;
+       void *data;
 
        obj->offset = consumed_bytes;
+       input_crc32 = crc32(0, Z_NULL, 0);
 
        p = fill(1);
        c = *p;
@@ -249,7 +228,7 @@ static void *unpack_raw_entry(struct object_entry *obj, union delta_base *delta_
                base_offset = c & 127;
                while (c & 128) {
                        base_offset += 1;
-                       if (!base_offset || base_offset & ~(~0UL >> 7))
+                       if (!base_offset || MSB(base_offset, 7))
                                bad_object(obj->offset, "offset value overflow for delta base object");
                        p = fill(1);
                        c = *p;
@@ -270,7 +249,9 @@ static void *unpack_raw_entry(struct object_entry *obj, union delta_base *delta_
        }
        obj->hdr_size = consumed_bytes - obj->offset;
 
-       return unpack_entry_data(obj->offset, obj->size);
+       data = unpack_entry_data(obj->offset, obj->size);
+       obj->crc32 = input_crc32;
+       return data;
 }
 
 static void *get_data_from_pack(struct object_entry *obj)
@@ -415,7 +396,7 @@ static int compare_delta_entry(const void *a, const void *b)
 /* Parse all objects and return the pack content SHA1 hash */
 static void parse_pack_objects(unsigned char *sha1)
 {
-       int i, percent = -1;
+       int i;
        struct delta_entry *delta = deltas;
        void *data;
        struct stat st;
@@ -427,7 +408,7 @@ static void parse_pack_objects(unsigned char *sha1)
         * - remember base (SHA1 or offset) for all deltas.
         */
        if (verbose)
-               fprintf(stderr, "Indexing %d objects.\n", nr_objects);
+               start_progress(&progress, "Indexing %u objects...", "", nr_objects);
        for (i = 0; i < nr_objects; i++) {
                struct object_entry *obj = &objects[i];
                data = unpack_raw_entry(obj, &delta->base);
@@ -440,11 +421,11 @@ static void parse_pack_objects(unsigned char *sha1)
                        sha1_object(data, obj->size, obj->type, obj->sha1);
                free(data);
                if (verbose)
-                       percent = display_progress(i+1, nr_objects, percent);
+                       display_progress(&progress, i+1);
        }
        objects[i].offset = consumed_bytes;
        if (verbose)
-               fputc('\n', stderr);
+               stop_progress(&progress);
 
        /* Check pack integrity */
        flush();
@@ -476,7 +457,7 @@ static void parse_pack_objects(unsigned char *sha1)
         *   for some more deltas.
         */
        if (verbose)
-               fprintf(stderr, "Resolving %d deltas.\n", nr_deltas);
+               start_progress(&progress, "Resolving %u deltas...", "", nr_deltas);
        for (i = 0; i < nr_objects; i++) {
                struct object_entry *obj = &objects[i];
                union delta_base base;
@@ -508,14 +489,11 @@ static void parse_pack_objects(unsigned char *sha1)
                        }
                free(data);
                if (verbose)
-                       percent = display_progress(nr_resolved_deltas,
-                                                  nr_deltas, percent);
+                       display_progress(&progress, nr_resolved_deltas);
        }
-       if (verbose && nr_resolved_deltas == nr_deltas)
-               fputc('\n', stderr);
 }
 
-static int write_compressed(int fd, void *in, unsigned int size)
+static int write_compressed(int fd, void *in, unsigned int size, uint32_t *obj_crc)
 {
        z_stream stream;
        unsigned long maxsize;
@@ -536,6 +514,7 @@ static int write_compressed(int fd, void *in, unsigned int size)
 
        size = stream.total_out;
        write_or_die(fd, out, size);
+       *obj_crc = crc32(*obj_crc, out, size);
        free(out);
        return size;
 }
@@ -556,8 +535,10 @@ static void append_obj_to_pack(const unsigned char *sha1, void *buf,
        }
        header[n++] = c;
        write_or_die(output_fd, header, n);
+       obj[0].crc32 = crc32(0, Z_NULL, 0);
+       obj[0].crc32 = crc32(obj[0].crc32, header, n);
        obj[1].offset = obj[0].offset + n;
-       obj[1].offset += write_compressed(output_fd, buf, size);
+       obj[1].offset += write_compressed(output_fd, buf, size, &obj[0].crc32);
        hashcpy(obj->sha1, sha1);
 }
 
@@ -571,7 +552,7 @@ static int delta_pos_compare(const void *_a, const void *_b)
 static void fix_unresolved_deltas(int nr_unresolved)
 {
        struct delta_entry **sorted_by_pos;
-       int i, n = 0, percent = -1;
+       int i, n = 0;
 
        /*
         * Since many unresolved deltas may well be themselves base objects
@@ -616,44 +597,13 @@ static void fix_unresolved_deltas(int nr_unresolved)
                append_obj_to_pack(d->base.sha1, data, size, type);
                free(data);
                if (verbose)
-                       percent = display_progress(nr_resolved_deltas,
-                                                  nr_deltas, percent);
+                       display_progress(&progress, nr_resolved_deltas);
        }
        free(sorted_by_pos);
-       if (verbose)
-               fputc('\n', stderr);
 }
 
-static void readjust_pack_header_and_sha1(unsigned char *sha1)
-{
-       struct pack_header hdr;
-       SHA_CTX ctx;
-       int size;
-
-       /* Rewrite pack header with updated object number */
-       if (lseek(output_fd, 0, SEEK_SET) != 0)
-               die("cannot seek back: %s", strerror(errno));
-       if (read_in_full(output_fd, &hdr, sizeof(hdr)) != sizeof(hdr))
-               die("cannot read pack header back: %s", strerror(errno));
-       hdr.hdr_entries = htonl(nr_objects);
-       if (lseek(output_fd, 0, SEEK_SET) != 0)
-               die("cannot seek back: %s", strerror(errno));
-       write_or_die(output_fd, &hdr, sizeof(hdr));
-       if (lseek(output_fd, 0, SEEK_SET) != 0)
-               die("cannot seek back: %s", strerror(errno));
-
-       /* Recompute and store the new pack's SHA1 */
-       SHA1_Init(&ctx);
-       do {
-               unsigned char *buf[4096];
-               size = xread(output_fd, buf, sizeof(buf));
-               if (size < 0)
-                       die("cannot read pack data back: %s", strerror(errno));
-               SHA1_Update(&ctx, buf, size);
-       } while (size > 0);
-       SHA1_Final(sha1, &ctx);
-       write_or_die(output_fd, sha1, 20);
-}
+static uint32_t index_default_version = 1;
+static uint32_t index_off32_limit = 0x7fffffff;
 
 static int sha1_compare(const void *_a, const void *_b)
 {
@@ -670,9 +620,10 @@ static const char *write_index_file(const char *index_name, unsigned char *sha1)
 {
        struct sha1file *f;
        struct object_entry **sorted_by_sha, **list, **last;
-       unsigned int array[256];
+       uint32_t array[256];
        int i, fd;
        SHA_CTX ctx;
+       uint32_t index_version;
 
        if (nr_objects) {
                sorted_by_sha =
@@ -683,7 +634,6 @@ static const char *write_index_file(const char *index_name, unsigned char *sha1)
                        sorted_by_sha[i] = &objects[i];
                qsort(sorted_by_sha, nr_objects, sizeof(sorted_by_sha[0]),
                      sha1_compare);
-
        }
        else
                sorted_by_sha = list = last = NULL;
@@ -702,6 +652,17 @@ static const char *write_index_file(const char *index_name, unsigned char *sha1)
                die("unable to create %s: %s", index_name, strerror(errno));
        f = sha1fd(fd, index_name);
 
+       /* if last object's offset is >= 2^31 we should use index V2 */
+       index_version = (objects[nr_objects-1].offset >> 31) ? 2 : index_default_version;
+
+       /* index versions 2 and above need a header */
+       if (index_version >= 2) {
+               struct pack_idx_header hdr;
+               hdr.idx_signature = htonl(PACK_IDX_SIGNATURE);
+               hdr.idx_version = htonl(index_version);
+               sha1write(f, &hdr, sizeof(hdr));
+       }
+
        /*
         * Write the first-level table (the list is sorted,
         * but we use a 256-entry lookup to be able to avoid
@@ -718,24 +679,61 @@ static const char *write_index_file(const char *index_name, unsigned char *sha1)
                array[i] = htonl(next - sorted_by_sha);
                list = next;
        }
-       sha1write(f, array, 256 * sizeof(int));
+       sha1write(f, array, 256 * 4);
 
-       /* recompute the SHA1 hash of sorted object names.
-        * currently pack-objects does not do this, but that
-        * can be fixed.
-        */
+       /* compute the SHA1 hash of sorted object names. */
        SHA1_Init(&ctx);
+
        /*
         * Write the actual SHA1 entries..
         */
        list = sorted_by_sha;
        for (i = 0; i < nr_objects; i++) {
                struct object_entry *obj = *list++;
-               unsigned int offset = htonl(obj->offset);
-               sha1write(f, &offset, 4);
+               if (index_version < 2) {
+                       uint32_t offset = htonl(obj->offset);
+                       sha1write(f, &offset, 4);
+               }
                sha1write(f, obj->sha1, 20);
                SHA1_Update(&ctx, obj->sha1, 20);
        }
+
+       if (index_version >= 2) {
+               unsigned int nr_large_offset = 0;
+
+               /* write the crc32 table */
+               list = sorted_by_sha;
+               for (i = 0; i < nr_objects; i++) {
+                       struct object_entry *obj = *list++;
+                       uint32_t crc32_val = htonl(obj->crc32);
+                       sha1write(f, &crc32_val, 4);
+               }
+
+               /* write the 32-bit offset table */
+               list = sorted_by_sha;
+               for (i = 0; i < nr_objects; i++) {
+                       struct object_entry *obj = *list++;
+                       uint32_t offset = (obj->offset <= index_off32_limit) ?
+                               obj->offset : (0x80000000 | nr_large_offset++);
+                       offset = htonl(offset);
+                       sha1write(f, &offset, 4);
+               }
+
+               /* write the large offset table */
+               list = sorted_by_sha;
+               while (nr_large_offset) {
+                       struct object_entry *obj = *list++;
+                       uint64_t offset = obj->offset;
+                       if (offset > index_off32_limit) {
+                               uint32_t split[2];
+                               split[0]        = htonl(offset >> 32);
+                               split[1] = htonl(offset & 0xffffffff);
+                               sha1write(f, split, 8);
+                               nr_large_offset--;
+                       }
+               }
+       }
+
        sha1write(f, sha1, 20);
        sha1close(f, NULL, 1);
        free(sorted_by_sha);
@@ -865,6 +863,15 @@ int main(int argc, char **argv)
                                if (index_name || (i+1) >= argc)
                                        usage(index_pack_usage);
                                index_name = argv[++i];
+                       } else if (!prefixcmp(arg, "--index-version=")) {
+                               char *c;
+                               index_default_version = strtoul(arg + 16, &c, 10);
+                               if (index_default_version > 2)
+                                       die("bad %s", arg);
+                               if (*c == ',')
+                                       index_off32_limit = strtoul(c+1, &c, 0);
+                               if (*c || index_off32_limit & 0x80000000)
+                                       die("bad %s", arg);
                        } else
                                usage(index_pack_usage);
                        continue;
@@ -904,10 +911,13 @@ int main(int argc, char **argv)
        parse_pack_header();
        objects = xmalloc((nr_objects + 1) * sizeof(struct object_entry));
        deltas = xmalloc(nr_objects * sizeof(struct delta_entry));
-       if (verbose)
-               setup_progress_signal();
        parse_pack_objects(sha1);
-       if (nr_deltas != nr_resolved_deltas) {
+       if (nr_deltas == nr_resolved_deltas) {
+               if (verbose)
+                       stop_progress(&progress);
+               /* Flush remaining pack final 20-byte SHA1. */
+               flush();
+       } else {
                if (fix_thin_pack) {
                        int nr_unresolved = nr_deltas - nr_resolved_deltas;
                        int nr_objects_initial = nr_objects;
@@ -917,17 +927,17 @@ int main(int argc, char **argv)
                                           (nr_objects + nr_unresolved + 1)
                                           * sizeof(*objects));
                        fix_unresolved_deltas(nr_unresolved);
-                       if (verbose)
+                       if (verbose) {
+                               stop_progress(&progress);
                                fprintf(stderr, "%d objects were added to complete this thin pack.\n",
                                        nr_objects - nr_objects_initial);
-                       readjust_pack_header_and_sha1(sha1);
+                       }
+                       fixup_pack_header_footer(output_fd, sha1,
+                               curr_pack, nr_objects);
                }
                if (nr_deltas != nr_resolved_deltas)
                        die("pack has %d unresolved deltas",
                            nr_deltas - nr_resolved_deltas);
-       } else {
-               /* Flush remaining pack final 20-byte SHA1. */
-               flush();
        }
        free(deltas);
        curr_index = write_index_file(index_name, sha1);
index 2ba2c958e0aac63f0d5b092019e71f6905edb052..310f8d39082a12d2c3daddd1fca454686e7425c3 100644 (file)
@@ -25,6 +25,37 @@ static void process_blob(struct rev_info *revs,
        add_object(obj, p, path, name);
 }
 
+/*
+ * Processing a gitlink entry currently does nothing, since
+ * we do not recurse into the subproject.
+ *
+ * We *could* eventually add a flag that actually does that,
+ * which would involve:
+ *  - is the subproject actually checked out?
+ *  - if so, see if the subproject has already been added
+ *    to the alternates list, and add it if not.
+ *  - process the commit (or tag) the gitlink points to
+ *    recursively.
+ *
+ * However, it's unclear whether there is really ever any
+ * reason to see superprojects and subprojects as such a
+ * "unified" object pool (potentially resulting in a totally
+ * humongous pack - avoiding which was the whole point of
+ * having gitlinks in the first place!).
+ *
+ * So for now, there is just a note that we *could* follow
+ * the link, and how to do it. Whether it necessarily makes
+ * any sense what-so-ever to ever do that is another issue.
+ */
+static void process_gitlink(struct rev_info *revs,
+                           const unsigned char *sha1,
+                           struct object_array *p,
+                           struct name_path *path,
+                           const char *name)
+{
+       /* Nothing to do */
+}
+
 static void process_tree(struct rev_info *revs,
                         struct tree *tree,
                         struct object_array *p,
@@ -56,6 +87,9 @@ static void process_tree(struct rev_info *revs,
                        process_tree(revs,
                                     lookup_tree(entry.sha1),
                                     p, &me, entry.path);
+               else if (S_ISDIRLNK(entry.mode))
+                       process_gitlink(revs, entry.sha1,
+                                       p, &me, entry.path);
                else
                        process_blob(revs,
                                     lookup_blob(entry.sha1),
index 4824f4dc026e7b3f978fe4e9b2154335359e9d2e..23db35aff21d7c33197726128e2f120291e6e9f0 100644 (file)
@@ -4,11 +4,15 @@
 #include "cache.h"
 
 static struct lock_file *lock_file_list;
+static const char *alternate_index_output;
 
 static void remove_lock_file(void)
 {
+       pid_t me = getpid();
+
        while (lock_file_list) {
-               if (lock_file_list->filename[0])
+               if (lock_file_list->owner == me &&
+                   lock_file_list->filename[0])
                        unlink(lock_file_list->filename);
                lock_file_list = lock_file_list->next;
        }
@@ -27,6 +31,7 @@ static int lock_file(struct lock_file *lk, const char *path)
        sprintf(lk->filename, "%s.lock", path);
        fd = open(lk->filename, O_RDWR | O_CREAT | O_EXCL, 0666);
        if (0 <= fd) {
+               lk->owner = getpid();
                if (!lk->on_list) {
                        lk->next = lock_file_list;
                        lock_file_list = lk;
@@ -65,6 +70,27 @@ int commit_lock_file(struct lock_file *lk)
        return i;
 }
 
+int hold_locked_index(struct lock_file *lk, int die_on_error)
+{
+       return hold_lock_file_for_update(lk, get_index_file(), die_on_error);
+}
+
+void set_alternate_index_output(const char *name)
+{
+       alternate_index_output = name;
+}
+
+int commit_locked_index(struct lock_file *lk)
+{
+       if (alternate_index_output) {
+               int result = rename(lk->filename, alternate_index_output);
+               lk->filename[0] = 0;
+               return result;
+       }
+       else
+               return commit_lock_file(lk);
+}
+
 void rollback_lock_file(struct lock_file *lk)
 {
        if (lk->filename[0])
index dbd06490f97c735171974f068b48dbdbbb992723..4bef909144b2b5b402ee7cf0d959a0995c00ff0e 100644 (file)
@@ -4,6 +4,8 @@
 #include "log-tree.h"
 #include "reflog-walk.h"
 
+struct decoration name_decoration = { "object names" };
+
 static void show_parents(struct commit *commit, int abbrev)
 {
        struct commit_list *p;
@@ -13,6 +15,23 @@ static void show_parents(struct commit *commit, int abbrev)
        }
 }
 
+static void show_decorations(struct commit *commit)
+{
+       const char *prefix;
+       struct name_decoration *decoration;
+
+       decoration = lookup_decoration(&name_decoration, &commit->object);
+       if (!decoration)
+               return;
+       prefix = " (";
+       while (decoration) {
+               printf("%s%s", prefix, decoration->name);
+               prefix = ", ";
+               decoration = decoration->next;
+       }
+       putchar(')');
+}
+
 /*
  * Search for "^[-A-Za-z]+: [^@]+@" pattern. It usually matches
  * Signed-off-by: and Acked-by: lines.
@@ -136,6 +155,7 @@ void show_log(struct rev_info *opt, const char *sep)
                fputs(diff_unique_abbrev(commit->object.sha1, abbrev_commit), stdout);
                if (opt->parents)
                        show_parents(commit, abbrev_commit);
+               show_decorations(commit);
                putchar(opt->diffopt.line_termination);
                return;
        }
@@ -165,14 +185,20 @@ void show_log(struct rev_info *opt, const char *sep)
                if (opt->total > 0) {
                        static char buffer[64];
                        snprintf(buffer, sizeof(buffer),
-                                       "Subject: [PATCH %0*d/%d] ",
+                                       "Subject: [%s %0*d/%d] ",
+                                       opt->subject_prefix,
                                        digits_in_number(opt->total),
                                        opt->nr, opt->total);
                        subject = buffer;
-               } else if (opt->total == 0)
-                       subject = "Subject: [PATCH] ";
-               else
+               } else if (opt->total == 0) {
+                       static char buffer[256];
+                       snprintf(buffer, sizeof(buffer),
+                                       "Subject: [%s] ",
+                                       opt->subject_prefix);
+                       subject = buffer;
+               } else {
                        subject = "Subject: ";
+               }
 
                printf("From %s Mon Sep 17 00:00:00 2001\n", sha1);
                if (opt->message_id)
@@ -234,13 +260,14 @@ void show_log(struct rev_info *opt, const char *sep)
                        printf(" (from %s)",
                               diff_unique_abbrev(parent->object.sha1,
                                                  abbrev_commit));
+               show_decorations(commit);
                printf("%s",
                       diff_get_color(opt->diffopt.color_diff, DIFF_RESET));
                putchar(opt->commit_format == CMIT_FMT_ONELINE ? ' ' : '\n');
                if (opt->reflog_info) {
                        show_reflog_message(opt->reflog_info,
                                    opt->commit_format == CMIT_FMT_ONELINE,
-                                   opt->relative_date);
+                                   opt->date_mode);
                        if (opt->commit_format == CMIT_FMT_ONELINE) {
                                printf("%s", sep);
                                return;
@@ -253,7 +280,7 @@ void show_log(struct rev_info *opt, const char *sep)
         */
        len = pretty_print_commit(opt->commit_format, commit, ~0u, this_header,
                                  sizeof(this_header), abbrev, subject,
-                                 extra_headers, opt->relative_date);
+                                 extra_headers, opt->date_mode);
 
        if (opt->add_signoff)
                len = append_signoff(this_header, sizeof(this_header), len,
diff --git a/mailmap.c b/mailmap.c
new file mode 100644 (file)
index 0000000..cb567a2
--- /dev/null
+++ b/mailmap.c
@@ -0,0 +1,92 @@
+#include "cache.h"
+#include "path-list.h"
+#include "mailmap.h"
+
+int read_mailmap(struct path_list *map, const char *filename, char **repo_abbrev)
+{
+       char buffer[1024];
+       FILE *f = fopen(filename, "r");
+
+       if (f == NULL)
+               return 1;
+       while (fgets(buffer, sizeof(buffer), f) != NULL) {
+               char *end_of_name, *left_bracket, *right_bracket;
+               char *name, *email;
+               int i;
+               if (buffer[0] == '#') {
+                       static const char abbrev[] = "# repo-abbrev:";
+                       int abblen = sizeof(abbrev) - 1;
+                       int len = strlen(buffer);
+
+                       if (!repo_abbrev)
+                               continue;
+
+                       if (len && buffer[len - 1] == '\n')
+                               buffer[--len] = 0;
+                       if (!strncmp(buffer, abbrev, abblen)) {
+                               char *cp;
+
+                               if (repo_abbrev)
+                                       free(*repo_abbrev);
+                               *repo_abbrev = xmalloc(len);
+
+                               for (cp = buffer + abblen; isspace(*cp); cp++)
+                                       ; /* nothing */
+                               strcpy(*repo_abbrev, cp);
+                       }
+                       continue;
+               }
+               if ((left_bracket = strchr(buffer, '<')) == NULL)
+                       continue;
+               if ((right_bracket = strchr(left_bracket + 1, '>')) == NULL)
+                       continue;
+               if (right_bracket == left_bracket + 1)
+                       continue;
+               for (end_of_name = left_bracket; end_of_name != buffer
+                               && isspace(end_of_name[-1]); end_of_name--)
+                       /* keep on looking */
+               if (end_of_name == buffer)
+                       continue;
+               name = xmalloc(end_of_name - buffer + 1);
+               strlcpy(name, buffer, end_of_name - buffer + 1);
+               email = xmalloc(right_bracket - left_bracket);
+               for (i = 0; i < right_bracket - left_bracket - 1; i++)
+                       email[i] = tolower(left_bracket[i + 1]);
+               email[right_bracket - left_bracket - 1] = '\0';
+               path_list_insert(email, map)->util = name;
+       }
+       fclose(f);
+       return 0;
+}
+
+int map_email(struct path_list *map, const char *email, char *name, int maxlen)
+{
+       char *p;
+       struct path_list_item *item;
+       char buf[1024], *mailbuf;
+       int i;
+
+       /* autocomplete common developers */
+       p = strchr(email, '>');
+       if (!p)
+               return 0;
+       if (p - email + 1 < sizeof(buf))
+               mailbuf = buf;
+       else
+               mailbuf = xmalloc(p - email + 1);
+
+       /* downcase the email address */
+       for (i = 0; i < p - email; i++)
+               mailbuf[i] = tolower(email[i]);
+       mailbuf[i] = 0;
+       item = path_list_lookup(mailbuf, map);
+       if (mailbuf != buf)
+               free(mailbuf);
+       if (item != NULL) {
+               const char *realname = (const char *)item->util;
+               strlcpy(name, realname, maxlen);
+               return 1;
+       }
+       return 0;
+}
+
diff --git a/mailmap.h b/mailmap.h
new file mode 100644 (file)
index 0000000..3503fd2
--- /dev/null
+++ b/mailmap.h
@@ -0,0 +1,7 @@
+#ifndef MAILMAP_H
+#define MAILMAP_H
+
+int read_mailmap(struct path_list *map, const char *filename, char **repo_abbrev);
+int map_email(struct path_list *mailmap, const char *email, char *name, int maxlen);
+
+#endif
diff --git a/match-trees.c b/match-trees.c
new file mode 100644 (file)
index 0000000..23cafe4
--- /dev/null
@@ -0,0 +1,304 @@
+#include "cache.h"
+#include "tree.h"
+#include "tree-walk.h"
+
+static int score_missing(unsigned mode, const char *path)
+{
+       int score;
+
+       if (S_ISDIR(mode))
+               score = -1000;
+       else if (S_ISLNK(mode))
+               score = -500;
+       else
+               score = -50;
+       return score;
+}
+
+static int score_differs(unsigned mode1, unsigned mode2, const char *path)
+{
+       int score;
+
+       if (S_ISDIR(mode1) != S_ISDIR(mode2))
+               score = -100;
+       else if (S_ISLNK(mode1) != S_ISLNK(mode2))
+               score = -50;
+       else
+               score = -5;
+       return score;
+}
+
+static int score_matches(unsigned mode1, unsigned mode2, const char *path)
+{
+       int score;
+
+       /* Heh, we found SHA-1 collisions between different kind of objects */
+       if (S_ISDIR(mode1) != S_ISDIR(mode2))
+               score = -100;
+       else if (S_ISLNK(mode1) != S_ISLNK(mode2))
+               score = -50;
+
+       else if (S_ISDIR(mode1))
+               score = 1000;
+       else if (S_ISLNK(mode1))
+               score = 500;
+       else
+               score = 250;
+       return score;
+}
+
+/*
+ * Inspect two trees, and give a score that tells how similar they are.
+ */
+static int score_trees(const unsigned char *hash1, const unsigned char *hash2)
+{
+       struct tree_desc one;
+       struct tree_desc two;
+       void *one_buf, *two_buf;
+       int score = 0;
+       enum object_type type;
+       unsigned long size;
+
+       one_buf = read_sha1_file(hash1, &type, &size);
+       if (!one_buf)
+               die("unable to read tree (%s)", sha1_to_hex(hash1));
+       if (type != OBJ_TREE)
+               die("%s is not a tree", sha1_to_hex(hash1));
+       init_tree_desc(&one, one_buf, size);
+       two_buf = read_sha1_file(hash2, &type, &size);
+       if (!two_buf)
+               die("unable to read tree (%s)", sha1_to_hex(hash2));
+       if (type != OBJ_TREE)
+               die("%s is not a tree", sha1_to_hex(hash2));
+       init_tree_desc(&two, two_buf, size);
+       while (one.size | two.size) {
+               const unsigned char *elem1 = elem1;
+               const unsigned char *elem2 = elem2;
+               const char *path1 = path1;
+               const char *path2 = path2;
+               unsigned mode1 = mode1;
+               unsigned mode2 = mode2;
+               int cmp;
+
+               if (one.size)
+                       elem1 = tree_entry_extract(&one, &path1, &mode1);
+               if (two.size)
+                       elem2 = tree_entry_extract(&two, &path2, &mode2);
+
+               if (!one.size) {
+                       /* two has more entries */
+                       score += score_missing(mode2, path2);
+                       update_tree_entry(&two);
+                       continue;
+               }
+               if (!two.size) {
+                       /* two lacks this entry */
+                       score += score_missing(mode1, path1);
+                       update_tree_entry(&one);
+                       continue;
+               }
+               cmp = base_name_compare(path1, strlen(path1), mode1,
+                                       path2, strlen(path2), mode2);
+               if (cmp < 0) {
+                       /* path1 does not appear in two */
+                       score += score_missing(mode1, path1);
+                       update_tree_entry(&one);
+                       continue;
+               }
+               else if (cmp > 0) {
+                       /* path2 does not appear in one */
+                       score += score_missing(mode2, path2);
+                       update_tree_entry(&two);
+                       continue;
+               }
+               else if (hashcmp(elem1, elem2))
+                       /* they are different */
+                       score += score_differs(mode1, mode2, path1);
+               else
+                       /* same subtree or blob */
+                       score += score_matches(mode1, mode2, path1);
+               update_tree_entry(&one);
+               update_tree_entry(&two);
+       }
+       free(one_buf);
+       free(two_buf);
+       return score;
+}
+
+/*
+ * Match one itself and its subtrees with two and pick the best match.
+ */
+static void match_trees(const unsigned char *hash1,
+                       const unsigned char *hash2,
+                       int *best_score,
+                       char **best_match,
+                       char *base,
+                       int recurse_limit)
+{
+       struct tree_desc one;
+       void *one_buf;
+       enum object_type type;
+       unsigned long size;
+
+       one_buf = read_sha1_file(hash1, &type, &size);
+       if (!one_buf)
+               die("unable to read tree (%s)", sha1_to_hex(hash1));
+       if (type != OBJ_TREE)
+               die("%s is not a tree", sha1_to_hex(hash1));
+       init_tree_desc(&one, one_buf, size);
+
+       while (one.size) {
+               const char *path;
+               const unsigned char *elem;
+               unsigned mode;
+               int score;
+
+               elem = tree_entry_extract(&one, &path, &mode);
+               if (!S_ISDIR(mode))
+                       goto next;
+               score = score_trees(elem, hash2);
+               if (*best_score < score) {
+                       char *newpath;
+                       newpath = xmalloc(strlen(base) + strlen(path) + 1);
+                       sprintf(newpath, "%s%s", base, path);
+                       free(*best_match);
+                       *best_match = newpath;
+                       *best_score = score;
+               }
+               if (recurse_limit) {
+                       char *newbase;
+                       newbase = xmalloc(strlen(base) + strlen(path) + 2);
+                       sprintf(newbase, "%s%s/", base, path);
+                       match_trees(elem, hash2, best_score, best_match,
+                                   newbase, recurse_limit - 1);
+                       free(newbase);
+               }
+
+       next:
+               update_tree_entry(&one);
+       }
+       free(one_buf);
+}
+
+/*
+ * A tree "hash1" has a subdirectory at "prefix".  Come up with a
+ * tree object by replacing it with another tree "hash2".
+ */
+static int splice_tree(const unsigned char *hash1,
+                      char *prefix,
+                      const unsigned char *hash2,
+                      unsigned char *result)
+{
+       char *subpath;
+       int toplen;
+       char *buf;
+       unsigned long sz;
+       struct tree_desc desc;
+       unsigned char *rewrite_here;
+       const unsigned char *rewrite_with;
+       unsigned char subtree[20];
+       enum object_type type;
+       int status;
+
+       subpath = strchr(prefix, '/');
+       if (!subpath)
+               toplen = strlen(prefix);
+       else {
+               toplen = subpath - prefix;
+               subpath++;
+       }
+
+       buf = read_sha1_file(hash1, &type, &sz);
+       if (!buf)
+               die("cannot read tree %s", sha1_to_hex(hash1));
+       init_tree_desc(&desc, buf, sz);
+
+       rewrite_here = NULL;
+       while (desc.size) {
+               const char *name;
+               unsigned mode;
+               const unsigned char *sha1;
+
+               sha1 = tree_entry_extract(&desc, &name, &mode);
+               if (strlen(name) == toplen &&
+                   !memcmp(name, prefix, toplen)) {
+                       if (!S_ISDIR(mode))
+                               die("entry %s in tree %s is not a tree",
+                                   name, sha1_to_hex(hash1));
+                       rewrite_here = (unsigned char *) sha1;
+                       break;
+               }
+               update_tree_entry(&desc);
+       }
+       if (!rewrite_here)
+               die("entry %.*s not found in tree %s",
+                   toplen, prefix, sha1_to_hex(hash1));
+       if (subpath) {
+               status = splice_tree(rewrite_here, subpath, hash2, subtree);
+               if (status)
+                       return status;
+               rewrite_with = subtree;
+       }
+       else
+               rewrite_with = hash2;
+       hashcpy(rewrite_here, rewrite_with);
+       status = write_sha1_file(buf, sz, tree_type, result);
+       free(buf);
+       return status;
+}
+
+/*
+ * We are trying to come up with a merge between one and two that
+ * results in a tree shape similar to one.  The tree two might
+ * correspond to a subtree of one, in which case it needs to be
+ * shifted down by prefixing otherwise empty directories.  On the
+ * other hand, it could cover tree one and we might need to pick a
+ * subtree of it.
+ */
+void shift_tree(const unsigned char *hash1,
+               const unsigned char *hash2,
+               unsigned char *shifted,
+               int depth_limit)
+{
+       char *add_prefix;
+       char *del_prefix;
+       int add_score, del_score;
+
+       add_score = del_score = score_trees(hash1, hash2);
+       add_prefix = xcalloc(1, 1);
+       del_prefix = xcalloc(1, 1);
+
+       /*
+        * See if one's subtree resembles two; if so we need to prefix
+        * two with a few fake trees to match the prefix.
+        */
+       match_trees(hash1, hash2, &add_score, &add_prefix, "", depth_limit);
+
+       /*
+        * See if two's subtree resembles one; if so we need to
+        * pick only subtree of two.
+        */
+       match_trees(hash2, hash1, &del_score, &del_prefix, "", depth_limit);
+
+       /* Assume we do not have to do any shifting */
+       hashcpy(shifted, hash2);
+
+       if (add_score < del_score) {
+               /* We need to pick a subtree of two */
+               unsigned mode;
+
+               if (!*del_prefix)
+                       return;
+
+               if (get_tree_entry(hash2, del_prefix, shifted, &mode))
+                       die("cannot find path %s in tree %s",
+                           del_prefix, sha1_to_hex(hash2));
+               return;
+       }
+
+       if (!*add_prefix)
+               return;
+
+       splice_tree(hash1, add_prefix, hash2, shifted);
+}
+
index e1aebd77274bb75e9fda95b2d64f69a9dda4276a..8f72b2c079a68ac5cb9d1c90bb7b72fa779efb5f 100644 (file)
 #include "unpack-trees.h"
 #include "path-list.h"
 #include "xdiff-interface.h"
+#include "interpolate.h"
+#include "attr.h"
+
+static int subtree_merge;
+
+static struct tree *shift_tree_object(struct tree *one, struct tree *two)
+{
+       unsigned char shifted[20];
+
+       /*
+        * NEEDSWORK: this limits the recursion depth to hardcoded
+        * value '2' to avoid excessive overhead.
+        */
+       shift_tree(one->object.sha1, two->object.sha1, shifted, 2);
+       if (!hashcmp(two->object.sha1, shifted))
+               return two;
+       return lookup_tree(shifted);
+}
 
 /*
  * A virtual commit has
@@ -79,11 +97,6 @@ static struct path_list current_directory_set = {NULL, 0, 0, 1};
 static int call_depth = 0;
 static int verbosity = 2;
 static int buffer_output = 1;
-static int do_progress = 1;
-static unsigned last_percent;
-static unsigned merged_cnt;
-static unsigned total_cnt;
-static volatile sig_atomic_t progress_update;
 static struct output_buffer *output_list, *output_end;
 
 static int show (int v)
@@ -158,39 +171,6 @@ static void output_commit_title(struct commit *commit)
        }
 }
 
-static void progress_interval(int signum)
-{
-       progress_update = 1;
-}
-
-static void setup_progress_signal(void)
-{
-       struct sigaction sa;
-       struct itimerval v;
-
-       memset(&sa, 0, sizeof(sa));
-       sa.sa_handler = progress_interval;
-       sigemptyset(&sa.sa_mask);
-       sa.sa_flags = SA_RESTART;
-       sigaction(SIGALRM, &sa, NULL);
-
-       v.it_interval.tv_sec = 1;
-       v.it_interval.tv_usec = 0;
-       v.it_value = v.it_interval;
-       setitimer(ITIMER_REAL, &v, NULL);
-}
-
-static void display_progress()
-{
-       unsigned percent = total_cnt ? merged_cnt * 100 / total_cnt : 0;
-       if (progress_update || percent != last_percent) {
-               fprintf(stderr, "%4u%% (%u/%u) done\r",
-                       percent, merged_cnt, total_cnt);
-               progress_update = 0;
-               last_percent = percent;
-       }
-}
-
 static struct cache_entry *make_cache_entry(unsigned int mode,
                const unsigned char *sha1, const char *path, int stage, int refresh)
 {
@@ -221,7 +201,7 @@ static int add_cacheinfo(unsigned int mode, const unsigned char *sha1,
        struct cache_entry *ce;
        ce = make_cache_entry(mode, sha1 ? sha1 : null_sha1, path, stage, refresh);
        if (!ce)
-               return error("cache_addinfo failed: %s", strerror(cache_errno));
+               return error("addinfo_cache failed for path '%s'", path);
        return add_cache_entry(ce, options);
 }
 
@@ -361,14 +341,11 @@ static struct path_list *get_unmerged(void)
        int i;
 
        unmerged->strdup_paths = 1;
-       total_cnt += active_nr;
 
-       for (i = 0; i < active_nr; i++, merged_cnt++) {
+       for (i = 0; i < active_nr; i++) {
                struct path_list_item *item;
                struct stage_data *e;
                struct cache_entry *ce = active_cache[i];
-               if (do_progress)
-                       display_progress();
                if (!ce_stage(ce))
                        continue;
 
@@ -558,6 +535,31 @@ static void flush_buffer(int fd, const char *buf, unsigned long size)
        }
 }
 
+static int make_room_for_path(const char *path)
+{
+       int status;
+       const char *msg = "failed to create path '%s'%s";
+
+       status = mkdir_p(path, 0777);
+       if (status) {
+               if (status == -3) {
+                       /* something else exists */
+                       error(msg, path, ": perhaps a D/F conflict?");
+                       return -1;
+               }
+               die(msg, path, "");
+       }
+
+       /* Successful unlink is good.. */
+       if (!unlink(path))
+               return 0;
+       /* .. and so is no existing file */
+       if (errno == ENOENT)
+               return 0;
+       /* .. but not some other error (who really cares what?) */
+       return error(msg, path, ": perhaps a D/F conflict?");
+}
+
 static void update_file_flags(const unsigned char *sha,
                              unsigned mode,
                              const char *path,
@@ -578,11 +580,12 @@ static void update_file_flags(const unsigned char *sha,
                if (type != OBJ_BLOB)
                        die("blob expected for %s '%s'", sha1_to_hex(sha), path);
 
+               if (make_room_for_path(path) < 0) {
+                       update_wd = 0;
+                       goto update_index;
+               }
                if (S_ISREG(mode) || (!has_symlinks && S_ISLNK(mode))) {
                        int fd;
-                       if (mkdir_p(path, 0777))
-                               die("failed to create path %s: %s", path, strerror(errno));
-                       unlink(path);
                        if (mode & 0100)
                                mode = 0777;
                        else
@@ -604,6 +607,7 @@ static void update_file_flags(const unsigned char *sha,
                        die("do not know what to do with %06o %s '%s'",
                            mode, sha1_to_hex(sha), path);
        }
+ update_index:
        if (update_cache)
                add_cacheinfo(mode, sha, path, 0, update_wd, ADD_CACHE_OK_TO_ADD);
 }
@@ -643,6 +647,384 @@ static void fill_mm(const unsigned char *sha1, mmfile_t *mm)
        mm->size = size;
 }
 
+/*
+ * Customizable low-level merge drivers support.
+ */
+
+struct ll_merge_driver;
+typedef int (*ll_merge_fn)(const struct ll_merge_driver *,
+                          const char *path,
+                          mmfile_t *orig,
+                          mmfile_t *src1, const char *name1,
+                          mmfile_t *src2, const char *name2,
+                          mmbuffer_t *result);
+
+struct ll_merge_driver {
+       const char *name;
+       const char *description;
+       ll_merge_fn fn;
+       const char *recursive;
+       struct ll_merge_driver *next;
+       char *cmdline;
+};
+
+/*
+ * Built-in low-levels
+ */
+static int ll_xdl_merge(const struct ll_merge_driver *drv_unused,
+                       const char *path_unused,
+                       mmfile_t *orig,
+                       mmfile_t *src1, const char *name1,
+                       mmfile_t *src2, const char *name2,
+                       mmbuffer_t *result)
+{
+       xpparam_t xpp;
+
+       memset(&xpp, 0, sizeof(xpp));
+       return xdl_merge(orig,
+                        src1, name1,
+                        src2, name2,
+                        &xpp, XDL_MERGE_ZEALOUS,
+                        result);
+}
+
+static int ll_union_merge(const struct ll_merge_driver *drv_unused,
+                         const char *path_unused,
+                         mmfile_t *orig,
+                         mmfile_t *src1, const char *name1,
+                         mmfile_t *src2, const char *name2,
+                         mmbuffer_t *result)
+{
+       char *src, *dst;
+       long size;
+       const int marker_size = 7;
+
+       int status = ll_xdl_merge(drv_unused, path_unused,
+                                 orig, src1, NULL, src2, NULL, result);
+       if (status <= 0)
+               return status;
+       size = result->size;
+       src = dst = result->ptr;
+       while (size) {
+               char ch;
+               if ((marker_size < size) &&
+                   (*src == '<' || *src == '=' || *src == '>')) {
+                       int i;
+                       ch = *src;
+                       for (i = 0; i < marker_size; i++)
+                               if (src[i] != ch)
+                                       goto not_a_marker;
+                       if (src[marker_size] != '\n')
+                               goto not_a_marker;
+                       src += marker_size + 1;
+                       size -= marker_size + 1;
+                       continue;
+               }
+       not_a_marker:
+               do {
+                       ch = *src++;
+                       *dst++ = ch;
+                       size--;
+               } while (ch != '\n' && size);
+       }
+       result->size = dst - result->ptr;
+       return 0;
+}
+
+static int ll_binary_merge(const struct ll_merge_driver *drv_unused,
+                          const char *path_unused,
+                          mmfile_t *orig,
+                          mmfile_t *src1, const char *name1,
+                          mmfile_t *src2, const char *name2,
+                          mmbuffer_t *result)
+{
+       /*
+        * The tentative merge result is "ours" for the final round,
+        * or common ancestor for an internal merge.  Still return
+        * "conflicted merge" status.
+        */
+       mmfile_t *stolen = index_only ? orig : src1;
+
+       result->ptr = stolen->ptr;
+       result->size = stolen->size;
+       stolen->ptr = NULL;
+       return 1;
+}
+
+#define LL_BINARY_MERGE 0
+#define LL_TEXT_MERGE 1
+#define LL_UNION_MERGE 2
+static struct ll_merge_driver ll_merge_drv[] = {
+       { "binary", "built-in binary merge", ll_binary_merge },
+       { "text", "built-in 3-way text merge", ll_xdl_merge },
+       { "union", "built-in union merge", ll_union_merge },
+};
+
+static void create_temp(mmfile_t *src, char *path)
+{
+       int fd;
+
+       strcpy(path, ".merge_file_XXXXXX");
+       fd = mkstemp(path);
+       if (fd < 0)
+               die("unable to create temp-file");
+       if (write_in_full(fd, src->ptr, src->size) != src->size)
+               die("unable to write temp-file");
+       close(fd);
+}
+
+/*
+ * User defined low-level merge driver support.
+ */
+static int ll_ext_merge(const struct ll_merge_driver *fn,
+                       const char *path,
+                       mmfile_t *orig,
+                       mmfile_t *src1, const char *name1,
+                       mmfile_t *src2, const char *name2,
+                       mmbuffer_t *result)
+{
+       char temp[3][50];
+       char cmdbuf[2048];
+       struct interp table[] = {
+               { "%O" },
+               { "%A" },
+               { "%B" },
+       };
+       struct child_process child;
+       const char *args[20];
+       int status, fd, i;
+       struct stat st;
+
+       if (fn->cmdline == NULL)
+               die("custom merge driver %s lacks command line.", fn->name);
+
+       result->ptr = NULL;
+       result->size = 0;
+       create_temp(orig, temp[0]);
+       create_temp(src1, temp[1]);
+       create_temp(src2, temp[2]);
+
+       interp_set_entry(table, 0, temp[0]);
+       interp_set_entry(table, 1, temp[1]);
+       interp_set_entry(table, 2, temp[2]);
+
+       output(1, "merging %s using %s", path,
+              fn->description ? fn->description : fn->name);
+
+       interpolate(cmdbuf, sizeof(cmdbuf), fn->cmdline, table, 3);
+
+       memset(&child, 0, sizeof(child));
+       child.argv = args;
+       args[0] = "sh";
+       args[1] = "-c";
+       args[2] = cmdbuf;
+       args[3] = NULL;
+
+       status = run_command(&child);
+       if (status < -ERR_RUN_COMMAND_FORK)
+               ; /* failure in run-command */
+       else
+               status = -status;
+       fd = open(temp[1], O_RDONLY);
+       if (fd < 0)
+               goto bad;
+       if (fstat(fd, &st))
+               goto close_bad;
+       result->size = st.st_size;
+       result->ptr = xmalloc(result->size + 1);
+       if (read_in_full(fd, result->ptr, result->size) != result->size) {
+               free(result->ptr);
+               result->ptr = NULL;
+               result->size = 0;
+       }
+ close_bad:
+       close(fd);
+ bad:
+       for (i = 0; i < 3; i++)
+               unlink(temp[i]);
+       return status;
+}
+
+/*
+ * merge.default and merge.driver configuration items
+ */
+static struct ll_merge_driver *ll_user_merge, **ll_user_merge_tail;
+static const char *default_ll_merge;
+
+static int read_merge_config(const char *var, const char *value)
+{
+       struct ll_merge_driver *fn;
+       const char *ep, *name;
+       int namelen;
+
+       if (!strcmp(var, "merge.default")) {
+               if (value)
+                       default_ll_merge = strdup(value);
+               return 0;
+       }
+
+       /*
+        * We are not interested in anything but "merge.<name>.variable";
+        * especially, we do not want to look at variables such as
+        * "merge.summary", "merge.tool", and "merge.verbosity".
+        */
+       if (prefixcmp(var, "merge.") || (ep = strrchr(var, '.')) == var + 5)
+               return 0;
+
+       /*
+        * Find existing one as we might be processing merge.<name>.var2
+        * after seeing merge.<name>.var1.
+        */
+       name = var + 6;
+       namelen = ep - name;
+       for (fn = ll_user_merge; fn; fn = fn->next)
+               if (!strncmp(fn->name, name, namelen) && !fn->name[namelen])
+                       break;
+       if (!fn) {
+               char *namebuf;
+               fn = xcalloc(1, sizeof(struct ll_merge_driver));
+               namebuf = xmalloc(namelen + 1);
+               memcpy(namebuf, name, namelen);
+               namebuf[namelen] = 0;
+               fn->name = namebuf;
+               fn->fn = ll_ext_merge;
+               fn->next = NULL;
+               *ll_user_merge_tail = fn;
+               ll_user_merge_tail = &(fn->next);
+       }
+
+       ep++;
+
+       if (!strcmp("name", ep)) {
+               if (!value)
+                       return error("%s: lacks value", var);
+               fn->description = strdup(value);
+               return 0;
+       }
+
+       if (!strcmp("driver", ep)) {
+               if (!value)
+                       return error("%s: lacks value", var);
+               /*
+                * merge.<name>.driver specifies the command line:
+                *
+                *      command-line
+                *
+                * The command-line will be interpolated with the following
+                * tokens and is given to the shell:
+                *
+                *    %O - temporary file name for the merge base.
+                *    %A - temporary file name for our version.
+                *    %B - temporary file name for the other branches' version.
+                *
+                * The external merge driver should write the results in the
+                * file named by %A, and signal that it has done with zero exit
+                * status.
+                */
+               fn->cmdline = strdup(value);
+               return 0;
+       }
+
+       if (!strcmp("recursive", ep)) {
+               if (!value)
+                       return error("%s: lacks value", var);
+               fn->recursive = strdup(value);
+               return 0;
+       }
+
+       return 0;
+}
+
+static void initialize_ll_merge(void)
+{
+       if (ll_user_merge_tail)
+               return;
+       ll_user_merge_tail = &ll_user_merge;
+       git_config(read_merge_config);
+}
+
+static const struct ll_merge_driver *find_ll_merge_driver(const char *merge_attr)
+{
+       struct ll_merge_driver *fn;
+       const char *name;
+       int i;
+
+       initialize_ll_merge();
+
+       if (ATTR_TRUE(merge_attr))
+               return &ll_merge_drv[LL_TEXT_MERGE];
+       else if (ATTR_FALSE(merge_attr))
+               return &ll_merge_drv[LL_BINARY_MERGE];
+       else if (ATTR_UNSET(merge_attr)) {
+               if (!default_ll_merge)
+                       return &ll_merge_drv[LL_TEXT_MERGE];
+               else
+                       name = default_ll_merge;
+       }
+       else
+               name = merge_attr;
+
+       for (fn = ll_user_merge; fn; fn = fn->next)
+               if (!strcmp(fn->name, name))
+                       return fn;
+
+       for (i = 0; i < ARRAY_SIZE(ll_merge_drv); i++)
+               if (!strcmp(ll_merge_drv[i].name, name))
+                       return &ll_merge_drv[i];
+
+       /* default to the 3-way */
+       return &ll_merge_drv[LL_TEXT_MERGE];
+}
+
+static const char *git_path_check_merge(const char *path)
+{
+       static struct git_attr_check attr_merge_check;
+
+       if (!attr_merge_check.attr)
+               attr_merge_check.attr = git_attr("merge", 5);
+
+       if (git_checkattr(path, 1, &attr_merge_check))
+               return NULL;
+       return attr_merge_check.value;
+}
+
+static int ll_merge(mmbuffer_t *result_buf,
+                   struct diff_filespec *o,
+                   struct diff_filespec *a,
+                   struct diff_filespec *b,
+                   const char *branch1,
+                   const char *branch2)
+{
+       mmfile_t orig, src1, src2;
+       char *name1, *name2;
+       int merge_status;
+       const char *ll_driver_name;
+       const struct ll_merge_driver *driver;
+
+       name1 = xstrdup(mkpath("%s:%s", branch1, a->path));
+       name2 = xstrdup(mkpath("%s:%s", branch2, b->path));
+
+       fill_mm(o->sha1, &orig);
+       fill_mm(a->sha1, &src1);
+       fill_mm(b->sha1, &src2);
+
+       ll_driver_name = git_path_check_merge(a->path);
+       driver = find_ll_merge_driver(ll_driver_name);
+
+       if (index_only && driver->recursive)
+               driver = find_ll_merge_driver(driver->recursive);
+       merge_status = driver->fn(driver, a->path,
+                                 &orig, &src1, name1, &src2, name2,
+                                 result_buf);
+
+       free(name1);
+       free(name2);
+       free(orig.ptr);
+       free(src1.ptr);
+       free(src2.ptr);
+       return merge_status;
+}
+
 static struct merge_file_info merge_file(struct diff_filespec *o,
                struct diff_filespec *a, struct diff_filespec *b,
                const char *branch1, const char *branch2)
@@ -671,30 +1053,11 @@ static struct merge_file_info merge_file(struct diff_filespec *o,
                else if (sha_eq(b->sha1, o->sha1))
                        hashcpy(result.sha, a->sha1);
                else if (S_ISREG(a->mode)) {
-                       mmfile_t orig, src1, src2;
                        mmbuffer_t result_buf;
-                       xpparam_t xpp;
-                       char *name1, *name2;
                        int merge_status;
 
-                       name1 = xstrdup(mkpath("%s:%s", branch1, a->path));
-                       name2 = xstrdup(mkpath("%s:%s", branch2, b->path));
-
-                       fill_mm(o->sha1, &orig);
-                       fill_mm(a->sha1, &src1);
-                       fill_mm(b->sha1, &src2);
-
-                       memset(&xpp, 0, sizeof(xpp));
-                       merge_status = xdl_merge(&orig,
-                                                &src1, name1,
-                                                &src2, name2,
-                                                &xpp, XDL_MERGE_ZEALOUS,
-                                                &result_buf);
-                       free(name1);
-                       free(name2);
-                       free(orig.ptr);
-                       free(src1.ptr);
-                       free(src2.ptr);
+                       merge_status = ll_merge(&result_buf, o, a, b,
+                                               branch1, branch2);
 
                        if ((merge_status < 0) || !result_buf.ptr)
                                die("Failed to execute internal merge");
@@ -979,20 +1342,31 @@ static int process_renames(struct path_list *a_renames,
                                mfi = merge_file(o, a, b,
                                                a_branch, b_branch);
 
-                               if (mfi.merge || !mfi.clean)
-                                       output(1, "Renamed %s => %s", ren1_src, ren1_dst);
-                               if (mfi.merge)
-                                       output(2, "Auto-merged %s", ren1_dst);
-                               if (!mfi.clean) {
-                                       output(1, "CONFLICT (rename/modify): Merge conflict in %s",
-                                              ren1_dst);
-                                       clean_merge = 0;
-
-                                       if (!index_only)
-                                               update_stages(ren1_dst,
-                                                               o, a, b, 1);
+                               if (mfi.clean &&
+                                   sha_eq(mfi.sha, ren1->pair->two->sha1) &&
+                                   mfi.mode == ren1->pair->two->mode)
+                                       /*
+                                        * This messaged is part of
+                                        * t6022 test. If you change
+                                        * it update the test too.
+                                        */
+                                       output(3, "Skipped %s (merged same as existing)", ren1_dst);
+                               else {
+                                       if (mfi.merge || !mfi.clean)
+                                               output(1, "Renamed %s => %s", ren1_src, ren1_dst);
+                                       if (mfi.merge)
+                                               output(2, "Auto-merged %s", ren1_dst);
+                                       if (!mfi.clean) {
+                                               output(1, "CONFLICT (rename/modify): Merge conflict in %s",
+                                                      ren1_dst);
+                                               clean_merge = 0;
+
+                                               if (!index_only)
+                                                       update_stages(ren1_dst,
+                                                                     o, a, b, 1);
+                                       }
+                                       update_file(mfi.clean, mfi.sha, mfi.mode, ren1_dst);
                                }
-                               update_file(mfi.clean, mfi.sha, mfi.mode, ren1_dst);
                        }
                }
        }
@@ -1002,9 +1376,9 @@ static int process_renames(struct path_list *a_renames,
        return clean_merge;
 }
 
-static unsigned char *has_sha(const unsigned char *sha)
+static unsigned char *stage_sha(const unsigned char *sha, unsigned mode)
 {
-       return is_null_sha1(sha) ? NULL: (unsigned char *)sha;
+       return (is_null_sha1(sha) || mode == 0) ? NULL: (unsigned char *)sha;
 }
 
 /* Per entry merge function */
@@ -1017,12 +1391,12 @@ static int process_entry(const char *path, struct stage_data *entry,
        print_index_entry("\tpath: ", entry);
        */
        int clean_merge = 1;
-       unsigned char *o_sha = has_sha(entry->stages[1].sha);
-       unsigned char *a_sha = has_sha(entry->stages[2].sha);
-       unsigned char *b_sha = has_sha(entry->stages[3].sha);
        unsigned o_mode = entry->stages[1].mode;
        unsigned a_mode = entry->stages[2].mode;
        unsigned b_mode = entry->stages[3].mode;
+       unsigned char *o_sha = stage_sha(entry->stages[1].sha, o_mode);
+       unsigned char *a_sha = stage_sha(entry->stages[2].sha, a_mode);
+       unsigned char *b_sha = stage_sha(entry->stages[3].sha, b_mode);
 
        if (o_sha && (!a_sha || !b_sha)) {
                /* Case A: Deleted in one */
@@ -1123,6 +1497,12 @@ static int process_entry(const char *path, struct stage_data *entry,
                                update_file_flags(mfi.sha, mfi.mode, path,
                                              0 /* update_cache */, 1 /* update_working_directory */);
                }
+       } else if (!o_sha && !a_sha && !b_sha) {
+               /*
+                * this entry was deleted altogether. a_mode == 0 means
+                * we had that path and want to actively remove it.
+                */
+               remove_file(1, path, !a_mode);
        } else
                die("Fatal merge failure, shouldn't happen.");
 
@@ -1137,6 +1517,12 @@ static int merge_trees(struct tree *head,
                       struct tree **result)
 {
        int code, clean;
+
+       if (subtree_merge) {
+               merge = shift_tree_object(head, merge);
+               common = shift_tree_object(head, common);
+       }
+
        if (sha_eq(common->object.sha1, merge->object.sha1)) {
                output(0, "Already uptodate!");
                *result = head;
@@ -1163,15 +1549,12 @@ static int merge_trees(struct tree *head,
                re_merge = get_renames(merge, common, head, merge, entries);
                clean = process_renames(re_head, re_merge,
                                branch1, branch2);
-               total_cnt += entries->nr;
-               for (i = 0; i < entries->nr; i++, merged_cnt++) {
+               for (i = 0; i < entries->nr; i++) {
                        const char *path = entries->items[i].path;
                        struct stage_data *e = entries->items[i].util;
                        if (!e->processed
                                && !process_entry(path, e, branch1, branch2))
                                clean = 0;
-                       if (do_progress)
-                               display_progress();
                }
 
                path_list_clear(re_merge, 0);
@@ -1279,15 +1662,6 @@ static int merge(struct commit *h1,
                commit_list_insert(h1, &(*result)->parents);
                commit_list_insert(h2, &(*result)->parents->next);
        }
-       if (!call_depth && do_progress) {
-               /* Make sure we end at 100% */
-               if (!total_cnt)
-                       total_cnt = 1;
-               merged_cnt = total_cnt;
-               progress_update = 1;
-               display_progress();
-               fputc('\n', stderr);
-       }
        flush_output();
        return clean;
 }
@@ -1342,6 +1716,13 @@ int main(int argc, char *argv[])
        struct lock_file *lock = xcalloc(1, sizeof(struct lock_file));
        int index_fd;
 
+       if (argv[0]) {
+               int namelen = strlen(argv[0]);
+               if (8 < namelen &&
+                   !strcmp(argv[0] + namelen - 8, "-subtree"))
+                       subtree_merge = 1;
+       }
+
        git_config(merge_config);
        if (getenv("GIT_MERGE_VERBOSITY"))
                verbosity = strtol(getenv("GIT_MERGE_VERBOSITY"), NULL, 10);
@@ -1357,12 +1738,8 @@ int main(int argc, char *argv[])
        }
        if (argc - i != 3) /* "--" "<head>" "<remote>" */
                die("Not handling anything other than two heads merge.");
-       if (verbosity >= 5) {
+       if (verbosity >= 5)
                buffer_output = 0;
-               do_progress = 0;
-       }
-       else
-               do_progress = isatty(1);
 
        branch1 = argv[++i];
        branch2 = argv[++i];
@@ -1373,12 +1750,10 @@ int main(int argc, char *argv[])
        branch1 = better_branch_name(branch1);
        branch2 = better_branch_name(branch2);
 
-       if (do_progress)
-               setup_progress_signal();
        if (show(3))
                printf("Merging %s with %s\n", branch1, branch2);
 
-       index_fd = hold_lock_file_for_update(lock, get_index_file(), 1);
+       index_fd = hold_locked_index(lock, 1);
 
        for (i = 0; i < bases_count; i++) {
                struct commit *ancestor = get_ref(bases[i]);
@@ -1388,7 +1763,7 @@ int main(int argc, char *argv[])
 
        if (active_cache_changed &&
            (write_cache(index_fd, active_cache, active_nr) ||
-            close(index_fd) || commit_lock_file(lock)))
+            close(index_fd) || commit_locked_index(lock)))
                        die ("unable to write %s", get_index_file());
 
        return clean ? 0: 1;
index 98ea10005a851c3879c6e65929e3e34068dc9e8b..022e8d841c7f5498b6f008647ed37c430826b443 100644 (file)
@@ -1,75 +1,20 @@
 #include "cache.h"
 #include "object.h"
+#include "decorate.h"
 
 int track_object_refs = 0;
 
-static unsigned int refs_hash_size, nr_object_refs;
-static struct object_refs **refs_hash;
+static struct decoration ref_decorate;
 
-static unsigned int hash_obj(struct object *obj, unsigned int n)
+struct object_refs *lookup_object_refs(struct object *base)
 {
-       unsigned int hash = *(unsigned int *)obj->sha1;
-       return hash % n;
+       return lookup_decoration(&ref_decorate, base);
 }
 
-static void insert_ref_hash(struct object_refs *ref, struct object_refs **hash, unsigned int size)
+static void add_object_refs(struct object *obj, struct object_refs *refs)
 {
-       int j = hash_obj(ref->base, size);
-
-       while (hash[j]) {
-               j++;
-               if (j >= size)
-                       j = 0;
-       }
-       hash[j] = ref;
-}
-
-static void grow_refs_hash(void)
-{
-       int i;
-       int new_hash_size = (refs_hash_size + 1000) * 3 / 2;
-       struct object_refs **new_hash;
-
-       new_hash = xcalloc(new_hash_size, sizeof(struct object_refs *));
-       for (i = 0; i < refs_hash_size; i++) {
-               struct object_refs *ref = refs_hash[i];
-               if (!ref)
-                       continue;
-               insert_ref_hash(ref, new_hash, new_hash_size);
-       }
-       free(refs_hash);
-       refs_hash = new_hash;
-       refs_hash_size = new_hash_size;
-}
-
-static void add_object_refs(struct object *obj, struct object_refs *ref)
-{
-       int nr = nr_object_refs + 1;
-
-       if (nr > refs_hash_size * 2 / 3)
-               grow_refs_hash();
-       ref->base = obj;
-       insert_ref_hash(ref, refs_hash, refs_hash_size);
-       nr_object_refs = nr;
-}
-
-struct object_refs *lookup_object_refs(struct object *obj)
-{
-       struct object_refs *ref;
-       int j;
-
-       /* nothing to lookup */
-       if (!refs_hash_size)
-               return NULL;
-       j = hash_obj(obj, refs_hash_size);
-       while ((ref = refs_hash[j]) != NULL) {
-               if (ref->base == obj)
-                       break;
-               j++;
-               if (j >= refs_hash_size)
-                       j = 0;
-       }
-       return ref;
+       if (add_decoration(&ref_decorate, obj, refs))
+               die("object %s tried to add refs twice!", sha1_to_hex(obj->sha1));
 }
 
 struct object_refs *alloc_object_refs(unsigned count)
index 78a44a6ef4e4823487861c9173f3db4a3fb76e3a..37d1363359eb54070ed4dc52b9e96d4ad9457db3 100644 (file)
--- a/object.c
+++ b/object.c
@@ -105,11 +105,13 @@ static void grow_object_hash(void)
        obj_hash_size = new_hash_size;
 }
 
-void created_object(const unsigned char *sha1, struct object *obj)
+void *create_object(const unsigned char *sha1, int type, void *o)
 {
+       struct object *obj = o;
+
        obj->parsed = 0;
        obj->used = 0;
-       obj->type = OBJ_NONE;
+       obj->type = type;
        obj->flags = 0;
        hashcpy(obj->sha1, sha1);
 
@@ -118,25 +120,14 @@ void created_object(const unsigned char *sha1, struct object *obj)
 
        insert_obj_hash(obj, obj_hash, obj_hash_size);
        nr_objs++;
+       return obj;
 }
 
-union any_object {
-       struct object object;
-       struct commit commit;
-       struct tree tree;
-       struct blob blob;
-       struct tag tag;
-};
-
 struct object *lookup_unknown_object(const unsigned char *sha1)
 {
        struct object *obj = lookup_object(sha1);
-       if (!obj) {
-               union any_object *ret = xcalloc(1, sizeof(*ret));
-               created_object(sha1, &ret->object);
-               ret->object.type = OBJ_NONE;
-               return &ret->object;
-       }
+       if (!obj)
+               obj = create_object(sha1, OBJ_NONE, alloc_object_node());
        return obj;
 }
 
@@ -239,6 +230,11 @@ int object_list_contains(struct object_list *list, struct object *obj)
 }
 
 void add_object_array(struct object *obj, const char *name, struct object_array *array)
+{
+       add_object_array_with_mode(obj, name, array, S_IFINVALID);
+}
+
+void add_object_array_with_mode(struct object *obj, const char *name, struct object_array *array, unsigned mode)
 {
        unsigned nr = array->nr;
        unsigned alloc = array->alloc;
@@ -252,5 +248,6 @@ void add_object_array(struct object *obj, const char *name, struct object_array
        }
        objects[nr].item = obj;
        objects[nr].name = name;
+       objects[nr].mode = mode;
        array->nr = ++nr;
 }
index bdbf0facd47015b61a6fbf25eb358b6e256c86b6..94f19eed86ceebcc85770a379faf7b1270d1fad4 100644 (file)
--- a/object.h
+++ b/object.h
@@ -8,7 +8,6 @@ struct object_list {
 
 struct object_refs {
        unsigned count;
-       struct object *base;
        struct object *ref[FLEX_ARRAY]; /* more */
 };
 
@@ -18,6 +17,7 @@ struct object_array {
        struct object_array_entry {
                struct object *item;
                const char *name;
+               unsigned mode;
        } *objects;
 };
 
@@ -47,7 +47,7 @@ extern struct object_refs *lookup_object_refs(struct object *);
 /** Internal only **/
 struct object *lookup_object(const unsigned char *sha1);
 
-void created_object(const unsigned char *sha1, struct object *obj);
+extern void *create_object(const unsigned char *sha1, int type, void *obj);
 
 /** Returns the object, having parsed it to find out what it is. **/
 struct object *parse_object(const unsigned char *sha1);
@@ -78,5 +78,6 @@ int object_list_contains(struct object_list *list, struct object *obj);
 
 /* Object array handling .. */
 void add_object_array(struct object *obj, const char *name, struct object_array *array);
+void add_object_array_with_mode(struct object *obj, const char *name, struct object_array *array, unsigned mode);
 
 #endif /* OBJECT_H */
index d9883225eabf10ad9a3a169e7049c9ee25e5d9cd..d04536bbff7cba22ca67521d45e690dfa5aa8675 100644 (file)
@@ -40,15 +40,16 @@ static int verify_packfile(struct packed_git *p,
         * have verified that nr_objects matches between idx and pack,
         * we do not do scan-streaming check on the pack file.
         */
-       nr_objects = num_packed_objects(p);
+       nr_objects = p->num_objects;
        for (i = 0, err = 0; i < nr_objects; i++) {
-               unsigned char sha1[20];
+               const unsigned char *sha1;
                void *data;
                enum object_type type;
                unsigned long size;
                off_t offset;
 
-               if (nth_packed_object_sha1(p, i, sha1))
+               sha1 = nth_packed_object_sha1(p, i);
+               if (!sha1)
                        die("internal error pack-check nth-packed-object");
                offset = find_pack_entry_one(sha1, p);
                if (!offset)
@@ -78,18 +79,20 @@ static void show_pack_info(struct packed_git *p)
 {
        uint32_t nr_objects, i, chain_histogram[MAX_CHAIN];
 
-       nr_objects = num_packed_objects(p);
+       nr_objects = p->num_objects;
        memset(chain_histogram, 0, sizeof(chain_histogram));
 
        for (i = 0; i < nr_objects; i++) {
-               unsigned char sha1[20], base_sha1[20];
+               const unsigned char *sha1;
+               unsigned char base_sha1[20];
                const char *type;
                unsigned long size;
                unsigned long store_size;
                off_t offset;
                unsigned int delta_chain_length;
 
-               if (nth_packed_object_sha1(p, i, sha1))
+               sha1 = nth_packed_object_sha1(p, i);
+               if (!sha1)
                        die("internal error pack-check nth-packed-object");
                offset = find_pack_entry_one(sha1, p);
                if (!offset)
index 40e579b2d9788bb0867b345c3596b1ad1539272a..87077e150c1b53a26464089a2cabafafb32e6636 100644 (file)
@@ -247,16 +247,19 @@ static struct pack_list * pack_list_difference(const struct pack_list *A,
 
 static void cmp_two_packs(struct pack_list *p1, struct pack_list *p2)
 {
-       int p1_off, p2_off;
+       unsigned long p1_off = 0, p2_off = 0, p1_step, p2_step;
        const unsigned char *p1_base, *p2_base;
        struct llist_item *p1_hint = NULL, *p2_hint = NULL;
 
-       p1_off = p2_off = 256 * 4 + 4;
        p1_base = p1->pack->index_data;
        p2_base = p2->pack->index_data;
+       p1_base += 256 * 4 + ((p1->pack->index_version < 2) ? 4 : 8);
+       p2_base += 256 * 4 + ((p2->pack->index_version < 2) ? 4 : 8);
+       p1_step = (p1->pack->index_version < 2) ? 24 : 20;
+       p2_step = (p2->pack->index_version < 2) ? 24 : 20;
 
-       while (p1_off <= p1->pack->index_size - 3 * 20 &&
-              p2_off <= p2->pack->index_size - 3 * 20)
+       while (p1_off < p1->pack->num_objects * p1_step &&
+              p2_off < p2->pack->num_objects * p2_step)
        {
                int cmp = hashcmp(p1_base + p1_off, p2_base + p2_off);
                /* cmp ~ p1 - p2 */
@@ -265,14 +268,14 @@ static void cmp_two_packs(struct pack_list *p1, struct pack_list *p2)
                                        p1_base + p1_off, p1_hint);
                        p2_hint = llist_sorted_remove(p2->unique_objects,
                                        p1_base + p1_off, p2_hint);
-                       p1_off+=24;
-                       p2_off+=24;
+                       p1_off += p1_step;
+                       p2_off += p2_step;
                        continue;
                }
                if (cmp < 0) { /* p1 has the object, p2 doesn't */
-                       p1_off+=24;
+                       p1_off += p1_step;
                } else { /* p2 has the object, p1 doesn't */
-                       p2_off+=24;
+                       p2_off += p2_step;
                }
        }
 }
@@ -352,28 +355,31 @@ static int is_superset(struct pack_list *pl, struct llist *list)
 static size_t sizeof_union(struct packed_git *p1, struct packed_git *p2)
 {
        size_t ret = 0;
-       int p1_off, p2_off;
+       unsigned long p1_off = 0, p2_off = 0, p1_step, p2_step;
        const unsigned char *p1_base, *p2_base;
 
-       p1_off = p2_off = 256 * 4 + 4;
        p1_base = p1->index_data;
        p2_base = p2->index_data;
+       p1_base += 256 * 4 + ((p1->index_version < 2) ? 4 : 8);
+       p2_base += 256 * 4 + ((p2->index_version < 2) ? 4 : 8);
+       p1_step = (p1->index_version < 2) ? 24 : 20;
+       p2_step = (p2->index_version < 2) ? 24 : 20;
 
-       while (p1_off <= p1->index_size - 3 * 20 &&
-              p2_off <= p2->index_size - 3 * 20)
+       while (p1_off < p1->num_objects * p1_step &&
+              p2_off < p2->num_objects * p2_step)
        {
                int cmp = hashcmp(p1_base + p1_off, p2_base + p2_off);
                /* cmp ~ p1 - p2 */
                if (cmp == 0) {
                        ret++;
-                       p1_off+=24;
-                       p2_off+=24;
+                       p1_off += p1_step;
+                       p2_off += p2_step;
                        continue;
                }
                if (cmp < 0) { /* p1 has the object, p2 doesn't */
-                       p1_off+=24;
+                       p1_off += p1_step;
                } else { /* p2 has the object, p1 doesn't */
-                       p2_off+=24;
+                       p2_off += p2_step;
                }
        }
        return ret;
@@ -535,7 +541,7 @@ static void scan_alt_odb_packs(void)
 static struct pack_list * add_pack(struct packed_git *p)
 {
        struct pack_list l;
-       size_t off;
+       unsigned long off = 0, step;
        const unsigned char *base;
 
        if (!p->pack_local && !(alt_odb || verbose))
@@ -544,11 +550,12 @@ static struct pack_list * add_pack(struct packed_git *p)
        l.pack = p;
        llist_init(&l.all_objects);
 
-       off = 256 * 4 + 4;
        base = p->index_data;
-       while (off <= p->index_size - 3 * 20) {
+       base += 256 * 4 + ((p->index_version < 2) ? 4 : 8);
+       step = (p->index_version < 2) ? 24 : 20;
+       while (off < p->num_objects * step) {
                llist_insert_back(l.all_objects, base + off);
-               off += 24;
+               off += step;
        }
        /* this list will be pruned in cmp_two_packs later */
        l.unique_objects = llist_copy(l.all_objects);
diff --git a/pack-write.c b/pack-write.c
new file mode 100644 (file)
index 0000000..ae2e481
--- /dev/null
@@ -0,0 +1,39 @@
+#include "cache.h"
+#include "pack.h"
+
+void fixup_pack_header_footer(int pack_fd,
+                        unsigned char *pack_file_sha1,
+                        const char *pack_name,
+                        uint32_t object_count)
+{
+       static const int buf_sz = 128 * 1024;
+       SHA_CTX c;
+       struct pack_header hdr;
+       char *buf;
+
+       if (lseek(pack_fd, 0, SEEK_SET) != 0)
+               die("Failed seeking to start: %s", strerror(errno));
+       if (read_in_full(pack_fd, &hdr, sizeof(hdr)) != sizeof(hdr))
+               die("Unable to reread header of %s: %s", pack_name, strerror(errno));
+       if (lseek(pack_fd, 0, SEEK_SET) != 0)
+               die("Failed seeking to start: %s", strerror(errno));
+       hdr.hdr_entries = htonl(object_count);
+       write_or_die(pack_fd, &hdr, sizeof(hdr));
+
+       SHA1_Init(&c);
+       SHA1_Update(&c, &hdr, sizeof(hdr));
+
+       buf = xmalloc(buf_sz);
+       for (;;) {
+               ssize_t n = xread(pack_fd, buf, buf_sz);
+               if (!n)
+                       break;
+               if (n < 0)
+                       die("Failed to checksum %s: %s", pack_name, strerror(errno));
+               SHA1_Update(&c, buf, n);
+       }
+       free(buf);
+
+       SHA1_Final(pack_file_sha1, &c);
+       write_or_die(pack_fd, pack_file_sha1, 20);
+}
diff --git a/pack.h b/pack.h
index d4d412ccbb403f1374d41a00791eec3c16ba64ef..d667fb8d5a49b4480beaa6f384f1f6f86b035806 100644 (file)
--- a/pack.h
+++ b/pack.h
@@ -44,6 +44,7 @@ struct pack_idx_header {
 
 
 extern int verify_pack(struct packed_git *, int);
+extern void fixup_pack_header_footer(int, unsigned char *, const char *, uint32_t);
 
 #define PH_ERROR_EOF           (-1)
 #define PH_ERROR_PACK_SIGNATURE        (-2)
diff --git a/patch-ids.c b/patch-ids.c
new file mode 100644 (file)
index 0000000..a288fac
--- /dev/null
@@ -0,0 +1,192 @@
+#include "cache.h"
+#include "diff.h"
+#include "commit.h"
+#include "patch-ids.h"
+
+static int commit_patch_id(struct commit *commit, struct diff_options *options,
+                   unsigned char *sha1)
+{
+       if (commit->parents)
+               diff_tree_sha1(commit->parents->item->object.sha1,
+                              commit->object.sha1, "", options);
+       else
+               diff_root_tree_sha1(commit->object.sha1, "", options);
+       diffcore_std(options);
+       return diff_flush_patch_id(options, sha1);
+}
+
+static uint32_t take2(const unsigned char *id)
+{
+       return ((id[0] << 8) | id[1]);
+}
+
+/*
+ * Conventional binary search loop looks like this:
+ *
+ *      do {
+ *              int mi = (lo + hi) / 2;
+ *              int cmp = "entry pointed at by mi" minus "target";
+ *              if (!cmp)
+ *                      return (mi is the wanted one)
+ *              if (cmp > 0)
+ *                      hi = mi; "mi is larger than target"
+ *              else
+ *                      lo = mi+1; "mi is smaller than target"
+ *      } while (lo < hi);
+ *
+ * The invariants are:
+ *
+ * - When entering the loop, lo points at a slot that is never
+ *   above the target (it could be at the target), hi points at a
+ *   slot that is guaranteed to be above the target (it can never
+ *   be at the target).
+ *
+ * - We find a point 'mi' between lo and hi (mi could be the same
+ *   as lo, but never can be the same as hi), and check if it hits
+ *   the target.  There are three cases:
+ *
+ *    - if it is a hit, we are happy.
+ *
+ *    - if it is strictly higher than the target, we update hi with
+ *      it.
+ *
+ *    - if it is strictly lower than the target, we update lo to be
+ *      one slot after it, because we allow lo to be at the target.
+ *
+ * When choosing 'mi', we do not have to take the "middle" but
+ * anywhere in between lo and hi, as long as lo <= mi < hi is
+ * satisfied.  When we somehow know that the distance between the
+ * target and lo is much shorter than the target and hi, we could
+ * pick mi that is much closer to lo than the midway.
+ */
+static int patch_pos(struct patch_id **table, int nr, const unsigned char *id)
+{
+       int hi = nr;
+       int lo = 0;
+       int mi = 0;
+
+       if (!nr)
+               return -1;
+
+       if (nr != 1) {
+               unsigned lov, hiv, miv, ofs;
+
+               for (ofs = 0; ofs < 18; ofs += 2) {
+                       lov = take2(table[0]->patch_id + ofs);
+                       hiv = take2(table[nr-1]->patch_id + ofs);
+                       miv = take2(id + ofs);
+                       if (miv < lov)
+                               return -1;
+                       if (hiv < miv)
+                               return -1 - nr;
+                       if (lov != hiv) {
+                               /*
+                                * At this point miv could be equal
+                                * to hiv (but id could still be higher);
+                                * the invariant of (mi < hi) should be
+                                * kept.
+                                */
+                               mi = (nr-1) * (miv - lov) / (hiv - lov);
+                               if (lo <= mi && mi < hi)
+                                       break;
+                               die("oops");
+                       }
+               }
+               if (18 <= ofs)
+                       die("cannot happen -- lo and hi are identical");
+       }
+
+       do {
+               int cmp;
+               cmp = hashcmp(table[mi]->patch_id, id);
+               if (!cmp)
+                       return mi;
+               if (cmp > 0)
+                       hi = mi;
+               else
+                       lo = mi + 1;
+               mi = (hi + lo) / 2;
+       } while (lo < hi);
+       return -lo-1;
+}
+
+#define BUCKET_SIZE 190 /* 190 * 21 = 3990, with slop close enough to 4K */
+struct patch_id_bucket {
+       struct patch_id_bucket *next;
+       int nr;
+       struct patch_id bucket[BUCKET_SIZE];
+};
+
+int init_patch_ids(struct patch_ids *ids)
+{
+       memset(ids, 0, sizeof(*ids));
+       diff_setup(&ids->diffopts);
+       ids->diffopts.recursive = 1;
+       if (diff_setup_done(&ids->diffopts) < 0)
+               return error("diff_setup_done failed");
+       return 0;
+}
+
+int free_patch_ids(struct patch_ids *ids)
+{
+       struct patch_id_bucket *next, *patches;
+
+       free(ids->table);
+       for (patches = ids->patches; patches; patches = next) {
+               next = patches->next;
+               free(patches);
+       }
+       return 0;
+}
+
+static struct patch_id *add_commit(struct commit *commit,
+                                  struct patch_ids *ids,
+                                  int no_add)
+{
+       struct patch_id_bucket *bucket;
+       struct patch_id *ent;
+       unsigned char sha1[20];
+       int pos;
+
+       if (commit_patch_id(commit, &ids->diffopts, sha1))
+               return NULL;
+       pos = patch_pos(ids->table, ids->nr, sha1);
+       if (0 <= pos)
+               return ids->table[pos];
+       if (no_add)
+               return NULL;
+
+       pos = -1 - pos;
+
+       bucket = ids->patches;
+       if (!bucket || (BUCKET_SIZE <= bucket->nr)) {
+               bucket = xcalloc(1, sizeof(*bucket));
+               bucket->next = ids->patches;
+               ids->patches = bucket;
+       }
+       ent = &bucket->bucket[bucket->nr++];
+       hashcpy(ent->patch_id, sha1);
+
+       if (ids->alloc <= ids->nr) {
+               ids->alloc = alloc_nr(ids->nr);
+               ids->table = xrealloc(ids->table, sizeof(ent) * ids->alloc);
+       }
+       if (pos < ids->nr)
+               memmove(ids->table + pos + 1, ids->table + pos,
+                       sizeof(ent) * (ids->nr - pos));
+       ids->nr++;
+       ids->table[pos] = ent;
+       return ids->table[pos];
+}
+
+struct patch_id *has_commit_patch_id(struct commit *commit,
+                                    struct patch_ids *ids)
+{
+       return add_commit(commit, ids, 1);
+}
+
+struct patch_id *add_commit_patch_id(struct commit *commit,
+                                    struct patch_ids *ids)
+{
+       return add_commit(commit, ids, 0);
+}
diff --git a/patch-ids.h b/patch-ids.h
new file mode 100644 (file)
index 0000000..c8c7ca1
--- /dev/null
@@ -0,0 +1,21 @@
+#ifndef PATCH_IDS_H
+#define PATCH_IDS_H
+
+struct patch_id {
+       unsigned char patch_id[20];
+       char seen;
+};
+
+struct patch_ids {
+       struct diff_options diffopts;
+       int nr, alloc;
+       struct patch_id **table;
+       struct patch_id_bucket *patches;
+};
+
+int init_patch_ids(struct patch_ids *);
+int free_patch_ids(struct patch_ids *);
+struct patch_id *add_commit_patch_id(struct commit *, struct patch_ids *);
+struct patch_id *has_commit_patch_id(struct commit *, struct patch_ids *);
+
+#endif /* PATCH_IDS_H */
index d6401eaa352ea068ae59c1e04354d7784eaeac96..ce5ffabcce347b87e48d120f55fa0a8d07cc5193 100644 (file)
@@ -1,5 +1,5 @@
-#ifndef _PATH_LIST_H_
-#define _PATH_LIST_H_
+#ifndef PATH_LIST_H
+#define PATH_LIST_H
 
 struct path_list_item {
        char *path;
@@ -19,4 +19,4 @@ void path_list_clear(struct path_list *list, int free_items);
 struct path_list_item *path_list_insert(const char *path, struct path_list *list);
 struct path_list_item *path_list_lookup(const char *path, struct path_list *list);
 
-#endif /* _PATH_LIST_H_ */
+#endif /* PATH_LIST_H */
index b5b1cf5edcd860e88c461c600d3fa841654fef76..8fd36117539b528173e84f1df3acfc754ccc868f 100644 (file)
@@ -516,9 +516,11 @@ sub config {
 }
 
 
-=item config_boolean ( VARIABLE )
+=item config_bool ( VARIABLE )
 
-Retrieve the boolean configuration C<VARIABLE>.
+Retrieve the bool configuration C<VARIABLE>. The return value
+is usable as a boolean in perl (and C<undef> if it's not defined,
+of course).
 
 Must be called on a repository instance.
 
@@ -526,14 +528,16 @@ This currently wraps command('config') so it is not so fast.
 
 =cut
 
-sub config_boolean {
+sub config_bool {
        my ($self, $var) = @_;
        $self->repo_path()
                or throw Error::Simple("not a repository");
 
        try {
-               return $self->command_oneline('config', '--bool', '--get',
+               my $val = $self->command_oneline('config', '--bool', '--get',
                                              $var);
+               return undef unless defined $val;
+               return $val eq 'true';
        } catch Git::Error::Command with {
                my $E = shift;
                if ($E->value() == 1) {
index b4cb7e2756dcc52c17aebf4c0fc6adc7b415ef3c..b60526869a38bd20f800c275f7b42390c8bea1ee 100644 (file)
@@ -65,10 +65,10 @@ void packet_write(int fd, const char *fmt, ...)
 
 static void safe_read(int fd, void *buffer, unsigned size)
 {
-       int n = 0;
+       size_t n = 0;
 
        while (n < size) {
-               int ret = xread(fd, (char *) buffer + n, size - n);
+               ssize_t ret = xread(fd, (char *) buffer + n, size - n);
                if (ret < 0)
                        die("read error (%s)", strerror(errno));
                if (!ret)
diff --git a/progress.c b/progress.c
new file mode 100644 (file)
index 0000000..05f7890
--- /dev/null
@@ -0,0 +1,106 @@
+#include "git-compat-util.h"
+#include "progress.h"
+
+static volatile sig_atomic_t progress_update;
+
+static void progress_interval(int signum)
+{
+       progress_update = 1;
+}
+
+static void set_progress_signal(void)
+{
+       struct sigaction sa;
+       struct itimerval v;
+
+       progress_update = 0;
+
+       memset(&sa, 0, sizeof(sa));
+       sa.sa_handler = progress_interval;
+       sigemptyset(&sa.sa_mask);
+       sa.sa_flags = SA_RESTART;
+       sigaction(SIGALRM, &sa, NULL);
+
+       v.it_interval.tv_sec = 1;
+       v.it_interval.tv_usec = 0;
+       v.it_value = v.it_interval;
+       setitimer(ITIMER_REAL, &v, NULL);
+}
+
+static void clear_progress_signal(void)
+{
+       struct itimerval v = {{0,},};
+       setitimer(ITIMER_REAL, &v, NULL);
+       signal(SIGALRM, SIG_IGN);
+       progress_update = 0;
+}
+
+int display_progress(struct progress *progress, unsigned n)
+{
+       if (progress->delay) {
+               char buf[80];
+               if (!progress_update || --progress->delay)
+                       return 0;
+               if (progress->total) {
+                       unsigned percent = n * 100 / progress->total;
+                       if (percent > progress->delayed_percent_treshold) {
+                               /* inhibit this progress report entirely */
+                               clear_progress_signal();
+                               progress->delay = -1;
+                               progress->total = 0;
+                               return 0;
+                       }
+               }
+               if (snprintf(buf, sizeof(buf),
+                            progress->delayed_title, progress->total))
+                       fprintf(stderr, "%s\n", buf);
+       }
+       if (progress->total) {
+               unsigned percent = n * 100 / progress->total;
+               if (percent != progress->last_percent || progress_update) {
+                       progress->last_percent = percent;
+                       fprintf(stderr, "%s%4u%% (%u/%u) done\r",
+                               progress->prefix, percent, n, progress->total);
+                       progress_update = 0;
+                       return 1;
+               }
+       } else if (progress_update) {
+               fprintf(stderr, "%s%u\r", progress->prefix, n);
+               progress_update = 0;
+               return 1;
+       }
+       return 0;
+}
+
+void start_progress(struct progress *progress, const char *title,
+                   const char *prefix, unsigned total)
+{
+       char buf[80];
+       progress->prefix = prefix;
+       progress->total = total;
+       progress->last_percent = -1;
+       progress->delay = 0;
+       if (snprintf(buf, sizeof(buf), title, total))
+               fprintf(stderr, "%s\n", buf);
+       set_progress_signal();
+}
+
+void start_progress_delay(struct progress *progress, const char *title,
+                         const char *prefix, unsigned total,
+                         unsigned percent_treshold, unsigned delay)
+{
+       progress->prefix = prefix;
+       progress->total = total;
+       progress->last_percent = -1;
+       progress->delayed_percent_treshold = percent_treshold;
+       progress->delayed_title = title;
+       progress->delay = delay;
+       set_progress_signal();
+}
+
+void stop_progress(struct progress *progress)
+{
+       clear_progress_signal();
+       if (progress->total)
+               fputc('\n', stderr);
+}
diff --git a/progress.h b/progress.h
new file mode 100644 (file)
index 0000000..5ae1a89
--- /dev/null
@@ -0,0 +1,21 @@
+#ifndef PROGRESS_H
+#define PROGRESS_H
+
+struct progress {
+       const char *prefix;
+       unsigned total;
+       unsigned last_percent;
+       unsigned delay;
+       unsigned delayed_percent_treshold;
+       const char *delayed_title;
+};
+
+int display_progress(struct progress *progress, unsigned n);
+void start_progress(struct progress *progress, const char *title,
+                   const char *prefix, unsigned total);
+void start_progress_delay(struct progress *progress, const char *title,
+                         const char *prefix, unsigned total,
+                         unsigned percent_treshold, unsigned delay);
+void stop_progress(struct progress *progress);
+
+#endif
index 6339a278da1ae1b323b5abf7d2604c7afdfde4e4..d9f46da5cc547d8cfaa34c901be50a13995b120e 100644 (file)
@@ -3,8 +3,10 @@
  *
  * Copyright (C) Linus Torvalds, 2005
  */
+#define NO_THE_INDEX_COMPATIBILITY_MACROS
 #include "cache.h"
 #include "cache-tree.h"
+#include "refs.h"
 
 /* Index extensions.
  *
 #define CACHE_EXT(s) ( (s[0]<<24)|(s[1]<<16)|(s[2]<<8)|(s[3]) )
 #define CACHE_EXT_TREE 0x54524545      /* "TREE" */
 
-struct cache_entry **active_cache;
-static time_t index_file_timestamp;
-unsigned int active_nr, active_alloc, active_cache_changed;
-
-struct cache_tree *active_cache_tree;
-
-int cache_errno;
-
-static void *cache_mmap;
-static size_t cache_mmap_size;
+struct index_state the_index;
 
 /*
  * This only updates the "non-critical" parts of the directory
@@ -93,6 +86,23 @@ static int ce_compare_link(struct cache_entry *ce, size_t expected_size)
        return match;
 }
 
+static int ce_compare_gitlink(struct cache_entry *ce)
+{
+       unsigned char sha1[20];
+
+       /*
+        * We don't actually require that the .git directory
+        * under DIRLNK directory be a valid git directory. It
+        * might even be missing (in case nobody populated that
+        * sub-project).
+        *
+        * If so, we consider it always to match.
+        */
+       if (resolve_gitlink_ref(ce->name, "HEAD", sha1) < 0)
+               return 0;
+       return hashcmp(sha1, ce->sha1);
+}
+
 static int ce_modified_check_fs(struct cache_entry *ce, struct stat *st)
 {
        switch (st->st_mode & S_IFMT) {
@@ -104,6 +114,9 @@ static int ce_modified_check_fs(struct cache_entry *ce, struct stat *st)
                if (ce_compare_link(ce, xsize_t(st->st_size)))
                        return DATA_CHANGED;
                break;
+       case S_IFDIR:
+               if (S_ISDIRLNK(ntohl(ce->ce_mode)))
+                       return 0;
        default:
                return TYPE_CHANGED;
        }
@@ -129,6 +142,12 @@ static int ce_match_stat_basic(struct cache_entry *ce, struct stat *st)
                    (has_symlinks || !S_ISREG(st->st_mode)))
                        changed |= TYPE_CHANGED;
                break;
+       case S_IFDIRLNK:
+               if (!S_ISDIR(st->st_mode))
+                       changed |= TYPE_CHANGED;
+               else if (ce_compare_gitlink(ce))
+                       changed |= DATA_CHANGED;
+               return changed;
        default:
                die("internal error: ce_mode is %o", ntohl(ce->ce_mode));
        }
@@ -171,7 +190,8 @@ static int ce_match_stat_basic(struct cache_entry *ce, struct stat *st)
        return changed;
 }
 
-int ce_match_stat(struct cache_entry *ce, struct stat *st, int options)
+int ie_match_stat(struct index_state *istate,
+                 struct cache_entry *ce, struct stat *st, int options)
 {
        unsigned int changed;
        int ignore_valid = options & 01;
@@ -203,8 +223,8 @@ int ce_match_stat(struct cache_entry *ce, struct stat *st, int options)
         * carefully than others.
         */
        if (!changed &&
-           index_file_timestamp &&
-           index_file_timestamp <= ntohl(ce->ce_mtime.sec)) {
+           istate->timestamp &&
+           istate->timestamp <= ntohl(ce->ce_mtime.sec)) {
                if (assume_racy_is_modified)
                        changed |= DATA_CHANGED;
                else
@@ -214,10 +234,11 @@ int ce_match_stat(struct cache_entry *ce, struct stat *st, int options)
        return changed;
 }
 
-int ce_modified(struct cache_entry *ce, struct stat *st, int really)
+int ie_modified(struct index_state *istate,
+               struct cache_entry *ce, struct stat *st, int really)
 {
        int changed, changed_fs;
-       changed = ce_match_stat(ce, st, really);
+       changed = ie_match_stat(istate, ce, st, really);
        if (!changed)
                return 0;
        /*
@@ -285,15 +306,15 @@ int cache_name_compare(const char *name1, int flags1, const char *name2, int fla
        return 0;
 }
 
-int cache_name_pos(const char *name, int namelen)
+int index_name_pos(struct index_state *istate, const char *name, int namelen)
 {
        int first, last;
 
        first = 0;
-       last = active_nr;
+       last = istate->cache_nr;
        while (last > first) {
                int next = (last + first) >> 1;
-               struct cache_entry *ce = active_cache[next];
+               struct cache_entry *ce = istate->cache[next];
                int cmp = cache_name_compare(name, namelen, ce->name, ntohs(ce->ce_flags));
                if (!cmp)
                        return next;
@@ -307,27 +328,29 @@ int cache_name_pos(const char *name, int namelen)
 }
 
 /* Remove entry, return true if there are more entries to go.. */
-int remove_cache_entry_at(int pos)
+int remove_index_entry_at(struct index_state *istate, int pos)
 {
-       active_cache_changed = 1;
-       active_nr--;
-       if (pos >= active_nr)
+       istate->cache_changed = 1;
+       istate->cache_nr--;
+       if (pos >= istate->cache_nr)
                return 0;
-       memmove(active_cache + pos, active_cache + pos + 1, (active_nr - pos) * sizeof(struct cache_entry *));
+       memmove(istate->cache + pos,
+               istate->cache + pos + 1,
+               (istate->cache_nr - pos) * sizeof(struct cache_entry *));
        return 1;
 }
 
-int remove_file_from_cache(const char *path)
+int remove_file_from_index(struct index_state *istate, const char *path)
 {
-       int pos = cache_name_pos(path, strlen(path));
+       int pos = index_name_pos(istate, path, strlen(path));
        if (pos < 0)
                pos = -pos-1;
-       while (pos < active_nr && !strcmp(active_cache[pos]->name, path))
-               remove_cache_entry_at(pos);
+       while (pos < istate->cache_nr && !strcmp(istate->cache[pos]->name, path))
+               remove_index_entry_at(istate, pos);
        return 0;
 }
 
-int add_file_to_index(const char *path, int verbose)
+int add_file_to_index(struct index_state *istate, const char *path, int verbose)
 {
        int size, namelen;
        struct stat st;
@@ -336,10 +359,14 @@ int add_file_to_index(const char *path, int verbose)
        if (lstat(path, &st))
                die("%s: unable to stat (%s)", path, strerror(errno));
 
-       if (!S_ISREG(st.st_mode) && !S_ISLNK(st.st_mode))
-               die("%s: can only add regular files or symbolic links", path);
+       if (!S_ISREG(st.st_mode) && !S_ISLNK(st.st_mode) && !S_ISDIR(st.st_mode))
+               die("%s: can only add regular files, symbolic links or git-directories", path);
 
        namelen = strlen(path);
+       if (S_ISDIR(st.st_mode)) {
+               while (namelen && path[namelen-1] == '/')
+                       namelen--;
+       }
        size = cache_entry_size(namelen);
        ce = xcalloc(1, size);
        memcpy(ce->name, path, namelen);
@@ -353,19 +380,19 @@ int add_file_to_index(const char *path, int verbose)
                 * from it, otherwise assume unexecutable regular file.
                 */
                struct cache_entry *ent;
-               int pos = cache_name_pos(path, namelen);
+               int pos = index_name_pos(istate, path, namelen);
 
-               ent = (0 <= pos) ? active_cache[pos] : NULL;
+               ent = (0 <= pos) ? istate->cache[pos] : NULL;
                ce->ce_mode = ce_mode_from_stat(ent, st.st_mode);
        }
 
        if (index_path(ce->sha1, path, &st, 1))
                die("unable to index file %s", path);
-       if (add_cache_entry(ce, ADD_CACHE_OK_TO_ADD|ADD_CACHE_OK_TO_REPLACE))
+       if (add_index_entry(istate, ce, ADD_CACHE_OK_TO_ADD|ADD_CACHE_OK_TO_REPLACE))
                die("unable to add %s to index",path);
        if (verbose)
                printf("add '%s'\n", path);
-       cache_tree_invalidate_path(active_cache_tree, path);
+       cache_tree_invalidate_path(istate->cache_tree, path);
        return 0;
 }
 
@@ -469,15 +496,16 @@ inside:
  * Do we have another file that has the beginning components being a
  * proper superset of the name we're trying to add?
  */
-static int has_file_name(const struct cache_entry *ce, int pos, int ok_to_replace)
+static int has_file_name(struct index_state *istate,
+                        const struct cache_entry *ce, int pos, int ok_to_replace)
 {
        int retval = 0;
        int len = ce_namelen(ce);
        int stage = ce_stage(ce);
        const char *name = ce->name;
 
-       while (pos < active_nr) {
-               struct cache_entry *p = active_cache[pos++];
+       while (pos < istate->cache_nr) {
+               struct cache_entry *p = istate->cache[pos++];
 
                if (len >= ce_namelen(p))
                        break;
@@ -487,10 +515,12 @@ static int has_file_name(const struct cache_entry *ce, int pos, int ok_to_replac
                        continue;
                if (p->name[len] != '/')
                        continue;
+               if (!ce_stage(p) && !p->ce_mode)
+                       continue;
                retval = -1;
                if (!ok_to_replace)
                        break;
-               remove_cache_entry_at(--pos);
+               remove_index_entry_at(istate, --pos);
        }
        return retval;
 }
@@ -499,7 +529,8 @@ static int has_file_name(const struct cache_entry *ce, int pos, int ok_to_replac
  * Do we have another file with a pathname that is a proper
  * subset of the name we're trying to add?
  */
-static int has_dir_name(const struct cache_entry *ce, int pos, int ok_to_replace)
+static int has_dir_name(struct index_state *istate,
+                       const struct cache_entry *ce, int pos, int ok_to_replace)
 {
        int retval = 0;
        int stage = ce_stage(ce);
@@ -517,28 +548,39 @@ static int has_dir_name(const struct cache_entry *ce, int pos, int ok_to_replace
                }
                len = slash - name;
 
-               pos = cache_name_pos(name, ntohs(create_ce_flags(len, stage)));
+               pos = index_name_pos(istate, name, ntohs(create_ce_flags(len, stage)));
                if (pos >= 0) {
-                       retval = -1;
-                       if (!ok_to_replace)
-                               break;
-                       remove_cache_entry_at(pos);
-                       continue;
+                       /*
+                        * Found one, but not so fast.  This could
+                        * be a marker that says "I was here, but
+                        * I am being removed".  Such an entry is
+                        * not a part of the resulting tree, and
+                        * it is Ok to have a directory at the same
+                        * path.
+                        */
+                       if (stage || istate->cache[pos]->ce_mode) {
+                               retval = -1;
+                               if (!ok_to_replace)
+                                       break;
+                               remove_index_entry_at(istate, pos);
+                               continue;
+                       }
                }
+               else
+                       pos = -pos-1;
 
                /*
                 * Trivial optimization: if we find an entry that
                 * already matches the sub-directory, then we know
                 * we're ok, and we can exit.
                 */
-               pos = -pos-1;
-               while (pos < active_nr) {
-                       struct cache_entry *p = active_cache[pos];
+               while (pos < istate->cache_nr) {
+                       struct cache_entry *p = istate->cache[pos];
                        if ((ce_namelen(p) <= len) ||
                            (p->name[len] != '/') ||
                            memcmp(p->name, name, len))
                                break; /* not our subdirectory */
-                       if (ce_stage(p) == stage)
+                       if (ce_stage(p) == stage && (stage || p->ce_mode))
                                /* p is at the same stage as our entry, and
                                 * is a subdirectory of what we are looking
                                 * at, so we cannot have conflicts at our
@@ -560,34 +602,45 @@ static int has_dir_name(const struct cache_entry *ce, int pos, int ok_to_replace
  * from the cache so the caller should recompute the insert position.
  * When this happens, we return non-zero.
  */
-static int check_file_directory_conflict(const struct cache_entry *ce, int pos, int ok_to_replace)
+static int check_file_directory_conflict(struct index_state *istate,
+                                        const struct cache_entry *ce,
+                                        int pos, int ok_to_replace)
 {
+       int retval;
+
+       /*
+        * When ce is an "I am going away" entry, we allow it to be added
+        */
+       if (!ce_stage(ce) && !ce->ce_mode)
+               return 0;
+
        /*
         * We check if the path is a sub-path of a subsequent pathname
         * first, since removing those will not change the position
-        * in the array
+        * in the array.
         */
-       int retval = has_file_name(ce, pos, ok_to_replace);
+       retval = has_file_name(istate, ce, pos, ok_to_replace);
+
        /*
         * Then check if the path might have a clashing sub-directory
         * before it.
         */
-       return retval + has_dir_name(ce, pos, ok_to_replace);
+       return retval + has_dir_name(istate, ce, pos, ok_to_replace);
 }
 
-int add_cache_entry(struct cache_entry *ce, int option)
+int add_index_entry(struct index_state *istate, struct cache_entry *ce, int option)
 {
        int pos;
        int ok_to_add = option & ADD_CACHE_OK_TO_ADD;
        int ok_to_replace = option & ADD_CACHE_OK_TO_REPLACE;
        int skip_df_check = option & ADD_CACHE_SKIP_DFCHECK;
 
-       pos = cache_name_pos(ce->name, ntohs(ce->ce_flags));
+       pos = index_name_pos(istate, ce->name, ntohs(ce->ce_flags));
 
        /* existing match? Just replace it. */
        if (pos >= 0) {
-               active_cache_changed = 1;
-               active_cache[pos] = ce;
+               istate->cache_changed = 1;
+               istate->cache[pos] = ce;
                return 0;
        }
        pos = -pos-1;
@@ -596,10 +649,10 @@ int add_cache_entry(struct cache_entry *ce, int option)
         * Inserting a merged entry ("stage 0") into the index
         * will always replace all non-merged entries..
         */
-       if (pos < active_nr && ce_stage(ce) == 0) {
-               while (ce_same_name(active_cache[pos], ce)) {
+       if (pos < istate->cache_nr && ce_stage(ce) == 0) {
+               while (ce_same_name(istate->cache[pos], ce)) {
                        ok_to_add = 1;
-                       if (!remove_cache_entry_at(pos))
+                       if (!remove_index_entry_at(istate, pos))
                                break;
                }
        }
@@ -610,25 +663,29 @@ int add_cache_entry(struct cache_entry *ce, int option)
                return -1;
 
        if (!skip_df_check &&
-           check_file_directory_conflict(ce, pos, ok_to_replace)) {
+           check_file_directory_conflict(istate, ce, pos, ok_to_replace)) {
                if (!ok_to_replace)
-                       return error("'%s' appears as both a file and as a directory", ce->name);
-               pos = cache_name_pos(ce->name, ntohs(ce->ce_flags));
+                       return error("'%s' appears as both a file and as a directory",
+                                    ce->name);
+               pos = index_name_pos(istate, ce->name, ntohs(ce->ce_flags));
                pos = -pos-1;
        }
 
        /* Make sure the array is big enough .. */
-       if (active_nr == active_alloc) {
-               active_alloc = alloc_nr(active_alloc);
-               active_cache = xrealloc(active_cache, active_alloc * sizeof(struct cache_entry *));
+       if (istate->cache_nr == istate->cache_alloc) {
+               istate->cache_alloc = alloc_nr(istate->cache_alloc);
+               istate->cache = xrealloc(istate->cache,
+                                       istate->cache_alloc * sizeof(struct cache_entry *));
        }
 
        /* Add it in.. */
-       active_nr++;
-       if (active_nr > pos)
-               memmove(active_cache + pos + 1, active_cache + pos, (active_nr - pos - 1) * sizeof(ce));
-       active_cache[pos] = ce;
-       active_cache_changed = 1;
+       istate->cache_nr++;
+       if (istate->cache_nr > pos)
+               memmove(istate->cache + pos + 1,
+                       istate->cache + pos,
+                       (istate->cache_nr - pos - 1) * sizeof(ce));
+       istate->cache[pos] = ce;
+       istate->cache_changed = 1;
        return 0;
 }
 
@@ -643,18 +700,20 @@ int add_cache_entry(struct cache_entry *ce, int option)
  * For example, you'd want to do this after doing a "git-read-tree",
  * to link up the stat cache details with the proper files.
  */
-struct cache_entry *refresh_cache_entry(struct cache_entry *ce, int really)
+static struct cache_entry *refresh_cache_ent(struct index_state *istate,
+                                            struct cache_entry *ce, int really, int *err)
 {
        struct stat st;
        struct cache_entry *updated;
        int changed, size;
 
        if (lstat(ce->name, &st) < 0) {
-               cache_errno = errno;
+               if (err)
+                       *err = errno;
                return NULL;
        }
 
-       changed = ce_match_stat(ce, &st, really);
+       changed = ie_match_stat(istate, ce, &st, really);
        if (!changed) {
                if (really && assume_unchanged &&
                    !(ce->ce_flags & htons(CE_VALID)))
@@ -663,8 +722,9 @@ struct cache_entry *refresh_cache_entry(struct cache_entry *ce, int really)
                        return ce;
        }
 
-       if (ce_modified(ce, &st, really)) {
-               cache_errno = EINVAL;
+       if (ie_modified(istate, ce, &st, really)) {
+               if (err)
+                       *err = EINVAL;
                return NULL;
        }
 
@@ -685,7 +745,7 @@ struct cache_entry *refresh_cache_entry(struct cache_entry *ce, int really)
        return updated;
 }
 
-int refresh_cache(unsigned int flags)
+int refresh_index(struct index_state *istate, unsigned int flags)
 {
        int i;
        int has_errors = 0;
@@ -694,12 +754,14 @@ int refresh_cache(unsigned int flags)
        int quiet = (flags & REFRESH_QUIET) != 0;
        int not_new = (flags & REFRESH_IGNORE_MISSING) != 0;
 
-       for (i = 0; i < active_nr; i++) {
+       for (i = 0; i < istate->cache_nr; i++) {
                struct cache_entry *ce, *new;
-               ce = active_cache[i];
+               int cache_errno = 0;
+
+               ce = istate->cache[i];
                if (ce_stage(ce)) {
-                       while ((i < active_nr) &&
-                              ! strcmp(active_cache[i]->name, ce->name))
+                       while ((i < istate->cache_nr) &&
+                              ! strcmp(istate->cache[i]->name, ce->name))
                                i++;
                        i--;
                        if (allow_unmerged)
@@ -709,7 +771,7 @@ int refresh_cache(unsigned int flags)
                        continue;
                }
 
-               new = refresh_cache_entry(ce, really);
+               new = refresh_cache_ent(istate, ce, really, &cache_errno);
                if (new == ce)
                        continue;
                if (!new) {
@@ -720,7 +782,7 @@ int refresh_cache(unsigned int flags)
                                 * means the index is not valid anymore.
                                 */
                                ce->ce_flags &= ~htons(CE_VALID);
-                               active_cache_changed = 1;
+                               istate->cache_changed = 1;
                        }
                        if (quiet)
                                continue;
@@ -728,15 +790,20 @@ int refresh_cache(unsigned int flags)
                        has_errors = 1;
                        continue;
                }
-               active_cache_changed = 1;
-               /* You can NOT just free active_cache[i] here, since it
+               istate->cache_changed = 1;
+               /* You can NOT just free istate->cache[i] here, since it
                 * might not be necessarily malloc()ed but can also come
                 * from mmap(). */
-               active_cache[i] = new;
+               istate->cache[i] = new;
        }
        return has_errors;
 }
 
+struct cache_entry *refresh_cache_entry(struct cache_entry *ce, int really)
+{
+       return refresh_cache_ent(&the_index, ce, really, NULL);
+}
+
 static int verify_hdr(struct cache_header *hdr, unsigned long size)
 {
        SHA_CTX c;
@@ -754,11 +821,12 @@ static int verify_hdr(struct cache_header *hdr, unsigned long size)
        return 0;
 }
 
-static int read_index_extension(const char *ext, void *data, unsigned long sz)
+static int read_index_extension(struct index_state *istate,
+                               const char *ext, void *data, unsigned long sz)
 {
        switch (CACHE_EXT(ext)) {
        case CACHE_EXT_TREE:
-               active_cache_tree = cache_tree_read(data, sz);
+               istate->cache_tree = cache_tree_read(data, sz);
                break;
        default:
                if (*ext < 'A' || 'Z' < *ext)
@@ -770,13 +838,13 @@ static int read_index_extension(const char *ext, void *data, unsigned long sz)
        return 0;
 }
 
-int read_cache(void)
+int read_index(struct index_state *istate)
 {
-       return read_cache_from(get_index_file());
+       return read_index_from(istate, get_index_file());
 }
 
 /* remember to discard_cache() before reading a different cache! */
-int read_cache_from(const char *path)
+int read_index_from(struct index_state *istate, const char *path)
 {
        int fd, i;
        struct stat st;
@@ -784,11 +852,11 @@ int read_cache_from(const char *path)
        struct cache_header *hdr;
 
        errno = EBUSY;
-       if (cache_mmap)
-               return active_nr;
+       if (istate->mmap)
+               return istate->cache_nr;
 
        errno = ENOENT;
-       index_file_timestamp = 0;
+       istate->timestamp = 0;
        fd = open(path, O_RDONLY);
        if (fd < 0) {
                if (errno == ENOENT)
@@ -796,33 +864,35 @@ int read_cache_from(const char *path)
                die("index file open failed (%s)", strerror(errno));
        }
 
-       if (!fstat(fd, &st)) {
-               cache_mmap_size = xsize_t(st.st_size);
-               errno = EINVAL;
-               if (cache_mmap_size >= sizeof(struct cache_header) + 20)
-                       cache_mmap = xmmap(NULL, cache_mmap_size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);
-               else
-                       die("index file smaller than expected");
-       } else
+       if (fstat(fd, &st))
                die("cannot stat the open index (%s)", strerror(errno));
+
+       errno = EINVAL;
+       istate->mmap_size = xsize_t(st.st_size);
+       if (istate->mmap_size < sizeof(struct cache_header) + 20)
+               die("index file smaller than expected");
+
+       istate->mmap = xmmap(NULL, istate->mmap_size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);
        close(fd);
 
-       hdr = cache_mmap;
-       if (verify_hdr(hdr, cache_mmap_size) < 0)
+       hdr = istate->mmap;
+       if (verify_hdr(hdr, istate->mmap_size) < 0)
                goto unmap;
 
-       active_nr = ntohl(hdr->hdr_entries);
-       active_alloc = alloc_nr(active_nr);
-       active_cache = xcalloc(active_alloc, sizeof(struct cache_entry *));
+       istate->cache_nr = ntohl(hdr->hdr_entries);
+       istate->cache_alloc = alloc_nr(istate->cache_nr);
+       istate->cache = xcalloc(istate->cache_alloc, sizeof(struct cache_entry *));
 
        offset = sizeof(*hdr);
-       for (i = 0; i < active_nr; i++) {
-               struct cache_entry *ce = (struct cache_entry *) ((char *) cache_mmap + offset);
+       for (i = 0; i < istate->cache_nr; i++) {
+               struct cache_entry *ce;
+
+               ce = (struct cache_entry *)((char *)(istate->mmap) + offset);
                offset = offset + ce_size(ce);
-               active_cache[i] = ce;
+               istate->cache[i] = ce;
        }
-       index_file_timestamp = st.st_mtime;
-       while (offset <= cache_mmap_size - 20 - 8) {
+       istate->timestamp = st.st_mtime;
+       while (offset <= istate->mmap_size - 20 - 8) {
                /* After an array of active_nr index entries,
                 * there can be arbitrary number of extended
                 * sections, each of which is prefixed with
@@ -830,35 +900,37 @@ int read_cache_from(const char *path)
                 * in 4-byte network byte order.
                 */
                unsigned long extsize;
-               memcpy(&extsize, (char *) cache_mmap + offset + 4, 4);
+               memcpy(&extsize, (char *)(istate->mmap) + offset + 4, 4);
                extsize = ntohl(extsize);
-               if (read_index_extension(((const char *) cache_mmap) + offset,
-                                        (char *) cache_mmap + offset + 8,
+               if (read_index_extension(istate,
+                                        ((const char *) (istate->mmap)) + offset,
+                                        (char *) (istate->mmap) + offset + 8,
                                         extsize) < 0)
                        goto unmap;
                offset += 8;
                offset += extsize;
        }
-       return active_nr;
+       return istate->cache_nr;
 
 unmap:
-       munmap(cache_mmap, cache_mmap_size);
+       munmap(istate->mmap, istate->mmap_size);
        errno = EINVAL;
        die("index file corrupt");
 }
 
-int discard_cache(void)
+int discard_index(struct index_state *istate)
 {
        int ret;
 
-       active_nr = active_cache_changed = 0;
-       index_file_timestamp = 0;
-       cache_tree_free(&active_cache_tree);
-       if (cache_mmap == NULL)
+       istate->cache_nr = 0;
+       istate->cache_changed = 0;
+       istate->timestamp = 0;
+       cache_tree_free(&(istate->cache_tree));
+       if (istate->mmap == NULL)
                return 0;
-       ret = munmap(cache_mmap, cache_mmap_size);
-       cache_mmap = NULL;
-       cache_mmap_size = 0;
+       ret = munmap(istate->mmap, istate->mmap_size);
+       istate->mmap = NULL;
+       istate->mmap_size = 0;
 
        /* no need to throw away allocated active_cache */
        return ret;
@@ -977,11 +1049,13 @@ static void ce_smudge_racily_clean_entry(struct cache_entry *ce)
        }
 }
 
-int write_cache(int newfd, struct cache_entry **cache, int entries)
+int write_index(struct index_state *istate, int newfd)
 {
        SHA_CTX c;
        struct cache_header hdr;
        int i, removed;
+       struct cache_entry **cache = istate->cache;
+       int entries = istate->cache_nr;
 
        for (i = removed = 0; i < entries; i++)
                if (!cache[i]->ce_mode)
@@ -999,17 +1073,17 @@ int write_cache(int newfd, struct cache_entry **cache, int entries)
                struct cache_entry *ce = cache[i];
                if (!ce->ce_mode)
                        continue;
-               if (index_file_timestamp &&
-                   index_file_timestamp <= ntohl(ce->ce_mtime.sec))
+               if (istate->timestamp &&
+                   istate->timestamp <= ntohl(ce->ce_mtime.sec))
                        ce_smudge_racily_clean_entry(ce);
                if (ce_write(&c, newfd, ce, ce_size(ce)) < 0)
                        return -1;
        }
 
        /* Write extension data here */
-       if (active_cache_tree) {
+       if (istate->cache_tree) {
                unsigned long sz;
-               void *data = cache_tree_write(active_cache_tree, &sz);
+               void *data = cache_tree_write(istate->cache_tree, &sz);
                if (data &&
                    !write_index_ext_header(&c, newfd, CACHE_EXT_TREE, sz) &&
                    !ce_write(&c, newfd, data, sz))
diff --git a/refs.c b/refs.c
index d7be2841c5f2a5fbee8964051b86356af7bf9905..89876bff871d007a6675f5790ce8cb34fe21fb39 100644 (file)
--- a/refs.c
+++ b/refs.c
@@ -47,22 +47,7 @@ static struct ref_list *add_ref(const char *name, const unsigned char *sha1,
                                struct ref_list **new_entry)
 {
        int len;
-       struct ref_list **p = &list, *entry;
-
-       /* Find the place to insert the ref into.. */
-       while ((entry = *p) != NULL) {
-               int cmp = strcmp(entry->name, name);
-               if (cmp > 0)
-                       break;
-
-               /* Same as existing entry? */
-               if (!cmp) {
-                       if (new_entry)
-                               *new_entry = entry;
-                       return list;
-               }
-               p = &entry->next;
-       }
+       struct ref_list *entry;
 
        /* Allocate it and add it in.. */
        len = strlen(name) + 1;
@@ -71,11 +56,94 @@ static struct ref_list *add_ref(const char *name, const unsigned char *sha1,
        hashclr(entry->peeled);
        memcpy(entry->name, name, len);
        entry->flag = flag;
-       entry->next = *p;
-       *p = entry;
+       entry->next = list;
        if (new_entry)
                *new_entry = entry;
-       return list;
+       return entry;
+}
+
+/* merge sort the ref list */
+static struct ref_list *sort_ref_list(struct ref_list *list)
+{
+       int psize, qsize, last_merge_count, cmp;
+       struct ref_list *p, *q, *l, *e;
+       struct ref_list *new_list = list;
+       int k = 1;
+       int merge_count = 0;
+
+       if (!list)
+               return list;
+
+       do {
+               last_merge_count = merge_count;
+               merge_count = 0;
+
+               psize = 0;
+
+               p = new_list;
+               q = new_list;
+               new_list = NULL;
+               l = NULL;
+
+               while (p) {
+                       merge_count++;
+
+                       while (psize < k && q->next) {
+                               q = q->next;
+                               psize++;
+                       }
+                       qsize = k;
+
+                       while ((psize > 0) || (qsize > 0 && q)) {
+                               if (qsize == 0 || !q) {
+                                       e = p;
+                                       p = p->next;
+                                       psize--;
+                               } else if (psize == 0) {
+                                       e = q;
+                                       q = q->next;
+                                       qsize--;
+                               } else {
+                                       cmp = strcmp(q->name, p->name);
+                                       if (cmp < 0) {
+                                               e = q;
+                                               q = q->next;
+                                               qsize--;
+                                       } else if (cmp > 0) {
+                                               e = p;
+                                               p = p->next;
+                                               psize--;
+                                       } else {
+                                               if (hashcmp(q->sha1, p->sha1))
+                                                       die("Duplicated ref, and SHA1s don't match: %s",
+                                                           q->name);
+                                               warning("Duplicated ref: %s", q->name);
+                                               e = q;
+                                               q = q->next;
+                                               qsize--;
+                                               free(e);
+                                               e = p;
+                                               p = p->next;
+                                               psize--;
+                                       }
+                               }
+
+                               e->next = NULL;
+
+                               if (l)
+                                       l->next = e;
+                               if (!new_list)
+                                       new_list = e;
+                               l = e;
+                       }
+
+                       p = q;
+               };
+
+               k = k * 2;
+       } while ((last_merge_count != merge_count) || (last_merge_count != 1));
+
+       return new_list;
 }
 
 /*
@@ -142,7 +210,7 @@ static void read_packed_refs(FILE *f, struct cached_refs *cached_refs)
                    !get_sha1_hex(refline + 1, sha1))
                        hashcpy(last->peeled, sha1);
        }
-       cached_refs->packed = list;
+       cached_refs->packed = sort_ref_list(list);
 }
 
 static struct ref_list *get_packed_refs(void)
@@ -201,7 +269,7 @@ static struct ref_list *get_ref_dir(const char *base, struct ref_list *list)
                free(ref);
                closedir(dir);
        }
-       return list;
+       return sort_ref_list(list);
 }
 
 static struct ref_list *get_loose_refs(void)
@@ -215,6 +283,86 @@ static struct ref_list *get_loose_refs(void)
 
 /* We allow "recursive" symbolic refs. Only within reason, though */
 #define MAXDEPTH 5
+#define MAXREFLEN (1024)
+
+static int resolve_gitlink_packed_ref(char *name, int pathlen, const char *refname, unsigned char *result)
+{
+       FILE *f;
+       struct cached_refs refs;
+       struct ref_list *ref;
+       int retval;
+
+       strcpy(name + pathlen, "packed-refs");
+       f = fopen(name, "r");
+       if (!f)
+               return -1;
+       read_packed_refs(f, &refs);
+       fclose(f);
+       ref = refs.packed;
+       retval = -1;
+       while (ref) {
+               if (!strcmp(ref->name, refname)) {
+                       retval = 0;
+                       memcpy(result, ref->sha1, 20);
+                       break;
+               }
+               ref = ref->next;
+       }
+       free_ref_list(refs.packed);
+       return retval;
+}
+
+static int resolve_gitlink_ref_recursive(char *name, int pathlen, const char *refname, unsigned char *result, int recursion)
+{
+       int fd, len = strlen(refname);
+       char buffer[128], *p;
+
+       if (recursion > MAXDEPTH || len > MAXREFLEN)
+               return -1;
+       memcpy(name + pathlen, refname, len+1);
+       fd = open(name, O_RDONLY);
+       if (fd < 0)
+               return resolve_gitlink_packed_ref(name, pathlen, refname, result);
+
+       len = read(fd, buffer, sizeof(buffer)-1);
+       close(fd);
+       if (len < 0)
+               return -1;
+       while (len && isspace(buffer[len-1]))
+               len--;
+       buffer[len] = 0;
+
+       /* Was it a detached head or an old-fashioned symlink? */
+       if (!get_sha1_hex(buffer, result))
+               return 0;
+
+       /* Symref? */
+       if (strncmp(buffer, "ref:", 4))
+               return -1;
+       p = buffer + 4;
+       while (isspace(*p))
+               p++;
+
+       return resolve_gitlink_ref_recursive(name, pathlen, p, result, recursion+1);
+}
+
+int resolve_gitlink_ref(const char *path, const char *refname, unsigned char *result)
+{
+       int len = strlen(path), retval;
+       char *gitdir;
+
+       while (len && path[len-1] == '/')
+               len--;
+       if (!len)
+               return -1;
+       gitdir = xmalloc(len + MAXREFLEN + 8);
+       memcpy(gitdir, path, len);
+       memcpy(gitdir + len, "/.git/", 7);
+
+       retval = resolve_gitlink_ref_recursive(gitdir, len+6, refname, result, 0);
+       free(gitdir);
+       return retval;
+}
 
 const char *resolve_ref(const char *ref, unsigned char *sha1, int reading, int *flag)
 {
diff --git a/refs.h b/refs.h
index acedffc0e412e1de6137d665a7c6b32f58b1c20b..f61f6d934e80b21432f93cd3b9f138770a9d2b86 100644 (file)
--- a/refs.h
+++ b/refs.h
@@ -60,4 +60,7 @@ extern int check_ref_format(const char *target);
 /** rename ref, return 0 on success **/
 extern int rename_ref(const char *oldref, const char *newref, const char *logmsg);
 
+/** resolve ref in nested "gitlink" repository */
+extern int resolve_gitlink_ref(const char *name, const char *refname, unsigned char *result);
+
 #endif /* REFS_H */
index 486393cb0835ce70f685d665b916e1b67974f184..0125d41136871ad2cd07daaaabf40bd2f902a44f 100644 (file)
@@ -8,6 +8,7 @@
 #include "revision.h"
 #include "grep.h"
 #include "reflog-walk.h"
+#include "patch-ids.h"
 
 static char *path_name(struct name_path *path, const char *name)
 {
@@ -114,10 +115,15 @@ void mark_parents_uninteresting(struct commit *commit)
 }
 
 void add_pending_object(struct rev_info *revs, struct object *obj, const char *name)
+{
+       add_pending_object_with_mode(revs, obj, name, S_IFINVALID);
+}
+
+void add_pending_object_with_mode(struct rev_info *revs, struct object *obj, const char *name, unsigned mode)
 {
        if (revs->no_walk && (obj->flags & UNINTERESTING))
                die("object ranges do not make sense when not walking revisions");
-       add_object_array(obj, name, &revs->pending);
+       add_object_array_with_mode(obj, name, &revs->pending, mode);
        if (revs->reflog_info && obj->type == OBJ_COMMIT)
                add_reflog_for_walk(revs->reflog_info,
                                (struct commit *)obj, name);
@@ -312,7 +318,10 @@ static void try_to_simplify_commit(struct rev_info *revs, struct commit *commit)
        while ((parent = *pp) != NULL) {
                struct commit *p = parent->item;
 
-               parse_commit(p);
+               if (parse_commit(p) < 0)
+                       die("cannot simplify commit %s (because of %s)",
+                           sha1_to_hex(commit->object.sha1),
+                           sha1_to_hex(p->object.sha1));
                switch (rev_compare_tree(revs, p->tree, commit->tree)) {
                case REV_TREE_SAME:
                        tree_same = 1;
@@ -341,7 +350,10 @@ static void try_to_simplify_commit(struct rev_info *revs, struct commit *commit)
                                 * IOW, we pretend this parent is a
                                 * "root" commit.
                                 */
-                               parse_commit(p);
+                               if (parse_commit(p) < 0)
+                                       die("cannot simplify commit %s (invalid %s)",
+                                           sha1_to_hex(commit->object.sha1),
+                                           sha1_to_hex(p->object.sha1));
                                p->parents = NULL;
                        }
                /* fallthrough */
@@ -356,14 +368,14 @@ static void try_to_simplify_commit(struct rev_info *revs, struct commit *commit)
                commit->object.flags |= TREECHANGE;
 }
 
-static void add_parents_to_list(struct rev_info *revs, struct commit *commit, struct commit_list **list)
+static int add_parents_to_list(struct rev_info *revs, struct commit *commit, struct commit_list **list)
 {
        struct commit_list *parent = commit->parents;
        unsigned left_flag;
        int add, rest;
 
        if (commit->object.flags & ADDED)
-               return;
+               return 0;
        commit->object.flags |= ADDED;
 
        /*
@@ -382,7 +394,8 @@ static void add_parents_to_list(struct rev_info *revs, struct commit *commit, st
                while (parent) {
                        struct commit *p = parent->item;
                        parent = parent->next;
-                       parse_commit(p);
+                       if (parse_commit(p) < 0)
+                               return -1;
                        p->object.flags |= UNINTERESTING;
                        if (p->parents)
                                mark_parents_uninteresting(p);
@@ -391,7 +404,7 @@ static void add_parents_to_list(struct rev_info *revs, struct commit *commit, st
                        p->object.flags |= SEEN;
                        insert_by_date(p, list);
                }
-               return;
+               return 0;
        }
 
        /*
@@ -403,7 +416,7 @@ static void add_parents_to_list(struct rev_info *revs, struct commit *commit, st
                revs->prune_fn(revs, commit);
 
        if (revs->no_walk)
-               return;
+               return 0;
 
        left_flag = (commit->object.flags & SYMMETRIC_LEFT);
 
@@ -412,7 +425,8 @@ static void add_parents_to_list(struct rev_info *revs, struct commit *commit, st
                struct commit *p = parent->item;
 
                parent = parent->next;
-               parse_commit(p);
+               if (parse_commit(p) < 0)
+                       return -1;
                p->object.flags |= left_flag;
                if (p->object.flags & SEEN)
                        continue;
@@ -420,9 +434,90 @@ static void add_parents_to_list(struct rev_info *revs, struct commit *commit, st
                if (add)
                        insert_by_date(p, list);
        }
+       return 0;
 }
 
-static void limit_list(struct rev_info *revs)
+static void cherry_pick_list(struct commit_list *list)
+{
+       struct commit_list *p;
+       int left_count = 0, right_count = 0;
+       int left_first;
+       struct patch_ids ids;
+
+       /* First count the commits on the left and on the right */
+       for (p = list; p; p = p->next) {
+               struct commit *commit = p->item;
+               unsigned flags = commit->object.flags;
+               if (flags & BOUNDARY)
+                       ;
+               else if (flags & SYMMETRIC_LEFT)
+                       left_count++;
+               else
+                       right_count++;
+       }
+
+       left_first = left_count < right_count;
+       init_patch_ids(&ids);
+
+       /* Compute patch-ids for one side */
+       for (p = list; p; p = p->next) {
+               struct commit *commit = p->item;
+               unsigned flags = commit->object.flags;
+
+               if (flags & BOUNDARY)
+                       continue;
+               /*
+                * If we have fewer left, left_first is set and we omit
+                * commits on the right branch in this loop.  If we have
+                * fewer right, we skip the left ones.
+                */
+               if (left_first != !!(flags & SYMMETRIC_LEFT))
+                       continue;
+               commit->util = add_commit_patch_id(commit, &ids);
+       }
+
+       /* Check the other side */
+       for (p = list; p; p = p->next) {
+               struct commit *commit = p->item;
+               struct patch_id *id;
+               unsigned flags = commit->object.flags;
+
+               if (flags & BOUNDARY)
+                       continue;
+               /*
+                * If we have fewer left, left_first is set and we omit
+                * commits on the left branch in this loop.
+                */
+               if (left_first == !!(flags & SYMMETRIC_LEFT))
+                       continue;
+
+               /*
+                * Have we seen the same patch id?
+                */
+               id = has_commit_patch_id(commit, &ids);
+               if (!id)
+                       continue;
+               id->seen = 1;
+               commit->object.flags |= SHOWN;
+       }
+
+       /* Now check the original side for seen ones */
+       for (p = list; p; p = p->next) {
+               struct commit *commit = p->item;
+               struct patch_id *ent;
+
+               ent = commit->util;
+               if (!ent)
+                       continue;
+               if (ent->seen)
+                       commit->object.flags |= SHOWN;
+               commit->util = NULL;
+       }
+
+       free_patch_ids(&ids);
+}
+
+static int limit_list(struct rev_info *revs)
 {
        struct commit_list *list = revs->commits;
        struct commit_list *newlist = NULL;
@@ -438,7 +533,8 @@ static void limit_list(struct rev_info *revs)
 
                if (revs->max_age != -1 && (commit->date < revs->max_age))
                        obj->flags |= UNINTERESTING;
-               add_parents_to_list(revs, commit, &list);
+               if (add_parents_to_list(revs, commit, &list) < 0)
+                       return -1;
                if (obj->flags & UNINTERESTING) {
                        mark_parents_uninteresting(commit);
                        if (everybody_uninteresting(list))
@@ -449,7 +545,11 @@ static void limit_list(struct rev_info *revs)
                        continue;
                p = &commit_list_insert(commit, p)->next;
        }
+       if (revs->cherry_pick)
+               cherry_pick_list(newlist);
+
        revs->commits = newlist;
+       return 0;
 }
 
 struct all_refs_cb {
@@ -567,6 +667,7 @@ void init_revisions(struct rev_info *revs, const char *prefix)
        revs->min_age = -1;
        revs->skip_count = -1;
        revs->max_count = -1;
+       revs->subject_prefix = "PATCH";
 
        revs->prune_fn = NULL;
        revs->prune_data = NULL;
@@ -638,6 +739,7 @@ int handle_revision_arg(const char *arg, struct rev_info *revs,
                        int flags,
                        int cant_be_filename)
 {
+       unsigned mode;
        char *dotdot;
        struct object *object;
        unsigned char sha1[20];
@@ -711,12 +813,12 @@ int handle_revision_arg(const char *arg, struct rev_info *revs,
                local_flags = UNINTERESTING;
                arg++;
        }
-       if (get_sha1(arg, sha1))
+       if (get_sha1_with_mode(arg, sha1, &mode))
                return -1;
        if (!cant_be_filename)
                verify_non_filename(revs->prefix, arg);
        object = get_reference(revs, arg, sha1, flags ^ local_flags);
-       add_pending_object(revs, object, arg);
+       add_pending_object_with_mode(revs, object, arg, mode);
        return 0;
 }
 
@@ -913,6 +1015,10 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch
                                revs->left_right = 1;
                                continue;
                        }
+                       if (!strcmp(arg, "--cherry-pick")) {
+                               revs->cherry_pick = 1;
+                               continue;
+                       }
                        if (!strcmp(arg, "--objects")) {
                                revs->tag_objects = 1;
                                revs->tree_objects = 1;
@@ -1016,7 +1122,18 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch
                                continue;
                        }
                        if (!strcmp(arg, "--relative-date")) {
-                               revs->relative_date = 1;
+                               revs->date_mode = DATE_RELATIVE;
+                               continue;
+                       }
+                       if (!strncmp(arg, "--date=", 7)) {
+                               if (!strcmp(arg + 7, "relative"))
+                                       revs->date_mode = DATE_RELATIVE;
+                               else if (!strcmp(arg + 7, "local"))
+                                       revs->date_mode = DATE_LOCAL;
+                               else if (!strcmp(arg + 7, "default"))
+                                       revs->date_mode = DATE_NORMAL;
+                               else
+                                       die("unknown date format %s", arg);
                                continue;
                        }
 
@@ -1088,10 +1205,11 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch
        if (def && !revs->pending.nr) {
                unsigned char sha1[20];
                struct object *object;
-               if (get_sha1(def, sha1))
+               unsigned mode;
+               if (get_sha1_with_mode(def, sha1, &mode))
                        die("bad default revision '%s'", def);
                object = get_reference(revs, def, sha1, 0);
-               add_pending_object(revs, object, def);
+               add_pending_object_with_mode(revs, object, def, mode);
        }
 
        if (revs->topo_order)
@@ -1120,7 +1238,7 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch
        return left;
 }
 
-void prepare_revision_walk(struct rev_info *revs)
+int prepare_revision_walk(struct rev_info *revs)
 {
        int nr = revs->pending.nr;
        struct object_array_entry *e, *list;
@@ -1142,42 +1260,57 @@ void prepare_revision_walk(struct rev_info *revs)
        free(list);
 
        if (revs->no_walk)
-               return;
+               return 0;
        if (revs->limited)
-               limit_list(revs);
+               if (limit_list(revs) < 0)
+                       return -1;
        if (revs->topo_order)
                sort_in_topological_order_fn(&revs->commits, revs->lifo,
                                             revs->topo_setter,
                                             revs->topo_getter);
+       return 0;
 }
 
-static int rewrite_one(struct rev_info *revs, struct commit **pp)
+enum rewrite_result {
+       rewrite_one_ok,
+       rewrite_one_noparents,
+       rewrite_one_error,
+};
+
+static enum rewrite_result rewrite_one(struct rev_info *revs, struct commit **pp)
 {
        for (;;) {
                struct commit *p = *pp;
                if (!revs->limited)
-                       add_parents_to_list(revs, p, &revs->commits);
+                       if (add_parents_to_list(revs, p, &revs->commits) < 0)
+                               return rewrite_one_error;
                if (p->parents && p->parents->next)
-                       return 0;
+                       return rewrite_one_ok;
                if (p->object.flags & (TREECHANGE | UNINTERESTING))
-                       return 0;
+                       return rewrite_one_ok;
                if (!p->parents)
-                       return -1;
+                       return rewrite_one_noparents;
                *pp = p->parents->item;
        }
 }
 
-static void rewrite_parents(struct rev_info *revs, struct commit *commit)
+static int rewrite_parents(struct rev_info *revs, struct commit *commit)
 {
        struct commit_list **pp = &commit->parents;
        while (*pp) {
                struct commit_list *parent = *pp;
-               if (rewrite_one(revs, &parent->item) < 0) {
+               switch (rewrite_one(revs, &parent->item)) {
+               case rewrite_one_ok:
+                       break;
+               case rewrite_one_noparents:
                        *pp = parent->next;
                        continue;
+               case rewrite_one_error:
+                       return -1;
                }
                pp = &parent->next;
        }
+       return 0;
 }
 
 static int commit_match(struct commit *commit, struct rev_info *opt)
@@ -1213,7 +1346,8 @@ static struct commit *get_revision_1(struct rev_info *revs)
                        if (revs->max_age != -1 &&
                            (commit->date < revs->max_age))
                                continue;
-                       add_parents_to_list(revs, commit, &revs->commits);
+                       if (add_parents_to_list(revs, commit, &revs->commits) < 0)
+                               return NULL;
                }
                if (commit->object.flags & SHOWN)
                        continue;
@@ -1241,8 +1375,8 @@ static struct commit *get_revision_1(struct rev_info *revs)
                                if (!commit->parents || !commit->parents->next)
                                        continue;
                        }
-                       if (revs->parents)
-                               rewrite_parents(revs, commit);
+                       if (revs->parents && rewrite_parents(revs, commit) < 0)
+                               return NULL;
                }
                return commit;
        } while (revs->commits);
index 55e6b531ce3e5838f988ca1896484333e795f192..2845167746ce4632894fce88e840df60513933aa 100644 (file)
@@ -47,6 +47,7 @@ struct rev_info {
                        left_right:1,
                        parents:1,
                        reverse:1,
+                       cherry_pick:1,
                        first_parent_only:1;
 
        /* Diff flags */
@@ -62,8 +63,8 @@ struct rev_info {
 
        /* Format info */
        unsigned int    shown_one:1,
-                       abbrev_commit:1,
-                       relative_date:1;
+                       abbrev_commit:1;
+       enum date_mode date_mode;
 
        const char **ignore_packed; /* pretend objects in these are unpacked */
        int num_ignore_packed;
@@ -78,6 +79,7 @@ struct rev_info {
        const char      *add_signoff;
        const char      *extra_headers;
        const char      *log_reencode;
+       const char      *subject_prefix;
        int             no_inline;
 
        /* Filter by commit log message */
@@ -111,7 +113,7 @@ extern void init_revisions(struct rev_info *revs, const char *prefix);
 extern int setup_revisions(int argc, const char **argv, struct rev_info *revs, const char *def);
 extern int handle_revision_arg(const char *arg, struct rev_info *revs,int flags,int cant_be_filename);
 
-extern void prepare_revision_walk(struct rev_info *revs);
+extern int prepare_revision_walk(struct rev_info *revs);
 extern struct commit *get_revision(struct rev_info *revs);
 
 extern void mark_parents_uninteresting(struct commit *commit);
@@ -129,5 +131,6 @@ extern void add_object(struct object *obj,
                       const char *name);
 
 extern void add_pending_object(struct rev_info *revs, struct object *obj, const char *name);
+extern void add_pending_object_with_mode(struct rev_info *revs, struct object *obj, const char *name, unsigned mode);
 
 #endif
index 523417027a1785d4ab5b729d03ba794efc1bb4d4..be991ed22acb0c84141474360f345d51ccc594be 100644 (file)
@@ -13,6 +13,7 @@
 #include "commit.h"
 #include "tag.h"
 #include "tree.h"
+#include "refs.h"
 
 #ifndef O_NOATIME
 #if defined(__linux__) && (defined(__i386__) || defined(__PPC__))
@@ -437,7 +438,7 @@ static int check_packed_git_idx(const char *path,  struct packed_git *p)
        void *idx_map;
        struct pack_idx_header *hdr;
        size_t idx_size;
-       uint32_t nr, i, *index;
+       uint32_t version, nr, i, *index;
        int fd = open(path, O_RDONLY);
        struct stat st;
 
@@ -455,21 +456,23 @@ static int check_packed_git_idx(const char *path,  struct packed_git *p)
        idx_map = xmmap(NULL, idx_size, PROT_READ, MAP_PRIVATE, fd, 0);
        close(fd);
 
-       /* a future index format would start with this, as older git
-        * binaries would fail the non-monotonic index check below.
-        * give a nicer warning to the user if we can.
-        */
        hdr = idx_map;
        if (hdr->idx_signature == htonl(PACK_IDX_SIGNATURE)) {
-               munmap(idx_map, idx_size);
-               return error("index file %s is a newer version"
-                       " and is not supported by this binary"
-                       " (try upgrading GIT to a newer version)",
-                       path);
-       }
+               version = ntohl(hdr->idx_version);
+               if (version < 2 || version > 2) {
+                       munmap(idx_map, idx_size);
+                       return error("index file %s is version %d"
+                                    " and is not supported by this binary"
+                                    " (try upgrading GIT to a newer version)",
+                                    path, version);
+               }
+       } else
+               version = 1;
 
        nr = 0;
        index = idx_map;
+       if (version > 1)
+               index += 2;  /* skip index header */
        for (i = 0; i < 256; i++) {
                uint32_t n = ntohl(index[i]);
                if (n < nr) {
@@ -479,21 +482,51 @@ static int check_packed_git_idx(const char *path,  struct packed_git *p)
                nr = n;
        }
 
-       /*
-        * Total size:
-        *  - 256 index entries 4 bytes each
-        *  - 24-byte entries * nr (20-byte sha1 + 4-byte offset)
-        *  - 20-byte SHA1 of the packfile
-        *  - 20-byte SHA1 file checksum
-        */
-       if (idx_size != 4*256 + nr * 24 + 20 + 20) {
-               munmap(idx_map, idx_size);
-               return error("wrong index file size in %s", path);
+       if (version == 1) {
+               /*
+                * Total size:
+                *  - 256 index entries 4 bytes each
+                *  - 24-byte entries * nr (20-byte sha1 + 4-byte offset)
+                *  - 20-byte SHA1 of the packfile
+                *  - 20-byte SHA1 file checksum
+                */
+               if (idx_size != 4*256 + nr * 24 + 20 + 20) {
+                       munmap(idx_map, idx_size);
+                       return error("wrong index file size in %s", path);
+               }
+       } else if (version == 2) {
+               /*
+                * Minimum size:
+                *  - 8 bytes of header
+                *  - 256 index entries 4 bytes each
+                *  - 20-byte sha1 entry * nr
+                *  - 4-byte crc entry * nr
+                *  - 4-byte offset entry * nr
+                *  - 20-byte SHA1 of the packfile
+                *  - 20-byte SHA1 file checksum
+                * And after the 4-byte offset table might be a
+                * variable sized table containing 8-byte entries
+                * for offsets larger than 2^31.
+                */
+               unsigned long min_size = 8 + 4*256 + nr*(20 + 4 + 4) + 20 + 20;
+               if (idx_size < min_size || idx_size > min_size + (nr - 1)*8) {
+                       munmap(idx_map, idx_size);
+                       return error("wrong index file size in %s", path);
+               }
+               if (idx_size != min_size) {
+                       /* make sure we can deal with large pack offsets */
+                       off_t x = 0x7fffffffUL, y = 0xffffffffUL;
+                       if (x > (x + 1) || y > (y + 1)) {
+                               munmap(idx_map, idx_size);
+                               return error("pack too large for current definition of off_t in %s", path);
+                       }
+               }
        }
 
-       p->index_version = 1;
+       p->index_version = version;
        p->index_data = idx_map;
        p->index_size = idx_size;
+       p->num_objects = nr;
        return 0;
 }
 
@@ -605,11 +638,11 @@ static int open_packed_git_1(struct packed_git *p)
                        p->pack_name, ntohl(hdr.hdr_version));
 
        /* Verify the pack matches its index. */
-       if (num_packed_objects(p) != ntohl(hdr.hdr_entries))
+       if (p->num_objects != ntohl(hdr.hdr_entries))
                return error("packfile %s claims to have %u objects"
-                       " while index size indicates %u objects",
-                       p->pack_name, ntohl(hdr.hdr_entries),
-                       num_packed_objects(p));
+                            " while index indicates %u objects",
+                            p->pack_name, ntohl(hdr.hdr_entries),
+                            p->num_objects);
        if (lseek(p->pack_fd, p->pack_size - sizeof(sha1), SEEK_SET) == -1)
                return error("end of packfile %s is unavailable", p->pack_name);
        if (read_in_full(p->pack_fd, sha1, sizeof(sha1)) != sizeof(sha1))
@@ -1128,6 +1161,43 @@ static void *unpack_sha1_file(void *map, unsigned long mapsize, enum object_type
        return unpack_sha1_rest(&stream, hdr, *size, sha1);
 }
 
+unsigned long get_size_from_delta(struct packed_git *p,
+                                 struct pack_window **w_curs,
+                                 off_t curpos)
+{
+       const unsigned char *data;
+       unsigned char delta_head[20], *in;
+       z_stream stream;
+       int st;
+
+       memset(&stream, 0, sizeof(stream));
+       stream.next_out = delta_head;
+       stream.avail_out = sizeof(delta_head);
+
+       inflateInit(&stream);
+       do {
+               in = use_pack(p, w_curs, curpos, &stream.avail_in);
+               stream.next_in = in;
+               st = inflate(&stream, Z_FINISH);
+               curpos += stream.next_in - in;
+       } while ((st == Z_OK || st == Z_BUF_ERROR) &&
+                stream.total_out < sizeof(delta_head));
+       inflateEnd(&stream);
+       if ((st != Z_STREAM_END) && stream.total_out != sizeof(delta_head))
+               die("delta data unpack-initial failed");
+
+       /* Examine the initial part of the delta to figure out
+        * the result size.
+        */
+       data = delta_head;
+
+       /* ignore base size */
+       get_delta_hdr_size(&data, delta_head+sizeof(delta_head));
+
+       /* Read the result size */
+       return get_delta_hdr_size(&data, delta_head+sizeof(delta_head));
+}
+
 static off_t get_delta_base(struct packed_git *p,
                                    struct pack_window **w_curs,
                                    off_t *curpos,
@@ -1149,7 +1219,7 @@ static off_t get_delta_base(struct packed_git *p,
                base_offset = c & 127;
                while (c & 128) {
                        base_offset += 1;
-                       if (!base_offset || base_offset & ~(~0UL >> 7))
+                       if (!base_offset || MSB(base_offset, 7))
                                die("offset value overflow for delta base object");
                        c = base_info[used++];
                        base_offset = (base_offset << 7) + (c & 127);
@@ -1191,40 +1261,8 @@ static int packed_delta_info(struct packed_git *p,
         * based on a base with a wrong size.  This saves tons of
         * inflate() calls.
         */
-       if (sizep) {
-               const unsigned char *data;
-               unsigned char delta_head[20], *in;
-               z_stream stream;
-               int st;
-
-               memset(&stream, 0, sizeof(stream));
-               stream.next_out = delta_head;
-               stream.avail_out = sizeof(delta_head);
-
-               inflateInit(&stream);
-               do {
-                       in = use_pack(p, w_curs, curpos, &stream.avail_in);
-                       stream.next_in = in;
-                       st = inflate(&stream, Z_FINISH);
-                       curpos += stream.next_in - in;
-               } while ((st == Z_OK || st == Z_BUF_ERROR)
-                       && stream.total_out < sizeof(delta_head));
-               inflateEnd(&stream);
-               if ((st != Z_STREAM_END) &&
-                   stream.total_out != sizeof(delta_head))
-                       die("delta data unpack-initial failed");
-
-               /* Examine the initial part of the delta to figure out
-                * the result size.
-                */
-               data = delta_head;
-
-               /* ignore base size */
-               get_delta_hdr_size(&data, delta_head+sizeof(delta_head));
-
-               /* Read the result size */
-               *sizep = get_delta_hdr_size(&data, delta_head+sizeof(delta_head));
-       }
+       if (sizep)
+               *sizep = get_size_from_delta(p, w_curs, curpos);
 
        return type;
 }
@@ -1526,38 +1564,60 @@ void *unpack_entry(struct packed_git *p, off_t obj_offset,
        return data;
 }
 
-uint32_t num_packed_objects(const struct packed_git *p)
+const unsigned char *nth_packed_object_sha1(const struct packed_git *p,
+                                           uint32_t n)
 {
-       /* See check_packed_git_idx() */
-       return (uint32_t)((p->index_size - 20 - 20 - 4*256) / 24);
+       const unsigned char *index = p->index_data;
+       if (n >= p->num_objects)
+               return NULL;
+       index += 4 * 256;
+       if (p->index_version == 1) {
+               return index + 24 * n + 4;
+       } else {
+               index += 8;
+               return index + 20 * n;
+       }
 }
 
-int nth_packed_object_sha1(const struct packed_git *p, uint32_t n,
-                          unsigned char* sha1)
+static off_t nth_packed_object_offset(const struct packed_git *p, uint32_t n)
 {
        const unsigned char *index = p->index_data;
        index += 4 * 256;
-       if (num_packed_objects(p) <= n)
-               return -1;
-       hashcpy(sha1, index + 24 * n + 4);
-       return 0;
+       if (p->index_version == 1) {
+               return ntohl(*((uint32_t *)(index + 24 * n)));
+       } else {
+               uint32_t off;
+               index += 8 + p->num_objects * (20 + 4);
+               off = ntohl(*((uint32_t *)(index + 4 * n)));
+               if (!(off & 0x80000000))
+                       return off;
+               index += p->num_objects * 4 + (off & 0x7fffffff) * 8;
+               return (((uint64_t)ntohl(*((uint32_t *)(index + 0)))) << 32) |
+                                  ntohl(*((uint32_t *)(index + 4)));
+       }
 }
 
 off_t find_pack_entry_one(const unsigned char *sha1,
                                  struct packed_git *p)
 {
        const uint32_t *level1_ofs = p->index_data;
-       int hi = ntohl(level1_ofs[*sha1]);
-       int lo = ((*sha1 == 0x0) ? 0 : ntohl(level1_ofs[*sha1 - 1]));
        const unsigned char *index = p->index_data;
+       unsigned hi, lo;
 
+       if (p->index_version > 1) {
+               level1_ofs += 2;
+               index += 8;
+       }
        index += 4 * 256;
+       hi = ntohl(level1_ofs[*sha1]);
+       lo = ((*sha1 == 0x0) ? 0 : ntohl(level1_ofs[*sha1 - 1]));
 
        do {
-               int mi = (lo + hi) / 2;
-               int cmp = hashcmp(index + 24 * mi + 4, sha1);
+               unsigned mi = (lo + hi) / 2;
+               unsigned x = (p->index_version > 1) ? (mi * 20) : (mi * 24 + 4);
+               int cmp = hashcmp(index + x, sha1);
                if (!cmp)
-                       return ntohl(*((uint32_t *)((char *)index + (24 * mi))));
+                       return nth_packed_object_offset(p, mi);
                if (cmp > 0)
                        hi = mi;
                else
@@ -2216,7 +2276,7 @@ int read_pipe(int fd, char** return_buf, unsigned long* return_size)
 {
        char* buf = *return_buf;
        unsigned long size = *return_size;
-       int iret;
+       ssize_t iret;
        unsigned long off = 0;
 
        do {
@@ -2278,10 +2338,9 @@ int index_fd(unsigned char *sha1, int fd, struct stat *st, int write_object,
         */
        if ((type == OBJ_BLOB) && S_ISREG(st->st_mode)) {
                unsigned long nsize = size;
-               char *nbuf = buf;
-               if (convert_to_git(path, &nbuf, &nsize)) {
-                       if (size)
-                               munmap(buf, size);
+               char *nbuf = convert_to_git(path, buf, &nsize);
+               if (nbuf) {
+                       munmap(buf, size);
                        size = nsize;
                        buf = nbuf;
                        re_allocated = 1;
@@ -2333,6 +2392,8 @@ int index_path(unsigned char *sha1, const char *path, struct stat *st, int write
                                     path);
                free(target);
                break;
+       case S_IFDIR:
+               return resolve_gitlink_ref(path, "HEAD", sha1);
        default:
                return error("%s: unsupported file type", path);
        }
index bede0e5b0659db31a1dfdaab3d17627199150ea7..55f25a2d3b49b1d565a9840b7dcfb2e22687cdd3 100644 (file)
@@ -71,19 +71,19 @@ static int match_sha(unsigned len, const unsigned char *a, const unsigned char *
 static int find_short_packed_object(int len, const unsigned char *match, unsigned char *sha1)
 {
        struct packed_git *p;
-       unsigned char found_sha1[20];
+       const unsigned char *found_sha1 = NULL;
        int found = 0;
 
        prepare_packed_git();
        for (p = packed_git; p && found < 2; p = p->next) {
-               uint32_t num = num_packed_objects(p);
+               uint32_t num = p->num_objects;
                uint32_t first = 0, last = num;
                while (first < last) {
                        uint32_t mid = (first + last) / 2;
-                       unsigned char now[20];
+                       const unsigned char *now;
                        int cmp;
 
-                       nth_packed_object_sha1(p, mid, now);
+                       now = nth_packed_object_sha1(p, mid);
                        cmp = hashcmp(match, now);
                        if (!cmp) {
                                first = mid;
@@ -96,14 +96,14 @@ static int find_short_packed_object(int len, const unsigned char *match, unsigne
                        last = mid;
                }
                if (first < num) {
-                       unsigned char now[20], next[20];
-                       nth_packed_object_sha1(p, first, now);
+                       const unsigned char *now, *next;
+                      now = nth_packed_object_sha1(p, first);
                        if (match_sha(len, match, now)) {
-                               if (nth_packed_object_sha1(p, first+1, next) ||
-                                   !match_sha(len, match, next)) {
+                               next = nth_packed_object_sha1(p, first+1);
+                              if (!next|| !match_sha(len, match, next)) {
                                        /* unique within this pack */
                                        if (!found) {
-                                               hashcpy(found_sha1, now);
+                                               found_sha1 = now;
                                                found++;
                                        }
                                        else if (hashcmp(found_sha1, now)) {
@@ -643,11 +643,17 @@ static int get_sha1_oneline(const char *prefix, unsigned char *sha1)
  */
 int get_sha1(const char *name, unsigned char *sha1)
 {
-       int ret, bracket_depth;
        unsigned unused;
+       return get_sha1_with_mode(name, sha1, &unused);
+}
+
+int get_sha1_with_mode(const char *name, unsigned char *sha1, unsigned *mode)
+{
+       int ret, bracket_depth;
        int namelen = strlen(name);
        const char *cp;
 
+       *mode = S_IFINVALID;
        prepare_alt_odb();
        ret = get_sha1_1(name, namelen, sha1);
        if (!ret)
@@ -685,6 +691,7 @@ int get_sha1(const char *name, unsigned char *sha1)
                                break;
                        if (ce_stage(ce) == stage) {
                                hashcpy(sha1, ce->sha1);
+                               *mode = ntohl(ce->ce_mode);
                                return 0;
                        }
                        pos++;
@@ -703,7 +710,7 @@ int get_sha1(const char *name, unsigned char *sha1)
                unsigned char tree_sha1[20];
                if (!get_sha1_1(name, cp-name, tree_sha1))
                        return get_tree_entry(tree_sha1, cp+1, sha1,
-                                             &unused);
+                                             mode);
        }
        return ret;
 }
index a30a2de5d13931c590169cf30bd7004e23f2df1b..57ed9e87b7fca6c899d4c23d709a97dabce28106 100644 (file)
@@ -1,14 +1,26 @@
 #include "cache.h"
+#include "pack.h"
 
 int main(int argc, char **argv)
 {
        int i;
        unsigned nr;
-       unsigned int entry[6];
+       unsigned int version;
        static unsigned int top_index[256];
 
-       if (fread(top_index, sizeof(top_index), 1, stdin) != 1)
-               die("unable to read index");
+       if (fread(top_index, 2 * 4, 1, stdin) != 1)
+               die("unable to read header");
+       if (top_index[0] == htonl(PACK_IDX_SIGNATURE)) {
+               version = ntohl(top_index[1]);
+               if (version < 2 || version > 2)
+                       die("unknown index version");
+               if (fread(top_index, 256 * 4, 1, stdin) != 1)
+                       die("unable to read index");
+       } else {
+               version = 1;
+               if (fread(&top_index[2], 254 * 4, 1, stdin) != 1)
+                       die("unable to read index");
+       }
        nr = 0;
        for (i = 0; i < 256; i++) {
                unsigned n = ntohl(top_index[i]);
@@ -16,13 +28,51 @@ int main(int argc, char **argv)
                        die("corrupt index file");
                nr = n;
        }
-       for (i = 0; i < nr; i++) {
-               unsigned offset;
+       if (version == 1) {
+               for (i = 0; i < nr; i++) {
+                       unsigned int offset, entry[6];
 
-               if (fread(entry, 24, 1, stdin) != 1)
-                       die("unable to read entry %u/%u", i, nr);
-               offset = ntohl(entry[0]);
-               printf("%u %s\n", offset, sha1_to_hex((void *)(entry+1)));
+                       if (fread(entry, 4 + 20, 1, stdin) != 1)
+                               die("unable to read entry %u/%u", i, nr);
+                       offset = ntohl(entry[0]);
+                       printf("%u %s\n", offset, sha1_to_hex((void *)(entry+1)));
+               }
+       } else {
+               unsigned off64_nr = 0;
+               struct {
+                       unsigned char sha1[20];
+                       uint32_t crc;
+                       uint32_t off;
+               } *entries = xmalloc(nr * sizeof(entries[0]));
+               for (i = 0; i < nr; i++)
+                       if (fread(entries[i].sha1, 20, 1, stdin) != 1)
+                               die("unable to read sha1 %u/%u", i, nr);
+               for (i = 0; i < nr; i++)
+                       if (fread(&entries[i].crc, 4, 1, stdin) != 1)
+                               die("unable to read crc %u/%u", i, nr);
+               for (i = 0; i < nr; i++)
+                       if (fread(&entries[i].off, 4, 1, stdin) != 1)
+                               die("unable to read 32b offset %u/%u", i, nr);
+               for (i = 0; i < nr; i++) {
+                       uint64_t offset;
+                       uint32_t off = ntohl(entries[i].off);
+                       if (!(off & 0x80000000)) {
+                               offset = off;
+                       } else {
+                               uint32_t off64[2];
+                               if ((off & 0x7fffffff) != off64_nr)
+                                       die("inconsistent 64b offset index");
+                               if (fread(off64, 8, 1, stdin) != 1)
+                                       die("unable to read 64b offset %u", off64_nr);
+                               offset = (((uint64_t)ntohl(off64[0])) << 32) |
+                                                    ntohl(off64[1]);
+                               off64_nr++;
+                       }
+                       printf("%llu %s (%08x)\n", (unsigned long long) offset,
+                              sha1_to_hex(entries[i].sha1),
+                              ntohl(entries[i].crc));
+               }
+               free(entries);
        }
        return 0;
 }
index 2f045727875707198dd1763d75c9e8c7406e9042..498d41e19b5756c82ef8e0ead779f40812f84227 100644 (file)
@@ -86,7 +86,7 @@ static int serve_ref(int fd_in, int fd_out)
 
 static void service(int fd_in, int fd_out) {
        char type;
-       int retval;
+       ssize_t retval;
        do {
                retval = xread(fd_in, &type, 1);
                if (retval < 1) {
diff --git a/symlinks.c b/symlinks.c
new file mode 100644 (file)
index 0000000..be9ace6
--- /dev/null
@@ -0,0 +1,48 @@
+#include "cache.h"
+
+int has_symlink_leading_path(const char *name, char *last_symlink)
+{
+       char path[PATH_MAX];
+       const char *sp, *ep;
+       char *dp;
+
+       sp = name;
+       dp = path;
+
+       if (last_symlink && *last_symlink) {
+               size_t last_len = strlen(last_symlink);
+               size_t len = strlen(name);
+               if (last_len < len &&
+                   !strncmp(name, last_symlink, last_len) &&
+                   name[last_len] == '/')
+                       return 1;
+               *last_symlink = '\0';
+       }
+
+       while (1) {
+               size_t len;
+               struct stat st;
+
+               ep = strchr(sp, '/');
+               if (!ep)
+                       break;
+               len = ep - sp;
+               if (PATH_MAX <= dp + len - path + 2)
+                       return 0; /* new name is longer than that??? */
+               memcpy(dp, sp, len);
+               dp[len] = 0;
+
+               if (lstat(path, &st))
+                       return 0;
+               if (S_ISLNK(st.st_mode)) {
+                       if (last_symlink)
+                               strcpy(last_symlink, path);
+                       return 1;
+               }
+
+               dp[len++] = '/';
+               dp = dp + len;
+               sp = ep + 1;
+       }
+       return 0;
+}
old mode 100755 (executable)
new mode 100644 (file)
old mode 100755 (executable)
new mode 100644 (file)
index 723b29ad17f778f9f9a96682dee1793191b88667..fe1dfd08a02e7a8c9c26542e934ccd6fc4f16f5c 100755 (executable)
@@ -4,6 +4,10 @@ test_description='CRLF conversion'
 
 . ./test-lib.sh
 
+q_to_nul () {
+       tr Q '\0'
+}
+
 append_cr () {
        sed -e 's/$/Q/' | tr Q '\015'
 }
@@ -20,6 +24,7 @@ test_expect_success setup '
        for w in Hello world how are you; do echo $w; done >one &&
        mkdir dir &&
        for w in I am very very fine thank you; do echo $w; done >dir/two &&
+       for w in Oh here is NULQin text here; do echo $w; done | q_to_nul >three &&
        git add . &&
 
        git commit -m initial &&
@@ -27,6 +32,7 @@ test_expect_success setup '
        one=`git rev-parse HEAD:one` &&
        dir=`git rev-parse HEAD:dir` &&
        two=`git rev-parse HEAD:dir/two` &&
+       three=`git rev-parse HEAD:three` &&
 
        for w in Some extra lines here; do echo $w; done >>one &&
        git diff >patch.file &&
@@ -38,7 +44,7 @@ test_expect_success setup '
 
 test_expect_success 'update with autocrlf=input' '
 
-       rm -f tmp one dir/two &&
+       rm -f tmp one dir/two three &&
        git read-tree --reset -u HEAD &&
        git repo-config core.autocrlf input &&
 
@@ -62,7 +68,7 @@ test_expect_success 'update with autocrlf=input' '
 
 test_expect_success 'update with autocrlf=true' '
 
-       rm -f tmp one dir/two &&
+       rm -f tmp one dir/two three &&
        git read-tree --reset -u HEAD &&
        git repo-config core.autocrlf true &&
 
@@ -86,7 +92,7 @@ test_expect_success 'update with autocrlf=true' '
 
 test_expect_success 'checkout with autocrlf=true' '
 
-       rm -f tmp one dir/two &&
+       rm -f tmp one dir/two three &&
        git repo-config core.autocrlf true &&
        git read-tree --reset -u HEAD &&
 
@@ -110,7 +116,7 @@ test_expect_success 'checkout with autocrlf=true' '
 
 test_expect_success 'checkout with autocrlf=input' '
 
-       rm -f tmp one dir/two &&
+       rm -f tmp one dir/two three &&
        git repo-config core.autocrlf input &&
        git read-tree --reset -u HEAD &&
 
@@ -136,7 +142,7 @@ test_expect_success 'checkout with autocrlf=input' '
 
 test_expect_success 'apply patch (autocrlf=input)' '
 
-       rm -f tmp one dir/two &&
+       rm -f tmp one dir/two three &&
        git repo-config core.autocrlf input &&
        git read-tree --reset -u HEAD &&
 
@@ -149,7 +155,7 @@ test_expect_success 'apply patch (autocrlf=input)' '
 
 test_expect_success 'apply patch --cached (autocrlf=input)' '
 
-       rm -f tmp one dir/two &&
+       rm -f tmp one dir/two three &&
        git repo-config core.autocrlf input &&
        git read-tree --reset -u HEAD &&
 
@@ -162,7 +168,7 @@ test_expect_success 'apply patch --cached (autocrlf=input)' '
 
 test_expect_success 'apply patch --index (autocrlf=input)' '
 
-       rm -f tmp one dir/two &&
+       rm -f tmp one dir/two three &&
        git repo-config core.autocrlf input &&
        git read-tree --reset -u HEAD &&
 
@@ -176,7 +182,7 @@ test_expect_success 'apply patch --index (autocrlf=input)' '
 
 test_expect_success 'apply patch (autocrlf=true)' '
 
-       rm -f tmp one dir/two &&
+       rm -f tmp one dir/two three &&
        git repo-config core.autocrlf true &&
        git read-tree --reset -u HEAD &&
 
@@ -189,7 +195,7 @@ test_expect_success 'apply patch (autocrlf=true)' '
 
 test_expect_success 'apply patch --cached (autocrlf=true)' '
 
-       rm -f tmp one dir/two &&
+       rm -f tmp one dir/two three &&
        git repo-config core.autocrlf true &&
        git read-tree --reset -u HEAD &&
 
@@ -202,7 +208,7 @@ test_expect_success 'apply patch --cached (autocrlf=true)' '
 
 test_expect_success 'apply patch --index (autocrlf=true)' '
 
-       rm -f tmp one dir/two &&
+       rm -f tmp one dir/two three &&
        git repo-config core.autocrlf true &&
        git read-tree --reset -u HEAD &&
 
@@ -214,4 +220,74 @@ test_expect_success 'apply patch --index (autocrlf=true)' '
        }
 '
 
+test_expect_success '.gitattributes says two is binary' '
+
+       rm -f tmp one dir/two three &&
+       echo "two -crlf" >.gitattributes &&
+       git repo-config core.autocrlf true &&
+       git read-tree --reset -u HEAD &&
+
+       if remove_cr dir/two >/dev/null
+       then
+               echo "Huh?"
+               false
+       else
+               : happy
+       fi &&
+
+       if remove_cr one >/dev/null
+       then
+               : happy
+       else
+               echo "Huh?"
+               false
+       fi &&
+
+       if remove_cr three >/dev/null
+       then
+               echo "Huh?"
+               false
+       else
+               : happy
+       fi
+'
+
+test_expect_success '.gitattributes says two is input' '
+
+       rm -f tmp one dir/two three &&
+       echo "two crlf=input" >.gitattributes &&
+       git read-tree --reset -u HEAD &&
+
+       if remove_cr dir/two >/dev/null
+       then
+               echo "Huh?"
+               false
+       else
+               : happy
+       fi
+'
+
+test_expect_success '.gitattributes says two and three are text' '
+
+       rm -f tmp one dir/two three &&
+       echo "t* crlf" >.gitattributes &&
+       git read-tree --reset -u HEAD &&
+
+       if remove_cr dir/two >/dev/null
+       then
+               : happy
+       else
+               echo "Huh?"
+               false
+       fi &&
+
+       if remove_cr three >/dev/null
+       then
+               : happy
+       else
+               echo "Huh?"
+               false
+       fi
+'
+
 test_done
diff --git a/t/t0021-conversion.sh b/t/t0021-conversion.sh
new file mode 100755 (executable)
index 0000000..6c26fd8
--- /dev/null
@@ -0,0 +1,48 @@
+#!/bin/sh
+
+test_description='blob conversion via gitattributes'
+
+. ./test-lib.sh
+
+cat <<\EOF >rot13.sh
+tr '[a-zA-Z]' '[n-za-mN-ZA-M]'
+EOF
+chmod +x rot13.sh
+
+test_expect_success setup '
+       git config filter.rot13.smudge ./rot13.sh &&
+       git config filter.rot13.clean ./rot13.sh &&
+
+       {
+           echo "*.t filter=rot13"
+           echo "*.i ident"
+       } >.gitattributes &&
+
+       {
+           echo a b c d e f g h i j k l m
+           echo n o p q r s t u v w x y z
+           echo '\''$Id$'\''
+       } >test &&
+       cat test >test.t &&
+       cat test >test.o &&
+       cat test >test.i &&
+       git add test test.t test.i &&
+       rm -f test test.t test.i &&
+       git checkout -- test test.t test.i
+'
+
+script='s/^\$Id: \([0-9a-f]*\) \$/\1/p'
+
+test_expect_success check '
+
+       cmp test.o test &&
+       cmp test.o test.t &&
+
+       # ident should be stripped in the repository
+       git diff --raw --exit-code :test :test.i &&
+       id=$(git rev-parse --verify :test) &&
+       embedded=$(sed -ne "$script" test.i) &&
+       test "z$id" = "z$embedded"
+'
+
+test_done
index e26a36cf0f4c113d916c2d23daf036e4844be053..de4e5eb61f5cc197a51005c6cfbd3bf2b9428480 100755 (executable)
@@ -184,7 +184,7 @@ checked.
   9  exists  O!=A    missing   no merge    must match A and be
                                            up-to-date, if exists.
  ------------------------------------------------------------------
- 10  exists  O==A    missing   remove      ditto
+ 10  exists  O==A    missing   no merge    must match A
  ------------------------------------------------------------------
  11  exists  O!=A    O!=B      no merge    must match A and be
                      A!=B                  up-to-date, if exists.
diff --git a/t/t2200-add-update.sh b/t/t2200-add-update.sh
new file mode 100755 (executable)
index 0000000..83005e7
--- /dev/null
@@ -0,0 +1,38 @@
+#!/bin/sh
+
+test_description='git-add -u with path limiting
+
+This test creates a working tree state with three files:
+
+  top (previously committed, modified)
+  dir/sub (previously committed, modified)
+  dir/other (untracked)
+
+and issues a git-add -u with path limiting on "dir" to add
+only the updates to dir/sub.'
+
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+echo initial >top &&
+mkdir dir &&
+echo initial >dir/sub &&
+git-add dir/sub top &&
+git-commit -m initial &&
+echo changed >top &&
+echo changed >dir/sub &&
+echo other >dir/other
+'
+
+test_expect_success 'update' 'git-add -u dir'
+
+test_expect_success 'update touched correct path' \
+  'test "`git-diff-files --name-status dir/sub`" = ""'
+
+test_expect_success 'update did not touch other tracked files' \
+  'test "`git-diff-files --name-status top`" = "M      top"'
+
+test_expect_success 'update did not touch untracked files' \
+  'test "`git-diff-files --name-status dir/other`" = ""'
+
+test_done
diff --git a/t/t3030-merge-recursive.sh b/t/t3030-merge-recursive.sh
new file mode 100755 (executable)
index 0000000..86ee2b0
--- /dev/null
@@ -0,0 +1,528 @@
+#!/bin/sh
+
+test_description='merge-recursive backend test'
+
+. ./test-lib.sh
+
+test_expect_success 'setup 1' '
+
+       echo hello >a &&
+       o0=$(git hash-object a) &&
+       cp a b &&
+       cp a c &&
+       mkdir d &&
+       cp a d/e &&
+
+       test_tick &&
+       git add a b c d/e &&
+       git commit -m initial &&
+       c0=$(git rev-parse --verify HEAD) &&
+       git branch side &&
+       git branch df-1 &&
+       git branch df-2 &&
+       git branch df-3 &&
+       git branch remove &&
+
+       echo hello >>a &&
+       cp a d/e &&
+       o1=$(git hash-object a) &&
+
+       git add a d/e &&
+
+       test_tick &&
+       git commit -m "master modifies a and d/e" &&
+       c1=$(git rev-parse --verify HEAD) &&
+       ( git ls-tree -r HEAD ; git ls-files -s ) >actual &&
+       (
+               echo "100644 blob $o1   a"
+               echo "100644 blob $o0   b"
+               echo "100644 blob $o0   c"
+               echo "100644 blob $o1   d/e"
+               echo "100644 $o1 0      a"
+               echo "100644 $o0 0      b"
+               echo "100644 $o0 0      c"
+               echo "100644 $o1 0      d/e"
+       ) >expected &&
+       git diff -u expected actual
+'
+
+test_expect_success 'setup 2' '
+
+       rm -rf [abcd] &&
+       git checkout side &&
+       ( git ls-tree -r HEAD ; git ls-files -s ) >actual &&
+       (
+               echo "100644 blob $o0   a"
+               echo "100644 blob $o0   b"
+               echo "100644 blob $o0   c"
+               echo "100644 blob $o0   d/e"
+               echo "100644 $o0 0      a"
+               echo "100644 $o0 0      b"
+               echo "100644 $o0 0      c"
+               echo "100644 $o0 0      d/e"
+       ) >expected &&
+       git diff -u expected actual &&
+
+       echo goodbye >>a &&
+       o2=$(git hash-object a) &&
+
+       git add a &&
+
+       test_tick &&
+       git commit -m "side modifies a" &&
+       c2=$(git rev-parse --verify HEAD) &&
+       ( git ls-tree -r HEAD ; git ls-files -s ) >actual &&
+       (
+               echo "100644 blob $o2   a"
+               echo "100644 blob $o0   b"
+               echo "100644 blob $o0   c"
+               echo "100644 blob $o0   d/e"
+               echo "100644 $o2 0      a"
+               echo "100644 $o0 0      b"
+               echo "100644 $o0 0      c"
+               echo "100644 $o0 0      d/e"
+       ) >expected &&
+       git diff -u expected actual
+'
+
+test_expect_success 'setup 3' '
+
+       rm -rf [abcd] &&
+       git checkout df-1 &&
+       ( git ls-tree -r HEAD ; git ls-files -s ) >actual &&
+       (
+               echo "100644 blob $o0   a"
+               echo "100644 blob $o0   b"
+               echo "100644 blob $o0   c"
+               echo "100644 blob $o0   d/e"
+               echo "100644 $o0 0      a"
+               echo "100644 $o0 0      b"
+               echo "100644 $o0 0      c"
+               echo "100644 $o0 0      d/e"
+       ) >expected &&
+       git diff -u expected actual &&
+
+       rm -f b && mkdir b && echo df-1 >b/c && git add b/c &&
+       o3=$(git hash-object b/c) &&
+
+       test_tick &&
+       git commit -m "df-1 makes b/c" &&
+       c3=$(git rev-parse --verify HEAD) &&
+       ( git ls-tree -r HEAD ; git ls-files -s ) >actual &&
+       (
+               echo "100644 blob $o0   a"
+               echo "100644 blob $o3   b/c"
+               echo "100644 blob $o0   c"
+               echo "100644 blob $o0   d/e"
+               echo "100644 $o0 0      a"
+               echo "100644 $o3 0      b/c"
+               echo "100644 $o0 0      c"
+               echo "100644 $o0 0      d/e"
+       ) >expected &&
+       git diff -u expected actual
+'
+
+test_expect_success 'setup 4' '
+
+       rm -rf [abcd] &&
+       git checkout df-2 &&
+       ( git ls-tree -r HEAD ; git ls-files -s ) >actual &&
+       (
+               echo "100644 blob $o0   a"
+               echo "100644 blob $o0   b"
+               echo "100644 blob $o0   c"
+               echo "100644 blob $o0   d/e"
+               echo "100644 $o0 0      a"
+               echo "100644 $o0 0      b"
+               echo "100644 $o0 0      c"
+               echo "100644 $o0 0      d/e"
+       ) >expected &&
+       git diff -u expected actual &&
+
+       rm -f a && mkdir a && echo df-2 >a/c && git add a/c &&
+       o4=$(git hash-object a/c) &&
+
+       test_tick &&
+       git commit -m "df-2 makes a/c" &&
+       c4=$(git rev-parse --verify HEAD) &&
+       ( git ls-tree -r HEAD ; git ls-files -s ) >actual &&
+       (
+               echo "100644 blob $o4   a/c"
+               echo "100644 blob $o0   b"
+               echo "100644 blob $o0   c"
+               echo "100644 blob $o0   d/e"
+               echo "100644 $o4 0      a/c"
+               echo "100644 $o0 0      b"
+               echo "100644 $o0 0      c"
+               echo "100644 $o0 0      d/e"
+       ) >expected &&
+       git diff -u expected actual
+'
+
+test_expect_success 'setup 5' '
+
+       rm -rf [abcd] &&
+       git checkout remove &&
+       ( git ls-tree -r HEAD ; git ls-files -s ) >actual &&
+       (
+               echo "100644 blob $o0   a"
+               echo "100644 blob $o0   b"
+               echo "100644 blob $o0   c"
+               echo "100644 blob $o0   d/e"
+               echo "100644 $o0 0      a"
+               echo "100644 $o0 0      b"
+               echo "100644 $o0 0      c"
+               echo "100644 $o0 0      d/e"
+       ) >expected &&
+       git diff -u expected actual &&
+
+       rm -f b &&
+       echo remove-conflict >a &&
+
+       git add a &&
+       git rm b &&
+       o5=$(git hash-object a) &&
+
+       test_tick &&
+       git commit -m "remove removes b and modifies a" &&
+       c5=$(git rev-parse --verify HEAD) &&
+       ( git ls-tree -r HEAD ; git ls-files -s ) >actual &&
+       (
+               echo "100644 blob $o5   a"
+               echo "100644 blob $o0   c"
+               echo "100644 blob $o0   d/e"
+               echo "100644 $o5 0      a"
+               echo "100644 $o0 0      c"
+               echo "100644 $o0 0      d/e"
+       ) >expected &&
+       git diff -u expected actual
+
+'
+
+test_expect_success 'setup 6' '
+
+       rm -rf [abcd] &&
+       git checkout df-3 &&
+       ( git ls-tree -r HEAD ; git ls-files -s ) >actual &&
+       (
+               echo "100644 blob $o0   a"
+               echo "100644 blob $o0   b"
+               echo "100644 blob $o0   c"
+               echo "100644 blob $o0   d/e"
+               echo "100644 $o0 0      a"
+               echo "100644 $o0 0      b"
+               echo "100644 $o0 0      c"
+               echo "100644 $o0 0      d/e"
+       ) >expected &&
+       git diff -u expected actual &&
+
+       rm -fr d && echo df-3 >d && git add d &&
+       o6=$(git hash-object d) &&
+
+       test_tick &&
+       git commit -m "df-3 makes d" &&
+       c6=$(git rev-parse --verify HEAD) &&
+       ( git ls-tree -r HEAD ; git ls-files -s ) >actual &&
+       (
+               echo "100644 blob $o0   a"
+               echo "100644 blob $o0   b"
+               echo "100644 blob $o0   c"
+               echo "100644 blob $o6   d"
+               echo "100644 $o0 0      a"
+               echo "100644 $o0 0      b"
+               echo "100644 $o0 0      c"
+               echo "100644 $o6 0      d"
+       ) >expected &&
+       git diff -u expected actual
+'
+
+test_expect_success 'merge-recursive simple' '
+
+       rm -fr [abcd] &&
+       git checkout -f "$c2" &&
+
+       git-merge-recursive "$c0" -- "$c2" "$c1"
+       status=$?
+       case "$status" in
+       1)
+               : happy
+               ;;
+       *)
+               echo >&2 "why status $status!!!"
+               false
+               ;;
+       esac
+'
+
+test_expect_success 'merge-recursive result' '
+
+       git ls-files -s >actual &&
+       (
+               echo "100644 $o0 1      a"
+               echo "100644 $o2 2      a"
+               echo "100644 $o1 3      a"
+               echo "100644 $o0 0      b"
+               echo "100644 $o0 0      c"
+               echo "100644 $o1 0      d/e"
+       ) >expected &&
+       git diff -u expected actual
+
+'
+
+test_expect_success 'merge-recursive remove conflict' '
+
+       rm -fr [abcd] &&
+       git checkout -f "$c1" &&
+
+       git-merge-recursive "$c0" -- "$c1" "$c5"
+       status=$?
+       case "$status" in
+       1)
+               : happy
+               ;;
+       *)
+               echo >&2 "why status $status!!!"
+               false
+               ;;
+       esac
+'
+
+test_expect_success 'merge-recursive remove conflict' '
+
+       git ls-files -s >actual &&
+       (
+               echo "100644 $o0 1      a"
+               echo "100644 $o1 2      a"
+               echo "100644 $o5 3      a"
+               echo "100644 $o0 0      c"
+               echo "100644 $o1 0      d/e"
+       ) >expected &&
+       git diff -u expected actual
+
+'
+
+test_expect_success 'merge-recursive d/f simple' '
+       rm -fr [abcd] &&
+       git reset --hard &&
+       git checkout -f "$c1" &&
+
+       git-merge-recursive "$c0" -- "$c1" "$c3"
+'
+
+test_expect_success 'merge-recursive result' '
+
+       git ls-files -s >actual &&
+       (
+               echo "100644 $o1 0      a"
+               echo "100644 $o3 0      b/c"
+               echo "100644 $o0 0      c"
+               echo "100644 $o1 0      d/e"
+       ) >expected &&
+       git diff -u expected actual
+
+'
+
+test_expect_success 'merge-recursive d/f conflict' '
+
+       rm -fr [abcd] &&
+       git reset --hard &&
+       git checkout -f "$c1" &&
+
+       git-merge-recursive "$c0" -- "$c1" "$c4"
+       status=$?
+       case "$status" in
+       1)
+               : happy
+               ;;
+       *)
+               echo >&2 "why status $status!!!"
+               false
+               ;;
+       esac
+'
+
+test_expect_success 'merge-recursive d/f conflict result' '
+
+       git ls-files -s >actual &&
+       (
+               echo "100644 $o0 1      a"
+               echo "100644 $o1 2      a"
+               echo "100644 $o4 0      a/c"
+               echo "100644 $o0 0      b"
+               echo "100644 $o0 0      c"
+               echo "100644 $o1 0      d/e"
+       ) >expected &&
+       git diff -u expected actual
+
+'
+
+test_expect_success 'merge-recursive d/f conflict the other way' '
+
+       rm -fr [abcd] &&
+       git reset --hard &&
+       git checkout -f "$c4" &&
+
+       git-merge-recursive "$c0" -- "$c4" "$c1"
+       status=$?
+       case "$status" in
+       1)
+               : happy
+               ;;
+       *)
+               echo >&2 "why status $status!!!"
+               false
+               ;;
+       esac
+'
+
+test_expect_success 'merge-recursive d/f conflict result the other way' '
+
+       git ls-files -s >actual &&
+       (
+               echo "100644 $o0 1      a"
+               echo "100644 $o1 3      a"
+               echo "100644 $o4 0      a/c"
+               echo "100644 $o0 0      b"
+               echo "100644 $o0 0      c"
+               echo "100644 $o1 0      d/e"
+       ) >expected &&
+       git diff -u expected actual
+
+'
+
+test_expect_success 'merge-recursive d/f conflict' '
+
+       rm -fr [abcd] &&
+       git reset --hard &&
+       git checkout -f "$c1" &&
+
+       git-merge-recursive "$c0" -- "$c1" "$c6"
+       status=$?
+       case "$status" in
+       1)
+               : happy
+               ;;
+       *)
+               echo >&2 "why status $status!!!"
+               false
+               ;;
+       esac
+'
+
+test_expect_success 'merge-recursive d/f conflict result' '
+
+       git ls-files -s >actual &&
+       (
+               echo "100644 $o1 0      a"
+               echo "100644 $o0 0      b"
+               echo "100644 $o0 0      c"
+               echo "100644 $o6 3      d"
+               echo "100644 $o0 1      d/e"
+               echo "100644 $o1 2      d/e"
+       ) >expected &&
+       git diff -u expected actual
+
+'
+
+test_expect_success 'merge-recursive d/f conflict' '
+
+       rm -fr [abcd] &&
+       git reset --hard &&
+       git checkout -f "$c6" &&
+
+       git-merge-recursive "$c0" -- "$c6" "$c1"
+       status=$?
+       case "$status" in
+       1)
+               : happy
+               ;;
+       *)
+               echo >&2 "why status $status!!!"
+               false
+               ;;
+       esac
+'
+
+test_expect_success 'merge-recursive d/f conflict result' '
+
+       git ls-files -s >actual &&
+       (
+               echo "100644 $o1 0      a"
+               echo "100644 $o0 0      b"
+               echo "100644 $o0 0      c"
+               echo "100644 $o6 2      d"
+               echo "100644 $o0 1      d/e"
+               echo "100644 $o1 3      d/e"
+       ) >expected &&
+       git diff -u expected actual
+
+'
+
+test_expect_success 'reset and 3-way merge' '
+
+       git reset --hard "$c2" &&
+       git read-tree -m "$c0" "$c2" "$c1"
+
+'
+
+test_expect_success 'reset and bind merge' '
+
+       git reset --hard master &&
+       git read-tree --prefix=M/ master &&
+       git ls-files -s >actual &&
+       (
+               echo "100644 $o1 0      M/a"
+               echo "100644 $o0 0      M/b"
+               echo "100644 $o0 0      M/c"
+               echo "100644 $o1 0      M/d/e"
+               echo "100644 $o1 0      a"
+               echo "100644 $o0 0      b"
+               echo "100644 $o0 0      c"
+               echo "100644 $o1 0      d/e"
+       ) >expected &&
+       git diff -u expected actual &&
+
+       git read-tree --prefix=a1/ master &&
+       git ls-files -s >actual &&
+       (
+               echo "100644 $o1 0      M/a"
+               echo "100644 $o0 0      M/b"
+               echo "100644 $o0 0      M/c"
+               echo "100644 $o1 0      M/d/e"
+               echo "100644 $o1 0      a"
+               echo "100644 $o1 0      a1/a"
+               echo "100644 $o0 0      a1/b"
+               echo "100644 $o0 0      a1/c"
+               echo "100644 $o1 0      a1/d/e"
+               echo "100644 $o0 0      b"
+               echo "100644 $o0 0      c"
+               echo "100644 $o1 0      d/e"
+       ) >expected &&
+       git diff -u expected actual
+
+       git read-tree --prefix=z/ master &&
+       git ls-files -s >actual &&
+       (
+               echo "100644 $o1 0      M/a"
+               echo "100644 $o0 0      M/b"
+               echo "100644 $o0 0      M/c"
+               echo "100644 $o1 0      M/d/e"
+               echo "100644 $o1 0      a"
+               echo "100644 $o1 0      a1/a"
+               echo "100644 $o0 0      a1/b"
+               echo "100644 $o0 0      a1/c"
+               echo "100644 $o1 0      a1/d/e"
+               echo "100644 $o0 0      b"
+               echo "100644 $o0 0      c"
+               echo "100644 $o1 0      d/e"
+               echo "100644 $o1 0      z/a"
+               echo "100644 $o0 0      z/b"
+               echo "100644 $o0 0      z/c"
+               echo "100644 $o1 0      z/d/e"
+       ) >expected &&
+       git diff -u expected actual
+
+'
+
+test_done
+
diff --git a/t/t3040-subprojects-basic.sh b/t/t3040-subprojects-basic.sh
new file mode 100755 (executable)
index 0000000..79b9f23
--- /dev/null
@@ -0,0 +1,85 @@
+#!/bin/sh
+
+test_description='Basic subproject functionality'
+. ./test-lib.sh
+
+test_expect_success 'Super project creation' \
+    ': >Makefile &&
+    git add Makefile &&
+    git commit -m "Superproject created"'
+
+
+cat >expected <<EOF
+:000000 160000 00000... A      sub1
+:000000 160000 00000... A      sub2
+EOF
+test_expect_success 'create subprojects' \
+    'mkdir sub1 &&
+    ( cd sub1 && git init && : >Makefile && git add * &&
+    git commit -q -m "subproject 1" ) &&
+    mkdir sub2 &&
+    ( cd sub2 && git init && : >Makefile && git add * &&
+    git commit -q -m "subproject 2" ) &&
+    git update-index --add sub1 &&
+    git add sub2 &&
+    git commit -q -m "subprojects added" &&
+    git diff-tree --abbrev=5 HEAD^ HEAD |cut -d" " -f-3,5- >current &&
+    git diff expected current'
+
+git branch save HEAD
+
+test_expect_success 'check if fsck ignores the subprojects' \
+    'git fsck --full'
+
+test_expect_success 'check if commit in a subproject detected' \
+    '( cd sub1 &&
+    echo "all:" >>Makefile &&
+    echo "     true" >>Makefile &&
+    git commit -q -a -m "make all" ) && {
+        git diff-files --exit-code
+       test $? = 1
+    }'
+
+test_expect_success 'check if a changed subproject HEAD can be committed' \
+    'git commit -q -a -m "sub1 changed" && {
+       git diff-tree --exit-code HEAD^ HEAD
+       test $? = 1
+    }'
+
+test_expect_success 'check if diff-index works for subproject elements' \
+    'git diff-index --exit-code --cached save -- sub1
+    test $? = 1'
+
+test_expect_success 'check if diff-tree works for subproject elements' \
+    'git diff-tree --exit-code HEAD^ HEAD -- sub1
+    test $? = 1'
+
+test_expect_success 'check if git diff works for subproject elements' \
+    'git diff --exit-code HEAD^ HEAD
+    test $? = 1'
+
+test_expect_success 'check if clone works' \
+    'git ls-files -s >expected &&
+    git clone -l -s . cloned &&
+    ( cd cloned && git ls-files -s ) >current &&
+    git diff expected current'
+
+test_expect_success 'removing and adding subproject' \
+    'git update-index --force-remove -- sub2 &&
+    mv sub2 sub3 &&
+    git add sub3 &&
+    git commit -q -m "renaming a subproject" && {
+       git diff -M --name-status --exit-code HEAD^ HEAD
+       test $? = 1
+    }'
+
+# the index must contain the object name the HEAD of the
+# subproject sub1 was at the point "save"
+test_expect_success 'checkout in superproject' \
+    'git checkout save &&
+    git diff-index --exit-code --raw --cached save -- sub1'
+
+# just interesting what happened...
+# git diff --name-status -M save master
+
+test_done
index e31cf93a00ab377355734b3d88d536d36fe734e1..0a97b75288d44cf93e0a8f8d9ab1b76715f946d1 100755 (executable)
@@ -84,6 +84,30 @@ test_expect_success \
     'When the rm in "git-rm -f" fails, it should not remove the file from the index' \
     'git-ls-files --error-unmatch baz'
 
+test_expect_success 'Remove nonexistent file with --ignore-unmatch' '
+       git rm --ignore-unmatch nonexistent
+'
+
+test_expect_success '"rm" command printed' '
+       echo frotz > test-file &&
+       git add test-file &&
+       git commit -m "add file for rm test" &&
+       git rm test-file > rm-output &&
+       test `egrep "^rm " rm-output | wc -l` = 1 &&
+       rm -f test-file rm-output &&
+       git commit -m "remove file from rm test"
+'
+
+test_expect_success '"rm" command suppressed with --quiet' '
+       echo frotz > test-file &&
+       git add test-file &&
+       git commit -m "add file for rm --quiet test" &&
+       git rm --quiet test-file > rm-output &&
+       test `wc -l < rm-output` = 0 &&
+       rm -f test-file rm-output &&
+       git commit -m "remove file from rm --quiet test"
+'
+
 # Now, failure cases.
 test_expect_success 'Re-add foo and baz' '
        git add foo baz &&
@@ -154,4 +178,8 @@ test_expect_success 'Recursive with -r -f' '
        ! test -d frotz
 '
 
+test_expect_failure 'Remove nonexistent file returns nonzero exit status' '
+       git rm nonexistent
+'
+
 test_done
index 488e075c16611e5e6132744939574cdd878c3959..8f4c29a6b5a263d6f18d95757b76ac55e9443068 100755 (executable)
@@ -241,6 +241,7 @@ format-patch --attach --stdout initial..master
 format-patch --inline --stdout initial..side
 format-patch --inline --stdout initial..master^
 format-patch --inline --stdout initial..master
+format-patch --inline --stdout --subject-prefix=TESTCASE initial..master
 
 diff --abbrev initial..side
 diff -r initial..side
diff --git a/t/t4013/diff.format-patch_--inline_--stdout_--subject-prefix=TESTCASE_initial..master b/t/t4013/diff.format-patch_--inline_--stdout_--subject-prefix=TESTCASE_initial..master
new file mode 100644 (file)
index 0000000..a8093be
--- /dev/null
@@ -0,0 +1,164 @@
+$ git format-patch --inline --stdout --subject-prefix=TESTCASE initial..master
+From 1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44 Mon Sep 17 00:00:00 2001
+From: A U Thor <author@example.com>
+Date: Mon, 26 Jun 2006 00:01:00 +0000
+Subject: [TESTCASE] Second
+MIME-Version: 1.0
+Content-Type: multipart/mixed; boundary="------------g-i-t--v-e-r-s-i-o-n"
+
+This is a multi-part message in MIME format.
+--------------g-i-t--v-e-r-s-i-o-n
+Content-Type: text/plain; charset=UTF-8; format=fixed
+Content-Transfer-Encoding: 8bit
+
+
+This is the second commit.
+---
+ dir/sub |    2 ++
+ file0   |    3 +++
+ file2   |    3 ---
+ 3 files changed, 5 insertions(+), 3 deletions(-)
+ delete mode 100644 file2
+--------------g-i-t--v-e-r-s-i-o-n
+Content-Type: text/x-patch; name="1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44.diff"
+Content-Transfer-Encoding: 8bit
+Content-Disposition: inline; filename="1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44.diff"
+
+diff --git a/dir/sub b/dir/sub
+index 35d242b..8422d40 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -1,2 +1,4 @@
+ A
+ B
++C
++D
+diff --git a/file0 b/file0
+index 01e79c3..b414108 100644
+--- a/file0
++++ b/file0
+@@ -1,3 +1,6 @@
+ 1
+ 2
+ 3
++4
++5
++6
+diff --git a/file2 b/file2
+deleted file mode 100644
+index 01e79c3..0000000
+--- a/file2
++++ /dev/null
+@@ -1,3 +0,0 @@
+-1
+-2
+-3
+
+--------------g-i-t--v-e-r-s-i-o-n--
+
+
+
+From 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0 Mon Sep 17 00:00:00 2001
+From: A U Thor <author@example.com>
+Date: Mon, 26 Jun 2006 00:02:00 +0000
+Subject: [TESTCASE] Third
+MIME-Version: 1.0
+Content-Type: multipart/mixed; boundary="------------g-i-t--v-e-r-s-i-o-n"
+
+This is a multi-part message in MIME format.
+--------------g-i-t--v-e-r-s-i-o-n
+Content-Type: text/plain; charset=UTF-8; format=fixed
+Content-Transfer-Encoding: 8bit
+
+---
+ dir/sub |    2 ++
+ file1   |    3 +++
+ 2 files changed, 5 insertions(+), 0 deletions(-)
+ create mode 100644 file1
+--------------g-i-t--v-e-r-s-i-o-n
+Content-Type: text/x-patch; name="9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0.diff"
+Content-Transfer-Encoding: 8bit
+Content-Disposition: inline; filename="9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0.diff"
+
+diff --git a/dir/sub b/dir/sub
+index 8422d40..cead32e 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -2,3 +2,5 @@ A
+ B
+ C
+ D
++E
++F
+diff --git a/file1 b/file1
+new file mode 100644
+index 0000000..b1e6722
+--- /dev/null
++++ b/file1
+@@ -0,0 +1,3 @@
++A
++B
++C
+
+--------------g-i-t--v-e-r-s-i-o-n--
+
+
+
+From c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a Mon Sep 17 00:00:00 2001
+From: A U Thor <author@example.com>
+Date: Mon, 26 Jun 2006 00:03:00 +0000
+Subject: [TESTCASE] Side
+MIME-Version: 1.0
+Content-Type: multipart/mixed; boundary="------------g-i-t--v-e-r-s-i-o-n"
+
+This is a multi-part message in MIME format.
+--------------g-i-t--v-e-r-s-i-o-n
+Content-Type: text/plain; charset=UTF-8; format=fixed
+Content-Transfer-Encoding: 8bit
+
+---
+ dir/sub |    2 ++
+ file0   |    3 +++
+ file3   |    4 ++++
+ 3 files changed, 9 insertions(+), 0 deletions(-)
+ create mode 100644 file3
+--------------g-i-t--v-e-r-s-i-o-n
+Content-Type: text/x-patch; name="c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a.diff"
+Content-Transfer-Encoding: 8bit
+Content-Disposition: inline; filename="c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a.diff"
+
+diff --git a/dir/sub b/dir/sub
+index 35d242b..7289e35 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -1,2 +1,4 @@
+ A
+ B
++1
++2
+diff --git a/file0 b/file0
+index 01e79c3..f4615da 100644
+--- a/file0
++++ b/file0
+@@ -1,3 +1,6 @@
+ 1
+ 2
+ 3
++A
++B
++C
+diff --git a/file3 b/file3
+new file mode 100644
+index 0000000..7289e35
+--- /dev/null
++++ b/file3
+@@ -0,0 +1,4 @@
++A
++B
++1
++2
+
+--------------g-i-t--v-e-r-s-i-o-n--
+
+
+$
diff --git a/t/t4020-diff-external.sh b/t/t4020-diff-external.sh
new file mode 100755 (executable)
index 0000000..f0045cd
--- /dev/null
@@ -0,0 +1,97 @@
+#!/bin/sh
+
+test_description='external diff interface test'
+
+. ./test-lib.sh
+
+_z40=0000000000000000000000000000000000000000
+
+test_expect_success setup '
+
+       test_tick &&
+       echo initial >file &&
+       git add file &&
+       git commit -m initial &&
+
+       test_tick &&
+       echo second >file &&
+       git add file &&
+       git commit -m second &&
+
+       test_tick &&
+       echo third >file
+'
+
+test_expect_success 'GIT_EXTERNAL_DIFF environment' '
+
+       GIT_EXTERNAL_DIFF=echo git diff | {
+               read path oldfile oldhex oldmode newfile newhex newmode &&
+               test "z$path" = zfile &&
+               test "z$oldmode" = z100644 &&
+               test "z$newhex" = "z$_z40" &&
+               test "z$newmode" = z100644 &&
+               oh=$(git rev-parse --verify HEAD:file) &&
+               test "z$oh" = "z$oldhex"
+       }
+
+'
+
+test_expect_success 'GIT_EXTERNAL_DIFF environment should apply only to diff' '
+
+       GIT_EXTERNAL_DIFF=echo git log -p -1 HEAD |
+       grep "^diff --git a/file b/file"
+
+'
+
+test_expect_success 'diff attribute' '
+
+       git config diff.parrot.command echo &&
+
+       echo >.gitattributes "file diff=parrot" &&
+
+       git diff | {
+               read path oldfile oldhex oldmode newfile newhex newmode &&
+               test "z$path" = zfile &&
+               test "z$oldmode" = z100644 &&
+               test "z$newhex" = "z$_z40" &&
+               test "z$newmode" = z100644 &&
+               oh=$(git rev-parse --verify HEAD:file) &&
+               test "z$oh" = "z$oldhex"
+       }
+
+'
+
+test_expect_success 'diff attribute should apply only to diff' '
+
+       git log -p -1 HEAD |
+       grep "^diff --git a/file b/file"
+
+'
+
+test_expect_success 'diff attribute' '
+
+       git config --unset diff.parrot.command &&
+       git config diff.color.command echo &&
+
+       echo >.gitattributes "file diff=color" &&
+
+       git diff | {
+               read path oldfile oldhex oldmode newfile newhex newmode &&
+               test "z$path" = zfile &&
+               test "z$oldmode" = z100644 &&
+               test "z$newhex" = "z$_z40" &&
+               test "z$newmode" = z100644 &&
+               oh=$(git rev-parse --verify HEAD:file) &&
+               test "z$oh" = "z$oldhex"
+       }
+
+'
+
+test_expect_success 'diff attribute should apply only to diff' '
+
+       git log -p -1 HEAD |
+       grep "^diff --git a/file b/file"
+
+'
+
+test_done
diff --git a/t/t4122-apply-symlink-inside.sh b/t/t4122-apply-symlink-inside.sh
new file mode 100755 (executable)
index 0000000..3ddfe64
--- /dev/null
@@ -0,0 +1,56 @@
+#!/bin/sh
+
+test_description='apply to deeper directory without getting fooled with symlink'
+. ./test-lib.sh
+
+lecho () {
+       for l_
+       do
+               echo "$l_"
+       done
+}
+
+test_expect_success setup '
+
+       mkdir -p arch/i386/boot arch/x86_64 &&
+       lecho 1 2 3 4 5 >arch/i386/boot/Makefile &&
+       ln -s ../i386/boot arch/x86_64/boot &&
+       git add . &&
+       test_tick &&
+       git commit -m initial &&
+       git branch test &&
+
+       rm arch/x86_64/boot &&
+       mkdir arch/x86_64/boot &&
+       lecho 2 3 4 5 6 >arch/x86_64/boot/Makefile &&
+       git add . &&
+       test_tick &&
+       git commit -a -m second &&
+
+       git format-patch --binary -1 --stdout >test.patch
+
+'
+
+test_expect_success apply '
+
+       git checkout test &&
+       git diff --exit-code test &&
+       git diff --exit-code --cached test &&
+       git apply --index test.patch
+
+'
+
+test_expect_success 'check result' '
+
+       git diff --exit-code master &&
+       git diff --exit-code --cached master &&
+       test_tick &&
+       git commit -m replay &&
+       T1=$(git rev-parse "master^{tree}") &&
+       T2=$(git rev-parse "HEAD^{tree}") &&
+       test "z$T1" = "z$T2"
+
+'
+
+test_done
+
diff --git a/t/t4201-shortlog.sh b/t/t4201-shortlog.sh
new file mode 100755 (executable)
index 0000000..a48733c
--- /dev/null
@@ -0,0 +1,50 @@
+#!/bin/sh
+#
+# Copyright (c) 2006 Johannes E. Schindelin
+#
+
+test_description='git-shortlog
+'
+
+. ./test-lib.sh
+
+echo 1 > a1
+git add a1
+tree=$(git write-tree)
+commit=$( (echo "Test"; echo) | git commit-tree $tree )
+git update-ref HEAD $commit
+
+echo 2 > a1
+git commit --quiet -m "This is a very, very long first line for the commit message to see if it is wrapped correctly" a1
+
+# test if the wrapping is still valid when replacing all i's by treble clefs.
+echo 3 > a1
+git commit --quiet -m "$(echo "This is a very, very long first line for the commit message to see if it is wrapped correctly" | sed "s/i/1234/g" | tr 1234 '\360\235\204\236')" a1
+
+# now fsck up the utf8
+git repo-config i18n.commitencoding non-utf-8
+echo 4 > a1
+git commit --quiet -m "$(echo "This is a very, very long first line for the commit message to see if it is wrapped correctly" | sed "s/i/1234/g" | tr 1234 '\370\235\204\236')" a1
+
+echo 5 > a1
+git commit --quiet -m "a                                                               12      34      56      78" a1
+
+git shortlog -w HEAD > out
+
+cat > expect << EOF
+A U Thor (5):
+      Test
+      This is a very, very long first line for the commit message to see if
+         it is wrapped correctly
+      Th𝄞s 𝄞s a very, very long f𝄞rst l𝄞ne for the comm𝄞t message to see 𝄞f
+         𝄞t 𝄞s wrapped correctly
+      Thø\9d\84\9es ø\9d\84\9es a very, very long fø\9d\84\9erst lø\9d\84\9ene for the commø\9d\84\9et
+         message to see ø\9d\84\9ef ø\9d\84\9et ø\9d\84\9es wrapped correctly
+      a                                                                12      34
+         56    78
+
+EOF
+
+test_expect_success 'shortlog wrapping' 'diff -u expect out'
+
+test_done
index b4359df795483691e61452366add69a212347723..e223c074f043571ff868a2f4783c7cc155185004 100755 (executable)
@@ -49,9 +49,17 @@ test_expect_success \
      git-update-ref HEAD $(TZ=GMT GIT_COMMITTER_DATE="2005-05-27 22:00:00" \
      git-commit-tree $treeid </dev/null)'
 
+test_expect_success \
+    'git-archive' \
+    'git-archive HEAD >b.tar'
+
 test_expect_success \
     'git-tar-tree' \
-    'git-tar-tree HEAD >b.tar'
+    'git-tar-tree HEAD >b2.tar'
+
+test_expect_success \
+    'git-archive vs. git-tar-tree' \
+    'diff b.tar b2.tar'
 
 test_expect_success \
     'validate file modification time' \
index 083095f7f3a6720836f135835091e6d27d18617a..f336769836f794c390d6e0ddd19f98da5c7b6e40 100755 (executable)
@@ -152,7 +152,7 @@ test_expect_success \
     'use packed deltified (REF_DELTA) objects' \
     'GIT_OBJECT_DIRECTORY=.git2/objects &&
      export GIT_OBJECT_DIRECTORY &&
-     rm .git2/objects/pack/test-* &&
+     rm -f .git2/objects/pack/test-* &&
      cp test-2-${packname_2}.pack test-2-${packname_2}.idx .git2/objects/pack && {
         git-diff-tree --root -p $commit &&
         while read object
@@ -167,7 +167,7 @@ test_expect_success \
     'use packed deltified (OFS_DELTA) objects' \
     'GIT_OBJECT_DIRECTORY=.git2/objects &&
      export GIT_OBJECT_DIRECTORY &&
-     rm .git2/objects/pack/test-* &&
+     rm -f .git2/objects/pack/test-* &&
      cp test-3-${packname_3}.pack test-3-${packname_3}.idx .git2/objects/pack && {
         git-diff-tree --root -p $commit &&
         while read object
@@ -188,15 +188,15 @@ test_expect_success \
 
 test_expect_success \
     'corrupt a pack and see if verify catches' \
-    'cp test-1-${packname_1}.idx test-3.idx &&
-     cp test-2-${packname_2}.pack test-3.pack &&
+    'cat test-1-${packname_1}.idx >test-3.idx &&
+     cat test-2-${packname_2}.pack >test-3.pack &&
      if git-verify-pack test-3.idx
      then false
      else :;
      fi &&
 
      : PACK_SIGNATURE &&
-     cp test-1-${packname_1}.pack test-3.pack &&
+     cat test-1-${packname_1}.pack >test-3.pack &&
      dd if=/dev/zero of=test-3.pack count=1 bs=1 conv=notrunc seek=2 &&
      if git-verify-pack test-3.idx
      then false
@@ -204,7 +204,7 @@ test_expect_success \
      fi &&
 
      : PACK_VERSION &&
-     cp test-1-${packname_1}.pack test-3.pack &&
+     cat test-1-${packname_1}.pack >test-3.pack &&
      dd if=/dev/zero of=test-3.pack count=1 bs=1 conv=notrunc seek=7 &&
      if git-verify-pack test-3.idx
      then false
@@ -212,7 +212,7 @@ test_expect_success \
      fi &&
 
      : TYPE/SIZE byte of the first packed object data &&
-     cp test-1-${packname_1}.pack test-3.pack &&
+     cat test-1-${packname_1}.pack >test-3.pack &&
      dd if=/dev/zero of=test-3.pack count=1 bs=1 conv=notrunc seek=12 &&
      if git-verify-pack test-3.idx
      then false
@@ -222,7 +222,7 @@ test_expect_success \
      : sum of the index file itself &&
      l=`wc -c <test-3.idx` &&
      l=`expr $l - 20` &&
-     cp test-1-${packname_1}.pack test-3.pack &&
+     cat test-1-${packname_1}.pack >test-3.pack &&
      dd if=/dev/zero of=test-3.idx count=20 bs=1 conv=notrunc seek=$l &&
      if git-verify-pack test-3.pack
      then false
@@ -233,21 +233,21 @@ test_expect_success \
 
 test_expect_success \
     'build pack index for an existing pack' \
-    'cp test-1-${packname_1}.pack test-3.pack &&
+    'cat test-1-${packname_1}.pack >test-3.pack &&
      git-index-pack -o tmp.idx test-3.pack &&
      cmp tmp.idx test-1-${packname_1}.idx &&
 
      git-index-pack test-3.pack &&
      cmp test-3.idx test-1-${packname_1}.idx &&
 
-     cp test-2-${packname_2}.pack test-3.pack &&
+     cat test-2-${packname_2}.pack >test-3.pack &&
      git-index-pack -o tmp.idx test-2-${packname_2}.pack &&
      cmp tmp.idx test-2-${packname_2}.idx &&
 
      git-index-pack test-3.pack &&
      cmp test-3.idx test-2-${packname_2}.idx &&
 
-     cp test-3-${packname_3}.pack test-3.pack &&
+     cat test-3-${packname_3}.pack >test-3.pack &&
      git-index-pack -o tmp.idx test-3-${packname_3}.pack &&
      cmp tmp.idx test-3-${packname_3}.idx &&
 
index a6dbb04a86c06cabb22810822dd0f079c9268dd7..fce77f1255378b715c23be5978fcc13e56ba263d 100755 (executable)
@@ -12,7 +12,7 @@ test_expect_success \
      for i in a b c
      do
          echo $i >$i &&
-         dd if=/dev/urandom bs=32k count=1 >>$i &&
+         test-genrandom "$i" 32768 >>$i &&
          git-update-index --add $i || return 1
      done &&
      echo d >d && cat c >>d && git-update-index --add d &&
diff --git a/t/t5302-pack-index.sh b/t/t5302-pack-index.sh
new file mode 100755 (executable)
index 0000000..4d06eca
--- /dev/null
@@ -0,0 +1,149 @@
+#!/bin/sh
+#
+# Copyright (c) 2007 Nicolas Pitre
+#
+
+test_description='pack index with 64-bit offsets and object CRC'
+. ./test-lib.sh
+
+test_expect_success \
+    'setup' \
+    'rm -rf .git
+     git-init &&
+     i=1 &&
+        while test $i -le 100
+     do
+                i=`printf '%03i' $i`
+         echo $i >file_$i &&
+         test-genrandom "$i" 8192 >>file_$i &&
+         git-update-index --add file_$i &&
+                i=`expr $i + 1` || return 1
+     done &&
+     { echo 101 && test-genrandom 100 8192; } >file_101 &&
+     git-update-index --add file_101 &&
+     tree=`git-write-tree` &&
+     commit=`git-commit-tree $tree </dev/null` && {
+        echo $tree &&
+        git-ls-tree $tree | sed -e "s/.* \\([0-9a-f]*\\)       .*/\\1/"
+     } >obj-list &&
+     git-update-ref HEAD $commit'
+
+test_expect_success \
+    'pack-objects with index version 1' \
+    'pack1=$(git-pack-objects --index-version=1 test-1 <obj-list) &&
+     git-verify-pack -v "test-1-${pack1}.pack"'
+
+test_expect_success \
+    'pack-objects with index version 2' \
+    'pack2=$(git-pack-objects --index-version=2 test-2 <obj-list) &&
+     git-verify-pack -v "test-2-${pack2}.pack"'
+
+test_expect_success \
+    'both packs should be identical' \
+    'cmp "test-1-${pack1}.pack" "test-2-${pack2}.pack"'
+
+test_expect_failure \
+    'index v1 and index v2 should be different' \
+    'cmp "test-1-${pack1}.idx" "test-2-${pack2}.idx"'
+
+test_expect_success \
+    'index-pack with index version 1' \
+    'git-index-pack --index-version=1 -o 1.idx "test-1-${pack1}.pack"'
+
+test_expect_success \
+    'index-pack with index version 2' \
+    'git-index-pack --index-version=2 -o 2.idx "test-1-${pack1}.pack"'
+
+test_expect_success \
+    'index-pack results should match pack-objects ones' \
+    'cmp "test-1-${pack1}.idx" "1.idx" &&
+     cmp "test-2-${pack2}.idx" "2.idx"'
+
+test_expect_success \
+    'index v2: force some 64-bit offsets with pack-objects' \
+    'pack3=$(git-pack-objects --index-version=2,0x40000 test-3 <obj-list) &&
+     git-verify-pack -v "test-3-${pack3}.pack"'
+
+test_expect_failure \
+    '64-bit offsets: should be different from previous index v2 results' \
+    'cmp "test-2-${pack2}.idx" "test-3-${pack3}.idx"'
+
+test_expect_success \
+    'index v2: force some 64-bit offsets with index-pack' \
+    'git-index-pack --index-version=2,0x40000 -o 3.idx "test-1-${pack1}.pack"'
+
+test_expect_success \
+    '64-bit offsets: index-pack result should match pack-objects one' \
+    'cmp "test-3-${pack3}.idx" "3.idx"'
+
+test_expect_success \
+    '[index v1] 1) stream pack to repository' \
+    'git-index-pack --index-version=1 --stdin < "test-1-${pack1}.pack" &&
+     git-prune-packed &&
+     git-count-objects | ( read nr rest && test "$nr" -eq 1 ) &&
+     cmp "test-1-${pack1}.pack" ".git/objects/pack/pack-${pack1}.pack" &&
+     cmp "test-1-${pack1}.idx"  ".git/objects/pack/pack-${pack1}.idx"'
+
+test_expect_success \
+    '[index v1] 2) create a stealth corruption in a delta base reference' \
+    '# this test assumes a delta smaller than 16 bytes at the end of the pack
+     git-show-index <1.idx | sort -n | tail -n 1 | (
+       read delta_offs delta_sha1 &&
+       git-cat-file blob "$delta_sha1" > blob_1 &&
+       chmod +w ".git/objects/pack/pack-${pack1}.pack" &&
+       dd of=".git/objects/pack/pack-${pack1}.pack" seek=$(($delta_offs + 1)) \
+         if=".git/objects/pack/pack-${pack1}.idx" skip=$((256 * 4 + 4)) \
+         bs=1 count=20 conv=notrunc &&
+       git-cat-file blob "$delta_sha1" > blob_2 )'
+
+test_expect_failure \
+    '[index v1] 3) corrupted delta happily returned wrong data' \
+    'cmp blob_1 blob_2'
+
+test_expect_failure \
+    '[index v1] 4) confirm that the pack is actually corrupted' \
+    'git-fsck --full $commit'
+
+test_expect_success \
+    '[index v1] 5) pack-objects happily reuses corrupted data' \
+    'pack4=$(git-pack-objects test-4 <obj-list) &&
+     test -f "test-4-${pack1}.pack"'
+
+test_expect_failure \
+    '[index v1] 6) newly created pack is BAD !' \
+    'git-verify-pack -v "test-4-${pack1}.pack"'
+
+test_expect_success \
+    '[index v2] 1) stream pack to repository' \
+    'rm -f .git/objects/pack/* &&
+     git-index-pack --index-version=2,0x40000 --stdin < "test-1-${pack1}.pack" &&
+     git-prune-packed &&
+     git-count-objects | ( read nr rest && test "$nr" -eq 1 ) &&
+     cmp "test-1-${pack1}.pack" ".git/objects/pack/pack-${pack1}.pack" &&
+     cmp "test-3-${pack1}.idx"  ".git/objects/pack/pack-${pack1}.idx"'
+
+test_expect_success \
+    '[index v2] 2) create a stealth corruption in a delta base reference' \
+    '# this test assumes a delta smaller than 16 bytes at the end of the pack
+     git-show-index <1.idx | sort -n | tail -n 1 | (
+       read delta_offs delta_sha1 delta_crc &&
+       git-cat-file blob "$delta_sha1" > blob_3 &&
+       chmod +w ".git/objects/pack/pack-${pack1}.pack" &&
+       dd of=".git/objects/pack/pack-${pack1}.pack" seek=$(($delta_offs + 1)) \
+         if=".git/objects/pack/pack-${pack1}.idx" skip=$((8 + 256 * 4)) \
+         bs=1 count=20 conv=notrunc &&
+       git-cat-file blob "$delta_sha1" > blob_4 )'
+
+test_expect_failure \
+    '[index v2] 3) corrupted delta happily returned wrong data' \
+    'cmp blob_3 blob_4'
+
+test_expect_failure \
+    '[index v2] 4) confirm that the pack is actually corrupted' \
+    'git-fsck --full $commit'
+
+test_expect_failure \
+    '[index v2] 5) pack-objects refuses to reuse corrupted data' \
+    'git-pack-objects test-5 <obj-list'
+
+test_done
diff --git a/t/t5502-quickfetch.sh b/t/t5502-quickfetch.sh
new file mode 100755 (executable)
index 0000000..b4760f2
--- /dev/null
@@ -0,0 +1,89 @@
+#!/bin/sh
+
+test_description='test quickfetch from local'
+
+. ./test-lib.sh
+
+test_expect_success setup '
+
+       test_tick &&
+       echo ichi >file &&
+       git add file &&
+       git commit -m initial &&
+
+       cnt=$( (
+               git count-objects | sed -e "s/ *objects,.*//"
+       ) ) &&
+       test $cnt -eq 3
+'
+
+test_expect_success 'clone without alternate' '
+
+       (
+               mkdir cloned &&
+               cd cloned &&
+               git init-db &&
+               git remote add -f origin ..
+       ) &&
+       cnt=$( (
+               cd cloned &&
+               git count-objects | sed -e "s/ *objects,.*//"
+       ) ) &&
+       test $cnt -eq 3
+'
+
+test_expect_success 'further commits in the original' '
+
+       test_tick &&
+       echo ni >file &&
+       git commit -a -m second &&
+
+       cnt=$( (
+               git count-objects | sed -e "s/ *objects,.*//"
+       ) ) &&
+       test $cnt -eq 6
+'
+
+test_expect_success 'copy commit and tree but not blob by hand' '
+
+       git rev-list --objects HEAD |
+       git pack-objects --stdout |
+       (
+               cd cloned &&
+               git unpack-objects
+       ) &&
+
+       cnt=$( (
+               cd cloned &&
+               git count-objects | sed -e "s/ *objects,.*//"
+       ) ) &&
+       test $cnt -eq 6
+
+       blob=$(git rev-parse HEAD:file | sed -e "s|..|&/|") &&
+       test -f "cloned/.git/objects/$blob" &&
+       rm -f "cloned/.git/objects/$blob" &&
+
+       cnt=$( (
+               cd cloned &&
+               git count-objects | sed -e "s/ *objects,.*//"
+       ) ) &&
+       test $cnt -eq 5
+
+'
+
+test_expect_success 'quickfetch should not leave a corrupted repository' '
+
+       (
+               cd cloned &&
+               git fetch
+       ) &&
+
+       cnt=$( (
+               cd cloned &&
+               git count-objects | sed -e "s/ *objects,.*//"
+       ) ) &&
+       test $cnt -eq 6
+
+'
+
+test_done
index 7831e3461c3dd7d332db56e9f5828a27009c9460..fcb33027648b8449cba5869cbe808a4e26ff7f2d 100755 (executable)
@@ -163,7 +163,7 @@ test_sequence()
 # the bisection point is the head - this is the bad point.
 #
 
-test_output_expect_success "--bisect l5 ^root" 'git-rev-list $_bisect_option l5 ^root' <<EOF
+test_output_expect_success "$_bisect_option l5 ^root" 'git-rev-list $_bisect_option l5 ^root' <<EOF
 c3
 EOF
 
index 5182dbb15811ae518c1686c0a8f037a84d3cbd06..761f09b1e537ebf9c24171c646e8578d99ce95fa 100755 (executable)
@@ -7,7 +7,8 @@ test_description='git-rev-list trivial path optimization test'
 test_expect_success setup '
 echo Hello > a &&
 git add a &&
-git commit -m "Initial commit" a
+git commit -m "Initial commit" a &&
+initial=$(git rev-parse --verify HEAD)
 '
 
 test_expect_success path-optimization '
@@ -16,4 +17,35 @@ test_expect_success path-optimization '
     test $(git-rev-list $commit -- . | wc -l) = 1
 '
 
+test_expect_success 'further setup' '
+       git checkout -b side &&
+       echo Irrelevant >c &&
+       git add c &&
+       git commit -m "Side makes an irrelevant commit" &&
+       echo "More Irrelevancy" >c &&
+       git add c &&
+       git commit -m "Side makes another irrelevant commit" &&
+       echo Bye >a &&
+       git add a &&
+       git commit -m "Side touches a" &&
+       side=$(git rev-parse --verify HEAD) &&
+       echo "Yet more Irrelevancy" >c &&
+       git add c &&
+       git commit -m "Side makes yet another irrelevant commit" &&
+       git checkout master &&
+       echo Another >b &&
+       git add b &&
+       git commit -m "Master touches b" &&
+       git merge side &&
+       echo Touched >b &&
+       git add b &&
+       git commit -m "Master touches b again"
+'
+
+test_expect_success 'path optimization 2' '
+       ( echo "$side"; echo "$initial" ) >expected &&
+       git rev-list HEAD -- a >actual &&
+       diff -u expected actual
+'
+
 test_done
index b608e202c18fd545868898fcd0528c3501459b6f..e3f7ae8120aa2a46b25dd3830597cb863a5f5e20 100755 (executable)
@@ -47,6 +47,8 @@ git branch white &&
 git branch red &&
 git branch blue &&
 git branch yellow &&
+git branch change &&
+git branch change+rename &&
 
 sed -e "/^g /s/.*/g : master changes a line/" <A >A+ &&
 mv A+ A &&
@@ -77,6 +79,17 @@ rm -f A M &&
 git update-index --add --remove A C M N &&
 git commit -m "blue renames A->C, M->N" &&
 
+git checkout change &&
+sed -e "/^g /s/.*/g : changed line/" <A >A+ &&
+mv A+ A &&
+git commit -q -a -m "changed" &&
+
+git checkout change+rename &&
+sed -e "/^g /s/.*/g : changed line/" <A >B &&
+rm A &&
+git update-index --add B &&
+git commit -q -a -m "changed and renamed" &&
+
 git checkout master'
 
 test_expect_success 'pull renaming branch into unrenaming one' \
@@ -318,4 +331,14 @@ test_expect_success 'interference with untracked working tree file' '
        git reset --hard anchor
 '
 
+test_expect_success 'merge of identical changes in a renamed file' '
+       rm -f A M N
+       git reset --hard &&
+       git checkout change+rename &&
+       GIT_MERGE_VERBOSITY=3 git merge change | grep "^Skipped B" &&
+       git reset --hard HEAD^ &&
+       git checkout change &&
+       GIT_MERGE_VERBOSITY=3 git merge change+rename | grep "^Skipped B"
+'
+
 test_done
old mode 100644 (file)
new mode 100755 (executable)
old mode 100644 (file)
new mode 100755 (executable)
old mode 100644 (file)
new mode 100755 (executable)
diff --git a/t/t6026-merge-attr.sh b/t/t6026-merge-attr.sh
new file mode 100755 (executable)
index 0000000..56fc341
--- /dev/null
@@ -0,0 +1,145 @@
+#!/bin/sh
+#
+# Copyright (c) 2007 Junio C Hamano
+#
+
+test_description='per path merge controlled by merge attribute'
+
+. ./test-lib.sh
+
+test_expect_success setup '
+
+       for f in text binary union
+       do
+               echo Initial >$f && git add $f || break
+       done &&
+       test_tick &&
+       git commit -m Initial &&
+
+       git branch side &&
+       for f in text binary union
+       do
+               echo Master >>$f && git add $f || break
+       done &&
+       test_tick &&
+       git commit -m Master &&
+
+       git checkout side &&
+       for f in text binary union
+       do
+               echo Side >>$f && git add $f || break
+       done &&
+       test_tick &&
+       git commit -m Side &&
+
+       git tag anchor
+'
+
+test_expect_success merge '
+
+       {
+               echo "binary -merge"
+               echo "union merge=union"
+       } >.gitattributes &&
+
+       if git merge master
+       then
+               echo Gaah, should have conflicted
+               false
+       else
+               echo Ok, conflicted.
+       fi
+'
+
+test_expect_success 'check merge result in index' '
+
+       git ls-files -u | grep binary &&
+       git ls-files -u | grep text &&
+       ! (git ls-files -u | grep union)
+
+'
+
+test_expect_success 'check merge result in working tree' '
+
+       git cat-file -p HEAD:binary >binary-orig &&
+       grep "<<<<<<<" text &&
+       cmp binary-orig binary &&
+       ! grep "<<<<<<<" union &&
+       grep Master union &&
+       grep Side union
+
+'
+
+cat >./custom-merge <<\EOF
+#!/bin/sh
+
+orig="$1" ours="$2" theirs="$3" exit="$4"
+(
+       echo "orig is $orig"
+       echo "ours is $ours"
+       echo "theirs is $theirs"
+       echo "=== orig ==="
+       cat "$orig"
+       echo "=== ours ==="
+       cat "$ours"
+       echo "=== theirs ==="
+       cat "$theirs"
+) >"$ours+"
+cat "$ours+" >"$ours"
+rm -f "$ours+"
+exit "$exit"
+EOF
+chmod +x ./custom-merge
+
+test_expect_success 'custom merge backend' '
+
+       echo "* merge=union" >.gitattributes &&
+       echo "text merge=custom" >>.gitattributes &&
+
+       git reset --hard anchor &&
+       git config --replace-all \
+       merge.custom.driver "./custom-merge %O %A %B 0" &&
+       git config --replace-all \
+       merge.custom.name "custom merge driver for testing" &&
+
+       git merge master &&
+
+       cmp binary union &&
+       sed -e 1,3d text >check-1 &&
+       o=$(git-unpack-file master^:text) &&
+       a=$(git-unpack-file side^:text) &&
+       b=$(git-unpack-file master:text) &&
+       sh -c "./custom-merge $o $a $b 0" &&
+       sed -e 1,3d $a >check-2 &&
+       cmp check-1 check-2 &&
+       rm -f $o $a $b
+'
+
+test_expect_success 'custom merge backend' '
+
+       git reset --hard anchor &&
+       git config --replace-all \
+       merge.custom.driver "./custom-merge %O %A %B 1" &&
+       git config --replace-all \
+       merge.custom.name "custom merge driver for testing" &&
+
+       if git merge master
+       then
+               echo "Eh? should have conflicted"
+               false
+       else
+               echo "Ok, conflicted"
+       fi &&
+
+       cmp binary union &&
+       sed -e 1,3d text >check-1 &&
+       o=$(git-unpack-file master^:text) &&
+       a=$(git-unpack-file anchor:text) &&
+       b=$(git-unpack-file master:text) &&
+       sh -c "./custom-merge $o $a $b 0" &&
+       sed -e 1,3d $a >check-2 &&
+       cmp check-1 check-2 &&
+       rm -f $o $a $b
+'
+
+test_done
diff --git a/t/t6030-bisect-porcelain.sh b/t/t6030-bisect-porcelain.sh
new file mode 100755 (executable)
index 0000000..30f6ade
--- /dev/null
@@ -0,0 +1,105 @@
+#!/bin/sh
+#
+# Copyright (c) 2007 Christian Couder
+#
+test_description='Tests git-bisect functionality'
+
+exec </dev/null
+
+. ./test-lib.sh
+
+add_line_into_file()
+{
+    _line=$1
+    _file=$2
+
+    if [ -f "$_file" ]; then
+        echo "$_line" >> $_file || return $?
+        MSG="Add <$_line> into <$_file>."
+    else
+        echo "$_line" > $_file || return $?
+        git add $_file || return $?
+        MSG="Create file <$_file> with <$_line> inside."
+    fi
+
+    test_tick
+    git-commit --quiet -m "$MSG" $_file
+}
+
+HASH1=
+HASH2=
+HASH3=
+HASH4=
+
+test_expect_success 'set up basic repo with 1 file (hello) and 4 commits' '
+     add_line_into_file "1: Hello World" hello &&
+     HASH1=$(git rev-parse --verify HEAD) &&
+     add_line_into_file "2: A new day for git" hello &&
+     HASH2=$(git rev-parse --verify HEAD) &&
+     add_line_into_file "3: Another new day for git" hello &&
+     HASH3=$(git rev-parse --verify HEAD) &&
+     add_line_into_file "4: Ciao for now" hello &&
+     HASH4=$(git rev-parse --verify HEAD)
+'
+
+test_expect_success 'bisect starts with only one bad' '
+       git bisect reset &&
+       git bisect start &&
+       git bisect bad $HASH4 &&
+       git bisect next
+'
+
+test_expect_success 'bisect does not start with only one good' '
+       git bisect reset &&
+       git bisect start &&
+       git bisect good $HASH1 || return 1
+
+       if git bisect next
+       then
+               echo Oops, should have failed.
+               false
+       else
+               :
+       fi
+'
+
+test_expect_success 'bisect start with one bad and good' '
+       git bisect reset &&
+       git bisect start &&
+       git bisect good $HASH1 &&
+       git bisect bad $HASH4 &&
+       git bisect next
+'
+
+# We want to automatically find the commit that
+# introduced "Another" into hello.
+test_expect_success \
+    '"git bisect run" simple case' \
+    'echo "#"\!"/bin/sh" > test_script.sh &&
+     echo "grep Another hello > /dev/null" >> test_script.sh &&
+     echo "test \$? -ne 0" >> test_script.sh &&
+     chmod +x test_script.sh &&
+     git bisect start &&
+     git bisect good $HASH1 &&
+     git bisect bad $HASH4 &&
+     git bisect run ./test_script.sh > my_bisect_log.txt &&
+     grep "$HASH3 is first bad commit" my_bisect_log.txt &&
+     git bisect reset'
+
+# We want to automatically find the commit that
+# introduced "Ciao" into hello.
+test_expect_success \
+    '"git bisect run" with more complex "git bisect start"' \
+    'echo "#"\!"/bin/sh" > test_script.sh &&
+     echo "grep Ciao hello > /dev/null" >> test_script.sh &&
+     echo "test \$? -ne 0" >> test_script.sh &&
+     chmod +x test_script.sh &&
+     git bisect start $HASH4 $HASH1 &&
+     git bisect run ./test_script.sh > my_bisect_log.txt &&
+     grep "$HASH4 is first bad commit" my_bisect_log.txt &&
+     git bisect reset'
+
+#
+#
+test_done
+
diff --git a/t/t6030-bisect-run.sh b/t/t6030-bisect-run.sh
deleted file mode 100755 (executable)
index 39c7228..0000000
+++ /dev/null
@@ -1,57 +0,0 @@
-#!/bin/sh
-#
-# Copyright (c) 2007 Christian Couder
-#
-test_description='Tests git-bisect run functionality'
-
-. ./test-lib.sh
-
-add_line_into_file()
-{
-    _line=$1
-    _file=$2
-
-    if [ -f "$_file" ]; then
-        echo "$_line" >> $_file || return $?
-        MSG="Add <$_line> into <$_file>."
-    else
-        echo "$_line" > $_file || return $?
-        git add $_file || return $?
-        MSG="Create file <$_file> with <$_line> inside."
-    fi
-
-    git-commit -m "$MSG" $_file
-}
-
-HASH1=
-HASH3=
-HASH4=
-
-test_expect_success \
-    'set up basic repo with 1 file (hello) and 4 commits' \
-    'add_line_into_file "1: Hello World" hello &&
-     add_line_into_file "2: A new day for git" hello &&
-     add_line_into_file "3: Another new day for git" hello &&
-     add_line_into_file "4: Ciao for now" hello &&
-     HASH1=$(git rev-list HEAD | tail -1) &&
-     HASH3=$(git rev-list HEAD | head -2 | tail -1) &&
-     HASH4=$(git rev-list HEAD | head -1)'
-
-# We want to automatically find the commit that
-# introduced "Another" into hello.
-test_expect_success \
-    'git bisect run simple case' \
-    'echo "#!/bin/sh" > test_script.sh &&
-     echo "grep Another hello > /dev/null" >> test_script.sh &&
-     echo "test \$? -ne 0" >> test_script.sh &&
-     chmod +x test_script.sh &&
-     git bisect start &&
-     git bisect good $HASH1 &&
-     git bisect bad $HASH4 &&
-     git bisect run ./test_script.sh > my_bisect_log.txt &&
-     grep "$HASH3 is first bad commit" my_bisect_log.txt'
-
-#
-#
-test_done
-
diff --git a/t/t7300-clean.sh b/t/t7300-clean.sh
new file mode 100755 (executable)
index 0000000..de70b38
--- /dev/null
@@ -0,0 +1,180 @@
+#!/bin/sh
+#
+# Copyright (c) 2007 Michael Spang
+#
+
+test_description='git-clean basic tests'
+
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+
+       mkdir -p src &&
+       touch src/part1.c Makefile &&
+       echo build >.gitignore &&
+       echo \*.o >>.gitignore &&
+       git-add . &&
+       git-commit -m setup &&
+       touch src/part2.c README &&
+       git-add .
+
+'
+
+test_expect_success 'git-clean' '
+
+       mkdir -p build docs &&
+       touch a.out src/part3.c docs/manual.txt obj.o build/lib.so &&
+       git-clean &&
+       test -f Makefile &&
+       test -f README &&
+       test -f src/part1.c &&
+       test -f src/part2.c &&
+       test ! -f a.out &&
+       test ! -f src/part3.c &&
+       test -f docs/manual.txt &&
+       test -f obj.o &&
+       test -f build/lib.so
+
+'
+
+test_expect_success 'git-clean -n' '
+
+       mkdir -p build docs &&
+       touch a.out src/part3.c docs/manual.txt obj.o build/lib.so &&
+       git-clean -n &&
+       test -f Makefile &&
+       test -f README &&
+       test -f src/part1.c &&
+       test -f src/part2.c &&
+       test -f a.out &&
+       test -f src/part3.c &&
+       test -f docs/manual.txt &&
+       test -f obj.o &&
+       test -f build/lib.so
+
+'
+
+test_expect_success 'git-clean -d' '
+
+       mkdir -p build docs &&
+       touch a.out src/part3.c docs/manual.txt obj.o build/lib.so &&
+       git-clean -d &&
+       test -f Makefile &&
+       test -f README &&
+       test -f src/part1.c &&
+       test -f src/part2.c &&
+       test ! -f a.out &&
+       test ! -f src/part3.c &&
+       test ! -d docs &&
+       test -f obj.o &&
+       test -f build/lib.so
+
+'
+
+test_expect_success 'git-clean -x' '
+
+       mkdir -p build docs &&
+       touch a.out src/part3.c docs/manual.txt obj.o build/lib.so &&
+       git-clean -x &&
+       test -f Makefile &&
+       test -f README &&
+       test -f src/part1.c &&
+       test -f src/part2.c &&
+       test ! -f a.out &&
+       test ! -f src/part3.c &&
+       test -f docs/manual.txt &&
+       test ! -f obj.o &&
+       test -f build/lib.so
+
+'
+
+test_expect_success 'git-clean -d -x' '
+
+       mkdir -p build docs &&
+       touch a.out src/part3.c docs/manual.txt obj.o build/lib.so &&
+       git-clean -d -x &&
+       test -f Makefile &&
+       test -f README &&
+       test -f src/part1.c &&
+       test -f src/part2.c &&
+       test ! -f a.out &&
+       test ! -f src/part3.c &&
+       test ! -d docs &&
+       test ! -f obj.o &&
+       test ! -d build
+
+'
+
+test_expect_success 'git-clean -X' '
+
+       mkdir -p build docs &&
+       touch a.out src/part3.c docs/manual.txt obj.o build/lib.so &&
+       git-clean -X &&
+       test -f Makefile &&
+       test -f README &&
+       test -f src/part1.c &&
+       test -f src/part2.c &&
+       test -f a.out &&
+       test -f src/part3.c &&
+       test -f docs/manual.txt &&
+       test ! -f obj.o &&
+       test -f build/lib.so
+
+'
+
+test_expect_success 'git-clean -d -X' '
+
+       mkdir -p build docs &&
+       touch a.out src/part3.c docs/manual.txt obj.o build/lib.so &&
+       git-clean -d -X &&
+       test -f Makefile &&
+       test -f README &&
+       test -f src/part1.c &&
+       test -f src/part2.c &&
+       test -f a.out &&
+       test -f src/part3.c &&
+       test -f docs/manual.txt &&
+       test ! -f obj.o &&
+       test ! -d build
+
+'
+
+test_expect_success 'clean.requireForce' '
+
+       git-config clean.requireForce true &&
+       ! git-clean
+
+'
+
+test_expect_success 'clean.requireForce and -n' '
+
+       mkdir -p build docs &&
+       touch a.out src/part3.c docs/manual.txt obj.o build/lib.so &&
+       git-clean -n &&
+       test -f Makefile &&
+       test -f README &&
+       test -f src/part1.c &&
+       test -f src/part2.c &&
+       test -f a.out &&
+       test -f src/part3.c &&
+       test -f docs/manual.txt &&
+       test -f obj.o &&
+       test -f build/lib.so
+
+'
+
+test_expect_success 'clean.requireForce and -f' '
+
+       git-clean -f &&
+       test -f README &&
+       test -f src/part1.c &&
+       test -f src/part2.c &&
+       test ! -f a.out &&
+       test ! -f src/part3.c &&
+       test -f docs/manual.txt &&
+       test -f obj.o &&
+       test -f build/lib.so
+
+'
+
+test_done
diff --git a/t/t9400-git-cvsserver-server.sh b/t/t9400-git-cvsserver-server.sh
new file mode 100755 (executable)
index 0000000..d406a88
--- /dev/null
@@ -0,0 +1,126 @@
+#!/bin/sh
+#
+# Copyright (c) 2007 Frank Lichtenheld
+#
+
+test_description='git-cvsserver access
+
+tests read access to a git repository with the
+cvs CLI client via git-cvsserver server'
+
+. ./test-lib.sh
+
+cvs >/dev/null 2>&1
+if test $? -ne 1
+then
+    test_expect_success 'skipping git-cvsserver tests, cvs not found' :
+    test_done
+    exit
+fi
+perl -e 'use DBI; use DBD::SQLite' >/dev/null 2>&1 || {
+    test_expect_success 'skipping git-cvsserver tests, Perl SQLite interface unavailable' :
+    test_done
+    exit
+}
+
+unset GIT_DIR GIT_CONFIG
+WORKDIR=$(pwd)
+SERVERDIR=$(pwd)/gitcvs.git
+git_config="$SERVERDIR/config"
+CVSROOT=":fork:$SERVERDIR"
+CVSWORK="$(pwd)/cvswork"
+CVS_SERVER=git-cvsserver
+export CVSROOT CVS_SERVER
+
+rm -rf "$CVSWORK" "$SERVERDIR"
+echo >empty &&
+  git add empty &&
+  git commit -q -m "First Commit" &&
+  git clone -q --local --bare "$WORKDIR/.git" "$SERVERDIR" >/dev/null 2>&1 &&
+  GIT_DIR="$SERVERDIR" git config --bool gitcvs.enabled true &&
+  GIT_DIR="$SERVERDIR" git config --bool gitcvs.logfile "$SERVERDIR/gitcvs.log" ||
+  exit 1
+
+# note that cvs doesn't accept absolute pathnames
+# as argument to co -d
+test_expect_success 'basic checkout' \
+  'GIT_CONFIG="$git_config" cvs -Q co -d cvswork master &&
+   test "$(echo $(grep -v ^D cvswork/CVS/Entries|cut -d/ -f2,3,5))" = "empty/1.1/"'
+
+test_expect_success 'cvs update (create new file)' \
+  'echo testfile1 >testfile1 &&
+   git add testfile1 &&
+   git commit -q -m "Add testfile1" &&
+   git push gitcvs.git >/dev/null &&
+   cd cvswork &&
+   GIT_CONFIG="$git_config" cvs -Q update &&
+   test "$(echo $(grep testfile1 CVS/Entries|cut -d/ -f2,3,5))" = "testfile1/1.1/" &&
+   diff -q testfile1 ../testfile1'
+
+cd "$WORKDIR"
+test_expect_success 'cvs update (update existing file)' \
+  'echo line 2 >>testfile1 &&
+   git add testfile1 &&
+   git commit -q -m "Append to testfile1" &&
+   git push gitcvs.git >/dev/null &&
+   cd cvswork &&
+   GIT_CONFIG="$git_config" cvs -Q update &&
+   test "$(echo $(grep testfile1 CVS/Entries|cut -d/ -f2,3,5))" = "testfile1/1.2/" &&
+   diff -q testfile1 ../testfile1'
+
+cd "$WORKDIR"
+#TODO: cvsserver doesn't support update w/o -d
+test_expect_failure "cvs update w/o -d doesn't create subdir (TODO)" \
+  'mkdir test &&
+   echo >test/empty &&
+   git add test &&
+   git commit -q -m "Single Subdirectory" &&
+   git push gitcvs.git >/dev/null &&
+   cd cvswork &&
+   GIT_CONFIG="$git_config" cvs -Q update &&
+   test ! -d test'
+
+cd "$WORKDIR"
+test_expect_success 'cvs update (subdirectories)' \
+  '(for dir in A A/B A/B/C A/D E; do
+      mkdir $dir &&
+      echo "test file in $dir" >"$dir/file_in_$(echo $dir|sed -e "s#/# #g")"  &&
+      git add $dir;
+   done) &&
+   git commit -q -m "deep sub directory structure" &&
+   git push gitcvs.git >/dev/null &&
+   cd cvswork &&
+   GIT_CONFIG="$git_config" cvs -Q update -d &&
+   (for dir in A A/B A/B/C A/D E; do
+      filename="file_in_$(echo $dir|sed -e "s#/# #g")" &&
+      if test "$(echo $(grep -v ^D $dir/CVS/Entries|cut -d/ -f2,3,5))" = "$filename/1.1/" &&
+           diff -q "$dir/$filename" "../$dir/$filename"; then
+        :
+      else
+        echo >failure
+      fi
+    done) &&
+   test ! -f failure'
+
+cd "$WORKDIR"
+test_expect_success 'cvs update (delete file)' \
+  'git rm testfile1 &&
+   git commit -q -m "Remove testfile1" &&
+   git push gitcvs.git >/dev/null &&
+   cd cvswork &&
+   GIT_CONFIG="$git_config" cvs -Q update &&
+   test -z "$(grep testfile1 CVS/Entries)" &&
+   test ! -f testfile1'
+
+cd "$WORKDIR"
+test_expect_success 'cvs update (re-add deleted file)' \
+  'echo readded testfile >testfile1 &&
+   git add testfile1 &&
+   git commit -q -m "Re-Add testfile1" &&
+   git push gitcvs.git >/dev/null &&
+   cd cvswork &&
+   GIT_CONFIG="$git_config" cvs -Q update &&
+   test "$(echo $(grep testfile1 CVS/Entries|cut -d/ -f2,3,5))" = "testfile1/1.4/" &&
+   diff -q testfile1 ../testfile1'
+
+test_done
old mode 100755 (executable)
new mode 100644 (file)
index c075474..dee3ad7
@@ -16,6 +16,7 @@ unset AUTHOR_EMAIL
 unset AUTHOR_NAME
 unset COMMIT_AUTHOR_EMAIL
 unset COMMIT_AUTHOR_NAME
+unset EMAIL
 unset GIT_ALTERNATE_OBJECT_DIRECTORIES
 unset GIT_AUTHOR_DATE
 GIT_AUTHOR_EMAIL=author@example.com
@@ -36,6 +37,10 @@ export GIT_AUTHOR_EMAIL GIT_AUTHOR_NAME
 export GIT_COMMITTER_EMAIL GIT_COMMITTER_NAME
 export EDITOR VISUAL
 
+# Protect ourselves from common misconfiguration to export
+# CDPATH into the environment
+unset CDPATH
+
 case $(echo $GIT_TRACE |tr "[A-Z]" "[a-z]") in
        1|2|true)
                echo "* warning: Some tests will not work if GIT_TRACE" \
diff --git a/tag.c b/tag.c
index 56a49f4fe1f705ee70bc5318a504c35d1bce963e..330d287924765c95dac428f040b104930c316b81 100644 (file)
--- a/tag.c
+++ b/tag.c
@@ -20,13 +20,9 @@ struct object *deref_tag(struct object *o, const char *warn, int warnlen)
 
 struct tag *lookup_tag(const unsigned char *sha1)
 {
-        struct object *obj = lookup_object(sha1);
-        if (!obj) {
-                struct tag *ret = alloc_tag_node();
-                created_object(sha1, &ret->object);
-                ret->object.type = OBJ_TAG;
-                return ret;
-        }
+       struct object *obj = lookup_object(sha1);
+       if (!obj)
+               return create_object(sha1, OBJ_TAG, alloc_tag_node());
        if (!obj->type)
                obj->type = OBJ_TAG;
         if (obj->type != OBJ_TAG) {
diff --git a/test-genrandom.c b/test-genrandom.c
new file mode 100644 (file)
index 0000000..8cefe6c
--- /dev/null
@@ -0,0 +1,34 @@
+/*
+ * Simple random data generator used to create reproducible test files.
+ * This is inspired from POSIX.1-2001 implementation example for rand().
+ * Copyright (C) 2007 by Nicolas Pitre, licensed under the GPL version 2.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+int main(int argc, char *argv[])
+{
+       unsigned long count, next = 0;
+       unsigned char *c;
+
+       if (argc < 2 || argc > 3) {
+               fprintf( stderr, "Usage: %s <seed_string> [<size>]", argv[0]);
+               return 1;
+       }
+
+       c = (unsigned char *) argv[1];
+       do {
+               next = next * 11 + *c;
+       } while (*c++);
+
+       count = (argc == 3) ? strtoul(argv[2], NULL, 0) : -1L;
+
+       while (count--) {
+               next = next * 1103515245 + 12345;
+               if (putchar((next >> 16) & 0xff) == EOF)
+                       return -1;
+       }
+
+       return 0;
+}
diff --git a/test-match-trees.c b/test-match-trees.c
new file mode 100644 (file)
index 0000000..a3c4688
--- /dev/null
@@ -0,0 +1,24 @@
+#include "cache.h"
+#include "tree.h"
+
+int main(int ac, char **av)
+{
+       unsigned char hash1[20], hash2[20], shifted[20];
+       struct tree *one, *two;
+
+       if (get_sha1(av[1], hash1))
+               die("cannot parse %s as an object name", av[1]);
+       if (get_sha1(av[2], hash2))
+               die("cannot parse %s as an object name", av[2]);
+       one = parse_tree_indirect(hash1);
+       if (!one)
+               die("not a treeish %s", av[1]);
+       two = parse_tree_indirect(hash2);
+       if (!two)
+               die("not a treeish %s", av[2]);
+
+       shift_tree(one->object.sha1, two->object.sha1, shifted, -1);
+       printf("shifted: %s\n", sha1_to_hex(shifted));
+
+       exit(0);
+}
diff --git a/tree.c b/tree.c
index d188c0fbaee110a17ca7a0d16dcc979091f44ded..e4a39aa3c36964c9034d2393a8b9cb483d1543da 100644 (file)
--- a/tree.c
+++ b/tree.c
@@ -127,12 +127,8 @@ int read_tree(struct tree *tree, int stage, const char **match)
 struct tree *lookup_tree(const unsigned char *sha1)
 {
        struct object *obj = lookup_object(sha1);
-       if (!obj) {
-               struct tree *ret = alloc_tree_node();
-               created_object(sha1, &ret->object);
-               ret->object.type = OBJ_TREE;
-               return ret;
-       }
+       if (!obj)
+               return create_object(sha1, OBJ_TREE, alloc_tree_node());
        if (!obj->type)
                obj->type = OBJ_TREE;
        if (obj->type != OBJ_TREE) {
@@ -143,6 +139,14 @@ struct tree *lookup_tree(const unsigned char *sha1)
        return (struct tree *) obj;
 }
 
+/*
+ * NOTE! Tree refs to external git repositories
+ * (ie gitlinks) do not count as real references.
+ *
+ * You don't have to have those repositories
+ * available at all, much less have the objects
+ * accessible from the current repository.
+ */
 static void track_tree_refs(struct tree *item)
 {
        int n_refs = 0, i;
@@ -152,8 +156,11 @@ static void track_tree_refs(struct tree *item)
 
        /* Count how many entries there are.. */
        init_tree_desc(&desc, item->buffer, item->size);
-       while (tree_entry(&desc, &entry))
+       while (tree_entry(&desc, &entry)) {
+               if (S_ISDIRLNK(entry.mode))
+                       continue;
                n_refs++;
+       }
 
        /* Allocate object refs and walk it again.. */
        i = 0;
@@ -162,6 +169,8 @@ static void track_tree_refs(struct tree *item)
        while (tree_entry(&desc, &entry)) {
                struct object *obj;
 
+               if (S_ISDIRLNK(entry.mode))
+                       continue;
                if (S_ISDIR(entry.mode))
                        obj = &lookup_tree(entry.sha1)->object;
                else
index ee10eea24cdd37d308066721c947b2d2449e786e..906ce69ea6daf98cdb4f9dd3d6498264159b6b76 100644 (file)
@@ -4,6 +4,7 @@
 #include "tree-walk.h"
 #include "cache-tree.h"
 #include "unpack-trees.h"
+#include "progress.h"
 
 #define DBRT_DEBUG 1
 
@@ -70,7 +71,6 @@ static int entcmp(const char *name1, int dir1, const char *name2, int dir2)
 
 static int unpack_trees_rec(struct tree_entry_list **posns, int len,
                            const char *base, struct unpack_trees_options *o,
-                           int *indpos,
                            struct tree_entry_list *df_conflict_list)
 {
        int baselen = strlen(base);
@@ -100,7 +100,7 @@ static int unpack_trees_rec(struct tree_entry_list **posns, int len,
                cache_name = NULL;
 
                /* Check the cache */
-               if (o->merge && *indpos < active_nr) {
+               if (o->merge && o->pos < active_nr) {
                        /* This is a bit tricky: */
                        /* If the index has a subdirectory (with
                         * contents) as the first name, it'll get a
@@ -118,7 +118,7 @@ static int unpack_trees_rec(struct tree_entry_list **posns, int len,
                         * file case.
                         */
 
-                       cache_name = active_cache[*indpos]->name;
+                       cache_name = active_cache[o->pos]->name;
                        if (strlen(cache_name) > baselen &&
                            !memcmp(cache_name, base, baselen)) {
                                cache_name += baselen;
@@ -158,8 +158,8 @@ static int unpack_trees_rec(struct tree_entry_list **posns, int len,
 
                if (cache_name && !strcmp(cache_name, first)) {
                        any_files = 1;
-                       src[0] = active_cache[*indpos];
-                       remove_cache_entry_at(*indpos);
+                       src[0] = active_cache[o->pos];
+                       remove_cache_entry_at(o->pos);
                }
 
                for (i = 0; i < len; i++) {
@@ -228,7 +228,7 @@ static int unpack_trees_rec(struct tree_entry_list **posns, int len,
 #if DBRT_DEBUG > 1
                                printf("Added %d entries\n", ret);
 #endif
-                               *indpos += ret;
+                               o->pos += ret;
                        } else {
                                for (i = 0; i < src_size; i++) {
                                        if (src[i]) {
@@ -244,7 +244,7 @@ static int unpack_trees_rec(struct tree_entry_list **posns, int len,
                        newbase[baselen + pathlen] = '/';
                        newbase[baselen + pathlen + 1] = '\0';
                        if (unpack_trees_rec(subposns, len, newbase, o,
-                                            indpos, df_conflict_list)) {
+                                            df_conflict_list)) {
                                retval = -1;
                                goto leave_directory;
                        }
@@ -264,10 +264,12 @@ static int unpack_trees_rec(struct tree_entry_list **posns, int len,
  * directories, in case this unlink is the removal of the
  * last entry in the directory -- empty directories are removed.
  */
-static void unlink_entry(char *name)
+static void unlink_entry(char *name, char *last_symlink)
 {
        char *cp, *prev;
 
+       if (has_symlink_leading_path(name, last_symlink))
+               return;
        if (unlink(name))
                return;
        prev = NULL;
@@ -289,36 +291,14 @@ static void unlink_entry(char *name)
        }
 }
 
-static volatile sig_atomic_t progress_update;
-
-static void progress_interval(int signum)
-{
-       progress_update = 1;
-}
-
-static void setup_progress_signal(void)
-{
-       struct sigaction sa;
-       struct itimerval v;
-
-       memset(&sa, 0, sizeof(sa));
-       sa.sa_handler = progress_interval;
-       sigemptyset(&sa.sa_mask);
-       sa.sa_flags = SA_RESTART;
-       sigaction(SIGALRM, &sa, NULL);
-
-       v.it_interval.tv_sec = 1;
-       v.it_interval.tv_usec = 0;
-       v.it_value = v.it_interval;
-       setitimer(ITIMER_REAL, &v, NULL);
-}
-
 static struct checkout state;
 static void check_updates(struct cache_entry **src, int nr,
-               struct unpack_trees_options *o)
+                       struct unpack_trees_options *o)
 {
        unsigned short mask = htons(CE_UPDATE);
-       unsigned last_percent = 200, cnt = 0, total = 0;
+       unsigned cnt = 0, total = 0;
+       struct progress progress;
+       char last_symlink[PATH_MAX];
 
        if (o->update && o->verbose_update) {
                for (total = cnt = 0; cnt < nr; cnt++) {
@@ -327,55 +307,37 @@ static void check_updates(struct cache_entry **src, int nr,
                                total++;
                }
 
-               /* Don't bother doing this for very small updates */
-               if (total < 250)
-                       total = 0;
-
-               if (total) {
-                       fprintf(stderr, "Checking files out...\n");
-                       setup_progress_signal();
-                       progress_update = 1;
-               }
+               start_progress_delay(&progress, "Checking %u files out...",
+                                    "", total, 50, 2);
                cnt = 0;
        }
 
+       *last_symlink = '\0';
        while (nr--) {
                struct cache_entry *ce = *src++;
 
-               if (total) {
-                       if (!ce->ce_mode || ce->ce_flags & mask) {
-                               unsigned percent;
-                               cnt++;
-                               percent = (cnt * 100) / total;
-                               if (percent != last_percent ||
-                                   progress_update) {
-                                       fprintf(stderr, "%4u%% (%u/%u) done\r",
-                                               percent, cnt, total);
-                                       last_percent = percent;
-                                       progress_update = 0;
-                               }
-                       }
-               }
+               if (total)
+                       if (!ce->ce_mode || ce->ce_flags & mask)
+                               display_progress(&progress, ++cnt);
                if (!ce->ce_mode) {
                        if (o->update)
-                               unlink_entry(ce->name);
+                               unlink_entry(ce->name, last_symlink);
                        continue;
                }
                if (ce->ce_flags & mask) {
                        ce->ce_flags &= ~mask;
-                       if (o->update)
+                       if (o->update) {
                                checkout_entry(ce, &state, NULL);
+                               *last_symlink = '\0';
+                       }
                }
        }
-       if (total) {
-               signal(SIGALRM, SIG_IGN);
-               fputc('\n', stderr);
-       }
+       if (total)
+               stop_progress(&progress);;
 }
 
 int unpack_trees(struct object_list *trees, struct unpack_trees_options *o)
 {
-       int indpos = 0;
        unsigned len = object_list_length(trees);
        struct tree_entry_list **posns;
        int i;
@@ -404,7 +366,7 @@ int unpack_trees(struct object_list *trees, struct unpack_trees_options *o)
                        posn = posn->next;
                }
                if (unpack_trees_rec(posns, len, o->prefix ? o->prefix : "",
-                                    o, &indpos, &df_conflict_list))
+                                    o, &df_conflict_list))
                        return -1;
        }
 
@@ -467,6 +429,64 @@ static void invalidate_ce_path(struct cache_entry *ce)
                cache_tree_invalidate_path(active_cache_tree, ce->name);
 }
 
+static int verify_clean_subdirectory(const char *path, const char *action,
+                                     struct unpack_trees_options *o)
+{
+       /*
+        * we are about to extract "path"; we would not want to lose
+        * anything in the existing directory there.
+        */
+       int namelen;
+       int pos, i;
+       struct dir_struct d;
+       char *pathbuf;
+       int cnt = 0;
+
+       /*
+        * First let's make sure we do not have a local modification
+        * in that directory.
+        */
+       namelen = strlen(path);
+       pos = cache_name_pos(path, namelen);
+       if (0 <= pos)
+               return cnt; /* we have it as nondirectory */
+       pos = -pos - 1;
+       for (i = pos; i < active_nr; i++) {
+               struct cache_entry *ce = active_cache[i];
+               int len = ce_namelen(ce);
+               if (len < namelen ||
+                   strncmp(path, ce->name, namelen) ||
+                   ce->name[namelen] != '/')
+                       break;
+               /*
+                * ce->name is an entry in the subdirectory.
+                */
+               if (!ce_stage(ce)) {
+                       verify_uptodate(ce, o);
+                       ce->ce_mode = 0;
+               }
+               cnt++;
+       }
+
+       /*
+        * Then we need to make sure that we do not lose a locally
+        * present file that is not ignored.
+        */
+       pathbuf = xmalloc(namelen + 2);
+       memcpy(pathbuf, path, namelen);
+       strcpy(pathbuf+namelen, "/");
+
+       memset(&d, 0, sizeof(d));
+       if (o->dir)
+               d.exclude_per_dir = o->dir->exclude_per_dir;
+       i = read_directory(&d, path, pathbuf, namelen+1, NULL);
+       if (i)
+               die("Updating '%s' would lose untracked files in it",
+                   path);
+       free(pathbuf);
+       return cnt;
+}
+
 /*
  * We do not want to remove or overwrite a working tree file that
  * is not tracked, unless it is ignored.
@@ -478,9 +498,62 @@ static void verify_absent(const char *path, const char *action,
 
        if (o->index_only || o->reset || !o->update)
                return;
-       if (!lstat(path, &st) && !(o->dir && excluded(o->dir, path)))
+
+       if (!lstat(path, &st)) {
+               int cnt;
+
+               if (o->dir && excluded(o->dir, path))
+                       /*
+                        * path is explicitly excluded, so it is Ok to
+                        * overwrite it.
+                        */
+                       return;
+               if (S_ISDIR(st.st_mode)) {
+                       /*
+                        * We are checking out path "foo" and
+                        * found "foo/." in the working tree.
+                        * This is tricky -- if we have modified
+                        * files that are in "foo/" we would lose
+                        * it.
+                        */
+                       cnt = verify_clean_subdirectory(path, action, o);
+
+                       /*
+                        * If this removed entries from the index,
+                        * what that means is:
+                        *
+                        * (1) the caller unpack_trees_rec() saw path/foo
+                        * in the index, and it has not removed it because
+                        * it thinks it is handling 'path' as blob with
+                        * D/F conflict;
+                        * (2) we will return "ok, we placed a merged entry
+                        * in the index" which would cause o->pos to be
+                        * incremented by one;
+                        * (3) however, original o->pos now has 'path/foo'
+                        * marked with "to be removed".
+                        *
+                        * We need to increment it by the number of
+                        * deleted entries here.
+                        */
+                       o->pos += cnt;
+                       return;
+               }
+
+               /*
+                * The previous round may already have decided to
+                * delete this path, which is in a subdirectory that
+                * is being replaced with a blob.
+                */
+               cnt = cache_name_pos(path, strlen(path));
+               if (0 <= cnt) {
+                       struct cache_entry *ce = active_cache[cnt];
+                       if (!ce_stage(ce) && !ce->ce_mode)
+                               return;
+               }
+
                die("Untracked working tree file '%s' "
                    "would be %s by merge.", path, action);
+       }
 }
 
 static int merged_entry(struct cache_entry *merge, struct cache_entry *old,
@@ -525,7 +598,7 @@ static int deleted_entry(struct cache_entry *ce, struct cache_entry *old,
        return 1;
 }
 
-static int keep_entry(struct cache_entry *ce)
+static int keep_entry(struct cache_entry *ce, struct unpack_trees_options *o)
 {
        add_cache_entry(ce, ADD_CACHE_OK_TO_ADD);
        return 1;
@@ -556,7 +629,6 @@ int threeway_merge(struct cache_entry **stages,
        int count;
        int head_match = 0;
        int remote_match = 0;
-       const char *path = NULL;
 
        int df_conflict_head = 0;
        int df_conflict_remote = 0;
@@ -566,13 +638,10 @@ int threeway_merge(struct cache_entry **stages,
        int i;
 
        for (i = 1; i < o->head_idx; i++) {
-               if (!stages[i])
+               if (!stages[i] || stages[i] == o->df_conflict_entry)
                        any_anc_missing = 1;
-               else {
-                       if (!path)
-                               path = stages[i]->name;
+               else
                        no_anc_exists = 0;
-               }
        }
 
        index = stages[0];
@@ -588,13 +657,6 @@ int threeway_merge(struct cache_entry **stages,
                remote = NULL;
        }
 
-       if (!path && index)
-               path = index->name;
-       if (!path && head)
-               path = head->name;
-       if (!path && remote)
-               path = remote->name;
-
        /* First, if there's a #16 situation, note that to prevent #13
         * and #14.
         */
@@ -646,6 +708,23 @@ int threeway_merge(struct cache_entry **stages,
        if (o->aggressive) {
                int head_deleted = !head && !df_conflict_head;
                int remote_deleted = !remote && !df_conflict_remote;
+               const char *path = NULL;
+
+               if (index)
+                       path = index->name;
+               else if (head)
+                       path = head->name;
+               else if (remote)
+                       path = remote->name;
+               else {
+                       for (i = 1; i < o->head_idx; i++) {
+                               if (stages[i] && stages[i] != o->df_conflict_entry) {
+                                       path = stages[i]->name;
+                                       break;
+                               }
+                       }
+               }
+
                /*
                 * Deleted in both.
                 * Deleted in one and unchanged in the other.
@@ -677,12 +756,12 @@ int threeway_merge(struct cache_entry **stages,
 
        o->nontrivial_merge = 1;
 
-       /* #2, #3, #4, #6, #7, #9, #11. */
+       /* #2, #3, #4, #6, #7, #9, #10, #11. */
        count = 0;
        if (!head_match || !remote_match) {
                for (i = 1; i < o->head_idx; i++) {
-                       if (stages[i]) {
-                               keep_entry(stages[i]);
+                       if (stages[i] && stages[i] != o->df_conflict_entry) {
+                               keep_entry(stages[i], o);
                                count++;
                                break;
                        }
@@ -695,8 +774,8 @@ int threeway_merge(struct cache_entry **stages,
                show_stage_entry(stderr, "remote ", stages[remote_match]);
        }
 #endif
-       if (head) { count += keep_entry(head); }
-       if (remote) { count += keep_entry(remote); }
+       if (head) { count += keep_entry(head, o); }
+       if (remote) { count += keep_entry(remote, o); }
        return count;
 }
 
@@ -713,12 +792,18 @@ int twoway_merge(struct cache_entry **src,
                struct unpack_trees_options *o)
 {
        struct cache_entry *current = src[0];
-       struct cache_entry *oldtree = src[1], *newtree = src[2];
+       struct cache_entry *oldtree = src[1];
+       struct cache_entry *newtree = src[2];
 
        if (o->merge_size != 2)
                return error("Cannot do a twoway merge of %d trees",
                             o->merge_size);
 
+       if (oldtree == o->df_conflict_entry)
+               oldtree = NULL;
+       if (newtree == o->df_conflict_entry)
+               newtree = NULL;
+
        if (current) {
                if ((!oldtree && !newtree) || /* 4 and 5 */
                    (!oldtree && newtree &&
@@ -726,9 +811,9 @@ int twoway_merge(struct cache_entry **src,
                    (oldtree && newtree &&
                     same(oldtree, newtree)) || /* 14 and 15 */
                    (oldtree && newtree &&
-                    !same(oldtree, newtree) && /* 18 and 19*/
+                    !same(oldtree, newtree) && /* 18 and 19 */
                     same(current, newtree))) {
-                       return keep_entry(current);
+                       return keep_entry(current, o);
                }
                else if (oldtree && !newtree && same(current, oldtree)) {
                        /* 10 or 11 */
@@ -774,7 +859,7 @@ int bind_merge(struct cache_entry **src,
        if (a && old)
                die("Entry '%s' overlaps.  Cannot bind.", a->name);
        if (!a)
-               return keep_entry(old);
+               return keep_entry(old, o);
        else
                return merged_entry(a, NULL, o);
 }
@@ -804,7 +889,7 @@ int oneway_merge(struct cache_entry **src,
                            ce_match_stat(old, &st, 1))
                                old->ce_flags |= htons(CE_UPDATE);
                }
-               return keep_entry(old);
+               return keep_entry(old, o);
        }
        return merged_entry(a, old, o);
 }
index 191f7442f10683c8043288eece36f39166fedc95..fee7da43822b63e5b1f24444e5c51c43d3ff5760 100644 (file)
@@ -16,6 +16,7 @@ struct unpack_trees_options {
        int verbose_update;
        int aggressive;
        const char *prefix;
+       int pos;
        struct dir_struct *dir;
        merge_fn_t fn;
 
index a25632bc87867748016e32a4ba4652918c8705a3..a0559905a0b7072f4a4b44ea321c1316cfc84414 100644 (file)
@@ -260,7 +260,7 @@ static void wt_status_print_untracked(struct wt_status *s)
        if (file_exists(x))
                add_excludes_from_file(&dir, x);
 
-       read_directory(&dir, ".", "", 0);
+       read_directory(&dir, ".", "", 0, NULL);
        for(i = 0; i < dir.nr; i++) {
                /* check for matching entry, which is unmerged; lifted from
                 * builtin-ls-files:show_other_files */