Merge branch 'lt/objalloc'
authorJunio C Hamano <junkio@cox.net>
Sun, 22 Apr 2007 00:41:40 +0000 (17:41 -0700)
committerJunio C Hamano <junkio@cox.net>
Sun, 22 Apr 2007 00:42:02 +0000 (17:42 -0700)
* 'lt/objalloc':
Clean up object creation to use more common code
Use proper object allocators for unknown object nodes too

87 files changed:
.gitignore
Documentation/Makefile
Documentation/RelNotes-1.5.1.1.txt
Documentation/RelNotes-1.5.1.2.txt [new file with mode: 0644]
Documentation/RelNotes-1.5.2.txt
Documentation/cmd-list.perl
Documentation/config.txt
Documentation/git-archive.txt
Documentation/git-check-attr.txt [new file with mode: 0644]
Documentation/git-cherry-pick.txt
Documentation/git-config.txt
Documentation/git-cvsserver.txt
Documentation/git-index-pack.txt
Documentation/git-pack-objects.txt
Documentation/git-rm.txt
Documentation/git-shortlog.txt
Documentation/git-tar-tree.txt
Documentation/gitattributes.txt [new file with mode: 0644]
Documentation/tutorial.txt
GIT-VERSION-GEN
Makefile
attr.c [new file with mode: 0644]
attr.h [new file with mode: 0644]
builtin-add.c
builtin-apply.c
builtin-check-attr.c [new file with mode: 0644]
builtin-config.c
builtin-count-objects.c
builtin-fetch--tool.c
builtin-fsck.c
builtin-ls-files.c
builtin-ls-tree.c
builtin-pack-objects.c
builtin-rev-list.c
builtin-rm.c
builtin-unpack-objects.c
builtin-update-index.c
builtin.h
cache-tree.c
cache.h
contrib/emacs/git.el
contrib/gitview/gitview
contrib/hooks/update-paranoid [new file with mode: 0644]
convert-objects.c
convert.c
csum-file.c
csum-file.h
decorate.c
diff-lib.c
diff.c
dir.c
dir.h
entry.c
git-clone.sh
git-compat-util.h
git-cvsserver.perl
git-fetch.sh
git-gui/Makefile
git-gui/git-gui.sh
git-svn.perl
git.c
index-pack.c
list-objects.c
lockfile.c
merge-recursive.c
pack-check.c
pack-redundant.c
perl/Makefile.PL
read-cache.c
refs.c
refs.h
sha1_file.c
sha1_name.c
show-index.c
t/t0020-crlf.sh
t/t1000-read-tree-m-3way.sh
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/t4121-apply-diffs.sh [new file with mode: 0755]
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/t6026-merge-attr.sh [new file with mode: 0755]
test-genrandom.c [new file with mode: 0644]
tree.c
unpack-trees.c
index 9229e918cd1c8a43b6c73b0083ddfe973b1e3b88..4dc0c395fa7d6e7cff7588ee66c928c954cc014d 100644 (file)
@@ -16,6 +16,7 @@ git-blame
 git-branch
 git-bundle
 git-cat-file
+git-check-attr
 git-check-ref-format
 git-checkout
 git-checkout-index
@@ -149,6 +150,7 @@ test-chmtime
 test-date
 test-delta
 test-dump-cache-tree
+test-genrandom
 test-match-trees
 common-cmds.h
 *.tar.gz
index a637d8d559b6a41505e5381b92d523e55f4b3be8..8d3617db9772249a1e9d7bc242d7963672081acb 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,$(MAN1_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,15 +56,19 @@ 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)
 
 
@@ -99,7 +106,7 @@ cmd-list.made: cmd-list.perl $(MAN1_TXT)
 git.7 git.html: git.txt core-intro.txt
 
 clean:
-       rm -f *.xml *.xml+ *.html *.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
@@ -109,7 +116,7 @@ clean:
                sed -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' >$@+
        mv $@+ $@
 
-%.1 %.7 : %.xml
+%.1 %.5 %.7 : %.xml
        xmlto -m callouts.xsl man $<
 
 %.xml : %.txt
index 3054b5a3f003ad7a223d10910917a99ed4a76fd0..91471213bdec8d95209db256f594b0e1b3f3ec2a 100644 (file)
@@ -59,3 +59,7 @@ Fixes since v1.5.1
 
   - git-svn dcommit and rebase was confused by patches that were
     merged from another branch that is managed by git-svn.
+
+  - git-svn used to get confused when globbing remote branch/tag
+    spec (e.g. "branches = proj/branches/*:refs/remotes/origin/*")
+    is used and there was a plain file that matched the glob.
diff --git a/Documentation/RelNotes-1.5.1.2.txt b/Documentation/RelNotes-1.5.1.2.txt
new file mode 100644 (file)
index 0000000..d884563
--- /dev/null
@@ -0,0 +1,50 @@
+GIT v1.5.1.2 Release Notes
+==========================
+
+Fixes since v1.5.1.1
+--------------------
+
+* Bugfixes
+
+  - "git clone" over http from a repository that has lost the
+    loose refs by running "git pack-refs" were broken (a code to
+    deal with this was added to "git fetch" in v1.5.0, but it
+    was missing from "git clone").
+
+  - "git diff a/ b/" incorrectly fell in "diff between two
+    filesystem objects" codepath, when the user most likely
+    wanted to limit the extent of output to two tracked
+    directories.
+
+  - git-quiltimport had the same bug as we fixed for
+    git-applymbox in v1.5.1.1 -- it gave an alarming "did not
+    have any patch" message (but did not actually fail and was
+    harmless).
+
+  - various git-svn fixes.
+
+  - Sample update hook incorrectly always refused requests to
+    delete branches through push.
+
+  - git-blame on a very long working tree path had buffer
+    overrun problem.
+
+  - git-apply did not like to be fed two patches in a row that created
+    and then modified the same file.
+
+  - git-svn was confused when a non-project was stored directly under
+    trunk/, branches/ and tags/.
+
+  - git-svn wants the Error.pm module that was at least as new
+    as what we ship as part of git; install ours in our private
+    installation location if the one on the system is older.
+
+  - An earlier update to command line integer parameter parser was
+    botched and made 'update-index --cacheinfo' completely useless.
+
+
+* Documentation updates
+
+  - Various documentation updates from J. Bruce Fields, Frank
+    Lichtenheld, Alex Riesen and others.  Andrew Ruder started a
+    war on undocumented options.
index 2e3c7bc4f1cd39833f741dd2359f1b226c75a699..d93da608c70c3544963ad7dfd50ca12d39bc7fa9 100644 (file)
@@ -9,6 +9,14 @@ Updates since v1.5.1
   - "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]".
+
 * Updated behavior of existing commands.
 
   - "git diff --stat" shows size of preimage and postimage blobs
@@ -27,6 +35,12 @@ Updates since v1.5.1
     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".
+
 * Builds
 
   - git-p4import has never been installed; now there is an
@@ -55,8 +69,30 @@ The following are all in v1.5.1.x series, unless otherwise noted.
 
 * Documentation updates
 
+  - Various documentation updates from J. Bruce Fields, Frank
+    Lichtenheld, Alex Riesen and others.  Andrew Ruder started a
+    war on undocumented options.
+
 * Bugfixes
 
+  - "git diff a/ b/" incorrectly fell in "diff between two
+    filesystem objects" codepath, when the user most likely
+    wanted to limit the extent of output to two tracked
+    directories.
+
+  - git-quiltimport had the same bug as we fixed for
+    git-applymbox in v1.5.1.1 -- it gave an alarming "did not
+    have any patch" message (but did not actually fail and was
+    harmless).
+
+  - various git-svn fixes.
+
+  - Sample update hook incorrectly always refused requests to
+    delete branches through push.
+
+  - git-blame on a very long working tree path had buffer
+    overrun problem.
+
   - 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
@@ -67,10 +103,17 @@ The following are all in v1.5.1.x series, unless otherwise noted.
     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.
+
 * Performance Tweaks
 
 --
 exec >/var/tmp/1
-O=v1.5.1-91-g640ee0d
+O=v1.5.1.1-158-g86da9de
 echo O=`git describe refs/heads/master`
 git shortlog --no-merges $O..refs/heads/master ^refs/heads/maint
index 0381590d383c4fffbf04895be4c47e407dd94e80..443802a9a3e4982c33f831217602ea600f0f2775 100755 (executable)
@@ -84,6 +84,7 @@ sub format_one {
 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 7e41ca6a0d3e4329d1efc10b4dcb90613975e5bd..b13ff3a1bbedb70766f08b2e4d9b5048a4903d4d 100644 (file)
@@ -423,8 +423,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,6 +525,19 @@ 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.
index 8d1041598e566a3ffc87cbe294c7800be1ab0c60..d3ca9a90cee97622d2a717d618da23d20aa248e4 100644 (file)
@@ -33,9 +33,12 @@ OPTIONS
        Format of the resulting archive: 'tar', 'zip'...  The default
        is 'tar'.
 
---list::
+--list, -l::
        Show all available formats.
 
+--verbose, -v::
+       Report progress to stderr.
+
 --prefix=<prefix>/::
        Prepend <prefix>/ to each filename in the archive.
 
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 3149d08da83f5cd7b92f13da47298791ce9a3737..68bba982606b5e17b730f0e5d4d201923231c235 100644 (file)
@@ -38,7 +38,7 @@ OPTIONS
        development branch), adding this information can be
        useful.
 
--r|--replay::
+-r::
        It used to be that the command defaulted to do `-x`
        described above, and `-r` was to disable it.  Now the
        default is not to do `-x` so this option is a no-op.
index c759efb7fc6ebb69d9c6b59e23e31f33e0596675..280ef2058ca95e601243ae57946b80695234c41c 100644 (file)
@@ -9,16 +9,16 @@ git-config - Get and set repository or global options
 SYNOPSIS
 --------
 [verse]
-'git-config' [--global] [type] name [value [value_regex]]
-'git-config' [--global] [type] --add name value
-'git-config' [--global] [type] --replace-all name [value [value_regex]]
-'git-config' [--global] [type] --get name [value_regex]
-'git-config' [--global] [type] --get-all name [value_regex]
-'git-config' [--global] [type] --unset name [value_regex]
-'git-config' [--global] [type] --unset-all name [value_regex]
-'git-config' [--global] [type] --rename-section old_name new_name
-'git-config' [--global] [type] --remove-section name
-'git-config' [--global] -l | --list
+'git-config' [--system | --global] [type] name [value [value_regex]]
+'git-config' [--system | --global] [type] --add name value
+'git-config' [--system | --global] [type] --replace-all name [value [value_regex]]
+'git-config' [--system | --global] [type] --get name [value_regex]
+'git-config' [--system | --global] [type] --get-all name [value_regex]
+'git-config' [--system | --global] [type] --unset name [value_regex]
+'git-config' [--system | --global] [type] --unset-all name [value_regex]
+'git-config' [--system | --global] [type] --rename-section old_name new_name
+'git-config' [--system | --global] [type] --remove-section name
+'git-config' [--system | --global] -l | --list
 
 DESCRIPTION
 -----------
@@ -76,6 +76,10 @@ OPTIONS
 --global::
        Use global ~/.gitconfig file rather than the repository .git/config.
 
+--system::
+       Use system-wide $(prefix)/etc/gitconfig rather than the repository
+       .git/config.
+
 --remove-section::
        Remove the given section from the configuration file.
 
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 2229ee86b72865c656112aae87b9593d199bc2b8..b7a49b9f58af24844e0eae486d1cfec72bc518c6 100644 (file)
@@ -68,6 +68,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 fdc6f9728921e3bf71a57d441c99c8fb7d280803..d9e11c65344a52fd81023e26cc1535ac825106dd 100644 (file)
@@ -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 b051ccb6d6d47d7fdb41ba27e3e42c82a4460608..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,9 @@ 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.
index b0df92e8192bc494e5d4db1707c233790560afb8..1c8c55ef6eac57d5bcc1cad7afbecd354af9d3ab 100644 (file)
@@ -7,6 +7,7 @@ git-shortlog - Summarize 'git log' output
 
 SYNOPSIS
 --------
+[verse]
 git-log --pretty=short | 'git-shortlog' [-h] [-n] [-s]
 git-shortlog [-n|--number] [-s|--summary] [<committish>...]
 
@@ -33,7 +34,8 @@ OPTIONS
 
 FILES
 -----
-'.mailmap'::
+
+.mailmap::
        If this file exists, it will be used for mapping author email
        addresses to a real author name. One mapping per line, first
        the author name followed by the email address enclosed by
index 595940524e7d7f1112700899cf74e24e1c2e1e87..7bde73b1b85df03893fa15b99b17c5aff09ae574 100644 (file)
@@ -13,7 +13,7 @@ SYNOPSIS
 DESCRIPTION
 -----------
 THIS COMMAND IS DEPRECATED.  Use `git-archive` with `--format=tar`
-option instead.
+option instead (and move the <base> argument to `--prefix=base/`).
 
 Creates a tar archive containing the tree structure for the named tree.
 When <base> is specified it is added as a leading path to the files in the
diff --git a/Documentation/gitattributes.txt b/Documentation/gitattributes.txt
new file mode 100644 (file)
index 0000000..ece58ab
--- /dev/null
@@ -0,0 +1,285 @@
+gitattributes(5)
+================
+
+NAME
+----
+gitattributes - defining attributes per path
+
+SYNOPSIS
+--------
+.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.
+
+When more than one glob pattern matches the path, a later line
+overrides an earlier line.
+
+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
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The attribute `crlf` affects how the contents stored in the
+repository are copied to the working tree files when commands
+such as `git checkout` and `git merge` run.  It also affects how
+git stores the contents you prepare in the working tree in the
+repository upon `git add` and `git commit`.
+
+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.
+
+
+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`.
+
+Any other value set to `diff` attribute is ignored and git acts
+as if the attribute is left unspecified.
+
+
+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.
+
+Any other string value::
+
+       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 by "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 129c5c5f5b8657175db7efed2c06078affd05ab6..e978562d6e6b9685eb0835ff7e8bc131226a581d 100644 (file)
@@ -111,7 +111,7 @@ make it real.
 Note: don't forget to 'add' a file again if you modified it after the
 first 'add' and before 'commit'. Otherwise only the previous added
 state of that file will be committed. This is because git tracks
-content, so what you're really 'add'ing to the commit is the *content*
+content, so what you're really 'adding' to the commit is the *content*
 of the file in the state it is in when you 'add' it.
 
 2) By using 'git commit -a' directly
index 2325660ff42bf7f6e1c998723fd72f150c347c7d..41ee8b4ea2af9e25f7ac2ad56c083108080de49e 100755 (executable)
@@ -1,7 +1,7 @@
 #!/bin/sh
 
 GVF=GIT-VERSION-FILE
-DEF_VER=v1.5.1.1.GIT
+DEF_VER=v1.5.1.2.GIT
 
 LF='
 '
index 251fc31fc05035bf85265d3ea56c8b3ab24c895f..c9c2a5fb6685333895b6ab28a4ae355a6ac378bb 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -283,7 +283,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 patch-ids.h decorate.h
+       utf8.h reflog-walk.h patch-ids.h attr.h decorate.h
 
 DIFF_OBJS = \
        diff.o diff-lib.o diffcore-break.o diffcore-order.o \
@@ -305,7 +305,7 @@ LIB_OBJS = \
        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 decorate.o
+       convert.o attr.o decorate.o
 
 BUILTIN_OBJS = \
        builtin-add.o \
@@ -316,6 +316,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 \
@@ -933,7 +934,7 @@ endif
 
 export NO_SVN_TESTS
 
-test: all test-chmtime$X
+test: all test-chmtime$X test-genrandom$X
        $(MAKE) -C t/ all
 
 test-date$X: test-date.c date.o ctype.o
@@ -954,6 +955,9 @@ test-match-trees$X: test-match-trees.o $(GITLIBS)
 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
 
@@ -1029,9 +1033,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 .
@@ -1042,7 +1047,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
diff --git a/attr.c b/attr.c
new file mode 100644 (file)
index 0000000..285e689
--- /dev/null
+++ b/attr.c
@@ -0,0 +1,563 @@
+#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, 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, 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 */
index 9ec292590c49574dfd2a698772a406b5ccdc419e..5e6748f3566b17c7729576c0c42c61bcf0f7657d 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_all_worktree_changes;
 static const char *excludes_file;
 
 static void prune_directory(struct dir_struct *dir, const char **pathspec, int prefix)
@@ -92,6 +97,44 @@ static void fill_directory(struct dir_struct *dir, const char **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_all(int verbose)
+{
+       struct rev_info rev;
+       init_revisions(&rev, "");
+       setup_revisions(0, NULL, &rev, NULL);
+       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")) {
@@ -156,8 +199,20 @@ int cmd_add(int argc, const char **argv, const char *prefix)
                        verbose = 1;
                        continue;
                }
+               if (!strcmp(arg, "-u")) {
+                       take_all_worktree_changes = 1;
+                       continue;
+               }
                usage(builtin_add_usage);
        }
+
+       if (take_all_worktree_changes) {
+               if (i < argc)
+                       die("-u and explicit paths are incompatible");
+               update_all(verbose);
+               goto finish;
+       }
+
        if (argc <= i) {
                fprintf(stderr, "Nothing specified, nothing added.\n");
                fprintf(stderr, "Maybe you wanted to say 'git add .'?\n");
@@ -207,6 +262,7 @@ int cmd_add(int argc, const char **argv, const char *prefix)
        for (i = 0; i < dir.nr; i++)
                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_locked_index(&lock_file))
index fd92ef7174dae1586921ebd34233c93b381106de..f94d0dbf488ff43ccc496939560c81daf05772cf 100644 (file)
@@ -1475,8 +1475,8 @@ static int read_old_data(struct stat *st, const char *path, char **buf_p, unsign
                }
                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;
@@ -2355,9 +2355,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 +2368,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 +2383,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;
 }
@@ -2416,8 +2412,7 @@ static void create_one_file(char *path, unsigned mode, const char *buf, unsigned
                 * used to be.
                 */
                struct stat st;
-               errno = 0;
-               if (!lstat(path, &st) && S_ISDIR(st.st_mode) && !rmdir(path))
+               if (!lstat(path, &st) && (!S_ISDIR(st.st_mode) || !rmdir(path)))
                        errno = EEXIST;
        }
 
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 dfa403b94baea97c655ab707d75e80f58f318c51..b2515f7e65ed05a5352639686b5163f4c74bc1c2 100644 (file)
@@ -2,7 +2,7 @@
 #include "cache.h"
 
 static const char git_config_set_usage[] =
-"git-config [ --global ] [ --bool | --int ] [--get | --get-all | --get-regexp | --replace-all | --add | --unset | --unset-all] name [value [value_regex]] | --rename-section old_name new_name | --remove-section name | --list";
+"git-config [ --global | --system ] [ --bool | --int ] [--get | --get-all | --get-regexp | --replace-all | --add | --unset | --unset-all] name [value [value_regex]] | --rename-section old_name new_name | --remove-section name | --list";
 
 static char *key;
 static regex_t *key_regexp;
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 e9d676455078b40138ad4716a912c4b2b5e10c2e..be341c159fb17117c4cc55a8efa9e2107375a06b 100644 (file)
@@ -436,10 +436,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];
@@ -447,6 +524,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--;
@@ -491,6 +570,11 @@ 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])) {
+               if (argc != 4)
+                       return error("pick-rref takes 2 args");
+               return pick_rref(sopt, argv[2], argv[3]);
+       }
        if (!strcmp("expand-refs-wildcard", argv[1])) {
                const char *reflist;
                if (argc < 4)
index 05d98d2cfc0c8ba5e38482a181fee8115e9bd640..fcb8ed5af1bc50df19a2f533989aa9673ea166e8 100644 (file)
@@ -253,6 +253,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
@@ -661,7 +662,7 @@ 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);
+                       uint32_t i, num = p->num_objects;
                        for (i = 0; i < num; i++)
                                fsck_sha1(nth_packed_object_sha1(p, i));
                }
@@ -703,8 +704,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 74a6acacc15b416a6149a519f3a69c5facfa5067..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);
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 45ac3e482acc1c0f8f2bad043f1ba50019dec505..c72e07a2bb73b74e6be9a038fae4eafd0b88e2b2 100644 (file)
@@ -22,28 +22,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,25 +49,17 @@ 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;
@@ -79,8 +69,7 @@ static int num_preferred_base;
 
 /*
  * 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 +153,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 +207,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,12 +221,6 @@ 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)
 {
@@ -300,6 +303,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 +394,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 +406,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 +489,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 +512,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 +530,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,34 +539,47 @@ 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 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;
+               snprintf(tmpname, sizeof(tmpname), "tmp_pack_XXXXXX");
+               fd = mkstemp(tmpname);
+               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);
 
@@ -542,6 +591,7 @@ 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;
@@ -559,16 +609,61 @@ static void write_pack_file(void)
        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;
+
+       snprintf(tmpname, sizeof(tmpname), "tmp_idx_XXXXXX");
+       fd = mkstemp(tmpname);
+       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 +683,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 +805,72 @@ 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;
+
+       return 1;
 }
 
 struct pbase_tree_cache {
@@ -849,22 +992,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 +1068,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 +1118,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 +1225,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;
-}
+       const struct object_entry *a = *(struct object_entry **)_a;
+       const struct object_entry *b = *(struct object_entry **)_b;
 
-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);
-       }
-}
-
-typedef int (*entry_sort_t)(const struct object_entry *, const struct object_entry *);
-
-static entry_sort_t current_sort;
+       /* avoid filesystem trashing with loose objects */
+       if (!a->in_pack && !b->in_pack)
+               return hashcmp(a->sha1, b->sha1);
 
-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 +1279,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 +1325,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 +1372,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)
@@ -1280,6 +1391,7 @@ static void find_deltas(struct object_entry **list, int window, int depth)
        unsigned int array_size = window * sizeof(struct unpacked);
        struct unpacked *array;
        unsigned last_percent = 999;
+       int max_depth;
 
        if (!nr_objects)
                return;
@@ -1320,6 +1432,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 +1453,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,6 +1464,7 @@ 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;
@@ -1356,64 +1482,25 @@ 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;
+       struct object_entry **delta_list;
+       uint32_t i;
 
-       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;
-               }
-       }
+       get_object_details();
 
-       if (progress)
-               fprintf(stderr, "Reusing %u objects pack %s\n", nr_objects,
-                       sha1_to_hex(sha1));
+       if (!window || !depth)
+               return;
 
-       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));
-       }
+       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);
+}
 
-       return 1;
+static void progress_interval(int signum)
+{
+       progress_update = 1;
 }
 
 static void setup_progress_signal(void)
@@ -1471,22 +1558,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)
@@ -1529,12 +1614,12 @@ static void get_object_list(int ac, const char **av)
 
 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 +1712,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);
        }
 
@@ -1668,37 +1764,34 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
 
        if (progress)
                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);
+       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;
+       }
+       last_obj_offset = write_pack_file();
+       if (!pack_to_stdout) {
+               unsigned char object_list_sha1[20];
+               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 (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 (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 09774f9559b81050d89bd6663b8b672438da4342..c0329dcecdbb4775b2c438a0bf6429a5ee72b18e 100644 (file)
@@ -113,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),
index b77b058e38c9a2b148f3c4deffa13945ee7daef6..4a0bd93c8b3b644fb86ce05686b09d79b180bffc 100644 (file)
@@ -10,7 +10,7 @@
 #include "tree-walk.h"
 
 static const char builtin_rm_usage[] =
-"git-rm [-f] [-n] [-r] [--cached] [--quiet] [--] <file>...";
+"git-rm [-f] [-n] [-r] [--cached] [--ignore-unmatch] [--quiet] [--] <file>...";
 
 static struct {
        int nr, alloc;
@@ -105,6 +105,7 @@ 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, quiet = 0;
+       int ignore_unmatch = 0;
        const char **pathspec;
        char *seen;
 
@@ -134,6 +135,8 @@ int cmd_rm(int argc, const char **argv, const char *prefix)
                        recursive = 1;
                else if (!strcmp(arg, "--quiet"))
                        quiet = 1;
+               else if (!strcmp(arg, "--ignore-unmatch"))
+                       ignore_unmatch = 1;
                else
                        usage(builtin_rm_usage);
        }
@@ -155,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);
        }
 
        /*
index 3956c5633448a5c29c60cad370ec7da6a8bfeb64..f8219064602775346bb0d8a323339b3d077da31c 100644 (file)
@@ -13,7 +13,8 @@ static const char unpack_usage[] = "git-unpack-objects [-n] [-q] [-r] < pack-fil
 
 /* 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;
 
 /*
@@ -49,6 +50,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 +93,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 +118,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 +205,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 +214,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;
index b3d4acee6de30aa27717133d09a3bc313c7569f4..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 process_file(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 (process_file(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))
@@ -551,7 +627,7 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)
                                if (i+3 >= argc)
                                        die("git-update-index: --cacheinfo <mode> <sha1> <path>");
 
-                               if ((strtoul_ui(argv[i+1], 8, &mode) != 1) ||
+                               if (strtoul_ui(argv[i+1], 8, &mode) ||
                                    get_sha1_hex(argv[i+2], sha1) ||
                                    add_cacheinfo(mode, sha1, argv[i+3], 0))
                                        die("git-update-index: --cacheinfo"
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 4de25cc4b2c57131d48e62ae7ef5331a25b267ec..89aaf0022d95dfe8ffda4031021e4d059998d28c 100644 (file)
--- a/cache.h
+++ b/cache.h
 #define DTYPE(de)      DT_UNKNOWN
 #endif
 
+/*
+ * 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 +120,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,7 +139,7 @@ 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)
 
@@ -151,6 +169,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);
@@ -206,6 +227,7 @@ extern int refresh_cache(unsigned int flags);
 
 struct lock_file {
        struct lock_file *next;
+       pid_t owner;
        char on_list;
        char filename[PATH_MAX];
 };
@@ -217,7 +239,7 @@ 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 *, unsigned char *sha1);
+extern int delete_ref(const char *, const unsigned char *sha1);
 
 /* Environment bits from configuration mechanism */
 extern int use_legacy_headers;
@@ -376,11 +398,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];
@@ -431,11 +454,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 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 */
@@ -490,8 +513,8 @@ 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);
 
 /* match-trees.c */
 void shift_tree(const unsigned char *, const unsigned char *, unsigned char *, int);
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 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)
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 cf03bcfe5aa4f95ec4b6384bc38d63b1e453f731..cefbcebdcaa0a085745c81b2ed1103e9575cb635 100644 (file)
@@ -88,7 +88,7 @@ static int write_subdirectory(void *buffer, unsigned long size, const char *base
                unsigned int mode;
                char *slash, *origpath;
 
-               if (!path || strtoul_ui(buffer, 8, &mode) != 1)
+               if (!path || strtoul_ui(buffer, 8, &mode))
                        die("bad tree conversion");
                mode = convert_mode(mode);
                path++;
index 898bfe3eb219618e746afbe02eb4a8756eccc6aa..37239ace83872e4c91c2f317d70713bac380b9a8 100644 (file)
--- a/convert.c
+++ b/convert.c
@@ -1,4 +1,6 @@
 #include "cache.h"
+#include "attr.h"
+
 /*
  * convert.c - convert a file when checking it out and checking it in.
  *
@@ -8,6 +10,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 +79,171 @@ 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) || (action == CRLF_GUESS && !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;
-
-       /*
-        * 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;
-
-       /*
-        * And add some heuristics for binary vs text, of course...
-        */
-       if (is_binary(size, &stats))
-               return 0;
+               return NULL;
+
+       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 NULL;
+       }
 
        /*
         * Ok, allocate a new buffer, fill it in, and return true
         * to let the caller know that we switched buffers on it.
         */
        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 && *buffer == '\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) ||
+           (action == CRLF_GUESS && 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.
         */
        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 void setup_convert_check(struct git_attr_check *check)
+{
+       static struct git_attr *attr_crlf;
+
+       if (!attr_crlf)
+               attr_crlf = git_attr("crlf", 4);
+       check->attr = attr_crlf;
+}
+
+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;
+}
+
+char *convert_to_git(const char *path, const char *src, unsigned long *sizep)
+{
+       struct git_attr_check check[1];
+       int crlf = CRLF_GUESS;
+
+       setup_convert_check(check);
+       if (!git_checkattr(path, 1, check)) {
+               crlf = git_path_check_crlf(path, check);
+       }
+       return crlf_to_git(path, src, sizep, crlf);
+}
+
+char *convert_to_working_tree(const char *path, const char *src, unsigned long *sizep)
+{
+       struct git_attr_check check[1];
+       int crlf = CRLF_GUESS;
+
+       setup_convert_check(check);
+       if (!git_checkattr(path, 1, check)) {
+               crlf = git_path_check_crlf(path, check);
+       }
+       return crlf_to_worktree(path, src, sizep, crlf);
 }
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
index 396b41311a0d1549897d4b92bccd12001d13bc01..23f6b0040f1cda9a550e5b1d90589fa4a7f76eb5 100644 (file)
@@ -24,7 +24,6 @@ static void *insert_decoration(struct decoration *n, struct object *base, void *
                        hash[j].decoration = decoration;
                        return old;
                }
-               j++;
                if (++j >= size)
                        j = 0;
        }
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 fbb79d70a93dd0c6c46a1d24505e55f737d44189..f516664968a6cb448611c24489254cbb2c9947ff 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
@@ -1051,13 +1052,44 @@ 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;
+               else if (ATTR_UNSET(value))
+                       ;
+               else
+                       die("unknown value %s given to 'diff' attribute",
+                           value);
+       }
+
+       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,
@@ -1114,7 +1146,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))
@@ -1190,7 +1222,7 @@ static void builtin_diffstat(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(&mf1) || mmfile_is_binary(&mf2)) {
+       if (file_is_binary(one) || file_is_binary(two)) {
                data->is_binary = 1;
                data->added = mf2.size;
                data->deleted = mf1.size;
@@ -1228,7 +1260,7 @@ 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))
+       if (file_is_binary(two))
                return;
        else {
                /* Crazy xdl interfaces.. */
@@ -1397,6 +1429,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.
@@ -1415,6 +1463,10 @@ int diff_populate_filespec(struct diff_filespec *s, int size_only)
 
        if (s->data)
                return err;
+
+       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;
@@ -1461,9 +1513,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;
@@ -1805,8 +1857,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,
@@ -2701,7 +2753,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/dir.c b/dir.c
index 7426fde330a200e3137e722c4b9adbc5ce6bdd90..6564a929ff66cdffca0666e175c9361149b25b5e 100644 (file)
--- a/dir.c
+++ b/dir.c
@@ -7,12 +7,17 @@
  */
 #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)
 {
        const char *path, *slash, *next;
@@ -286,15 +291,111 @@ 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)
-               return 1;
-       pos = -pos-1;
-       if (pos >= active_nr) /* can't */
-               return 0;
-       return !strncmp(active_cache[pos]->name, dirname, len);
+       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;
 }
 
 /*
@@ -353,6 +454,9 @@ 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 (simplify_away(fullname, baselen + len, simplify))
                                continue;
@@ -377,19 +481,17 @@ 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, simplify))
-                                               continue;
+                               switch (treat_directory(dir, fullname, baselen + len, simplify)) {
+                               case show_directory:
                                        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, simplify);
-                               continue;
+                               break;
                        case DT_REG:
                        case DT_LNK:
                                break;
diff --git a/dir.h b/dir.h
index 33c31f25fbabc36db26e6fdf9f33381f166d2d7f..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 */
diff --git a/entry.c b/entry.c
index d72f811580ad10e792e38b40fe79bf4af3868846..84f78025ff9f24d3ef9b2412894e18f3d9fc1859 100644 (file)
--- a/entry.c
+++ b/entry.c
@@ -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 void *read_blob_entry(struct cache_entry *ce, const char *path, unsigned long *size)
+{
+       enum object_type type;
+       void *new = read_sha1_file(ce->sha1, &type, size);
+
+       if (new) {
+               if (type == OBJ_BLOB)
+                       return new;
+               free(new);
+       }
+       return NULL;
+}
+
 static int write_entry(struct cache_entry *ce, char *path, struct checkout *state, int to_tempfile)
 {
        int fd;
-       void *new;
-       unsigned long size;
        long wrote;
-       enum object_type type;
 
-       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));
-       }
        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);
        }
 
@@ -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 513b574d13858f1a81e6f66251890d81bf0e55ce..cad5c0c088fa997c6f8f36c44e8888a5cb19515e 100755 (executable)
@@ -60,7 +60,7 @@ Perhaps git-update-server-info needs to be run there?"
                else
                        tname=$name
                fi
-               git-http-fetch $v -a -w "$tname" "$name" "$1" || exit 1
+               git-http-fetch $v -a -w "$tname" "$sha1" "$1" || exit 1
        done <"$clone_tmp/refs"
        rm -fr "$clone_tmp"
        http_fetch "$1/HEAD" "$GIT_DIR/REMOTE_HEAD" ||
index 5f6a281b78245bbb4e1e480d81cfb8c1b4cfa6ac..0b6d74d4d7ca0df726dd0464951a5e0f045c8715 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))))
+
 #if !defined(__APPLE__) && !defined(__FreeBSD__)
 #define _XOPEN_SOURCE 600 /* glibc2 and AIX 5.3L need 500, OpenBSD needs 600 for S_ISLNK() */
 #define _XOPEN_SOURCE_EXTENDED 1 /* AIX 5.3L needs this */
index 25816c5a21285cae1d8e42f46f26126a03c6631f..087e3abaefd54c8a950ce11e889757e48c428808 100755 (executable)
@@ -91,7 +91,9 @@
 # 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";
@@ -181,11 +183,18 @@ sub req_Root
     }
     foreach my $line ( @gitvars )
     {
-        next unless ( $line =~ /^(.*?)\.(.*?)=(.*)$/ );
-        $cfg->{$1}{$2} = $3;
+        next unless ( $line =~ /^(.*?)\.(.*?)(?:\.(.*?))?=(.*)$/ );
+        unless ($3) {
+            $cfg->{$1}{$2} = $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 +203,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 +360,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 +1077,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 +2182,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 +2913,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 b04bd553f86213478a36f8ec2f19476f02ccf09f..832b20cce629cb5753e849fb2a5b60ca707397a0 100755 (executable)
@@ -177,9 +177,33 @@ fetch_all_at_once () {
            git-bundle unbundle "$remote" $rref ||
            echo failed "$remote"
        else
-         git-fetch-pack --thin $exec $keep $shallow_depth \
-             $quiet $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=$(git-fetch--tool -s pick-rref \
+                                       "$rref" "$ls_remote_result") &&
+
+                       # 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
+                       git-fetch--tool pick-rref "$rref" "$ls_remote_result"
+               else
+                       git-fetch-pack --thin $exec $keep $shallow_depth \
+                               $quiet $no_progress "$remote" $rref ||
+                       echo failed "$remote"
+               fi
        fi
       ) |
       (
@@ -239,16 +263,8 @@ 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=$(git-fetch--tool -s pick-rref \
+                       "$remote_name" "$ls_remote_result")
          expr "z$head" : "z$_x40\$" >/dev/null ||
                die "No such ref $remote_name at $remote"
          echo >&2 "Fetching $remote_name from $remote using $proto"
index b82789ead6255b33be0f1ed2029a91611cea3072..b29d7d1e68d2489fd07612c8d0062d3491f04719 100644 (file)
@@ -28,6 +28,8 @@ ifndef V
        QUIET_BUILT_IN = @echo '   ' BUILTIN $@;
 endif
 
+TCLTK_PATH ?= wish
+
 ifeq ($(findstring $(MAKEFLAGS),s),s)
 QUIET_GEN =
 QUIET_BUILT_IN =
@@ -36,10 +38,12 @@ endif
 DESTDIR_SQ = $(subst ','\'',$(DESTDIR))
 gitexecdir_SQ = $(subst ','\'',$(gitexecdir))
 SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH))
+TCLTK_PATH_SQ = $(subst ','\'',$(TCLTK_PATH))
 
 $(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' \
                $@.sh >$@+ && \
        chmod +x $@+ && \
index 60e79ca1b01bc8b057abe17ddab484699a7f5fdb..94067cc5f73388f33722d52ae02f44692bc07490 100755 (executable)
@@ -242,6 +242,8 @@ proc error_popup {msg} {
        if {[reponame] ne {}} {
                append title " ([reponame])"
        }
+       option add *Dialog.msg.font font_ui
+       option add *Button.font font_ui
        set cmd [list tk_messageBox \
                -icon error \
                -type ok \
@@ -258,6 +260,8 @@ proc warn_popup {msg} {
        if {[reponame] ne {}} {
                append title " ([reponame])"
        }
+       option add *Dialog.msg.font font_ui
+       option add *Button.font font_ui
        set cmd [list tk_messageBox \
                -icon warning \
                -type ok \
@@ -274,6 +278,8 @@ proc info_popup {msg {parent .}} {
        if {[reponame] ne {}} {
                append title " ([reponame])"
        }
+       option add *Dialog.msg.font font_ui
+       option add *Button.font font_ui
        tk_messageBox \
                -parent $parent \
                -icon info \
@@ -287,6 +293,8 @@ proc ask_popup {msg} {
        if {[reponame] ne {}} {
                append title " ([reponame])"
        }
+       option add *Dialog.msg.font font_ui
+       option add *Button.font font_ui
        return [tk_messageBox \
                -parent . \
                -icon question \
@@ -727,12 +735,9 @@ proc handle_empty_diff {} {
 
 [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.
+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."
+A rescan will be automatically started to find other files which may have the same state."
 
        clear_diff
        display_file $path __
@@ -1033,8 +1038,7 @@ proc load_last_commit {} {
        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.
+You are about to create the initial commit.  There is no commit before this to amend.
 }
                return
        }
@@ -1043,10 +1047,7 @@ There is no commit before this to amend.
        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.
+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
        }
@@ -1136,9 +1137,7 @@ proc commit_tree {} {
        } 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.
+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.
 }
@@ -1159,8 +1158,7 @@ The rescan will be automatically started now.
                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.
+File [short_path $path] has merge conflicts.  You must resolve them and add the file before committing.
 "
                        unlock_index
                        return
@@ -1276,8 +1274,7 @@ proc commit_committree {fd_wt curHEAD msg} {
                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.
+No files were modified by this commit and it was not a merge commit.
 
 A rescan will be automatically started now.
 }
@@ -2116,7 +2113,10 @@ proc do_create_branch {} {
                -value head \
                -variable create_branch_revtype \
                -font font_ui
-       eval tk_optionMenu $w.from.head_m create_branch_head $all_heads
+       set lbranchm [eval tk_optionMenu $w.from.head_m create_branch_head \
+               $all_heads]
+       $lbranchm configure -font font_ui
+       $w.from.head_m configure -font font_ui
        grid $w.from.head_r $w.from.head_m -sticky w
        set all_trackings [all_tracking_branches]
        if {$all_trackings ne {}} {
@@ -2126,9 +2126,11 @@ proc do_create_branch {} {
                        -value tracking \
                        -variable create_branch_revtype \
                        -font font_ui
-               eval tk_optionMenu $w.from.tracking_m \
+               set tbranchm [eval tk_optionMenu $w.from.tracking_m \
                        create_branch_trackinghead \
-                       $all_trackings
+                       $all_trackings]
+               $tbranchm configure -font font_ui
+               $w.from.tracking_m configure -font font_ui
                grid $w.from.tracking_r $w.from.tracking_m -sticky w
        }
        set all_tags [load_all_tags]
@@ -2139,9 +2141,11 @@ proc do_create_branch {} {
                        -value tag \
                        -variable create_branch_revtype \
                        -font font_ui
-               eval tk_optionMenu $w.from.tag_m \
+               set tagsm [eval tk_optionMenu $w.from.tag_m \
                        create_branch_tag \
-                       $all_tags
+                       $all_tags]
+               $tagsm configure -font font_ui
+               $w.from.tag_m configure -font font_ui
                grid $w.from.tag_r $w.from.tag_m -sticky w
        }
        radiobutton $w.from.exp_r \
@@ -2335,7 +2339,11 @@ proc do_delete_branch {} {
                -value head \
                -variable delete_branch_checktype \
                -font font_ui
-       eval tk_optionMenu $w.validate.head_m delete_branch_head $all_heads
+       set mergedlocalm [eval tk_optionMenu $w.validate.head_m \
+               delete_branch_head \
+               $all_heads]
+       $mergedlocalm configure -font font_ui
+       $w.validate.head_m configure -font font_ui
        grid $w.validate.head_r $w.validate.head_m -sticky w
        set all_trackings [all_tracking_branches]
        if {$all_trackings ne {}} {
@@ -2345,9 +2353,11 @@ proc do_delete_branch {} {
                        -value tracking \
                        -variable delete_branch_checktype \
                        -font font_ui
-               eval tk_optionMenu $w.validate.tracking_m \
+               set mergedtrackm [eval tk_optionMenu $w.validate.tracking_m \
                        delete_branch_trackinghead \
-                       $all_trackings
+                       $all_trackings]
+               $mergedtrackm configure -font font_ui
+               $w.validate.tracking_m configure -font font_ui
                grid $w.validate.tracking_r $w.validate.tracking_m -sticky w
        }
        radiobutton $w.validate.always_r \
@@ -2382,9 +2392,7 @@ proc switch_branch {new_branch} {
        } 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.
+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.
 }
@@ -2475,12 +2483,9 @@ Staying on branch '$current_branch'."
        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 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.
+This should not have occurred.  [appname] will now close and give up.
 
 $err"
                do_quit
@@ -2684,10 +2689,12 @@ proc do_push_anywhere {} {
        frame $w.buttons
        button $w.buttons.create -text Push \
                -font font_ui \
+               -default active \
                -command [list start_push_anywhere_action $w]
        pack $w.buttons.create -side right
        button $w.buttons.cancel -text {Cancel} \
                -font font_ui \
+               -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
@@ -2721,7 +2728,10 @@ proc do_push_anywhere {} {
                        -value remote \
                        -variable push_urltype \
                        -font font_ui
-               eval tk_optionMenu $w.dest.remote_m push_remote $all_remotes
+               set remmenu [eval tk_optionMenu $w.dest.remote_m push_remote \
+                       $all_remotes]
+               $remmenu configure -font font_ui
+               $w.dest.remote_m configure -font font_ui
                grid $w.dest.remote_r $w.dest.remote_m -sticky w
                if {[lsearch -sorted -exact $all_remotes origin] != -1} {
                        set push_remote origin
@@ -2775,8 +2785,9 @@ proc do_push_anywhere {} {
        set push_thin 0
        set push_tags 0
 
-       bind $w <Visibility> "grab $w"
+       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
 }
@@ -2791,8 +2802,7 @@ proc can_merge {} {
        if {[string match amend* $commit_type]} {
                info_popup {Cannot merge while amending.
 
-You must finish amending this commit before
-starting any type of merge.
+You must finish amending this commit before starting any type of merge.
 }
                return 0
        }
@@ -2806,9 +2816,7 @@ starting any type of merge.
        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.
+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.
 }
@@ -2827,9 +2835,7 @@ The rescan will be automatically started now.
 
 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.
+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
@@ -2839,9 +2845,7 @@ begin another merge.
 
 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.
+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
@@ -2917,13 +2921,11 @@ proc finish_merge {revcnt w ok} {
 
 Your merge of $revcnt branches has failed.
 
-There are file-level conflicts between the
-branches which must be resolved manually.
+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
+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
@@ -3036,8 +3038,7 @@ You must finish amending this commit.
 
        if {[ask_popup "Abort $op?
 
-Aborting the current $op will cause
-*ALL* uncommitted changes to be lost.
+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]
@@ -3604,12 +3605,14 @@ proc read_blame_incremental {fd w w_load w_cmit w_line w_file} {
 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%%)" \
-               $blame_data($w,blame_lines) \
-               $blame_data($w,total_lines) \
-               [expr {100 * $blame_data($w,blame_lines)
-                       / $blame_data($w,total_lines)}]]
+               $have $total $pdone]
 }
 
 proc blame_click {w w_cmit w_line w_file cur_w pos} {
@@ -4107,6 +4110,7 @@ proc console_done {args} {
                if {[winfo exists $w]} {
                        $w.m.s conf -background green -text {Success}
                        $w.ok conf -state normal
+                       focus $w.ok
                }
        } else {
                if {![winfo exists $w]} {
@@ -4114,6 +4118,7 @@ proc console_done {args} {
                }
                $w.m.s conf -background red -text {Error: Command Failed}
                $w.ok conf -state normal
+               focus $w.ok
        }
 
        array unset console_cr $w
@@ -4181,9 +4186,11 @@ proc do_stats {} {
        frame $w.buttons -border 1
        button $w.buttons.close -text Close \
                -font font_ui \
+               -default active \
                -command [list destroy $w]
        button $w.buttons.gc -text {Compress Database} \
                -font font_ui \
+               -default normal \
                -command "destroy $w;do_gc"
        pack $w.buttons.close -side right
        pack $w.buttons.gc -side left
@@ -4212,7 +4219,7 @@ proc do_stats {} {
        }
        pack $w.stat -pady 10 -padx 10
 
-       bind $w <Visibility> "grab $w; focus $w"
+       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"
@@ -4509,6 +4516,7 @@ proc do_about {} {
        frame $w.buttons
        button $w.buttons.close -text {Close} \
                -font font_ui \
+               -default active \
                -command [list destroy $w]
        pack $w.buttons.close -side right
        pack $w.buttons -side bottom -fill x -pady 10 -padx 10
@@ -4554,8 +4562,9 @@ $copyright" \
                clipboard append -format STRING -type STRING -- \[$w.vers cget -text\]
        "
 
-       bind $w <Visibility> "grab $w; focus $w"
+       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
@@ -4592,14 +4601,17 @@ proc do_options {} {
        frame $w.buttons
        button $w.buttons.restore -text {Restore Defaults} \
                -font font_ui \
+               -default normal \
                -command do_restore_defaults
        pack $w.buttons.restore -side left
        button $w.buttons.save -text Save \
                -font font_ui \
+               -default active \
                -command [list do_save_config $w]
        pack $w.buttons.save -side right
        button $w.buttons.cancel -text {Cancel} \
                -font font_ui \
+               -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
@@ -4686,9 +4698,11 @@ proc do_options {} {
                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 \
+               set fontmenu [eval tk_optionMenu $w.global.$name.family \
                        global_config_new(gui.$font^^family) \
-                       $all_fonts
+                       $all_fonts]
+               $w.global.$name.family configure -font font_ui
+               $fontmenu configure -font font_ui
                spinbox $w.global.$name.size \
                        -textvariable global_config_new(gui.$font^^size) \
                        -from 2 -to 80 -increment 1 \
@@ -4700,8 +4714,9 @@ proc do_options {} {
                pack $w.global.$name -side top -anchor w -fill x
        }
 
-       bind $w <Visibility> "grab $w; focus $w"
+       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
 }
@@ -5083,18 +5098,18 @@ set ui_comm {}
 # -- Menu Bar
 #
 menu .mbar -tearoff 0
-.mbar add cascade -label Repository -menu .mbar.repository
-.mbar add cascade -label Edit -menu .mbar.edit
+.mbar add cascade -label Repository -menu .mbar.repository -font font_ui
+.mbar add cascade -label Edit -menu .mbar.edit -font font_ui
 if {[is_enabled branch]} {
-       .mbar add cascade -label Branch -menu .mbar.branch
+       .mbar add cascade -label Branch -menu .mbar.branch -font font_ui
 }
 if {[is_enabled multicommit] || [is_enabled singlecommit]} {
-       .mbar add cascade -label Commit -menu .mbar.commit
+       .mbar add cascade -label Commit -menu .mbar.commit -font font_ui
 }
 if {[is_enabled transport]} {
-       .mbar add cascade -label Merge -menu .mbar.merge
-       .mbar add cascade -label Fetch -menu .mbar.fetch
-       .mbar add cascade -label Push -menu .mbar.push
+       .mbar add cascade -label Merge -menu .mbar.merge -font font_ui
+       .mbar add cascade -label Fetch -menu .mbar.fetch -font font_ui
+       .mbar add cascade -label Push -menu .mbar.push -font font_ui
 }
 . configure -menu .mbar
 
@@ -5370,7 +5385,7 @@ if {[is_MacOSX]} {
 
 # -- Help Menu
 #
-.mbar add cascade -label Help -menu .mbar.help
+.mbar add cascade -label Help -menu .mbar.help -font font_ui
 menu .mbar.help
 
 if {![is_MacOSX]} {
@@ -5953,7 +5968,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
@@ -6032,9 +6047,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
index ac44f60b81412753248c78fbe73fe7f6a212b6df..efc4c88a4ea158bd630a6f53a8346e344b360ed6 100755 (executable)
@@ -168,14 +168,14 @@ BEGIN
 my %opts = %{$cmd{$cmd}->[2]} if (defined $cmd);
 
 read_repo_config(\%opts);
-Getopt::Long::Configure('pass_through') if $cmd eq 'log';
+Getopt::Long::Configure('pass_through') if ($cmd && $cmd eq 'log');
 my $rv = GetOptions(%opts, 'help|H|h' => \$_help, 'version|V' => \$_version,
                     'minimize-connections' => \$Git::SVN::Migration::_minimize,
                     'id|i=s' => \$Git::SVN::default_ref_id,
                     'svn-remote|remote|R=s' => sub {
                        $Git::SVN::no_reuse_existing = 1;
                        $Git::SVN::default_repo_id = $_[1] });
-exit 1 if (!$rv && $cmd ne 'log');
+exit 1 if (!$rv && $cmd && $cmd ne 'log');
 
 usage(0) if $_help;
 version() if $_version;
@@ -1682,7 +1682,10 @@ sub find_parent_branch {
        }
        my ($r0, $parent) = $gs->find_rev_before($r, 1);
        if (!defined $r0 || !defined $parent) {
-               $gs->fetch(0, $r);
+               my ($base, $head) = parse_revision_argument(0, $r);
+               if ($base <= $r) {
+                       $gs->fetch($base, $r);
+               }
                ($r0, $parent) = $gs->last_rev_commit;
        }
        if (defined $r0 && defined $parent) {
@@ -3159,6 +3162,8 @@ sub match_globs {
                        my $p = $1;
                        my $pathname = $g->{path}->full_path($p);
                        next if $exists->{$pathname};
+                       next if ($self->check_path($pathname, $r) !=
+                                $SVN::Node::dir);
                        $exists->{$pathname} = Git::SVN->init(
                                              $self->{url}, $pathname, undef,
                                              $g->{ref}->full_path($p), 1);
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 3c768fbc631387b59bbbae3423b65b6a311a702b..7aad261d485f209f7459362dbe5a7cf5f6a9cf42 100644 (file)
@@ -12,9 +12,10 @@ static const char index_pack_usage[] =
 
 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 +23,7 @@ struct object_entry
 
 union delta_base {
        unsigned char sha1[20];
-       unsigned long offset;
+       off_t offset;
 };
 
 /*
@@ -83,8 +84,10 @@ static unsigned display_progress(unsigned n, unsigned total, unsigned last_pc)
 
 /* 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. */
@@ -127,8 +130,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 +224,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 +260,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 +281,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)
@@ -515,7 +528,7 @@ static void parse_pack_objects(unsigned char *sha1)
                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 +549,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 +570,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);
 }
 
@@ -655,6 +671,9 @@ static void readjust_pack_header_and_sha1(unsigned char *sha1)
        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)
 {
        struct object_entry *a = *(struct object_entry **)_a;
@@ -670,9 +689,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 +703,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 +721,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 +748,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 +932,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;
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 bed6b21daf302c76cb87bb99b613f168df9899e1..23db35aff21d7c33197726128e2f120291e6e9f0 100644 (file)
@@ -8,8 +8,11 @@ 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;
        }
@@ -28,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;
index 3096594b3e9f07785229236700c6039efd61c305..403a4c8bca3062c8fa586e369b3fd1308c919641 100644 (file)
@@ -15,6 +15,8 @@
 #include "unpack-trees.h"
 #include "path-list.h"
 #include "xdiff-interface.h"
+#include "interpolate.h"
+#include "attr.h"
 
 static int subtree_merge;
 
@@ -95,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)
@@ -174,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)
 {
@@ -377,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;
 
@@ -574,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,
@@ -594,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
@@ -620,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);
 }
@@ -659,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)
@@ -687,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");
@@ -1018,9 +1365,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 */
@@ -1033,12 +1380,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 */
@@ -1139,6 +1486,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.");
 
@@ -1185,15 +1538,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);
@@ -1301,15 +1651,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;
 }
@@ -1386,12 +1727,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];
@@ -1402,8 +1739,6 @@ 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 f58083d11e0cfb974861d340bdea4ae18d2469e8..d04536bbff7cba22ca67521d45e690dfa5aa8675 100644 (file)
@@ -40,7 +40,7 @@ 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++) {
                const unsigned char *sha1;
                void *data;
@@ -79,7 +79,7 @@ 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++) {
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);
index 9b117fd0d736615a8c4aeda742384956373d295a..437516142cb6c14f197dc5821635a6ff8bc91adf 100644 (file)
@@ -13,7 +13,7 @@ my %pm = ('Git.pm' => '$(INST_LIBDIR)/Git.pm');
 # We come with our own bundled Error.pm. It's not in the set of default
 # Perl modules so install it if it's not available on the system yet.
 eval { require Error };
-if ($@) {
+if ($@ || $Error::VERSION < 0.15009) {
        $pm{'private-Error.pm'} = '$(INST_LIBDIR)/Error.pm';
 }
 
index 54573ce2ee3b2c70d5419716b20ade61683bc289..d2f332a6222cc3543bde8fa661c54c5c904cf561 100644 (file)
@@ -5,6 +5,7 @@
  */
 #include "cache.h"
 #include "cache-tree.h"
+#include "refs.h"
 
 /* Index extensions.
  *
@@ -91,6 +92,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) {
@@ -102,6 +120,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;
        }
@@ -127,6 +148,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));
        }
@@ -334,10 +361,14 @@ int add_file_to_cache(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);
diff --git a/refs.c b/refs.c
index d2b7b7fb56f76294bb48526496429968d86e49b2..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)
 {
@@ -705,7 +853,7 @@ static int repack_without_ref(const char *refname)
        return commit_lock_file(&packlock);
 }
 
-int delete_ref(const char *refname, unsigned char *sha1)
+int delete_ref(const char *refname, const unsigned char *sha1)
 {
        struct ref_lock *lock;
        int err, i, ret = 0, flag = 0;
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 4304fe9bbc2b8e796e944fa7ddb2ea2791000adf..06426640eae9db23a0e68e28b74402662fc4da87 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,37 +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;
+       }
 }
 
-const unsigned char *nth_packed_object_sha1(const struct packed_git *p,
-                                           uint32_t n)
+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 NULL;
-       return index + 24 * n + 4;
+       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
@@ -2277,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;
@@ -2332,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 267ea3f3edc63600bc1591d2115ee85222f3e8c5..b0b12bbe9de11403c5965518026433c8851e092b 100644 (file)
@@ -76,7 +76,7 @@ static int find_short_packed_object(int len, const unsigned char *match, unsigne
 
        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;
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 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
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/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 da9da9218065678a5f223143e3d3de2b928e7114..0a97b75288d44cf93e0a8f8d9ab1b76715f946d1 100755 (executable)
@@ -84,6 +84,10 @@ 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 &&
diff --git a/t/t4121-apply-diffs.sh b/t/t4121-apply-diffs.sh
new file mode 100755 (executable)
index 0000000..2b2f1ed
--- /dev/null
@@ -0,0 +1,33 @@
+#!/bin/sh
+
+test_description='git-apply for contextually independent diffs'
+. ./test-lib.sh
+
+echo '1
+2
+3
+4
+5
+6
+7
+8' >file
+
+test_expect_success 'setup' \
+       'git add file &&
+       git commit -q -m 1 &&
+       git checkout -b test &&
+       mv file file.tmp &&
+       echo 0 >file &&
+       cat file.tmp >>file &&
+       rm file.tmp &&
+       git commit -a -q -m 2 &&
+       echo 9 >>file &&
+       git commit -a -q -m 3 &&
+       git checkout master'
+
+test_expect_success \
+       'check if contextually independent diffs for the same file apply' \
+       '( git diff test~2 test~1; git diff test~1 test~0 )| git apply'
+
+test_done
+
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..232e5f1
--- /dev/null
@@ -0,0 +1,146 @@
+#!/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 &&
+     for i in `seq -w 100`
+     do
+         echo $i >file_$i &&
+         test-genrandom "$i" 8192 >>file_$i &&
+         git-update-index --add file_$i || return 1
+     done &&
+     echo 101 >file_101 && tail -c 8192 file_100 >>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
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/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/tree.c b/tree.c
index e5bfbceb22a2fde619bfc3121fa81251c6f51765..e4a39aa3c36964c9034d2393a8b9cb483d1543da 100644 (file)
--- a/tree.c
+++ b/tree.c
@@ -139,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;
@@ -148,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;
@@ -158,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 a0b676903ad042bfc1bb199a33f1658cdfd511c9..5139481358d0838a95db3fdb62e32221eb7343b1 100644 (file)
@@ -665,7 +665,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;
@@ -675,13 +674,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];
@@ -697,13 +693,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.
         */
@@ -755,6 +744,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.
@@ -786,11 +792,11 @@ 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]) {
+                       if (stages[i] && stages[i] != o->df_conflict_entry) {
                                keep_entry(stages[i], o);
                                count++;
                                break;