Merge branch 'ra/anno'
authorJunio C Hamano <junkio@cox.net>
Thu, 23 Feb 2006 03:20:08 +0000 (19:20 -0800)
committerJunio C Hamano <junkio@cox.net>
Thu, 23 Feb 2006 03:20:08 +0000 (19:20 -0800)
* ra/anno:
  Use Ryan's git-annotate instead of jsannotate
  Add git-annotate, a tool for assigning blame.

61 files changed:
.gitignore
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-rm.txt [new file with mode: 0644]
Documentation/git-update-index.txt
Makefile
apply.c
cache.h
checkout-index.c
commit-tree.c
config.c
contrib/gitview/gitview
diff-files.c
diff-index.c
diff.c
entry.c
environment.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-rebase.sh
git-repack.sh
git-rerere.perl
git-resolve.sh
git-revert.sh
git-rm.sh [new file with mode: 0755]
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
sha1_file.c
t/Makefile
t/t0000-basic.sh
t/t3020-ls-files-error-unmatch.sh [new file with mode: 0755]
t/t3600-rm.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
var.c
write-tree.c

index d7e8d2a..94f66d5 100644 (file)
@@ -84,6 +84,7 @@ git-resolve
 git-rev-list
 git-rev-parse
 git-revert
+git-rm
 git-send-email
 git-send-pack
 git-sh-setup
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`.
diff --git a/Documentation/git-rm.txt b/Documentation/git-rm.txt
new file mode 100644 (file)
index 0000000..401bfb2
--- /dev/null
@@ -0,0 +1,89 @@
+git-rm(1)
+=========
+
+NAME
+----
+git-rm - Remove files from the working tree and from the index.
+
+SYNOPSIS
+--------
+'git-rm' [-f] [-n] [-v] [--] <file>...
+
+DESCRIPTION
+-----------
+A convenience wrapper for git-update-index --remove. For those coming
+from cvs, git-rm provides an operation similar to "cvs rm" or "cvs
+remove".
+
+
+OPTIONS
+-------
+<file>...::
+       Files to remove from the index and optionally, from the
+       working tree as well.
+
+-f::
+       Remove files from the working tree as well as from the index.
+
+-n::
+        Don't actually remove the file(s), just show if they exist in
+        the index.
+
+-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
+----------
+
+The list of <file> given to the command is fed to `git-ls-files`
+command to list files that are registered in the index and
+are not ignored/excluded by `$GIT_DIR/info/exclude` file or
+`.gitignore` file in each directory.  This means two things:
+
+. You can put the name of a directory on the command line, and the
+  command will remove all files in it and its subdirectories (the
+  directories themselves are never removed from the working tree);
+
+. Giving the name of a file that is not in the index does not
+  remove that file.
+
+
+EXAMPLES
+--------
+git-rm Documentation/\\*.txt::
+
+       Removes all `\*.txt` files from the index that are under the
+       `Documentation` directory and any of its subdirectories. The
+       files are not removed from the working tree.
++
+Note that the asterisk `\*` is quoted from the shell in this
+example; this lets the command include the files from
+subdirectories of `Documentation/` directory.
+
+git-rm -f git-*.sh::
+
+       Remove all git-*.sh scripts that are in the index. The files
+       are removed from the index, and (because of the -f option),
+       from the working tree as well. Because this example lets the
+       shell expand the asterisk (i.e. you are listing the files
+       explicitly), it does not remove `subdir/git-foo.sh`.
+
+
+Author
+------
+Written by Linus Torvalds <torvalds@osdl.org>
+
+Documentation
+--------------
+Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
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..4d8aad3 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=
@@ -109,7 +120,7 @@ SCRIPT_SH = \
        git-merge-one-file.sh git-parse-remote.sh \
        git-prune.sh git-pull.sh git-push.sh git-rebase.sh \
        git-repack.sh git-request-pull.sh git-reset.sh \
-       git-resolve.sh git-revert.sh git-sh-setup.sh \
+       git-resolve.sh git-revert.sh git-rm.sh git-sh-setup.sh \
        git-tag.sh git-verify-tag.sh git-whatchanged.sh \
        git-applymbox.sh git-applypatch.sh git-am.sh \
        git-merge.sh git-merge-stupid.sh git-merge-octopus.sh \
@@ -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
 
 # 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/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..4b52eb7 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,20 @@ class GitView:
                respective sha1 details """
 
                self.bt_sha1 = { }
-               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()
+               ls_remote = re.compile('^(.{40})\trefs/([^^]+)(?:\\^(..))?$');
+               fp = os.popen('git ls-remote "${GIT_DIR-.git}"')
+               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 +513,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 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 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..fcc24f8 100755 (executable)
@@ -368,20 +368,25 @@ fetch_main "$reflist"
 # automated tag following
 case "$no_tags$tags" in
 '')
-       taglist=$(IFS=" " &&
-       git-ls-remote $upload_pack --tags "$remote" |
-       sed -ne 's|^\([0-9a-f]*\)[      ]\(refs/tags/.*\)^{}$|\1 \2|p' |
-       while read sha1 name
-       do
-               test -f "$GIT_DIR/$name" && continue
-               git-check-ref-format "$name" || {
-                       echo >&2 "warning: tag ${name} ignored"
-                       continue
-               }
-               git-cat-file -t "$sha1" >/dev/null 2>&1 || continue
-               echo >&2 "Auto-following $name"
-               echo ".${name}:${name}"
-       done)
+       case "$reflist" in
+       *:refs/*)
+               # effective only when we are following remote branch
+               # using local tracking branch.
+               taglist=$(IFS=" " &&
+               git-ls-remote $upload_pack --tags "$remote" |
+               sed -ne 's|^\([0-9a-f]*\)[      ]\(refs/tags/.*\)^{}$|\1 \2|p' |
+               while read sha1 name
+               do
+                       test -f "$GIT_DIR/$name" && continue
+                       git-check-ref-format "$name" || {
+                               echo >&2 "warning: tag ${name} ignored"
+                               continue
+                       }
+                       git-cat-file -t "$sha1" >/dev/null 2>&1 || continue
+                       echo >&2 "Auto-following $name"
+                       echo ".${name}:${name}"
+               done)
+       esac
        case "$taglist" in
        '') ;;
        ?*)
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 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"
diff --git a/git-rm.sh b/git-rm.sh
new file mode 100755 (executable)
index 0000000..fda4541
--- /dev/null
+++ b/git-rm.sh
@@ -0,0 +1,70 @@
+#!/bin/sh
+
+USAGE='[-f] [-n] [-v] [--] <file>...'
+SUBDIRECTORY_OK='Yes'
+. git-sh-setup
+
+remove_files=
+show_only=
+verbose=
+while : ; do
+  case "$1" in
+    -f)
+       remove_files=true
+       ;;
+    -n)
+       show_only=true
+       ;;
+    -v)
+       verbose=--verbose
+       ;;
+    --)
+       shift; break
+       ;;
+    -*)
+       usage
+       ;;
+    *)
+       break
+       ;;
+  esac
+  shift
+done
+
+# This is typo-proofing. If some paths match and some do not, we want
+# to do nothing.
+case "$#" in
+0)     ;;
+*)
+       git-ls-files --error-unmatch -- "$@" >/dev/null || {
+               echo >&2 "Maybe you misspelled it?"
+               exit 1
+       }
+       ;;
+esac
+
+if test -f "$GIT_DIR/info/exclude"
+then
+       git-ls-files -z \
+       --exclude-from="$GIT_DIR/info/exclude" \
+       --exclude-per-directory=.gitignore -- "$@"
+else
+       git-ls-files -z \
+       --exclude-per-directory=.gitignore -- "$@"
+fi |
+case "$show_only,$remove_files" in
+true,*)
+       xargs -0 echo
+       ;;
+*,true)
+       xargs -0 sh -c "
+               while [ \$# -gt 0 ]; do
+                       file=\$1; shift
+                       rm -- \"\$file\" && git-update-index --remove $verbose \"\$file\"
+               done
+       " inline
+       ;;
+*)
+       git-update-index --force-remove $verbose -z --stdin
+       ;;
+esac
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..8f352aa 100644 (file)
@@ -4,22 +4,47 @@
 #include "pack.h"
 #include "csum-file.h"
 #include <sys/time.h>
+#include <signal.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
+                                            */
 };
 
+/*
+ * 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;
@@ -28,6 +53,138 @@ static int nr_objects = 0, nr_alloc = 0;
 static const char *base_name;
 static unsigned char pack_file_sha1[20];
 static int progress = 1;
+static volatile int progress_update = 0;
+
+/*
+ * 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)
 {
@@ -78,35 +235,69 @@ 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);
-
-       /*
-        * 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,24 +323,41 @@ static void write_pack_file(void)
        int i;
        struct sha1file *f;
        unsigned long offset;
-       unsigned long mb;
        struct pack_header hdr;
+       unsigned last_percent = 999;
+       int do_progress = 0;
 
        if (!base_name)
                f = sha1fd(1, "<stdout>");
-       else
-               f = sha1create("%s-%s.%s", base_name, sha1_to_hex(object_list_sha1), "pack");
+       else {
+               f = sha1create("%s-%s.%s", base_name,
+                              sha1_to_hex(object_list_sha1), "pack");
+               do_progress = progress;
+       }
+       if (do_progress)
+               fprintf(stderr, "Writing %d objects.\n", nr_objects);
+
        hdr.hdr_signature = htonl(PACK_SIGNATURE);
        hdr.hdr_version = htonl(PACK_VERSION);
        hdr.hdr_entries = htonl(nr_objects);
        sha1write(f, &hdr, sizeof(hdr));
        offset = sizeof(hdr);
-       for (i = 0; i < nr_objects; i++)
+       for (i = 0; i < nr_objects; i++) {
                offset = write_one(f, objects + i, offset);
+               if (do_progress) {
+                       unsigned percent = written * 100 / nr_objects;
+                       if (progress_update || percent != last_percent) {
+                               fprintf(stderr, "%4u%% (%u/%u) done\r",
+                                       percent, written, nr_objects);
+                               progress_update = 0;
+                               last_percent = percent;
+                       }
+               }
+       }
+       if (do_progress)
+               fputc('\n', stderr);
 
        sha1close(f, pack_file_sha1, 1);
-       mb = offset >> 20;
-       offset &= 0xfffff;
 }
 
 static void write_index_file(void)
@@ -196,18 +404,20 @@ static int add_object_entry(unsigned char *sha1, unsigned int hash)
 {
        unsigned int idx = nr_objects;
        struct object_entry *entry;
-
-       if (incremental || local) {
-               struct packed_git *p;
-
-               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;
+       struct packed_git *p;
+       unsigned int found_offset = 0;
+       struct packed_git *found_pack = NULL;
+
+       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;
                        }
                }
        }
@@ -221,39 +431,143 @@ static int add_object_entry(unsigned char *sha1, unsigned int hash)
        memset(entry, 0, sizeof(*entry));
        memcpy(entry->sha1, sha1, 20);
        entry->hash = hash;
+       if (found_pack) {
+               entry->in_pack = found_pack;
+               entry->in_pack_offset = found_offset;
+       }
        nr_objects = idx+1;
        return 1;
 }
 
+static int locate_object_entry_hash(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(unsigned char *sha1)
+{
+       int i = locate_object_entry_hash(sha1);
+       if (0 <= i)
+               return &objects[object_ix[i]-1];
+       return NULL;
+}
+
 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) {
+               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))) {
+
+                       /* 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 void hash_objects(void)
+{
+       int i;
+       struct object_entry *oe;
+
+       object_ix_hashsz = nr_objects * 2;
+       object_ix = xcalloc(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) {
+                       error("the same object '%s' added twice",
+                             sha1_to_hex(oe->sha1));
+                       continue;
+               }
+               ix = -1 - ix;
+               object_ix[ix] = i + 1;
+       }
+}
+
+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++);
+       hash_objects();
+       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 *);
@@ -326,6 +640,16 @@ static int try_delta(struct unpacked *cur, struct unpacked *old, unsigned max_de
        if (cur_entry->type != old_entry->type)
                return -1;
 
+       /* If the current object is at 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;
        if (size < 50)
                return -1;
@@ -359,17 +683,25 @@ static int try_delta(struct unpacked *cur, struct unpacked *old, unsigned max_de
        return 0;
 }
 
+static void progress_interval(int signum)
+{
+       signal(SIGALRM, progress_interval);
+       progress_update = 1;
+}
+
 static void find_deltas(struct object_entry **list, int window, int depth)
 {
        int i, idx;
        unsigned int array_size = window * sizeof(struct unpacked);
        struct unpacked *array = xmalloc(array_size);
-       int eye_candy;
+       unsigned processed = 0;
+       unsigned last_percent = 999;
 
        memset(array, 0, array_size);
        i = nr_objects;
        idx = 0;
-       eye_candy = i - (nr_objects / 20);
+       if (progress)
+               fprintf(stderr, "Deltifying %d objects.\n", nr_objects);
 
        while (--i >= 0) {
                struct object_entry *entry = list[i];
@@ -378,15 +710,29 @@ static void find_deltas(struct object_entry **list, int window, int depth)
                char type[10];
                int j;
 
-               if (progress && i <= eye_candy) {
-                       eye_candy -= nr_objects / 20;
-                       fputc('.', stderr);
+               processed++;
+               if (progress) {
+                       unsigned percent = processed * 100 / nr_objects;
+                       if (percent != last_percent || progress_update) {
+                               fprintf(stderr, "%4u%% (%u/%u) done\r",
+                                       percent, processed, nr_objects);
+                               progress_update = 0;
+                               last_percent = percent;
+                       }
                }
+
+               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;
@@ -404,6 +750,9 @@ static void find_deltas(struct object_entry **list, int window, int depth)
                        idx = 0;
        }
 
+       if (progress)
+               fputc('\n', stderr);
+
        for (i = 0; i < window; ++i)
                free(array[i].data);
        free(array);
@@ -412,15 +761,9 @@ static void find_deltas(struct object_entry **list, int window, int depth)
 static void prepare_pack(int window, int depth)
 {
        get_object_details();
-
-       if (progress)
-               fprintf(stderr, "Packing %d objects", nr_objects);
        sorted_by_type = create_sorted_list(type_size_sort);
        if (window && depth)
                find_deltas(sorted_by_type, window+1, depth);
-       if (progress)
-               fputc('\n', stderr);
-       write_pack_file();
 }
 
 static int reuse_cached_pack(unsigned char *sha1, int pack_to_stdout)
@@ -443,8 +786,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))
@@ -483,10 +827,6 @@ int main(int argc, char **argv)
        int window = 10, depth = 10, pack_to_stdout = 0;
        struct object_entry **list;
        int i;
-       struct timeval prev_tv;
-       int eye_candy = 0;
-       int eye_candy_incr = 500;
-
 
        setup_git_directory();
 
@@ -524,6 +864,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;
@@ -539,30 +883,25 @@ int main(int argc, char **argv)
                usage(pack_usage);
 
        prepare_packed_git();
+
        if (progress) {
+               struct itimerval v;
+               v.it_interval.tv_sec = 1;
+               v.it_interval.tv_usec = 0;
+               v.it_value = v.it_interval;
+               signal(SIGALRM, progress_interval);
+               setitimer(ITIMER_REAL, &v, NULL);
                fprintf(stderr, "Generating pack...\n");
-               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)) {
+               if (progress_update) {
                        fprintf(stderr, "Counting objects...%d\r", nr_objects);
-                       if (eye_candy && (50 <= eye_candy_incr)) {
-                               struct timeval tv;
-                               int time_diff;
-                               gettimeofday(&tv, NULL);
-                               time_diff = (tv.tv_sec - prev_tv.tv_sec);
-                               time_diff <<= 10;
-                               time_diff += (tv.tv_usec - prev_tv.tv_usec);
-                               if ((1 << 9) < time_diff)
-                                       eye_candy_incr += 50;
-                               else if (50 < eye_candy_incr)
-                                       eye_candy_incr -= 50;
-                       }
-                       eye_candy += eye_candy_incr;
+                       progress_update = 0;
                }
                if (get_sha1_hex(line, sha1))
                        die("expected sha1, got garbage:\n %s", line);
@@ -594,10 +933,21 @@ int main(int argc, char **argv)
                ;
        else {
                prepare_pack(window, depth);
+               if (progress && pack_to_stdout) {
+                       /* the other end usually displays progress itself */
+                       struct itimerval v = {{0,},};
+                       setitimer(ITIMER_REAL, &v, NULL);
+                       signal(SIGALRM, SIG_IGN );
+                       progress_update = 0;
+               }
+               write_pack_file();
                if (!pack_to_stdout) {
                        write_index_file();
                        puts(sha1_to_hex(object_list_sha1));
                }
        }
+       if (progress)
+               fprintf(stderr, "Total %d, written %d (delta %d), reused %d (delta %d)\n",
+                       nr_objects, 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..ee5f15a 100644 (file)
@@ -255,8 +255,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) {
index 1d799f7..1fd5b79 100644 (file)
@@ -247,6 +247,7 @@ static void link_alt_odb_entries(const char *alt, const char *ep, int sep,
                for ( ; cp < ep && *cp != sep; cp++)
                        ;
                if (last != cp) {
+                       struct stat st;
                        struct alternate_object_database *alt;
                        /* 43 = 40-byte + 2 '/' + terminating NUL */
                        int pfxlen = cp - last;
@@ -269,9 +270,19 @@ static void link_alt_odb_entries(const char *alt, const char *ep, int sep,
                        }
                        else
                                memcpy(ent->base, last, pfxlen);
+
                        ent->name = ent->base + pfxlen + 1;
-                       ent->base[pfxlen] = ent->base[pfxlen + 3] = '/';
-                       ent->base[entlen-1] = 0;
+                       ent->base[pfxlen + 3] = '/';
+                       ent->base[pfxlen] = ent->base[entlen-1] = 0;
+
+                       /* Detect cases where alternate disappeared */
+                       if (stat(ent->base, &st) || !S_ISDIR(st.st_mode)) {
+                               error("object directory %s does not exist; "
+                                     "check .git/objects/info/alternates.",
+                                     ent->base);
+                               goto bad;
+                       }
+                       ent->base[pfxlen] = '/';
 
                        /* Prevent the common mistake of listing the same
                         * thing twice, or object directory itself.
@@ -552,7 +563,9 @@ static void prepare_packed_git_one(char *objdir, int local)
        len = strlen(path);
        dir = opendir(path);
        if (!dir) {
-               fprintf(stderr, "unable to open object pack directory: %s: %s\n", path, strerror(errno));
+               if (errno != ENOENT)
+                       error("unable to open object pack directory: %s: %s\n",
+                             path, strerror(errno));
                return;
        }
        path[len++] = '/';
@@ -828,6 +841,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/t3600-rm.sh b/t/t3600-rm.sh
new file mode 100755 (executable)
index 0000000..cabfadd
--- /dev/null
@@ -0,0 +1,60 @@
+#!/bin/sh
+#
+# Copyright (c) 2006 Carl D. Worth
+#
+
+test_description='Test of the various options to git-rm.'
+
+. ./test-lib.sh
+
+# Setup some files to be removed, some with funny characters
+touch -- foo bar baz 'space embedded' 'tab     embedded' 'newline
+embedded' -q
+git-add -- foo bar baz 'space embedded' 'tab   embedded' 'newline
+embedded' -q
+git-commit -m "add files"
+
+test_expect_success \
+    'Pre-check that foo exists and is in index before git-rm foo' \
+    '[ -f foo ] && git-ls-files --error-unmatch foo'
+
+test_expect_success \
+    'Test that git-rm foo succeeds' \
+    'git-rm foo'
+
+test_expect_success \
+    'Post-check that foo exists but is not in index after git-rm foo' \
+    '[ -f foo ] && ! git-ls-files --error-unmatch foo'
+
+test_expect_success \
+    'Pre-check that bar exists and is in index before "git-rm -f bar"' \
+    '[ -f bar ] && git-ls-files --error-unmatch bar'
+
+test_expect_success \
+    'Test that "git-rm -f bar" succeeds' \
+    'git-rm -f bar'
+
+test_expect_success \
+    'Post-check that bar does not exist and is not in index after "git-rm -f bar"' \
+    '! [ -f bar ] && ! git-ls-files --error-unmatch bar'
+
+test_expect_success \
+    'Test that "git-rm -- -q" succeeds (remove a file that looks like an option)' \
+    'git-rm -- -q'
+
+test_expect_success \
+    "Test that \"git-rm -f\" succeeds with embedded space, tab, or newline characters." \
+    "git-rm -f 'space embedded' 'tab   embedded' 'newline
+embedded'"
+
+chmod u-w .
+test_expect_failure \
+    'Test that "git-rm -f" fails if its rm fails' \
+    'git-rm -f baz'
+chmod u+w .
+
+test_expect_success \
+    'When the rm in "git-rm -f" fails, it should not remove the file from the index' \
+    'git-ls-files --error-unmatch baz'
+
+test_done
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;
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;