Merge branch 'ra/anno' into next
authorJunio C Hamano <junkio@cox.net>
Wed, 22 Feb 2006 10:07:20 +0000 (02:07 -0800)
committerJunio C Hamano <junkio@cox.net>
Wed, 22 Feb 2006 10:07:20 +0000 (02:07 -0800)
* ra/anno:
  Use Ryan's git-annotate instead of jsannotate

64 files changed:
Documentation/git-add.txt
Documentation/git-ls-files.txt
Documentation/git-pack-objects.txt
Documentation/git-push.txt
Documentation/git-rebase.txt
Documentation/git-repack.txt
Documentation/git-update-index.txt
Makefile
apply.c
blame.c [new file with mode: 0644]
cache.h
checkout-index.c
commit-tree.c
config.c
contrib/gitview/gitview
diff-delta.c
diff-files.c
diff-index.c
diff.c
entry.c
environment.c
fetch-pack.c
git-add.sh
git-am.sh
git-applymbox.sh
git-clone.sh
git-cvsimport.perl
git-fetch.sh
git-fmt-merge-msg.perl
git-format-patch.sh
git-merge.sh
git-push.sh
git-rebase.sh
git-repack.sh
git-rerere.perl
git-resolve.sh
git-revert.sh
git-send-email.perl
git-svnimport.perl
gitk
ident.c
ls-files.c
merge-tree.c [new file with mode: 0644]
mktree.c [new file with mode: 0644]
pack-objects.c
pack.h
read-cache.c
read-tree.c
rev-list.c
rev-parse.c
send-pack.c
sha1_file.c
t/Makefile
t/t0000-basic.sh
t/t3020-ls-files-error-unmatch.sh [new file with mode: 0755]
t/t3700-add.sh [new file with mode: 0755]
t/t5600-clone-fail-cleanup.sh [new file with mode: 0755]
t/t6021-merge-criss-cross.sh
t/t6022-merge-rename.sh
t/test-lib.sh
update-index.c
upload-pack.c
var.c
write-tree.c

index 89e4614..7e29383 100644 (file)
@@ -7,7 +7,7 @@ git-add - Add files to the index file.
 
 SYNOPSIS
 --------
-'git-add' [-n] [-v] <file>...
+'git-add' [-n] [-v] [--] <file>...
 
 DESCRIPTION
 -----------
@@ -26,6 +26,11 @@ OPTIONS
 -v::
         Be verbose.
 
+--::
+       This option can be used to separate command-line options from
+       the list of files, (useful when filenames might be mistaken
+       for command-line options).
+
 
 DISCUSSION
 ----------
index fe53412..e813f84 100644 (file)
@@ -8,12 +8,14 @@ git-ls-files - Information about files in the index/working directory
 
 SYNOPSIS
 --------
-'git-ls-files' [-z] [-t]
+[verse]
+'git-ls-files' [-z] [-t] [-v]
                (--[cached|deleted|others|ignored|stage|unmerged|killed|modified])\*
                (-[c|d|o|i|s|u|k|m])\*
                [-x <pattern>|--exclude=<pattern>]
                [-X <file>|--exclude-from=<file>]
                [--exclude-per-directory=<file>] 
+               [--error-unmatch]
                [--full-name] [--] [<file>]\*
 
 DESCRIPTION
@@ -72,6 +74,10 @@ OPTIONS
        read additional exclude patterns that apply only to the
        directory and its subdirectories in <file>.
 
+--error-unmatch::
+       If any <file> does not appear in the index, treat this as an
+       error (return 1).
+
 -t::
        Identify the file status with the following tags (followed by
        a space) at the start of each line:
@@ -82,6 +88,10 @@ OPTIONS
        K::     to be killed
        ?::     other
 
+-v::
+       Similar to `-t`, but use lowercase letters for files
+       that are marked as 'always matching index'.
+
 --full-name::
        When run from a subdirectory, the command usually
        outputs paths relative to the current directory.  This
index 2d67d39..4cb2e83 100644 (file)
@@ -8,7 +8,10 @@ git-pack-objects - Create a packed archive of objects.
 
 SYNOPSIS
 --------
-'git-pack-objects' [--non-empty] [--local] [--incremental] [--window=N] [--depth=N] {--stdout | base-name} < object-list
+[verse]
+'git-pack-objects' [-q] [--no-reuse-delta] [--non-empty]
+       [--local] [--incremental] [--window=N] [--depth=N]
+       {--stdout | base-name} < object-list
 
 
 DESCRIPTION
@@ -32,6 +35,10 @@ Placing both in the pack/ subdirectory of $GIT_OBJECT_DIRECTORY (or
 any of the directories on $GIT_ALTERNATE_OBJECT_DIRECTORIES)
 enables git to read from such an archive.
 
+In a packed archive, an object is either stored as a compressed
+whole, or as a difference from some other object.  The latter is
+often called a delta.
+
 
 OPTIONS
 -------
@@ -74,6 +81,18 @@ base-name::
         Only create a packed archive if it would contain at
         least one object.
 
+-q::
+       This flag makes the command not to report its progress
+       on the standard error stream.
+
+--no-reuse-delta::
+       When creating a packed archive in a repository that
+       has existing packs, the command reuses existing deltas.
+       This sometimes results in a slightly suboptimal pack.
+       This flag tells the command not to reuse existing deltas
+       but compute them from scratch.
+
+
 Author
 ------
 Written by Linus Torvalds <torvalds@osdl.org>
index 5b89110..6f4a48a 100644 (file)
@@ -43,6 +43,12 @@ to fast forward the remote ref that matches <dst>.  If
 the optional plus `+` is used, the remote ref is updated
 even if it does not result in a fast forward update.
 +
+Note: If no explicit refspec is found, (that is neither
+on the command line nor in any Push line of the
+corresponding remotes file---see below), then all the
+refs that exist both on the local side and on the remote
+side are updated.
++
 Some short-cut notations are also supported.
 +
 * `tag <tag>` means the same as `refs/tags/<tag>:refs/tags/<tag>`.
index 16c158f..f037d12 100644 (file)
@@ -7,14 +7,54 @@ git-rebase - Rebase local commits to new upstream head.
 
 SYNOPSIS
 --------
-'git-rebase' <upstream> [<head>]
+'git-rebase' [--onto <newbase>] <upstream> [<branch>]
 
 DESCRIPTION
 -----------
-Rebases local commits to the new head of the upstream tree.
+git-rebase applies to <upstream> (or optionally to <newbase>) commits
+from <branch> that do not appear in <upstream>. When <branch> is not
+specified it defaults to the current branch (HEAD).
+
+When git-rebase is complete, <branch> will be updated to point to the
+newly created line of commit objects, so the previous line will not be
+accessible unless there are other references to it already.
+
+Assume the following history exists and the current branch is "topic":
+
+          A---B---C topic
+         /
+    D---E---F---G master
+
+From this point, the result of the following commands:
+
+    git-rebase master
+    git-rebase master topic
+
+would be:
+
+                  A'--B'--C' topic
+                 /
+    D---E---F---G master
+
+While, starting from the same point, the result of the following
+commands:
+
+    git-rebase --onto master~1 master
+    git-rebase --onto master~1 master topic
+
+would be:
+
+              A'--B'--C' topic
+             /
+    D---E---F---G master
 
 OPTIONS
 -------
+<newbase>::
+       Starting point at which to create the new commits. If the
+       --onto option is not specified, the starting point is
+       <upstream>.
+
 <upstream>::
        Upstream branch to compare against.
 
index 9060fe8..6c0f792 100644 (file)
@@ -9,7 +9,7 @@ objects into pack files.
 
 SYNOPSIS
 --------
-'git-repack' [-a] [-d] [-l] [-n]
+'git-repack' [-a] [-d] [-f] [-l] [-n] [-q]
 
 DESCRIPTION
 -----------
@@ -43,6 +43,14 @@ OPTIONS
         Pass the `--local` option to `git pack-objects`, see
         gitlink:git-pack-objects[1].
 
+-f::
+        Pass the `--no-reuse-delta` option to `git pack-objects`, see
+        gitlink:git-pack-objects[1].
+
+-q::
+        Pass the `-q` option to `git pack-objects`, see
+        gitlink:git-pack-objects[1].
+
 -n::
         Do not update the server information with
         `git update-server-info`.
index c74311d..0a1b0ad 100644 (file)
@@ -8,11 +8,14 @@ git-update-index - Modifies the index or directory cache
 
 SYNOPSIS
 --------
+[verse]
 'git-update-index'
             [--add] [--remove | --force-remove] [--replace] 
             [--refresh [-q] [--unmerged] [--ignore-missing]]
             [--cacheinfo <mode> <object> <file>]\*
             [--chmod=(+|-)x]
+            [--assume-unchanged | --no-assume-unchanged]
+            [--really-refresh]
             [--info-only] [--index-info]
             [-z] [--stdin]
             [--verbose]
@@ -65,6 +68,18 @@ OPTIONS
 --chmod=(+|-)x::
         Set the execute permissions on the updated files.        
 
+--assume-unchanged, --no-assume-unchanged::
+       When these flags are specified, the object name recorded
+       for the paths are not updated.  Instead, these options
+       sets and unsets the "assume unchanged" bit for the
+       paths.  When the "assume unchanged" bit is on, git stops
+       checking the working tree files for possible
+       modifications, so you need to manually unset the bit to
+       tell git when you change the working tree file. This is
+       sometimes helpful when working with a big project on a
+       filesystem that has very slow lstat(2) system call
+       (e.g. cifs).
+
 --info-only::
        Do not create objects in the object database for all
        <file> arguments that follow this flag; just insert
@@ -193,6 +208,37 @@ $ git ls-files -s
 ------------
 
 
+Using "assume unchanged" bit
+----------------------------
+
+Many operations in git depend on your filesystem to have an
+efficient `lstat(2)` implementation, so that `st_mtime`
+information for working tree files can be cheaply checked to see
+if the file contents have changed from the version recorded in
+the index file.  Unfortunately, some filesystems have
+inefficient `lstat(2)`.  If your filesystem is one of them, you
+can set "assume unchanged" bit to paths you have not changed to
+cause git not to do this check.  Note that setting this bit on a
+path does not mean git will check the contents of the file to
+see if it has changed -- it makes git to omit any checking and
+assume it has *not* changed.  When you make changes to working
+tree files, you have to explicitly tell git about it by dropping
+"assume unchanged" bit, either before or after you modify them.
+
+In order to set "assume unchanged" bit, use `--assume-unchanged`
+option.  To unset, use `--no-assume-unchanged`.
+
+The command looks at `core.ignorestat` configuration variable.  When
+this is true, paths updated with `git-update-index paths...` and
+paths updated with other git commands that update both index and
+working tree (e.g. `git-apply --index`, `git-checkout-index -u`,
+and `git-read-tree -u`) are automatically marked as "assume
+unchanged".  Note that "assume unchanged" bit is *not* set if
+`git-update-index --refresh` finds the working tree file matches
+the index (use `git-update-index --really-refresh` if you want
+to mark them as "assume unchanged").
+
+
 Examples
 --------
 To update and refresh only the files already checked out:
@@ -201,6 +247,35 @@ To update and refresh only the files already checked out:
 $ git-checkout-index -n -f -a && git-update-index --ignore-missing --refresh
 ----------------
 
+On an inefficient filesystem with `core.ignorestat` set:
+
+------------
+$ git update-index --really-refresh <1>
+$ git update-index --no-assume-unchanged foo.c <2>
+$ git diff --name-only <3>
+$ edit foo.c
+$ git diff --name-only <4>
+M foo.c
+$ git update-index foo.c <5>
+$ git diff --name-only <6>
+$ edit foo.c
+$ git diff --name-only <7>
+$ git update-index --no-assume-unchanged foo.c <8>
+$ git diff --name-only <9>
+M foo.c
+
+<1> forces lstat(2) to set "assume unchanged" bits for paths
+    that match index.
+<2> mark the path to be edited.
+<3> this does lstat(2) and finds index matches the path.
+<4> this does lstat(2) and finds index does not match the path.
+<5> registering the new version to index sets "assume unchanged" bit.
+<6> and it is assumed unchanged.
+<7> even after you edit it.
+<8> you can tell about the change after the fact.
+<9> now it checks with lstat(2) and finds it has been changed.
+------------
+
 
 Configuration
 -------------
@@ -213,6 +288,9 @@ in the index and the file mode on the filesystem if they differ only on
 executable bit.   On such an unfortunate filesystem, you may
 need to use `git-update-index --chmod=`.
 
+The command looks at `core.ignorestat` configuration variable.  See
+'Using "assume unchanged" bit' section above.
+
 
 See Also
 --------
index 86ffcf4..d13e7e9 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -55,6 +55,11 @@ all:
 #
 # Define NO_ICONV if your libc does not properly support iconv.
 #
+# Define NO_ACCURATE_DIFF if your diff program at least sometimes misses
+# a missing newline at the end of the file.
+#
+# Define NO_PYTHON if you want to loose all benefits of the recursive merge.
+#
 # Define COLLISION_CHECK below if you believe that SHA1's
 # 1461501637330902918203684832716283019655932542976 hashes do not give you
 # sufficient guarantee that no collisions between objects will ever happen.
@@ -72,6 +77,12 @@ GIT-VERSION-FILE: .FORCE-GIT-VERSION-FILE
        @$(SHELL_PATH) ./GIT-VERSION-GEN
 -include GIT-VERSION-FILE
 
+uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not')
+uname_M := $(shell sh -c 'uname -m 2>/dev/null || echo not')
+uname_O := $(shell sh -c 'uname -o 2>/dev/null || echo not')
+uname_R := $(shell sh -c 'uname -r 2>/dev/null || echo not')
+uname_P := $(shell sh -c 'uname -p 2>/dev/null || echo not')
+
 # CFLAGS and LDFLAGS are for the users to override from the command line.
 
 CFLAGS = -g -O2 -Wall
@@ -82,7 +93,7 @@ STRIP ?= strip
 
 prefix = $(HOME)
 bindir = $(prefix)/bin
-gitexecdir = $(prefix)/bin
+gitexecdir = $(bindir)
 template_dir = $(prefix)/share/git-core/templates/
 GIT_PYTHON_DIR = $(prefix)/share/git-core/python
 # DESTDIR=
@@ -132,7 +143,7 @@ SCRIPTS = $(patsubst %.sh,%,$(SCRIPT_SH)) \
 
 # The ones that do not have to link with lcrypto nor lz.
 SIMPLE_PROGRAMS = \
-       git-get-tar-commit-id$X git-mailinfo$X git-mailsplit$X \
+       git-get-tar-commit-id$X git-mailsplit$X \
        git-stripspace$X git-daemon$X
 
 # ... and all the rest that could be moved out of bindir to gitexecdir
@@ -144,7 +155,7 @@ PROGRAMS = \
        git-diff-tree$X git-fetch-pack$X git-fsck-objects$X \
        git-hash-object$X git-index-pack$X git-init-db$X \
        git-local-fetch$X git-ls-files$X git-ls-tree$X git-merge-base$X \
-       git-merge-index$X git-mktag$X git-pack-objects$X git-patch-id$X \
+       git-merge-index$X git-mktag$X git-mktree$X git-pack-objects$X git-patch-id$X \
        git-peek-remote$X git-prune-packed$X git-read-tree$X \
        git-receive-pack$X git-rev-list$X git-rev-parse$X \
        git-send-pack$X git-show-branch$X git-shell$X \
@@ -154,7 +165,7 @@ PROGRAMS = \
        git-upload-pack$X git-verify-pack$X git-write-tree$X \
        git-update-ref$X git-symbolic-ref$X git-check-ref-format$X \
        git-name-rev$X git-pack-redundant$X git-repo-config$X git-var$X \
-       git-describe$X
+       git-describe$X git-merge-tree$X git-blame$X
 
 # what 'all' will build and 'install' will install, in gitexecdir
 ALL_PROGRAMS = $(PROGRAMS) $(SIMPLE_PROGRAMS) $(SCRIPTS)
@@ -200,12 +211,6 @@ LIB_OBJS = \
 LIBS = $(LIB_FILE)
 LIBS += -lz
 
-# Shell quote;
-# Result of this needs to be placed inside ''
-shq = $(subst ','\'',$(1))
-# This has surrounding ''
-shellquote = '$(call shq,$(1))'
-
 #
 # Platform specific tweaks
 #
@@ -213,10 +218,6 @@ shellquote = '$(call shq,$(1))'
 # We choose to avoid "if .. else if .. else .. endif endif"
 # because maintaining the nesting to match is a pain.  If
 # we had "elif" things would have been much nicer...
-uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not')
-uname_M := $(shell sh -c 'uname -m 2>/dev/null || echo not')
-uname_O := $(shell sh -c 'uname -o 2>/dev/null || echo not')
-uname_R := $(shell sh -c 'uname -r 2>/dev/null || echo not')
 
 ifeq ($(uname_S),Darwin)
        NEEDS_SSL_WITH_CRYPTO = YesPlease
@@ -231,10 +232,10 @@ endif
 ifeq ($(uname_S),SunOS)
        NEEDS_SOCKET = YesPlease
        NEEDS_NSL = YesPlease
-       NEEDS_LIBICONV = YesPlease
        SHELL_PATH = /bin/bash
        NO_STRCASESTR = YesPlease
        ifeq ($(uname_R),5.8)
+               NEEDS_LIBICONV = YesPlease
                NO_UNSETENV = YesPlease
                NO_SETENV = YesPlease
        endif
@@ -274,6 +275,16 @@ ifeq ($(uname_S),AIX)
        NO_STRCASESTR=YesPlease
        NEEDS_LIBICONV=YesPlease
 endif
+ifeq ($(uname_S),IRIX64)
+       NO_IPV6=YesPlease
+       NO_SETENV=YesPlease
+       NO_STRCASESTR=YesPlease
+       NO_SOCKADDR_STORAGE=YesPlease
+       SHELL_PATH=/usr/gnu/bin/bash
+       ALL_CFLAGS += -DPATH_MAX=1024
+       # for now, build 32-bit version
+       ALL_LDFLAGS += -L/usr/lib32
+endif
 ifneq (,$(findstring arm,$(uname_M)))
        ARM_SHA1 = YesPlease
 endif
@@ -283,8 +294,10 @@ endif
 ifdef WITH_OWN_SUBPROCESS_PY
        PYMODULES += compat/subprocess.py
 else
-       ifneq ($(shell $(PYTHON_PATH) -c 'import subprocess;print"OK"' 2>/dev/null),OK)
-               PYMODULES += compat/subprocess.py
+       ifeq ($(NO_PYTHON),)
+               ifneq ($(shell $(PYTHON_PATH) -c 'import subprocess;print"OK"' 2>/dev/null),OK)
+                       PYMODULES += compat/subprocess.py
+               endif
        endif
 endif
 
@@ -404,8 +417,25 @@ else
 endif
 endif
 endif
+ifdef NO_ACCURATE_DIFF
+       ALL_CFLAGS += -DNO_ACCURATE_DIFF
+endif
+
+# Shell quote (do not use $(call) to accomodate ancient setups);
+
+SHA1_HEADER_SQ = $(subst ','\'',$(SHA1_HEADER))
 
-ALL_CFLAGS += -DSHA1_HEADER=$(call shellquote,$(SHA1_HEADER)) $(COMPAT_CFLAGS)
+DESTDIR_SQ = $(subst ','\'',$(DESTDIR))
+bindir_SQ = $(subst ','\'',$(bindir))
+gitexecdir_SQ = $(subst ','\'',$(gitexecdir))
+template_dir_SQ = $(subst ','\'',$(template_dir))
+
+SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH))
+PERL_PATH_SQ = $(subst ','\'',$(PERL_PATH))
+PYTHON_PATH_SQ = $(subst ','\'',$(PYTHON_PATH))
+GIT_PYTHON_DIR_SQ = $(subst ','\'',$(GIT_PYTHON_DIR))
+
+ALL_CFLAGS += -DSHA1_HEADER='$(SHA1_HEADER_SQ)' $(COMPAT_CFLAGS)
 LIB_OBJS += $(COMPAT_OBJS)
 export prefix TAR INSTALL DESTDIR SHELL_PATH template_dir
 ### Build rules
@@ -424,23 +454,24 @@ git$X: git.c $(LIB_FILE)
 
 $(patsubst %.sh,%,$(SCRIPT_SH)) : % : %.sh
        rm -f $@
-       sed -e '1s|#!.*/sh|#!$(call shq,$(SHELL_PATH))|' \
+       sed -e '1s|#!.*/sh|#!$(SHELL_PATH_SQ)|' \
            -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \
            -e 's/@@NO_CURL@@/$(NO_CURL)/g' \
+           -e 's/@@NO_PYTHON@@/$(NO_PYTHON)/g' \
            $@.sh >$@
        chmod +x $@
 
 $(patsubst %.perl,%,$(SCRIPT_PERL)) : % : %.perl
        rm -f $@
-       sed -e '1s|#!.*perl|#!$(call shq,$(PERL_PATH))|' \
+       sed -e '1s|#!.*perl|#!$(PERL_PATH_SQ)|' \
            -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \
            $@.perl >$@
        chmod +x $@
 
 $(patsubst %.py,%,$(SCRIPT_PYTHON)) : % : %.py
        rm -f $@
-       sed -e '1s|#!.*python|#!$(call shq,$(PYTHON_PATH))|' \
-           -e 's|@@GIT_PYTHON_PATH@@|$(call shq,$(GIT_PYTHON_DIR))|g' \
+       sed -e '1s|#!.*python|#!$(PYTHON_PATH_SQ)|' \
+           -e 's|@@GIT_PYTHON_PATH@@|$(GIT_PYTHON_DIR_SQ)|g' \
            -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \
            $@.py >$@
        chmod +x $@
@@ -466,32 +497,42 @@ git$X git.spec \
 %.o: %.S
        $(CC) -o $*.o -c $(ALL_CFLAGS) $<
 
-exec_cmd.o: ALL_CFLAGS += -DGIT_EXEC_PATH=\"$(gitexecdir)\"
+exec_cmd.o: exec_cmd.c
+       $(CC) -o $*.o -c $(ALL_CFLAGS) '-DGIT_EXEC_PATH="$(gitexecdir_SQ)"' $<
 
 git-%$X: %.o $(LIB_FILE)
        $(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(LIBS)
 
-git-mailinfo$X : SIMPLE_LIB += $(LIB_4_ICONV)
 $(SIMPLE_PROGRAMS) : $(LIB_FILE)
 $(SIMPLE_PROGRAMS) : git-%$X : %.o
        $(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \
                $(LIB_FILE) $(SIMPLE_LIB)
 
-git-http-fetch$X: fetch.o http.o
-git-http-push$X: http.o
+git-mailinfo$X: mailinfo.o $(LIB_FILE)
+       $(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \
+               $(LIB_FILE) $(SIMPLE_LIB) $(LIB_4_ICONV)
+
 git-local-fetch$X: fetch.o
 git-ssh-fetch$X: rsh.o fetch.o
 git-ssh-upload$X: rsh.o
 git-ssh-pull$X: rsh.o fetch.o
 git-ssh-push$X: rsh.o
 
-git-http-fetch$X: LIBS += $(CURL_LIBCURL)
-git-http-push$X: LIBS += $(CURL_LIBCURL) $(EXPAT_LIBEXPAT)
-git-rev-list$X: LIBS += $(OPENSSL_LIBSSL)
+git-http-fetch$X: fetch.o http.o http-fetch.o $(LIB_FILE)
+       $(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \
+               $(LIBS) $(CURL_LIBCURL)
+
+git-http-push$X: http.o http-push.o $(LIB_FILE)
+       $(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \
+               $(LIBS) $(CURL_LIBCURL) $(EXPAT_LIBEXPAT)
+
+git-rev-list$X: rev-list.o $(LIB_FILE)
+       $(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \
+               $(LIBS) $(OPENSSL_LIBSSL)
 
 init-db.o: init-db.c
        $(CC) -c $(ALL_CFLAGS) \
-               -DDEFAULT_GIT_TEMPLATE_DIR=$(call shellquote,"$(template_dir)") $*.c
+               -DDEFAULT_GIT_TEMPLATE_DIR='"$(template_dir_SQ)"' $*.c
 
 $(LIB_OBJS): $(LIB_H)
 $(patsubst git-%$X,%.o,$(PROGRAMS)): $(LIB_H)
@@ -506,6 +547,12 @@ doc:
 
 ### Testing rules
 
+# GNU make supports exporting all variables by "export" without parameters.
+# However, the environment gets quite big, and some programs have problems
+# with that.
+
+export NO_PYTHON
+
 test: all
        $(MAKE) -C t/ all
 
@@ -523,13 +570,13 @@ check:
 ### Installation rules
 
 install: all
-       $(INSTALL) -d -m755 $(call shellquote,$(DESTDIR)$(bindir))
-       $(INSTALL) -d -m755 $(call shellquote,$(DESTDIR)$(gitexecdir))
-       $(INSTALL) $(ALL_PROGRAMS) $(call shellquote,$(DESTDIR)$(gitexecdir))
-       $(INSTALL) git$X gitk $(call shellquote,$(DESTDIR)$(bindir))
+       $(INSTALL) -d -m755 '$(DESTDIR_SQ)$(bindir_SQ)'
+       $(INSTALL) -d -m755 '$(DESTDIR_SQ)$(gitexecdir_SQ)'
+       $(INSTALL) $(ALL_PROGRAMS) '$(DESTDIR_SQ)$(gitexecdir_SQ)'
+       $(INSTALL) git$X gitk '$(DESTDIR_SQ)$(bindir_SQ)'
        $(MAKE) -C templates install
-       $(INSTALL) -d -m755 $(call shellquote,$(DESTDIR)$(GIT_PYTHON_DIR))
-       $(INSTALL) $(PYMODULES) $(call shellquote,$(DESTDIR)$(GIT_PYTHON_DIR))
+       $(INSTALL) -d -m755 '$(DESTDIR_SQ)$(GIT_PYTHON_DIR_SQ)'
+       $(INSTALL) $(PYMODULES) '$(DESTDIR_SQ)$(GIT_PYTHON_DIR_SQ)'
 
 install-doc:
        $(MAKE) -C Documentation install
diff --git a/apply.c b/apply.c
index 2ad47fb..244718c 100644 (file)
--- a/apply.c
+++ b/apply.c
@@ -1142,6 +1142,14 @@ static int apply_one_fragment(struct buffer_desc *desc, struct fragment *frag)
                size -= len;
        }
 
+#ifdef NO_ACCURATE_DIFF
+       if (oldsize > 0 && old[oldsize - 1] == '\n' &&
+                       newsize > 0 && new[newsize - 1] == '\n') {
+               oldsize--;
+               newsize--;
+       }
+#endif
+                       
        offset = find_offset(buf, desc->size, old, oldsize, frag->newpos);
        if (offset >= 0) {
                int diff = newsize - oldsize;
@@ -1309,7 +1317,7 @@ static int check_patch(struct patch *patch)
                                        return -1;
                        }
 
-                       changed = ce_match_stat(active_cache[pos], &st);
+                       changed = ce_match_stat(active_cache[pos], &st, 1);
                        if (changed)
                                return error("%s: does not match index",
                                             old_name);
diff --git a/blame.c b/blame.c
new file mode 100644 (file)
index 0000000..1e65546
--- /dev/null
+++ b/blame.c
@@ -0,0 +1,443 @@
+#include <assert.h>
+
+#include "cache.h"
+#include "refs.h"
+#include "tag.h"
+#include "commit.h"
+#include "tree.h"
+#include "blob.h"
+#include "epoch.h"
+#include "diff.h"
+
+#define DEBUG 0
+
+struct commit** blame_lines;
+int num_blame_lines;
+
+struct util_info
+{
+    int* line_map;
+    int num_lines;
+    unsigned char sha1[20]; /* blob sha, not commit! */
+    char* buf;
+    unsigned long size;
+//    const char* path;
+};
+
+struct chunk
+{
+    int off1, len1; // ---
+    int off2, len2; // +++
+};
+
+struct patch
+{
+    struct chunk* chunks;
+    int num;
+};
+
+static void get_blob(struct commit* commit);
+
+int num_get_patch = 0;
+int num_commits = 0;
+
+struct patch* get_patch(struct commit* commit, struct commit* other)
+{
+    struct patch* ret = xmalloc(sizeof(struct patch));
+    ret->chunks = NULL;
+    ret->num = 0;
+
+    struct util_info* info_c = (struct util_info*) commit->object.util;
+    struct util_info* info_o = (struct util_info*) other->object.util;
+
+    if(!memcmp(info_c->sha1, info_o->sha1, 20))
+        return ret;
+
+    get_blob(commit);
+    get_blob(other);
+
+    FILE* fout = fopen("/tmp/git-blame-tmp1", "w");
+    if(!fout)
+        die("fopen tmp1 failed: %s", strerror(errno));
+
+    if(fwrite(info_c->buf, info_c->size, 1, fout) != 1)
+        die("fwrite 1 failed: %s", strerror(errno));
+    fclose(fout);
+
+    fout = fopen("/tmp/git-blame-tmp2", "w");
+    if(!fout)
+        die("fopen tmp2 failed: %s", strerror(errno));
+
+    if(fwrite(info_o->buf, info_o->size, 1, fout) != 1)
+        die("fwrite 2 failed: %s", strerror(errno));
+    fclose(fout);
+
+    FILE* fin = popen("diff -u0 /tmp/git-blame-tmp1 /tmp/git-blame-tmp2", "r");
+    if(!fin)
+        die("popen failed: %s", strerror(errno));
+
+    char buf[1024];
+    while(fgets(buf, sizeof(buf), fin)) {
+        if(buf[0] != '@' || buf[1] != '@')
+            continue;
+
+        if(DEBUG)
+            printf("chunk line: %s", buf);
+        ret->num++;
+        ret->chunks = xrealloc(ret->chunks, sizeof(struct chunk)*ret->num);
+        struct chunk* chunk = &ret->chunks[ret->num-1];
+
+        assert(!strncmp(buf, "@@ -", 4));
+
+        char* start = buf+4;
+        char* sp = index(start, ' ');
+        *sp = '\0';
+        if(index(start, ',')) {
+            int ret = sscanf(start, "%d,%d", &chunk->off1, &chunk->len1);
+            assert(ret == 2);
+        } else {
+            int ret = sscanf(start, "%d", &chunk->off1);
+            assert(ret == 1);
+            chunk->len1 = 1;
+        }
+        *sp = ' ';
+
+        start = sp+1;
+        sp = index(start, ' ');
+        *sp = '\0';
+        if(index(start, ',')) {
+            int ret = sscanf(start, "%d,%d", &chunk->off2, &chunk->len2);
+            assert(ret == 2);
+        } else {
+            int ret = sscanf(start, "%d", &chunk->off2);
+            assert(ret == 1);
+            chunk->len2 = 1;
+        }
+        *sp = ' ';
+
+        if(chunk->off1 > 0)
+            chunk->off1 -= 1;
+        if(chunk->off2 > 0)
+            chunk->off2 -= 1;
+
+        assert(chunk->off1 >= 0);
+        assert(chunk->off2 >= 0);
+    }
+    fclose(fin);
+
+    num_get_patch++;
+    return ret;
+}
+
+void free_patch(struct patch* p)
+{
+    free(p->chunks);
+    free(p);
+}
+
+static int get_blob_sha1_internal(unsigned char *sha1, const char *base, int baselen,
+                                  const char *pathname, unsigned mode, int stage);
+
+
+static unsigned char blob_sha1[20];
+static int get_blob_sha1(struct tree* t, const char* pathname, unsigned char* sha1)
+{
+    const char *pathspec[2];
+    pathspec[0] = pathname;
+    pathspec[1] = NULL;
+    memset(blob_sha1, 0, sizeof(blob_sha1));
+    read_tree_recursive(t, "", 0, 0, pathspec, get_blob_sha1_internal);
+
+    int i;
+    for(i = 0; i < 20; i++) {
+        if(blob_sha1[i] != 0)
+            break;
+    }
+
+    if(i == 20)
+        return -1;
+
+    memcpy(sha1, blob_sha1, 20);
+    return 0;
+}
+
+static int get_blob_sha1_internal(unsigned char *sha1, const char *base, int baselen,
+                                  const char *pathname, unsigned mode, int stage)
+{
+//    printf("Got blob: %s base: '%s' baselen: %d pathname: '%s' mode: %o stage: %d\n",
+//           sha1_to_hex(sha1), base, baselen, pathname, mode, stage);
+
+    if(S_ISDIR(mode))
+        return READ_TREE_RECURSIVE;
+
+    memcpy(blob_sha1, sha1, 20);
+    return -1;
+}
+
+static void get_blob(struct commit* commit)
+{
+    struct util_info* info = commit->object.util;
+    char type[20];
+
+    if(info->buf)
+        return;
+
+    info->buf = read_sha1_file(info->sha1, type, &info->size);
+    assert(!strcmp(type, "blob"));
+}
+
+void print_patch(struct patch* p)
+{
+    printf("Num chunks: %d\n", p->num);
+    int i;
+    for(i = 0; i < p->num; i++) {
+        printf("%d,%d %d,%d\n", p->chunks[i].off1, p->chunks[i].len1, p->chunks[i].off2, p->chunks[i].len2);
+    }
+}
+
+
+// p is a patch from commit to other.
+void fill_line_map(struct commit* commit, struct commit* other, struct patch* p)
+{
+    int num_lines = ((struct util_info*) commit->object.util)->num_lines;
+    int* line_map = ((struct util_info*) commit->object.util)->line_map;
+    int num_lines2 = ((struct util_info*) other->object.util)->num_lines;
+    int* line_map2 = ((struct util_info*) other->object.util)->line_map;
+    int cur_chunk = 0;
+    int i1, i2;
+
+    if(p->num && DEBUG)
+        print_patch(p);
+
+    for(i1 = 0; i1 < num_lines; i1++)
+        line_map[i1] = -1;
+
+    if(DEBUG)
+        printf("num lines 1: %d num lines 2: %d\n", num_lines, num_lines2);
+
+    for(i1 = 0, i2 = 0; i1 < num_lines; i1++, i2++) {
+        if(DEBUG > 1)
+            printf("%d %d\n", i1, i2);
+
+        if(i2 >= num_lines2)
+            break;
+
+        line_map[i1] = line_map2[i2];
+
+        struct chunk* chunk = NULL;
+        if(cur_chunk < p->num)
+            chunk = &p->chunks[cur_chunk];
+
+        if(chunk && chunk->off1 == i1) {
+            i2 = chunk->off2;
+
+            if(chunk->len1 > 0)
+                i1 += chunk->len1-1;
+            if(chunk->len2 > 0)
+                i2 += chunk->len2-1;
+            cur_chunk++;
+        }
+    }
+}
+
+int map_line(struct commit* commit, int line)
+{
+    struct util_info* info = commit->object.util;
+    assert(line >= 0 && line < info->num_lines);
+    return info->line_map[line];
+}
+
+int fill_util_info(struct commit* commit, const char* path)
+{
+    if(commit->object.util)
+        return 0;
+
+    struct util_info* util = xmalloc(sizeof(struct util_info));
+    util->buf = NULL;
+    util->size = 0;
+    util->num_lines = -1;
+    util->line_map = NULL;
+
+    commit->object.util = util;
+
+    if(get_blob_sha1(commit->tree, path, util->sha1))
+        return -1;
+
+    return 0;
+}
+
+void alloc_line_map(struct commit* commit)
+{
+    struct util_info* util = commit->object.util;
+
+    if(util->line_map)
+        return;
+
+    get_blob(commit);
+
+    int i;
+    util->num_lines = 0;
+    for(i = 0; i < util->size; i++) {
+        if(util->buf[i] == '\n')
+            util->num_lines++;
+    }
+    util->line_map = xmalloc(sizeof(int)*util->num_lines);
+}
+
+void copy_line_map(struct commit* dst, struct commit* src)
+{
+    struct util_info* u_dst = dst->object.util;
+    struct util_info* u_src = src->object.util;
+
+    u_dst->line_map = u_src->line_map;
+    u_dst->num_lines = u_src->num_lines;
+    u_dst->buf = u_src->buf;
+    u_dst->size = u_src->size;
+}
+
+void process_commits(struct commit_list* list, const char* path)
+{
+    int i;
+
+    while(list) {
+        struct commit* commit = pop_commit(&list);
+        struct commit_list* parents;
+        struct util_info* info;
+
+        info = commit->object.util;
+        num_commits++;
+        if(DEBUG)
+            printf("\nProcessing commit: %d %s\n", num_commits, sha1_to_hex(commit->object.sha1));
+        for(parents = commit->parents;
+            parents != NULL; parents = parents->next) {
+            struct commit* parent = parents->item;
+
+            if(parse_commit(parent) < 0)
+                die("parse_commit error");
+
+            if(DEBUG)
+                printf("parent: %s\n", sha1_to_hex(parent->object.sha1));
+
+            if(fill_util_info(parent, path))
+                continue;
+
+            // Temporarily assign everything to the parent.
+            int num_blame = 0;
+            for(i = 0; i < num_blame_lines; i++) {
+                if(blame_lines[i] == commit) {
+                    num_blame++;
+                    blame_lines[i] = parent;
+                }
+            }
+
+            if(num_blame == 0)
+                continue;
+
+            struct patch* patch = get_patch(parent, commit);
+            if(patch->num == 0) {
+                copy_line_map(parent, commit);
+            } else {
+                alloc_line_map(parent);
+                fill_line_map(parent, commit, patch);
+            }
+
+            for(i = 0; i < patch->num; i++) {
+                int l;
+                for(l = 0; l < patch->chunks[i].len2; l++) {
+                    int mapped_line = map_line(commit, patch->chunks[i].off2 + l);
+                    if(mapped_line != -1 && blame_lines[mapped_line] == parent)
+                        blame_lines[mapped_line] = commit;
+                }
+            }
+            free_patch(patch);
+        }
+    }
+}
+
+#define SEEN 1
+struct commit_list* get_commit_list(struct commit* commit, const char* pathname)
+{
+    struct commit_list* ret = NULL;
+    struct commit_list* process = NULL;
+    unsigned char sha1[20];
+
+    commit_list_insert(commit, &process);
+
+    while(process) {
+        struct commit* com = pop_commit(&process);
+        if(com->object.flags & SEEN)
+            continue;
+
+        com->object.flags |= SEEN;
+        commit_list_insert(com, &ret);
+        struct commit_list* parents;
+
+        parse_commit(com);
+
+        for(parents = com->parents;
+            parents != NULL; parents = parents->next) {
+            struct commit* parent = parents->item;
+
+            parse_commit(parent);
+
+            if(!get_blob_sha1(parent->tree, pathname, sha1))
+                commit_list_insert(parent, &process);
+        }
+    }
+
+    return ret;
+}
+
+int main(int argc, const char **argv)
+{
+    unsigned char sha1[20];
+    struct commit *commit;
+    const char* filename;
+    int i;
+
+    setup_git_directory();
+
+    if (argc != 3)
+        die("Usage: blame commit-ish file");
+
+    if (get_sha1(argv[1], sha1))
+        die("get_sha1 failed");
+
+    commit = lookup_commit_reference(sha1);
+
+    filename = argv[2];
+
+    struct commit_list* list = get_commit_list(commit, filename);
+    sort_in_topological_order(&list, 1);
+
+    if(fill_util_info(commit, filename)) {
+        printf("%s not found in %s\n", filename, argv[1]);
+        return 0;
+    }
+    alloc_line_map(commit);
+
+    struct util_info* util = commit->object.util;
+    num_blame_lines = util->num_lines;
+    blame_lines = xmalloc(sizeof(struct commit*)*num_blame_lines);
+
+
+    for(i = 0; i < num_blame_lines; i++) {
+        blame_lines[i] = commit;
+
+        ((struct util_info*) commit->object.util)->line_map[i] = i;
+    }
+
+    process_commits(list, filename);
+
+    for(i = 0; i < num_blame_lines; i++) {
+        printf("%d %s\n", i+1-1, sha1_to_hex(blame_lines[i]->object.sha1));
+//        printf("%d %s\n", i+1-1, find_unique_abbrev(blame_lines[i]->object.sha1, 6));
+    }
+
+    if(DEBUG) {
+        printf("num get patch: %d\n", num_get_patch);
+        printf("num commits: %d\n", num_commits);
+    }
+
+    return 0;
+}
diff --git a/cache.h b/cache.h
index b5db01f..5020f07 100644 (file)
--- a/cache.h
+++ b/cache.h
@@ -91,6 +91,7 @@ struct cache_entry {
 #define CE_NAMEMASK  (0x0fff)
 #define CE_STAGEMASK (0x3000)
 #define CE_UPDATE    (0x4000)
+#define CE_VALID     (0x8000)
 #define CE_STAGESHIFT 12
 
 #define create_ce_flags(len, stage) htons((len) | ((stage) << CE_STAGESHIFT))
@@ -144,8 +145,8 @@ extern int add_cache_entry(struct cache_entry *ce, int option);
 extern int remove_cache_entry_at(int pos);
 extern int remove_file_from_cache(const char *path);
 extern int ce_same_name(struct cache_entry *a, struct cache_entry *b);
-extern int ce_match_stat(struct cache_entry *ce, struct stat *st);
-extern int ce_modified(struct cache_entry *ce, struct stat *st);
+extern int ce_match_stat(struct cache_entry *ce, struct stat *st, int);
+extern int ce_modified(struct cache_entry *ce, struct stat *st, int);
 extern int ce_path_match(const struct cache_entry *ce, const char **pathspec);
 extern int index_fd(unsigned char *sha1, int fd, struct stat *st, int write_object, const char *type);
 extern int index_pipe(unsigned char *sha1, int fd, const char *type, int write_object);
@@ -161,6 +162,7 @@ extern int commit_index_file(struct cache_file *);
 extern void rollback_index_file(struct cache_file *);
 
 extern int trust_executable_bit;
+extern int assume_unchanged;
 extern int only_use_symrefs;
 extern int diff_rename_limit_default;
 extern int shared_repository;
@@ -246,8 +248,8 @@ void datestamp(char *buf, int bufsize);
 unsigned long approxidate(const char *);
 
 extern int setup_ident(void);
-extern const char *git_author_info(void);
-extern const char *git_committer_info(void);
+extern const char *git_author_info(int);
+extern const char *git_committer_info(int);
 
 struct checkout {
        const char *base_dir;
index 53dd8cb..957b4a8 100644 (file)
@@ -116,6 +116,7 @@ int main(int argc, char **argv)
        int all = 0;
 
        prefix = setup_git_directory();
+       git_config(git_default_config);
        prefix_length = prefix ? strlen(prefix) : 0;
 
        if (read_cache() < 0) {
index b1c8dca..88871b0 100644 (file)
@@ -118,8 +118,8 @@ int main(int argc, char **argv)
                add_buffer(&buffer, &size, "parent %s\n", sha1_to_hex(parent_sha1[i]));
 
        /* Person/date information */
-       add_buffer(&buffer, &size, "author %s\n", git_author_info());
-       add_buffer(&buffer, &size, "committer %s\n\n", git_committer_info());
+       add_buffer(&buffer, &size, "author %s\n", git_author_info(1));
+       add_buffer(&buffer, &size, "committer %s\n\n", git_committer_info(1));
 
        /* And add the comment */
        while (fgets(comment, sizeof(comment), stdin) != NULL)
index 8355224..7dbdce1 100644 (file)
--- a/config.c
+++ b/config.c
@@ -222,6 +222,11 @@ int git_default_config(const char *var, const char *value)
                return 0;
        }
 
+       if (!strcmp(var, "core.ignorestat")) {
+               assume_unchanged = git_config_bool(var, value);
+               return 0;
+       }
+
        if (!strcmp(var, "core.symrefsonly")) {
                only_use_symrefs = git_config_bool(var, value);
                return 0;
index 5862fcc..5c338c0 100755 (executable)
@@ -56,20 +56,6 @@ def show_date(epoch, tz):
 
        return time.strftime("%Y-%m-%d %H:%M:%S", time.gmtime(secs))
 
-def get_sha1_from_tags(line):
-       fp = os.popen("git cat-file -t " + line)
-       entry = string.strip(fp.readline())
-       fp.close()
-       if (entry == "commit"):
-               return line
-       elif (entry == "tag"):
-               fp = os.popen("git cat-file tag "+ line)
-               entry = string.strip(fp.readline())
-               fp.close()
-               obj = re.split(" ", entry)
-               if (obj[0] == "object"):
-                       return obj[1]
-       return None
 
 class CellRendererGraph(gtk.GenericCellRenderer):
        """Cell renderer for directed graph.
@@ -174,9 +160,9 @@ class CellRendererGraph(gtk.GenericCellRenderer):
                names_len = 0
                if (len(names) != 0):
                        for item in names:
-                               names_len += len(item)/3
+                               names_len += len(item)
 
-               width = box_size * (cols + 1 + names_len )
+               width = box_size * (cols + 1 ) + names_len 
                height = box_size
 
                # FIXME I have no idea how to use cell_area properly
@@ -258,6 +244,8 @@ class CellRendererGraph(gtk.GenericCellRenderer):
                        for item in names:
                                name = name + item + " "
 
+                       ctx.select_font_face("Monospace")
+                       ctx.set_font_size(13)
                        ctx.text_path(name)
 
                self.set_colour(ctx, colour, 0.0, 0.5)
@@ -465,32 +453,24 @@ class GitView:
                respective sha1 details """
 
                self.bt_sha1 = { }
+               ls_remote = re.compile('^(.{40})\trefs/([^^]+)(?:\\^(..))?$');
                git_dir = os.getenv("GIT_DIR")
                if (git_dir == None):
                        git_dir = ".git"
 
-               #FIXME the path seperator
-               ref_files = os.listdir(git_dir + "/refs/tags")
-               for file in ref_files:
-                       fp = open(git_dir + "/refs/tags/"+file)
-                       sha1 = get_sha1_from_tags(string.strip(fp.readline()))
-                       try:
-                               self.bt_sha1[sha1].append(file)
-                       except KeyError:
-                               self.bt_sha1[sha1] = [file]
-                       fp.close()
-
-
-               #FIXME the path seperator
-               ref_files = os.listdir(git_dir + "/refs/heads")
-               for file in ref_files:
-                       fp = open(git_dir + "/refs/heads/" + file)
-                       sha1 = get_sha1_from_tags(string.strip(fp.readline()))
-                       try:
-                               self.bt_sha1[sha1].append(file)
-                       except KeyError:
-                               self.bt_sha1[sha1] = [file]
-                       fp.close()
+               fp = os.popen('git ls-remote ' + git_dir)
+               while 1:
+                       line = string.strip(fp.readline())
+                       if line == '':
+                               break
+                       m = ls_remote.match(line)
+                       if not m:
+                               continue
+                       (sha1, name) = (m.group(1), m.group(2))
+                       if not self.bt_sha1.has_key(sha1):
+                               self.bt_sha1[sha1] = []
+                       self.bt_sha1[sha1].append(name)
+               fp.close()
 
 
        def construct(self):
@@ -537,8 +517,8 @@ class GitView:
 
                cell = CellRendererGraph()
                column = gtk.TreeViewColumn()
-               column.set_resizable(False)
-               column.pack_start(cell, expand=False)
+               column.set_resizable(True)
+               column.pack_start(cell, expand=True)
                column.add_attribute(cell, "node", 1)
                column.add_attribute(cell, "in-lines", 2)
                column.add_attribute(cell, "out-lines", 3)
index c2f656a..2ed5984 100644 (file)
@@ -19,8 +19,9 @@
  */
 
 #include <stdlib.h>
+#include <string.h>
+#include <zlib.h>
 #include "delta.h"
-#include "zlib.h"
 
 
 /* block size: min = 16, max = 64k, power of 2 */
 #define MIN(a, b) ((a) < (b) ? (a) : (b))
 
 #define GR_PRIME 0x9e370001
-#define HASH(v, b) (((unsigned int)(v) * GR_PRIME) >> (32 - (b)))
-       
-static unsigned int hashbits(unsigned int size)
-{
-       unsigned int val = 1, bits = 0;
-       while (val < size && bits < 32) {
-               val <<= 1;
-               bits++;
-       }
-       return bits ? bits: 1;
-}
-
-typedef struct s_chanode {
-       struct s_chanode *next;
-       int icurr;
-} chanode_t;
-
-typedef struct s_chastore {
-       int isize, nsize;
-       chanode_t *ancur;
-} chastore_t;
-
-static void cha_init(chastore_t *cha, int isize, int icount)
-{
-       cha->isize = isize;
-       cha->nsize = icount * isize;
-       cha->ancur = NULL;
-}
-
-static void *cha_alloc(chastore_t *cha)
-{
-       chanode_t *ancur;
-       void *data;
+#define HASH(v, shift) (((unsigned int)(v) * GR_PRIME) >> (shift))
 
-       ancur = cha->ancur;
-       if (!ancur || ancur->icurr == cha->nsize) {
-               ancur = malloc(sizeof(chanode_t) + cha->nsize);
-               if (!ancur)
-                       return NULL;
-               ancur->icurr = 0;
-               ancur->next = cha->ancur;
-               cha->ancur = ancur;
-       }
-
-       data = (void *)ancur + sizeof(chanode_t) + ancur->icurr;
-       ancur->icurr += cha->isize;
-       return data;
-}
-
-static void cha_free(chastore_t *cha)
-{
-       chanode_t *cur = cha->ancur;
-       while (cur) {
-               chanode_t *tmp = cur;
-               cur = cur->next;
-               free(tmp);
-       }
-}
-
-typedef struct s_bdrecord {
-       struct s_bdrecord *next;
-       unsigned int fp;
+struct index {
        const unsigned char *ptr;
-} bdrecord_t;
-
-typedef struct s_bdfile {
-       chastore_t cha;
-       unsigned int fphbits;
-       bdrecord_t **fphash;
-} bdfile_t;
+       unsigned int val;
+       struct index *next;
+};
 
-static int delta_prepare(const unsigned char *buf, int bufsize, bdfile_t *bdf)
+static struct index ** delta_index(const unsigned char *buf,
+                                  unsigned long bufsize,
+                                  unsigned int *hash_shift)
 {
-       unsigned int fphbits;
-       int i, hsize;
-       const unsigned char *data, *top;
-       bdrecord_t *brec;
-       bdrecord_t **fphash;
-
-       fphbits = hashbits(bufsize / BLK_SIZE + 1);
-       hsize = 1 << fphbits;
-       fphash = malloc(hsize * sizeof(bdrecord_t *));
-       if (!fphash)
-               return -1;
-       for (i = 0; i < hsize; i++)
-               fphash[i] = NULL;
-       cha_init(&bdf->cha, sizeof(bdrecord_t), hsize / 4 + 1);
-
-       top = buf + bufsize;
-       data = buf + (bufsize / BLK_SIZE) * BLK_SIZE;
-       if (data == top)
+       unsigned int hsize, hshift, entries, blksize, i;
+       const unsigned char *data;
+       struct index *entry, **hash;
+       void *mem;
+
+       /* determine index hash size */
+       entries = (bufsize + BLK_SIZE - 1) / BLK_SIZE;
+       hsize = entries / 4;
+       for (i = 4; (1 << i) < hsize && i < 16; i++);
+       hsize = 1 << i;
+       hshift = 32 - i;
+       *hash_shift = hshift;
+
+       /* allocate lookup index */
+       mem = malloc(hsize * sizeof(*hash) + entries * sizeof(*entry));
+       if (!mem)
+               return NULL;
+       hash = mem;
+       entry = mem + hsize * sizeof(*hash);
+       memset(hash, 0, hsize * sizeof(*hash));
+
+       /* then populate it */
+       data = buf + entries * BLK_SIZE - BLK_SIZE;
+       blksize = bufsize - (data - buf);
+       while (data >= buf) {
+               unsigned int val = adler32(0, data, blksize);
+               i = HASH(val, hshift);
+               entry->ptr = data;
+               entry->val = val;
+               entry->next = hash[i];
+               hash[i] = entry++;
+               blksize = BLK_SIZE;
                data -= BLK_SIZE;
+       }
 
-       for ( ; data >= buf; data -= BLK_SIZE) {
-               brec = cha_alloc(&bdf->cha);
-               if (!brec) {
-                       cha_free(&bdf->cha);
-                       free(fphash);
-                       return -1;
-               }
-               brec->fp = adler32(0, data, MIN(BLK_SIZE, top - data));
-               brec->ptr = data;
-               i = HASH(brec->fp, fphbits);
-               brec->next = fphash[i];
-               fphash[i] = brec;
-       }
-
-       bdf->fphbits = fphbits;
-       bdf->fphash = fphash;
-
-       return 0;
-}
-
-static void delta_cleanup(bdfile_t *bdf)
-{
-       free(bdf->fphash);
-       cha_free(&bdf->cha);
+       return hash;
 }
 
+/* provide the size of the copy opcode given the block offset and size */
 #define COPYOP_SIZE(o, s) \
     (!!(o & 0xff) + !!(o & 0xff00) + !!(o & 0xff0000) + !!(o & 0xff000000) + \
      !!(s & 0xff) + !!(s & 0xff00) + 1)
 
+/* the maximum size for any opcode */
+#define MAX_OP_SIZE COPYOP_SIZE(0xffffffff, 0xffffffff)
+
 void *diff_delta(void *from_buf, unsigned long from_size,
                 void *to_buf, unsigned long to_size,
                 unsigned long *delta_size,
                 unsigned long max_size)
 {
-       int i, outpos, outsize, inscnt, csize, msize, moff;
-       unsigned int fp;
-       const unsigned char *ref_data, *ref_top, *data, *top, *ptr1, *ptr2;
-       unsigned char *out, *orig;
-       bdrecord_t *brec;
-       bdfile_t bdf;
+       unsigned int i, outpos, outsize, inscnt, hash_shift;
+       const unsigned char *ref_data, *ref_top, *data, *top;
+       unsigned char *out;
+       struct index *entry, **hash;
 
-       if (!from_size || !to_size || delta_prepare(from_buf, from_size, &bdf))
+       if (!from_size || !to_size)
+               return NULL;
+       hash = delta_index(from_buf, from_size, &hash_shift);
+       if (!hash)
                return NULL;
-       
+
        outpos = 0;
        outsize = 8192;
+       if (max_size && outsize >= max_size)
+               outsize = max_size + MAX_OP_SIZE + 1;
        out = malloc(outsize);
        if (!out) {
-               delta_cleanup(&bdf);
+               free(hash);
                return NULL;
        }
 
@@ -199,28 +138,32 @@ void *diff_delta(void *from_buf, unsigned long from_size,
        }
 
        inscnt = 0;
-       moff = 0;
-       while (data < top) {
-               msize = 0;
-               fp = adler32(0, data, MIN(top - data, BLK_SIZE));
-               i = HASH(fp, bdf.fphbits);
-               for (brec = bdf.fphash[i]; brec; brec = brec->next) {
-                       if (brec->fp == fp) {
-                               csize = ref_top - brec->ptr;
-                               if (csize > top - data)
-                                       csize = top - data;
-                               for (ptr1 = brec->ptr, ptr2 = data; 
-                                    csize && *ptr1 == *ptr2;
-                                    csize--, ptr1++, ptr2++);
 
-                               csize = ptr1 - brec->ptr;
-                               if (csize > msize) {
-                                       moff = brec->ptr - ref_data;
-                                       msize = csize;
-                                       if (msize >= 0x10000) {
-                                               msize = 0x10000;
-                                               break;
-                                       }
+       while (data < top) {
+               unsigned int moff = 0, msize = 0;
+               unsigned int blksize = MIN(top - data, BLK_SIZE);
+               unsigned int val = adler32(0, data, blksize);
+               i = HASH(val, hash_shift);
+               for (entry = hash[i]; entry; entry = entry->next) {
+                       const unsigned char *ref = entry->ptr;
+                       const unsigned char *src = data;
+                       unsigned int ref_size = ref_top - ref;
+                       if (entry->val != val)
+                               continue;
+                       if (ref_size > top - src)
+                               ref_size = top - src;
+                       while (ref_size && *src++ == *ref) {
+                               ref++;
+                               ref_size--;
+                       }
+                       ref_size = ref - entry->ptr;
+                       if (ref_size > msize) {
+                               /* this is our best match so far */
+                               moff = entry->ptr - ref_data;
+                               msize = ref_size;
+                               if (msize >= 0x10000) {
+                                       msize = 0x10000;
+                                       break;
                                }
                        }
                }
@@ -235,13 +178,15 @@ void *diff_delta(void *from_buf, unsigned long from_size,
                                inscnt = 0;
                        }
                } else {
+                       unsigned char *op;
+
                        if (inscnt) {
                                out[outpos - inscnt - 1] = inscnt;
                                inscnt = 0;
                        }
 
                        data += msize;
-                       orig = out + outpos++;
+                       op = out + outpos++;
                        i = 0x80;
 
                        if (moff & 0xff) { out[outpos++] = moff; i |= 0x01; }
@@ -256,23 +201,21 @@ void *diff_delta(void *from_buf, unsigned long from_size,
                        msize >>= 8;
                        if (msize & 0xff) { out[outpos++] = msize; i |= 0x20; }
 
-                       *orig = i;
-               }
-
-               if (max_size && outpos > max_size) {
-                       free(out);
-                       delta_cleanup(&bdf);
-                       return NULL;
+                       *op = i;
                }
 
-               /* next time around the largest possible output is 1 + 4 + 3 */
-               if (outpos > outsize - 8) {
+               if (outpos >= outsize - MAX_OP_SIZE) {
                        void *tmp = out;
                        outsize = outsize * 3 / 2;
-                       out = realloc(out, outsize);
+                       if (max_size && outsize >= max_size)
+                               outsize = max_size + MAX_OP_SIZE + 1;
+                       if (max_size && outpos > max_size)
+                               out = NULL;
+                       else
+                               out = realloc(out, outsize);
                        if (!out) {
                                free(tmp);
-                               delta_cleanup(&bdf);
+                               free(hash);
                                return NULL;
                        }
                }
@@ -281,7 +224,7 @@ void *diff_delta(void *from_buf, unsigned long from_size,
        if (inscnt)
                out[outpos - inscnt - 1] = inscnt;
 
-       delta_cleanup(&bdf);
+       free(hash);
        *delta_size = outpos;
        return out;
 }
index 0c3f800..b1c05b3 100644 (file)
@@ -193,7 +193,7 @@ int main(int argc, const char **argv)
                        show_file('-', ce);
                        continue;
                }
-               changed = ce_match_stat(ce, &st);
+               changed = ce_match_stat(ce, &st, 0);
                if (!changed && !diff_options.find_copies_harder)
                        continue;
                oldmode = ntohl(ce->ce_mode);
index f8a102e..12a9418 100644 (file)
@@ -33,7 +33,7 @@ static int get_stat_data(struct cache_entry *ce,
                        }
                        return -1;
                }
-               changed = ce_match_stat(ce, &st);
+               changed = ce_match_stat(ce, &st, 0);
                if (changed) {
                        mode = create_ce_mode(st.st_mode);
                        if (!trust_executable_bit &&
diff --git a/diff.c b/diff.c
index 890bdaa..804c08c 100644 (file)
--- a/diff.c
+++ b/diff.c
@@ -311,7 +311,7 @@ static int work_tree_matches(const char *name, const unsigned char *sha1)
        ce = active_cache[pos];
        if ((lstat(name, &st) < 0) ||
            !S_ISREG(st.st_mode) || /* careful! */
-           ce_match_stat(ce, &st) ||
+           ce_match_stat(ce, &st, 0) ||
            memcmp(sha1, ce->sha1, 20))
                return 0;
        /* we return 1 only when we can stat, it is a regular file,
diff --git a/entry.c b/entry.c
index 6c47c3a..8fb99bc 100644 (file)
--- a/entry.c
+++ b/entry.c
@@ -123,7 +123,7 @@ int checkout_entry(struct cache_entry *ce, struct checkout *state)
        strcpy(path + len, ce->name);
 
        if (!lstat(path, &st)) {
-               unsigned changed = ce_match_stat(ce, &st);
+               unsigned changed = ce_match_stat(ce, &st, 1);
                if (!changed)
                        return 0;
                if (!state->force) {
index 0596fc6..251e53c 100644 (file)
@@ -12,6 +12,7 @@
 char git_default_email[MAX_GITNAME];
 char git_default_name[MAX_GITNAME];
 int trust_executable_bit = 1;
+int assume_unchanged = 0;
 int only_use_symrefs = 0;
 int repository_format_version = 0;
 char git_commit_encoding[MAX_ENCODING_LENGTH] = "utf-8";
index aa6f42a..09738fe 100644 (file)
@@ -8,7 +8,7 @@ static int keep_pack;
 static int quiet;
 static int verbose;
 static const char fetch_pack_usage[] =
-"git-fetch-pack [-q] [-v] [-k] [--exec=upload-pack] [host:]directory <refs>...";
+"git-fetch-pack [-q] [-v] [-k] [--thin] [--exec=upload-pack] [host:]directory <refs>...";
 static const char *exec = "git-upload-pack";
 
 #define COMPLETE       (1U << 0)
@@ -18,7 +18,7 @@ static const char *exec = "git-upload-pack";
 #define POPPED         (1U << 4)
 
 static struct commit_list *rev_list = NULL;
-static int non_common_revs = 0, multi_ack = 0;
+static int non_common_revs = 0, multi_ack = 0, use_thin_pack = 0;
 
 static void rev_list_push(struct commit *commit, int mark)
 {
@@ -156,8 +156,9 @@ static int find_common(int fd[2], unsigned char *result_sha1,
                        continue;
                }
 
-               packet_write(fd[1], "want %s%s\n", sha1_to_hex(remote),
-                       multi_ack ? " multi_ack" : "");
+               packet_write(fd[1], "want %s%s%s\n", sha1_to_hex(remote),
+                            (multi_ack ? " multi_ack" : ""),
+                            (use_thin_pack ? " thin-pack" : ""));
                fetching++;
        }
        packet_flush(fd[1]);
@@ -421,6 +422,10 @@ int main(int argc, char **argv)
                                keep_pack = 1;
                                continue;
                        }
+                       if (!strcmp("--thin", arg)) {
+                               use_thin_pack = 1;
+                               continue;
+                       }
                        if (!strcmp("-v", arg)) {
                                verbose = 1;
                                continue;
@@ -434,6 +439,8 @@ int main(int argc, char **argv)
        }
        if (!dest)
                usage(fetch_pack_usage);
+       if (keep_pack)
+               use_thin_pack = 0;
        pid = git_connect(fd, dest, exec);
        if (pid < 0)
                return 1;
index 13fad82..d6a4bc7 100755 (executable)
@@ -14,6 +14,10 @@ while : ; do
     -v)
        verbose=--verbose
        ;;
+    --)
+       shift
+       break
+       ;;
     -*)
        usage
        ;;
index 98b9215..85ecada 100755 (executable)
--- a/git-am.sh
+++ b/git-am.sh
@@ -1,11 +1,13 @@
 #!/bin/sh
 #
-#
+# Copyright (c) 2005, 2006 Junio C Hamano
 
 USAGE='[--signoff] [--dotest=<dir>] [--utf8] [--binary] [--3way] <mbox>
   or, when resuming [--skip | --resolved]'
 . git-sh-setup
 
+git var GIT_COMMITTER_IDENT >/dev/null || exit
+
 stop_here () {
     echo "$1" >"$dotest/next"
     exit 1
index 61c8c02..5569fdc 100755 (executable)
@@ -21,6 +21,8 @@
 USAGE='[-u] [-k] [-q] [-m] (-c .dotest/<num> | mbox) [signoff]'
 . git-sh-setup
 
+git var GIT_COMMITTER_IDENT >/dev/null || exit
+
 keep_subject= query_apply= continue= utf8= resume=t
 while case "$#" in 0) break ;; esac
 do
index d184ceb..dc0ad55 100755 (executable)
@@ -154,7 +154,7 @@ yes,yes)
            fi &&
            rm -f "$GIT_DIR/objects/sample" &&
            cd "$repo" &&
-           find objects -depth -print | cpio -puamd$l "$GIT_DIR/" || exit 1
+           find objects -depth -print | cpio -pumd$l "$GIT_DIR/" || exit 1
            ;;
        yes)
            mkdir -p "$GIT_DIR/objects/info"
index 24f9834..b46469a 100755 (executable)
@@ -846,8 +846,12 @@ while(<CVS>) {
                        print "Drop $fn\n" if $opt_v;
                } else {
                        print "".($init ? "New" : "Update")." $fn: $size bytes\n" if $opt_v;
-                       open my $F, '-|', "git-hash-object -w $tmpname"
+                       my $pid = open(my $F, '-|');
+                       die $! unless defined $pid;
+                       if (!$pid) {
+                           exec("git-hash-object", "-w", $tmpname)
                                or die "Cannot create object: $!\n";
+                       }
                        my $sha = <$F>;
                        chomp $sha;
                        close $F;
index b4325d9..23d965f 100755 (executable)
@@ -320,7 +320,7 @@ fetch_main () {
     ( : subshell because we muck with IFS
       IFS="    $LF"
       (
-         git-fetch-pack $exec $keep "$remote" $rref || echo failed "$remote"
+         git-fetch-pack $exec $keep --thin "$remote" $rref || echo failed "$remote"
       ) |
       while read sha1 remote_name
       do
index c34ddc5..c13af48 100755 (executable)
@@ -28,11 +28,12 @@ sub andjoin {
 }
 
 sub repoconfig {
-       my $fh;
        my $val;
        eval {
-               open $fh, '-|', 'git-repo-config', '--get', 'merge.summary'
-                   or die "$!";
+               my $pid = open(my $fh, '-|');
+               if (!$pid) {
+                       exec('git-repo-config', '--get', 'merge.summary');
+               }
                ($val) = <$fh>;
                close $fh;
        };
@@ -41,25 +42,32 @@ sub repoconfig {
 
 sub current_branch {
        my $fh;
-       open $fh, '-|', 'git-symbolic-ref', 'HEAD' or die "$!";
+       my $pid = open($fh, '-|');
+       die "$!" unless defined $pid;
+       if (!$pid) {
+           exec('git-symbolic-ref', 'HEAD') or die "$!";
+       }
        my ($bra) = <$fh>;
        chomp($bra);
+       close $fh or die "$!";
        $bra =~ s|^refs/heads/||;
        if ($bra ne 'master') {
                $bra = " into $bra";
        } else {
                $bra = "";
        }
-
        return $bra;
 }
 
 sub shortlog {
-       my ($tip, $limit) = @_;
+       my ($tip) = @_;
        my ($fh, @result);
-       open $fh, '-|', ('git-log', "--max-count=$limit", '--topo-order',
-                        '--pretty=oneline', $tip, '^HEAD')
-           or die "$!";
+       my $pid = open($fh, '-|');
+       die "$!" unless defined $pid;
+       if (!$pid) {
+           exec('git-log', '--topo-order',
+                '--pretty=oneline', $tip, '^HEAD') or die "$!";
+       }
        while (<$fh>) {
                s/^[0-9a-f]{40}\s+//;
                push @result, $_;
@@ -160,7 +168,7 @@ my $limit = 20;
 
 for (@origin) {
        my ($sha1, $name) = @$_;
-       my @log = shortlog($sha1, $limit + 1);
+       my @log = shortlog($sha1);
        if ($limit + 1 <= @log) {
                print "\n* $name: (" . scalar(@log) . " commits)\n";
        }
index e54c9e4..eb75de4 100755 (executable)
@@ -189,7 +189,7 @@ my @month_names = qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec);
 sub show_date {
     my ($time, $tz) = @_;
     my $minutes = abs($tz);
-    $minutes = ($minutes / 100) * 60 + ($minutes % 100);
+    $minutes = int($minutes / 100) * 60 + ($minutes % 100);
     if ($tz < 0) {
         $minutes = -$minutes;
     }
index 74f0761..4609fe5 100755 (executable)
@@ -13,6 +13,10 @@ LF='
 all_strategies='recursive octopus resolve stupid ours'
 default_strategies='recursive'
 use_strategies=
+if test "@@NO_PYTHON@@"; then
+       all_strategies='resolve octopus stupid ours'
+       default_strategies='resolve'
+fi
 
 dropsave() {
        rm -f -- "$GIT_DIR/MERGE_HEAD" "$GIT_DIR/MERGE_MSG" \
@@ -142,6 +146,8 @@ case "$#,$common,$no_commit" in
 1,*,)
        # We are not doing octopus, not fast forward, and have only
        # one common.  See if it is really trivial.
+       git var GIT_COMMITTER_IDENT >/dev/null || exit
+
        echo "Trying really trivial in-index merge..."
        git-update-index --refresh 2>/dev/null
        if git-read-tree --trivial -m -u $common $head "$1" &&
@@ -179,6 +185,9 @@ case "$#,$common,$no_commit" in
        ;;
 esac
 
+# We are going to make a new commit.
+git var GIT_COMMITTER_IDENT >/dev/null || exit
+
 case "$use_strategies" in
 '')
        case "$#" in
index 706db99..73dcf06 100755 (executable)
@@ -8,6 +8,7 @@ USAGE='[--all] [--tags] [--force] <repository> [<refspec>...]'
 has_all=
 has_force=
 has_exec=
+has_thin=
 remote=
 do_tags=
 
@@ -22,6 +23,8 @@ do
                has_force=--force ;;
        --exec=*)
                has_exec="$1" ;;
+       --thin)
+               has_thin="$1" ;;
        -*)
                 usage ;;
         *)
@@ -72,6 +75,7 @@ set x "$remote" "$@"; shift
 test "$has_all" && set x "$has_all" "$@" && shift
 test "$has_force" && set x "$has_force" "$@" && shift
 test "$has_exec" && set x "$has_exec" "$@" && shift
+test "$has_thin" && set x "$has_thin" "$@" && shift
 
 case "$remote" in
 http://* | https://*)
index 21c3d83..5956f06 100755 (executable)
@@ -4,24 +4,28 @@
 #
 
 USAGE='[--onto <newbase>] <upstream> [<branch>]'
-LONG_USAGE='If <branch> is specified, switch to that branch first.  Then,
-extract commits in the current branch that are not in <upstream>,
-and reconstruct the current on top of <upstream>, discarding the original
-development history.  If --onto <newbase> is specified, the history is
-reconstructed on top of <newbase>, instead of <upstream>.  For example,
-while on "topic" branch:
+LONG_USAGE='git-rebase applies to <upstream> (or optionally to <newbase>) commits
+from <branch> that do not appear in <upstream>. When <branch> is not
+specified it defaults to the current branch (HEAD).
+
+When git-rebase is complete, <branch> will be updated to point to the
+newly created line of commit objects, so the previous line will not be
+accessible unless there are other references to it already.
+
+Assuming the following history:
 
           A---B---C topic
          /
     D---E---F---G master
 
-       $ '"$0"' --onto master~1 master topic
+The result of the following command:
 
-would rewrite the history to look like this:
+    git-rebase --onto master~1 master topic
 
+  would be:
 
-             A'\''--B'\''--C'\'' topic
-            /
+              A'\''--B'\''--C'\'' topic
+             /
     D---E---F---G master
 '
 
@@ -71,7 +75,7 @@ esac
 # The upstream head must be given.  Make sure it is valid.
 upstream_name="$1"
 upstream=`git rev-parse --verify "${upstream_name}^0"` ||
-    die "invalid upsteram $upstream_name"
+    die "invalid upstream $upstream_name"
 
 # If a hook exists, give it a chance to interrupt
 if test -x "$GIT_DIR/hooks/pre-rebase"
index 1fafb6e..3d6fec1 100755 (executable)
@@ -3,17 +3,20 @@
 # Copyright (c) 2005 Linus Torvalds
 #
 
-USAGE='[-a] [-d] [-l] [-n]'
+USAGE='[-a] [-d] [-f] [-l] [-n] [-q]'
 . git-sh-setup
        
-no_update_info= all_into_one= remove_redundant= local=
+no_update_info= all_into_one= remove_redundant=
+local= quiet= no_reuse_delta=
 while case "$#" in 0) break ;; esac
 do
        case "$1" in
        -n)     no_update_info=t ;;
        -a)     all_into_one=t ;;
        -d)     remove_redundant=t ;;
-       -l)     local=t ;;
+       -q)     quiet=-q ;;
+       -f)     no_reuse_delta=--no-reuse-delta ;;
+       -l)     local=--local ;;
        *)      usage ;;
        esac
        shift
@@ -39,9 +42,7 @@ case ",$all_into_one," in
            find . -type f \( -name '*.pack' -o -name '*.idx' \) -print`
        ;;
 esac
-if [ "$local" ]; then
-       pack_objects="$pack_objects --local"
-fi
+pack_objects="$pack_objects $local $quiet $no_reuse_delta"
 name=$(git-rev-list --objects $rev_list $(git-rev-parse $rev_parse) 2>&1 |
        git-pack-objects --non-empty $pack_objects .tmp-pack) ||
        exit 1
index df11951..d3664ff 100755 (executable)
@@ -131,7 +131,11 @@ sub record_preimage {
 sub find_conflict {
        my $in;
        local $/ = "\0";
-       open $in, '-|', qw(git ls-files -z -u) or die "$!: ls-files";
+       my $pid = open($in, '-|');
+       die "$!" unless defined $pid;
+       if (!$pid) {
+               exec(qw(git ls-files -z -u)) or die "$!: ls-files";
+       }
        my %path = ();
        my @path = ();
        while (<$in>) {
index 9263070..b53ede8 100755 (executable)
@@ -50,6 +50,9 @@ case "$common" in
        ;;
 esac
 
+# We are going to make a new commit.
+git var GIT_COMMITTER_IDENT >/dev/null || exit
+
 # Find an optimum merge base if there are more than one candidates.
 LF='
 '
index 2c58706..c19d3a6 100755 (executable)
@@ -141,8 +141,9 @@ git-read-tree -m -u $base $head $next &&
 result=$(git-write-tree 2>/dev/null) || {
     echo >&2 "Simple $me fails; trying Automatic $me."
     git-merge-index -o git-merge-one-file -a || {
-           echo >&2 "Automatic $me failed.  After fixing it up,"
-           echo >&2 "you can use \"git commit -F .msg\""
+           echo >&2 "Automatic $me failed.  After resolving the conflicts,"
+           echo >&2 "mark the corrected paths with 'git-update-index <paths>'"
+           echo >&2 "and commit with 'git commit -F .msg'"
            case "$me" in
            cherry-pick)
                echo >&2 "You may choose to use the following when making"
index 13b85dd..b0d095b 100755 (executable)
@@ -59,24 +59,29 @@ my $rc = GetOptions("from=s" => \$from,
 
 # Now, let's fill any that aren't set in with defaults:
 
-open(GITVAR,"-|","git-var","-l")
-       or die "Failed to open pipe from git-var: $!";
-
-my ($author,$committer);
-while(<GITVAR>) {
-       chomp;
-       my ($var,$data) = split /=/,$_,2;
-       my @fields = split /\s+/, $data;
-
-       my $ident = join(" ", @fields[0...(@fields-3)]);
+sub gitvar {
+    my ($var) = @_;
+    my $fh;
+    my $pid = open($fh, '-|');
+    die "$!" unless defined $pid;
+    if (!$pid) {
+       exec('git-var', $var) or die "$!";
+    }
+    my ($val) = <$fh>;
+    close $fh or die "$!";
+    chomp($val);
+    return $val;
+}
 
-       if ($var eq 'GIT_AUTHOR_IDENT') {
-               $author = $ident;
-       } elsif ($var eq 'GIT_COMMITTER_IDENT') {
-               $committer = $ident;
-       }
+sub gitvar_ident {
+    my ($name) = @_;
+    my $val = gitvar($name);
+    my @field = split(/\s+/, $val);
+    return join(' ', @field[0...(@field-3)]);
 }
-close(GITVAR);
+
+my ($author) = gitvar_ident('GIT_AUTHOR_IDENT');
+my ($committer) = gitvar_ident('GIT_COMMITTER_IDENT');
 
 my $prompting = 0;
 if (!defined $from) {
index c536d70..ee2940f 100755 (executable)
@@ -10,7 +10,6 @@
 # The head revision is on branch "origin" by default.
 # You can change that with the '-o' option.
 
-require 5.008; # for shell-safe open("-|",LIST)
 use strict;
 use warnings;
 use Getopt::Std;
@@ -322,8 +321,12 @@ sub get_file($$$) {
                return undef unless defined $name;
        }
 
-       open my $F, '-|', "git-hash-object", "-w", $name
+       my $pid = open(my $F, '-|');
+       die $! unless defined $pid;
+       if (!$pid) {
+           exec("git-hash-object", "-w", $name)
                or die "Cannot create object: $!\n";
+       }
        my $sha = <$F>;
        chomp $sha;
        close $F;
@@ -398,7 +401,12 @@ sub copy_path($$$$$$$$) {
                        $srcpath =~ s#/*$#/#;
        }
        
-       open my $f,"-|","git-ls-tree","-r","-z",$gitrev,$srcpath;
+       my $pid = open my $f,'-|';
+       die $! unless defined $pid;
+       if (!$pid) {
+               exec("git-ls-tree","-r","-z",$gitrev,$srcpath)
+                       or die $!;
+       }
        local $/ = "\0";
        while(<$f>) {
                chomp;
@@ -554,7 +562,11 @@ sub commit {
                                @o1 = @old;
                                @old = ();
                        }
-                       open my $F, "-|", "git-ls-files", "-z", @o1 or die $!;
+                       my $pid = open my $F, "-|";
+                       die "$!" unless defined $pid;
+                       if (!$pid) {
+                               exec("git-ls-files", "-z", @o1) or die $!;
+                       }
                        @o1 = ();
                        local $/ = "\0";
                        while(<$F>) {
diff --git a/gitk b/gitk
index e482140..f4c6624 100755 (executable)
--- a/gitk
+++ b/gitk
@@ -1936,7 +1936,7 @@ proc findfiles {} {
     global selectedline numcommits lineid ctext
     global ffileline finddidsel parents nparents
     global findinprogress findstartline findinsertpos
-    global treediffs fdiffids fdiffsneeded fdiffpos
+    global treediffs fdiffid fdiffsneeded fdiffpos
     global findmergefiles
 
     if {$numcommits == 0} return
@@ -1953,11 +1953,9 @@ proc findfiles {} {
     while 1 {
        set id $lineid($l)
        if {$findmergefiles || $nparents($id) == 1} {
-           foreach p $parents($id) {
-               if {![info exists treediffs([list $id $p])]} {
-                   append diffsneeded "$id $p\n"
-                   lappend fdiffsneeded [list $id $p]
-               }
+           if {![info exists treediffs($id)]} {
+               append diffsneeded "$id\n"
+               lappend fdiffsneeded $id
            }
        }
        if {[incr l] >= $numcommits} {
@@ -1974,7 +1972,7 @@ proc findfiles {} {
            error_popup "Error starting search process: $err"
            return
        }
-       catch {unset fdiffids}
+       catch {unset fdiffid}
        set fdiffpos 0
        fconfigure $df -blocking 0
        fileevent $df readable [list readfilediffs $df]
@@ -1983,16 +1981,15 @@ proc findfiles {} {
     set finddidsel 0
     set findinsertpos end
     set id $lineid($l)
-    set p [lindex $parents($id) 0]
     . config -cursor watch
     settextcursor watch
     set findinprogress 1
-    findcont [list $id $p]
+    findcont $id
     update
 }
 
 proc readfilediffs {df} {
-    global findids fdiffids fdiffs
+    global findid fdiffid fdiffs
 
     set n [gets $df line]
     if {$n < 0} {
@@ -2002,19 +1999,19 @@ proc readfilediffs {df} {
                stopfindproc
                bell
                error_popup "Error in git-diff-tree: $err"
-           } elseif {[info exists findids]} {
-               set ids $findids
+           } elseif {[info exists findid]} {
+               set id $findid
                stopfindproc
                bell
-               error_popup "Couldn't find diffs for {$ids}"
+               error_popup "Couldn't find diffs for $id"
            }
        }
        return
     }
-    if {[regexp {^([0-9a-f]{40}) \(from ([0-9a-f]{40})\)} $line match id p]} {
+    if {[regexp {^([0-9a-f]{40})$} $line match id]} {
        # start of a new string of diffs
        donefilediff
-       set fdiffids [list $id $p]
+       set fdiffid $id
        set fdiffs {}
     } elseif {[string match ":*" $line]} {
        lappend fdiffs [lindex $line 5]
@@ -2022,53 +2019,50 @@ proc readfilediffs {df} {
 }
 
 proc donefilediff {} {
-    global fdiffids fdiffs treediffs findids
+    global fdiffid fdiffs treediffs findid
     global fdiffsneeded fdiffpos
 
-    if {[info exists fdiffids]} {
-       while {[lindex $fdiffsneeded $fdiffpos] ne $fdiffids
+    if {[info exists fdiffid]} {
+       while {[lindex $fdiffsneeded $fdiffpos] ne $fdiffid
               && $fdiffpos < [llength $fdiffsneeded]} {
            # git-diff-tree doesn't output anything for a commit
            # which doesn't change anything
-           set nullids [lindex $fdiffsneeded $fdiffpos]
-           set treediffs($nullids) {}
-           if {[info exists findids] && $nullids eq $findids} {
-               unset findids
-               findcont $nullids
+           set nullid [lindex $fdiffsneeded $fdiffpos]
+           set treediffs($nullid) {}
+           if {[info exists findid] && $nullid eq $findid} {
+               unset findid
+               findcont $nullid
            }
            incr fdiffpos
        }
        incr fdiffpos
 
-       if {![info exists treediffs($fdiffids)]} {
-           set treediffs($fdiffids) $fdiffs
+       if {![info exists treediffs($fdiffid)]} {
+           set treediffs($fdiffid) $fdiffs
        }
-       if {[info exists findids] && $fdiffids eq $findids} {
-           unset findids
-           findcont $fdiffids
+       if {[info exists findid] && $fdiffid eq $findid} {
+           unset findid
+           findcont $fdiffid
        }
     }
 }
 
-proc findcont {ids} {
-    global findids treediffs parents nparents
+proc findcont {id} {
+    global findid treediffs parents nparents
     global ffileline findstartline finddidsel
     global lineid numcommits matchinglines findinprogress
     global findmergefiles
 
-    set id [lindex $ids 0]
-    set p [lindex $ids 1]
-    set pi [lsearch -exact $parents($id) $p]
     set l $ffileline
     while 1 {
        if {$findmergefiles || $nparents($id) == 1} {
-           if {![info exists treediffs($ids)]} {
-               set findids $ids
+           if {![info exists treediffs($id)]} {
+               set findid $id
                set ffileline $l
                return
            }
            set doesmatch 0
-           foreach f $treediffs($ids) {
+           foreach f $treediffs($id) {
                set x [findmatches $f]
                if {$x != {}} {
                    set doesmatch 1
@@ -2077,21 +2071,13 @@ proc findcont {ids} {
            }
            if {$doesmatch} {
                insertmatch $l $id
-               set pi $nparents($id)
            }
-       } else {
-           set pi $nparents($id)
        }
-       if {[incr pi] >= $nparents($id)} {
-           set pi 0
-           if {[incr l] >= $numcommits} {
-               set l 0
-           }
-           if {$l == $findstartline} break
-           set id $lineid($l)
+       if {[incr l] >= $numcommits} {
+           set l 0
        }
-       set p [lindex $parents($id) $pi]
-       set ids [list $id $p]
+       if {$l == $findstartline} break
+       set id $lineid($l)
     }
     stopfindproc
     if {!$finddidsel} {
diff --git a/ident.c b/ident.c
index 23b8cfc..7c81fe8 100644 (file)
--- a/ident.c
+++ b/ident.c
@@ -156,8 +156,18 @@ static int copy(char *buf, int size, int offset, const char *src)
        return offset;
 }
 
+static const char au_env[] = "GIT_AUTHOR_NAME";
+static const char co_env[] = "GIT_COMMITTER_NAME";
+static const char *env_hint =
+"\n*** Environment problem:\n"
+"*** Your name cannot be determined from your system services (gecos).\n"
+"*** You would need to set %s and %s\n"
+"*** environment variables; otherwise you won't be able to perform\n"
+"*** certain operations because of \"empty ident\" errors.\n"
+"*** Alternatively, you can use user.name configuration variable.\n\n";
+
 static const char *get_ident(const char *name, const char *email,
-                            const char *date_str)
+                            const char *date_str, int error_on_no_name)
 {
        static char buffer[1000];
        char date[50];
@@ -168,9 +178,14 @@ static const char *get_ident(const char *name, const char *email,
        if (!email)
                email = git_default_email;
 
-       if (!*name || !*email)
-               die("empty ident %s <%s> not allowed",
-                   name, email);
+       if (!*name) {
+               if (name == git_default_name && env_hint) {
+                       fprintf(stderr, env_hint, au_env, co_env);
+                       env_hint = NULL; /* warn only once, for "git-var -l" */
+               }
+               if (error_on_no_name)
+                       die("empty ident %s <%s> not allowed", name, email);
+       }
 
        strcpy(date, git_default_date);
        if (date_str)
@@ -187,16 +202,18 @@ static const char *get_ident(const char *name, const char *email,
        return buffer;
 }
 
-const char *git_author_info(void)
+const char *git_author_info(int error_on_no_name)
 {
        return get_ident(getenv("GIT_AUTHOR_NAME"),
                         getenv("GIT_AUTHOR_EMAIL"),
-                        getenv("GIT_AUTHOR_DATE"));
+                        getenv("GIT_AUTHOR_DATE"),
+                        error_on_no_name);
 }
 
-const char *git_committer_info(void)
+const char *git_committer_info(int error_on_no_name)
 {
        return get_ident(getenv("GIT_COMMITTER_NAME"),
                         getenv("GIT_COMMITTER_EMAIL"),
-                        getenv("GIT_COMMITTER_DATE"));
+                        getenv("GIT_COMMITTER_DATE"),
+                        error_on_no_name);
 }
index df93cf2..90b289f 100644 (file)
@@ -20,6 +20,7 @@ static int show_unmerged = 0;
 static int show_modified = 0;
 static int show_killed = 0;
 static int show_other_directories = 0;
+static int show_valid_bit = 0;
 static int line_terminator = '\n';
 
 static int prefix_len = 0, prefix_offset = 0;
@@ -457,6 +458,23 @@ static void show_ce_entry(const char *tag, struct cache_entry *ce)
        if (pathspec && !match(pathspec, ps_matched, ce->name, len))
                return;
 
+       if (tag && *tag && show_valid_bit &&
+           (ce->ce_flags & htons(CE_VALID))) {
+               static char alttag[4];
+               memcpy(alttag, tag, 3);
+               if (isalpha(tag[0]))
+                       alttag[0] = tolower(tag[0]);
+               else if (tag[0] == '?')
+                       alttag[0] = '!';
+               else {
+                       alttag[0] = 'v';
+                       alttag[1] = tag[0];
+                       alttag[2] = ' ';
+                       alttag[3] = 0;
+               }
+               tag = alttag;
+       }
+
        if (!show_stage) {
                fputs(tag, stdout);
                write_name_quoted("", 0, ce->name + offset,
@@ -533,7 +551,7 @@ static void show_files(void)
                        err = lstat(ce->name, &st);
                        if (show_deleted && err)
                                show_ce_entry(tag_removed, ce);
-                       if (show_modified && ce_modified(ce, &st))
+                       if (show_modified && ce_modified(ce, &st, 0))
                                show_ce_entry(tag_modified, ce);
                }
        }
@@ -606,7 +624,7 @@ static void verify_pathspec(void)
 }
 
 static const char ls_files_usage[] =
-       "git-ls-files [-z] [-t] (--[cached|deleted|others|stage|unmerged|killed|modified])* "
+       "git-ls-files [-z] [-t] [-v] (--[cached|deleted|others|stage|unmerged|killed|modified])* "
        "[ --ignored ] [--exclude=<pattern>] [--exclude-from=<file>] "
        "[ --exclude-per-directory=<filename> ] [--full-name] [--] [<file>]*";
 
@@ -631,13 +649,15 @@ int main(int argc, const char **argv)
                        line_terminator = 0;
                        continue;
                }
-               if (!strcmp(arg, "-t")) {
+               if (!strcmp(arg, "-t") || !strcmp(arg, "-v")) {
                        tag_cached = "H ";
                        tag_unmerged = "M ";
                        tag_removed = "R ";
                        tag_modified = "C ";
                        tag_other = "? ";
                        tag_killed = "K ";
+                       if (arg[1] == 'v')
+                               show_valid_bit = 1;
                        continue;
                }
                if (!strcmp(arg, "-c") || !strcmp(arg, "--cached")) {
@@ -758,6 +778,7 @@ int main(int argc, const char **argv)
                                continue;
                        error("pathspec '%s' did not match any.",
                              pathspec[num] + prefix_offset);
+                       errors++;
                }
                return errors ? 1 : 0;
        }
diff --git a/merge-tree.c b/merge-tree.c
new file mode 100644 (file)
index 0000000..768d83a
--- /dev/null
@@ -0,0 +1,272 @@
+#include "cache.h"
+#include "diff.h"
+
+static const char merge_tree_usage[] = "git-merge-tree <base-tree> <branch1> <branch2>";
+static int resolve_directories = 1;
+
+static void merge_trees(struct tree_desc t[3], const char *base);
+
+static void *fill_tree_descriptor(struct tree_desc *desc, const unsigned char *sha1)
+{
+       unsigned long size = 0;
+       void *buf = NULL;
+
+       if (sha1) {
+               buf = read_object_with_reference(sha1, "tree", &size, NULL);
+               if (!buf)
+                       die("unable to read tree %s", sha1_to_hex(sha1));
+       }
+       desc->size = size;
+       desc->buf = buf;
+       return buf;
+}
+
+struct name_entry {
+       const unsigned char *sha1;
+       const char *path;
+       unsigned int mode;
+       int pathlen;
+};
+
+static void entry_clear(struct name_entry *a)
+{
+       memset(a, 0, sizeof(*a));
+}
+
+static int entry_compare(struct name_entry *a, struct name_entry *b)
+{
+       return base_name_compare(
+                       a->path, a->pathlen, a->mode,
+                       b->path, b->pathlen, b->mode);
+}
+
+static void entry_extract(struct tree_desc *t, struct name_entry *a)
+{
+       a->sha1 = tree_entry_extract(t, &a->path, &a->mode);
+       a->pathlen = strlen(a->path);
+}
+
+/* An empty entry never compares same, not even to another empty entry */
+static int same_entry(struct name_entry *a, struct name_entry *b)
+{
+       return  a->sha1 &&
+               b->sha1 &&
+               !memcmp(a->sha1, b->sha1, 20) &&
+               a->mode == b->mode;
+}
+
+static const char *sha1_to_hex_zero(const unsigned char *sha1)
+{
+       if (sha1)
+               return sha1_to_hex(sha1);
+       return "0000000000000000000000000000000000000000";
+}
+
+static void resolve(const char *base, struct name_entry *branch1, struct name_entry *result)
+{
+       char branch1_sha1[50];
+
+       /* If it's already branch1, don't bother showing it */
+       if (!branch1)
+               return;
+       memcpy(branch1_sha1, sha1_to_hex_zero(branch1->sha1), 41);
+
+       printf("0 %06o->%06o %s->%s %s%s\n",
+               branch1->mode, result->mode,
+               branch1_sha1, sha1_to_hex_zero(result->sha1),
+               base, result->path);
+}
+
+static int unresolved_directory(const char *base, struct name_entry n[3])
+{
+       int baselen;
+       char *newbase;
+       struct name_entry *p;
+       struct tree_desc t[3];
+       void *buf0, *buf1, *buf2;
+
+       if (!resolve_directories)
+               return 0;
+       p = n;
+       if (!p->mode) {
+               p++;
+               if (!p->mode)
+                       p++;
+       }
+       if (!S_ISDIR(p->mode))
+               return 0;
+       baselen = strlen(base);
+       newbase = xmalloc(baselen + p->pathlen + 2);
+       memcpy(newbase, base, baselen);
+       memcpy(newbase + baselen, p->path, p->pathlen);
+       memcpy(newbase + baselen + p->pathlen, "/", 2);
+
+       buf0 = fill_tree_descriptor(t+0, n[0].sha1);
+       buf1 = fill_tree_descriptor(t+1, n[1].sha1);
+       buf2 = fill_tree_descriptor(t+2, n[2].sha1);
+       merge_trees(t, newbase);
+
+       free(buf0);
+       free(buf1);
+       free(buf2);
+       free(newbase);
+       return 1;
+}
+
+static void unresolved(const char *base, struct name_entry n[3])
+{
+       if (unresolved_directory(base, n))
+               return;
+       if (n[0].sha1)
+               printf("1 %06o %s %s%s\n", n[0].mode, sha1_to_hex(n[0].sha1), base, n[0].path);
+       if (n[1].sha1)
+               printf("2 %06o %s %s%s\n", n[1].mode, sha1_to_hex(n[1].sha1), base, n[1].path);
+       if (n[2].sha1)
+               printf("3 %06o %s %s%s\n", n[2].mode, sha1_to_hex(n[2].sha1), base, n[2].path);
+}
+
+typedef void (*traverse_callback_t)(int n, unsigned long mask, struct name_entry *entry, const char *base);
+
+static void traverse_trees(int n, struct tree_desc *t, const char *base, traverse_callback_t callback)
+{
+       struct name_entry *entry = xmalloc(n*sizeof(*entry));
+
+       for (;;) {
+               struct name_entry entry[3];
+               unsigned long mask = 0;
+               int i, last;
+
+               last = -1;
+               for (i = 0; i < n; i++) {
+                       if (!t[i].size)
+                               continue;
+                       entry_extract(t+i, entry+i);
+                       if (last >= 0) {
+                               int cmp = entry_compare(entry+i, entry+last);
+
+                               /*
+                                * Is the new name bigger than the old one?
+                                * Ignore it
+                                */
+                               if (cmp > 0)
+                                       continue;
+                               /*
+                                * Is the new name smaller than the old one?
+                                * Ignore all old ones
+                                */
+                               if (cmp < 0)
+                                       mask = 0;
+                       }
+                       mask |= 1ul << i;
+                       last = i;
+               }
+               if (!mask)
+                       break;
+
+               /*
+                * Update the tree entries we've walked, and clear
+                * all the unused name-entries.
+                */
+               for (i = 0; i < n; i++) {
+                       if (mask & (1ul << i)) {
+                               update_tree_entry(t+i);
+                               continue;
+                       }
+                       entry_clear(entry + i);
+               }
+               callback(n, mask, entry, base);
+       }
+       free(entry);
+}
+
+/*
+ * Merge two trees together (t[1] and t[2]), using a common base (t[0])
+ * as the origin.
+ *
+ * This walks the (sorted) trees in lock-step, checking every possible
+ * name. Note that directories automatically sort differently from other
+ * files (see "base_name_compare"), so you'll never see file/directory
+ * conflicts, because they won't ever compare the same.
+ *
+ * IOW, if a directory changes to a filename, it will automatically be
+ * seen as the directory going away, and the filename being created.
+ *
+ * Think of this as a three-way diff.
+ *
+ * The output will be either:
+ *  - successful merge
+ *      "0 mode sha1 filename"
+ *    NOTE NOTE NOTE! FIXME! We really really need to walk the index
+ *    in parallel with this too!
+ *
+ *  - conflict:
+ *     "1 mode sha1 filename"
+ *     "2 mode sha1 filename"
+ *     "3 mode sha1 filename"
+ *    where not all of the 1/2/3 lines may exist, of course.
+ *
+ * The successful merge rules are the same as for the three-way merge
+ * in git-read-tree.
+ */
+static void threeway_callback(int n, unsigned long mask, struct name_entry *entry, const char *base)
+{
+       /* Same in both? */
+       if (same_entry(entry+1, entry+2)) {
+               if (entry[0].sha1) {
+                       resolve(base, NULL, entry+1);
+                       return;
+               }
+       }
+
+       if (same_entry(entry+0, entry+1)) {
+               if (entry[2].sha1 && !S_ISDIR(entry[2].mode)) {
+                       resolve(base, entry+1, entry+2);
+                       return;
+               }
+       }
+
+       if (same_entry(entry+0, entry+2)) {
+               if (entry[1].sha1 && !S_ISDIR(entry[1].mode)) {
+                       resolve(base, NULL, entry+1);
+                       return;
+               }
+       }
+
+       unresolved(base, entry);
+}
+
+static void merge_trees(struct tree_desc t[3], const char *base)
+{
+       traverse_trees(3, t, base, threeway_callback);
+}
+
+static void *get_tree_descriptor(struct tree_desc *desc, const char *rev)
+{
+       unsigned char sha1[20];
+       void *buf;
+
+       if (get_sha1(rev, sha1) < 0)
+               die("unknown rev %s", rev);
+       buf = fill_tree_descriptor(desc, sha1);
+       if (!buf)
+               die("%s is not a tree", rev);
+       return buf;
+}
+
+int main(int argc, char **argv)
+{
+       struct tree_desc t[3];
+       void *buf1, *buf2, *buf3;
+
+       if (argc < 4)
+               usage(merge_tree_usage);
+
+       buf1 = get_tree_descriptor(t+0, argv[1]);
+       buf2 = get_tree_descriptor(t+1, argv[2]);
+       buf3 = get_tree_descriptor(t+2, argv[3]);
+       merge_trees(t, "");
+       free(buf1);
+       free(buf2);
+       free(buf3);
+       return 0;
+}
diff --git a/mktree.c b/mktree.c
new file mode 100644 (file)
index 0000000..f853585
--- /dev/null
+++ b/mktree.c
@@ -0,0 +1,137 @@
+/*
+ * GIT - the stupid content tracker
+ *
+ * Copyright (c) Junio C Hamano, 2006
+ */
+#include "cache.h"
+#include "strbuf.h"
+#include "quote.h"
+
+static struct treeent {
+       unsigned mode;
+       unsigned char sha1[20];
+       int len;
+       char name[FLEX_ARRAY];
+} **entries;
+static int alloc, used;
+
+static void append_to_tree(unsigned mode, unsigned char *sha1, char *path)
+{
+       struct treeent *ent;
+       int len = strlen(path);
+       if (strchr(path, '/'))
+               die("path %s contains slash", path);
+
+       if (alloc <= used) {
+               alloc = alloc_nr(used);
+               entries = xrealloc(entries, sizeof(*entries) * alloc);
+       }
+       ent = entries[used++] = xmalloc(sizeof(**entries) + len + 1);
+       ent->mode = mode;
+       ent->len = len;
+       memcpy(ent->sha1, sha1, 20);
+       memcpy(ent->name, path, len+1);
+}
+
+static int ent_compare(const void *a_, const void *b_)
+{
+       struct treeent *a = *(struct treeent **)a_;
+       struct treeent *b = *(struct treeent **)b_;
+       return base_name_compare(a->name, a->len, a->mode,
+                                b->name, b->len, b->mode);
+}
+
+static void write_tree(unsigned char *sha1)
+{
+       char *buffer;
+       unsigned long size, offset;
+       int i;
+
+       qsort(entries, used, sizeof(*entries), ent_compare);
+       size = 100;
+       for (size = i = 0; i < used; i++)
+               size += 32 + entries[i]->len;
+       buffer = xmalloc(size);
+       offset = 0;
+
+       for (i = 0; i < used; i++) {
+               struct treeent *ent = entries[i];
+
+               if (offset + ent->len + 100 < size) {
+                       size = alloc_nr(offset + ent->len + 100);
+                       buffer = xrealloc(buffer, size);
+               }
+               offset += sprintf(buffer + offset, "%o ", ent->mode);
+               offset += sprintf(buffer + offset, "%s", ent->name);
+               buffer[offset++] = 0;
+               memcpy(buffer + offset, ent->sha1, 20);
+               offset += 20;
+       }
+       write_sha1_file(buffer, offset, "tree", sha1);
+}
+
+static const char mktree_usage[] = "mktree [-z]";
+
+int main(int ac, char **av)
+{
+       struct strbuf sb;
+       unsigned char sha1[20];
+       int line_termination = '\n';
+
+       setup_git_directory();
+
+       while ((1 < ac) && av[1][0] == '-') {
+               char *arg = av[1];
+               if (!strcmp("-z", arg))
+                       line_termination = 0;
+               else
+                       usage(mktree_usage);
+               ac--;
+               av++;
+       }
+
+       strbuf_init(&sb);
+       while (1) {
+               int len;
+               char *ptr, *ntr;
+               unsigned mode;
+               char type[20];
+               char *path;
+
+               read_line(&sb, stdin, line_termination);
+               if (sb.eof)
+                       break;
+               len = sb.len;
+               ptr = sb.buf;
+               /* Input is non-recursive ls-tree output format
+                * mode SP type SP sha1 TAB name
+                */
+               mode = strtoul(ptr, &ntr, 8);
+               if (ptr == ntr || !ntr || *ntr != ' ')
+                       die("input format error: %s", sb.buf);
+               ptr = ntr + 1; /* type */
+               ntr = strchr(ptr, ' ');
+               if (!ntr || sb.buf + len <= ntr + 41 ||
+                   ntr[41] != '\t' ||
+                   get_sha1_hex(ntr + 1, sha1))
+                       die("input format error: %s", sb.buf);
+               if (sha1_object_info(sha1, type, NULL))
+                       die("object %s unavailable", sha1_to_hex(sha1));
+               *ntr++ = 0; /* now at the beginning of SHA1 */
+               if (strcmp(ptr, type))
+                       die("object type %s mismatch (%s)", ptr, type);
+               ntr += 41; /* at the beginning of name */
+               if (line_termination && ntr[0] == '"')
+                       path = unquote_c_style(ntr, NULL);
+               else
+                       path = ntr;
+
+               append_to_tree(mode, sha1, path);
+
+               if (path != ntr)
+                       free(path);
+       }
+       write_tree(sha1);
+       puts(sha1_to_hex(sha1));
+       exit(0);
+}
index c5a5e61..4f8814d 100644 (file)
 #include "delta.h"
 #include "pack.h"
 #include "csum-file.h"
+#include "diff.h"
 #include <sys/time.h>
 
-static const char pack_usage[] = "git-pack-objects [-q] [--non-empty] [--local] [--incremental] [--window=N] [--depth=N] {--stdout | base-name} < object-list";
+static const char pack_usage[] = "git-pack-objects [-q] [--no-reuse-delta] [--non-empty] [--local] [--incremental] [--window=N] [--depth=N] {--stdout | base-name} < object-list";
 
 struct object_entry {
        unsigned char sha1[20];
-       unsigned long size;
-       unsigned long offset;
-       unsigned int depth;
-       unsigned int hash;
+       unsigned long size;     /* uncompressed size */
+       unsigned long 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;
-       unsigned long delta_size;
-       struct object_entry *delta;
+       enum object_type in_pack_type;  /* could be delta */
+       unsigned long delta_size;       /* delta data size (uncompressed) */
+       struct object_entry *delta;     /* delta base object */
+       struct packed_git *in_pack;     /* already in pack */
+       unsigned int in_pack_offset;
+       struct object_entry *delta_child; /* delitified 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.
+                                */
+       int based_on_preferred; /* current delta candidate is a preferred
+                                * one, or delta against a preferred one.
+                                */
 };
 
+/*
+ * Objects we are going to pack are colected in objects array (dynamically
+ * 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 unsigned char object_list_sha1[20];
 static int non_empty = 0;
+static int no_reuse_delta = 0;
 static int local = 0;
 static int incremental = 0;
 static struct object_entry **sorted_by_sha, **sorted_by_type;
 static struct object_entry *objects = NULL;
-static int nr_objects = 0, nr_alloc = 0;
+static int nr_objects = 0, nr_alloc = 0, nr_result = 0;
 static const char *base_name;
 static unsigned char pack_file_sha1[20];
 static int progress = 1;
 
+/*
+ * 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.
+ * This hashtable is built after all the objects are seen.
+ */
+static int *object_ix = NULL;
+static int object_ix_hashsz = 0;
+
+/*
+ * Pack index for existing packs give us easy access to the offsets into
+ * corresponding pack file where each object's data starts, but the entries
+ * do not store the size of the compressed representation (uncompressed
+ * size is easily available by examining the pack entry header).  We build
+ * a hashtable of existing packs (pack_revindex), and keep reverse index
+ * here -- pack index file is sorted by object name mapping to offset; this
+ * pack_revindex[].revindex array is an ordered list of offsets, so if you
+ * know the offset of an object, next offset is where its packed
+ * representation ends.
+ */
+struct pack_revindex {
+       struct packed_git *p;
+       unsigned long *revindex;
+} *pack_revindex = NULL;
+static int pack_revindex_hashsz = 0;
+
+/*
+ * stats
+ */
+static int written = 0;
+static int written_delta = 0;
+static int reused = 0;
+static int reused_delta = 0;
+
+static int pack_revindex_ix(struct packed_git *p)
+{
+       unsigned int ui = (unsigned int) p;
+       int i;
+
+       ui = ui ^ (ui >> 16); /* defeat structure alignment */
+       i = (int)(ui % pack_revindex_hashsz);
+       while (pack_revindex[i].p) {
+               if (pack_revindex[i].p == p)
+                       return i;
+               if (++i == pack_revindex_hashsz)
+                       i = 0;
+       }
+       return -1 - i;
+}
+
+static void prepare_pack_ix(void)
+{
+       int num;
+       struct packed_git *p;
+       for (num = 0, p = packed_git; p; p = p->next)
+               num++;
+       if (!num)
+               return;
+       pack_revindex_hashsz = num * 11;
+       pack_revindex = xcalloc(sizeof(*pack_revindex), pack_revindex_hashsz);
+       for (p = packed_git; p; p = p->next) {
+               num = pack_revindex_ix(p);
+               num = - 1 - num;
+               pack_revindex[num].p = p;
+       }
+       /* revindex elements are lazily initialized */
+}
+
+static int cmp_offset(const void *a_, const void *b_)
+{
+       unsigned long a = *(unsigned long *) a_;
+       unsigned long b = *(unsigned long *) b_;
+       if (a < b)
+               return -1;
+       else if (a == b)
+               return 0;
+       else
+               return 1;
+}
+
+/*
+ * Ordered list of offsets of objects in the pack.
+ */
+static void prepare_pack_revindex(struct pack_revindex *rix)
+{
+       struct packed_git *p = rix->p;
+       int num_ent = num_packed_objects(p);
+       int i;
+       void *index = p->index_base + 256;
+
+       rix->revindex = xmalloc(sizeof(unsigned long) * (num_ent + 1));
+       for (i = 0; i < num_ent; i++) {
+               long hl = *((long *)(index + 24 * i));
+               rix->revindex[i] = ntohl(hl);
+       }
+       /* This knows the pack format -- the 20-byte trailer
+        * follows immediately after the last object data.
+        */
+       rix->revindex[num_ent] = p->pack_size - 20;
+       qsort(rix->revindex, num_ent, sizeof(unsigned long), cmp_offset);
+}
+
+static unsigned long find_packed_object_size(struct packed_git *p,
+                                            unsigned long ofs)
+{
+       int num;
+       int lo, hi;
+       struct pack_revindex *rix;
+       unsigned long *revindex;
+       num = pack_revindex_ix(p);
+       if (num < 0)
+               die("internal error: pack revindex uninitialized");
+       rix = &pack_revindex[num];
+       if (!rix->revindex)
+               prepare_pack_revindex(rix);
+       revindex = rix->revindex;
+       lo = 0;
+       hi = num_packed_objects(p) + 1;
+       do {
+               int mi = (lo + hi) / 2;
+               if (revindex[mi] == ofs) {
+                       return revindex[mi+1] - ofs;
+               }
+               else if (ofs < revindex[mi])
+                       hi = mi;
+               else
+                       lo = mi + 1;
+       } while (lo < hi);
+       die("internal error: pack revindex corrupt");
+}
+
 static void *delta_against(void *buf, unsigned long size, struct object_entry *entry)
 {
        unsigned long othersize, delta_size;
@@ -74,39 +237,77 @@ static int encode_header(enum object_type type, unsigned long size, unsigned cha
        return n;
 }
 
-static unsigned long write_object(struct sha1file *f, struct object_entry *entry)
+static unsigned long write_object(struct sha1file *f,
+                                 struct object_entry *entry)
 {
        unsigned long size;
        char type[10];
-       void *buf = read_sha1_file(entry->sha1, type, &size);
+       void *buf;
        unsigned char header[10];
        unsigned hdrlen, datalen;
        enum object_type obj_type;
+       int to_reuse = 0;
 
-       if (!buf)
-               die("unable to read %s", sha1_to_hex(entry->sha1));
-       if (size != entry->size)
-               die("object %s size inconsistency (%lu vs %lu)", sha1_to_hex(entry->sha1), size, entry->size);
+       if (entry->preferred_base)
+               return 0;
 
-       /*
-        * The object header is a byte of 'type' followed by zero or
-        * more bytes of length.  For deltas, the 20 bytes of delta sha1
-        * follows that.
-        */
        obj_type = entry->type;
-       if (entry->delta) {
-               buf = delta_against(buf, size, entry);
-               size = entry->delta_size;
-               obj_type = OBJ_DELTA;
+       if (! entry->in_pack)
+               to_reuse = 0;   /* can't reuse what we don't have */
+       else if (obj_type == OBJ_DELTA)
+               to_reuse = 1;   /* check_object() decided it for us */
+       else if (obj_type != entry->in_pack_type)
+               to_reuse = 0;   /* pack has delta which is unusable */
+       else if (entry->delta)
+               to_reuse = 0;   /* we want to pack afresh */
+       else
+               to_reuse = 1;   /* we have it in-pack undeltified,
+                                * and we do not need to deltify it.
+                                */
+
+       if (! to_reuse) {
+               buf = read_sha1_file(entry->sha1, type, &size);
+               if (!buf)
+                       die("unable to read %s", sha1_to_hex(entry->sha1));
+               if (size != entry->size)
+                       die("object %s size inconsistency (%lu vs %lu)",
+                           sha1_to_hex(entry->sha1), size, entry->size);
+               if (entry->delta) {
+                       buf = delta_against(buf, size, entry);
+                       size = entry->delta_size;
+                       obj_type = OBJ_DELTA;
+               }
+               /*
+                * The object header is a byte of 'type' followed by zero or
+                * more bytes of length.  For deltas, the 20 bytes of delta
+                * sha1 follows that.
+                */
+               hdrlen = encode_header(obj_type, size, header);
+               sha1write(f, header, hdrlen);
+
+               if (entry->delta) {
+                       sha1write(f, entry->delta, 20);
+                       hdrlen += 20;
+               }
+               datalen = sha1write_compressed(f, buf, size);
+               free(buf);
        }
-       hdrlen = encode_header(obj_type, size, header);
-       sha1write(f, header, hdrlen);
-       if (entry->delta) {
-               sha1write(f, entry->delta, 20);
-               hdrlen += 20;
+       else {
+               struct packed_git *p = entry->in_pack;
+               use_packed_git(p);
+
+               datalen = find_packed_object_size(p, entry->in_pack_offset);
+               buf = p->pack_base + entry->in_pack_offset;
+               sha1write(f, buf, datalen);
+               unuse_packed_git(p);
+               hdrlen = 0; /* not really */
+               if (obj_type == OBJ_DELTA)
+                       reused_delta++;
+               reused++;
        }
-       datalen = sha1write_compressed(f, buf, size);
-       free(buf);
+       if (obj_type == OBJ_DELTA)
+               written_delta++;
+       written++;
        return hdrlen + datalen;
 }
 
@@ -132,32 +333,31 @@ static void write_pack_file(void)
        int i;
        struct sha1file *f;
        unsigned long offset;
-       unsigned long mb;
        struct pack_header hdr;
 
        if (!base_name)
                f = sha1fd(1, "<stdout>");
        else
-               f = sha1create("%s-%s.%s", base_name, sha1_to_hex(object_list_sha1), "pack");
+               f = sha1create("%s-%s.%s", base_name,
+                              sha1_to_hex(object_list_sha1), "pack");
        hdr.hdr_signature = htonl(PACK_SIGNATURE);
        hdr.hdr_version = htonl(PACK_VERSION);
-       hdr.hdr_entries = htonl(nr_objects);
+       hdr.hdr_entries = htonl(nr_result);
        sha1write(f, &hdr, sizeof(hdr));
        offset = sizeof(hdr);
        for (i = 0; i < nr_objects; i++)
                offset = write_one(f, objects + i, offset);
 
        sha1close(f, pack_file_sha1, 1);
-       mb = offset >> 20;
-       offset &= 0xfffff;
 }
 
 static void write_index_file(void)
 {
        int i;
-       struct sha1file *f = sha1create("%s-%s.%s", base_name, sha1_to_hex(object_list_sha1), "idx");
+       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_objects;
+       struct object_entry **last = list + nr_result;
        unsigned int array[256];
 
        /*
@@ -182,7 +382,7 @@ static void write_index_file(void)
         * Write the actual SHA1 entries..
         */
        list = sorted_by_sha;
-       for (i = 0; i < nr_objects; i++) {
+       for (i = 0; i < nr_result; i++) {
                struct object_entry *entry = *list++;
                unsigned int offset = htonl(entry->offset);
                sha1write(f, &offset, 4);
@@ -192,25 +392,87 @@ static void write_index_file(void)
        sha1close(f, NULL, 1);
 }
 
-static int add_object_entry(unsigned char *sha1, unsigned int hash)
+static int locate_object_entry_hash(const unsigned char *sha1)
 {
+       int i;
+       unsigned int ui;
+       memcpy(&ui, sha1, sizeof(unsigned int));
+       i = ui % object_ix_hashsz;
+       while (0 < object_ix[i]) {
+               if (!memcmp(sha1, objects[object_ix[i]-1].sha1, 20))
+                       return i;
+               if (++i == object_ix_hashsz)
+                       i = 0;
+       }
+       return -1 - i;
+}
+
+static struct object_entry *locate_object_entry(const unsigned char *sha1)
+{
+       int i;
+
+       if (!object_ix_hashsz)
+               return NULL;
+
+       i = locate_object_entry_hash(sha1);
+       if (0 <= i)
+               return &objects[object_ix[i]-1];
+       return NULL;
+}
+
+static void rehash_objects(void)
+{
+       int i;
+       struct object_entry *oe;
+
+       object_ix_hashsz = nr_objects * 3;
+       if (object_ix_hashsz < 1024)
+               object_ix_hashsz = 1024;
+       object_ix = xrealloc(object_ix, sizeof(int) * object_ix_hashsz);
+       object_ix = memset(object_ix, 0, sizeof(int) * object_ix_hashsz);
+       for (i = 0, oe = objects; i < nr_objects; i++, oe++) {
+               int ix = locate_object_entry_hash(oe->sha1);
+               if (0 <= ix)
+                       continue;
+               ix = -1 - ix;
+               object_ix[ix] = i + 1;
+       }
+}
+
+static int add_object_entry(const unsigned char *sha1, const char *name, int exclude)
+{
+       unsigned int hash = 0;
        unsigned int idx = nr_objects;
        struct object_entry *entry;
+       struct packed_git *p;
+       unsigned int found_offset = 0;
+       struct packed_git *found_pack = NULL;
+       int ix;
 
-       if (incremental || local) {
-               struct packed_git *p;
-
+       if (!exclude) {
                for (p = packed_git; p; p = p->next) {
                        struct pack_entry e;
-
                        if (find_pack_entry_one(sha1, &e, p)) {
                                if (incremental)
                                        return 0;
                                if (local && !p->pack_local)
                                        return 0;
+                               if (!found_pack) {
+                                       found_offset = e.offset;
+                                       found_pack = e.p;
+                               }
                        }
                }
        }
+       if ((entry = locate_object_entry(sha1)) != NULL)
+               goto already_added;
+
+       while (*name) {
+               unsigned char c = *name++;
+               if (isspace(c))
+                       continue;
+               hash = hash * 11 + c;
+       }
 
        if (idx >= nr_alloc) {
                unsigned int needed = (idx + 1024) * 3 / 2;
@@ -218,42 +480,161 @@ static int add_object_entry(unsigned char *sha1, unsigned int hash)
                nr_alloc = needed;
        }
        entry = objects + idx;
+       nr_objects = idx + 1;
        memset(entry, 0, sizeof(*entry));
        memcpy(entry->sha1, sha1, 20);
        entry->hash = hash;
-       nr_objects = idx+1;
+
+       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;
+       }
+
+ already_added:
+       if (exclude)
+               entry->preferred_base = 1;
+       else {
+               if (found_pack) {
+                       entry->in_pack = found_pack;
+                       entry->in_pack_offset = found_offset;
+               }
+       }
        return 1;
 }
 
+static void add_pbase_tree(struct tree_desc *tree)
+{
+       while (tree->size) {
+               const unsigned char *sha1;
+               const char *name;
+               unsigned mode;
+               unsigned long size;
+               char type[20];
+
+               sha1 = tree_entry_extract(tree, &name, &mode);
+               update_tree_entry(tree);
+               if (!has_sha1_file(sha1))
+                       continue;
+               if (sha1_object_info(sha1, type, &size))
+                       continue;
+               add_object_entry(sha1, name, 1);
+               if (!strcmp(type, "tree")) {
+                       struct tree_desc sub;
+                       void *elem;
+                       elem = read_sha1_file(sha1, type, &sub.size);
+                       sub.buf = elem;
+                       if (sub.buf) {
+                               add_pbase_tree(&sub);
+                               free(elem);
+                       }
+               }
+       }
+}
+
+static void add_preferred_base(unsigned char *sha1)
+{
+       struct tree_desc tree;
+       void *elem;
+       elem = read_object_with_reference(sha1, "tree", &tree.size, NULL);
+       tree.buf = elem;
+       if (!tree.buf)
+               return;
+       add_object_entry(sha1, "", 1);
+       add_pbase_tree(&tree);
+       free(elem);
+}
+
 static void check_object(struct object_entry *entry)
 {
        char type[20];
 
-       if (!sha1_object_info(entry->sha1, type, &entry->size)) {
-               if (!strcmp(type, "commit")) {
-                       entry->type = OBJ_COMMIT;
-               } else if (!strcmp(type, "tree")) {
-                       entry->type = OBJ_TREE;
-               } else if (!strcmp(type, "blob")) {
-                       entry->type = OBJ_BLOB;
-               } else if (!strcmp(type, "tag")) {
-                       entry->type = OBJ_TAG;
-               } else
-                       die("unable to pack object %s of type %s",
-                           sha1_to_hex(entry->sha1), type);
+       if (entry->in_pack && !entry->preferred_base) {
+               unsigned char base[20];
+               unsigned long size;
+               struct object_entry *base_entry;
+
+               /* We want in_pack_type even if we do not reuse delta.
+                * There is no point not reusing non-delta representations.
+                */
+               check_reuse_pack_delta(entry->in_pack,
+                                      entry->in_pack_offset,
+                                      base, &size,
+                                      &entry->in_pack_type);
+
+               /* 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.
+                */
+               if (!no_reuse_delta &&
+                   entry->in_pack_type == OBJ_DELTA &&
+                   (base_entry = locate_object_entry(base)) &&
+                   (!base_entry->preferred_base)) {
+
+                       /* 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 = entry->delta_size = size;
+                       entry->delta = base_entry;
+                       entry->type = OBJ_DELTA;
+
+                       entry->delta_sibling = base_entry->delta_child;
+                       base_entry->delta_child = entry;
+
+                       return;
+               }
+               /* Otherwise we would do the usual */
        }
-       else
+
+       if (sha1_object_info(entry->sha1, type, &entry->size))
                die("unable to get type of object %s",
                    sha1_to_hex(entry->sha1));
+
+       if (!strcmp(type, "commit")) {
+               entry->type = OBJ_COMMIT;
+       } else if (!strcmp(type, "tree")) {
+               entry->type = OBJ_TREE;
+       } else if (!strcmp(type, "blob")) {
+               entry->type = OBJ_BLOB;
+       } else if (!strcmp(type, "tag")) {
+               entry->type = OBJ_TAG;
+       } else
+               die("unable to pack object %s of type %s",
+                   sha1_to_hex(entry->sha1), type);
+}
+
+static unsigned int check_delta_limit(struct object_entry *me, unsigned int n)
+{
+       struct object_entry *child = me->delta_child;
+       unsigned int m = n;
+       while (child) {
+               unsigned int c = check_delta_limit(child, n + 1);
+               if (m < c)
+                       m = c;
+               child = child->delta_sibling;
+       }
+       return m;
 }
 
 static void get_object_details(void)
 {
        int i;
-       struct object_entry *entry = objects;
+       struct object_entry *entry;
 
-       for (i = 0; i < nr_objects; i++)
-               check_object(entry++);
+       prepare_pack_ix();
+       for (i = 0, entry = objects; i < nr_objects; i++, entry++)
+               check_object(entry);
+       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 *);
@@ -284,6 +665,24 @@ static int sha1_sort(const struct object_entry *a, const struct object_entry *b)
        return memcmp(a->sha1, b->sha1, 20);
 }
 
+static struct object_entry **create_final_object_list()
+{
+       struct object_entry **list;
+       int 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;
+}
+
 static int type_size_sort(const struct object_entry *a, const struct object_entry *b)
 {
        if (a->type < b->type)
@@ -294,6 +693,10 @@ static int type_size_sort(const struct object_entry *a, const struct object_entr
                return -1;
        if (a->hash > b->hash)
                return 1;
+       if (a->preferred_base < b->preferred_base)
+               return -1;
+       if (a->preferred_base > b->preferred_base)
+               return 1;
        if (a->size < b->size)
                return -1;
        if (a->size > b->size)
@@ -318,6 +721,8 @@ static int try_delta(struct unpacked *cur, struct unpacked *old, unsigned max_de
 {
        struct object_entry *cur_entry = cur->entry;
        struct object_entry *old_entry = old->entry;
+       int old_preferred = (old_entry->preferred_base ||
+                            old_entry->based_on_preferred);
        unsigned long size, oldsize, delta_size, sizediff;
        long max_size;
        void *delta_buf;
@@ -326,12 +731,27 @@ static int try_delta(struct unpacked *cur, struct unpacked *old, unsigned max_de
        if (cur_entry->type != old_entry->type)
                return -1;
 
-       size = cur_entry->size;
-       if (size < 50)
+       /* We do not compute delta to *create* objects we are not
+        * going to pack.
+        */
+       if (cur_entry->preferred_base)
                return -1;
+
+       /* 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 (cur_entry->delta_child) {
+               if (max_depth <= cur_entry->delta_limit)
+                       return 0;
+               max_depth -= cur_entry->delta_limit;
+       }
+
+       size = cur_entry->size;
        oldsize = old_entry->size;
        sizediff = oldsize > size ? oldsize - size : size - oldsize;
-       if (sizediff > size / 8)
+
+       if (size < 50)
                return -1;
        if (old_entry->depth >= max_depth)
                return 0;
@@ -344,8 +764,27 @@ static int try_delta(struct unpacked *cur, struct unpacked *old, unsigned max_de
         * delete).
         */
        max_size = size / 2 - 20;
-       if (cur_entry->delta)
-               max_size = cur_entry->delta_size-1;
+       if (cur_entry->delta) {
+               if (cur_entry->based_on_preferred) {
+                       if (old_preferred)
+                               max_size = cur_entry->delta_size-1;
+                       else
+                               /* trying with non-preferred one when we
+                                * already have a delta based on preferred
+                                * one is pointless.
+                                */
+                               return 0;
+               }
+               else if (!old_preferred)
+                       max_size = cur_entry->delta_size-1;
+               else
+                       /* otherwise...  even if delta with a
+                        * preferred one produces a bigger result than
+                        * what we currently have, which is based on a
+                        * non-preferred one, it is OK.
+                        */
+                       ;
+       }
        if (sizediff >= max_size)
                return -1;
        delta_buf = diff_delta(old->data, oldsize,
@@ -355,6 +794,7 @@ static int try_delta(struct unpacked *cur, struct unpacked *old, unsigned max_de
        cur_entry->delta = old_entry;
        cur_entry->delta_size = delta_size;
        cur_entry->depth = old_entry->depth + 1;
+       cur_entry->based_on_preferred = old_preferred;
        free(delta_buf);
        return 0;
 }
@@ -382,11 +822,19 @@ static void find_deltas(struct object_entry **list, int window, int depth)
                        eye_candy -= nr_objects / 20;
                        fputc('.', stderr);
                }
+
+               if (entry->delta)
+                       /* This happens if we decided to reuse existing
+                        * delta from a pack.  "!no_reuse_delta &&" is implied.
+                        */
+                       continue;
+
                free(n->data);
                n->entry = entry;
                n->data = read_sha1_file(entry->sha1, type, &size);
                if (size != entry->size)
                        die("object %s inconsistent object length (%lu vs %lu)", sha1_to_hex(entry->sha1), size, entry->size);
+
                j = window;
                while (--j > 0) {
                        unsigned int other_idx = idx + j;
@@ -411,10 +859,12 @@ static void find_deltas(struct object_entry **list, int window, int depth)
 
 static void prepare_pack(int window, int depth)
 {
+       if (progress)
+               fprintf(stderr, "Packing %d objects", nr_result);
        get_object_details();
-
        if (progress)
-               fprintf(stderr, "Packing %d objects", nr_objects);
+               fputc('.', stderr);
+
        sorted_by_type = create_sorted_list(type_size_sort);
        if (window && depth)
                find_deltas(sorted_by_type, window+1, depth);
@@ -443,8 +893,9 @@ static int reuse_cached_pack(unsigned char *sha1, int pack_to_stdout)
                }
        }
 
-       fprintf(stderr, "Reusing %d objects pack %s\n", nr_objects,
-               sha1_to_hex(sha1));
+       if (progress)
+               fprintf(stderr, "Reusing %d objects pack %s\n", nr_objects,
+                       sha1_to_hex(sha1));
 
        if (pack_to_stdout) {
                if (copy_fd(ifd, 1))
@@ -524,6 +975,10 @@ int main(int argc, char **argv)
                                progress = 0;
                                continue;
                        }
+                       if (!strcmp("--no-reuse-delta", arg)) {
+                               no_reuse_delta = 1;
+                               continue;
+                       }
                        if (!strcmp("--stdout", arg)) {
                                pack_to_stdout = 1;
                                continue;
@@ -544,8 +999,6 @@ int main(int argc, char **argv)
                gettimeofday(&prev_tv, NULL);
        }
        while (fgets(line, sizeof(line), stdin) != NULL) {
-               unsigned int hash;
-               char *p;
                unsigned char sha1[20];
 
                if (progress && (eye_candy <= nr_objects)) {
@@ -564,31 +1017,32 @@ int main(int argc, char **argv)
                        }
                        eye_candy += eye_candy_incr;
                }
+               if (line[0] == '-') {
+                       if (get_sha1_hex(line+1, sha1))
+                               die("expected edge sha1, got garbage:\n %s",
+                                   line+1);
+                       add_preferred_base(sha1);
+                       continue;
+               }
                if (get_sha1_hex(line, sha1))
                        die("expected sha1, got garbage:\n %s", line);
-               hash = 0;
-               p = line+40;
-               while (*p) {
-                       unsigned char c = *p++;
-                       if (isspace(c))
-                               continue;
-                       hash = hash * 11 + c;
-               }
-               add_object_entry(sha1, hash);
+               add_object_entry(sha1, line+40, 0);
        }
        if (progress)
                fprintf(stderr, "Done counting %d objects.\n", nr_objects);
        if (non_empty && !nr_objects)
                return 0;
 
-       sorted_by_sha = create_sorted_list(sha1_sort);
+       sorted_by_sha = create_final_object_list();
        SHA1_Init(&ctx);
        list = sorted_by_sha;
-       for (i = 0; i < nr_objects; i++) {
+       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 %d objects.\n", nr_result);
 
        if (reuse_cached_pack(object_list_sha1, pack_to_stdout))
                ;
@@ -599,5 +1053,8 @@ int main(int argc, char **argv)
                        puts(sha1_to_hex(object_list_sha1));
                }
        }
+       if (progress)
+               fprintf(stderr, "Total %d, written %d (delta %d), reused %d (delta %d)\n",
+                       nr_result, written, written_delta, reused, reused_delta);
        return 0;
 }
diff --git a/pack.h b/pack.h
index 9dafa2b..694e0c5 100644 (file)
--- a/pack.h
+++ b/pack.h
@@ -29,5 +29,7 @@ struct pack_header {
 };
 
 extern int verify_pack(struct packed_git *, int);
-
+extern int check_reuse_pack_delta(struct packed_git *, unsigned long,
+                                 unsigned char *, unsigned long *,
+                                 enum object_type *);
 #endif
index c5474d4..f97f92d 100644 (file)
@@ -27,6 +27,9 @@ void fill_stat_cache_info(struct cache_entry *ce, struct stat *st)
        ce->ce_uid = htonl(st->st_uid);
        ce->ce_gid = htonl(st->st_gid);
        ce->ce_size = htonl(st->st_size);
+
+       if (assume_unchanged)
+               ce->ce_flags |= htons(CE_VALID);
 }
 
 static int ce_compare_data(struct cache_entry *ce, struct stat *st)
@@ -146,9 +149,18 @@ static int ce_match_stat_basic(struct cache_entry *ce, struct stat *st)
        return changed;
 }
 
-int ce_match_stat(struct cache_entry *ce, struct stat *st)
+int ce_match_stat(struct cache_entry *ce, struct stat *st, int ignore_valid)
 {
-       unsigned int changed = ce_match_stat_basic(ce, st);
+       unsigned int changed;
+
+       /*
+        * If it's marked as always valid in the index, it's
+        * valid whatever the checked-out copy says.
+        */
+       if (!ignore_valid && (ce->ce_flags & htons(CE_VALID)))
+               return 0;
+
+       changed = ce_match_stat_basic(ce, st);
 
        /*
         * Within 1 second of this sequence:
@@ -164,7 +176,7 @@ int ce_match_stat(struct cache_entry *ce, struct stat *st)
         * effectively mean we can make at most one commit per second,
         * which is not acceptable.  Instead, we check cache entries
         * whose mtime are the same as the index file timestamp more
-        * careful than others.
+        * carefully than others.
         */
        if (!changed &&
            index_file_timestamp &&
@@ -174,10 +186,10 @@ int ce_match_stat(struct cache_entry *ce, struct stat *st)
        return changed;
 }
 
-int ce_modified(struct cache_entry *ce, struct stat *st)
+int ce_modified(struct cache_entry *ce, struct stat *st, int really)
 {
        int changed, changed_fs;
-       changed = ce_match_stat(ce, st);
+       changed = ce_match_stat(ce, st, really);
        if (!changed)
                return 0;
        /*
@@ -233,6 +245,11 @@ int cache_name_compare(const char *name1, int flags1, const char *name2, int fla
                return -1;
        if (len1 > len2)
                return 1;
+
+       /* Compare stages  */
+       flags1 &= CE_STAGEMASK;
+       flags2 &= CE_STAGEMASK;
+
        if (flags1 < flags2)
                return -1;
        if (flags1 > flags2)
@@ -430,6 +447,7 @@ int add_cache_entry(struct cache_entry *ce, int option)
        int ok_to_add = option & ADD_CACHE_OK_TO_ADD;
        int ok_to_replace = option & ADD_CACHE_OK_TO_REPLACE;
        int skip_df_check = option & ADD_CACHE_SKIP_DFCHECK;
+
        pos = cache_name_pos(ce->name, ntohs(ce->ce_flags));
 
        /* existing match? Just replace it. */
index 5580f15..52f06e3 100644 (file)
@@ -349,7 +349,7 @@ static void verify_uptodate(struct cache_entry *ce)
                return;
 
        if (!lstat(ce->name, &st)) {
-               unsigned changed = ce_match_stat(ce, &st);
+               unsigned changed = ce_match_stat(ce, &st, 1);
                if (!changed)
                        return;
                errno = 0;
index f2d1105..1589400 100644 (file)
@@ -30,7 +30,7 @@ static const char rev_list_usage[] =
 "    --date-order\n"
 "  formatting output:\n"
 "    --parents\n"
-"    --objects\n"
+"    --objects | --objects-edge\n"
 "    --unpacked\n"
 "    --header | --pretty\n"
 "    --abbrev=nr | --no-abbrev\n"
@@ -44,6 +44,7 @@ static int bisect_list = 0;
 static int tag_objects = 0;
 static int tree_objects = 0;
 static int blob_objects = 0;
+static int edge_hint = 0;
 static int verbose_header = 0;
 static int abbrev = DEFAULT_ABBREV;
 static int show_parents = 0;
@@ -255,8 +256,8 @@ static void show_commit_list(struct commit_list *list)
                die("unknown pending object %s (%s)", sha1_to_hex(obj->sha1), name);
        }
        while (objects) {
-               /* An object with name "foo\n0000000000000000000000000000000000000000"
-                * can be used confuse downstream git-pack-objects very badly.
+               /* An object with name "foo\n0000000..." can be used to
+                * confuse downstream git-pack-objects very badly.
                 */
                const char *ep = strchr(objects->name, '\n');
                if (ep) {
@@ -430,16 +431,30 @@ static struct commit_list *find_bisection(struct commit_list *list)
        return best;
 }
 
+static void mark_edge_parents_uninteresting(struct commit *commit)
+{
+       struct commit_list *parents;
+
+       for (parents = commit->parents; parents; parents = parents->next) {
+               struct commit *parent = parents->item;
+               if (!(parent->object.flags & UNINTERESTING))
+                       continue;
+               mark_tree_uninteresting(parent->tree);
+               if (edge_hint)
+                       printf("-%s\n", sha1_to_hex(parent->object.sha1));
+       }
+}
+
 static void mark_edges_uninteresting(struct commit_list *list)
 {
        for ( ; list; list = list->next) {
-               struct commit_list *parents = list->item->parents;
+               struct commit *commit = list->item;
 
-               for ( ; parents; parents = parents->next) {
-                       struct commit *commit = parents->item;
-                       if (commit->object.flags & UNINTERESTING)
-                               mark_tree_uninteresting(commit->tree);
+               if (commit->object.flags & UNINTERESTING) {
+                       mark_tree_uninteresting(commit->tree);
+                       continue;
                }
+               mark_edge_parents_uninteresting(commit);
        }
 }
 
@@ -843,6 +858,13 @@ int main(int argc, const char **argv)
                        blob_objects = 1;
                        continue;
                }
+               if (!strcmp(arg, "--objects-edge")) {
+                       tag_objects = 1;
+                       tree_objects = 1;
+                       blob_objects = 1;
+                       edge_hint = 1;
+                       continue;
+               }
                if (!strcmp(arg, "--unpacked")) {
                        unpacked = 1;
                        limited = 1;
index a5fb93c..610eacb 100644 (file)
@@ -43,6 +43,7 @@ static int is_rev_argument(const char *arg)
                "--min-age=",
                "--no-merges",
                "--objects",
+               "--objects-edge",
                "--parents",
                "--pretty",
                "--show-breaks",
index 990be3f..f558386 100644 (file)
@@ -12,6 +12,7 @@ static const char *exec = "git-receive-pack";
 static int verbose = 0;
 static int send_all = 0;
 static int force_update = 0;
+static int use_thin_pack = 0;
 
 static int is_zero_sha1(const unsigned char *sha1)
 {
@@ -37,26 +38,47 @@ static void exec_pack_objects(void)
 
 static void exec_rev_list(struct ref *refs)
 {
+       struct ref *ref;
        static char *args[1000];
-       int i = 0;
+       int i = 0, j;
 
        args[i++] = "rev-list"; /* 0 */
-       args[i++] = "--objects";        /* 1 */
-       while (refs) {
-               char *buf = malloc(100);
-               if (i > 900)
+       if (use_thin_pack)      /* 1 */
+               args[i++] = "--objects-edge";
+       else
+               args[i++] = "--objects";
+
+       /* First send the ones we care about most */
+       for (ref = refs; ref; ref = ref->next) {
+               if (900 < i)
                        die("git-rev-list environment overflow");
-               if (!is_zero_sha1(refs->old_sha1) &&
-                   has_sha1_file(refs->old_sha1)) {
+               if (!is_zero_sha1(ref->new_sha1)) {
+                       char *buf = malloc(100);
                        args[i++] = buf;
-                       snprintf(buf, 50, "^%s", sha1_to_hex(refs->old_sha1));
+                       snprintf(buf, 50, "%s", sha1_to_hex(ref->new_sha1));
                        buf += 50;
+                       if (!is_zero_sha1(ref->old_sha1) &&
+                           has_sha1_file(ref->old_sha1)) {
+                               args[i++] = buf;
+                               snprintf(buf, 50, "^%s",
+                                        sha1_to_hex(ref->old_sha1));
+                       }
                }
-               if (!is_zero_sha1(refs->new_sha1)) {
+       }
+
+       /* Then a handful of the remainder
+        * NEEDSWORK: we would be better off if used the newer ones first.
+        */
+       for (ref = refs, j = i + 16;
+            i < 900 && i < j && ref;
+            ref = ref->next) {
+               if (is_zero_sha1(ref->new_sha1) &&
+                   !is_zero_sha1(ref->old_sha1) &&
+                   has_sha1_file(ref->old_sha1)) {
+                       char *buf = malloc(42);
                        args[i++] = buf;
-                       snprintf(buf, 50, "%s", sha1_to_hex(refs->new_sha1));
+                       snprintf(buf, 42, "^%s", sha1_to_hex(ref->old_sha1));
                }
-               refs = refs->next;
        }
        args[i] = NULL;
        execv_git_cmd(args);
@@ -361,6 +383,10 @@ int main(int argc, char **argv)
                                verbose = 1;
                                continue;
                        }
+                       if (!strcmp(arg, "--thin")) {
+                               use_thin_pack = 1;
+                               continue;
+                       }
                        usage(send_pack_usage);
                }
                if (!dest) {
index 1d799f7..9cab99a 100644 (file)
@@ -828,6 +828,25 @@ static unsigned long unpack_object_header(struct packed_git *p, unsigned long of
        return offset;
 }
 
+int check_reuse_pack_delta(struct packed_git *p, unsigned long offset,
+                          unsigned char *base, unsigned long *sizep,
+                          enum object_type *kindp)
+{
+       unsigned long ptr;
+       int status = -1;
+
+       use_packed_git(p);
+       ptr = offset;
+       ptr = unpack_object_header(p, ptr, kindp, sizep);
+       if (*kindp != OBJ_DELTA)
+               goto done;
+       memcpy(base, p->pack_base + ptr, 20);
+       status = 0;
+ done:
+       unuse_packed_git(p);
+       return status;
+}
+
 void packed_object_info_detail(struct pack_entry *e,
                               char *type,
                               unsigned long *size,
index 5c5a620..fe65f53 100644 (file)
@@ -8,17 +8,18 @@ SHELL_PATH ?= $(SHELL)
 TAR ?= $(TAR)
 
 # Shell quote;
-# Result of this needs to be placed inside ''
-shq = $(subst ','\'',$(1))
-# This has surrounding ''
-shellquote = '$(call shq,$(1))'
+SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH))
 
 T = $(wildcard t[0-9][0-9][0-9][0-9]-*.sh)
 
+ifdef NO_PYTHON
+       GIT_TEST_OPTS += --no-python
+endif
+
 all: $(T) clean
 
 $(T):
-       @echo "*** $@ ***"; $(call shellquote,$(SHELL_PATH)) $@ $(GIT_TEST_OPTS)
+       @echo "*** $@ ***"; '$(SHELL_PATH_SQ)' $@ $(GIT_TEST_OPTS)
 
 clean:
        rm -fr trash
index c339a36..6729a18 100755 (executable)
@@ -42,7 +42,7 @@ fi
 
 . ./test-lib.sh
 
-"$PYTHON" -c 'import subprocess' || {
+test "$no_python" || "$PYTHON" -c 'import subprocess' || {
        echo >&2 'Your python seem to lack "subprocess" module.
 Please check INSTALL document.'
        exit 1
diff --git a/t/t3020-ls-files-error-unmatch.sh b/t/t3020-ls-files-error-unmatch.sh
new file mode 100755 (executable)
index 0000000..d55559e
--- /dev/null
@@ -0,0 +1,27 @@
+#!/bin/sh
+#
+# Copyright (c) 2006 Carl D. Worth
+#
+
+test_description='git-ls-files test for --error-unmatch option
+
+This test runs git-ls-files --error-unmatch to ensure it correctly
+returns an error when a non-existent path is provided on the command
+line.
+'
+. ./test-lib.sh
+
+touch foo bar
+git-update-index --add foo bar
+git-commit -m "add foo bar"
+
+test_expect_failure \
+    'git-ls-files --error-unmatch should fail with unmatched path.' \
+    'git-ls-files --error-unmatch foo bar-does-not-match'
+
+test_expect_success \
+    'git-ls-files --error-unmatch should succeed eith matched paths.' \
+    'git-ls-files --error-unmatch foo bar'
+
+test_done
+1
diff --git a/t/t3700-add.sh b/t/t3700-add.sh
new file mode 100755 (executable)
index 0000000..6cd05c3
--- /dev/null
@@ -0,0 +1,22 @@
+#!/bin/sh
+#
+# Copyright (c) 2006 Carl D. Worth
+#
+
+test_description='Test of git-add, including the -- option.'
+
+. ./test-lib.sh
+
+test_expect_success \
+    'Test of git-add' \
+    'touch foo && git-add foo'
+
+test_expect_success \
+    'Post-check that foo is in the index' \
+    'git-ls-files foo | grep foo'
+
+test_expect_success \
+    'Test that "git-add -- -q" works' \
+    'touch -- -q && git-add -- -q'
+
+test_done
diff --git a/t/t5600-clone-fail-cleanup.sh b/t/t5600-clone-fail-cleanup.sh
new file mode 100755 (executable)
index 0000000..0c6a363
--- /dev/null
@@ -0,0 +1,36 @@
+#!/bin/sh
+#
+# Copyright (C) 2006 Carl D. Worth <cworth@cworth.org>
+#
+
+test_description='test git-clone to cleanup after failure
+
+This test covers the fact that if git-clone fails, it should remove
+the directory it created, to avoid the user having to manually
+remove the directory before attempting a clone again.'
+
+. ./test-lib.sh
+
+test_expect_failure \
+    'clone of non-existent source should fail' \
+    'git-clone foo bar'
+
+test_expect_failure \
+    'failed clone should not leave a directory' \
+    'cd bar'
+
+# Need a repo to clone
+test_create_repo foo
+
+# clone doesn't like it if there is no HEAD. Is that a bug?
+(cd foo && touch file && git add file && git commit -m 'add file' >/dev/null 2>&1)
+
+test_expect_success \
+    'clone should work now that source exists' \
+    'git-clone foo bar'
+
+test_expect_success \
+    'successfull clone must leave the directory' \
+    'cd bar'
+
+test_done
index e8606c7..2623813 100755 (executable)
 test_description='Test criss-cross merge'
 . ./test-lib.sh
 
+if test "$no_python"; then
+       echo "Skipping: no python => no recursive merge"
+       test_done
+       exit 0
+fi
+
 test_expect_success 'prepare repository' \
 'echo "1
 2
index 1292caf..a2d24b5 100755 (executable)
@@ -3,6 +3,12 @@
 test_description='Merge-recursive merging renames'
 . ./test-lib.sh
 
+if test "$no_python"; then
+       echo "Skipping: no python => no recursive merge"
+       test_done
+       exit 0
+fi
+
 test_expect_success setup \
 '
 cat >A <<\EOF &&
index 66f62b9..05f6e79 100755 (executable)
@@ -63,6 +63,8 @@ do
                exit 0 ;;
        -v|--v|--ve|--ver|--verb|--verbo|--verbos|--verbose)
                verbose=t; shift ;;
+       --no-python)
+               no_python=t; shift ;;
        *)
                break ;;
        esac
index afec98d..ce1db38 100644 (file)
@@ -23,6 +23,10 @@ static int quiet; /* --refresh needing update is not error */
 static int info_only;
 static int force_remove;
 static int verbose;
+static int mark_valid_only = 0;
+#define MARK_VALID 1
+#define UNMARK_VALID 2
+
 
 /* Three functions to allow overloaded pointer return; see linux/err.h */
 static inline void *ERR_PTR(long error)
@@ -53,6 +57,25 @@ static void report(const char *fmt, ...)
        va_end(vp);
 }
 
+static int mark_valid(const char *path)
+{
+       int namelen = strlen(path);
+       int pos = cache_name_pos(path, namelen);
+       if (0 <= pos) {
+               switch (mark_valid_only) {
+               case MARK_VALID:
+                       active_cache[pos]->ce_flags |= htons(CE_VALID);
+                       break;
+               case UNMARK_VALID:
+                       active_cache[pos]->ce_flags &= ~htons(CE_VALID);
+                       break;
+               }
+               active_cache_changed = 1;
+               return 0;
+       }
+       return -1;
+}
+
 static int add_file_to_cache(const char *path)
 {
        int size, namelen, option, status;
@@ -94,6 +117,7 @@ static int add_file_to_cache(const char *path)
        ce = xmalloc(size);
        memset(ce, 0, size);
        memcpy(ce->name, path, namelen);
+       ce->ce_flags = htons(namelen);
        fill_stat_cache_info(ce, &st);
 
        ce->ce_mode = create_ce_mode(st.st_mode);
@@ -105,7 +129,6 @@ static int add_file_to_cache(const char *path)
                if (0 <= pos)
                        ce->ce_mode = active_cache[pos]->ce_mode;
        }
-       ce->ce_flags = htons(namelen);
 
        if (index_path(ce->sha1, path, &st, !info_only))
                return -1;
@@ -128,7 +151,7 @@ static int add_file_to_cache(const char *path)
  * For example, you'd want to do this after doing a "git-read-tree",
  * to link up the stat cache details with the proper files.
  */
-static struct cache_entry *refresh_entry(struct cache_entry *ce)
+static struct cache_entry *refresh_entry(struct cache_entry *ce, int really)
 {
        struct stat st;
        struct cache_entry *updated;
@@ -137,21 +160,36 @@ static struct cache_entry *refresh_entry(struct cache_entry *ce)
        if (lstat(ce->name, &st) < 0)
                return ERR_PTR(-errno);
 
-       changed = ce_match_stat(ce, &st);
-       if (!changed)
-               return NULL;
+       changed = ce_match_stat(ce, &st, really);
+       if (!changed) {
+               if (really && assume_unchanged &&
+                   !(ce->ce_flags & htons(CE_VALID)))
+                       ; /* mark this one VALID again */
+               else
+                       return NULL;
+       }
 
-       if (ce_modified(ce, &st))
+       if (ce_modified(ce, &st, really))
                return ERR_PTR(-EINVAL);
 
        size = ce_size(ce);
        updated = xmalloc(size);
        memcpy(updated, ce, size);
        fill_stat_cache_info(updated, &st);
+
+       /* In this case, if really is not set, we should leave
+        * CE_VALID bit alone.  Otherwise, paths marked with
+        * --no-assume-unchanged (i.e. things to be edited) will
+        * reacquire CE_VALID bit automatically, which is not
+        * really what we want.
+        */
+       if (!really && assume_unchanged && !(ce->ce_flags & htons(CE_VALID)))
+               updated->ce_flags &= ~htons(CE_VALID);
+
        return updated;
 }
 
-static int refresh_cache(void)
+static int refresh_cache(int really)
 {
        int i;
        int has_errors = 0;
@@ -171,12 +209,19 @@ static int refresh_cache(void)
                        continue;
                }
 
-               new = refresh_entry(ce);
+               new = refresh_entry(ce, really);
                if (!new)
                        continue;
                if (IS_ERR(new)) {
                        if (not_new && PTR_ERR(new) == -ENOENT)
                                continue;
+                       if (really && PTR_ERR(new) == -EINVAL) {
+                               /* If we are doing --really-refresh that
+                                * means the index is not valid anymore.
+                                */
+                               ce->ce_flags &= ~htons(CE_VALID);
+                               active_cache_changed = 1;
+                       }
                        if (quiet)
                                continue;
                        printf("%s: needs update\n", ce->name);
@@ -274,6 +319,8 @@ static int add_cacheinfo(unsigned int mode, const unsigned char *sha1,
        memcpy(ce->name, path, len);
        ce->ce_flags = create_ce_flags(len, stage);
        ce->ce_mode = create_ce_mode(mode);
+       if (assume_unchanged)
+               ce->ce_flags |= htons(CE_VALID);
        option = allow_add ? ADD_CACHE_OK_TO_ADD : 0;
        option |= allow_replace ? ADD_CACHE_OK_TO_REPLACE : 0;
        if (add_cache_entry(ce, option))
@@ -317,6 +364,12 @@ static void update_one(const char *path, const char *prefix, int prefix_length)
                fprintf(stderr, "Ignoring path %s\n", path);
                return;
        }
+       if (mark_valid_only) {
+               if (mark_valid(p))
+                       die("Unable to mark file %s", path);
+               return;
+       }
+
        if (force_remove) {
                if (remove_file_from_cache(p))
                        die("git-update-index: unable to remove %s", path);
@@ -467,7 +520,11 @@ int main(int argc, const char **argv)
                                continue;
                        }
                        if (!strcmp(path, "--refresh")) {
-                               has_errors |= refresh_cache();
+                               has_errors |= refresh_cache(0);
+                               continue;
+                       }
+                       if (!strcmp(path, "--really-refresh")) {
+                               has_errors |= refresh_cache(1);
                                continue;
                        }
                        if (!strcmp(path, "--cacheinfo")) {
@@ -493,6 +550,14 @@ int main(int argc, const char **argv)
                                        die("git-update-index: %s cannot chmod %s", path, argv[i]);
                                continue;
                        }
+                       if (!strcmp(path, "--assume-unchanged")) {
+                               mark_valid_only = MARK_VALID;
+                               continue;
+                       }
+                       if (!strcmp(path, "--no-assume-unchanged")) {
+                               mark_valid_only = UNMARK_VALID;
+                               continue;
+                       }
                        if (!strcmp(path, "--info-only")) {
                                info_only = 1;
                                continue;
index 3606529..635abb3 100644 (file)
@@ -14,6 +14,7 @@ static const char upload_pack_usage[] = "git-upload-pack [--strict] [--timeout=n
 #define MAX_HAS 256
 #define MAX_NEEDS 256
 static int nr_has = 0, nr_needs = 0, multi_ack = 0, nr_our_refs = 0;
+static int use_thin_pack = 0;
 static unsigned char has_sha1[MAX_HAS][20];
 static unsigned char needs_sha1[MAX_NEEDS][20];
 static unsigned int timeout = 0;
@@ -49,8 +50,10 @@ static void create_pack_file(void)
                char *buf;
                char **p;
 
-               if (create_full_pack)
+               if (create_full_pack) {
                        args = 10;
+                       use_thin_pack = 0; /* no point doing it */
+               }
                else
                        args = nr_has + nr_needs + 5;
                argv = xmalloc(args * sizeof(char *));
@@ -62,7 +65,7 @@ static void create_pack_file(void)
                close(fd[0]);
                close(fd[1]);
                *p++ = "rev-list";
-               *p++ = "--objects";
+               *p++ = use_thin_pack ? "--objects-edge" : "--objects";
                if (create_full_pack || MAX_NEEDS <= nr_needs)
                        *p++ = "--all";
                else {
@@ -192,6 +195,8 @@ static int receive_needs(void)
                            "expected to get sha, not '%s'", line);
                if (strstr(line+45, "multi_ack"))
                        multi_ack = 1;
+               if (strstr(line+45, "thin-pack"))
+                       use_thin_pack = 1;
 
                /* We have sent all our refs already, and the other end
                 * should have chosen out of them; otherwise they are
@@ -213,7 +218,7 @@ static int receive_needs(void)
 
 static int send_ref(const char *refname, const unsigned char *sha1)
 {
-       static char *capabilities = "multi_ack";
+       static char *capabilities = "multi_ack thin-pack";
        struct object *o = parse_object(sha1);
 
        if (!o)
diff --git a/var.c b/var.c
index 59da56d..a57a33b 100644 (file)
--- a/var.c
+++ b/var.c
@@ -12,7 +12,7 @@ static const char var_usage[] = "git-var [-l | <variable>]";
 
 struct git_var {
        const char *name;
-       const char *(*read)(void);
+       const char *(*read)(int);
 };
 static struct git_var git_vars[] = {
        { "GIT_COMMITTER_IDENT", git_committer_info },
@@ -24,7 +24,7 @@ static void list_vars(void)
 {
        struct git_var *ptr;
        for(ptr = git_vars; ptr->read; ptr++) {
-               printf("%s=%s\n", ptr->name, ptr->read());
+               printf("%s=%s\n", ptr->name, ptr->read(0));
        }
 }
 
@@ -35,7 +35,7 @@ static const char *read_var(const char *var)
        val = NULL;
        for(ptr = git_vars; ptr->read; ptr++) {
                if (strcmp(var, ptr->name) == 0) {
-                       val = ptr->read();
+                       val = ptr->read(1);
                        break;
                }
        }
index f866059..addb5de 100644 (file)
@@ -111,7 +111,7 @@ int main(int argc, char **argv)
        funny = 0;
        for (i = 0; i < entries; i++) {
                struct cache_entry *ce = active_cache[i];
-               if (ntohs(ce->ce_flags) & ~CE_NAMEMASK) {
+               if (ce_stage(ce)) {
                        if (10 < ++funny) {
                                fprintf(stderr, "...\n");
                                break;