GIT 0.99.9e
authorJunio C Hamano <junkio@cox.net>
Mon, 7 Nov 2005 02:57:40 +0000 (18:57 -0800)
committerJunio C Hamano <junkio@cox.net>
Mon, 7 Nov 2005 02:57:40 +0000 (18:57 -0800)
Signed-off-by: Junio C Hamano <junkio@cox.net>
288 files changed:
.gitignore
Documentation/Makefile
Documentation/asciidoc.conf [new file with mode: 0644]
Documentation/cvs-migration.txt
Documentation/diff-format.txt
Documentation/diff-options.txt
Documentation/diffcore.txt
Documentation/git-add.txt
Documentation/git-am.txt [new file with mode: 0644]
Documentation/git-apply.txt
Documentation/git-applymbox.txt
Documentation/git-applypatch.txt
Documentation/git-archimport.txt
Documentation/git-bisect.txt
Documentation/git-branch.txt
Documentation/git-cat-file.txt
Documentation/git-check-ref-format.txt [new file with mode: 0644]
Documentation/git-checkout-index.txt
Documentation/git-checkout.txt
Documentation/git-cherry-pick.txt
Documentation/git-cherry.txt
Documentation/git-clone-pack.txt
Documentation/git-clone.txt
Documentation/git-commit-tree.txt
Documentation/git-commit.txt
Documentation/git-convert-objects.txt
Documentation/git-count-objects.txt
Documentation/git-cvsimport.txt
Documentation/git-daemon.txt
Documentation/git-diff-files.txt
Documentation/git-diff-helper.txt [deleted file]
Documentation/git-diff-index.txt
Documentation/git-diff-stages.txt
Documentation/git-diff-tree.txt
Documentation/git-diff.txt
Documentation/git-export.txt [deleted file]
Documentation/git-fetch-pack.txt
Documentation/git-fetch.txt
Documentation/git-fmt-merge-msg.txt [new file with mode: 0644]
Documentation/git-format-patch.txt
Documentation/git-fsck-objects.txt
Documentation/git-get-tar-commit-id.txt
Documentation/git-grep.txt
Documentation/git-hash-object.txt
Documentation/git-http-fetch.txt
Documentation/git-http-push.txt [new file with mode: 0644]
Documentation/git-index-pack.txt [new file with mode: 0644]
Documentation/git-init-db.txt
Documentation/git-local-fetch.txt
Documentation/git-log.txt
Documentation/git-ls-files.txt
Documentation/git-ls-remote.txt
Documentation/git-ls-tree.txt
Documentation/git-mailinfo.txt
Documentation/git-mailsplit.txt
Documentation/git-merge-base.txt
Documentation/git-merge-index.txt
Documentation/git-merge-one-file.txt
Documentation/git-merge.txt
Documentation/git-mktag.txt
Documentation/git-mv.txt [new file with mode: 0644]
Documentation/git-name-rev.txt [new file with mode: 0644]
Documentation/git-octopus.txt
Documentation/git-pack-objects.txt
Documentation/git-parse-remote.txt
Documentation/git-patch-id.txt
Documentation/git-peek-remote.txt
Documentation/git-prune-packed.txt
Documentation/git-prune.txt
Documentation/git-pull.txt
Documentation/git-push.txt
Documentation/git-read-tree.txt
Documentation/git-rebase.txt
Documentation/git-receive-pack.txt
Documentation/git-relink.txt
Documentation/git-rename.txt
Documentation/git-repack.txt
Documentation/git-request-pull.txt
Documentation/git-reset.txt
Documentation/git-resolve.txt
Documentation/git-rev-list.txt
Documentation/git-rev-parse.txt
Documentation/git-rev-tree.txt [deleted file]
Documentation/git-revert.txt
Documentation/git-send-email.txt
Documentation/git-send-pack.txt
Documentation/git-sh-setup.txt
Documentation/git-shell.txt [new file with mode: 0644]
Documentation/git-shortlog.txt
Documentation/git-show-branch.txt
Documentation/git-show-index.txt
Documentation/git-ssh-fetch.txt
Documentation/git-ssh-upload.txt
Documentation/git-status.txt
Documentation/git-stripspace.txt
Documentation/git-svnimport.txt [new file with mode: 0644]
Documentation/git-symbolic-ref.txt [new file with mode: 0644]
Documentation/git-tag.txt
Documentation/git-tar-tree.txt
Documentation/git-unpack-file.txt
Documentation/git-unpack-objects.txt
Documentation/git-update-index.txt
Documentation/git-update-ref.txt [new file with mode: 0644]
Documentation/git-update-server-info.txt
Documentation/git-upload-pack.txt
Documentation/git-var.txt
Documentation/git-verify-pack.txt
Documentation/git-verify-tag.txt
Documentation/git-whatchanged.txt
Documentation/git-write-tree.txt
Documentation/git.txt
Documentation/gitk.txt
Documentation/glossary.txt
Documentation/hooks.txt
Documentation/howto/using-topic-branches.txt
Documentation/install-webdoc.sh
Documentation/merge-pull-opts.txt [new file with mode: 0644]
Documentation/merge-strategies.txt [new file with mode: 0644]
Documentation/pull-fetch-param.txt
Documentation/repository-layout.txt
Documentation/technical/trivial-merge.txt
Documentation/tutorial.txt
INSTALL
Makefile
README
apply.c
arm/sha1.c [new file with mode: 0644]
arm/sha1.h [new file with mode: 0644]
arm/sha1_arm.S [new file with mode: 0644]
cache.h
check-ref-format.c [new file with mode: 0644]
checkout-index.c
clone-pack.c
cmd-rename.sh
commit-tree.c
commit.c
compat/mmap.c [new file with mode: 0644]
config.c [new file with mode: 0644]
connect.c
convert-objects.c
copy.c [new file with mode: 0644]
ctype.c [new file with mode: 0644]
daemon.c
date.c
debian/changelog
debian/control
debian/git-arch.files [new file with mode: 0644]
debian/git-cvs.files [new file with mode: 0644]
debian/git-doc.files [new file with mode: 0644]
debian/git-email.files [new file with mode: 0644]
debian/git-svn.files [new file with mode: 0644]
debian/rules
diff-files.c
diff-helper.c [deleted file]
diff-index.c
diff-stages.c
diff-tree.c
diff.c
diff.h
diffcore-rename.c
diffcore.h
entry.c
environment.c [new file with mode: 0644]
export.c [deleted file]
fetch-pack.c
fetch.c
fetch.h
fsck-objects.c
git-add.sh
git-am.sh [new file with mode: 0755]
git-applymbox.sh
git-applypatch.sh
git-archimport.perl
git-bisect.sh
git-branch.sh
git-checkout.sh
git-clone.sh
git-commit.sh
git-core.spec.in
git-count-objects.sh
git-cvsimport.perl
git-diff.sh
git-external-diff-script [deleted file]
git-fetch.sh
git-fmt-merge-msg.perl [new file with mode: 0755]
git-format-patch.sh
git-ls-remote.sh
git-merge-ours.sh [new file with mode: 0755]
git-merge-recursive.py
git-merge-resolve.sh
git-merge.sh
git-mv.perl [new file with mode: 0755]
git-octopus.sh
git-parse-remote.sh
git-prune.sh
git-pull.sh
git-push.sh
git-rebase.sh
git-rename.perl
git-repack.sh
git-reset.sh
git-resolve.sh
git-revert.sh
git-sh-setup.sh
git-shortlog.perl
git-status.sh
git-svnimport.perl [new file with mode: 0755]
git-tag.sh
git-verify-tag.sh
git.sh
gitMergeCommon.py
gitk
http-fetch.c
http-push.c [new file with mode: 0644]
ident.c
index-pack.c [new file with mode: 0644]
index.c
init-db.c
local-fetch.c
ls-files.c
ls-tree.c
mailinfo.c
mailsplit.c
mozilla-sha1/sha1.c
mozilla-sha1/sha1.h
name-rev.c [new file with mode: 0644]
pack-objects.c
patch-id.c
path.c
peek-remote.c
prune-packed.c
quote.c
quote.h
read-cache.c
read-tree.c
receive-pack.c
refs.c
rev-list.c
rev-parse.c
rev-tree.c [deleted file]
rsh.c
send-pack.c
server-info.c
setup.c
sha1_file.c
sha1_name.c
shell.c [new file with mode: 0644]
show-branch.c
ssh-fetch.c
symbolic-ref.c [new file with mode: 0644]
t/Makefile
t/diff-lib.sh
t/t0000-basic.sh
t/t1000-read-tree-m-3way.sh
t/t1200-tutorial.sh [new file with mode: 0644]
t/t3001-ls-files-others-exclude.sh
t/t3002-ls-files-dashpath.sh [new file with mode: 0755]
t/t3010-ls-files-killed-modified.sh [new file with mode: 0755]
t/t3010-ls-files-killed.sh [deleted file]
t/t3101-ls-tree-dirname.sh [new file with mode: 0644]
t/t3300-funny-names.sh [new file with mode: 0755]
t/t4000-diff-format.sh
t/t4001-diff-rename.sh
t/t4004-diff-rename-symlink.sh
t/t4005-diff-rename-2.sh
t/t4009-diff-rename-4.sh
t/t4102-apply-rename.sh
t/t5000-tar-tree.sh
t/t5300-pack-object.sh
t/t5400-send-pack.sh
t/t5500-fetch-pack.sh [new file with mode: 0644]
t/t5501-old-fetch-and-upload.sh [new file with mode: 0755]
t/t6001-rev-list-merge-order.sh
t/t6002-rev-list-bisect.sh
t/t6003-rev-list-topo-order.sh
t/test-lib.sh
tag.c
tag.h
tar-tree.c
templates/.gitignore
templates/Makefile
tree-diff.c [new file with mode: 0644]
update-index.c
update-ref.c [new file with mode: 0644]
upload-pack.c
usage.c
var.c
verify-pack.c
index 938669fc09f5796c6a877aebbd6b5a229ff3c8a5..3edf6b41afe453130a3e7215991c6e2cd8dc57d7 100644 (file)
@@ -1,5 +1,6 @@
 git
 git-add
+git-am
 git-apply
 git-applymbox
 git-applypatch
@@ -7,9 +8,11 @@ git-archimport
 git-bisect
 git-branch
 git-cat-file
+git-check-ref-format
 git-checkout
 git-checkout-index
 git-cherry
+git-cherry-pick
 git-clone
 git-clone-pack
 git-commit
@@ -20,19 +23,20 @@ git-cvsimport
 git-daemon
 git-diff
 git-diff-files
-git-diff-helper
 git-diff-index
 git-diff-stages
 git-diff-tree
-git-export
 git-fetch
 git-fetch-pack
+git-findtags
+git-fmt-merge-msg
 git-format-patch
 git-fsck-objects
 git-get-tar-commit-id
 git-grep
 git-hash-object
 git-http-fetch
+git-index-pack
 git-init-db
 git-local-fetch
 git-log
@@ -46,10 +50,13 @@ git-merge-base
 git-merge-index
 git-merge-octopus
 git-merge-one-file
+git-merge-ours
 git-merge-recursive
 git-merge-resolve
 git-merge-stupid
 git-mktag
+git-name-rev
+git-mv
 git-octopus
 git-pack-objects
 git-parse-remote
@@ -70,11 +77,11 @@ git-reset
 git-resolve
 git-rev-list
 git-rev-parse
-git-rev-tree
 git-revert
 git-send-email
 git-send-pack
 git-sh-setup
+git-shell
 git-shortlog
 git-show-branch
 git-show-index
@@ -84,11 +91,14 @@ git-ssh-push
 git-ssh-upload
 git-status
 git-stripspace
+git-svnimport
+git-symbolic-ref
 git-tag
 git-tar-tree
 git-unpack-file
 git-unpack-objects
 git-update-index
+git-update-ref
 git-update-server-info
 git-upload-pack
 git-var
@@ -101,3 +111,6 @@ git-core-*/?*
 *.dsc
 *.deb
 git-core.spec
+*.exe
+libgit.a
+*.o
index 01fad8ea5d0867fa32b03a0e9b77383201b6a41b..741f14cfad214cfc69aad0f5965917bd2f0ef280 100644 (file)
@@ -17,14 +17,14 @@ DOC_HTML += $(patsubst %,%.html,$(ARTICLES) $(SP_ARTICLES))
 DOC_MAN1=$(patsubst %.txt,%.1,$(MAN1_TXT))
 DOC_MAN7=$(patsubst %.txt,%.7,$(MAN7_TXT))
 
-prefix=$(HOME)
+prefix?=$(HOME)
 bin=$(prefix)/bin
 mandir=$(prefix)/man
 man1=$(mandir)/man1
 man7=$(mandir)/man7
 # DESTDIR=
 
-INSTALL=install
+INSTALL?=install
 
 #
 # Please note that there is a minor bug in asciidoc.
@@ -52,20 +52,28 @@ install: man
 # 'include' dependencies
 $(patsubst %.txt,%.1,$(wildcard git-diff-*.txt)): \
        diff-format.txt diff-options.txt
+$(patsubst %.txt,%.html,$(wildcard git-diff-*.txt)): \
+       diff-format.txt diff-options.txt
+
 $(patsubst %,%.1,git-fetch git-pull git-push): pull-fetch-param.txt
+$(patsubst %,%.html,git-fetch git-pull git-push): pull-fetch-param.txt
+
+$(patsubst %,%.1,git-merge git-pull): merge-pull-opts.txt
+$(patsubst %,%.html,git-merge git-pull): merge-pull-opts.txt
+
 git.7: ../README
 
 clean:
        rm -f *.xml *.html *.1 *.7 howto-index.txt howto/*.html
 
 %.html : %.txt
-       asciidoc -b xhtml11 -d manpage $<
+       asciidoc -b xhtml11 -d manpage -f asciidoc.conf $<
 
 %.1 %.7 : %.xml
        xmlto man $<
 
 %.xml : %.txt
-       asciidoc -b docbook -d manpage $<
+       asciidoc -b docbook -d manpage -f asciidoc.conf $<
 
 git.html: git.txt ../README
 
diff --git a/Documentation/asciidoc.conf b/Documentation/asciidoc.conf
new file mode 100644 (file)
index 0000000..fa0877d
--- /dev/null
@@ -0,0 +1,26 @@
+## gitlink: macro
+#
+# Usage: gitlink:command[manpage-section]
+#
+# Note, {0} is the manpage section, while {target} is the command.
+#
+# Show GIT link as: <command>(<section>); if section is defined, else just show
+# the command.
+
+[attributes]
+caret=^
+
+ifdef::backend-docbook[]
+[gitlink-inlinemacro]
+{0%{target}}
+{0#<citerefentry>}
+{0#<refentrytitle>{target}</refentrytitle><manvolnum>{0}</manvolnum>}
+{0#</citerefentry>}
+endif::backend-docbook[]
+
+ifdef::backend-xhtml11[]
+[gitlink-inlinemacro]
+<a href="{target}.html">{target}{0?({0})}</a>
+endif::backend-xhtml11[]
+
+
index 390a72392b2aee91facd0c019474391ed64a646a..57436f00783a7f6682431b24a5719effd04faf32 100644 (file)
@@ -1,6 +1,5 @@
-Git for CVS users
+git for CVS users
 =================
-v0.99.5, Aug 2005
 
 Ok, so you're a CVS user. That's ok, it's a treatable condition, and the
 first step to recovery is admitting you have a problem. The fact that
@@ -8,7 +7,7 @@ you are reading this file means that you may be well on that path
 already.
 
 The thing about CVS is that it absolutely sucks as a source control
-manager, and you'll thus be happy with almost anything else. Git,
+manager, and you'll thus be happy with almost anything else. git,
 however, may be a bit 'too' different (read: "good") for your taste, and
 does a lot of things differently. 
 
@@ -16,7 +15,7 @@ One particular suckage of CVS is very hard to work around: CVS is
 basically a tool for tracking 'file' history, while git is a tool for
 tracking 'project' history.  This sometimes causes problems if you are
 used to doing very strange things in CVS, in particular if you're doing
-things like making branches of just a subset of the project.  Git can't
+things like making branches of just a subset of the project.  git can't
 track that, since git never tracks things on the level of an individual
 file, only on the whole project level. 
 
@@ -24,7 +23,7 @@ The good news is that most people don't do that, and in fact most sane
 people think it's a bug in CVS that makes it tag (and check in changes)
 one file at a time.  So most projects you'll ever see will use CVS
 'as if' it was sane.  In which case you'll find it very easy indeed to
-move over to Git. 
+move over to git. 
 
 First off: this is not a git tutorial. See
 link:tutorial.html[Documentation/tutorial.txt] for how git
@@ -33,7 +32,7 @@ and notes on converting from CVS to git.
 
 Second: CVS has the notion of a "repository" as opposed to the thing
 that you're actually working in (your working directory, or your
-"checked out tree").  Git does not have that notion at all, and all git
+"checked out tree").  git does not have that notion at all, and all git
 working directories 'are' the repositories.  However, you can easily
 emulate the CVS model by having one special "global repository", which
 people can synchronize with.  See details later, but in the meantime
@@ -50,7 +49,7 @@ gone through the git tutorial, and generally familiarized yourself with
 how to commit stuff etc in git) is to create a git'ified version of your
 CVS archive.
 
-Happily, that's very easy indeed. Git will do it for you, although git
+Happily, that's very easy indeed. git will do it for you, although git
 will need the help of a program called "cvsps":
 
        http://www.cobite.com/cvsps/
@@ -136,7 +135,7 @@ technically possible, and there are at least two specialized scripts out
 there that can be used to get equivalent information (see the git
 mailing list archives for details). 
 
-Git has a couple of alternatives, though, that you may find sufficient
+git has a couple of alternatives, though, that you may find sufficient
 or even superior depending on your use.  One is called "git-whatchanged"
 (for obvious reasons) and the other one is called "pickaxe" ("a tool for
 the software archeologist"). 
@@ -209,7 +208,7 @@ show anything for commits that do not touch this "if" statement.
 Also, in the original context, the same statement might have
 appeared at first in a different file and later the file was
 renamed to "a-file.c".  CVS annotate would not help you to go
-back across such a rename, but GIT would still help you in such
+back across such a rename, but git would still help you in such
 a situation.  For that, you can give the -C flag to
 git-diff-tree, like this:
 
@@ -229,10 +228,10 @@ does rename or copy would not show in the output, and if the
 "o-file.c", it would find the commit that changed the statement
 when it was in "o-file.c".
 
-[ BTW, the current versions of "git-diff-tree -C" is not eager
+NOTE: The current version of "git-diff-tree -C" is not eager
   enough to find copies, and it will miss the fact that a-file.c
   was created by copying o-file.c unless o-file.c was somehow
-  changed in the same commit.]
+  changed in the same commit.
 
 You can use the --pickaxe-all flag in addition to the -S flag.
 This causes the differences from all the files contained in
@@ -243,6 +242,6 @@ that contain this changed "if" statement:
                nitfol();
        }' --pickaxe-all
 
-[ Side note.  This option is called "--pickaxe-all" because -S
+NOTE: This option is called "--pickaxe-all" because -S
   option is internally called "pickaxe", a tool for software
-  archaeologists.]
+  archaeologists.
index 424e75a1c2d4747226ac1b55caac90b544f8f273..d1d0d2d3dc8760a8030313de3dd14a942d0fc2bf 100644 (file)
@@ -1,8 +1,8 @@
 The output format from "git-diff-index", "git-diff-tree" and
 "git-diff-files" are very similar.
 
-These commands all compare two sets of things; what are
-compared are different:
+These commands all compare two sets of things; what is 
+compared differs:
 
 git-diff-index <tree-ish>::
         compares the <tree-ish> and the files on the filesystem.
@@ -46,7 +46,7 @@ That is, from the left to the right:
 . path for "dst"; only exists for C or R.
 . an LF or a NUL when '-z' option is used, to terminate the record.
 
-<sha1> is shown as all 0's if new is a file on the filesystem
+<sha1> is shown as all 0's if a file is new on the filesystem
 and it is out of sync with the cache.
 
 Example:
@@ -55,6 +55,11 @@ Example:
 :100644 100644 5be4a4...... 000000...... M file.c
 ------------------------------------------------
 
+When `-z` option is not used, TAB, LF, and backslash characters
+in pathnames are represented as `\t`, `\n`, and `\\`,
+respectively.
+
+
 Generating patches with -p
 --------------------------
 
@@ -62,8 +67,7 @@ When "git-diff-index", "git-diff-tree", or "git-diff-files" are run
 with a '-p' option, they do not produce the output described above;
 instead they produce a patch file.
 
-The patch generation can be customized at two levels.  This
-customization also applies to "git-diff-helper".
+The patch generation can be customized at two levels.
 
 1. When the environment variable 'GIT_EXTERNAL_DIFF' is not set,
    these commands internally invoke "diff" like this:
@@ -92,7 +96,7 @@ For a path that is added, removed, or modified,
 where:
 
      <old|new>-file:: are files GIT_EXTERNAL_DIFF can use to read the
-                     contents of <old|ne>,
+                     contents of <old|new>,
      <old|new>-hex:: are the 40-hexdigit SHA1 hashes,
      <old|new>-mode:: are the octal representation of the file modes.
 
@@ -107,7 +111,7 @@ For a path that is unmerged, 'GIT_EXTERNAL_DIFF' is called with 1
 parameter, <path>.
 
 
-Git specific extension to diff format
+git specific extension to diff format
 -------------------------------------
 
 What -p option produces is slightly different from the
@@ -122,12 +126,11 @@ The `a/` and `b/` filenames are the same unless rename/copy is
 involved.  Especially, even for a creation or a deletion,
 `/dev/null` is _not_ used in place of `a/` or `b/` filenames.
 +
-When rename/copy is involved, `file1` and `file2` shows the
+When rename/copy is involved, `file1` and `file2` show the
 name of the source file of the rename/copy and the name of
 the file that rename/copy produces, respectively.
 
-2.   It is followed by extended header lines that are one or
-     more of:
+2.   It is followed by one or more extended header lines:
 
        old mode <mode>
        new mode <mode>
@@ -139,3 +142,7 @@ the file that rename/copy produces, respectively.
        rename to <path>
        similarity index <number>
        dissimilarity index <number>
+       index <hash>..<hash> <mode>
+
+3.  TAB, LF, and backslash characters in pathnames are
+    represented as `\t`, `\n`, and `\\`, respectively.
index 48cf93cc388fa354da58d00af7aba3020ca478d0..32005b03f3bee40413d7ff0d33e143de85b194c6 100644 (file)
@@ -4,19 +4,14 @@
 -u::
        Synonym for "-p".
 
--r::
-       Look recursively in subdirectories; this flag does not
-       mean anything to commands other than "git-diff-tree";
-       other diff commands always look at all the subdirectories.
-
 -z::
        \0 line termination on output
 
 --name-only::
        Show only names of changed files.
 
---name-only-z::
-       Same as --name-only, but terminate lines with NUL.
+--name-status::
+       Show only names and status of changed files.
 
 -B::
        Break complete rewrite changes into pairs of delete and create.
        Detect copies as well as renames.
 
 --find-copies-harder::
-       By default, -C option finds copies only if the original
-       file of the copy was modified in the same changeset for
-       performance reasons.  This flag makes the command
+       For performance reasons, by default, -C option finds copies only 
+       if the original file of the copy was modified in the same 
+       changeset.  This flag makes the command
        inspect unmodified files as candidates for the source of
        copy.  This is a very expensive operation for large
        projects, so use it with caution.
 
+-l<num>::
+       -M and -C options require O(n^2) processing time where n
+       is the number of potential rename/copy targets.  This
+       option prevents rename/copy detection from running if
+       the number of rename/copy targets exceeds the specified
+       number.
+
 -S<string>::
-       Look for differences that contains the change in <string>.
+       Look for differences that contain the change in <string>.
 
 --pickaxe-all::
        When -S finds a change, show all the changes in that
-       changeset, not just the files that contains the change
+       changeset, not just the files that contain the change
        in <string>.
 
 -O<orderfile>::
index 1908b92f3804c9d0e5951ee8050c1de312fb30a4..cb4e562004e58439a0055d9ed6a6bdab249dfcdc 100644 (file)
@@ -6,13 +6,12 @@ June 2005
 Introduction
 ------------
 
-The diff commands git-diff-index, git-diff-files, and
-git-diff-tree can be told to manipulate differences they find
-in unconventional ways before showing diff(1) output.  The
-manipulation is collectively called "diffcore transformation".
-This short note describes what they are and how to use them to
-produce diff outputs that are easier to understand than the
-conventional kind.
+The diff commands git-diff-index, git-diff-files, git-diff-tree, and
+git-diff-stages can be told to manipulate differences they find in
+unconventional ways before showing diff(1) output.  The manipulation
+is collectively called "diffcore transformation".  This short note
+describes what they are and how to use them to produce diff outputs
+that are easier to understand than the conventional kind.
 
 
 The chain of operation
@@ -29,7 +28,10 @@ files:
  - git-diff-files compares contents of the index file and the
    working directory;
 
- - git-diff-tree compares contents of two "tree" objects.
+ - git-diff-tree compares contents of two "tree" objects;
+
+ - git-diff-stages compares contents of blobs at two stages in an
+   unmerged index file.
 
 In all of these cases, the commands themselves compare
 corresponding paths in the two sets of files.  The result of
@@ -65,14 +67,23 @@ format sections of the manual for git-diff-\* commands) or
 diff-patch format.
 
 
-diffcore-pathspec
------------------
+diffcore-pathspec: For Ignoring Files Outside Our Consideration
+---------------------------------------------------------------
 
 The first transformation in the chain is diffcore-pathspec, and
 is controlled by giving the pathname parameters to the
 git-diff-* commands on the command line.  The pathspec is used
 to limit the world diff operates in.  It removes the filepairs
-outside the specified set of pathnames.
+outside the specified set of pathnames.  E.g. If the input set 
+of filepairs included:
+
+------------------------------------------------
+:100644 100644 bcd1234... 0123456... M junkfile
+------------------------------------------------
+
+but the command invocation was "git-diff-files myfile", then the
+junkfile entry would be removed from the list because only "myfile"
+is under consideration.
 
 Implementation note.  For performance reasons, git-diff-tree
 uses the pathname parameters on the command line to cull set of
@@ -80,8 +91,8 @@ filepairs it feeds the diffcore mechanism itself, and does not
 use diffcore-pathspec, but the end result is the same.
 
 
-diffcore-break
---------------
+diffcore-break: For Splitting Up "Complete Rewrites"
+----------------------------------------------------
 
 The second transformation in the chain is diffcore-break, and is
 controlled by the -B option to the git-diff-* commands.  This is
@@ -115,8 +126,8 @@ the original is used), and can be customized by giving a number
 after "-B" option (e.g. "-B75" to tell it to use 75%).
 
 
-diffcore-rename
----------------
+diffcore-rename: For Detection Renames and Copies
+-------------------------------------------------
 
 This transformation is used to detect renames and copies, and is
 controlled by the -M option (to detect renames) and the -C option
@@ -136,16 +147,16 @@ merges these filepairs and creates:
 :100644 100644 0123456... 0123456... R100 fileX file0
 ------------------------------------------------
 
-When the "-C" option is used, the original contents of modified
-files and contents of unchanged files are considered as
-candidates of the source files in rename/copy operation, in
-addition to the deleted files.  If the input were like these
-filepairs, that talk about a modified file fileY and a newly
+When the "-C" option is used, the original contents of modified files,
+and deleted files (and also unmodified files, if the
+"\--find-copies-harder" option is used) are considered as candidates
+of the source files in rename/copy operation.  If the input were like
+these filepairs, that talk about a modified file fileY and a newly
 created file file0:
 
 ------------------------------------------------
 :100644 100644 0123456... 1234567... M fileY
-:000000 100644 0000000... 0123456... A file0
+:000000 100644 0000000... bcd3456... A file0
 ------------------------------------------------
 
 the original contents of fileY and the resulting contents of
@@ -154,14 +165,14 @@ changed to:
 
 ------------------------------------------------
 :100644 100644 0123456... 1234567... M fileY
-:100644 100644 0123456... 0123456... C100 fileY file0
+:100644 100644 0123456... bcd3456... C100 fileY file0
 ------------------------------------------------
 
 In both rename and copy detection, the same "extent of changes"
 algorithm used in diffcore-break is used to determine if two
 files are "similar enough", and can be customized to use
-similarity score different from the default 50% by giving a
-number after "-M" or "-C" option (e.g. "-M8" to tell it to use
+a similarity score different from the default of 50% by giving a
+number after the "-M" or "-C" option (e.g. "-M8" to tell it to use
 8/10 = 80%).
 
 Note.  When the "-C" option is used with `\--find-copies-harder`
@@ -173,11 +184,11 @@ git-diff-\* commands can detect copies only if the file that was
 copied happened to have been modified in the same changeset.
 
 
-diffcore-merge-broken
----------------------
+diffcore-merge-broken: For Putting "Complete Rewrites" Back Together
+--------------------------------------------------------------------
 
 This transformation is used to merge filepairs broken by
-diffcore-break, and were not transformed into rename/copy by
+diffcore-break, and not transformed into rename/copy by
 diffcore-rename, back into a single modification.  This always
 runs when diffcore-break is used.
 
@@ -206,17 +217,17 @@ like these:
 * -B/60 (the same as above, since diffcore-break defaults to 50%).
 
 Note that earlier implementation left a broken pair as a separate
-creation and deletion patches.  This was unnecessary hack and
+creation and deletion patches.  This was an unnecessary hack and
 the latest implementation always merges all the broken pairs
 back into modifications, but the resulting patch output is
-formatted differently to still let the reviewing easier for such
+formatted differently for easier review in case of such
 a complete rewrite by showing the entire contents of old version
 prefixed with '-', followed by the entire contents of new
 version prefixed with '+'.
 
 
-diffcore-pickaxe
-----------------
+diffcore-pickaxe: For Detecting Addition/Deletion of Specified String
+---------------------------------------------------------------------
 
 This transformation is used to find filepairs that represent
 changes that touch a specified string, and is controlled by the
@@ -230,7 +241,7 @@ string appeared in this changeset".  It also checks for the
 opposite case that loses the specified string.
 
 When `\--pickaxe-all` is not in effect, diffcore-pickaxe leaves
-only such filepairs that touches the specified string in its
+only such filepairs that touch the specified string in its
 output.  When `\--pickaxe-all` is used, diffcore-pickaxe leaves all
 filepairs intact if there is such a filepair, or makes the
 output empty otherwise.  The latter behaviour is designed to
@@ -238,27 +249,27 @@ make reviewing of the changes in the context of the whole
 changeset easier.
 
 
-diffcore-order
---------------
+diffcore-order: For Sorting the Output Based on Filenames
+---------------------------------------------------------
 
 This is used to reorder the filepairs according to the user's
 (or project's) taste, and is controlled by the -O option to the
 git-diff-* commands.
 
-This takes a text file each of whose line is a shell glob
+This takes a text file each of whose lines is a shell glob
 pattern.  Filepairs that match a glob pattern on an earlier line
 in the file are output before ones that match a later line, and
 filepairs that do not match any glob pattern are output last.
 
-As an example, typical orderfile for the core GIT probably
+As an example, a typical orderfile for the core git probably
 would look like this:
 
 ------------------------------------------------
-    README
-    Makefile
-    Documentation
-    *.h
-    *.c
-    t
+README
+Makefile
+Documentation
+*.h
+*.c
+t
 ------------------------------------------------
 
index beb450075bf6db4877ebfae9f2b120a7fc7f375e..4cae41267a903d21e6745b3fb45963b6da4dad04 100644 (file)
@@ -3,21 +3,63 @@ git-add(1)
 
 NAME
 ----
-git-add - Add files to the cache.
+git-add - Add files to the index file.
 
 SYNOPSIS
 --------
-'git-add' <file>...
+'git-add' [-n] [-v] <file>...
 
 DESCRIPTION
 -----------
-A simple wrapper to git-update-index to add files to the cache for people used
-to do "cvs add".
+A simple wrapper for git-update-index to add files to the index,
+for people used to do "cvs add".
+
 
 OPTIONS
 -------
 <file>...::
-       Files to add to the cache.
+       Files to add to the index.
+
+-n::
+        Don't actually add the file(s), just show if they exist.
+
+-v::
+        Be verbose.
+
+
+DISCUSSION
+----------
+
+The list of <file> given to the command is fed to `git-ls-files`
+command to list files that are not registerd 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 add all files in it and its subdirectories;
+
+. Giving the name of a file that is already in index does not
+  run `git-update-index` on that path.
+
+
+EXAMPLES
+--------
+git-add Documentation/\\*.txt::
+
+       Adds all `\*.txt` files that are not in the index under
+       `Documentation` directory and its subdirectories.
++
+Note that the asterisk `\*` is quoted from the shell in this
+example; this lets the command to include the files from
+subdirectories of `Documentation/` directory.
+
+git-add git-*.sh::
+
+       Adds all git-*.sh scripts that are not in the index.
+       Because this example lets shell expand the asterisk
+       (i.e. you are listing the files explicitly), it does not
+       add `subdir/git-foo.sh` to the index.
+
 
 Author
 ------
@@ -29,5 +71,5 @@ Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
 
 GIT
 ---
-Part of the link:git.html[git] suite
+Part of the gitlink:git[7] suite
 
diff --git a/Documentation/git-am.txt b/Documentation/git-am.txt
new file mode 100644 (file)
index 0000000..e4df4a4
--- /dev/null
@@ -0,0 +1,88 @@
+git-am(1)
+=========
+
+NAME
+----
+git-am - Apply a series of patches in a mailbox
+
+
+SYNOPSIS
+--------
+'git-am' [--signoff] [--dotest=<dir>] [--utf8] [--3way] <mbox>...
+'git-am' [--skip]
+
+DESCRIPTION
+-----------
+Splits mail messages in a mailbox into commit log message,
+authorship information and patches, and applies them to the
+current branch.
+
+OPTIONS
+-------
+--signoff::
+       Add `Signed-off-by:` line to the commit message, using
+       the committer identity of yourself.
+
+--dotest=<dir>::
+       Instead of `.dotest` directory, use <dir> as a working
+       area to store extracted patches.
+
+--utf8, --keep::
+       Pass `--utf8` and `--keep` flags to `git-mailinfo` (see
+       gitlink:git-mailinfo[1]).
+
+--3way::
+       When the patch does not apply cleanly, fall back on
+       3-way merge, if the patch records the identity of blobs
+       it is supposed to apply to, and we have those blobs
+       locally.
+
+--skip::
+       Skip the current patch.  This is only meaningful when
+       restarting an aborted patch.
+
+--interactive::
+       Run interactively, just like git-applymbox.
+
+
+DISCUSSION
+----------
+
+When initially invoking it, you give it names of the mailboxes
+to crunch.  Upon seeing the first patch that does not apply, it
+aborts in the middle, just like 'git-applymbox' does.  You can
+recover from this in one of two ways:
+
+. skip the current one by re-running the command with '--skip'
+  option.
+
+. hand resolve the conflict in the working directory, run 'git
+  diff HEAD' to extract the merge result into a patch form and
+  replacing the patch in .dotest/patch file.  After doing this,
+  run `git-reset --hard HEAD` to bring the working tree to the
+  state before half-applying the patch, then re-run the command
+  without any options.
+
+The command refuses to process new mailboxes while `.dotest`
+directory exists, so if you decide to start over from scratch,
+run `rm -f .dotest` before running the command with mailbox
+names.
+
+
+SEE ALSO
+--------
+gitlink:git-applymbox[1], gitlink:git-applypatch[1].
+
+
+Author
+------
+Written by Junio C Hamano <junkio@cox.net>
+
+Documentation
+--------------
+Documentation by Petr Baudis, Junio C Hamano and the git-list <git@vger.kernel.org>.
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
index 391d6f5c2ffcde93091676514000910817d140df..eb8f90683724c69f1a47dfcc53d93e1d6accb3d0 100644 (file)
@@ -1,19 +1,18 @@
 git-apply(1)
 ============
-v0.1, June 2005
 
 NAME
 ----
-git-apply - Apply patch on a GIT index file and a work tree
+git-apply - Apply patch on a git index file and a work tree
 
 
 SYNOPSIS
 --------
-'git-apply' [--no-merge] [--stat] [--summary] [--check] [--index] [--show-files] [--apply] [<patch>...]
+'git-apply' [--stat] [--numstat] [--summary] [--check] [--index] [--apply] [--index-info] [-z] [<patch>...]
 
 DESCRIPTION
 -----------
-Reads supplied diff output and applies it on a GIT index file
+Reads supplied diff output and applies it on a git index file
 and a work tree.
 
 OPTIONS
@@ -22,15 +21,16 @@ OPTIONS
        The files to read patch from.  '-' can be used to read
        from the standard input.
 
---no-merge::
-       The default mode of operation is the merge behaviour
-       which is not implemented yet.  This flag explicitly
-       tells the program not to use the merge behaviour.
-
 --stat::
        Instead of applying the patch, output diffstat for the
        input.  Turns off "apply".
 
+--numstat::
+       Similar to \--stat, but shows number of added and
+       deleted lines in decimal notation and pathname without
+       abbreviation, to make it more machine friendly.  Turns
+       off "apply".
+
 --summary::
        Instead of applying the patch, output a condensed
        summary of information obtained from git diff extended
@@ -51,8 +51,19 @@ OPTIONS
        up-to-date, it is flagged as an error.  This flag also
        causes the index file to be updated.
 
---show-files::
-       Show summary of files that are affected by the patch.
+--index-info::
+       Newer git-diff output has embedded 'index information'
+       for each blob to help identify the original version that
+       the patch applies to.  When this flag is given, and if
+       the original version of the blob is available locally,
+       outputs information about them to the standard output.
+
+-z::
+       When showing the index information, do not munge paths,
+       but use NUL terminated machine readable format.  Without
+       this flag, the pathnames output will have TAB, LF, and
+       backslash characters replaced with `\t`, `\n`, and `\\`,
+       respectively.
 
 --apply::
        If you use any of the options marked ``Turns off
@@ -72,5 +83,5 @@ Documentation by Junio C Hamano
 
 GIT
 ---
-Part of the link:git.html[git] suite
+Part of the gitlink:git[7] suite
 
index f6d857cda47e26956d5da682ec99c5be28d1d1df..f74c6a49b3c5d52ed662a83832c7d06915fac65b 100644 (file)
@@ -8,7 +8,7 @@ git-applymbox - Apply a series of patches in a mailbox
 
 SYNOPSIS
 --------
-'git-applymbox' [-u] [-k] [-q] ( -c .dotest/<num> | <mbox> ) [ <signoff> ]
+'git-applymbox' [-u] [-k] [-q] [-m] ( -c .dotest/<num> | <mbox> ) [ <signoff> ]
 
 DESCRIPTION
 -----------
@@ -22,7 +22,7 @@ OPTIONS
 -q::
        Apply patches interactively.  The user will be given
        opportunity to edit the log message and the patch before
-       attempting to apply patch in each e-mail message.
+       attempting to apply it.
 
 -k::
        Usually the program 'cleans up' the Subject: header line
@@ -33,6 +33,14 @@ OPTIONS
        munging, and is most useful when used to read back 'git
        format-patch --mbox' output.
 
+-m::
+       Patches are applied with `git-apply` command, and unless
+       it cleanly applies without fuzz, the processing fails.
+       With this flag, if a tree that the patch applies cleanly
+       is found in a repository, the patch is applied to the
+       tree and then a 3-way merge between the resulting tree
+       and the current tree.
+
 -u::
        By default, the commit log message, author name and
        author email are taken from the e-mail without any
@@ -67,7 +75,7 @@ OPTIONS
 
 SEE ALSO
 --------
-link:git-applypatch.html[git-applypatch].
+gitlink:git-am[1], gitlink:git-applypatch[1].
 
 
 Author
@@ -80,5 +88,5 @@ Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
 
 GIT
 ---
-Part of the link:git.html[git] suite
+Part of the gitlink:git[7] suite
 
index b8946321d33f571ef59ed01581bc63dbe9a9147f..5b9037de9f90626d9715de49e39f9708fdc25c75 100644 (file)
@@ -30,7 +30,7 @@ OPTIONS
 <patch>::
        The patch to apply.
 
-<info>:
+<info>::
        Author and subject information extracted from e-mail,
        used on "author" line and as the first line of the
        commit log message.
@@ -46,5 +46,5 @@ Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
 
 GIT
 ---
-Part of the link:git.html[git] suite
+Part of the gitlink:git[7] suite
 
index fa0779b39e3bbceddeda348d1f155bb7cebe4ad3..fcda0125af92ff9c3d72d711a78d23118787608c 100644 (file)
@@ -3,7 +3,7 @@ git-archimport(1)
 
 NAME
 ----
-git-archimport - Import an Arch repository into GIT
+git-archimport - Import an Arch repository into git
 
 
 SYNOPSIS
@@ -20,31 +20,34 @@ it will just import it as a regular commit. If it can find it, it will mark it
 as a merge whenever possible (see discussion below). 
 
 The script expects you to provide the key roots where it can start the import 
-from an 'initial import' or 'tag' type of Arch commit. It will follow and import 
-new branches within the provided roots. 
+from an 'initial import' or 'tag' type of Arch commit. It will follow and 
+import new branches within the provided roots. 
 
 It expects to be dealing with one project only. If it sees 
-branches that have different roots, it will refuse to run. In that case, edit your
-<archive/branch> parameters to define clearly the scope of the import. 
+branches that have different roots, it will refuse to run. In that case, 
+edit your <archive/branch> parameters to define clearly the scope of the 
+import. 
 
-`git-archimport` uses `tla` extensively in the background to access the Arch repository.
+`git-archimport` uses `tla` extensively in the background to access the 
+Arch repository.
 Make sure you have a recent version of `tla` available in the path. `tla` must
 know about the repositories you pass to `git-archimport`. 
 
 For the initial import `git-archimport` expects to find itself in an empty 
 directory. To follow the development of a project that uses Arch, rerun 
-`git-archimport` with the same parameters as the initial import to perform incremental imports.
+`git-archimport` with the same parameters as the initial import to perform 
+incremental imports.
 
 MERGES
 ------
-Patch merge data from Arch is used to mark merges in GIT as well. GIT 
+Patch merge data from Arch is used to mark merges in git as well. git 
 does not care much about tracking patches, and only considers a merge when a
 branch incorporates all the commits since the point they forked. The end result
-is that GIT will have a good idea of how far branches have diverged. So the 
+is that git will have a good idea of how far branches have diverged. So the 
 import process does lose some patch-trading metadata.
 
 Fortunately, when you try and merge branches imported from Arch, 
-GIT will find a good merge base, and it has a good chance of identifying 
+git will find a good merge base, and it has a good chance of identifying 
 patches that have been traded out-of-sequence between the branches. 
 
 OPTIONS
@@ -78,5 +81,5 @@ Documentation by Junio C Hamano, Martin Langhoff and the git-list <git@vger.kern
 
 GIT
 ---
-Part of the link:git.html[git] suite
+Part of the gitlink:git[7] suite
 
index f10df535a5b31d195bd53b46c460ec0891f1d0ea..39fa665d9d6ee7700adc7bb84cb09c517b38cfa0 100644 (file)
@@ -76,7 +76,7 @@ During the bisection process, you can say
 
 to see the currently remaining suspects in `gitk`.
 
-The good/bad you told the command is logged, and `git bisect
+The good/bad input is logged, and `git bisect
 log` shows what you have done so far.  You can truncate its
 output somewhere and save it in a file, and run
 
@@ -91,10 +91,10 @@ 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 link:git.html[git] suite
+Part of the gitlink:git[7] suite
 
index 914c0e6a5a4d600d161a9d4e56b593dbffc4030c..a7121a4c632da21a6e00f5f53d136969ac8b9804 100644 (file)
@@ -23,7 +23,7 @@ OPTIONS
        The name of the branch to create.
 
 start-point::
-       Where to make the branch; defaults to HEAD.
+       Where to create the branch; defaults to HEAD.
 
 Author
 ------
@@ -35,5 +35,5 @@ Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
 
 GIT
 ---
-Part of the link:git.html[git] suite
+Part of the gitlink:git[7] suite
 
index 2131a29993405742fcc67ed81a578c06bf950f44..ab4dcae21caa190a41c19ec24b4dc2d71a5a73fb 100644 (file)
@@ -1,6 +1,5 @@
 git-cat-file(1)
 ===============
-v0.1, May 2005
 
 NAME
 ----
@@ -32,7 +31,7 @@ OPTIONS
 
 <type>::
        Typically this matches the real type of <object> but asking
-       for a type that can trivially dereferenced from the given
+       for a type that can trivially be dereferenced from the given
        <object> is also permitted.  An example is to ask for a
        "tree" with <object> being a commit object that contains it,
        or to ask for a "blob" with <object> being a tag object that
@@ -57,5 +56,5 @@ Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel
 
 GIT
 ---
-Part of the link:git.html[git] suite
+Part of the gitlink:git[7] suite
 
diff --git a/Documentation/git-check-ref-format.txt b/Documentation/git-check-ref-format.txt
new file mode 100644 (file)
index 0000000..636e951
--- /dev/null
@@ -0,0 +1,50 @@
+git-check-ref-format(1)
+=======================
+
+NAME
+----
+git-check-ref-format - Make sure ref name is well formed.
+
+SYNOPSIS
+--------
+'git-check-ref-format' <refname>
+
+DESCRIPTION
+-----------
+Checks if a given 'refname' is acceptable, and exits non-zero if
+it is not.
+
+A reference is used in git to specify branches and tags.  A
+branch head is stored under `$GIT_DIR/refs/heads` directory, and
+a tag is stored under `$GIT_DIR/refs/tags` directory.  git
+imposes the following rules on how refs are named:
+
+. It could be named hierarchically (i.e. separated with slash
+  `/`), but each of its component cannot begin with a dot `.`;
+
+. It cannot have two consecutive dots `..` anywhere;
+
+. It cannot have ASCII control character (i.e. bytes whose
+  values are lower than \040, or \177 `DEL`), space, tilde `~`,
+  caret `{caret}`, or colon `:` anywhere;
+
+. It cannot end with a slash `/`.
+
+These rules makes it easy for shell script based tools to parse
+refnames, and also avoids ambiguities in certain refname
+expressions (see gitlink:git-rev-parse[1]).  Namely:
+
+. double-dot `..` are often used as in `ref1..ref2`, and in some
+  context this notation means `{caret}ref1 ref2` (i.e. not in
+  ref1 and in ref2).
+
+. tilde `~` and caret `{caret}` are used to introduce postfix
+  'nth parent' and 'peel onion' operation.
+
+. colon `:` is used as in `srcref:dstref` to mean "use srcref\'s
+  value and store it in dstref" in fetch and push operations.
+
+
+GIT
+---
+Part of the gitlink:git[7] suite
index bb15214c989b73f37c79fef027900393aed419fd..589dc9ad12943767c73658904d54a574d6592f54 100644 (file)
@@ -1,6 +1,5 @@
 git-checkout-index(1)
 =====================
-v0.1, May 2005
 
 NAME
 ----
@@ -30,8 +29,8 @@ OPTIONS
        forces overwrite of existing files
 
 -a::
-       checks out all files in the cache (will then continue to
-       process listed files).
+       checks out all files in the cache.  Cannot be used
+       together with explicit filenames.
 
 -n::
        Don't checkout new files, only refresh files already checked
@@ -44,15 +43,9 @@ OPTIONS
 --::
        Do not interpret any more arguments as options.
 
-Note that the order of the flags matters:
+The order of the flags used to matter, but not anymore.
 
-     git-checkout-index -a -f file.c
-
-will first check out all files listed in the cache (but not overwrite
-any old ones), and then force-checkout `file.c` a second time (ie that
-one *will* overwrite any old contents with the same filename).
-
-Also, just doing "git-checkout-index" does nothing. You probably meant
+Just doing "git-checkout-index" does nothing. You probably meant
 "git-checkout-index -a". And if you want to force it, you want
 "git-checkout-index -f -a".
 
@@ -78,12 +71,12 @@ scripting!).
 The prefix ability basically makes it trivial to use
 git-checkout-index as an "export as tree" function. Just read the
 desired tree into the index, and do a
-  
+
         git-checkout-index --prefix=git-export-dir/ -a
-  
+
 and git-checkout-index will "export" the cache into the specified
 directory.
-  
+
 NOTE The final "/" is important. The exported name is literally just
 prefixed with the specified string, so you can also do something like
 
@@ -102,5 +95,5 @@ Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel
 
 GIT
 ---
-Part of the link:git.html[git] suite
+Part of the gitlink:git[7] suite
 
index 3bc7743ef61ddb9cf4476432f5f44e941414d29a..b7bb1b4c74ad3f0e06ddf23487b7735cb321952b 100644 (file)
@@ -7,12 +7,24 @@ git-checkout - Checkout and switch to a branch.
 
 SYNOPSIS
 --------
-'git-checkout' [-f] [-b <new_branch>] [<branch>]
+'git-checkout' [-f] [-b <new_branch>] [<branch>] [<paths>...]
 
 DESCRIPTION
 -----------
-Updates the index and working tree to reflect the specified branch,
-<branch>. Updates HEAD to be <branch> or, if specified, <new_branch>.
+
+When <paths> are not given, this command switches branches, by
+updating the index and working tree to reflect the specified
+branch, <branch>, and updating HEAD to be <branch> or, if
+specified, <new_branch>.
+
+When <paths> are given, this command does *not* switch
+branches.  It updates the named paths in the working tree from
+the index file (i.e. it runs `git-checkout-index -f -u`).  In
+this case, `-f` and `-b` options are meaningless and giving
+either of them results in an error.  <branch> argument can be
+used to specify a specific tree-ish to update the index for the
+given paths before updating the working tree.
+
 
 OPTIONS
 -------
@@ -29,6 +41,30 @@ OPTIONS
        Branch to checkout; may be any object ID that resolves to a
        commit. Defaults to HEAD.
 
+
+EXAMPLE
+-------
+
+The following sequence checks out the `master` branch, reverts
+the `Makefile` to two revisions back, deletes hello.c by
+mistake, and gets it back from the index.
+
+------------
+$ git checkout master
+$ git checkout master~2 Makefile
+$ rm -f hello.c
+$ git checkout hello.c
+------------
+
+If you have an unfortunate branch that is named `hello.c`, the
+last step above would be confused as an instruction to switch to
+that branch.  You should instead write:
+
+------------
+$ git checkout -- hello.c
+------------
+
+
 Author
 ------
 Written by Linus Torvalds <torvalds@osdl.org>
@@ -39,5 +75,5 @@ Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
 
 GIT
 ---
-Part of the link:git.html[git] suite
+Part of the gitlink:git[7] suite
 
index 38a48752489719871308f1c0b182074b872b8590..26e04677972f410cbec93c72ba2210c28d1f39bc 100644 (file)
@@ -1,6 +1,5 @@
 git-cherry-pick(1)
 ==================
-v0.99.5 Aug 2005
 
 NAME
 ----
@@ -22,7 +21,7 @@ OPTIONS
        Commit to cherry-pick.
 
 -r::
-       Usuall the command appends which commit was
+       Usually the command appends which commit was
        cherry-picked after the original commit message when
        making a commit.  This option, '--replay', causes it to
        use the original commit message intact.  This is useful
@@ -38,9 +37,9 @@ OPTIONS
        option is used, your working tree does not have to match
        the HEAD commit.  The cherry-pick is done against the
        beginning state of your working tree.
-
-       This is useful when cherry-picking more than one commits'
-       effect to your working tree in a row.
++
+This is useful when cherry-picking more than one commits'
+effect to your working tree in a row.
 
 
 Author
@@ -53,5 +52,5 @@ Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
 
 GIT
 ---
-Part of the link:git.html[git] suite
+Part of the gitlink:git[7] suite
 
index 9ff7bc25c1e8ccdf8204414536ad4373bd868b89..af87966e511184c4e29b3b07fac01e0df53a618a 100644 (file)
@@ -38,5 +38,5 @@ Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
 
 GIT
 ---
-Part of the link:git.html[git] suite
+Part of the gitlink:git[7] suite
 
index 0dc89a907286c47cd00fa50535ec2a2e1c9dfc65..cfc7b62f31b6470b9d9011b49d73d135a0623dd9 100644 (file)
@@ -1,6 +1,5 @@
 git-clone-pack(1)
 =================
-v0.1, July 2005
 
 NAME
 ----
@@ -9,27 +8,23 @@ git-clone-pack - Clones a repository by receiving packed objects.
 
 SYNOPSIS
 --------
-'git-clone-pack' [-q] [--exec=<git-upload-pack>] [<host>:]<directory> [<head>...]
+'git-clone-pack' [--exec=<git-upload-pack>] [<host>:]<directory> [<head>...]
 
 DESCRIPTION
 -----------
 Clones a repository into the current repository by invoking
 'git-upload-pack', possibly on the remote host via ssh, in
-the named repository, and invoking 'git-unpack-objects' locally
-to receive the pack.
+the named repository, and stores the sent pack in the local
+repository.
 
 OPTIONS
 -------
--q::
-       Pass '-q' flag to 'git-unpack-objects'; this makes the
-       cloning process less verbose.
-
 --exec=<git-upload-pack>::
        Use this to specify the path to 'git-upload-pack' on the
-       remote side, if is not found on your $PATH.
-       Installations of sshd ignores the user's environment
+       remote side, if it is not found on your $PATH.
+       Installations of sshd ignore the user's environment
        setup scripts for login shells (e.g. .bash_profile) and
-       your privately installed GIT may not be found on the system
+       your privately installed git may not be found on the system
        default $PATH.  Another workaround suggested is to set
        up your $PATH in ".bashrc", but this flag is for people
        who do not want to pay the overhead for non-interactive
@@ -61,5 +56,5 @@ Documentation by Junio C Hamano.
 
 GIT
 ---
-Part of the link:git.html[git] suite
+Part of the gitlink:git[7] suite
 
index cff87ea1dc0f3f02ddebb8f1c1375cb2b3ef9cd1..fefd2985f3f1424ddaff24fed6b57a85b86f8acc 100644 (file)
@@ -1,6 +1,5 @@
 git-clone(1)
 ============
-v0.1, July 2005
 
 NAME
 ----
@@ -9,14 +8,30 @@ git-clone - Clones a repository.
 
 SYNOPSIS
 --------
-'git clone' [-l] [-u <upload-pack>] [-q] <repository> <directory>
+'git-clone' [-l [-s]] [-q] [-n] [-u <upload-pack>] <repository> <directory>
 
 DESCRIPTION
 -----------
-Clones a repository into a newly created directory.
+Clones a repository into a newly created directory.  All remote
+branch heads are copied under `$GIT_DIR/refs/heads/`, except
+that the remote `master` is also copied to `origin` branch.
+
+In addition, `$GIT_DIR/remotes/origin` file is set up to have
+this line:
+
+       Pull: master:origin
+
+This is to help the typical workflow of working off of the
+remote `master` branch.  Every time `git pull` without argument
+is run, the progress on the remote `master` branch is tracked by
+copying it into the local `origin` branch, and merged into the
+branch you are currently working on.  Remote branches other than
+`master` are also added there to be tracked.
+
 
 OPTIONS
 -------
+--local::
 -l::
        When the repository to clone from is on a local machine,
        this flag bypasses normal "git aware" transport
@@ -25,10 +40,23 @@ OPTIONS
        The files under .git/objects/ directory are hardlinked
        to save space when possible.
 
+--shared::
+-s::
+       When the repository to clone is on the local machine,
+       instead of using hard links, automatically setup
+       .git/objects/info/alternatives to share the objects
+       with the source repository.  The resulting repository
+       starts out without any object of its own.
+
+--quiet::
 -q::
        Operate quietly.  This flag is passed to "rsync" and
        "git-clone-pack" commands when given.
 
+-n::
+       No checkout of HEAD is performed after the clone is complete.
+
+--upload-pack <upload-pack>::
 -u <upload-pack>::
        When given, and the repository to clone from is handled
        by 'git-clone-pack', '--exec=<upload-pack>' is passed to
@@ -37,14 +65,13 @@ OPTIONS
 
 <repository>::
        The (possibly remote) repository to clone from.  It can
-       be an "rsync://host/dir" URL, an "http://host/dir" URL,
-       or [<host>:]/dir notation that is used by 'git-clone-pack'.
-       Currently http transport is not supported.
+       be any URL git-fetch supports.
 
 <directory>::
        The name of a new directory to be cloned into.  It is an
        error to specify an existing directory.
 
+
 Author
 ------
 Written by Linus Torvalds <torvalds@osdl.org>
@@ -56,5 +83,5 @@ Documentation by Junio C Hamano.
 
 GIT
 ---
-Part of the link:git.html[git] suite
+Part of the gitlink:git[7] suite
 
index 55366688136010ff079a502c99a6af9e9b12dc13..b64cd6a84b7f08164b55445999a12460a3d3c52c 100644 (file)
@@ -1,6 +1,5 @@
 git-commit-tree(1)
 ==================
-v0.1, May 2005
 
 NAME
 ----
@@ -36,7 +35,7 @@ OPTIONS
        An existing tree object
 
 -p <parent commit>::
-       Each '-p' indicates the id of a parent commit object.
+       Each '-p' indicates the id of a parent commit object.
        
 
 Commit Information
@@ -49,8 +48,8 @@ A commit encapsulates:
 - committer name and email and the commit time.
 
 If not provided, "git-commit-tree" uses your name, hostname and domain to
-provide author and committer info. This can be overridden using the
-following environment variables.
+provide author and committer info. This can be overridden by
+either `.git/config` file, or using the following environment variables.
 
        GIT_AUTHOR_NAME
        GIT_AUTHOR_EMAIL
@@ -58,11 +57,18 @@ following environment variables.
        GIT_COMMITTER_NAME
        GIT_COMMITTER_EMAIL
 
-(nb <,> and '\n's are stripped)
+(nb "<", ">" and "\n"s are stripped)
+
+In `.git/config` file, the following items are used:
+
+       [user]
+               name = "Your Name"
+               email = "your@email.address.xz"
 
 A commit comment is read from stdin (max 999 chars). If a changelog
-entry is not provided via '<' redirection, "git-commit-tree" will just wait
-for one to be entered and terminated with ^D
+entry is not provided via "<" redirection, "git-commit-tree" will just wait
+for one to be entered and terminated with ^D.
+
 
 Diagnostics
 -----------
@@ -75,7 +81,7 @@ Your sysadmin must hate you!::
 
 See Also
 --------
-link:git-write-tree.html[git-write-tree]
+gitlink:git-write-tree[1]
 
 
 Author
@@ -88,5 +94,5 @@ Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel
 
 GIT
 ---
-Part of the link:git.html[git] suite
+Part of the gitlink:git[7] suite
 
index afcf6ef85866df9dd6a2fd28e133cd9686648037..1edc278c64827b47d8b672106433417510342392 100644 (file)
@@ -1,6 +1,5 @@
 git-commit(1)
 =============
-v0.99.4, Aug 2005
 
 NAME
 ----
@@ -8,7 +7,7 @@ git-commit - Record your changes
 
 SYNOPSIS
 --------
-'git commit' [-a] [-s] [-v] [(-c | -C) <commit> | -F <file> | -m <msg>] [-e] <file>...
+'git-commit' [-a] [-s] [-v] [(-c | -C) <commit> | -F <file> | -m <msg>] [-e] <file>...
 
 DESCRIPTION
 -----------
@@ -69,4 +68,4 @@ Junio C Hamano <junkio@cox.net>
 
 GIT
 ---
-Part of the link:git.html[git] suite
+Part of the gitlink:git[7] suite
index b1bc1c5752c99aedb82828c983247d12dd032231..b1220c06e193ddf3942da6e5e0faf91368d0886a 100644 (file)
@@ -1,10 +1,9 @@
 git-convert-objects(1)
 ======================
-v0.1, May 2005
 
 NAME
 ----
-git-convert-objects - Converts old-style GIT repository
+git-convert-objects - Converts old-style git repository
 
 
 SYNOPSIS
@@ -13,7 +12,7 @@ SYNOPSIS
 
 DESCRIPTION
 -----------
-Converts old-style GIT repository to the latest format
+Converts old-style git repository to the latest format
 
 
 Author
@@ -26,5 +25,5 @@ Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel
 
 GIT
 ---
-Part of the link:git.html[git] suite
+Part of the gitlink:git[7] suite
 
index 824852c7a15461578ae0afa5bf6b86ebbf0ede7b..36888d98bfde3de96e11bc61eaa53c67f64b911b 100644 (file)
@@ -24,5 +24,5 @@ Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
 
 GIT
 ---
-Part of the link:git.html[git] suite
+Part of the gitlink:git[7] suite
 
index edf952122fea11897911d11170b2dd38037ebe36..4b62256a79034c4dcd60e2954e7f5c1b7f3caa81 100644 (file)
@@ -1,6 +1,5 @@
 git-cvsimport(1)
 ================
-v0.1, July 2005
 
 NAME
 ----
@@ -11,7 +10,7 @@ SYNOPSIS
 --------
 'git-cvsimport' [ -o <branch-for-HEAD> ] [ -h ] [ -v ]
                        [ -d <CVSROOT> ] [ -p <options-for-cvsps> ]
-                       [ -C <GIT_repository> ] [ -i ] [ -k ]
+                       [ -C <git_repository> ] [ -i ] [ -P <file> ] [ -k ]
                        [ -s <subst> ] [ -m ] [ -M regex ] [ <CVS_module> ]
 
 
@@ -31,7 +30,7 @@ OPTIONS
        are supported.
 
 -C <target-dir>::
-        The GIT repository to import to.  If the directory doesn't
+        The git repository to import to.  If the directory doesn't
         exist, it will be created.  Default is the current directory.
 
 -i::
@@ -51,15 +50,19 @@ OPTIONS
        The 'HEAD' branch from CVS is imported to the 'origin' branch within
        the git repository, as 'HEAD' already has a special meaning for git.
        Use this option if you want to import into a different branch.
-
-       Use '-o master' for continuing an import that was initially done by
-       the old cvs2git tool.
++
+Use '-o master' for continuing an import that was initially done by
+the old cvs2git tool.
 
 -p <options-for-cvsps>::
        Additional options for cvsps.
        The options '-u' and '-A' are implicit and should not be used here.
++
+If you need to pass multiple options, separate them with a comma.
 
-       If you need to pass multiple options, separate them with a comma.
+-P:: <cvsps-output-file>
+       Instead of calling cvsps, read the provided cvsps output file. Useful
+       for debugging or when cvsps is being handled outside cvsimport.
 
 -m::    
        Attempt to detect merges based on the commit message. This option
@@ -105,5 +108,5 @@ Documentation by Matthias Urlichs <smurf@smurf.noris.de>.
 
 GIT
 ---
-Part of the link:git.html[git] suite
+Part of the gitlink:git[7] suite
 
index 5fc45f215835610c7780ada5b5dc36f590b836da..67c5f22a7d8179e18be9f0e7cac59866ee5dd530 100644 (file)
@@ -3,11 +3,12 @@ git-daemon(1)
 
 NAME
 ----
-git-daemon - A really simple server for GIT repositories.
+git-daemon - A really simple server for git repositories.
 
 SYNOPSIS
 --------
-'git-daemon' [--inetd | --port=n]
+'git-daemon' [--verbose] [--syslog] [--inetd | --port=n] [--export-all]
+             [--timeout=n] [--init-timeout=n] [directory...]
 
 DESCRIPTION
 -----------
@@ -20,18 +21,42 @@ what directory to upload, and it verifies that the directory is ok.
 
 It verifies that the directory has the magic file "git-daemon-export-ok", and
 it will refuse to export any git directory that hasn't explicitly been marked
-for export this way.
+for export this way (unless the '--export-all' parameter is specified). If you
+pass some directory paths as 'git-daemon' arguments, you can further restrict
+the offers to a whitelist comprising of those.
 
 This is ideally suited for read-only updates, ie pulling from git repositories.
 
 OPTIONS
 -------
+--export-all::
+       Allow pulling from all directories that look like GIT repositories
+       (have the 'objects' subdirectory and a 'HEAD' file), even if they
+       do not have the 'git-daemon-export-ok' file.
+
 --inetd::
        Have the server run as an inetd service.
 
 --port::
        Listen on an alternative port.
 
+--init-timeout::
+       Timeout between the moment the connection is established and the
+       client request is received (typically a rather low value, since
+       that should be basically immediate).
+
+--timeout::
+       Timeout for specific client sub-requests. This includes the time
+       it takes for the server to process the sub-request and time spent
+       waiting for next client's request.
+
+--syslog::
+       Log to syslog instead of stderr. Note that this option does not imply
+       --verbose, thus by default only error conditions will be logged.
+
+--verbose::
+       Log details about the incoming connections and requested files.
+
 Author
 ------
 Written by Linus Torvalds <torvalds@osdl.org> and YOSHIFUJI Hideaki <yoshfuji@linux-ipv6.org>
@@ -42,5 +67,5 @@ Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
 
 GIT
 ---
-Part of the link:git.html[git] suite
+Part of the gitlink:git[7] suite
 
index 72c7a718f57470755baf8d42179477ddd5372645..e3873888f2236acb3ac04a6cda3784d61a2ce1a2 100644 (file)
@@ -1,6 +1,5 @@
 git-diff-files(1)
 =================
-v0.1, May 2005
 
 NAME
 ----
@@ -40,5 +39,5 @@ Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel
 
 GIT
 ---
-Part of the link:git.html[git] suite
+Part of the gitlink:git[7] suite
 
diff --git a/Documentation/git-diff-helper.txt b/Documentation/git-diff-helper.txt
deleted file mode 100644 (file)
index 26f5f39..0000000
+++ /dev/null
@@ -1,53 +0,0 @@
-git-diff-helper(1)
-==================
-v0.1, May 2005
-
-NAME
-----
-git-diff-helper - Generates patch format output for git-diff-*
-
-
-SYNOPSIS
---------
-'git-diff-helper' [-z] [-S<string>] [-O<orderfile>]
-
-DESCRIPTION
------------
-Reads output from "git-diff-index", "git-diff-tree" and "git-diff-files" and
-generates patch format output.
-
-OPTIONS
--------
--z::
-       \0 line termination on input
-
--S<string>::
-       Look for differences that contains the change in <string>.
-
---pickaxe-all::
-       When -S finds a change, show all the changes in that
-       changeset, not just the files that contains the change
-       in <string>.
-
--O<orderfile>::
-       Output the patch in the order specified in the
-       <orderfile>, which has one shell glob pattern per line.
-
-See Also
---------
-The section on generating patches in link:git-diff-index.html[git-diff-index]
-
-
-Author
-------
-Written by Junio C Hamano <junkio@cox.net>
-
-
-Documentation
---------------
-Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>.
-
-GIT
----
-Part of the link:git.html[git] suite
-
index 12bfcb32436f64975d2e3944fc2ddcaaafb982e2..2fc3eed710dbfeda8911097a8b5f493a220e5f3c 100644 (file)
@@ -1,6 +1,5 @@
 git-diff-index(1)
 =================
-v0.1, May 2005
 
 NAME
 ----
@@ -86,8 +85,8 @@ the more useful of the two in that what it does can't be emulated with
 a "git-write-tree" + "git-diff-tree". Thus that's the default mode.
 The non-cached version asks the question:
 
-   show me the differences between HEAD and the currently checked out
-   tree - index contents _and_ files that aren't up-to-date
+  show me the differences between HEAD and the currently checked out
+  tree - index contents _and_ files that aren't up-to-date
 
 which is obviously a very useful question too, since that tells you what
 you *could* commit. Again, the output matches the "git-diff-tree -r"
@@ -107,13 +106,13 @@ not up-to-date and may contain new stuff. The all-zero sha1 means that to
 get the real diff, you need to look at the object in the working directory
 directly rather than do an object-to-object diff.
 
-NOTE! As with other commands of this type, "git-diff-index" does not
+NOTE: As with other commands of this type, "git-diff-index" does not
 actually look at the contents of the file at all. So maybe
 `kernel/sched.c` hasn't actually changed, and it's just that you
 touched it. In either case, it's a note that you need to
 "git-upate-cache" it to make the cache be in sync.
 
-NOTE 2! You can have a mixture of files show up as "has been updated"
+NOTE: You can have a mixture of files show up as "has been updated"
 and "is still dirty in the working directory" together. You can always
 tell which file is in which state, since the "has been updated" ones
 show a valid sha1, and the "not in sync with the index" ones will
@@ -130,5 +129,5 @@ Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel
 
 GIT
 ---
-Part of the link:git.html[git] suite
+Part of the gitlink:git[7] suite
 
index 2084c7041f852759452b4c16026744eef9ce360e..28c60fc7e40a042c5cdbad22b103a9d93faeaae6 100644 (file)
@@ -1,6 +1,5 @@
 git-diff-stages(1)
 ==================
-v0.1, June 2005
 
 NAME
 ----
@@ -38,4 +37,4 @@ Documentation by Junio C Hamano.
 
 GIT
 ---
-Part of the link:git.html[git] suite
+Part of the gitlink:git[7] suite
index 816c592ebf13ddd18fdf4ab7f0dbb9c8311be2e1..f57c8d0d8191dae8be8db930ef8b38ddc9c42623 100644 (file)
@@ -1,6 +1,5 @@
 git-diff-tree(1)
 ================
-v0.1, May 2005
 
 NAME
 ----
@@ -9,7 +8,7 @@ git-diff-tree - Compares the content and mode of blobs found via two tree object
 
 SYNOPSIS
 --------
-'git-diff-tree' [--stdin] [-m] [-s] [-v] [--pretty] [-t] [<common diff options>] <tree-ish> [<tree-ish>] [<path>...]
+'git-diff-tree' [--stdin] [-m] [-s] [-v] [--pretty] [-t] [-r] [--root] [<common diff options>] <tree-ish> [<tree-ish>] [<path>...]
 
 DESCRIPTION
 -----------
@@ -34,6 +33,9 @@ include::diff-options.txt[]
        Note that this parameter does not provide any wildcard or regexp
        features.
 
+-r::
+        recurse into sub-trees
+
 -t::
        show tree entry itself as well as subtrees.  Implies -r.
 
@@ -101,16 +103,18 @@ An example of normal usage is:
 which tells you that the last commit changed just one file (it's from
 this one:
 
-  commit 3c6f7ca19ad4043e9e72fa94106f352897e651a8
-  tree 5319e4d609cdd282069cc4dce33c1db559539b03
-  parent b4e628ea30d5ab3606119d2ea5caeab141d38df7
-  author Linus Torvalds <torvalds@ppc970.osdl.org> Sat Apr 9 12:02:30 2005
-  committer Linus Torvalds <torvalds@ppc970.osdl.org> Sat Apr 9 12:02:30 2005
+-----------------------------------------------------------------------------
+commit 3c6f7ca19ad4043e9e72fa94106f352897e651a8
+tree 5319e4d609cdd282069cc4dce33c1db559539b03
+parent b4e628ea30d5ab3606119d2ea5caeab141d38df7
+author Linus Torvalds <torvalds@ppc970.osdl.org> Sat Apr 9 12:02:30 2005
+committer Linus Torvalds <torvalds@ppc970.osdl.org> Sat Apr 9 12:02:30 2005
 
-  Make "git-fsck-objects" print out all the root commits it finds.
+Make "git-fsck-objects" print out all the root commits it finds.
 
-  Once I do the reference tracking, I'll also make it print out all the
-  HEAD commits it finds, which is even more interesting.
+Once I do the reference tracking, I'll also make it print out all the
+HEAD commits it finds, which is even more interesting.
+-----------------------------------------------------------------------------
 
 in case you care).
 
@@ -129,5 +133,5 @@ Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel
 
 GIT
 ---
-Part of the link:git.html[git] suite
+Part of the gitlink:git[7] suite
 
index fc37ea7412ad5976ccaef0e198a19b5bf1ffe057..cadaf59455c994c5c08fbdb84f9c0ed1bc974325 100644 (file)
@@ -48,5 +48,5 @@ Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
 
 GIT
 ---
-Part of the link:git.html[git] suite
+Part of the gitlink:git[7] suite
 
diff --git a/Documentation/git-export.txt b/Documentation/git-export.txt
deleted file mode 100644 (file)
index d2d0dc4..0000000
+++ /dev/null
@@ -1,31 +0,0 @@
-git-export(1)
-=============
-v0.1, May 2005
-
-NAME
-----
-git-export - Exports each commit and a diff against each of its parents
-
-
-SYNOPSIS
---------
-'git-export' top [base]
-
-DESCRIPTION
------------
-Exports each commit and diff against each of its parents, between
-top and base.  If base is not specified it exports everything.
-
-
-Author
-------
-Written by Linus Torvalds <torvalds@osdl.org>
-
-Documentation
---------------
-Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>.
-
-GIT
----
-Part of the link:git.html[git] suite
-
index 59afa14a3098f8d3109b04ef30e366277b73c99c..ea6faab059810e8eef858d58c041b7d98a7de841 100644 (file)
@@ -1,6 +1,5 @@
 git-fetch-pack(1)
 =================
-v0.1, July 2005
 
 NAME
 ----
@@ -35,7 +34,7 @@ OPTIONS
        remote side, if is not found on your $PATH.
        Installations of sshd ignores the user's environment
        setup scripts for login shells (e.g. .bash_profile) and
-       your privately installed GIT may not be found on the system
+       your privately installed git may not be found on the system
        default $PATH.  Another workaround suggested is to set
        up your $PATH in ".bashrc", but this flag is for people
        who do not want to pay the overhead for non-interactive
@@ -66,4 +65,4 @@ Documentation by Junio C Hamano.
 
 GIT
 ---
-Part of the link:git.html[git] suite
+Part of the gitlink:git[7] suite
index 8c1cc0704836b1ebf046a538329a9c0e3e9dc399..73f8a99ffe6e3d6b09377226d3fb83fbf6494994 100644 (file)
@@ -1,6 +1,5 @@
 git-fetch(1)
 ============
-v0.99.5, Aug 2005
 
 NAME
 ----
@@ -26,6 +25,11 @@ OPTIONS
 -------
 include::pull-fetch-param.txt[]
 
+-a, \--append::
+       Append ref names and object names of fetched refs to the
+       existing contents of $GIT_DIR/FETCH_HEAD.  Without this
+       option old data in $GIT_DIR/FETCH_HEAD will be overwritten.
+
 -u, \--update-head-ok::
        By default 'git-fetch' refuses to update the head which
        corresponds to the current branch.  This flag disables the
@@ -33,15 +37,20 @@ include::pull-fetch-param.txt[]
        update the index and working directory, so use it with care.
 
 
+SEE ALSO
+--------
+gitlink:git-pull[1]
+
+
 Author
 ------
 Written by Linus Torvalds <torvalds@osdl.org> and
 Junio C Hamano <junkio@cox.net>
 
 Documentation
---------------
+-------------
 Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>.
 
 GIT
 ---
-Part of the link:git.html[git] suite
+Part of the gitlink:git[7] suite
diff --git a/Documentation/git-fmt-merge-msg.txt b/Documentation/git-fmt-merge-msg.txt
new file mode 100644 (file)
index 0000000..a70eb39
--- /dev/null
@@ -0,0 +1,39 @@
+git-fmt-merge-msg(1)
+====================
+
+NAME
+----
+git-fmt-merge-msg - Produce a merge commit message
+
+
+SYNOPSIS
+--------
+'git-fmt-merge-msg' <$GIT_DIR/FETCH_HEAD
+
+DESCRIPTION
+-----------
+Takes the list of merged objects on stdin and produces a suitable
+commit message to be used for the merge commit, usually to be
+passed as the '<merge-message>' argument of `git-merge`.
+
+This script is intended mostly for internal use by scripts
+automatically invoking `git-merge`.
+
+
+SEE ALSO
+--------
+gitlink:git-merge[1]
+
+
+Author
+------
+Written by Junio C Hamano <junkio@cox.net>
+
+Documentation
+--------------
+Documentation by Petr Baudis, Junio C Hamano and the git-list <git@vger.kernel.org>.
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
index b314c3a0f287084786c70bfaeee18027a8789f8e..7a3abec02e5b07533d11322b2ab9a3a89d2bd0d6 100644 (file)
@@ -8,7 +8,7 @@ git-format-patch - Prepare patches for e-mail submission.
 
 SYNOPSIS
 --------
-'git-format-patch' [-n][-o <dir>][-k][--mbox][--diff-options] <his> [<mine>]
+'git-format-patch' [-n][-o <dir>|--stdout][-k][--mbox][--diff-options] <his> [<mine>]
 
 DESCRIPTION
 -----------
@@ -54,6 +54,30 @@ OPTIONS
        concatenated together and fed to `git-applymbox`.
        Implies --author and --date.
 
+--stdout::
+       This flag generates the mbox formatted output to the
+       standard output, instead of saving them into a file per
+       patch and implies --mbox.
+
+
+EXAMPLES
+--------
+
+git-format-patch -k --stdout R1..R2 | git-am -3 -k::
+       Extract commits between revisions R1 and R2, and apply
+       them on top of the current branch using `git-am` to
+       cherry-pick them.
+
+git-format-patch origin::
+       Extract commits the current branch accumulated since it
+       pulled from origin the last time in a patch form for
+       e-mail submission.
+
+
+See Also
+--------
+gitlink:git-am[1], gitlink:git-send-email
+
 
 Author
 ------
@@ -65,5 +89,5 @@ Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
 
 GIT
 ---
-Part of the link:git.html[git] suite
+Part of the gitlink:git[7] suite
 
index 2b8640439b94fe41aac561b1449dbd1c4fdaf2f0..5dc9dbdd78af931e7c0a69d2b2425287e0614687 100644 (file)
@@ -1,6 +1,5 @@
 git-fsck-objects(1)
 ===================
-v0.1, May 2005
 
 NAME
 ----
@@ -19,9 +18,9 @@ OPTIONS
 -------
 <object>::
        An object to treat as the head of an unreachability trace.
-
-       If no objects are given, git-fsck-objects defaults to using the
-       index file and all SHA1 references in .git/refs/* as heads.
++
+If no objects are given, git-fsck-objects defaults to using the
+index file and all SHA1 references in .git/refs/* as heads.
 
 --unreachable::
        Print out objects that exist but that aren't readable from any
@@ -42,22 +41,22 @@ OPTIONS
        ($GIT_DIR/objects), making sure that it is consistent and
        complete without referring to objects found in alternate
        object pools listed in GIT_ALTERNATE_OBJECT_DIRECTORIES,
-       nor packed GIT archives found in $GIT_DIR/objects/pack;
+       nor packed git archives found in $GIT_DIR/objects/pack;
        cannot be used with --full.
 
 --full::
        Check not just objects in GIT_OBJECT_DIRECTORY
        ($GIT_DIR/objects), but also the ones found in alternate
        object pools listed in GIT_ALTERNATE_OBJECT_DIRECTORIES,
-       and in packed GIT archives found in $GIT_DIR/objects/pack
+       and in packed git archives found in $GIT_DIR/objects/pack
        and corresponding pack subdirectories in alternate
        object pools; cannot be used with --standalone.
 
 --strict::
        Enable more strict checking, namely to catch a file mode
        recorded with g+w bit set, which was created by older
-       versions of GIT.  Existing repositories, including the
-       Linux kernel, GIT itself, and sparse repository have old
+       versions of git.  Existing repositories, including the
+       Linux kernel, git itself, and sparse repository have old
        objects that triggers this check, but it is recommended
        to check new projects with this flag.
 
@@ -81,7 +80,7 @@ Any corrupt objects you will have to find in backups or other archives
 the hopes that somebody else has the object you have corrupted).
 
 Of course, "valid tree" doesn't mean that it wasn't generated by some
-evil person, and the end result might be crap. Git is a revision
+evil person, and the end result might be crap. git is a revision
 tracking system, not a quality assurance system ;)
 
 Extracted Diagnostics
@@ -128,7 +127,7 @@ GIT_OBJECT_DIRECTORY::
 GIT_INDEX_FILE::
        used to specify the index file of the cache
 
-GIT_ALTERNATE_OBJECT_DIRECTORIES:
+GIT_ALTERNATE_OBJECT_DIRECTORIES::
        used to specify additional object database roots (usually unset)
 
 Author
@@ -141,5 +140,5 @@ Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel
 
 GIT
 ---
-Part of the link:git.html[git] suite
+Part of the gitlink:git[7] suite
 
index 0eb5e2889c9e5bb12af82fbc42f59493720ab731..30b1fbf6e71d010d94b10bd1556b4fa423509be6 100644 (file)
@@ -33,5 +33,5 @@ Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
 
 GIT
 ---
-Part of the link:git.html[git] suite
+Part of the gitlink:git[7] suite
 
index c275ae26fd6d55c48debbd4907d655b7031bbf17..017579348319477c6356fcfce309ce68746d6899 100644 (file)
@@ -1,6 +1,5 @@
 git-grep(1)
 ===========
-v0.99.6, Sep 2005
 
 NAME
 ----
@@ -43,5 +42,5 @@ Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
 
 GIT
 ---
-Part of the link:git.html[git] suite
+Part of the gitlink:git[7] suite
 
index 4dd06f7c896c685d73abca652ab84281dac97425..9239f11135d849ff0411e9ebef21ed8b1c83afd6 100644 (file)
@@ -1,6 +1,5 @@
 git-hash-object(1)
 ==================
-v0.1, May 2005
 
 NAME
 ----
@@ -40,5 +39,5 @@ Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel
 
 GIT
 ---
-Part of the link:git.html[git] suite
+Part of the gitlink:git[7] suite
 
index 9cd862e03d5abcffd8d6a9178c3488fe44b2d206..088624f6cc47c8ce2f72704d2d30d65248956204 100644 (file)
@@ -1,10 +1,9 @@
 git-http-fetch(1)
 =================
-v0.1, May 2005
 
 NAME
 ----
-git-http-fetch - Downloads a remote GIT repository via HTTP
+git-http-fetch - Downloads a remote git repository via HTTP
 
 
 SYNOPSIS
@@ -13,7 +12,7 @@ SYNOPSIS
 
 DESCRIPTION
 -----------
-Downloads a remote GIT repository via HTTP.
+Downloads a remote git repository via HTTP.
 
 -c::
        Get the commit objects.
@@ -38,5 +37,5 @@ Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel
 
 GIT
 ---
-Part of the link:git.html[git] suite
+Part of the gitlink:git[7] suite
 
diff --git a/Documentation/git-http-push.txt b/Documentation/git-http-push.txt
new file mode 100644 (file)
index 0000000..c7066d6
--- /dev/null
@@ -0,0 +1,89 @@
+git-http-push(1)
+================
+
+NAME
+----
+git-http-push - Push missing objects using HTTP/DAV.
+
+
+SYNOPSIS
+--------
+'git-http-push' [--complete] [--force] [--verbose] <url> <ref> [<ref>...]
+
+DESCRIPTION
+-----------
+Sends missing objects to remote repository, and updates the
+remote branch.
+
+
+OPTIONS
+-------
+--complete::
+       Do not assume that the remote repository is complete in its
+       current state, and verify all objects in the entire local
+       ref's history exist in the remote repository.
+
+--force::
+       Usually, the command refuses to update a remote ref that
+       is not an ancestor of the local ref used to overwrite it.
+       This flag disables the check.  What this means is that
+       the remote repository can lose commits; use it with
+       care.
+
+--verbose::
+       Report the list of objects being walked locally and the
+       list of objects successfully sent to the remote repository.
+
+<ref>...:
+       The remote refs to update.
+
+
+Specifying the Refs
+-------------------
+
+A '<ref>' specification can be either a single pattern, or a pair
+of such patterns separated by a colon ":" (this means that a ref name
+cannot have a colon in it).  A single pattern '<name>' is just a 
+shorthand for '<name>:<name>'.
+
+Each pattern pair consists of the source side (before the colon)
+and the destination side (after the colon).  The ref to be
+pushed is determined by finding a match that matches the source
+side, and where it is pushed is determined by using the
+destination side.
+
+ - It is an error if <src> does not match exactly one of the
+   local refs.
+
+ - If <dst> does not match any remote ref, either
+
+   * it has to start with "refs/"; <dst> is used as the
+     destination literally in this case.
+
+   * <src> == <dst> and the ref that matched the <src> must not
+     exist in the set of remote refs; the ref matched <src>
+     locally is used as the name of the destination.
+
+Without '--force', the <src> ref is stored at the remote only if
+<dst> does not exist, or <dst> is a proper subset (i.e. an
+ancestor) of <src>.  This check, known as "fast forward check",
+is performed in order to avoid accidentally overwriting the
+remote ref and lose other peoples' commits from there.
+
+With '--force', the fast forward check is disabled for all refs.
+
+Optionally, a <ref> parameter can be prefixed with a plus '+' sign
+to disable the fast-forward check only on that ref.
+
+
+Author
+------
+Written by Nick Hengeveld <nickh@reactrix.com>
+
+Documentation
+--------------
+Documentation by Nick Hengeveld
+
+GIT
+---
+Part of the gitlink:git[7] suite
diff --git a/Documentation/git-index-pack.txt b/Documentation/git-index-pack.txt
new file mode 100644 (file)
index 0000000..71ce557
--- /dev/null
@@ -0,0 +1,44 @@
+git-index-pack(1)
+=================
+
+NAME
+----
+git-index-pack - Build pack index file for an existing packed archive
+
+
+SYNOPSIS
+--------
+'git-index-pack' [-o <index-file>] <pack-file>
+
+
+DESCRIPTION
+-----------
+Reads a packed archive (.pack) from the specified file, and
+builds a pack index file (.idx) for it.  The packed archive
+together with the pack index can then be placed in the
+objects/pack/ directory of a git repository.
+
+
+OPTIONS
+-------
+-o <index-file>::
+       Write the generated pack index into the specified
+       file.  Without this option the name of pack index
+       file is constructed from the name of packed archive
+       file by replacing .pack with .idx (and the program
+       fails if the name of packed archive does not end
+       with .pack).
+
+
+Author
+------
+Written by Sergey Vlasov <vsu@altlinux.ru>
+
+Documentation
+-------------
+Documentation by Sergey Vlasov
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
index fb8b52253e4f2e26d8f241d99f4c528b097ed7f7..ef1826ae672cb6537d497d027e5babe4988a42b0 100644 (file)
@@ -1,6 +1,5 @@
 git-init-db(1)
 ==============
-v0.1, May 2005
 
 NAME
 ----
@@ -37,5 +36,5 @@ Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel
 
 GIT
 ---
-Part of the link:git.html[git] suite
+Part of the gitlink:git[7] suite
 
index aad7d99af1a9bbb3106589def29b67731bd8a10b..87abec1c4e46ad53e018264046091af4455da307 100644 (file)
@@ -1,10 +1,9 @@
 git-local-fetch(1)
 ==================
-v0.1, May 2005
 
 NAME
 ----
-git-local-fetch - Duplicates another GIT repository on a local system
+git-local-fetch - Duplicates another git repository on a local system
 
 
 SYNOPSIS
@@ -13,7 +12,7 @@ SYNOPSIS
 
 DESCRIPTION
 -----------
-Duplicates another GIT repository on a local system.
+Duplicates another git repository on a local system.
 
 OPTIONS
 -------
@@ -40,5 +39,5 @@ Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel
 
 GIT
 ---
-Part of the link:git.html[git] suite
+Part of the gitlink:git[7] suite
 
index d1c24f7b0cd0152397ab375eae36c5ca09b062f0..9cac0886cf3b03fd5c4454b1a0ea1920dd0edaf8 100644 (file)
@@ -1,6 +1,5 @@
 git-log(1)
 ==========
-v0.99.4, Aug 2005
 
 NAME
 ----
@@ -9,7 +8,7 @@ git-log - Show commit logs
 
 SYNOPSIS
 --------
-'git log' <option>...
+'git-log' <option>...
 
 DESCRIPTION
 -----------
@@ -31,6 +30,24 @@ OPTIONS
        Show only commits between the named two commits.
 
 
+Examples
+--------
+git log --no-merges::
+
+       Show the whole commit history, but skip any merges
+
+git log v2.6.12.. include/scsi drivers/scsi::
+
+       Show all commits since version 'v2.6.12' that changed any file
+       in the include/scsi or drivers/scsi subdirectories
+
+git log --since="2 weeks ago" -- gitk::
+
+       Show the changes during the last two weeks to the file 'gitk'.
+       The "--" is necessary to avoid confusion with the *branch* named
+       'gitk'
+
+
 Author
 ------
 Written by Linus Torvalds <torvalds@osdl.org>
@@ -41,5 +58,5 @@ Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel
 
 GIT
 ---
-Part of the link:git.html[git] suite
+Part of the gitlink:git[7] suite
 
index 9c5ef8e7a41425a02eeda8b28fafa26907aa7f47..8c1784d2e3f982c405863e8f79695bb92145a5f5 100644 (file)
@@ -1,6 +1,5 @@
 git-ls-files(1)
 ===============
-v0.1, May 2005
 
 NAME
 ----
@@ -10,11 +9,11 @@ git-ls-files - Information about files in the cache/working directory
 SYNOPSIS
 --------
 'git-ls-files' [-z] [-t]
-               (--[cached|deleted|others|ignored|stage|unmerged|killed])\*
-               (-[c|d|o|i|s|u|k])\*
+               (--[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>]
+               [--exclude-per-directory=<file>] [--] [<file>]\*
 
 DESCRIPTION
 -----------
@@ -33,6 +32,9 @@ OPTIONS
 -d|--deleted::
        Show deleted files in the output
 
+-m|--modified::
+       Show modified files in the output
+
 -o|--others::
        Show other files in the output
 
@@ -48,11 +50,11 @@ OPTIONS
 
 -k|--killed::
        Show files on the filesystem that need to be removed due
-       to file/directory conflicts for checkout-cache to
+       to file/directory conflicts for checkout-index to
        succeed.
 
 -z::
-       \0 line termination on output
+       \0 line termination on output.
 
 -x|--exclude=<pattern>::
        Skips files matching pattern.
@@ -68,12 +70,20 @@ OPTIONS
 -t::
        Identify the file status with the following tags (followed by
        a space) at the start of each line:
-       H       cached
-       M       unmerged
-       R       removed/deleted
-       K       to be killed
+       H::     cached
+       M::     unmerged
+       R::     removed/deleted
+       C::     modifed/changed
+       K::     to be killed
        ?       other
 
+--::
+       Do not interpret any more arguments as options.
+
+<file>::
+       Files to show. If no files are given all files which match the other
+       specified criteria are shown.
+
 Output
 ------
 show files just outputs the filename unless '--stage' is specified in
@@ -90,6 +100,10 @@ the dircache records up to three such pairs; one from tree O in stage
 the user (or the porcelain) to see what should eventually be recorded at the
 path. (see git-read-tree for more information on state)
 
+When `-z` option is not used, TAB, LF, and backslash characters
+in pathnames are represented as `\t`, `\n`, and `\\`,
+respectively.
+
 
 Exclude Patterns
 ----------------
@@ -100,13 +114,13 @@ flags --others or --ignored are specified.
 
 These exclude patterns come from these places:
 
(1) command line flag --exclude=<pattern> specifies a single
 1. command line flag --exclude=<pattern> specifies a single
      pattern.
 
(2) command line flag --exclude-from=<file> specifies a list of
 2. command line flag --exclude-from=<file> specifies a list of
      patterns stored in a file.
 
(3) command line flag --exclude-per-directory=<name> specifies
 3. command line flag --exclude-per-directory=<name> specifies
      a name of the file in each directory 'git-ls-files'
      examines, and if exists, its contents are used as an
      additional list of patterns.
@@ -158,12 +172,13 @@ An exclude pattern is of the following format:
  - otherwise, it is a shell glob pattern, suitable for
    consumption by fnmatch(3) with FNM_PATHNAME flag.  I.e. a
    slash in the pattern must match a slash in the pathname.
-   "Documentation/*.html" matches "Documentation/git.html" but
+   "Documentation/\*.html" matches "Documentation/git.html" but
    not "ppc/ppc.html".  As a natural exception, "/*.c" matches
    "cat-file.c" but not "mozilla-sha1/sha1.c".
 
 An example:
 
+--------------------------------------------------------------
     $ cat .git/ignore
     # ignore objects and archives, anywhere in the tree.
     *.[oa]
@@ -176,11 +191,12 @@ An example:
         --exclude='Documentation/*.[0-9]' \
         --exclude-from=.git/ignore \
         --exclude-per-directory=.gitignore
+--------------------------------------------------------------
 
 
 See Also
 --------
-link:git-read-tree.html[git-read-tree]
+gitlink:git-read-tree[1]
 
 
 Author
@@ -193,5 +209,5 @@ Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel
 
 GIT
 ---
-Part of the link:git.html[git] suite
+Part of the gitlink:git[7] suite
 
index c3152b17e7902c2a8f9c5f6c75a002386a7d7d8b..c0a80d4089d88417d0d91dbf44f728608ea1c467 100644 (file)
@@ -1,6 +1,5 @@
 git-ls-remote(1)
 ================
-v0.1, May 2005
 
 NAME
 ----
@@ -60,5 +59,5 @@ Written by Junio C Hamano <junkio@cox.net>
 
 GIT
 ---
-Part of the link:git.html[git] suite
+Part of the gitlink:git[7] suite
 
index fdb215ec603d185511168be0c3fe6ad729f1b32f..ba0438e9ad444d3647af192f3483f27a73f8e294 100644 (file)
@@ -1,6 +1,5 @@
 git-ls-tree(1)
 ==============
-v0.1, May 2005
 
 NAME
 ----
@@ -39,6 +38,10 @@ Output Format
 -------------
         <mode> SP <type> SP <object> TAB <file>
 
+When `-z` option is not used, TAB, LF, and backslash characters
+in pathnames are represented as `\t`, `\n`, and `\\`,
+respectively.
+
 
 Author
 ------
@@ -51,5 +54,5 @@ Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel
 
 GIT
 ---
-Part of the link:git.html[git] suite
+Part of the gitlink:git[7] suite
 
index eb3a3d1be7e6d45686408882ae16c5608faa8476..dc7d725ea155f9eccb7ebab7df6335ae7589d483 100644 (file)
@@ -61,5 +61,5 @@ Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
 
 GIT
 ---
-Part of the link:git.html[git] suite
+Part of the gitlink:git[7] suite
 
index 789d3a9f1eea744ffe8091200125b33b277489b0..03a9477664310c2dc5ee9bddc0b34a99b8ab6687 100644 (file)
@@ -7,7 +7,7 @@ git-mailsplit - Totally braindamaged mbox splitter program.
 
 SYNOPSIS
 --------
-'git-mailsplit' <mbox> <directory>
+'git-mailsplit' [-d<prec>] [<mbox>] <directory>
 
 DESCRIPTION
 -----------
@@ -17,14 +17,23 @@ directory so you can process them further from there.
 OPTIONS
 -------
 <mbox>::
-       Mbox file to split.
+       Mbox file to split.  If not given, the mbox is read from
+       the standard input.
 
 <directory>::
        Directory in which to place the individual messages.
 
+-d<prec>::
+       Instead of the default 4 digits with leading zeros,
+       different precision can be specified for the generated
+       filenames.
+
+
 Author
 ------
 Written by Linus Torvalds <torvalds@osdl.org>
+and Junio C Hamano <junkio@cox.net>
+
 
 Documentation
 --------------
@@ -32,5 +41,5 @@ Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
 
 GIT
 ---
-Part of the link:git.html[git] suite
+Part of the gitlink:git[7] suite
 
index 1e27bf23018c8d2c0630f6881ac1b79c92206b9c..d1d56f194aff866f49c5d0514ce9b47def456bfd 100644 (file)
@@ -1,6 +1,5 @@
 git-merge-base(1)
 =================
-v0.1, May 2005
 
 NAME
 ----
@@ -30,5 +29,5 @@ Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel
 
 GIT
 ---
-Part of the link:git.html[git] suite
+Part of the gitlink:git[7] suite
 
index 1b99f2b3cdd939964c275a202ffa71dc5d1b8f44..d072fdaa4f74a6508abcae17001b1321c73bb000 100644 (file)
@@ -1,6 +1,5 @@
 git-merge-index(1)
 ==================
-v0.1, May 2005
 
 NAME
 ----
@@ -85,5 +84,5 @@ Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel
 
 GIT
 ---
-Part of the link:git.html[git] suite
+Part of the gitlink:git[7] suite
 
index b84d14d8b627db6f19720c764f79a5347da65ace..86aad37c6acaf0c37995221456bffc61ed3bc883 100644 (file)
@@ -1,6 +1,5 @@
 git-merge-one-file(1)
 =====================
-v0.99.4, Aug 2005
 
 NAME
 ----
@@ -27,5 +26,5 @@ Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel
 
 GIT
 ---
-Part of the link:git.html[git] suite
+Part of the gitlink:git[7] suite
 
index a48697ff3484d075669e77366de35727c2017e7a..b3ef19bae44268a3a2159f9ccfbce51314312ab7 100644 (file)
@@ -1,6 +1,5 @@
 git-merge(1)
 ============
-v0.99.6, Sep 2005
 
 NAME
 ----
@@ -9,7 +8,7 @@ git-merge - Grand Unified Merge Driver
 
 SYNOPSIS
 --------
-'git-merge' [-n] [-s <strategy>]... <msg> <head> <remote> <remote>...
+'git-merge' [-n] [--no-commit] [-s <strategy>]... <msg> <head> <remote> <remote>...
 
 
 DESCRIPTION
@@ -20,14 +19,12 @@ which drives multiple merge strategy scripts.
 
 OPTIONS
 -------
--n::
-       Do not show diffstat at the end of the merge.
+include::merge-pull-opts.txt[]
 
--s <strategy>::
-       use that merge strategy; can be given more than once to
-       specify them in the order they should be tried.  If
-       there is no `-s` option, built-in list of strategies is
-       used instead.
+<msg>::
+       The commit message to be used for the merge commit (in case
+       it is created). The `git-fmt-merge-msg` script can be used
+       to give a good default for automated `git-merge` invocations.
 
 <head>::
        our branch head commit.
@@ -37,6 +34,13 @@ OPTIONS
        least one <remote>.  Specifying more than one <remote>
        obviously means you are trying an Octopus.
 
+include::merge-strategies.txt[]
+
+
+SEE ALSO
+--------
+gitlink:git-fmt-merge-msg[1], gitlink:git-pull[1]
+
 
 Author
 ------
@@ -49,4 +53,4 @@ Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
 
 GIT
 ---
-Part of the link:git.html[git] suite
+Part of the gitlink:git[7] suite
index 708f4ef8dad1b41628a1afcc1bc75a1bae89fa4e..2860a3d1ba54c712bb30febfa37693f3efbdb7bf 100644 (file)
@@ -1,6 +1,5 @@
 git-mktag(1)
 ============
-v0.1, May 2005
 
 NAME
 ----
@@ -44,5 +43,5 @@ Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel
 
 GIT
 ---
-Part of the link:git.html[git] suite
+Part of the gitlink:git[7] suite
 
diff --git a/Documentation/git-mv.txt b/Documentation/git-mv.txt
new file mode 100644 (file)
index 0000000..f2d5882
--- /dev/null
@@ -0,0 +1,51 @@
+git-mv(1)
+=========
+
+NAME
+----
+git-mv - Script used to move or rename a file, directory or symlink.
+
+
+SYNOPSIS
+--------
+'git-mv' [-f] [-n] <source> <destination>
+'git-mv' [-f] [-k] [-n] <source> ... <destination directory>
+
+DESCRIPTION
+-----------
+This script is used to move or rename a file, directory or symlink.
+In the first form, it renames <source>, which must exist and be either
+a file, symlink or directory, to <destination>, which must not exist.
+In the second form, the last argument has to be an existing
+directory; the given sources will be moved into this directory.
+
+The index is updated after successful completion, but the change must still be
+committed.
+
+OPTIONS
+-------
+-f::
+       Force renaming or moving even targets exist
+-k::
+        Skip move or rename actions which would lead to an error
+       condition. An error happens when a source is neither existing nor
+        controlled by GIT, or when it would overwrite an existing
+        file unless '-f' is given.
+-n::
+       Do nothing; only show what would happen
+
+
+Author
+------
+Written by Linus Torvalds <torvalds@osdl.org>
+Rewritten by Ryan Anderson <ryan@michonline.com>
+Move functionality added by Josef Weidendorfer <Josef.Weidendorfer@gmx.de>
+
+Documentation
+--------------
+Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>.
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
diff --git a/Documentation/git-name-rev.txt b/Documentation/git-name-rev.txt
new file mode 100644 (file)
index 0000000..e37b0b8
--- /dev/null
@@ -0,0 +1,66 @@
+git-name-rev(1)
+===============
+
+NAME
+----
+git-name-rev - Find symbolic names for given revs.
+
+
+SYNOPSIS
+--------
+'git-name-rev' [--tags] ( --all | --stdin | <commitish>... )
+
+DESCRIPTION
+-----------
+Finds symbolic names suitable for human digestion for revisions given in any
+format parsable by git-rev-parse.
+
+
+OPTIONS
+-------
+
+--tags::
+       Do not use branch names, but only tags to name the commits
+
+--all::
+       List all commits reachable from all refs
+
+--stdin::
+       Read from stdin, append "(<rev_name>)" to all sha1's of name'able
+       commits, and pass to stdout
+
+EXAMPLE
+-------
+
+Given a commit, find out where it is relative to the local refs. Say somebody
+wrote you about that phantastic commit 33db5f4d9027a10e477ccf054b2c1ab94f74c85a.
+Of course, you look into the commit, but that only tells you what happened, but
+not the context.
+
+Enter git-name-rev:
+
+------------
+% git name-rev 33db5f4d9027a10e477ccf054b2c1ab94f74c85a
+------------
+
+Now you are wiser, because you know that it happened 940 revisions before v0.99.
+
+Another nice thing you can do is:
+
+------------
+% git log | git name-rev --stdin
+------------
+
+
+Author
+------
+Written by Johannes Schindelin <Johannes.Schindelin@gmx.de>
+
+Documentation
+--------------
+Documentation by Johannes Schindelin.
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
index 7a9cc542d30f78044a39fe81c4ed1e21957f8293..6e32ea347c415a1849d58f80779f45257c9898df 100644 (file)
@@ -1,6 +1,5 @@
 git-octopus(1)
 ==============
-v0.99.5, Aug 2005
 
 NAME
 ----
@@ -35,5 +34,5 @@ Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
 
 GIT
 ---
-Part of the link:git.html[git] suite
+Part of the gitlink:git[7] suite
 
index 5ce195c5a490d9055b79b00060cdad0f7efcdf69..d1e93dbb372624d68fa96eb8bed141f1e76f0308 100644 (file)
@@ -1,6 +1,5 @@
 git-pack-objects(1)
 ===================
-v0.1, July 2005
 
 NAME
 ----
@@ -9,7 +8,7 @@ git-pack-objects - Create a packed archive of objects.
 
 SYNOPSIS
 --------
-'git-pack-objects' [--incremental] [--window=N] [--depth=N] {--stdout | base-name} < object-list
+'git-pack-objects' [--local] [--incremental] [--window=N] [--depth=N] {--stdout | base-name} < object-list
 
 
 DESCRIPTION
@@ -31,7 +30,7 @@ transport by their peers.
 
 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.
+enables git to read from such an archive.
 
 
 OPTIONS
@@ -65,6 +64,11 @@ base-name::
        This flag causes an object already in a pack ignored
        even if it appears in the standard input.
 
+--local::
+       This flag is similar to `--incremental`; instead of
+       ignoring all packed objects, it only ignores objects
+       that are packed and not in the local object store
+       (i.e. borrowed from an alternate).
 
 Author
 ------
@@ -76,9 +80,10 @@ Documentation by Junio C Hamano
 
 See-Also
 --------
-git-repack(1) git-prune-packed(1)
+gitlink:git-repack[1]
+gitlink:git-prune-packed[1]
 
 GIT
 ---
-Part of the link:git.html[git] suite
+Part of the gitlink:git[7] suite
 
index 7ee0145ea54208a0dc1d0f85618d3a4e7a433b81..fc27afe26d9d174c350e00804071630f879c663c 100644 (file)
@@ -45,4 +45,4 @@ Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
 
 GIT
 ---
-Part of the link:git.html[git] suite
+Part of the gitlink:git[7] suite
index b6fa2a8b399adc80bfa9acbc02c7f5cf5fb88aa6..c8bd1977790b2611d4b7974fc3a716d99b888a74 100644 (file)
@@ -18,6 +18,12 @@ ID" are almost guaranteed to be the same thing.
 
 IOW, you can use this thing to look for likely duplicate commits.
 
+When dealing with git-diff-tree output, it takes advantage of
+the fact that the patch is prefixed with the object name of the
+commit, and outputs two 40-byte hexadecimal string.  The first
+string is the patch ID, and the second string is the commit ID.
+This can be used to make a mapping from patch ID to commit ID.
+
 OPTIONS
 -------
 <patch>::
@@ -33,5 +39,5 @@ Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
 
 GIT
 ---
-Part of the link:git.html[git] suite
+Part of the gitlink:git[7] suite
 
index 90075403dce15f3232fceb1745561ebffa60400a..915d3f8a06a6577b093acd79d00f93731fc7dec2 100644 (file)
@@ -1,6 +1,5 @@
 git-peek-remote(1)
 ==================
-v0.1, July 2005
 
 NAME
 ----
@@ -23,7 +22,7 @@ OPTIONS
        remote side, if it is not found on your $PATH. Some
        installations of sshd ignores the user's environment
        setup scripts for login shells (e.g. .bash_profile) and
-       your privately installed GIT may not be found on the system
+       your privately installed git may not be found on the system
        default $PATH.  Another workaround suggested is to set
        up your $PATH in ".bashrc", but this flag is for people
        who do not want to pay the overhead for non-interactive
@@ -49,5 +48,5 @@ Documentation by Junio C Hamano.
 
 GIT
 ---
-Part of the link:git.html[git] suite
+Part of the gitlink:git[7] suite
 
index 3fca275bc5139cbf87d5bf9a980747fa10e0084c..28a1500d39a503e8341ad9c39c1c1597b1653d7c 100644 (file)
@@ -1,6 +1,5 @@
 git-prune-packed(1)
 =====================
-v0.1, August 2005
 
 NAME
 ----
@@ -34,9 +33,10 @@ Documentation by Ryan Anderson <ryan@michonline.com>
 
 See-Also
 --------
-git-pack-objects(1) git-repack(1)
+gitlink:git-pack-objects[1]
+gitlink:git-repack[1]
 
 GIT
 ---
-Part of the link:git.html[git] suite
+Part of the gitlink:git[7] suite
 
index f368427547152a2d7123b8a414047af7cdb16ee9..3367c9b21452db7e73ec29f03d36fd2d11b56b97 100644 (file)
@@ -1,6 +1,5 @@
 git-prune(1)
 ============
-v0.99.5, Aug 2005
 
 NAME
 ----
@@ -39,5 +38,5 @@ Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel
 
 GIT
 ---
-Part of the link:git.html[git] suite
+Part of the gitlink:git[7] suite
 
index 235a036b9d227bcc7a20361a4ec706df2f193d07..7ebb08da0c7c9065b1d26398601f69584fc867ef 100644 (file)
@@ -1,6 +1,5 @@
 git-pull(1)
 ===========
-v0.99.4, Aug 2005
 
 NAME
 ----
@@ -9,22 +8,109 @@ git-pull - Pull and merge from another repository.
 
 SYNOPSIS
 --------
-'git-pull' <repository> <refspec>...
+'git-pull' <options> <repository> <refspec>...
 
 
 DESCRIPTION
 -----------
-Runs 'git-fetch' with the given parameters.
-
-When only one ref is downloaded, runs 'git resolve' to merge it
-into the local HEAD.  Otherwise uses 'git octopus' to merge them
-into the local HEAD.
+Runs `git-fetch` with the given parameters, and calls `git-merge`
+to merge the retrieved head(s) into the current branch.
 
+Note that you can use `.` (current directory) as the
+<repository> to pull from the local repository -- this is useful
+when merging local branches into the current branch.
 
 OPTIONS
 -------
 include::pull-fetch-param.txt[]
 
+-a, \--append::
+       Append ref names and object names of fetched refs to the
+       existing contents of `$GIT_DIR/FETCH_HEAD`.  Without this
+       option old data in `$GIT_DIR/FETCH_HEAD` will be overwritten.
+
+include::merge-pull-opts.txt[]
+
+include::merge-strategies.txt[]
+
+
+
+EXAMPLES
+--------
+
+git pull, git pull origin::
+       Fetch the default head from the repository you cloned
+       from and merge it into your current branch.
+
+git pull -s ours . obsolete::
+       Merge local branch `obsolete` into the current branch,
+       using `ours` merge strategy.
+
+git pull . fixes enhancements::
+       Bundle local branch `fixes` and `enhancements` on top of
+       the current branch, making an Octopus merge.
+
+git pull --no-commit . maint::
+       Merge local branch `maint` into the current branch, but
+       do not make a commit automatically.  This can be used
+       when you want to include further changes to the merge,
+       or want to write your own merge commit message.
++
+You should refrain from abusing this option to sneak substantial
+changes into a merge commit.  Small fixups like bumping
+release/version name would be acceptable.
+
+Command line pull of multiple branches from one repository::
++
+------------------------------------------------
+$ cat .git/remotes/origin
+URL: git://git.kernel.org/pub/scm/git/git.git
+Pull: master:origin
+
+$ git checkout master
+$ git fetch origin master:origin +pu:pu maint:maint
+$ git pull . origin
+------------------------------------------------
++
+Here, a typical `$GIT_DIR/remotes/origin` file from a
+`git-clone` operation is used in combination with
+command line options to `git-fetch` to first update
+multiple branches of the local repository and then
+to merge the remote `origin` branch into the local
+`master` branch.  The local `pu` branch is updated
+even if it does not result in a fast forward update.
+Here, the pull can obtain its objects from the local
+repository using `.`, as the previous `git-fetch` is
+known to have already obtained and made available
+all the necessary objects.
+
+
+Pull of multiple branches from one repository using `$GIT_DIR/remotes` file::
++
+------------------------------------------------
+$ cat .git/remotes/origin
+URL: git://git.kernel.org/pub/scm/git/git.git
+Pull: master:origin
+Pull: +pu:pu
+Pull: maint:maint
+
+$ git checkout master
+$ git pull origin
+------------------------------------------------
++
+Here, a typical `$GIT_DIR/remotes/origin` file from a
+`git-clone` operation has been hand-modified to include
+the branch-mapping of additional remote and local
+heads directly.  A single `git-pull` operation while
+in the `master` branch will fetch multiple heads and
+merge the remote `origin` head into the current,
+local `master` branch.
+
+
+SEE ALSO
+--------
+gitlink:git-fetch[1], gitlink:git-merge[1]
+
 
 Author
 ------
@@ -33,9 +119,11 @@ and Junio C Hamano <junkio@cox.net>
 
 Documentation
 --------------
-Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>.
+Documentation by Jon Loeliger,
+David Greaves,
+Junio C Hamano and the git-list <git@vger.kernel.org>.
 
 GIT
 ---
-Part of the link:git.html[git] suite
+Part of the gitlink:git[7] suite
 
index b8993eba08b5d02b7c10d59e0c7b0b654b498514..f45ac5ee4910198fa351e56bb40333612787fcd2 100644 (file)
@@ -21,6 +21,15 @@ OPTIONS
 -------
 include::pull-fetch-param.txt[]
 
+\--all::
+       Instead of naming each ref to push, specifies all refs
+       to be pushed.
+
+-f, \--force::
+       Usually, the command refuses to update a local ref that is
+       not an ancestor of the remote ref used to overwrite it.
+       This flag disables the check.  What this means is that the
+       local repository can lose commits; use it with care.
 
 Author
 ------
@@ -32,5 +41,5 @@ Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
 
 GIT
 ---
-Part of the link:git.html[git] suite
+Part of the gitlink:git[7] suite
 
index d9c5a131250c9d9bf830013dafa1f1d6f2c2a813..7db5fb579597373128c9cbb44ef02adf5e9da8a0 100644 (file)
@@ -1,6 +1,5 @@
 git-read-tree(1)
 ================
-v0.1, May 2005
 
 NAME
 ----
@@ -9,7 +8,7 @@ git-read-tree - Reads tree information into the directory cache
 
 SYNOPSIS
 --------
-'git-read-tree' (<tree-ish> | [-m [-u]] <tree-ish1> [<tree-ish2> [<tree-ish3>]])
+'git-read-tree' (<tree-ish> | [-m [-u|-i]] <tree-ish1> [<tree-ish2> [<tree-ish3>]])
 
 
 DESCRIPTION
@@ -35,6 +34,16 @@ OPTIONS
        After a successful merge, update the files in the work
        tree with the result of the merge.
 
+-i::
+       Usually a merge requires the index file as well as the
+       files in the working tree are up to date with the
+       current head commit, in order not to lose local
+       changes.  This flag disables the check with the working
+       tree and is meant to be used when creating a merge of
+       trees that are not directly related to the current
+       working tree status into a temporary index file.
+
+
 <tree-ish#>::
        The id of the tree object(s) to be read/merged.
 
@@ -74,10 +83,10 @@ fast forward situation).
 When two trees are specified, the user is telling git-read-tree
 the following:
 
-    (1) The current index and work tree is derived from $H, but
+     1. The current index and work tree is derived from $H, but
         the user may have local changes in them since $H;
 
-    (2) The user wants to fast-forward to $M.
+     2. The user wants to fast-forward to $M.
 
 In this case, the "git-read-tree -m $H $M" command makes sure
 that no local change is lost as the result of this "merge".
@@ -251,7 +260,7 @@ updated to the result of the merge.
 
 See Also
 --------
-link:git-write-tree.html[git-write-tree]; link:git-ls-files.html[git-ls-files]
+gitlink:git-write-tree[1]; gitlink:git-ls-files[1]
 
 
 Author
@@ -264,5 +273,5 @@ Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel
 
 GIT
 ---
-Part of the link:git.html[git] suite
+Part of the gitlink:git[7] suite
 
index 09bd3289e978d6f1530b6405b7990e89b9d54d19..16c158f439c7299b92c4419d8a6233ef20fed914 100644 (file)
@@ -31,5 +31,5 @@ Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
 
 GIT
 ---
-Part of the link:git.html[git] suite
+Part of the gitlink:git[7] suite
 
index 2c6065fcb19b4871828d2addb42362f8802f6064..8afde14373723a9894e4da5b7f2281b18e75e93f 100644 (file)
@@ -1,6 +1,5 @@
 git-receive-pack(1)
 ===================
-v0.1, July 2005
 
 NAME
 ----
@@ -80,7 +79,7 @@ OPTIONS
 
 SEE ALSO
 --------
-link:git-send-pack.html[git-send-pack]
+gitlink:git-send-pack[1]
 
 
 Author
@@ -93,4 +92,4 @@ Documentation by Junio C Hamano.
 
 GIT
 ---
-Part of the link:git.html[git] suite
+Part of the gitlink:git[7] suite
index 25975ae4023e80bd08daafefccf8ec4710182a60..62405358fc8d79afac56434f249dc7b253296a6d 100644 (file)
@@ -33,5 +33,5 @@ Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
 
 GIT
 ---
-Part of the link:git.html[git] suite
+Part of the gitlink:git[7] suite
 
index 2c24bfa2ee30f1291ee5a2b3a5a50ae31d9c4f14..583cb0315e1d9c50cbb19575a0e61b30b5429e38 100644 (file)
@@ -1,6 +1,5 @@
 git-rename(1)
 =============
-v0.1, May 2005
 
 NAME
 ----
@@ -29,5 +28,5 @@ Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel
 
 GIT
 ---
-Part of the link:git.html[git] suite
+Part of the gitlink:git[7] suite
 
index d8e3ad093339766a494de10ca4b99e4d1c23d286..0c1ae49ed7b61a34af62c3e29f84207a5a3c3082 100644 (file)
@@ -1,6 +1,5 @@
 git-repack(1)
 =============
-v0.99.5, August 2005
 
 NAME
 ----
@@ -51,9 +50,10 @@ Documentation by Ryan Anderson <ryan@michonline.com>
 
 See-Also
 --------
-git-pack-objects(1) git-prune-packed(1)
+gitlink:git-pack-objects[1]
+gitlink:git-prune-packed[1]
 
 GIT
 ---
-Part of the link:git.html[git] suite
+Part of the gitlink:git[7] suite
 
index 27a66acc4add246af88e92353079239047d5f29a..2463ec91d5f6fe7925ff147229532bdc76382b0a 100644 (file)
@@ -36,5 +36,5 @@ Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
 
 GIT
 ---
-Part of the link:git.html[git] suite
+Part of the gitlink:git[7] suite
 
index a6567c4e2649c56cde9973f92a878be19c01fea4..31ec2076e7372ac3730ec7fe270d14bf5d69d1c3 100644 (file)
@@ -41,5 +41,5 @@ Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
 
 GIT
 ---
-Part of the link:git.html[git] suite
+Part of the gitlink:git[7] suite
 
index d0583181941bc88678f803953bbffe96836e992b..4e57c2b2877b1594b3a467c0930348221e98c672 100644 (file)
@@ -1,6 +1,5 @@
 git-resolve(1)
 ==============
-v0.99.5, Aug 2005
 
 NAME
 ----
@@ -9,7 +8,7 @@ git-resolve - Merge two commits
 
 SYNOPSIS
 --------
-'git resolve' <current> <merged> <message>
+'git-resolve' <current> <merged> <message>
 
 DESCRIPTION
 -----------
@@ -33,5 +32,5 @@ Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel
 
 GIT
 ---
-Part of the link:git.html[git] suite
+Part of the gitlink:git[7] suite
 
index 32c06a1662319a8c1a1d322a5a9faf54670aeb37..064ccb1f8707bc6e82d43bcabb20bb30aec6236f 100644 (file)
@@ -1,6 +1,5 @@
 git-rev-list(1)
 ===============
-v0.1, May 2005
 
 NAME
 ----
@@ -9,7 +8,18 @@ git-rev-list - Lists commit objects in reverse chronological order
 
 SYNOPSIS
 --------
-'git-rev-list' [ *--max-count*=number ] [ *--max-age*=timestamp ] [ *--min-age*=timestamp ] [ *--bisect* ] [ *--pretty* ] [ *--objects* ] [ *--merge-order* [ *--show-breaks* ] ] <commit> [ <commit> ...] [ ^<commit> ...]
+'git-rev-list' [ \--max-count=number ]
+       [ \--max-age=timestamp ]
+       [ \--min-age=timestamp ]
+       [ \--sparse ]
+       [ \--no-merges ]
+       [ \--all ]
+       [ [ \--merge-order [ \--show-breaks ] ] | [ \--topo-order ] | ]
+       [ \--parents ]
+       [ \--objects [ \--unpacked ] ]
+       [ \--pretty | \--header | ]
+       [ \--bisect ]
+       <commit>... [ \-- <paths>... ]
 
 DESCRIPTION
 -----------
@@ -17,71 +27,112 @@ Lists commit objects in reverse chronological order starting at the
 given commit(s), taking ancestry relationship into account.  This is
 useful to produce human-readable log output.
 
-Commits which are stated with a preceding '^' cause listing to stop at
-that point. Their parents are implied. "git-rev-list foo bar ^baz" thus
+Commits which are stated with a preceding '{caret}' cause listing to stop at
+that point. Their parents are implied. "git-rev-list foo bar {caret}baz" thus
 means "list all the commits which are included in 'foo' and 'bar', but
 not in 'baz'".
 
-If *--pretty* is specified, print the contents of the commit changesets
-in human-readable form.
-
-The *--objects* flag causes 'git-rev-list' to print the object IDs of
-any object referenced by the listed commits. 'git-rev-list --objects foo
-^bar' thus means "send me all object IDs which I need to download if
-I have the commit object 'bar', but not 'foo'".
-
-The *--bisect* flag limits output to the one commit object which is
-roughly halfway between the included and excluded commits. Thus,
-if 'git-rev-list --bisect foo ^bar
-^baz' outputs 'midpoint', the output
-of 'git-rev-list foo ^midpoint' and 'git-rev-list midpoint
-^bar
-^baz'
-would be of roughly the same length. Finding the change which introduces
-a regression is thus reduced to a binary search: repeatedly generate and
-test new 'midpoint's until the commit chain is of length one.
-
-If *--merge-order* is specified, the commit history is decomposed into a
-unique sequence of minimal, non-linear epochs and maximal, linear epochs.
-Non-linear epochs are then linearised by sorting them into merge order, which
-is described below.
-
+A special notation <commit1>..<commit2> can be used as a
+short-hand for {caret}<commit1> <commit2>.
+
+
+OPTIONS
+-------
+--pretty::
+       Print the contents of the commit changesets in human-readable form.
+
+--header::
+       Print the contents of the commit in raw-format; each
+       record is separated with a NUL character.
+
+--objects::
+       Print the object IDs of any object referenced by the listed commits.
+       'git-rev-list --objects foo ^bar' thus means "send me all object IDs
+       which I need to download if I have the commit object 'bar', but
+       not 'foo'".
+
+--unpacked::
+       Only useful with `--objects`; print the object IDs that
+       are not in packs.
+
+--bisect::
+       Limit output to the one commit object which is roughly halfway
+       between the included and excluded commits. Thus, if 'git-rev-list
+       --bisect foo ^bar ^baz' outputs 'midpoint', the output
+       of 'git-rev-list foo ^midpoint' and 'git-rev-list midpoint
+       ^bar ^baz' would be of roughly the same length. Finding the change
+       which introduces a regression is thus reduced to a binary search:
+       repeatedly generate and test new 'midpoint's until the commit chain
+       is of length one.
+
+--max-count::
+       Limit the number of commits output.
+
+--max-age=timestamp, --min-age=timestamp::
+       Limit the commits output to specified time range.
+
+--sparse::
+       When optional paths are given, the command outputs only
+       the commits that changes at least one of them, and also
+       ignores merges that do not touch the given paths.  This
+       flag makes the command output all eligible commits
+       (still subject to count and age limitation), but apply
+       merge simplification nevertheless.
+
+--all::
+       Pretend as if all the refs in `$GIT_DIR/refs/` are
+       listed on the command line as <commit>.
+
+--topo-order::
+       By default, the commits are shown in reverse
+       chronological order.  This option makes them appear in
+       topological order (i.e. descendant commits are shown
+       before their parents).
+
+--merge-order::
+       When specified the commit history is decomposed into a unique
+       sequence of minimal, non-linear epochs and maximal, linear epochs.
+       Non-linear epochs are then linearised by sorting them into merge
+       order, which is described below.
++
 Maximal, linear epochs correspond to periods of sequential development.
 Minimal, non-linear epochs correspond to periods of divergent development
 followed by a converging merge. The theory of epochs is described in more
 detail at
 link:http://blackcubes.dyndns.org/epoch/[http://blackcubes.dyndns.org/epoch/].
-
++
 The merge order for a non-linear epoch is defined as a linearisation for which
 the following invariants are true:
-
++
     1. if a commit P is reachable from commit N, commit P sorts after commit N
        in the linearised list.
     2. if Pi and Pj are any two parents of a merge M (with i < j), then any
        commit N, such that N is reachable from Pj but not reachable from Pi,
        sorts before all commits reachable from Pi.
-
++
 Invariant 1 states that later commits appear before earlier commits they are
 derived from.
-
++
 Invariant 2 states that commits unique to "later" parents in a merge, appear
 before all commits from "earlier" parents of a merge.
 
-If *--show-breaks* is specified, each item of the list is output with a
-2-character prefix consisting of one of: (|), (^), (=) followed by a space.
-
+--show-breaks::
+       Each item of the list is output with a 2-character prefix consisting
+       of one of: (|), (^), (=) followed by a space.
++
 Commits marked with (=) represent the boundaries of minimal, non-linear epochs
 and correspond either to the start of a period of divergent development or to
 the end of such a period.
-
++
 Commits marked with (|) are direct parents of commits immediately preceding
 the marked commit in the list.
-
++
 Commits marked with (^) are not parents of the immediately preceding commit.
 These "breaks" represent necessary discontinuities implied by trying to
 represent an arbtirary DAG in a linear form.
++
+`--show-breaks` is only valid if `--merge-order` is also specified.
 
-*--show-breaks* is only valid if *--merge-order* is also specified.
 
 Author
 ------
@@ -95,5 +146,5 @@ Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel
 
 GIT
 ---
-Part of the link:git.html[git] suite
+Part of the gitlink:git[7] suite
 
index 067e4f0025ea0403f902069be436693d5e057918..431b8f6e06194e302aac02fdf90522e6f5c04167 100644 (file)
@@ -54,13 +54,13 @@ OPTIONS
        `git-diff-\*`).
 
 --not::
-       When showing object names, prefix them with '^' and
-       strip '^' prefix from the object names that already have
+       When showing object names, prefix them with '{caret}' and
+       strip '{caret}' prefix from the object names that already have
        one.
 
 --symbolic::
        Usually the object names are output in SHA1 form (with
-       possible '^' prefix); this option makes them output in a
+       possible '{caret}' prefix); this option makes them output in a
        form as close to the original input as possible.
 
 
@@ -72,6 +72,14 @@ OPTIONS
        path of the current directory relative to the top-level
        directory.
 
+--since=datestring, --after=datestring::
+       Parses the date string, and outputs corresponding
+       --max-age= parameter for git-rev-list command.
+
+--until=datestring, --before=datestring::
+       Parses the date string, and outputs corresponding
+       --min-age= parameter for git-rev-list command.
+
 <args>...::
        Flags and parameters to be parsed.
 
@@ -79,8 +87,9 @@ OPTIONS
 SPECIFYING REVISIONS
 --------------------
 
-A revision parameter typically names a commit object.  They use
-what is called an 'extended SHA1' syntax.
+A revision parameter typically, but not necessarily, names a
+commit object.  They use what is called an 'extended SHA1'
+syntax.
 
 * The full SHA1 object name (40-byte hexadecimal string), or
   a substring of such that is unique within the repository.
@@ -91,24 +100,63 @@ what is called an 'extended SHA1' syntax.
 * A symbolic ref name.  E.g. 'master' typically means the commit
   object referenced by $GIT_DIR/refs/heads/master.  If you
   happen to have both heads/master and tags/master, you can
-  explicitly say 'heads/master' to tell GIT which one you mean.
+  explicitly say 'heads/master' to tell git which one you mean.
 
-* A suffix '^' to a revision parameter means the first parent of
-  that commit object.  '^<n>' means the <n>th parent (i.e.
-  'rev^'
-  is equivalent to 'rev^1').  As a special rule,
-  'rev^0' means the commit itself and is used when 'rev' is the
+* A suffix '{caret}' to a revision parameter means the first parent of
+  that commit object.  '{caret}<n>' means the <n>th parent (i.e.
+  'rev{caret}'
+  is equivalent to 'rev{caret}1').  As a special rule,
+  'rev{caret}0' means the commit itself and is used when 'rev' is the
   object name of a tag object that refers to a commit object.
 
 * A suffix '~<n>' to a revision parameter means the commit
   object that is the <n>th generation grand-parent of the named
   commit object, following only the first parent.  I.e. rev~3 is
-  equivalent to rev^^^ which is equivalent to rev^1^1^1.
-
-'git-rev-parse' also accepts a prefix '^' to revision parameter,
+  equivalent to rev{caret}{caret}{caret} which is equivalent to\
+  rev{caret}1{caret}1{caret}1.
+
+* A suffix '{caret}' followed by an object type name enclosed in
+  brace pair (e.g. `v0.99.8{caret}\{commit\}`) means the object
+  could be a tag, and dereference the tag recursively until an
+  object of that type is found or the object cannot be
+  dereferenced anymore (in which case, barf).  `rev{caret}0`
+  introduced earlier is a short-hand for `rev{caret}\{commit\}`.
+
+* A suffix '{caret}' followed by an empty brace pair
+  (e.g. `v0.99.8{caret}\{\}`) means the object could be a tag,
+  and dereference the tag recursively until a non-tag object is
+  found.
+
+'git-rev-parse' also accepts a prefix '{caret}' to revision parameter,
 which is passed to 'git-rev-list'.  Two revision parameters
 concatenated with '..' is a short-hand for writing a range
-between them.  I.e. 'r1..r2' is equivalent to saying '^r1 r2'
+between them.  I.e. 'r1..r2' is equivalent to saying '{caret}r1 r2'
+
+Here is an illustration, by Jon Loeliger.  Both node B and C are
+a commit parents of commit node A.  Parent commits are ordered
+left-to-right.
+
+    G   H   I   J
+     \ /     \ /
+      D   E   F
+       \  |  /
+        \ | /
+         \|/
+          B     C
+           \   /
+            \ /
+             A
+
+    A =      = A^0
+    B = A^   = A^1     = A~1
+    C = A^2  = A^2
+    D = A^^  = A^1^1   = A~2
+    E = B^2  = A^^2
+    F = B^3  = A^^3
+    G = A^^^ = A^1^1^1 = A~3
+    H = D^2  = B^^2    = A^^^2  = A~2^2
+    I = F^   = B^3^    = A^^3^
+    J = F^2  = B^3^2   = A^^3^2
 
 
 Author
@@ -122,5 +170,5 @@ Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
 
 GIT
 ---
-Part of the link:git.html[git] suite
+Part of the gitlink:git[7] suite
 
diff --git a/Documentation/git-rev-tree.txt b/Documentation/git-rev-tree.txt
deleted file mode 100644 (file)
index 2ec7ed0..0000000
+++ /dev/null
@@ -1,88 +0,0 @@
-git-rev-tree(1)
-===============
-v0.1, May 2005
-
-NAME
-----
-git-rev-tree - Provides the revision tree for one or more commits
-
-
-SYNOPSIS
---------
-'git-rev-tree' [--edges] [--cache <cache-file>] [^]<commit> [[^]<commit>]
-
-DESCRIPTION
------------
-Provides the revision tree for one or more commits.
-
-OPTIONS
--------
---edges::
-       Show edges (ie places where the marking changes between parent
-       and child)
-
---cache <cache-file>::
-       Use the specified file as a cache from a previous git-rev-list run
-       to speed things up.  Note that this "cache" is totally different
-       concept from the directory index.  Also this option is not
-       implemented yet.
-
-[^]<commit>::
-       The commit id to trace (a leading caret means to ignore this
-       commit-id and below)
-
-Output
-------
-
-        <date> <commit>:<flags> [<parent-commit>:<flags> ]\*
-
-<date>::
-       Date in 'seconds since epoch'
-
-<commit>::
-       id of commit object
-
-<parent-commit>::
-       id of each parent commit object (>1 indicates a merge)
-
-<flags>::
-
-       The flags are read as a bitmask representing each commit
-       provided on the commandline. eg: given the command:
-
-                $ git-rev-tree <com1> <com2> <com3>
-
-       The output:
-
-           <date> <commit>:5
-
-        means that <commit> is reachable from <com1>(1) and <com3>(4)
-       
-A revtree can get quite large. "git-rev-tree" will eventually allow
-you to cache previous state so that you don't have to follow the whole
-thing down.
-
-So the change difference between two commits is literally
-
-       git-rev-tree [commit-id1]  > commit1-revtree
-       git-rev-tree [commit-id2]  > commit2-revtree
-       join -t : commit1-revtree commit2-revtree > common-revisions
-
-(this is also how to find the most common parent - you'd look at just
-the head revisions - the ones that aren't referred to by other
-revisions - in "common-revision", and figure out the best one. I
-think.)
-
-
-Author
-------
-Written by Linus Torvalds <torvalds@osdl.org>
-
-Documentation
---------------
-Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>.
-
-GIT
----
-Part of the link:git.html[git] suite
-
index a3f9a5655b8626f5a15944a688996999b5806b1c..feebd81da5889da2e77dc8508f8b7e3d642e6214 100644 (file)
@@ -29,9 +29,9 @@ OPTIONS
        working tree does not have to match the HEAD commit.
        The revert is done against the beginning state of your
        working tree.
-
-       This is useful when reverting more than one commits'
-       effect to your working tree in a row.
++
+This is useful when reverting more than one commits'
+effect to your working tree in a row.
 
 
 Author
@@ -44,5 +44,5 @@ Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
 
 GIT
 ---
-Part of the link:git.html[git] suite
+Part of the gitlink:git[7] suite
 
index fa11efc83c3815a3e2d85d3c6c3811c049668432..b9bec55e53ad191e4cb0b052551aa7e45049013e 100644 (file)
@@ -1,6 +1,5 @@
 git-send-email(1)
 =================
-v0.1, July 2005
 
 NAME
 ----
@@ -21,35 +20,37 @@ The header of the email is configurable by command line options.  If not
 specified on the command line, the user will be prompted with a ReadLine
 enabled interface to provide the necessary information.
 
+OPTIONS
+-------
 The options available are:
 
-  --to
+--to::
        Specify the primary recipient of the emails generated.
        Generally, this will be the upstream maintainer of the
        project involved.
 
-   --from
+--from::
        Specify the sender of the emails.  This will default to
        the value GIT_COMMITTER_IDENT, as returned by "git-var -l".
        The user will still be prompted to confirm this entry.
 
-   --compose
+--compose::
        Use \$EDITOR to edit an introductory message for the
        patch series.
 
-   --subject
+--subject::
        Specify the initial subject of the email thread.
        Only necessary if --compose is also set.  If --compose
        is not set, this will be prompted for.
 
-   --in-reply-to
+--in-reply-to::
        Specify the contents of the first In-Reply-To header.
        Subsequent emails will refer to the previous email 
        instead of this if --chain-reply-to is set (the default)
        Only necessary if --compose is also set.  If --compose
        is not set, this will be prompted for.
 
-   --chain-reply-to, --no-chain-reply-to
+--chain-reply-to, --no-chain-reply-to::
        If this is set, each email will be sent as a reply to the previous
        email sent.  If disabled with "--no-chain-reply-to", all emails after
        the first will be sent as replies to the first email sent.  When using
@@ -57,7 +58,7 @@ The options available are:
        entire patch series.
        Default is --chain-reply-to
 
-   --smtp-server
+--smtp-server::
        If set, specifies the outgoing SMTP server to use.  Defaults to
        localhost.
 
@@ -75,5 +76,5 @@ Documentation by Ryan Anderson
 
 GIT
 ---
-Part of the link:git.html[git] suite
+Part of the gitlink:git[7] suite
 
index 5ed25f54d1a68c3cf3c43d7189337106519a1131..577f06a21460a4e06eaa4cc4994b3f6ea80601ff 100644 (file)
@@ -1,6 +1,5 @@
 git-send-pack(1)
 ================
-v0.1, July 2005
 
 NAME
 ----
@@ -61,9 +60,9 @@ this flag.
 Without '--all' and without any '<ref>', the refs that exist
 both on the local side and on the remote side are updated.
 
-When '<ref>'s are specified explicitly, it can be either a
+When one or more '<ref>' are specified explicitly, it can be either a
 single pattern, or a pair of such pattern separated by a colon
-':' (this means that a ref name cannot have a colon in it).  A
+":" (this means that a ref name cannot have a colon in it).  A
 single pattern '<name>' is just a shorthand for '<name>:<name>'.
 
 Each pattern pair consists of the source side (before the colon)
@@ -79,10 +78,10 @@ destination side.
 
  - If <dst> does not match any remote ref, either
 
-   - it has to start with "refs/"; <dst> is used as the
+   * it has to start with "refs/"; <dst> is used as the
      destination literally in this case.
 
-   - <src> == <dst> and the ref that matched the <src> must not
+   * <src> == <dst> and the ref that matched the <src> must not
      exist in the set of remote refs; the ref matched <src>
      locally is used as the name of the destination.
 
@@ -108,4 +107,4 @@ Documentation by Junio C Hamano.
 
 GIT
 ---
-Part of the link:git.html[git] suite
+Part of the gitlink:git[7] suite
index fcf2cdfd5468b8ac22082844b40b8ded3630c8d6..6ef59acf50a14088d0f93b5ac745366159f751f6 100644 (file)
@@ -14,11 +14,12 @@ DESCRIPTION
 
 Sets up the normal git environment variables and a few helper functions
 (currently just "die()"), and returns ok if it all looks like a git archive.
-So use it something like
+So, to make the rest of the git scripts more careful and readable,
+use it as follows:
 
-       . git-sh-setup || die "Not a git archive"
-
-to make the rest of the git scripts more careful and readable.
+-------------------------------------------------
+. git-sh-setup || die "Not a git archive"
+-------------------------------------------------
 
 Author
 ------
@@ -30,5 +31,5 @@ Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
 
 GIT
 ---
-Part of the link:git.html[git] suite
+Part of the gitlink:git[7] suite
 
diff --git a/Documentation/git-shell.txt b/Documentation/git-shell.txt
new file mode 100644 (file)
index 0000000..3f4d804
--- /dev/null
@@ -0,0 +1,35 @@
+git-shell(1)
+============
+
+NAME
+----
+git-shell - Restricted login shell for GIT over SSH only
+
+
+SYNOPSIS
+--------
+'git-shell -c <command> <argument>'
+
+DESCRIPTION
+-----------
+This is meant to be used as a login shell for SSH accounts you want
+to restrict to GIT pull/push access only. It permits execution only
+of server-side GIT commands implementing the pull/push functionality.
+The commands can be executed only by the '-c' option; the shell is not
+interactive.
+
+Currently, only the `git-receive-pack` and `git-upload-pack` commands
+are permitted to be called, with a single required argument.
+
+Author
+------
+Written by Linus Torvalds <torvalds@osdl.org>
+
+Documentation
+--------------
+Documentation by Petr Baudis and the git-list <git@vger.kernel.org>.
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
index 6968ca6060a075bdd2a53776f353dfabb444fc88..65ca77fbf657520a59b308fc3e99ed79b61cff95 100644 (file)
@@ -1,6 +1,5 @@
 git-shortlog(1)
 ===============
-v0.99.4, Aug 2005
 
 NAME
 ----
@@ -9,7 +8,7 @@ git-shortlog - Summarize 'git log' output.
 
 SYNOPSIS
 --------
-'git log --pretty=short | git shortlog'
+'git-log --pretty=short | git shortlog'
 
 DESCRIPTION
 -----------
@@ -27,5 +26,5 @@ Documentation by Junio C Hamano.
 
 GIT
 ---
-Part of the link:git.html[git] suite
+Part of the gitlink:git[7] suite
 
index 39e0682ee72795b552b94372056b07e321d6655a..c6c97b21c320ce7fa6533bfc153f6fac7d45a7c0 100644 (file)
@@ -1,6 +1,5 @@
 git-show-branch(1)
 ==================
-v0.99.5, Aug 2005
 
 NAME
 ----
@@ -8,7 +7,7 @@ git-show-branch - Show branches and their commits.
 
 SYNOPSIS
 --------
-'git show-branch [--all] [--heads] [--tags] [--more=<n> | --list | --independent | --merge-base] <reference>...'
+'git-show-branch [--all] [--heads] [--tags] [--more=<n> | --list | --independent | --merge-base] [--no-name | --sha1-name] <reference>...'
 
 DESCRIPTION
 -----------
@@ -45,6 +44,15 @@ OPTIONS
        Among the <reference>s given, display only the ones that
        cannot be reached from any other <reference>.
 
+--no-name::
+       Do not show naming strings for each commit.
+
+--sha1-name::
+       Instead of naming the commits using the path to reach
+       them from heads (e.g. "master~2" to mean the grandparent
+       of "master"), name them with the unique prefix of their
+       object names.
+
 Note that --more, --list, --independent and --merge-base options
 are mutually exclusive.
 
@@ -89,21 +97,6 @@ whose commit message is "Add 'git show-branch'.  "fixes" branch
 adds one commit 'Introduce "reset type"'.  "mhf" branch has many
 other commits.
 
-When only one head is given, the output format changes slightly
-to conserve space.  The '+' sign to show which commit is
-reachable from which head and the first N lines to show the list
-of heads being displayed are both meaningless so they are
-omitted.  Also the label given to each commit does not repeat
-the name of the branch because it is obvious.
-
-------------------------------------------------
-$ git show-branch --more=4 master
-[master] Add 'git show-branch'.
-[~1] Add a new extended SHA1 syntax <name>~<num>
-[~2] Fix "git-diff A B"
-[~3] git-ls-files: generalized pathspecs
-[~4] Make "git-ls-files" work in subdirectories
-------------------------------------------------
 
 Author
 ------
@@ -117,4 +110,4 @@ Documentation by Junio C Hamano.
 
 GIT
 ---
-Part of the link:git.html[git] suite
+Part of the gitlink:git[7] suite
index beefe947af3911427cdecb4505427ef767d937c4..be09b62bebb2b98fa8b0f39e47d29267126fa2a6 100644 (file)
@@ -1,6 +1,5 @@
 git-show-index(1)
 =================
-v0.1, July 2005
 
 NAME
 ----
@@ -14,7 +13,7 @@ SYNOPSIS
 
 DESCRIPTION
 -----------
-Reads given idx file for packed GIT archive created with
+Reads given idx file for packed git archive created with
 git-pack-objects command, and dumps its contents.
 
 The information it outputs is subset of what you can get from
@@ -32,5 +31,5 @@ Documentation by Junio C Hamano
 
 GIT
 ---
-Part of the link:git.html[git] suite
+Part of the gitlink:git[7] suite
 
index 4ef03f6c615cae83e018777429ae3e983e7b6915..b7116b30e0f47958d54c066776adbd318c84edb3 100644 (file)
@@ -1,6 +1,5 @@
 git-ssh-fetch(1)
 ================
-v0.1, May 2005
 
 NAME
 ----
@@ -48,5 +47,5 @@ Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel
 
 GIT
 ---
-Part of the link:git.html[git] suite
+Part of the gitlink:git[7] suite
 
index f1aa0df5d1bf5a12d73f1997411f2e0f9c4f9c28..702674e45d17fbdb6dfc60797ffcb2dbedb841bf 100644 (file)
@@ -1,6 +1,5 @@
 git-ssh-upload(1)
 =================
-v0.1, Jun 2005
 
 NAME
 ----
@@ -44,5 +43,5 @@ Documentation by Daniel Barkalow
 
 GIT
 ---
-Part of the link:git.html[git] suite
+Part of the gitlink:git[7] suite
 
index 886fb7c02c290baff2ddc5eefe1ee9150dbb5604..753fc0866d2883af59ed54fd7ca566f7694172e3 100644 (file)
@@ -1,6 +1,5 @@
 git-status(1)
 =============
-v0.99.4, Aug 2005
 
 NAME
 ----
@@ -9,7 +8,7 @@ git-status - Show working tree status.
 
 SYNOPSIS
 --------
-'git status'
+'git-status'
 
 DESCRIPTION
 -----------
@@ -42,5 +41,5 @@ Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel
 
 GIT
 ---
-Part of the link:git.html[git] suite
+Part of the gitlink:git[7] suite
 
index 4f82efb4f97f562f632d286e0650ea320867a791..528a1b6ce30c51e944fe662b28ec91c9bd4bddd9 100644 (file)
@@ -29,5 +29,5 @@ Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
 
 GIT
 ---
-Part of the link:git.html[git] suite
+Part of the gitlink:git[7] suite
 
diff --git a/Documentation/git-svnimport.txt b/Documentation/git-svnimport.txt
new file mode 100644 (file)
index 0000000..88bdc08
--- /dev/null
@@ -0,0 +1,137 @@
+git-svnimport(1)
+================
+v0.1, July 2005
+
+NAME
+----
+git-svnimport - Import a SVN repository into git
+
+
+SYNOPSIS
+--------
+'git-svnimport' [ -o <branch-for-HEAD> ] [ -h ] [ -v ] [ -d | -D ]
+                       [ -C <GIT_repository> ] [ -i ] [ -u ] [-l limit_nr_changes]
+                       [ -b branch_subdir ] [ -t trunk_subdir ] [ -T tag_subdir ]
+                       [ -s start_chg ] [ -m ] [ -M regex ]
+                       <SVN_repository_URL> [ <path> ]
+
+
+DESCRIPTION
+-----------
+Imports a SVN repository into git. It will either create a new
+repository, or incrementally import into an existing one.
+
+SVN access is done by the SVN:: Perl module.
+
+git-svnimport assumes that SVN repositories are organized into one
+"trunk" directory where the main development happens, "branch/FOO"
+directories for branches, and "/tags/FOO" directories for tags.
+Other subdirectories are ignored.
+
+git-svnimport creates a file ".git/svn2git", which is required for
+incremental SVN imports.
+
+OPTIONS
+-------
+-C <target-dir>::
+        The GIT repository to import to.  If the directory doesn't
+        exist, it will be created.  Default is the current directory.
+
+-s <start_rev>::
+        Start importing at this SVN change number. The  default is 1.
++
+When importing incementally, you might need to edit the .git/svn2git file.
+
+-i::
+       Import-only: don't perform a checkout after importing.  This option
+       ensures the working directory and cache remain untouched and will
+       not create them if they do not exist.
+
+-t <trunk_subdir>::
+       Name the SVN trunk. Default "trunk".
+
+-T <tag_subdir>::
+       Name the SVN subdirectory for tags. Default "tags".
+
+-b <branch_subdir>::
+       Name the SVN subdirectory for branches. Default "branches".
+
+-o <branch-for-HEAD>::
+       The 'trunk' branch from SVN is imported to the 'origin' branch within
+       the git repository. Use this option if you want to import into a
+       different branch.
+
+-m::
+       Attempt to detect merges based on the commit message. This option
+       will enable default regexes that try to capture the name source
+       branch name from the commit message.
+
+-M <regex>::
+       Attempt to detect merges based on the commit message with a custom
+       regex. It can be used with -m to also see the default regexes.
+       You must escape forward slashes.
+
+-l <max_num_changes>::
+       Limit the number of SVN changesets we pull before quitting.
+       This option is necessary because the SVN library has serious memory
+       leaks; the recommended value for nontrivial imports is 100.
+
+       git-svnimport will still exit with a zero exit code. You can check
+       the size of the file ".git/svn2git" to determine whether to call
+       the importer again.
+
+-v::
+       Verbosity: let 'svnimport' report what it is doing.
+
+-d::
+       Use direct HTTP requests if possible. The "<path>" argument is used
+       only for retrieving the SVN logs; the path to the contents is
+       included in the SVN log.
+
+-D::
+       Use direct HTTP requests if possible. The "<path>" argument is used
+       for retrieving the logs, as well as for the contents.
++
+There's no safe way to automatically find out which of these options to
+use, so you need to try both. Usually, the one that's wrong will die
+with a 40x error pretty quickly.
+
+<SVN_repository_URL>::
+       The URL of the SVN module you want to import. For local
+       repositories, use "file:///absolute/path".
++
+If you're using the "-d" or "-D" option, this is the URL of the SVN
+repository itself; it usually ends in "/svn".
+
+<SVN_repository_URL>::
+       The URL of the SVN module you want to import. For local
+       repositories, use "file:///absolute/path".
+
+<path>
+       The path to the module you want to check out.
+
+-h::
+       Print a short usage message and exit.
+
+OUTPUT
+------
+If '-v' is specified, the script reports what it is doing.
+
+Otherwise, success is indicated the Unix way, i.e. by simply exiting with
+a zero exit status.
+
+Author
+------
+Written by Matthias Urlichs <smurf@smurf.noris.de>, with help from
+various participants of the git-list <git@vger.kernel.org>.
+
+Based on a cvs2git script by the same author.
+
+Documentation
+--------------
+Documentation by Matthias Urlichs <smurf@smurf.noris.de>.
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
diff --git a/Documentation/git-symbolic-ref.txt b/Documentation/git-symbolic-ref.txt
new file mode 100644 (file)
index 0000000..a851ae2
--- /dev/null
@@ -0,0 +1,52 @@
+git-symbolic-ref(1)
+===================
+
+NAME
+----
+git-symbolic-ref - read and modify symbolic refs
+
+SYNOPSIS
+--------
+'git-symbolic-ref' <name> [<ref>]
+
+DESCRIPTION
+-----------
+Given one argument, reads which branch head the given symbolic
+ref refers to and outputs its path, relative to the `.git/`
+directory.  Typically you would give `HEAD` as the <name>
+argument to see on which branch your working tree is on.
+
+Give two arguments, create or update a symbolic ref <name> to
+point at the given branch <ref>.
+
+Traditionally, `.git/HEAD` is a symlink pointing at
+`refs/heads/master`.  When we want to switch to another branch,
+we did `ln -sf refs/heads/newbranch .git/HEAD`, and when we want
+to find out which branch we are on, we did `readlink .git/HEAD`.
+This was fine, and internally that is what still happens by
+default, but on platforms that does not have working symlinks,
+or that does not have the `readlink(1)` command, this was a bit
+cumbersome.  On some platforms, `ln -sf` does not even work as
+advertised (horrors).
+
+A symbolic ref can be a regular file that stores a string that
+begins with `ref: refs/`.  For example, your `.git/HEAD` *can*
+be a regular file whose contents is `ref: refs/heads/master`.
+This can be used on a filesystem that does not support symbolic
+links.  Instead of doing `readlink .git/HEAD`, `git-symbolic-ref
+HEAD` can be used to find out which branch we are on.  To point
+the HEAD to `newbranch`, instead of `ln -sf refs/heads/newbranch
+.git/HEAD`, `git-symbolic-ref HEAD refs/heads/newbranch` can be
+used.
+
+Currently, .git/HEAD uses a regular file symbolic ref on Cygwin,
+and everywhere else it is implemented as a symlink.  This can be
+changed at compilation time.
+
+Author
+------
+Written by Junio C Hamano <junkio@cox.net>
+
+GIT
+---
+Part of the gitlink:git[7] suite
index 0e1be72470b7603496f4209787335516f7c1fa15..3984812cecc4453b4f3fb84f017f3cd4c50751cb 100644 (file)
@@ -1,28 +1,34 @@
 git-tag(1)
 ==========
-v0.99.4, Aug 2005
 
 NAME
 ----
 git-tag -  Create a tag object signed with GPG
 
 
-
 SYNOPSIS
 --------
-'git-tag' [-s | -a] [-f] <name>
+'git-tag' [-a | -s | -u <key-id>] [-f] [-m <msg>] <name> [<head>]
 
 DESCRIPTION
 -----------
-Adds a "tag" reference in .git/refs/tags/
+Adds a 'tag' reference in .git/refs/tags/
+
+Unless `-f` is given, the tag must not yet exist in
+`.git/refs/tags/` directory.
 
-Unless "-f" is given, the tag must not yet exist in ".git/refs/tags"
+If one of `-a`, `-s`, or `-u <key-id>` is passed, the command
+creates a 'tag' object, and requires the tag message.  Unless
+`-m <msg>` is given, an editor is started for the user to type
+in the tag message.
 
-If "-s" or "-a" is passed, the user will be prompted for a tag message.
-and a tag object is created.  Otherwise just the SHA1 object
-name of the commit object is written.
+Otherwise just the SHA1 object name of the commit object is
+written (i.e. an lightweight tag).
 
-A GnuPG signed tag object will be created when "-s" is used.
+A GnuPG signed tag object will be created when `-s` or `-u
+<key-id>` is used.  When `-u <key-id>` is not used, the
+committer identity for the current user is used to find the
+GnuPG key for signing.
 
 
 Author
@@ -36,4 +42,4 @@ Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel
 
 GIT
 ---
-Part of the link:git.html[git] suite
+Part of the gitlink:git[7] suite
index 9323dddfa74aa4ef7197637d1a401da96cb5a033..2139b6ff8ce3df6d1c872be1410349e2637eb4f1 100644 (file)
@@ -1,6 +1,5 @@
 git-tar-tree(1)
 ===============
-v0.1, May 2005
 
 NAME
 ----
@@ -35,5 +34,5 @@ Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel
 
 GIT
 ---
-Part of the link:git.html[git] suite
+Part of the gitlink:git[7] suite
 
index 2f2130d511735e2bb8709bfb8f5b9ad15e6a6150..213dc8196b90170cc14e35f7c637815b39a7ff8d 100644 (file)
@@ -1,6 +1,5 @@
 git-unpack-file(1)
 ==================
-v0.1, May 2005
 
 NAME
 ----
@@ -33,5 +32,5 @@ Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel
 
 GIT
 ---
-Part of the link:git.html[git] suite
+Part of the gitlink:git[7] suite
 
index a42a75e0773384aacec4d1c05c3a1d86aec01e6a..b716ba1ad35b400fd8c862145283a32298a94f4a 100644 (file)
@@ -1,6 +1,5 @@
 git-unpack-objects(1)
 =====================
-v0.1, July 2005
 
 NAME
 ----
@@ -35,5 +34,5 @@ Documentation by Junio C Hamano
 
 GIT
 ---
-Part of the link:git.html[git] suite
+Part of the gitlink:git[7] suite
 
index 54b5f24c1702cbe0aacdec075d396938162669fd..58b9e49af5f269f59b6e884430322cfca8673c75 100644 (file)
@@ -1,6 +1,5 @@
 git-update-index(1)
 ===================
-v0.1, May 2005
 
 NAME
 ----
@@ -10,11 +9,13 @@ git-update-index - Modifies the index or directory cache
 SYNOPSIS
 --------
 'git-update-index'
-            [--add] [--remove] [--refresh] [--replace]
-            [--ignore-missing]
-            [--force-remove]
+            [--add] [--remove | --force-remove] [--replace] 
+            [--refresh [-q] [--unmerged] [--ignore-missing]]
             [--cacheinfo <mode> <object> <file>]\*
-            [--info-only]
+            [--chmod=(+|-)x]
+            [--info-only] [--index-info]
+            [-z] [--stdin]
+            [--verbose]
             [--] [<file>]\*
 
 DESCRIPTION
@@ -42,12 +43,28 @@ OPTIONS
        Looks at the current cache and checks to see if merges or
        updates are needed by checking stat() information.
 
+-q::
+        Quiet.  If --refresh finds that the cache needs an update, the
+        default behavior is to error out.  This option makes
+        git-update-index continue anyway.
+
+--unmerged::
+        If --refresh finds unmerged changes in the cache, the default 
+        behavior is to error out.  This option makes git-update-index 
+        continue anyway.
+
 --ignore-missing::
        Ignores missing files during a --refresh
 
 --cacheinfo <mode> <object> <path>::
        Directly insert the specified info into the cache.
        
+--index-info::
+        Read index info from stdin.
+
+--chmod=(+|-)x::
+        Set the execute permissions on the updated files.        
+
 --info-only::
        Do not create objects in the object database for all
        <file> arguments that follow this flag; just insert
@@ -65,12 +82,24 @@ OPTIONS
        that conflicts with the entry being added are
        automatically removed with warning messages.
 
+--stdin::
+       Instead of taking list of paths from the command line,
+       read list of paths from the standard input.  Paths are
+       separated by LF (i.e. one path per line) by default.
+
+--verbose::
+        Report what is being added and removed from index.
+
+-z::
+       Only meaningful with `--stdin`; paths are separated with
+       NUL character instead of LF.
+
 --::
        Do not interpret any more arguments as options.
 
 <file>::
        Files to act on.
-       Note that files begining with '.' are discarded. This includes
+       Note that files beginning with '.' are discarded. This includes
        `./file` and `dir/./file`. If you don't want this, then use     
        cleaner names.
        The same applies to directories ending '/' and paths with '//'
@@ -92,7 +121,7 @@ Using --cacheinfo or --info-only
 current working directory.  This is useful for minimum-checkout
 merging.
 
-  To pretend you have a file with mode and sha1 at path, say:
+To pretend you have a file with mode and sha1 at path, say:
 
    $ git-update-index --cacheinfo mode sha1 path
 
@@ -112,6 +141,17 @@ To update and refresh only the files already checked out:
    git-checkout-index -n -f -a && git-update-index --ignore-missing --refresh
 
 
+Configuration
+-------------
+
+The command honors `core.filemode` configuration variable.  If
+your repository is on an filesystem whose executable bits are
+unreliable, this should be set to 'false'.  This causes the
+command to ignore differences in file modes recorded 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=`.
+
 Author
 ------
 Written by Linus Torvalds <torvalds@osdl.org>
@@ -122,5 +162,5 @@ Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel
 
 GIT
 ---
-Part of the link:git.html[git] suite
+Part of the gitlink:git[7] suite
 
diff --git a/Documentation/git-update-ref.txt b/Documentation/git-update-ref.txt
new file mode 100644 (file)
index 0000000..69715aa
--- /dev/null
@@ -0,0 +1,58 @@
+git-update-ref(1)
+=================
+
+NAME
+----
+git-update-ref - update the object name stored in a ref safely
+
+SYNOPSIS
+--------
+`git-update-ref` <ref> <newvalue> [<oldvalue>]
+
+DESCRIPTION
+-----------
+Given two arguments, stores the <newvalue> in the <ref>, possibly
+dereferencing the symbolic refs.  E.g. `git-update-ref HEAD
+<newvalue>` updates the current branch head to the new object.
+
+Given three arguments, stores the <newvalue> in the <ref>,
+possibly dereferencing the symbolic refs, after verifying that
+the current value of the <ref> matches <oldvalue>.
+E.g. `git-update-ref refs/heads/master <newvalue> <oldvalue>`
+updates the master branch head to <newvalue> only if its current
+value is <oldvalue>.
+
+It also allows a "ref" file to be a symbolic pointer to another
+ref file by starting with the four-byte header sequence of
+"ref:".
+
+More importantly, it allows the update of a ref file to follow
+these symbolic pointers, whether they are symlinks or these
+"regular file symbolic refs".  It follows *real* symlinks only
+if they start with "refs/": otherwise it will just try to read
+them and update them as a regular file (i.e. it will allow the
+filesystem to follow them, but will overwrite such a symlink to
+somewhere else with a regular filename).
+
+In general, using
+
+       git-update-ref HEAD "$head"
+
+should be a _lot_ safer than doing
+
+       echo "$head" > "$GIT_DIR/HEAD"
+
+both from a symlink following standpoint *and* an error checking
+standpoint.  The "refs/" rule for symlinks means that symlinks
+that point to "outside" the tree are safe: they'll be followed
+for reading but not for writing (so we'll never write through a
+ref symlink to some other tree, if you have copied a whole
+archive by creating a symlink tree).
+
+Author
+------
+Written by Linus Torvalds <torvalds@osdl.org>.
+
+GIT
+---
+Part of the gitlink:git[7] suite
index 39222c3b4c85f47b42a04775b3928fa7c70ac663..3d0dea07fbca680c852959ec1e62d77421642b44 100644 (file)
@@ -1,6 +1,5 @@
 git-update-server-info(1)
 =========================
-v0.1, July 2005
 
 NAME
 ----
@@ -55,5 +54,5 @@ Documentation by Junio C Hamano.
 
 GIT
 ---
-Part of the link:git.html[git] suite
+Part of the gitlink:git[7] suite
 
index be597a17871e95d4219e32a1bf9446bfda43c5e2..3d8f8ef6670e84b5601fdcd3e74717a4f3a0fde4 100644 (file)
@@ -1,6 +1,5 @@
 git-upload-pack(1)
 ==================
-v0.1, July 2005
 
 NAME
 ----
@@ -37,4 +36,4 @@ Documentation by Junio C Hamano.
 
 GIT
 ---
-Part of the link:git.html[git] suite
+Part of the gitlink:git[7] suite
index ebe87d68470d9fca03b85a1d3036fe20308d61f5..c22d34f5fb9df2ce567f72a996f3d85b424ecf69 100644 (file)
@@ -1,6 +1,5 @@
 git-var(1)
 ==========
-v0.1, July 2005
 
 NAME
 ----
@@ -15,21 +14,23 @@ DESCRIPTION
 -----------
 Prints a git logical variable.
 
--l causes the logical variables to be listed.
+OPTIONS
+-------
+-l::
+       Cause the logical variables to be listed.
 
 EXAMPLE
 --------
-$git-var GIT_AUTHOR_IDENT
-
-Eric W. Biederman <ebiederm@lnxi.com> 1121223278 -0600
+       $ git-var GIT_AUTHOR_IDENT
+       Eric W. Biederman <ebiederm@lnxi.com> 1121223278 -0600
 
 
 VARIABLES
 ----------
-GIT_AUTHOR_IDENT
+GIT_AUTHOR_IDENT::
     The author of a piece of code.
 
-GIT_COMMITTER_IDENT
+GIT_COMMITTER_IDENT::
     The person who put a piece of code into git.
 
 Diagnostics
@@ -43,8 +44,8 @@ Your sysadmin must hate you!::
 
 See Also
 --------
-link:git-commit-tree.html[git-commit-tree]
-link:git-tag.html[git-tag]
+gitlink:git-commit-tree[1]
+gitlink:git-tag[1]
 
 Author
 ------
@@ -56,5 +57,5 @@ Documentation by Eric Biederman and the git-list <git@vger.kernel.org>.
 
 GIT
 ---
-Part of the link:git.html[git] suite
+Part of the gitlink:git[7] suite
 
index d1043eb8df46c3e285e9974c203556e86cf722f1..cd74ffd3912adee956bf139c453092635b5650c2 100644 (file)
@@ -1,10 +1,9 @@
 git-verify-pack(1)
 ==================
-v0.1, June 2005
 
 NAME
 ----
-git-verify-pack - Validate packed GIT archive files.
+git-verify-pack - Validate packed git archive files.
 
 
 SYNOPSIS
@@ -14,7 +13,7 @@ SYNOPSIS
 
 DESCRIPTION
 -----------
-Reads given idx file for packed GIT archive created with
+Reads given idx file for packed git archive created with
 git-pack-objects command and verifies idx file and the
 corresponding pack file.
 
@@ -25,15 +24,19 @@ OPTIONS
 
 -v::
        After verifying the pack, show list of objects contained
-       in the pack.  The format used is:
+       in the pack.
 
-               SHA1 type size offset-in-packfile
+OUTPUT FORMAT
+-------------
+When specifying the -v option the format used is:
 
-       for objects that are not deltified in the pack, and
+       SHA1 type size offset-in-packfile
 
-               SHA1 type size offset-in-packfile depth base-SHA1
+for objects that are not deltified in the pack, and
 
-       for objects that are deltified.
+       SHA1 type size offset-in-packfile depth base-SHA1
+
+for objects that are deltified.
 
 Author
 ------
@@ -45,5 +48,5 @@ Documentation by Junio C Hamano
 
 GIT
 ---
-Part of the link:git.html[git] suite
+Part of the gitlink:git[7] suite
 
index 98db1b363b587b96697fafc8492cdb8ac7487e6f..b8a73c47afc8480129a305cec6e171e1174e9cc1 100644 (file)
@@ -28,5 +28,5 @@ Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
 
 GIT
 ---
-Part of the link:git.html[git] suite
+Part of the gitlink:git[7] suite
 
index 742005353aaab062f78036d8a1c5a86b135e5144..6c150b0264ac5caf326ad5b0d2da225a241b77f2 100644 (file)
@@ -1,6 +1,5 @@
 git-whatchanged(1)
 ==================
-v0.99.4, Aug 2005
 
 NAME
 ----
@@ -9,7 +8,7 @@ git-whatchanged - Show logs with difference each commit introduces.
 
 SYNOPSIS
 --------
-'git whatchanged' <option>...
+'git-whatchanged' <option>...
 
 DESCRIPTION
 -----------
@@ -44,6 +43,27 @@ OPTIONS
        <format> can be one of 'raw', 'medium', 'short', 'full',
        and 'oneline'.
 
+-m::
+       By default, differences for merge commits are not shown.
+       With this flag, show differences to that commit from all
+       of its parents.
+
+       However, it is not very useful in general, although it
+       *is* useful on a file-by-file basis.
+
+Examples
+--------
+git-whatchanged -p v2.6.12.. include/scsi drivers/scsi::
+
+       Show as patches the commits since version 'v2.6.12' that changed
+       any file in the include/scsi or drivers/scsi subdirectories
+
+git-whatchanged --since="2 weeks ago" -- gitk::
+
+       Show the changes during the last two weeks to the file 'gitk'.
+       The "--" is necessary to avoid confusion with the *branch* named
+       'gitk'
+
 
 Author
 ------
@@ -57,5 +77,5 @@ Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel
 
 GIT
 ---
-Part of the link:git.html[git] suite
+Part of the gitlink:git[7] suite
 
index 5cd54ab587d64d99c861027e272cb959525b9869..51be44d1f24cb3195fa1784b2f29b7c6f9392708 100644 (file)
@@ -1,6 +1,5 @@
 git-write-tree(1)
 =================
-v0.1, May 2005
 
 NAME
 ----
@@ -39,5 +38,5 @@ Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel
 
 GIT
 ---
-Part of the link:git.html[git] suite
+Part of the gitlink:git[7] suite
 
index bec562e30e9aad32863d3ea85a283a993453715c..2f9622f5ac492140427e752ebd163cb22d003d8d 100644 (file)
@@ -1,6 +1,5 @@
 git(7)
 ======
-v0.99.6, Sep 2005
 
 NAME
 ----
@@ -21,8 +20,8 @@ at the link:tutorial.html[tutorial] document.
 
 The <<Discussion>> section below contains much useful definition and
 clarification info - read that first.  And of the commands, I suggest
-reading link:git-update-index.html[git-update-index] and
-link:git-read-tree.html[git-read-tree] first - I wish I had!
+reading gitlink:git-update-index[1] and
+gitlink:git-read-tree[1] first - I wish I had!
 
 If you are migrating from CVS, link:cvs-migration.html[cvs migration]
 document may be helpful after you finish the tutorial.
@@ -55,105 +54,100 @@ SCMs layered over git.
 
 Manipulation commands
 ~~~~~~~~~~~~~~~~~~~~~
-link:git-apply.html[git-apply]::
+gitlink:git-apply[1]::
        Reads a "diff -up1" or git generated patch file and
        applies it to the working tree.
 
-link:git-checkout-index.html[git-checkout-index]::
+gitlink:git-checkout-index[1]::
        Copy files from the cache to the working directory
-       Previously this command was known as git-checkout-cache.
 
-link:git-commit-tree.html[git-commit-tree]::
+gitlink:git-commit-tree[1]::
        Creates a new commit object
 
-link:git-hash-object.html[git-hash-object]::
+gitlink:git-hash-object[1]::
        Computes the object ID from a file.
 
-link:git-init-db.html[git-init-db]::
+gitlink:git-index-pack.html[1]::
+       Build pack index file for an existing packed archive.
+
+gitlink:git-init-db[1]::
        Creates an empty git object database
 
-link:git-merge-index.html[git-merge-index]::
+gitlink:git-merge-index[1]::
        Runs a merge for files needing merging
-       Previously this command was known as git-merge-cache.
 
-link:git-mktag.html[git-mktag]::
+gitlink:git-mktag[1]::
        Creates a tag object
 
-link:git-pack-objects.html[git-pack-objects]::
+gitlink:git-pack-objects[1]::
        Creates a packed archive of objects.
 
-link:git-prune-packed.html[git-prune-packed]::
+gitlink:git-prune-packed[1]::
        Remove extra objects that are already in pack files.
 
-link:git-read-tree.html[git-read-tree]::
+gitlink:git-read-tree[1]::
        Reads tree information into the directory cache
 
-link:git-unpack-objects.html[git-unpack-objects]::
+gitlink:git-unpack-objects[1]::
        Unpacks objects out of a packed archive.
 
-link:git-update-index.html[git-update-index]::
+gitlink:git-update-index[1]::
        Modifies the index or directory cache
-       Previously this command was known as git-update-cache.
 
-link:git-write-tree.html[git-write-tree]::
+gitlink:git-write-tree[1]::
        Creates a tree from the current cache
 
 
 Interrogation commands
 ~~~~~~~~~~~~~~~~~~~~~~
 
-link:git-cat-file.html[git-cat-file]::
+gitlink:git-cat-file[1]::
        Provide content or type information for repository objects
 
-link:git-diff-index.html[git-diff-index]::
+gitlink:git-diff-index[1]::
        Compares content and mode of blobs between the cache and repository
-       Previously this command was known as git-diff-cache.
 
-link:git-diff-files.html[git-diff-files]::
+gitlink:git-diff-files[1]::
        Compares files in the working tree and the cache
 
-link:git-diff-stages.html[git-diff-stages]::
+gitlink:git-diff-stages[1]::
        Compares two "merge stages" in the index file.
 
-link:git-diff-tree.html[git-diff-tree]::
+gitlink:git-diff-tree[1]::
        Compares the content and mode of blobs found via two tree objects
 
-link:git-export.html[git-export]::
-       Exports each commit and a diff against each of its parents
-
-link:git-fsck-objects.html[git-fsck-objects]::
+gitlink:git-fsck-objects[1]::
        Verifies the connectivity and validity of the objects in the database
-       Previously this command was known as git-fsck-cache.
 
-link:git-ls-files.html[git-ls-files]::
+gitlink:git-ls-files[1]::
        Information about files in the cache/working directory
 
-link:git-ls-tree.html[git-ls-tree]::
+gitlink:git-ls-tree[1]::
        Displays a tree object in human readable form
 
-link:git-merge-base.html[git-merge-base]::
+gitlink:git-merge-base[1]::
        Finds as good a common ancestor as possible for a merge
 
-link:git-rev-list.html[git-rev-list]::
-       Lists commit objects in reverse chronological order
+gitlink:git-name-rev[1]::
+       Find symbolic names for given revs
 
-link:git-rev-tree.html[git-rev-tree]::
-       Provides the revision tree for one or more commits
+gitlink:git-rev-list[1]::
+       Lists commit objects in reverse chronological order
 
-link:git-show-index.html[git-show-index]::
+gitlink:git-show-index[1]::
        Displays contents of a pack idx file.
 
-link:git-tar-tree.html[git-tar-tree]::
+gitlink:git-tar-tree[1]::
        Creates a tar archive of the files in the named tree
 
-link:git-unpack-file.html[git-unpack-file]::
+gitlink:git-unpack-file[1]::
        Creates a temporary file with a blob's contents
 
-link:git-var.html[git-var]::
+gitlink:git-var[1]::
        Displays a git logical variable
 
-link:git-verify-pack.html[git-verify-pack]::
-       Validates packed GIT archive files
+gitlink:git-verify-pack[1]::
+       Validates packed git archive files
 
 The interrogate commands may create files - and you can force them to
 touch the working file set - but in general they don't
@@ -162,43 +156,42 @@ touch the working file set - but in general they don't
 Synching repositories
 ~~~~~~~~~~~~~~~~~~~~~
 
-link:git-clone-pack.html[git-clone-pack]::
+gitlink:git-clone-pack[1]::
        Clones a repository into the current repository (engine
        for ssh and local transport)
 
-link:git-fetch-pack.html[git-fetch-pack]::
+gitlink:git-fetch-pack[1]::
        Updates from a remote repository.
 
-link:git-http-fetch.html[git-http-fetch]::
-       Downloads a remote GIT repository via HTTP
-       Previously this command was known as git-http-pull.
+gitlink:git-http-fetch[1]::
+       Downloads a remote git repository via HTTP
 
-link:git-local-fetch.html[git-local-fetch]::
-       Duplicates another GIT repository on a local system
-       Previously this command was known as git-local-pull.
+gitlink:git-local-fetch[1]::
+       Duplicates another git repository on a local system
 
-link:git-peek-remote.html[git-peek-remote]::
+gitlink:git-peek-remote[1]::
        Lists references on a remote repository using upload-pack protocol.
 
-link:git-receive-pack.html[git-receive-pack]::
+gitlink:git-receive-pack[1]::
        Invoked by 'git-send-pack' to receive what is pushed to it.
 
-link:git-send-pack.html[git-send-pack]::
+gitlink:git-send-pack[1]::
        Pushes to a remote repository, intelligently.
 
-link:git-ssh-fetch.html[git-ssh-fetch]::
+gitlink:git-shell[1]::
+       Restricted shell for GIT-only SSH access.
+
+gitlink:git-ssh-fetch[1]::
        Pulls from a remote repository over ssh connection
-       Previously this command was known as git-ssh-pull.
 
-link:git-ssh-upload.html[git-ssh-upload]::
+gitlink:git-ssh-upload[1]::
        Helper "server-side" program used by git-ssh-fetch
-       Previously this command was known as git-ssh-push.
 
-link:git-update-server-info.html[git-update-server-info]::
+gitlink:git-update-server-info[1]::
        Updates auxiliary information on a dumb server to help
        clients discover references and packs on it.
 
-link:git-upload-pack.html[git-upload-pack]::
+gitlink:git-upload-pack[1]::
        Invoked by 'git-clone-pack' and 'git-fetch-pack' to push
        what are asked for.
 
@@ -206,114 +199,97 @@ link:git-upload-pack.html[git-upload-pack]::
 Porcelain-ish Commands
 ----------------------
 
-link:git-add.html[git-add]::
+gitlink:git-add[1]::
        Add paths to the index file.
-       Previously this command was known as git-add-script.
 
-link:git-applymbox.html[git-applymbox]::
+gitlink:git-am[1]::
+       Apply patches from a mailbox, but cooler.
+
+gitlink:git-applymbox[1]::
        Apply patches from a mailbox.
 
-link:git-bisect.html[git-bisect]::
+gitlink:git-bisect[1]::
        Find the change that introduced a bug.
-       Previously this command was known as git-bisect-script.
 
-link:git-branch.html[git-branch]::
+gitlink:git-branch[1]::
        Create and Show branches.
-       Previously this command was known as git-branch-script.
 
-link:git-checkout.html[git-checkout]::
+gitlink:git-checkout[1]::
        Checkout and switch to a branch.
-       Previously this command was known as git-checkout-script.
 
-link:git-cherry-pick.html[git-cherry-pick]::
+gitlink:git-cherry-pick[1]::
        Cherry-pick the effect of an existing commit.
-       Previously this command was known as git-cherry-pick-script.
 
-link:git-clone.html[git-clone]::
+gitlink:git-clone[1]::
        Clones a repository into a new directory.
-       Previously this command was known as git-clone-script.
 
-link:git-commit.html[git-commit]::
+gitlink:git-commit[1]::
        Record changes to the repository.
-       Previously this command was known as git-commit-script.
 
-link:git-diff.html[git-diff]::
+gitlink:git-diff[1]::
        Show changes between commits, commit and working tree, etc.
-       Previously this command was known as git-diff-script.
 
-link:git-fetch.html[git-fetch]::
+gitlink:git-fetch[1]::
        Download from a remote repository via various protocols.
-       Previously this command was known as git-fetch-script.
 
-link:git-format-patch.html[git-format-patch]::
+gitlink:git-format-patch[1]::
        Prepare patches for e-mail submission.
-       Previously this command was known as git-format-patch-script.
 
-link:git-grep.html[git-grep]::
+gitlink:git-grep[1]::
        Print lines matching a pattern
 
-link:git-log.html[git-log]::
+gitlink:git-log[1]::
        Shows commit logs.
-       Previously this command was known as git-log-script.
 
-link:git-ls-remote.html[git-ls-remote]::
+gitlink:git-ls-remote[1]::
        Shows references in a remote or local repository.
-       Previously this command was known as git-ls-remote-script.
 
-link:git-merge.html[git-merge]::
+gitlink:git-merge[1]::
        Grand unified merge driver.
 
-link:git-octopus.html[git-octopus]::
+gitlink:git-mv[1]::
+       Move or rename a file, a directory, or a symlink.
+
+gitlink:git-octopus[1]::
        Merge more than two commits.
-       Previously this command was known as git-octopus-script.
 
-link:git-pull.html[git-pull]::
+gitlink:git-pull[1]::
        Fetch from and merge with a remote repository.
-       Previously this command was known as git-pull-script.
 
-link:git-push.html[git-push]::
+gitlink:git-push[1]::
        Update remote refs along with associated objects.
-       Previously this command was known as git-push-script.
 
-link:git-rebase.html[git-rebase]::
+gitlink:git-rebase[1]::
        Rebase local commits to new upstream head.
-       Previously this command was known as git-rebase-script.
 
-link:git-rename.html[git-rename]::
+gitlink:git-rename[1]::
        Rename files and directories.
-       Previously this command was known as git-rename-script.
 
-link:git-repack.html[git-repack]::
+gitlink:git-repack[1]::
        Pack unpacked objects in a repository.
-       Previously this command was known as git-repack-script.
 
-link:git-reset.html[git-reset]::
+gitlink:git-reset[1]::
        Reset current HEAD to the specified state.
-       Previously this command was known as git-reset-script.
 
-link:git-resolve.html[git-resolve]::
+gitlink:git-resolve[1]::
        Merge two commits.
-       Previously this command was known as git-resolve-script.
 
-link:git-revert.html[git-revert]::
+gitlink:git-revert[1]::
        Revert an existing commit.
-       Previously this command was known as git-revert-script.
 
-link:git-shortlog.html[git-shortlog]::
+gitlink:git-shortlog[1]::
        Summarizes 'git log' output.
 
-link:git-show-branch.html[git-show-branch]::
+gitlink:git-show-branch[1]::
        Show branches and their commits.
 
-link:git-status.html[git-status]::
+gitlink:git-status[1]::
        Shows the working tree status.
-       Previously this command was known as git-status-script.
 
-link:git-verify-tag.html[git-verify-tag]::
+gitlink:git-verify-tag[1]::
        Check the GPG signature of tag.
-       Previously this command was known as git-verify-tag-script.
 
-link:git-whatchanged.html[git-whatchanged]::
+gitlink:git-whatchanged[1]::
        Shows commit logs and differences they introduce.
 
 
@@ -321,95 +297,126 @@ Ancillary Commands
 ------------------
 Manipulators:
 
-link:git-applypatch.html[git-applypatch]::
+gitlink:git-applypatch[1]::
        Apply one patch extracted from an e-mail.
 
-link:git-archimport.html[git-archimport]::
+gitlink:git-archimport[1]::
        Import an arch repository into git.
-       Previously this command was known as git-archimport-script.
 
-link:git-convert-objects.html[git-convert-objects]::
-       Converts old-style GIT repository
-       Previously this command was known as git-convert-cache.
+gitlink:git-convert-objects[1]::
+       Converts old-style git repository
 
-link:git-cvsimport.html[git-cvsimport]::
+gitlink:git-cvsimport[1]::
        Salvage your data out of another SCM people love to hate.
-       Previously this command was known as git-cvsimport-script.
 
-link:git-merge-one-file.html[git-merge-one-file]::
+gitlink:git-merge-one-file[1]::
        The standard helper program to use with "git-merge-index"
-       Previously this command was known as git-merge-one-file-script.
 
-link:git-prune.html[git-prune]::
+gitlink:git-prune[1]::
        Prunes all unreachable objects from the object database
-       Previously this command was known as git-prune-script.
 
-link:git-relink.html[git-relink]::
+gitlink:git-relink[1]::
        Hardlink common objects in local repositories.
-       Previously this command was known as git-relink-script.
 
-link:git-sh-setup.html[git-sh-setup]::
+gitlink:git-svnimport[1]::
+       Import a SVN repository into git.
+
+gitlink:git-sh-setup[1]::
        Common git shell script setup code.
-       Previously this command was known as git-sh-setup-script.
 
-link:git-tag.html[git-tag]::
+gitlink:git-symbolic-ref[1]::
+       Read and modify symbolic refs
+
+gitlink:git-tag[1]::
        An example script to create a tag object signed with GPG
-       Previously this command was known as git-tag-script.
+
+gitlink:git-update-ref[1]::
+       Update the object name stored in a ref safely.
 
 
 Interrogators:
 
-link:git-cherry.html[git-cherry]::
+gitlink:git-check-ref-format[1]::
+       Make sure ref name is well formed.
+
+gitlink:git-cherry[1]::
        Find commits not merged upstream.
 
-link:git-count-objects.html[git-count-objects]::
+gitlink:git-count-objects[1]::
        Count unpacked number of objects and their disk consumption.
-       Previously this command was known as git-count-objects-script.
 
-link:git-daemon.html[git-daemon]::
-       A really simple server for GIT repositories.
+gitlink:git-daemon[1]::
+       A really simple server for git repositories.
 
-link:git-diff-helper.html[git-diff-helper]::
-       Generates patch format output for git-diff-*
-
-link:git-get-tar-commit-id.html[git-get-tar-commit-id]::
+gitlink:git-get-tar-commit-id[1]::
        Extract commit ID from an archive created using git-tar-tree.
 
-link:git-mailinfo.html[git-mailinfo]::
+gitlink:git-mailinfo[1]::
        Extracts patch from a single e-mail message.
 
-link:git-mailsplit.html[git-mailsplit]::
+gitlink:git-mailsplit[1]::
        git-mailsplit.
 
-link:git-patch-id.html[git-patch-id]::
+gitlink:git-patch-id[1]::
        Compute unique ID for a patch.
 
-link:git-parse-remote.html[git-parse-remote]::
+gitlink:git-parse-remote[1]::
        Routines to help parsing $GIT_DIR/remotes/
-       Previously this command was known as git-parse-remote-script.
 
-link:git-request-pull.html[git-request-pull]::
+gitlink:git-request-pull[1]::
        git-request-pull.
-       Previously this command was known as git-request-pull-script.
 
-link:git-rev-parse.html[git-rev-parse]::
+gitlink:git-rev-parse[1]::
        Pick out and massage parameters.
 
-link:git-send-email.html[git-send-email]::
+gitlink:git-send-email[1]::
        Send patch e-mails out of "format-patch --mbox" output.
-       Previously this command was known as git-send-email-script.
 
-link:git-stripspace.html[git-stripspace]::
+gitlink:git-symbolic-refs[1]::
+       Read and modify symbolic refs.
+
+gitlink:git-stripspace[1]::
        Filter out empty lines.
 
 
 Commands not yet documented
 ---------------------------
 
-link:gitk.html[gitk]::
+gitlink:gitk[1]::
        gitk.
 
 
+Configuration Mechanism
+-----------------------
+
+Starting from 0.99.9 (actually mid 0.99.8.GIT), .git/config file
+is used to hold per-repository configuration options.  It is a
+simple text file modelled after `.ini` format familiar to some
+people.  Here is an example:
+
+------------
+#
+# This is the config file, and
+# a '#' or ';' character indicates
+# a comment
+#
+
+; core variables
+[core]
+       ; Don't trust file modes
+       filemode = false
+
+; user identity
+[user]
+       name = "Junio C Hamano"
+       email = "junkio@twinsun.com"
+
+------------
+
+Various commands read from the configuration file and adjust
+their operation accordingly.
+
+
 Identifier Terminology
 ----------------------
 <object>::
@@ -511,16 +518,16 @@ git Commits
 'GIT_AUTHOR_DATE'::
 'GIT_COMMITTER_NAME'::
 'GIT_COMMITTER_EMAIL'::
-       see link:git-commit-tree.html[git-commit-tree]
+       see gitlink:git-commit-tree[1]
 
 git Diffs
 ~~~~~~~~~
 'GIT_DIFF_OPTS'::
 'GIT_EXTERNAL_DIFF'::
        see the "generating patches" section in :
-       link:git-diff-index.html[git-diff-index];
-       link:git-diff-files.html[git-diff-files];
-       link:git-diff-tree.html[git-diff-tree]
+       gitlink:git-diff-index[1];
+       gitlink:git-diff-files[1];
+       gitlink:git-diff-tree[1]
 
 Discussion[[Discussion]]
 ------------------------
@@ -536,5 +543,5 @@ Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel
 
 GIT
 ---
-Part of the link:git.html[git] suite
+Part of the gitlink:git[7] suite
 
index 8e2f479f2545414fe0be47da55a283545c73fec4..eb126d7a4b02c979709dff716eb9c9a64c169952 100644 (file)
@@ -24,6 +24,19 @@ OPTIONS
        Some argument not yet documented.
 
 
+Examples
+--------
+gitk v2.6.12.. include/scsi drivers/scsi::
+
+       Show as the changes since version 'v2.6.12' that changed any
+       file in the include/scsi or drivers/scsi subdirectories
+
+gitk --since="2 weeks ago" -- gitk::
+
+       Show the changes during the last two weeks to the file 'gitk'.
+       The "--" is necessary to avoid confusion with the *branch* named
+       'gitk'
+
 Author
 ------
 Written by Paul Mackerras <paulus@samba.org>
@@ -34,5 +47,5 @@ Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
 
 GIT
 ---
-Part of the link:git.html[git] suite
+Part of the gitlink:git[7] suite
 
index a069b7bb0cfbdacfc0ac2dd2183981b806792805..eb7b4710246c627d178c518d39c565ce48a02f02 100644 (file)
@@ -1,5 +1,5 @@
 object::
-       The unit of storage in GIT. It is uniquely identified by
+       The unit of storage in git. It is uniquely identified by
        the SHA1 of its contents. Consequently, an object can not
        be changed.
 
index 57f47208716fd1f51f40d8d8104b4e764e0a013d..7ee3571bc09642f07ec50d5b3cfc9d802119889e 100644 (file)
@@ -1,6 +1,5 @@
-Hooks used by GIT
+Hooks used by git
 =================
-v0.99.6, Sep 2005
 
 Hooks are little scripts you can place in `$GIT_DIR/hooks`
 directory to trigger action at certain points.  When
index b3b4d2c97aa4ccdffc3772ef4e745e14d323a995..d30fa850480446a6682430db2a327986e6f65e69 100644 (file)
@@ -245,7 +245,7 @@ gb=$(tput setab 2)
 rb=$(tput setab 1)
 restore=$(tput setab 9)
 
-if [ `git-rev-tree release ^test | wc -c` -gt 0 ]
+if [ `git-rev-list release ^test | wc -c` -gt 0 ]
 then
        echo $rb Warning: commits in release that are not in test $restore
        git-whatchanged release ^test
@@ -262,7 +262,7 @@ do
        status=
        for ref in test release linus
        do
-               if [ `git-rev-tree $branch ^$ref | wc -c` -gt 0 ]
+               if [ `git-rev-list $branch ^$ref | wc -c` -gt 0 ]
                then
                        status=$status${ref:0:1}
                fi
index d593ab988be48ba9f9d910e9fdbc73e2837241cc..50638c78d5a4067320db4519131d224c55b3925a 100755 (executable)
@@ -2,7 +2,7 @@
 
 T="$1"
 
-for h in *.html howto/*.txt howto/*.html
+for h in *.html *.txt howto/*.txt howto/*.html
 do
        diff -u -I'Last updated [0-9][0-9]-[A-Z][a-z][a-z]-' "$T/$h" "$h" || {
                echo >&2 "# install $h $T/$h"
@@ -12,7 +12,7 @@ do
        }
 done
 strip_leading=`echo "$T/" | sed -e 's|.|.|g'`
-for th in "$T"/*.html "$T"/howto/*.txt "$T"/howto/*.html
+for th in "$T"/*.html "$T"/*.txt "$T"/howto/*.txt "$T"/howto/*.html
 do
        h=`expr "$th" : "$strip_leading"'\(.*\)'`
        case "$h" in
diff --git a/Documentation/merge-pull-opts.txt b/Documentation/merge-pull-opts.txt
new file mode 100644 (file)
index 0000000..d9164a0
--- /dev/null
@@ -0,0 +1,14 @@
+-n, --no-summary::
+       Do not show diffstat at the end of the merge.
+
+--no-commit::
+       Perform the merge but pretend the merge failed and do
+       not autocommit, to give the user a chance to inspect and
+       further tweak the merge result before committing.
+
+-s <strategy>::
+       use that merge strategy; can be given more than once to
+       specify them in the order they should be tried.  If
+       there is no `-s` option, built-in list of strategies is
+       used instead (`git-merge-resolve` when merging a single
+       head, `git-merge-octopus` otherwise).
diff --git a/Documentation/merge-strategies.txt b/Documentation/merge-strategies.txt
new file mode 100644 (file)
index 0000000..3ec56d2
--- /dev/null
@@ -0,0 +1,35 @@
+MERGE STRATEGIES
+----------------
+
+resolve::
+       This can only resolve two heads (i.e. the current branch
+       and another branch you pulled from) using 3-way merge
+       algorithm.  It tries to carefully detect criss-cross
+       merge ambiguities and is considered generally safe and
+       fast.  This is the default merge strategy when pulling
+       one branch.
+
+recursive::
+       This can only resolve two heads using 3-way merge
+       algorithm.  When there are more than one common
+       ancestors that can be used for 3-way merge, it creates a
+       merged tree of the common ancestores and uses that as
+       the reference tree for the 3-way merge.  This has been
+       reported to result in fewer merge conflicts without
+       causing mis-merges by tests done on actual merge commits
+       taken from Linux 2.6 kernel development history.
+       Additionally this can detect and handle merges involving
+       renames.
+
+octopus::
+       This resolves more than two-head case, but refuses to do
+       complex merge that needs manual resolution.  It is
+       primarily meant to be used for bundling topic branch
+       heads together.  This is the default merge strategy when
+       pulling more than one branch.
+
+ours::
+       This resolves any number of heads, but the result of the
+       merge is always the current branch head.  It is meant to
+       be used to supersede old development history of side
+       branches.
index 8642182c89109b50eecc741cc71dde90f27d9048..5c2888e16362798a494b146036e721a19f622517 100644 (file)
 <repository>::
-       The "remote" repository to pull from.  One of the
-       following notations can be used to name the repository
-       to pull from:
-
-               Rsync URL
-                       rsync://remote.machine/path/to/repo.git/
-
-               HTTP(s) URL
-                       http://remote.machine/path/to/repo.git/
-
-               GIT URL
-                       git://remote.machine/path/to/repo.git/
-                       remote.machine:/path/to/repo.git/
-
-               Local directory
-                       /path/to/repo.git/
-
-       In addition to the above, as a short-hand, the name of a
-       file in $GIT_DIR/remotes directory can be given; the
-       named file should be in the following format:
-
-               URL: one of the above URL format
-               Push: <refspec>...
-               Pull: <refspec>...
-
-       When such a short-hand is specified in place of
-       <repository> without <refspec> parameters on the command
-       line, <refspec>... specified on Push lines or Pull lines
-       are used for "git push" and "git fetch/pull",
-       respectively.
-
-       The name of a file in $GIT_DIR/branches directory can be
-       specified as an older notation short-hand; the named
-       file should contain a single line, a URL in one of the
-       above formats, optionally followed by a hash '#' and the
-       name of remote head (URL fragment notation).
-       $GIT_DIR/branches/<remote> file that stores a <url>
-       without the fragment is equivalent to have this in the
-       corresponding file in the $GIT_DIR/remotes/ directory
-
-               URL: <url>
-               Pull: refs/heads/master:<remote>
-
-       while having <url>#<head> is equivalent to
-
-               URL: <url>
-               Pull: refs/heads/<head>:<remote>
+       The "remote" repository that is the source of a fetch
+       or pull operation, or the destination of a push operation.
+       One of the following notations can be used
+       to name the remote repository:
++
+===============================================================
+- Rsync URL:           rsync://remote.machine/path/to/repo.git/
+- HTTP(s) URL:         http://remote.machine/path/to/repo.git/
+- git URL:             git://remote.machine/path/to/repo.git/
+                       or remote.machine:/path/to/repo.git/
+- Local directory:     /path/to/repo.git/
+===============================================================
++
+In addition to the above, as a short-hand, the name of a
+file in `$GIT_DIR/remotes` directory can be given; the
+named file should be in the following format:
++
+       URL: one of the above URL format
+       Push: <refspec>
+       Pull: <refspec>
++
+When such a short-hand is specified in place of
+<repository> without <refspec> parameters on the command
+line, <refspec> specified on `Push:` lines or `Pull:`
+lines are used for `git-push` and `git-fetch`/`git-pull`,
+respectively.  Multiple `Push:` and and `Pull:` lines may
+be specified for additional branch mappings.
++
+The name of a file in `$GIT_DIR/branches` directory can be
+specified as an older notation short-hand; the named
+file should contain a single line, a URL in one of the
+above formats, optionally followed by a hash `#` and the
+name of remote head (URL fragment notation).
+`$GIT_DIR/branches/<remote>` file that stores a <url>
+without the fragment is equivalent to have this in the
+corresponding file in the `$GIT_DIR/remotes/` directory.
++
+       URL: <url>
+       Pull: refs/heads/master:<remote>
++
+while having `<url>#<head>` is equivalent to
++
+       URL: <url>
+       Pull: refs/heads/<head>:<remote>
 
 <refspec>::
        The canonical format of a <refspec> parameter is
-       '+?<src>:<dst>'; that is, an optional plus '+', followed
-       by the source ref, followed by a colon ':', followed by
+       `+?<src>:<dst>`; that is, an optional plus `+`, followed
+       by the source ref, followed by a colon `:`, followed by
        the destination ref.
++
+When used in `git-push`, the <src> side can be an
+arbitrary "SHA1 expression" that can be used as an
+argument to `git-cat-file -t`.  E.g. `master~4` (push
+four parents before the current master head).
++
+For `git-push`, the local ref that matches <src> is used
+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.
++
+For `git-fetch` and `git-pull`, the remote ref that matches <src>
+is fetched, and if <dst> is not empty string, the local
+ref that matches it is fast forwarded using <src>.
+Again, if the optional plus `+` is used, the local ref
+is updated even if it does not result in a fast forward
+update.
++
+[NOTE]
+If the remote branch from which you want to pull is
+modified in non-linear ways such as being rewound and
+rebased frequently, then a pull will attempt a merge with
+an older version of itself, likely conflict, and fail.
+It is under these conditions that you would want to use
+the `+` sign to indicate non-fast-forward updates will
+be needed.  There is currently no easy way to determine
+or declare that a branch will be made available in a
+repository with this behavior; the pulling user simply
+must know this is the expected usage pattern for a branch.
++
+[NOTE]
+You never do your own development on branches that appear
+on the right hand side of a <refspec> colon on `Pull:` lines;
+they are to be updated by `git-fetch`.  If you intend to do
+development derived from a remote branch `B`, have a `Pull:`
+line to track it (i.e. `Pull: B:remote-B`), and have a separate
+branch `my-B` to do your development on top of it.  The latter
+is created by `git branch my-B remote-B` (or its equivalent `git
+checkout -b my-B remote-B`).  Run `git fetch` to keep track of
+the progress of the remote side, and when you see something new
+on the remote branch, merge it into your development branch with
+`git pull . remote-B`, while you are on `my-B` branch.
+The common `Pull: master:origin` mapping of a remote `master`
+branch to a local `origin` branch, which is then merged to a
+ocal development branch, again typically named `master`, is made
+when you run `git clone` for you to follow this pattern.
++
+[NOTE]
+There is a difference between listing multiple <refspec>
+directly on `git-pull` command line and having multiple
+`Pull:` <refspec> lines for a <repository> and running
+`git-pull` command without any explicit <refspec> parameters.
+<refspec> listed explicitly on the command line are always
+merged into the current branch after fetching.  In other words,
+if you list more than one remote refs, you would be making
+an Octopus.  While `git-pull` run without any explicit <refspec>
+parameter takes default <refspec>s from `Pull:` lines, it
+merges only the first <refspec> found into the current branch,
+after fetching all the remote refs.  This is because making an
+Octopus from remote refs is rarely done, while keeping track
+of multiple remote heads in one-go by fetching more than one
+is often useful.
++
+Some short-cut notations are also supported.
++
+* For backward compatibility, `tag` is almost ignored;
+  it just makes the following parameter <tag> to mean a
+  refspec `refs/tags/<tag>:refs/tags/<tag>`.
+* A parameter <ref> without a colon is equivalent to
+  <ref>: when pulling/fetching, and <ref>`:`<ref> when
+  pushing.  That is, do not store it locally if
+  fetching, and update the same name if pushing.
 
-       When used in "git push", the <src> side can be an
-       arbitrary "SHA1 expression" that can be used as an
-       argument to "git-cat-file -t".  E.g. "master~4" (push
-       four parents before the current master head).
-
-        For "git push", the local ref that matches <src> is used
-        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.
-
-       For "git fetch/pull", the remote ref that matches <src>
-       is fetched, and if <dst> is not empty string, the local
-       ref that matches it is fast forwarded using <src>.
-       Again, if the optional plus '+' is used, the local ref
-       is updated even if it does not result in a fast forward
-       update.
-
-       Some short-cut notations are also supported.
-
-       * For backward compatibility, "tag" is almost ignored;
-          it just makes the following parameter <tag> to mean a
-          refspec "refs/tags/<tag>:refs/tags/<tag>".
-
-        * A parameter <ref> without a colon is equivalent to
-          <ref>: when pulling/fetching, and <ref>:<ref> when
-          pushing.  That is, do not store it locally if
-          fetching, and update the same name if pushing.
-
--a, \--append::
-       Append ref names and object names of fetched refs to the
-       existing contents of $GIT_DIR/FETCH_HEAD.  Without this
-       option old data in $GIT_DIR/FETCH_HEAD will be overwritten.
-
--f, \--force::
-       Usually, the command refuses to update a local ref that is
-       not an ancestor of the remote ref used to overwrite it.
-       This flag disables the check.  What this means is that the
-       local repository can lose commits; use it with care.
index d20fa80d872b94bdf56d95ac41ce9341c777281a..1b5f2282411486ad903939ed2e56997a27484670 100644 (file)
@@ -1,6 +1,5 @@
-GIT repository layout
+git repository layout
 =====================
-v0.99.5, Sep 2005
 
 You may find these things in your git repository (`.git`
 directory for a repository associated with your working tree, or
@@ -120,7 +119,7 @@ info/grafts::
 info/exclude::
        This file, by convention among Porcelains, stores the
        exclude pattern list.  `git status` looks at it, but
-       otherwise it is not looked at by any of the core GIT
+       otherwise it is not looked at by any of the core git
        commands.
 
 remotes::
index b3a9915845171d1b1afb7d1aa95cfc8d5e18dfe7..24c84100b0790be22330464e01ea876e5d30fc9a 100644 (file)
@@ -10,6 +10,9 @@ This replaces the index with a different tree, keeping the stat info
 for entries that don't change, and allowing -u to make the minimum
 required changes to the working tree to have it match.
 
+Entries marked '+' have stat information. Spaces marked '*' don't
+affect the result.
+
    index   tree    result
    -----------------------
    *       (empty) (empty)
@@ -20,7 +23,30 @@ required changes to the working tree to have it match.
 Two-way merge
 -------------
 
+It is permitted for the index to lack an entry; this does not prevent
+any case from applying.
+
+If the index exists, it is an error for it not to match either the old
+or the result.
+
+If multiple cases apply, the one used is listed first.
+
+A result which changes the index is an error if the index is not empty
+and not up-to-date.
+
+Entries marked '+' have stat information. Spaces marked '*' don't
+affect the result.
 
+ case  index   old     new     result
+ -------------------------------------
+ 0/2   (empty) *       (empty) (empty)
+ 1/3   (empty) *       new     new
+ 4/5   index+  (empty) (empty) index+
+ 6/7   index+  (empty) index   index+
+ 10    index+  index   (empty) (empty)
+ 14/15 index+  old     old     index+
+ 18/19 index+  old     index   index+
+ 20    index+  index   new     new
 
 Three-way merge
 ---------------
@@ -44,30 +70,28 @@ up-to-date.
 *empty* means that the tree must not have a directory-file conflict
  with the entry.
 
-For multiple ancestors or remotes, a '+' means that this case applies
-even if only one ancestor or remote fits; normally, all of the
-ancestors or remotes must be the same.
+For multiple ancestors, a '+' means that this case applies even if
+only one ancestor or remote fits; a '^' means all of the ancestors
+must be the same.
 
 case  ancest    head    remote    result
 ----------------------------------------
 1     (empty)+  (empty) (empty)   (empty)
 2ALT  (empty)+  *empty* remote    remote
-2ALT  (empty)+  *empty* remote    remote
 2     (empty)^  (empty) remote    no merge
 3ALT  (empty)+  head    *empty*   head
 3     (empty)^  head    (empty)   no merge
 4     (empty)^  head    remote    no merge
 5ALT  *         head    head      head
-6     ancest^   (empty) (empty)   no merge
-8ALT  ancest    (empty) ancest    (empty)
+6     ancest+   (empty) (empty)   no merge
+8     ancest^   (empty) ancest    no merge
 7     ancest+   (empty) remote    no merge
+10    ancest^   ancest  (empty)   no merge
 9     ancest+   head    (empty)   no merge
-10ALT ancest    ancest  (empty)   (empty)
-11    ancest+   head    remote    no merge
 16    anc1/anc2 anc1    anc2      no merge
 13    ancest+   head    ancest    head
 14    ancest+   ancest  remote    remote
-14ALT ancest+   ancest  remote    remote
+11    ancest+   head    remote    no merge
 
 Only #2ALT and #3ALT use *empty*, because these are the only cases
 where there can be conflicts that didn't exist before. Note that we
@@ -89,4 +113,9 @@ right. This is a case of a reverted patch (in some direction, maybe
 multiple times), and the right answer depends on looking at crossings
 of history or common ancestors of the ancestors.
 
-The status as of Sep 5 is that multiple remotes are not supported
\ No newline at end of file
+Note that, between #6, #7, #9, and #11, all cases not otherwise
+covered are handled in this table.
+
+For #8 and #10, there is alternative behavior, not currently
+implemented, where the result is (empty). As currently implemented,
+the automatic merge will generally give this effect.
index 928a22cd7844cb4e2754f3d0b7d7870a5b1b14ff..214673db06914c49c25038c4c70ac76c90dbeff5 100644 (file)
@@ -1,6 +1,5 @@
 A short git tutorial
 ====================
-v0.99.5, Aug 2005
 
 Introduction
 ------------
@@ -52,7 +51,9 @@ your new project. You will now have a `.git` directory, and you can
 inspect that with `ls`. For your new empty project, it should show you
 three entries, among other things:
 
- - a symlink called `HEAD`, pointing to `refs/heads/master`
+ - a symlink called `HEAD`, pointing to `refs/heads/master` (if your
+   platform does not have native symlinks, it is a file containing the
+   line "ref: refs/heads/master")
 +
 Don't worry about the fact that the file that the `HEAD` link points to
 doesn't even exist yet -- you haven't created the commit that will
@@ -161,7 +162,7 @@ you'll have to use the object name, not the filename of the object:
        git-cat-file -t 557db03de997c86a4a028e1ebd3a1ceb225be238
 
 where the `-t` tells `git-cat-file` to tell you what the "type" of the
-object is. Git will tell you that you have a "blob" object (ie just a
+object is. git will tell you that you have a "blob" object (ie just a
 regular file), and you can see the contents with
 
        git-cat-file "blob" 557db03
@@ -228,6 +229,7 @@ which will spit out
 
 ------------
 diff --git a/hello b/hello
+index 557db03..263414f 100644
 --- a/hello
 +++ b/hello
 @@ -1 +1,2 @@
@@ -290,13 +292,16 @@ also wants to get a commit message
 on its standard input, and it will write out the resulting object name for the
 commit to its standard output.
 
-And this is where we start using the `.git/HEAD` file. The `HEAD` file is
-supposed to contain the reference to the top-of-tree, and since that's
-exactly what `git-commit-tree` spits out, we can do this all with a simple
-shell pipeline:
+And this is where we create the `.git/refs/heads/master` file
+which is pointed at by `HEAD`. This file is supposed to contain
+the reference to the top-of-tree of the master branch, and since
+that's exactly what `git-commit-tree` spits out, we can do this
+all with a sequence of simple shell commands:
 
 ------------------------------------------------
-echo "Initial commit" | git-commit-tree $(git-write-tree) > .git/HEAD
+tree=$(git-write-tree)
+commit=$(echo 'Initial commit' | git-commit-tree $tree)
+git-update-ref HEAD $(commit)
 ------------------------------------------------
 
 which will say:
@@ -378,7 +383,7 @@ come from the working tree or not.
 
 This is not hard to understand, as soon as you realize that git simply
 never knows (or cares) about files that it is not told about
-explicitly. Git will never go *looking* for files to compare, it
+explicitly. git will never go *looking* for files to compare, it
 expects you to tell it what the files are, and that's what the index
 is there for.
 ================
@@ -432,8 +437,8 @@ message headers, and a few one-liners that actually do the
 commit itself (`git-commit`).
 
 
-Checking it out
----------------
+Inspecting Changes
+------------------
 
 While creating changes is useful, it's even more useful if you can tell
 later what changed. The most useful command for this is another of the
@@ -450,6 +455,41 @@ the same diff that we've already seen several times, we can now do
 (again, `-p` means to show the difference as a human-readable patch),
 and it will show what the last commit (in `HEAD`) actually changed.
 
+[NOTE]
+============
+Here is an ASCII art by Jon Loeliger that illustrates how
+various diff-\* commands compare things.
+
+                      diff-tree
+                       +----+
+                       |    |
+                       |    |
+                       V    V
+                    +-----------+
+                    | Object DB |
+                    |  Backing  |
+                    |   Store   |
+                    +-----------+
+                      ^    ^
+                      |    |
+                      |    |  diff-index --cached
+                      |    |
+          diff-index  |    V
+                      |  +-----------+
+                      |  |   Index   |
+                      |  |  "cache"  |
+                      |  +-----------+
+                      |    ^
+                      |    |
+                      |    |  diff-files
+                      |    |
+                      V    V
+                    +-----------+
+                    |  Working  |
+                    | Directory |
+                    +-----------+
+============
+
 More interestingly, you can also give `git-diff-tree` the `-v` flag, which
 tells it to also show the commit message and author and date of the
 commit, and you can tell it to show a whole series of diffs.
@@ -544,7 +584,7 @@ name for the state at that point.
 Copying repositories
 --------------------
 
-Git repositories are normally totally self-sufficient, and it's worth noting
+git repositories are normally totally self-sufficient, and it's worth noting
 that unlike CVS, for example, there is no separate notion of
 "repository" and "working tree". A git repository normally *is* the
 working tree, with the local git information hidden in the `.git`
@@ -692,7 +732,9 @@ other point in the history than the current `HEAD`, you can do so by
 just telling `git checkout` what the base of the checkout would be.
 In other words, if you have an earlier tag or branch, you'd just do
 
-       git checkout -b mybranch earlier-commit
+------------
+git checkout -b mybranch earlier-commit
+------------
 
 and it would create the new branch `mybranch` at the earlier commit,
 and check out the state at that time.
@@ -700,17 +742,29 @@ and check out the state at that time.
 
 You can always just jump back to your original `master` branch by doing
 
-       git checkout master
+------------
+git checkout master
+------------
 
 (or any other branch-name, for that matter) and if you forget which
 branch you happen to be on, a simple
 
-       ls -l .git/HEAD
+------------
+ls -l .git/HEAD
+------------
 
-will tell you where it's pointing. To get the list of branches
-you have, you can say
+will tell you where it's pointing (Note that on platforms with bad or no
+symlink support, you have to execute
 
-       git branch
+------------
+cat .git/HEAD
+------------
+
+instead). To get the list of branches you have, you can say
+
+------------
+git branch
+------------
 
 which is nothing more than a simple script around `ls .git/refs/heads`.
 There will be asterisk in front of the branch you are currently on.
@@ -718,7 +772,9 @@ There will be asterisk in front of the branch you are currently on.
 Sometimes you may wish to create a new branch _without_ actually
 checking it out and switching to it. If so, just use the command
 
-       git branch <branchname> [startingpoint]
+------------
+git branch <branchname> [startingpoint]
+------------
 
 which will simply _create_ the branch, but will not do anything further. 
 You can then later -- once you decide that you want to actually develop
@@ -844,7 +900,6 @@ $ git show-branch master mybranch
  ! [mybranch] Some work.
 --
 +  [master] Merged "mybranch" changes.
-+  [master~1] Some fun.
 ++ [mybranch] Some work.
 ------------------------------------------------
 
@@ -859,15 +914,22 @@ All of them have plus `+` characters in the first column, which
 means they are now part of the `master` branch. Only the "Some
 work" commit has the plus `+` character in the second column,
 because `mybranch` has not been merged to incorporate these
-commits from the master branch.
+commits from the master branch.  The string inside brackets
+before the commit log message is a short name you can use to
+name the commit.  In the above example, 'master' and 'mybranch'
+are branch heads.  'master~1' is the first parent of 'master'
+branch head.  Please see 'git-rev-parse' documentation if you
+see more complex cases.
 
 Now, let's pretend you are the one who did all the work in
 `mybranch`, and the fruit of your hard work has finally been merged
 to the `master` branch. Let's go back to `mybranch`, and run
 resolve to get the "upstream changes" back to your branch.
 
-       git checkout mybranch
-       git resolve HEAD master "Merge upstream changes."
+------------
+git checkout mybranch
+git resolve HEAD master "Merge upstream changes."
+------------
 
 This outputs something like this (the actual commit object names
 would be different)
@@ -946,7 +1008,7 @@ This transport is the same as SSH transport but uses `sh` to run
 both ends on the local machine instead of running other end on
 the remote machine via `ssh`.
 
-GIT Native::
+git Native::
        `git://remote.machine/path/to/repo.git/`
 +
 This transport was designed for anonymous downloading.  Like SSH
@@ -967,13 +1029,13 @@ necessary objects.  Because of this behaviour, they are
 sometimes also called 'commit walkers'.
 +
 The 'commit walkers' are sometimes also called 'dumb
-transports', because they do not require any GIT aware smart
-server like GIT Native transport does.  Any stock HTTP server
+transports', because they do not require any git aware smart
+server like git Native transport does.  Any stock HTTP server
 would suffice.
 +
 There are (confusingly enough) `git-ssh-fetch` and `git-ssh-upload`
 programs, which are 'commit walkers'; they outlived their
-usefulness when GIT Native and SSH transports were introduced,
+usefulness when git Native and SSH transports were introduced,
 and not used by `git pull` or `git push` scripts.
 
 Once you fetch from the remote repository, you `resolve` that
@@ -1001,7 +1063,9 @@ multiple working trees, but disk space is cheap these days.
 
 [NOTE]
 You could even pull from your own repository by
-giving '.' as <remote-repository> parameter to `git pull`.
+giving '.' as <remote-repository> parameter to `git pull`.  This
+is useful when you want to merge a local branch (or more, if you
+are making an Octopus) into the current branch.
 
 It is likely that you will be pulling from the same remote
 repository from time to time. As a short hand, you can store
@@ -1077,19 +1141,23 @@ done only once.
 on the remote machine. The communication between the two over
 the network internally uses an SSH connection.
 
-Your private repository's GIT directory is usually `.git`, but
+Your private repository's git directory is usually `.git`, but
 your public repository is often named after the project name,
 i.e. `<project>.git`. Let's create such a public repository for
 project `my-git`. After logging into the remote machine, create
 an empty directory:
 
-       mkdir my-git.git
+------------
+mkdir my-git.git
+------------
 
-Then, make that directory into a GIT repository by running
+Then, make that directory into a git repository by running
 `git init-db`, but this time, since its name is not the usual
 `.git`, we do things slightly differently:
 
-       GIT_DIR=my-git.git git-init-db
+------------
+GIT_DIR=my-git.git git-init-db
+------------
 
 Make sure this directory is available for others you want your
 changes to be pulled by via the transport of your choice. Also
@@ -1113,7 +1181,9 @@ Your "public repository" is now ready to accept your changes.
 Come back to the machine you have your private repository. From
 there, run this command:
 
-       git push <public-host>:/path/to/my-git.git master
+------------
+git push <public-host>:/path/to/my-git.git master
+------------
 
 This synchronizes your public repository to match the named
 branch head (i.e. `master` in this case) and objects reachable
@@ -1123,7 +1193,9 @@ As a real example, this is how I update my public git
 repository. Kernel.org mirror network takes care of the
 propagation to other publicly visible machines:
 
-       git push master.kernel.org:/pub/scm/git/git.git/ 
+------------
+git push master.kernel.org:/pub/scm/git/git.git/ 
+------------
 
 
 Packing your repository
@@ -1136,7 +1208,9 @@ not so convenient to transport over the network. Since git objects are
 immutable once they are created, there is a way to optimize the
 storage by "packing them together". The command
 
-       git repack
+------------
+git repack
+------------
 
 will do it for you. If you followed the tutorial examples, you
 would have accumulated about 17 objects in `.git/objects/??/`
@@ -1160,7 +1234,9 @@ Our programs are always perfect ;-).
 Once you have packed objects, you do not need to leave the
 unpacked objects that are contained in the pack file anymore.
 
-       git prune-packed
+------------
+git prune-packed
+------------
 
 would remove them for you.
 
@@ -1356,4 +1432,101 @@ fast forward.  You need to pull and merge those other changes
 back before you push your work when it happens.
 
 
+Bundling your work together
+---------------------------
+
+It is likely that you will be working on more than one thing at
+a time.  It is easy to use those more-or-less independent tasks
+using branches with git.
+
+We have already seen how branches work in a previous example,
+with "fun and work" example using two branches.  The idea is the
+same if there are more than two branches.  Let's say you started
+out from "master" head, and have some new code in the "master"
+branch, and two independent fixes in the "commit-fix" and
+"diff-fix" branches:
+
+------------
+$ git show-branch
+! [commit-fix] Fix commit message normalization.
+ ! [diff-fix] Fix rename detection.
+  * [master] Release candidate #1
+---
+ +  [diff-fix] Fix rename detection.
+ +  [diff-fix~1] Better common substring algorithm.
++   [commit-fix] Fix commit message normalization.
+  + [master] Release candidate #1
++++ [diff-fix~2] Pretty-print messages.
+------------
+
+Both fixes are tested well, and at this point, you want to merge
+in both of them.  You could merge in 'diff-fix' first and then
+'commit-fix' next, like this:
+
+------------
+$ git resolve master diff-fix 'Merge fix in diff-fix'
+$ git resolve master commit-fix 'Merge fix in commit-fix'
+------------
+
+Which would result in:
+
+------------
+$ git show-branch
+! [commit-fix] Fix commit message normalization.
+ ! [diff-fix] Fix rename detection.
+  * [master] Merge fix in commit-fix
+---
+  + [master] Merge fix in commit-fix
++ + [commit-fix] Fix commit message normalization.
+  + [master~1] Merge fix in diff-fix
+ ++ [diff-fix] Fix rename detection.
+ ++ [diff-fix~1] Better common substring algorithm.
+  + [master~2] Release candidate #1
++++ [master~3] Pretty-print messages.
+------------
+
+However, there is no particular reason to merge in one branch
+first and the other next, when what you have are a set of truly
+independent changes (if the order mattered, then they are not
+independent by definition).  You could instead merge those two
+branches into the current branch at once.  First let's undo what
+we just did and start over.  We would want to get the master
+branch before these two merges by resetting it to 'master~2':
+
+------------
+$ git reset --hard master~2
+------------
+
+You can make sure 'git show-branch' matches the state before
+those two 'git resolve' you just did.  Then, instead of running
+two 'git resolve' commands in a row, you would pull these two
+branch heads (this is known as 'making an Octopus'):
+
+------------
+$ git pull . commit-fix diff-fix
+$ git show-branch
+! [commit-fix] Fix commit message normalization.
+ ! [diff-fix] Fix rename detection.
+  * [master] Octopus merge of branches 'diff-fix' and 'commit-fix'
+---
+  + [master] Octopus merge of branches 'diff-fix' and 'commit-fix'
++ + [commit-fix] Fix commit message normalization.
+ ++ [diff-fix] Fix rename detection.
+ ++ [diff-fix~1] Better common substring algorithm.
+  + [master~1] Release candidate #1
++++ [master~2] Pretty-print messages.
+------------
+
+Note that you should not do Octopus because you can.  An octopus
+is a valid thing to do and often makes it easier to view the
+commit history if you are pulling more than two independent
+changes at the same time.  However, if you have merge conflicts
+with any of the branches you are merging in and need to hand
+resolve, that is an indication that the development happened in
+those branches were not independent after all, and you should
+merge two at a time, documenting how you resolved the conflicts,
+and the reason why you preferred changes made in one side over
+the other.  Otherwise it would make the project history harder
+to follow, not easier.
+
 [ to be continued.. cvsimports ]
diff --git a/INSTALL b/INSTALL
index 6e336d7c204ccb503fd7499ec9d9674da68e8318..bbb13f3fd98f96af5521226a8cdaf74f8c6732a2 100644 (file)
--- a/INSTALL
+++ b/INSTALL
@@ -46,6 +46,9 @@ Issues of note:
          transfer, you are probabaly OK if you do not have
          them.
 
+       - expat library; git-http-push uses it for remote lock
+         management over DAV.  Similar to "curl" above, this is optional.
+
        - "GNU diff" to generate patches.  Of course, you don't _have_ to
          generate patches if you don't want to, but let's face it, you'll
          be wanting to. Or why did you get git in the first place?
index cfde69cc015f4a6fffbb94efa5cc38a0987cdc82..56e0fb1ba60713c7d7559ffb3817c2d4ed074630 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -6,14 +6,24 @@
 # Define NO_OPENSSL environment variable if you do not have OpenSSL. You will
 # miss out git-rev-list --merge-order. This also implies MOZILLA_SHA1.
 #
-# Define NO_CURL if you do not have curl installed.  git-http-pull is not
-# built, and you cannot use http:// and https:// transports.
+# Define NO_CURL if you do not have curl installed.  git-http-pull and
+# git-http-push are not built, and you cannot use http:// and https://
+# transports.
+#
+# Define CURLDIR=/foo/bar if your curl header and library files are in
+# /foo/bar/include and /foo/bar/lib directories.
+#
+# Define NO_EXPAT if you do not have expat installed.  git-http-push is
+# not built, and you cannot push using http:// and https:// transports.
 #
 # Define NO_STRCASESTR if you don't have strcasestr.
 #
 # Define PPC_SHA1 environment variable when running make to make use of
 # a bundled SHA1 routine optimized for PowerPC.
 #
+# Define ARM_SHA1 environment variable when running make to make use of
+# a bundled SHA1 routine optimized for ARM.
+#
 # Define NEEDS_SSL_WITH_CRYPTO if you need -lcrypto with -lssl (Darwin).
 #
 # Define NEEDS_LIBICONV if linking with libc is not enough (Darwin).
 # Define NEEDS_SOCKET if linking with libc is not enough (SunOS,
 # Patrick Mauritz).
 #
-# Define NO_GETDOMAINNAME if your library lack it (SunOS, Patrick Mauritz).
+# Define NO_MMAP if you want to avoid mmap.
 #
 # Define WITH_OWN_SUBPROCESS_PY if you want to use with python 2.3.
 #
+# Define NO_IPV6 if you lack IPv6 support and getaddrinfo().
+#
 # 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.
 
-# DEFINES += -DCOLLISION_CHECK
-
 # Define USE_NSEC below if you want git to care about sub-second file mtimes
 # and ctimes. Note that you need recent glibc (at least 2.2.4) for this, and
 # it will BREAK YOUR LOCAL DIFFS! show-diff and anything using it will likely
 # randomly break unless your underlying filesystem supports those sub-second
 # times (my ext3 doesn't).
 
-# DEFINES += -DUSE_NSEC
-
 # Define USE_STDEV below if you want git to care about the underlying device
 # change being considered an inode change from the update-cache perspective.
 
-# DEFINES += -DUSE_STDEV
-
-GIT_VERSION = 0.99.7d
+GIT_VERSION = 0.99.9e
 
+# CFLAGS is for the users to override from the command line.
 CFLAGS = -g -O2 -Wall
-ALL_CFLAGS = $(CFLAGS) $(PLATFORM_DEFINES) $(DEFINES)
+ALL_CFLAGS = $(CFLAGS)
 
 prefix = $(HOME)
 bindir = $(prefix)/bin
@@ -57,6 +64,7 @@ GIT_PYTHON_DIR = $(prefix)/share/git-core/python
 
 CC = gcc
 AR = ar
+TAR = tar
 INSTALL = install
 RPMBUILD = rpmbuild
 
@@ -78,43 +86,47 @@ SCRIPT_SH = \
        git-repack.sh git-request-pull.sh git-reset.sh \
        git-resolve.sh git-revert.sh git-sh-setup.sh git-status.sh \
        git-tag.sh git-verify-tag.sh git-whatchanged.sh git.sh \
-       git-applymbox.sh git-applypatch.sh \
+       git-applymbox.sh git-applypatch.sh git-am.sh \
        git-merge.sh git-merge-stupid.sh git-merge-octopus.sh \
-       git-merge-resolve.sh git-grep.sh
+       git-merge-resolve.sh git-merge-ours.sh git-grep.sh
 
 SCRIPT_PERL = \
        git-archimport.perl git-cvsimport.perl git-relink.perl \
-       git-rename.perl git-shortlog.perl
+       git-rename.perl git-shortlog.perl git-fmt-merge-msg.perl \
+       git-svnimport.perl git-mv.perl
 
 SCRIPT_PYTHON = \
        git-merge-recursive.py
 
 # The ones that do not have to link with lcrypto nor lz.
 SIMPLE_PROGRAMS = \
-       git-get-tar-commit-id git-mailinfo git-mailsplit git-stripspace \
-       git-daemon git-var
+       git-get-tar-commit-id$X git-mailinfo$X git-mailsplit$X \
+       git-stripspace$X git-var$X git-daemon$X
 
 # ... and all the rest
 PROGRAMS = \
-       git-apply git-cat-file \
-       git-checkout-index git-clone-pack git-commit-tree \
-       git-convert-objects git-diff-files \
-       git-diff-helper git-diff-index git-diff-stages \
-       git-diff-tree git-export git-fetch-pack git-fsck-objects \
-       git-hash-object git-init-db \
-       git-local-fetch git-ls-files git-ls-tree git-merge-base \
-       git-merge-index git-mktag git-pack-objects git-patch-id \
-       git-peek-remote git-prune-packed git-read-tree \
-       git-receive-pack git-rev-list git-rev-parse \
-       git-rev-tree git-send-pack git-show-branch \
-       git-show-index git-ssh-fetch \
-       git-ssh-upload git-tar-tree git-unpack-file \
-       git-unpack-objects git-update-index git-update-server-info \
-       git-upload-pack git-verify-pack git-write-tree \
-       $(SIMPLE_PROGRAMS)
-
-# Backward compatibility -- to be removed in 0.99.8
-PROGRAMS += git-ssh-pull git-ssh-push
+       git-apply$X git-cat-file$X \
+       git-checkout-index$X git-clone-pack$X git-commit-tree$X \
+       git-convert-objects$X git-diff-files$X \
+       git-diff-index$X git-diff-stages$X \
+       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-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 \
+       git-show-index$X git-ssh-fetch$X \
+       git-ssh-upload$X git-tar-tree$X git-unpack-file$X \
+       git-unpack-objects$X git-update-index$X git-update-server-info$X \
+       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 $(SIMPLE_PROGRAMS)
+
+# Backward compatibility -- to be removed after 1.0
+PROGRAMS += git-ssh-pull$X git-ssh-push$X
+
+GIT_LIST_TWEAK =
 
 PYMODULES = \
        gitMergeCommon.py
@@ -125,10 +137,8 @@ endif
 
 ifdef WITH_SEND_EMAIL
        SCRIPT_PERL += git-send-email.perl
-endif
-
-ifndef NO_CURL
-       PROGRAMS += git-http-fetch
+else
+       GIT_LIST_TWEAK += -e '/^send-email$$/d'
 endif
 
 LIB_FILE=libgit.a
@@ -140,7 +150,7 @@ LIB_H = \
 
 DIFF_OBJS = \
        diff.o diffcore-break.o diffcore-order.o diffcore-pathspec.o \
-       diffcore-pickaxe.o diffcore-rename.o
+       diffcore-pickaxe.o diffcore-rename.o tree-diff.o
 
 LIB_OBJS = \
        blob.o commit.o connect.o count-delta.o csum-file.o \
@@ -148,19 +158,78 @@ LIB_OBJS = \
        object.o pack-check.o patch-delta.o path.o pkt-line.o \
        quote.o read-cache.o refs.o run-command.o \
        server-info.o setup.o sha1_file.o sha1_name.o strbuf.o \
-       tag.o tree.o usage.o $(DIFF_OBJS)
+       tag.o tree.o usage.o config.o environment.o ctype.o copy.o \
+       $(DIFF_OBJS)
 
 LIBS = $(LIB_FILE)
 LIBS += -lz
 
-ifeq ($(shell uname -s),Darwin)
+# Shell quote;
+# Result of this needs to be placed inside ''
+shq = $(subst ','\'',$(1))
+# This has surrounding ''
+shellquote = '$(call shq,$(1))'
+
+#
+# Platform specific tweaks
+#
+
+# 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')
+
+ifeq ($(uname_S),Darwin)
        NEEDS_SSL_WITH_CRYPTO = YesPlease
        NEEDS_LIBICONV = YesPlease
+       ## fink
+       ALL_CFLAGS += -I/sw/include -L/sw/lib
+       ## darwinports
+       ALL_CFLAGS += -I/opt/local/include -L/opt/local/lib
 endif
-ifeq ($(shell uname -s),SunOS)
+ifeq ($(uname_S),SunOS)
        NEEDS_SOCKET = YesPlease
        NEEDS_NSL = YesPlease
-       PLATFORM_DEFINES += -D__EXTENSIONS__
+       NEEDS_LIBICONV = YesPlease
+       SHELL_PATH = /bin/bash
+       NO_STRCASESTR = YesPlease
+       INSTALL = ginstall
+       TAR = gtar
+       ALL_CFLAGS += -D__EXTENSIONS__
+endif
+ifeq ($(uname_O),Cygwin)
+       NO_STRCASESTR = YesPlease
+       NEEDS_LIBICONV = YesPlease
+       NO_IPV6 = YesPlease
+       X = .exe
+       ALL_CFLAGS += -DUSE_SYMLINK_HEAD=0
+endif
+ifeq ($(uname_S),OpenBSD)
+       NO_STRCASESTR = YesPlease
+       NEEDS_LIBICONV = YesPlease
+       ALL_CFLAGS += -I/usr/local/include -L/usr/local/lib
+endif
+ifneq (,$(findstring arm,$(uname_M)))
+       ARM_SHA1 = YesPlease
+endif
+
+-include config.mak
+
+ifndef NO_CURL
+       ifdef CURLDIR
+               # This is still problematic -- gcc does not want -R.
+               ALL_CFLAGS += -I$(CURLDIR)/include
+               CURL_LIBCURL = -L$(CURLDIR)/lib -R$(CURLDIR)/lib -lcurl
+       else
+               CURL_LIBCURL = -lcurl
+       endif
+       PROGRAMS += git-http-fetch$X
+       ifndef NO_EXPAT
+               EXPAT_LIBEXPAT = -lexpat
+               PROGRAMS += git-http-push$X
+       endif
 endif
 
 ifndef SHELL_PATH
@@ -176,32 +245,34 @@ endif
 ifndef NO_OPENSSL
        LIB_OBJS += epoch.o
        OPENSSL_LIBSSL = -lssl
+       ifdef OPENSSLDIR
+               # Again this may be problematic -- gcc does not always want -R.
+               ALL_CFLAGS += -I$(OPENSSLDIR)/include
+               OPENSSL_LINK = -L$(OPENSSLDIR)/lib -R$(OPENSSLDIR)/lib
+       else
+               OPENSSL_LINK =
+       endif
 else
-       DEFINES += '-DNO_OPENSSL'
+       ALL_CFLAGS += -DNO_OPENSSL
        MOZILLA_SHA1 = 1
        OPENSSL_LIBSSL =
 endif
 ifdef NEEDS_SSL_WITH_CRYPTO
-       LIB_4_CRYPTO = -lcrypto -lssl
+       LIB_4_CRYPTO = $(OPENSSL_LINK) -lcrypto -lssl
 else
-       LIB_4_CRYPTO = -lcrypto
+       LIB_4_CRYPTO = $(OPENSSL_LINK) -lcrypto
 endif
 ifdef NEEDS_LIBICONV
-       LIB_4_ICONV = -liconv
-else
-       LIB_4_ICONV =
-endif
-ifdef MOZILLA_SHA1
-       SHA1_HEADER = "mozilla-sha1/sha1.h"
-       LIB_OBJS += mozilla-sha1/sha1.o
-else
-       ifdef PPC_SHA1
-               SHA1_HEADER = "ppc/sha1.h"
-               LIB_OBJS += ppc/sha1.o ppc/sha1ppc.o
+       ifdef ICONVDIR
+               # Again this may be problematic -- gcc does not always want -R.
+               ALL_CFLAGS += -I$(ICONVDIR)/include
+               ICONV_LINK = -L$(ICONVDIR)/lib -R$(ICONVDIR)/lib
        else
-               SHA1_HEADER = <openssl/sha.h>
-               LIBS += $(LIB_4_CRYPTO)
+               ICONV_LINK =
        endif
+       LIB_4_ICONV = $(ICONV_LINK) -liconv
+else
+       LIB_4_ICONV =
 endif
 ifdef NEEDS_SOCKET
        LIBS += -lsocket
@@ -212,17 +283,43 @@ ifdef NEEDS_NSL
        SIMPLE_LIB += -lnsl
 endif
 ifdef NO_STRCASESTR
-       DEFINES += -Dstrcasestr=gitstrcasestr
+       ALL_CFLAGS += -Dstrcasestr=gitstrcasestr -DNO_STRCASESTR=1
        LIB_OBJS += compat/strcasestr.o
 endif
+ifdef NO_MMAP
+       ALL_CFLAGS += -Dmmap=gitfakemmap -Dmunmap=gitfakemunmap -DNO_MMAP
+       LIB_OBJS += compat/mmap.o
+endif
+ifdef NO_IPV6
+       ALL_CFLAGS += -DNO_IPV6 -Dsockaddr_storage=sockaddr_in
+endif
 
-DEFINES += '-DSHA1_HEADER=$(SHA1_HEADER)'
+ifdef PPC_SHA1
+       SHA1_HEADER = "ppc/sha1.h"
+       LIB_OBJS += ppc/sha1.o ppc/sha1ppc.o
+else
+ifdef ARM_SHA1
+       SHA1_HEADER = "arm/sha1.h"
+       LIB_OBJS += arm/sha1.o arm/sha1_arm.o
+else
+ifdef MOZILLA_SHA1
+       SHA1_HEADER = "mozilla-sha1/sha1.h"
+       LIB_OBJS += mozilla-sha1/sha1.o
+else
+       SHA1_HEADER = <openssl/sha.h>
+       LIBS += $(LIB_4_CRYPTO)
+endif
+endif
+endif
+
+ALL_CFLAGS += -DSHA1_HEADER=$(call shellquote,$(SHA1_HEADER))
 
 SCRIPTS = $(patsubst %.sh,%,$(SCRIPT_SH)) \
          $(patsubst %.perl,%,$(SCRIPT_PERL)) \
          $(patsubst %.py,%,$(SCRIPT_PYTHON)) \
-         gitk
+         gitk git-cherry-pick
 
+export prefix TAR INSTALL DESTDIR SHELL_PATH template_dir
 ### Build rules
 
 all: $(PROGRAMS) $(SCRIPTS)
@@ -232,56 +329,68 @@ all:
 
 git: git.sh Makefile
        rm -f $@+ $@
-       sed -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' <$@.sh >$@+
+       sed -e '1s|#!.*/sh|#!$(call shq,$(SHELL_PATH))|' \
+           -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \
+           -e 's/@@X@@/$(X)/g' \
+           $(GIT_LIST_TWEAK) <$@.sh >$@+
        chmod +x $@+
        mv $@+ $@
 
 $(filter-out git,$(patsubst %.sh,%,$(SCRIPT_SH))) : % : %.sh
        rm -f $@
-       sed -e '1s|#!.*/sh|#!$(SHELL_PATH)|' $@.sh >$@
+       sed -e '1s|#!.*/sh|#!$(call shq,$(SHELL_PATH))|' \
+           -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \
+           $@.sh >$@
        chmod +x $@
 
 $(patsubst %.perl,%,$(SCRIPT_PERL)) : % : %.perl
        rm -f $@
-       sed -e '1s|#!.*perl|#!$(PERL_PATH)|' $@.perl >$@
+       sed -e '1s|#!.*perl|#!$(call shq,$(PERL_PATH))|' \
+           -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \
+           $@.perl >$@
        chmod +x $@
 
 $(patsubst %.py,%,$(SCRIPT_PYTHON)) : % : %.py
        rm -f $@
-       sed -e '1s|#!.*python|#!$(PYTHON_PATH)|' \
-           -e 's|@@GIT_PYTHON_PATH@@|$(GIT_PYTHON_DIR)|g' \
-               $@.py >$@
+       sed -e '1s|#!.*python|#!$(call shq,$(PYTHON_PATH))|' \
+           -e 's|@@GIT_PYTHON_PATH@@|$(call shq,$(GIT_PYTHON_DIR))|g' \
+           -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \
+           $@.py >$@
        chmod +x $@
 
+git-cherry-pick: git-revert
+       cp $< $@
+
 %.o: %.c
        $(CC) -o $*.o -c $(ALL_CFLAGS) $<
 %.o: %.S
        $(CC) -o $*.o -c $(ALL_CFLAGS) $<
 
-git-%: %.o $(LIB_FILE)
+git-%$X: %.o $(LIB_FILE)
        $(CC) $(ALL_CFLAGS) -o $@ $(filter %.o,$^) $(LIBS)
 
-git-mailinfo : SIMPLE_LIB += $(LIB_4_ICONV)
+git-mailinfo$X : SIMPLE_LIB += $(LIB_4_ICONV)
 $(SIMPLE_PROGRAMS) : $(LIB_FILE)
-$(SIMPLE_PROGRAMS) : git-% : %.o
+$(SIMPLE_PROGRAMS) : git-%$X : %.o
        $(CC) $(ALL_CFLAGS) -o $@ $(filter %.o,$^) $(LIB_FILE) $(SIMPLE_LIB)
 
-git-http-fetch: fetch.o
-git-local-fetch: fetch.o
-git-ssh-fetch: rsh.o fetch.o
-git-ssh-upload: rsh.o
-git-ssh-pull: rsh.o fetch.o
-git-ssh-push: rsh.o
+git-http-fetch$X: fetch.o
+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: LIBS += -lcurl
-git-rev-list: LIBS += $(OPENSSL_LIBSSL)
+git-http-fetch$X: LIBS += $(CURL_LIBCURL)
+git-http-push$X: LIBS += $(CURL_LIBCURL) $(EXPAT_LIBEXPAT)
+git-rev-list$X: LIBS += $(OPENSSL_LIBSSL)
 
 init-db.o: init-db.c
        $(CC) -c $(ALL_CFLAGS) \
-               -DDEFAULT_GIT_TEMPLATE_DIR='"$(template_dir)"' $*.c
+               -DDEFAULT_GIT_TEMPLATE_DIR=$(call shellquote,"$(template_dir)") $*.c
 
 $(LIB_OBJS): $(LIB_H)
-$(patsubst git-%,%.o,$(PROGRAMS)): $(LIB_H)
+$(patsubst git-%$X,%.o,$(PROGRAMS)): $(LIB_H)
 $(DIFF_OBJS): diffcore.h
 
 $(LIB_FILE): $(LIB_OBJS)
@@ -296,10 +405,10 @@ doc:
 test: all
        $(MAKE) -C t/ all
 
-test-date: test-date.c date.o
-       $(CC) $(ALL_CFLAGS) -o $@ test-date.c date.o
+test-date$X: test-date.c date.o ctype.o
+       $(CC) $(ALL_CFLAGS) -o $@ test-date.c date.o ctype.o
 
-test-delta: test-delta.c diff-delta.o patch-delta.o
+test-delta$X: test-delta.c diff-delta.o patch-delta.o
        $(CC) $(ALL_CFLAGS) -o $@ $^
 
 check:
@@ -310,13 +419,11 @@ check:
 ### Installation rules
 
 install: $(PROGRAMS) $(SCRIPTS)
-       $(INSTALL) -d -m755 $(DESTDIR)$(bindir)
-       $(INSTALL) $(PROGRAMS) $(SCRIPTS) $(DESTDIR)$(bindir)
-       $(INSTALL) git-revert $(DESTDIR)$(bindir)/git-cherry-pick
-       sh ./cmd-rename.sh $(DESTDIR)$(bindir)
+       $(INSTALL) -d -m755 $(call shellquote,$(DESTDIR)$(bindir))
+       $(INSTALL) $(PROGRAMS) $(SCRIPTS) $(call shellquote,$(DESTDIR)$(bindir))
        $(MAKE) -C templates install
-       $(INSTALL) -d -m755 $(DESTDIR)$(GIT_PYTHON_DIR)
-       $(INSTALL) $(PYMODULES) $(DESTDIR)$(GIT_PYTHON_DIR)
+       $(INSTALL) -d -m755 $(call shellquote,$(DESTDIR)$(GIT_PYTHON_DIR))
+       $(INSTALL) $(PYMODULES) $(call shellquote,$(DESTDIR)$(GIT_PYTHON_DIR))
 
 install-doc:
        $(MAKE) -C Documentation install
@@ -334,7 +441,7 @@ dist: git-core.spec git-tar-tree
        ./git-tar-tree HEAD $(GIT_TARNAME) > $(GIT_TARNAME).tar
        @mkdir -p $(GIT_TARNAME)
        @cp git-core.spec $(GIT_TARNAME)
-       tar rf $(GIT_TARNAME).tar $(GIT_TARNAME)/git-core.spec
+       $(TAR) rf $(GIT_TARNAME).tar $(GIT_TARNAME)/git-core.spec
        @rm -rf $(GIT_TARNAME)
        gzip -f -9 $(GIT_TARNAME).tar
 
@@ -343,20 +450,20 @@ rpm: dist
 
 deb: dist
        rm -rf $(GIT_TARNAME)
-       tar zxf $(GIT_TARNAME).tar.gz
+       $(TAR) zxf $(GIT_TARNAME).tar.gz
        dpkg-source -b $(GIT_TARNAME)
        cd $(GIT_TARNAME) && fakeroot debian/rules binary
 
 ### Cleaning rules
 
 clean:
-       rm -f *.o mozilla-sha1/*.o ppc/*.o $(PROGRAMS) $(LIB_FILE)
+       rm -f *.o mozilla-sha1/*.o ppc/*.o compat/*.o $(PROGRAMS) $(LIB_FILE)
        rm -f $(filter-out gitk,$(SCRIPTS))
-       rm -f git-core.spec
+       rm -f git-core.spec *.pyc *.pyo
        rm -rf $(GIT_TARNAME)
        rm -f $(GIT_TARNAME).tar.gz git-core_$(GIT_VERSION)-*.tar.gz
-       rm -f git-core_$(GIT_VERSION)-*.deb git-core_$(GIT_VERSION)-*.dsc
-       rm -f git-tk_$(GIT_VERSION)-*.deb
+       rm -f git-core_$(GIT_VERSION)-*.dsc
+       rm -f git-*_$(GIT_VERSION)-*.deb
        $(MAKE) -C Documentation/ clean
-       $(MAKE) -C templates/ clean
+       $(MAKE) -C templates clean
        $(MAKE) -C t/ clean
diff --git a/README b/README
index 6b38a7aa7a711c327d82e2da5e474544a7befc34..4a2616ba570728320f0b2cf8d7e80df32cbba603 100644 (file)
--- a/README
+++ b/README
@@ -104,8 +104,8 @@ object. The object is totally independent of its location in the
 directory tree, and renaming a file does not change the object that
 file is associated with in any way.
 
-A blob is typically created when link:git-update-index.html[git-update-index]
-is run, and its data can be accessed by link:git-cat-file.html[git-cat-file].
+A blob is typically created when gitlink:git-update-index[1]
+is run, and its data can be accessed by gitlink:git-cat-file[1].
 
 Tree Object
 ~~~~~~~~~~~
@@ -143,9 +143,9 @@ involved), you can see trivial renames or permission changes by
 noticing that the blob stayed the same.  However, renames with data
 changes need a smarter "diff" implementation.
 
-A tree is created with link:git-write-tree.html[git-write-tree] and
-its data can be accessed by link:git-ls-tree.html[git-ls-tree].
-Two trees can be compared with link:git-diff-tree.html[git-diff-tree].
+A tree is created with gitlink:git-write-tree[1] and
+its data can be accessed by gitlink:git-ls-tree[1].
+Two trees can be compared with gitlink:git-diff-tree[1].
 
 Commit Object
 ~~~~~~~~~~~~~
@@ -169,8 +169,8 @@ implicit in the trees involved (the result tree, and the result trees
 of the parents), and describing that makes no sense in this idiotic
 file manager.
 
-A commit is created with link:git-commit-tree.html[git-commit-tree] and
-its data can be accessed by link:git-cat-file.html[git-cat-file].
+A commit is created with gitlink:git-commit-tree[1] and
+its data can be accessed by gitlink:git-cat-file[1].
 
 Trust
 ~~~~~
@@ -215,10 +215,10 @@ Note that despite the tag features, "git" itself only handles content
 integrity; the trust framework (and signature provision and
 verification) has to come from outside.
 
-A tag is created with link:git-mktag.html[git-mktag],
-its data can be accessed by link:git-cat-file.html[git-cat-file],
+A tag is created with gitlink:git-mktag[1],
+its data can be accessed by gitlink:git-cat-file[1],
 and the signature can be verified by
-link:git-verify-tag.html[git-verify-tag].
+gitlink:git-verify-tag[1].
 
 
 The "index" aka "Current Directory Cache"
@@ -286,7 +286,7 @@ main combinations:
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
 You update the index with information from the working directory with
-the link:git-update-index.html[git-update-index] command.  You
+the gitlink:git-update-index[1] command.  You
 generally update the index information by just specifying the filename
 you want to update, like so:
 
@@ -399,12 +399,52 @@ save the note about that state, in practice we tend to just write the
 result to the file `.git/HEAD`, so that we can always see what the
 last committed state was.
 
+Here is an ASCII art by Jon Loeliger that illustrates how
+various pieces fit together.
+
+------------
+
+                     commit-tree
+                      commit obj
+                       +----+
+                       |    |
+                       |    |
+                       V    V
+                    +-----------+
+                    | Object DB |
+                    |  Backing  |
+                    |   Store   |
+                    +-----------+
+                       ^
+           write-tree  |     |
+             tree obj  |     |
+                       |     |  read-tree
+                       |     |  tree obj
+                             V
+                    +-----------+
+                    |   Index   |
+                    |  "cache"  |
+                    +-----------+
+         update-index  ^
+             blob obj  |     |
+                       |     |
+    checkout-index -u  |     |  checkout-index
+             stat      |     |  blob obj
+                             V
+                    +-----------+
+                    |  Working  |
+                    | Directory |
+                    +-----------+
+
+------------
+
+
 6) Examining the data
 ~~~~~~~~~~~~~~~~~~~~~
 
 You can examine the data represented in the object database and the
 index with various helper tools. For every object, you can use
-link:git-cat-file.html[git-cat-file] to examine details about the
+gitlink:git-cat-file[1] to examine details about the
 object:
 
                git-cat-file -t <objectname>
diff --git a/apply.c b/apply.c
index 964df2db10c22d6ed0b89532e0af8cf9a43e607c..3e53b3438169bcaaf4db97669b062ebe14335fe6 100644 (file)
--- a/apply.c
+++ b/apply.c
@@ -5,35 +5,28 @@
  *
  * This applies patches on top of some (arbitrary) version of the SCM.
  *
- * NOTE! It does all its work in the index file, and only cares about
- * the files in the working directory if you tell it to "merge" the
- * patch apply.
- *
- * Even when merging it always takes the source from the index, and
- * uses the working tree as a "branch" for a 3-way merge.
  */
-#include <ctype.h>
 #include <fnmatch.h>
 #include "cache.h"
+#include "quote.h"
 
-// We default to the merge behaviour, since that's what most people would
-// expect.
-//
 //  --check turns on checking that the working tree matches the
 //    files that are being modified, but doesn't apply the patch
 //  --stat does just a diffstat, and doesn't actually apply
-//  --show-files shows the directory changes
+//  --numstat does numeric diffstat, and doesn't actually apply
+//  --index-info shows the old and new index info for paths if available.
 //
-static int merge_patch = 1;
 static int check_index = 0;
 static int write_index = 0;
 static int diffstat = 0;
+static int numstat = 0;
 static int summary = 0;
 static int check = 0;
 static int apply = 1;
-static int show_files = 0;
+static int show_index_info = 0;
+static int line_termination = '\n';
 static const char apply_usage[] =
-"git-apply [--no-merge] [--stat] [--summary] [--check] [--index] [--apply] [--show-files] <patch>...";
+"git-apply [--stat] [--numstat] [--summary] [--check] [--index] [--apply] [--index-info] [-z] <patch>...";
 
 /*
  * For "diff-stat" like behaviour, we keep track of the biggest change
@@ -66,6 +59,8 @@ struct patch {
        struct fragment *fragments;
        char *result;
        unsigned long resultsize;
+       char old_sha1_prefix[41];
+       char new_sha1_prefix[41];
        struct patch *next;
 };
 
@@ -142,6 +137,35 @@ static char * find_name(const char *line, char *def, int p_value, int terminate)
        const char *start = line;
        char *name;
 
+       if (*line == '"') {
+               /* Proposed "new-style" GNU patch/diff format; see
+                * http://marc.theaimsgroup.com/?l=git&m=112927316408690&w=2
+                */
+               name = unquote_c_style(line, NULL);
+               if (name) {
+                       char *cp = name;
+                       while (p_value) {
+                               cp = strchr(name, '/');
+                               if (!cp)
+                                       break;
+                               cp++;
+                               p_value--;
+                       }
+                       if (cp) {
+                               /* name can later be freed, so we need
+                                * to memmove, not just return cp
+                                */
+                               memmove(name, cp, strlen(cp) + 1);
+                               free(def);
+                               return name;
+                       }
+                       else {
+                               free(name);
+                               name = NULL;
+                       }
+               }
+       }
+
        for (;;) {
                char c = *line;
 
@@ -231,37 +255,29 @@ static int gitdiff_hdrend(const char *line, struct patch *patch)
  */
 static char *gitdiff_verify_name(const char *line, int isnull, char *orig_name, const char *oldnew)
 {
-       int len;
-       const char *name;
-
        if (!orig_name && !isnull)
                return find_name(line, NULL, 1, 0);
 
-       name = "/dev/null";
-       len = 9;
        if (orig_name) {
+               int len;
+               const char *name;
+               char *another;
                name = orig_name;
                len = strlen(name);
                if (isnull)
                        die("git-apply: bad git-diff - expected /dev/null, got %s on line %d", name, linenr);
-       }
-
-       if (*name == '/')
-               goto absolute_path;
-
-       for (;;) {
-               char c = *line++;
-               if (c == '\n')
-                       break;
-               if (c != '/')
-                       continue;
-absolute_path:
-               if (memcmp(line, name, len) || line[len] != '\n')
-                       break;
+               another = find_name(line, NULL, 1, 0);
+               if (!another || memcmp(another, name, len))
+                       die("git-apply: bad git-diff - inconsistent %s filename on line %d", oldnew, linenr);
+               free(another);
                return orig_name;
        }
-       die("git-apply: bad git-diff - inconsistent %s filename on line %d", oldnew, linenr);
-       return NULL;
+       else {
+               /* expect "/dev/null" */
+               if (memcmp("/dev/null", line, 9) || line[9] != '\n')
+                       die("git-apply: bad git-diff - expected /dev/null on line %d", linenr);
+               return NULL;
+       }
 }
 
 static int gitdiff_oldname(const char *line, struct patch *patch)
@@ -344,6 +360,38 @@ static int gitdiff_dissimilarity(const char *line, struct patch *patch)
        return 0;
 }
 
+static int gitdiff_index(const char *line, struct patch *patch)
+{
+       /* index line is N hexadecimal, "..", N hexadecimal,
+        * and optional space with octal mode.
+        */
+       const char *ptr, *eol;
+       int len;
+
+       ptr = strchr(line, '.');
+       if (!ptr || ptr[1] != '.' || 40 <= ptr - line)
+               return 0;
+       len = ptr - line;
+       memcpy(patch->old_sha1_prefix, line, len);
+       patch->old_sha1_prefix[len] = 0;
+
+       line = ptr + 2;
+       ptr = strchr(line, ' ');
+       eol = strchr(line, '\n');
+
+       if (!ptr || eol < ptr)
+               ptr = eol;
+       len = ptr - line;
+
+       if (40 <= len)
+               return 0;
+       memcpy(patch->new_sha1_prefix, line, len);
+       patch->new_sha1_prefix[len] = 0;
+       if (*ptr == ' ')
+               patch->new_mode = patch->old_mode = strtoul(ptr+1, NULL, 8);
+       return 0;
+}
+
 /*
  * This is normal for a diff that doesn't change anything: we'll fall through
  * into the next diff. Tell the parser to break out.
@@ -353,29 +401,124 @@ static int gitdiff_unrecognized(const char *line, struct patch *patch)
        return -1;
 }
 
-static char *git_header_name(char *line)
+static const char *stop_at_slash(const char *line, int llen)
+{
+       int i;
+
+       for (i = 0; i < llen; i++) {
+               int ch = line[i];
+               if (ch == '/')
+                       return line + i;
+       }
+       return NULL;
+}
+
+/* This is to extract the same name that appears on "diff --git"
+ * line.  We do not find and return anything if it is a rename
+ * patch, and it is OK because we will find the name elsewhere.
+ * We need to reliably find name only when it is mode-change only,
+ * creation or deletion of an empty file.  In any of these cases,
+ * both sides are the same name under a/ and b/ respectively.
+ */
+static char *git_header_name(char *line, int llen)
 {
        int len;
-       char *name, *second;
+       const char *name;
+       const char *second = NULL;
 
-       /*
-        * Find the first '/'
-        */
-       name = line;
-       for (;;) {
-               char c = *name++;
-               if (c == '\n')
+       line += strlen("diff --git ");
+       llen -= strlen("diff --git ");
+
+       if (*line == '"') {
+               const char *cp;
+               char *first = unquote_c_style(line, &second);
+               if (!first)
                        return NULL;
-               if (c == '/')
-                       break;
+
+               /* advance to the first slash */
+               cp = stop_at_slash(first, strlen(first));
+               if (!cp || cp == first) {
+                       /* we do not accept absolute paths */
+               free_first_and_fail:
+                       free(first);
+                       return NULL;
+               }
+               len = strlen(cp+1);
+               memmove(first, cp+1, len+1); /* including NUL */
+
+               /* second points at one past closing dq of name.
+                * find the second name.
+                */
+               while ((second < line + llen) && isspace(*second))
+                       second++;
+
+               if (line + llen <= second)
+                       goto free_first_and_fail;
+               if (*second == '"') {
+                       char *sp = unquote_c_style(second, NULL);
+                       if (!sp)
+                               goto free_first_and_fail;
+                       cp = stop_at_slash(sp, strlen(sp));
+                       if (!cp || cp == sp) {
+                       free_both_and_fail:
+                               free(sp);
+                               goto free_first_and_fail;
+                       }
+                       /* They must match, otherwise ignore */
+                       if (strcmp(cp+1, first))
+                               goto free_both_and_fail;
+                       free(sp);
+                       return first;
+               }
+
+               /* unquoted second */
+               cp = stop_at_slash(second, line + llen - second);
+               if (!cp || cp == second)
+                       goto free_first_and_fail;
+               cp++;
+               if (line + llen - cp != len + 1 ||
+                   memcmp(first, cp, len))
+                       goto free_first_and_fail;
+               return first;
        }
 
-       /*
-        * We don't accept absolute paths (/dev/null) as possibly valid
-        */
-       if (name == line+1)
+       /* unquoted first name */
+       name = stop_at_slash(line, llen);
+       if (!name || name == line)
                return NULL;
 
+       name++;
+
+       /* since the first name is unquoted, a dq if exists must be
+        * the beginning of the second name.
+        */
+       for (second = name; second < line + llen; second++) {
+               if (*second == '"') {
+                       const char *cp = second;
+                       const char *np;
+                       char *sp = unquote_c_style(second, NULL);
+
+                       if (!sp)
+                               return NULL;
+                       np = stop_at_slash(sp, strlen(sp));
+                       if (!np || np == sp) {
+                       free_second_and_fail:
+                               free(sp);
+                               return NULL;
+                       }
+                       np++;
+                       len = strlen(np);
+                       if (len < cp - name &&
+                           !strncmp(np, name, len) &&
+                           isspace(name[len])) {
+                               /* Good */
+                               memmove(sp, np, len + 1);
+                               return sp;
+                       }
+                       goto free_second_and_fail;
+               }
+       }
+
        /*
         * Accept a name only if it shows up twice, exactly the same
         * form.
@@ -423,7 +566,7 @@ static int parse_git_header(char *line, int len, unsigned int size, struct patch
         * or removing or adding empty files), so we get
         * the default name from the header.
         */
-       patch->def_name = git_header_name(line + strlen("diff --git "));
+       patch->def_name = git_header_name(line, len);
 
        line += len;
        size -= len;
@@ -448,6 +591,7 @@ static int parse_git_header(char *line, int len, unsigned int size, struct patch
                        { "rename to ", gitdiff_renamedst },
                        { "similarity index ", gitdiff_similarity },
                        { "dissimilarity index ", gitdiff_dissimilarity },
+                       { "index ", gitdiff_index },
                        { "", gitdiff_unrecognized },
                };
                int i;
@@ -676,7 +820,10 @@ static int parse_fragment(char *line, unsigned long size, struct patch *patch, s
                 /* We allow "\ No newline at end of file". Depending
                  * on locale settings when the patch was produced we
                  * don't know what this line looks like. The only
-                 * thing we do know is that it begins with "\ ". */
+                 * thing we do know is that it begins with "\ ".
+                * Checking for 12 is just for sanity check -- any
+                * l10n of "\ No newline..." is at least that long.
+                */
                case '\\':
                        if (len < 12 || memcmp(line, "\\ ", 2))
                                return -1;
@@ -723,6 +870,16 @@ static int parse_single_patch(char *line, unsigned long size, struct patch *patc
        return offset;
 }
 
+static inline int metadata_changes(struct patch *patch)
+{
+       return  patch->is_rename > 0 ||
+               patch->is_copy > 0 ||
+               patch->is_new > 0 ||
+               patch->is_delete ||
+               (patch->old_mode && patch->new_mode &&
+                patch->old_mode != patch->new_mode);
+}
+
 static int parse_chunk(char *buffer, unsigned long size, struct patch *patch)
 {
        int hdrsize, patchsize;
@@ -733,6 +890,9 @@ static int parse_chunk(char *buffer, unsigned long size, struct patch *patch)
 
        patchsize = parse_single_patch(buffer + offset + hdrsize, size - offset - hdrsize, patch);
 
+       if (!patchsize && !metadata_changes(patch))
+               die("patch with only garbage at line %d", linenr);
+
        return offset + hdrsize + patchsize;
 }
 
@@ -743,11 +903,18 @@ static void show_stats(struct patch *patch)
 {
        const char *prefix = "";
        char *name = patch->new_name;
+       char *qname = NULL;
        int len, max, add, del, total;
 
        if (!name)
                name = patch->old_name;
 
+       if (0 < (len = quote_c_style(name, NULL, NULL, 0))) {
+               qname = xmalloc(len + 1);
+               quote_c_style(name, qname, NULL, 0);
+               name = qname;
+       }
+
        /*
         * "scale" the filename
         */
@@ -785,6 +952,8 @@ static void show_stats(struct patch *patch)
        printf(" %s%-*s |%5d %.*s%.*s\n", prefix,
                len, name, patch->lines_added + patch->lines_deleted,
                add, pluses, del, minuses);
+       if (qname)
+               free(qname);
 }
 
 static int read_old_data(struct stat *st, const char *path, void *buf, unsigned long size)
@@ -1017,17 +1186,39 @@ static int check_patch(struct patch *patch)
 
        if (old_name) {
                int changed;
+               int stat_ret = lstat(old_name, &st);
 
-               if (lstat(old_name, &st) < 0)
-                       return error("%s: %s", old_name, strerror(errno));
                if (check_index) {
                        int pos = cache_name_pos(old_name, strlen(old_name));
                        if (pos < 0)
-                               return error("%s: does not exist in index", old_name);
+                               return error("%s: does not exist in index",
+                                            old_name);
+                       if (stat_ret < 0) {
+                               struct checkout costate;
+                               if (errno != ENOENT)
+                                       return error("%s: %s", old_name,
+                                                    strerror(errno));
+                               /* checkout */
+                               costate.base_dir = "";
+                               costate.base_dir_len = 0;
+                               costate.force = 0;
+                               costate.quiet = 0;
+                               costate.not_new = 0;
+                               costate.refresh_cache = 1;
+                               if (checkout_entry(active_cache[pos],
+                                                  &costate) ||
+                                   lstat(old_name, &st))
+                                       return -1;
+                       }
+
                        changed = ce_match_stat(active_cache[pos], &st);
                        if (changed)
-                               return error("%s: does not match index", old_name);
+                               return error("%s: does not match index",
+                                            old_name);
                }
+               else if (stat_ret < 0)
+                       return error("%s: %s", old_name, strerror(errno));
+
                if (patch->is_new < 0)
                        patch->is_new = 0;
                st.st_mode = ntohl(create_ce_mode(st.st_mode));
@@ -1079,32 +1270,38 @@ static int check_patch_list(struct patch *patch)
        return error;
 }
 
-static void show_file(int c, unsigned int mode, const char *name)
+static inline int is_null_sha1(const unsigned char *sha1)
 {
-       printf("%c %o %s\n", c, mode, name);
+       return !memcmp(sha1, null_sha1, 20);
 }
 
-static void show_file_list(struct patch *patch)
+static void show_index_list(struct patch *list)
 {
-       for (;patch ; patch = patch->next) {
-               if (patch->is_rename) {
-                       show_file('-', patch->old_mode, patch->old_name);
-                       show_file('+', patch->new_mode, patch->new_name);
-                       continue;
-               }
-               if (patch->is_copy || patch->is_new) {
-                       show_file('+', patch->new_mode, patch->new_name);
-                       continue;
-               }
-               if (patch->is_delete) {
-                       show_file('-', patch->old_mode, patch->old_name);
-                       continue;
-               }
-               if (patch->old_mode && patch->new_mode && patch->old_mode != patch->new_mode) {
-                       printf("M %o:%o %s\n", patch->old_mode, patch->new_mode, patch->old_name);
-                       continue;
-               }
-               printf("M %o %s\n", patch->old_mode, patch->old_name);
+       struct patch *patch;
+
+       /* Once we start supporting the reverse patch, it may be
+        * worth showing the new sha1 prefix, but until then...
+        */
+       for (patch = list; patch; patch = patch->next) {
+               const unsigned char *sha1_ptr;
+               unsigned char sha1[20];
+               const char *name;
+
+               name = patch->old_name ? patch->old_name : patch->new_name;
+               if (patch->is_new)
+                       sha1_ptr = null_sha1;
+               else if (get_sha1(patch->old_sha1_prefix, sha1))
+                       die("sha1 information is lacking or useless (%s).",
+                           name);
+               else
+                       sha1_ptr = sha1;
+
+               printf("%06o %s ",patch->old_mode, sha1_to_hex(sha1_ptr));
+               if (line_termination && quote_c_style(name, NULL, NULL, 0))
+                       quote_c_style(name, NULL, stdout, 0);
+               else
+                       fputs(name, stdout);
+               putchar(line_termination);
        }
 }
 
@@ -1122,6 +1319,20 @@ static void stat_patch_list(struct patch *patch)
        printf(" %d files changed, %d insertions(+), %d deletions(-)\n", files, adds, dels);
 }
 
+static void numstat_patch_list(struct patch *patch)
+{
+       for ( ; patch; patch = patch->next) {
+               const char *name;
+               name = patch->old_name ? patch->old_name : patch->new_name;
+               printf("%d\t%d\t", patch->lines_added, patch->lines_deleted);
+               if (line_termination && quote_c_style(name, NULL, NULL, 0))
+                       quote_c_style(name, NULL, stdout, 0);
+               else
+                       fputs(name, stdout);
+               putchar('\n');
+       }
+}
+
 static void show_file_mode_name(const char *newdelete, unsigned int mode, const char *name)
 {
        if (mode)
@@ -1207,12 +1418,16 @@ static void patch_stats(struct patch *patch)
        if (lines > max_change)
                max_change = lines;
        if (patch->old_name) {
-               int len = strlen(patch->old_name);
+               int len = quote_c_style(patch->old_name, NULL, NULL, 0);
+               if (!len)
+                       len = strlen(patch->old_name);
                if (len > max_len)
                        max_len = len;
        }
        if (patch->new_name) {
-               int len = strlen(patch->new_name);
+               int len = quote_c_style(patch->new_name, NULL, NULL, 0);
+               if (!len)
+                       len = strlen(patch->new_name);
                if (len > max_len)
                        max_len = len;
        }
@@ -1445,12 +1660,15 @@ static int apply_patch(int fd)
                        die("Unable to write new cachefile");
        }
 
-       if (show_files)
-               show_file_list(list);
+       if (show_index_info)
+               show_index_list(list);
 
        if (diffstat)
                stat_patch_list(list);
 
+       if (numstat)
+               numstat_patch_list(list);
+
        if (summary)
                summary_patch_list(list);
 
@@ -1479,16 +1697,16 @@ int main(int argc, char **argv)
                        excludes = x;
                        continue;
                }
-               /* NEEDSWORK: this does not do anything at this moment. */
-               if (!strcmp(arg, "--no-merge")) {
-                       merge_patch = 0;
-                       continue;
-               }
                if (!strcmp(arg, "--stat")) {
                        apply = 0;
                        diffstat = 1;
                        continue;
                }
+               if (!strcmp(arg, "--numstat")) {
+                       apply = 0;
+                       numstat = 1;
+                       continue;
+               }
                if (!strcmp(arg, "--summary")) {
                        apply = 0;
                        summary = 1;
@@ -1507,8 +1725,13 @@ int main(int argc, char **argv)
                        apply = 1;
                        continue;
                }
-               if (!strcmp(arg, "--show-files")) {
-                       show_files = 1;
+               if (!strcmp(arg, "--index-info")) {
+                       apply = 0;
+                       show_index_info = 1;
+                       continue;
+               }
+               if (!strcmp(arg, "-z")) {
+                       line_termination = 0;
                        continue;
                }
                fd = open(arg, O_RDONLY);
diff --git a/arm/sha1.c b/arm/sha1.c
new file mode 100644 (file)
index 0000000..11b1a04
--- /dev/null
@@ -0,0 +1,82 @@
+/*
+ * SHA-1 implementation optimized for ARM
+ *
+ * Copyright:   (C) 2005 by Nicolas Pitre <nico@cam.org>
+ * Created:     September 17, 2005
+ */
+
+#include <string.h>
+#include "sha1.h"
+
+extern void sha_transform(uint32_t *hash, const unsigned char *data, uint32_t *W);
+
+void SHA1_Init(SHA_CTX *c)
+{
+       c->len = 0;
+       c->hash[0] = 0x67452301;
+       c->hash[1] = 0xefcdab89;
+       c->hash[2] = 0x98badcfe;
+       c->hash[3] = 0x10325476;
+       c->hash[4] = 0xc3d2e1f0;
+}
+
+void SHA1_Update(SHA_CTX *c, const void *p, unsigned long n)
+{
+       uint32_t workspace[80];
+       unsigned int partial;
+       unsigned long done;
+
+       partial = c->len & 0x3f;
+       c->len += n;
+       if ((partial + n) >= 64) {
+               if (partial) {
+                       done = 64 - partial;
+                       memcpy(c->buffer + partial, p, done);
+                       sha_transform(c->hash, c->buffer, workspace);
+                       partial = 0;
+               } else
+                       done = 0;
+               while (n >= done + 64) {
+                       sha_transform(c->hash, p + done, workspace);
+                       done += 64;
+               }
+       } else
+               done = 0;
+       if (n - done)
+               memcpy(c->buffer + partial, p + done, n - done);
+}
+
+void SHA1_Final(unsigned char *hash, SHA_CTX *c)
+{
+       uint64_t bitlen;
+       uint32_t bitlen_hi, bitlen_lo; 
+       unsigned int i, offset, padlen;
+       unsigned char bits[8];
+       static const unsigned char padding[64] = { 0x80, };
+
+       bitlen = c->len << 3;
+       offset = c->len & 0x3f;
+       padlen = ((offset < 56) ? 56 : (64 + 56)) - offset;
+       SHA1_Update(c, padding, padlen);
+
+       bitlen_hi = bitlen >> 32;
+       bitlen_lo = bitlen & 0xffffffff;
+       bits[0] = bitlen_hi >> 24;
+       bits[1] = bitlen_hi >> 16;
+       bits[2] = bitlen_hi >> 8;
+       bits[3] = bitlen_hi;
+       bits[4] = bitlen_lo >> 24;
+       bits[5] = bitlen_lo >> 16;
+       bits[6] = bitlen_lo >> 8;
+       bits[7] = bitlen_lo;
+       SHA1_Update(c, bits, 8); 
+
+       for (i = 0; i < 5; i++) {
+               uint32_t v = c->hash[i];
+               hash[0] = v >> 24;
+               hash[1] = v >> 16;
+               hash[2] = v >> 8;
+               hash[3] = v;
+               hash += 4;
+       }
+}
diff --git a/arm/sha1.h b/arm/sha1.h
new file mode 100644 (file)
index 0000000..3952646
--- /dev/null
@@ -0,0 +1,18 @@
+/*
+ * SHA-1 implementation optimized for ARM
+ *
+ * Copyright:  (C) 2005 by Nicolas Pitre <nico@cam.org>
+ * Created:    September 17, 2005
+ */
+
+#include <stdint.h>
+
+typedef struct sha_context {
+       uint64_t len;
+       uint32_t hash[5];
+       unsigned char buffer[64];
+} SHA_CTX;
+
+void SHA1_Init(SHA_CTX *c);
+void SHA1_Update(SHA_CTX *c, const void *p, unsigned long n);
+void SHA1_Final(unsigned char *hash, SHA_CTX *c);
diff --git a/arm/sha1_arm.S b/arm/sha1_arm.S
new file mode 100644 (file)
index 0000000..da92d20
--- /dev/null
@@ -0,0 +1,184 @@
+/*
+ *  SHA transform optimized for ARM
+ *
+ *  Copyright: (C) 2005 by Nicolas Pitre <nico@cam.org>
+ *  Created:   September 17, 2005
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License version 2 as
+ *  published by the Free Software Foundation.
+ */
+
+       .text
+       .globl  sha_transform
+
+/*
+ * void sha_transform(uint32_t *hash, const unsigned char *data, uint32_t *W);
+ *
+ * note: the "data" pointer may be unaligned.
+ */
+
+sha_transform:
+
+       stmfd   sp!, {r4 - r8, lr}
+
+       @ for (i = 0; i < 16; i++)
+       @         W[i] = ntohl(((uint32_t *)data)[i]); */
+
+#ifdef __ARMEB__
+       mov     r4, r0
+       mov     r0, r2
+       mov     r2, #64
+       bl      memcpy
+       mov     r2, r0
+       mov     r0, r4
+#else
+       mov     r3, r2
+       mov     lr, #16
+1:     ldrb    r4, [r1], #1
+       ldrb    r5, [r1], #1
+       ldrb    r6, [r1], #1
+       ldrb    r7, [r1], #1
+       subs    lr, lr, #1
+       orr     r5, r5, r4, lsl #8
+       orr     r6, r6, r5, lsl #8
+       orr     r7, r7, r6, lsl #8
+       str     r7, [r3], #4
+       bne     1b
+#endif
+
+       @ for (i = 0; i < 64; i++)
+       @         W[i+16] = ror(W[i+13] ^ W[i+8] ^ W[i+2] ^ W[i], 31);
+
+       sub     r3, r2, #4
+       mov     lr, #64
+2:     ldr     r4, [r3, #4]!
+       subs    lr, lr, #1
+       ldr     r5, [r3, #8]
+       ldr     r6, [r3, #32]
+       ldr     r7, [r3, #52]
+       eor     r4, r4, r5
+       eor     r4, r4, r6
+       eor     r4, r4, r7
+       mov     r4, r4, ror #31
+       str     r4, [r3, #64]
+       bne     2b
+
+       /*
+        * The SHA functions are:
+        *
+        * f1(B,C,D) = (D ^ (B & (C ^ D)))
+        * f2(B,C,D) = (B ^ C ^ D)
+        * f3(B,C,D) = ((B & C) | (D & (B | C)))
+        *
+        * Then the sub-blocks are processed as follows:
+        *
+        * A' = ror(A, 27) + f(B,C,D) + E + K + *W++
+        * B' = A
+        * C' = ror(B, 2)
+        * D' = C
+        * E' = D
+        *
+        * We therefore unroll each loop 5 times to avoid register shuffling.
+        * Also the ror for C (and also D and E which are successivelyderived
+        * from it) is applied in place to cut on an additional mov insn for
+        * each round.
+        */
+
+       .macro  sha_f1, A, B, C, D, E
+       ldr     r3, [r2], #4
+       eor     ip, \C, \D
+       add     \E, r1, \E, ror #2
+       and     ip, \B, ip, ror #2
+       add     \E, \E, \A, ror #27
+       eor     ip, ip, \D, ror #2
+       add     \E, \E, r3
+       add     \E, \E, ip
+       .endm
+
+       .macro  sha_f2, A, B, C, D, E
+       ldr     r3, [r2], #4
+       add     \E, r1, \E, ror #2
+       eor     ip, \B, \C, ror #2
+       add     \E, \E, \A, ror #27
+       eor     ip, ip, \D, ror #2
+       add     \E, \E, r3
+       add     \E, \E, ip
+       .endm
+
+       .macro  sha_f3, A, B, C, D, E
+       ldr     r3, [r2], #4
+       add     \E, r1, \E, ror #2
+       orr     ip, \B, \C, ror #2
+       add     \E, \E, \A, ror #27
+       and     ip, ip, \D, ror #2
+       add     \E, \E, r3
+       and     r3, \B, \C, ror #2
+       orr     ip, ip, r3
+       add     \E, \E, ip
+       .endm
+
+       ldmia   r0, {r4 - r8}
+
+       mov     lr, #4
+       ldr     r1, .L_sha_K + 0
+
+       /* adjust initial values */
+       mov     r6, r6, ror #30
+       mov     r7, r7, ror #30
+       mov     r8, r8, ror #30
+
+3:     subs    lr, lr, #1
+       sha_f1  r4, r5, r6, r7, r8
+       sha_f1  r8, r4, r5, r6, r7
+       sha_f1  r7, r8, r4, r5, r6
+       sha_f1  r6, r7, r8, r4, r5
+       sha_f1  r5, r6, r7, r8, r4
+       bne     3b
+
+       ldr     r1, .L_sha_K + 4
+       mov     lr, #4
+
+4:     subs    lr, lr, #1
+       sha_f2  r4, r5, r6, r7, r8
+       sha_f2  r8, r4, r5, r6, r7
+       sha_f2  r7, r8, r4, r5, r6
+       sha_f2  r6, r7, r8, r4, r5
+       sha_f2  r5, r6, r7, r8, r4
+       bne     4b
+
+       ldr     r1, .L_sha_K + 8
+       mov     lr, #4
+
+5:     subs    lr, lr, #1
+       sha_f3  r4, r5, r6, r7, r8
+       sha_f3  r8, r4, r5, r6, r7
+       sha_f3  r7, r8, r4, r5, r6
+       sha_f3  r6, r7, r8, r4, r5
+       sha_f3  r5, r6, r7, r8, r4
+       bne     5b
+
+       ldr     r1, .L_sha_K + 12
+       mov     lr, #4
+
+6:     subs    lr, lr, #1
+       sha_f2  r4, r5, r6, r7, r8
+       sha_f2  r8, r4, r5, r6, r7
+       sha_f2  r7, r8, r4, r5, r6
+       sha_f2  r6, r7, r8, r4, r5
+       sha_f2  r5, r6, r7, r8, r4
+       bne     6b
+
+       ldmia   r0, {r1, r2, r3, ip, lr}
+       add     r4, r1, r4
+       add     r5, r2, r5
+       add     r6, r3, r6, ror #2
+       add     r7, ip, r7, ror #2
+       add     r8, lr, r8, ror #2
+       stmia   r0, {r4 - r8}
+
+       ldmfd   sp!, {r4 - r8, pc}
+
+.L_sha_K:
+       .word   0x5a827999, 0x6ed9eba1, 0x8f1bbcdc, 0xca62c1d6
+
diff --git a/cache.h b/cache.h
index 1d99d6817d8bcd629d01ca75daf0767e93376f11..677c6acc350155d606e2ba05a9bbf5eb28637a31 100644 (file)
--- a/cache.h
+++ b/cache.h
@@ -11,7 +11,9 @@
 #include <string.h>
 #include <errno.h>
 #include <limits.h>
+#ifndef NO_MMAP
 #include <sys/mman.h>
+#endif
 #include <sys/param.h>
 #include <netinet/in.h>
 #include <sys/types.h>
@@ -136,6 +138,7 @@ extern unsigned int active_nr, active_alloc, active_cache_changed;
 #define INDEX_ENVIRONMENT "GIT_INDEX_FILE"
 #define GRAFT_ENVIRONMENT "GIT_GRAFT_FILE"
 
+extern char *get_git_dir(void);
 extern char *get_object_directory(void);
 extern char *get_refs_directory(void);
 extern char *get_index_file(void);
@@ -143,9 +146,9 @@ extern char *get_graft_file(void);
 
 #define ALTERNATE_DB_ENVIRONMENT "GIT_ALTERNATE_OBJECT_DIRECTORIES"
 
-extern const char **get_pathspec(const char *prefix, char **pathspec);
+extern const char **get_pathspec(const char *prefix, const char **pathspec);
 extern const char *setup_git_directory(void);
-extern char *prefix_path(const char *prefix, int len, char *path);
+extern const char *prefix_path(const char *prefix, int len, const char *path);
 
 #define alloc_nr(x) (((x)+16)*3/2)
 
@@ -158,11 +161,13 @@ extern int cache_name_pos(const char *name, int namelen);
 #define ADD_CACHE_SKIP_DFCHECK 4       /* Ok to skip DF conflict checks */
 extern int add_cache_entry(struct cache_entry *ce, int option);
 extern int remove_cache_entry_at(int pos);
-extern int remove_file_from_cache(char *path);
+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_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_path(unsigned char *sha1, const char *path, struct stat *st, int write_object);
 extern void fill_stat_cache_info(struct cache_entry *ce, struct stat *st);
 
 struct cache_file {
@@ -173,6 +178,8 @@ extern int hold_index_file_for_update(struct cache_file *, const char *path);
 extern int commit_index_file(struct cache_file *);
 extern void rollback_index_file(struct cache_file *);
 
+extern int trust_executable_bit;
+
 #define MTIME_CHANGED  0x0001
 #define CTIME_CHANGED  0x0002
 #define OWNER_CHANGED  0x0004
@@ -187,6 +194,8 @@ extern char *git_path(const char *fmt, ...) __attribute__((format (printf, 1, 2)
 extern char *sha1_file_name(const unsigned char *sha1);
 extern char *sha1_pack_name(const unsigned char *sha1);
 extern char *sha1_pack_index_name(const unsigned char *sha1);
+extern const char *find_unique_abbrev(const unsigned char *sha1, int);
+extern const unsigned char null_sha1[20];
 
 int git_mkstemp(char *path, size_t n, const char *template);
 
@@ -215,6 +224,7 @@ extern int read_tree(void *buffer, unsigned long size, int stage, const char **p
 extern int write_sha1_from_fd(const unsigned char *sha1, int fd, char *buffer,
                              size_t bufsize, size_t *bufposn);
 extern int write_sha1_to_fd(int fd, const unsigned char *sha1);
+extern int move_temp_to_file(const char *tmpfile, char *filename);
 
 extern int has_sha1_pack(const unsigned char *sha1);
 extern int has_sha1_file(const unsigned char *sha1);
@@ -226,6 +236,10 @@ extern int has_pack_index(const unsigned char *sha1);
 extern int get_sha1(const char *str, unsigned char *sha1);
 extern int get_sha1_hex(const char *hex, unsigned char *sha1);
 extern char *sha1_to_hex(const unsigned char *sha1);   /* static buffer result! */
+extern int read_ref(const char *filename, unsigned char *sha1);
+extern const char *resolve_ref(const char *path, unsigned char *sha1, int);
+extern int create_symref(const char *git_HEAD, const char *refs_heads_master);
+extern int validate_symref(const char *git_HEAD);
 
 /* General helper functions */
 extern void usage(const char *err) NORETURN;
@@ -241,7 +255,7 @@ extern void *read_object_with_reference(const unsigned char *sha1,
                                        unsigned char *sha1_ret);
 
 const char *show_date(unsigned long time, int timezone);
-void parse_date(const char *date, char *buf, int bufsize);
+int parse_date(const char *date, char *buf, int bufsize);
 void datestamp(char *buf, int bufsize);
 
 extern int setup_ident(void);
@@ -299,6 +313,7 @@ extern struct packed_git {
        void *pack_base;
        unsigned int pack_last_used;
        unsigned int pack_use_cnt;
+       int pack_local;
        unsigned char sha1[20];
        char pack_name[0]; /* something like ".git/objects/pack/xxxxx.pack" */
 } *packed_git;
@@ -324,7 +339,8 @@ extern int path_match(const char *path, int nr, char **match);
 extern int match_refs(struct ref *src, struct ref *dst, struct ref ***dst_tail,
                      int nr_refspec, char **refspec, int all);
 extern int get_ack(int fd, unsigned char *result_sha1);
-extern struct ref **get_remote_heads(int in, struct ref **list, int nr_match, char **match);
+extern struct ref **get_remote_heads(int in, struct ref **list, int nr_match, char **match, int ignore_funny);
+extern int server_supports(const char *feature);
 
 extern struct packed_git *parse_pack_index(unsigned char *sha1);
 extern struct packed_git *parse_pack_index_file(const unsigned char *sha1,
@@ -338,7 +354,7 @@ extern struct packed_git *find_sha1_pack(const unsigned char *sha1,
 
 extern int use_packed_git(struct packed_git *);
 extern void unuse_packed_git(struct packed_git *);
-extern struct packed_git *add_packed_git(char *, int);
+extern struct packed_git *add_packed_git(char *, int, int);
 extern int num_packed_objects(const struct packed_git *p);
 extern int nth_packed_object_sha1(const struct packed_git *, int, unsigned char*);
 extern int find_pack_entry_one(const unsigned char *, struct pack_entry *, struct packed_git *);
@@ -348,4 +364,55 @@ extern void packed_object_info_detail(struct pack_entry *, char *, unsigned long
 /* Dumb servers support */
 extern int update_server_info(int);
 
+#ifdef NO_MMAP
+
+#ifndef PROT_READ
+#define PROT_READ 1
+#define PROT_WRITE 2
+#define MAP_PRIVATE 1
+#define MAP_FAILED ((void*)-1)
+#endif
+
+extern void *gitfakemmap(void *start, size_t length, int prot , int flags, int fd, off_t offset);
+extern int gitfakemunmap(void *start, size_t length);
+
+#endif
+
+typedef int (*config_fn_t)(const char *, const char *);
+extern int git_default_config(const char *, const char *);
+extern int git_config(config_fn_t fn);
+extern int git_config_int(const char *, const char *);
+extern int git_config_bool(const char *, const char *);
+
+#define MAX_GITNAME (1000)
+extern char git_default_email[MAX_GITNAME];
+extern char git_default_name[MAX_GITNAME];
+
+/* Sane ctype - no locale, and works with signed chars */
+#undef isspace
+#undef isdigit
+#undef isalpha
+#undef isalnum
+#undef tolower
+#undef toupper
+extern unsigned char sane_ctype[256];
+#define GIT_SPACE 0x01
+#define GIT_DIGIT 0x02
+#define GIT_ALPHA 0x04
+#define sane_istest(x,mask) ((sane_ctype[(unsigned char)(x)] & (mask)) != 0)
+#define isspace(x) sane_istest(x,GIT_SPACE)
+#define isdigit(x) sane_istest(x,GIT_DIGIT)
+#define isalpha(x) sane_istest(x,GIT_ALPHA)
+#define isalnum(x) sane_istest(x,GIT_ALPHA | GIT_DIGIT)
+#define tolower(x) sane_case((unsigned char)(x), 0x20)
+#define toupper(x) sane_case((unsigned char)(x), 0)
+
+static inline int sane_case(int x, int high)
+{
+       if (sane_istest(x, GIT_ALPHA))
+               x = (x & ~0x20) | high;
+       return x;
+}
+
+extern int copy_fd(int ifd, int ofd);
 #endif /* CACHE_H */
diff --git a/check-ref-format.c b/check-ref-format.c
new file mode 100644 (file)
index 0000000..a0adb3d
--- /dev/null
@@ -0,0 +1,17 @@
+/*
+ * GIT - The information manager from hell
+ */
+
+#include "cache.h"
+#include "refs.h"
+
+#include <stdio.h>
+
+int main(int ac, char **av)
+{
+       if (ac != 2)
+               usage("git-check-ref-format refname");
+       if (check_ref_format(av[1]))
+               exit(1);
+       return 0;
+}
index f32513c507ff39e09082a9ae7b3bf157f7d21fe9..dab3778a9585bcfde98be436255542df13aacddb 100644 (file)
@@ -63,15 +63,20 @@ static int checkout_file(const char *name)
 
 static int checkout_all(void)
 {
-       int i;
+       int i, errs = 0;
 
        for (i = 0; i < active_nr ; i++) {
                struct cache_entry *ce = active_cache[i];
                if (ce_stage(ce))
                        continue;
                if (checkout_entry(ce, &state) < 0)
-                       return -1;
+                       errs++;
        }
+       if (errs)
+               /* we have already done our error reporting.
+                * exit with the same code as die().
+                */
+               exit(128);
        return 0;
 }
 
@@ -82,8 +87,9 @@ static struct cache_file cache_file;
 
 int main(int argc, char **argv)
 {
-       int i, force_filename = 0;
+       int i;
        int newfd = -1;
+       int all = 0;
 
        if (read_cache() < 0) {
                die("invalid cache");
@@ -91,58 +97,70 @@ int main(int argc, char **argv)
 
        for (i = 1; i < argc; i++) {
                const char *arg = argv[i];
-               if (!force_filename) {
-                       if (!strcmp(arg, "-a")) {
-                               checkout_all();
-                               continue;
-                       }
-                       if (!strcmp(arg, "--")) {
-                               force_filename = 1;
-                               continue;
-                       }
-                       if (!strcmp(arg, "-f")) {
-                               state.force = 1;
-                               continue;
-                       }
-                       if (!strcmp(arg, "-q")) {
-                               state.quiet = 1;
-                               continue;
-                       }
-                       if (!strcmp(arg, "-n")) {
-                               state.not_new = 1;
-                               continue;
-                       }
-                       if (!strcmp(arg, "-u")) {
-                               state.refresh_cache = 1;
-                               if (newfd < 0)
-                                       newfd = hold_index_file_for_update
-                                               (&cache_file,
-                                                get_index_file());
-                               if (newfd < 0)
-                                       die("cannot open index.lock file.");
-                               continue;
-                       }
-                       if (!memcmp(arg, "--prefix=", 9)) {
-                               state.base_dir = arg+9;
-                               state.base_dir_len = strlen(state.base_dir);
-                               continue;
-                       }
-                       if (arg[0] == '-')
-                               usage(checkout_cache_usage);
+
+               if (!strcmp(arg, "--")) {
+                       i++;
+                       break;
+               }
+               if (!strcmp(arg, "-a") || !strcmp(arg, "--all")) {
+                       all = 1;
+                       continue;
+               }
+               if (!strcmp(arg, "-f") || !strcmp(arg, "--force")) {
+                       state.force = 1;
+                       continue;
+               }
+               if (!strcmp(arg, "-q") || !strcmp(arg, "--quiet")) {
+                       state.quiet = 1;
+                       continue;
+               }
+               if (!strcmp(arg, "-n") || !strcmp(arg, "--no-create")) {
+                       state.not_new = 1;
+                       continue;
                }
-               if (state.base_dir_len) {
-                       /* when --prefix is specified we do not
-                        * want to update cache.
-                        */
-                       if (state.refresh_cache) {
-                               close(newfd); newfd = -1;
-                               rollback_index_file(&cache_file);
-                       }
-                       state.refresh_cache = 0;
+               if (!strcmp(arg, "-u") || !strcmp(arg, "--index")) {
+                       state.refresh_cache = 1;
+                       if (newfd < 0)
+                               newfd = hold_index_file_for_update
+                                       (&cache_file,
+                                        get_index_file());
+                       if (newfd < 0)
+                               die("cannot open index.lock file.");
+                       continue;
+               }
+               if (!memcmp(arg, "--prefix=", 9)) {
+                       state.base_dir = arg+9;
+                       state.base_dir_len = strlen(state.base_dir);
+                       continue;
+               }
+               if (arg[0] == '-')
+                       usage(checkout_cache_usage);
+               break;
+       }
+
+       if (state.base_dir_len) {
+               /* when --prefix is specified we do not
+                * want to update cache.
+                */
+               if (state.refresh_cache) {
+                       close(newfd); newfd = -1;
+                       rollback_index_file(&cache_file);
                }
+               state.refresh_cache = 0;
+       }
+
+       /* Check out named files first */
+       for ( ; i < argc; i++) {
+               const char *arg = argv[i];
+
+               if (all)
+                       die("git-checkout-index: don't mix '--all' and explicit filenames");
                checkout_file(arg);
        }
 
+       if (all)
+               checkout_all();
+
        if (0 <= newfd &&
            (write_cache(newfd, active_cache, active_nr) ||
             commit_index_file(&cache_file)))
index 49820c6579dc286634d083a37a80baa508626408..960921903eaa712523af0b03098970127729f363 100644 (file)
@@ -3,8 +3,8 @@
 #include "pkt-line.h"
 #include <sys/wait.h>
 
-static int quiet;
-static const char clone_pack_usage[] = "git-clone-pack [-q] [--exec=<git-upload-pack>] [<host>:]<directory> [<heads>]*";
+static const char clone_pack_usage[] =
+"git-clone-pack [--exec=<git-upload-pack>] [<host>:]<directory> [<heads>]*";
 static const char *exec = "git-upload-pack";
 
 static void clone_handshake(int fd[2], struct ref *ref)
@@ -34,6 +34,12 @@ static void write_one_ref(struct ref *ref)
        int fd;
        char *hex;
 
+       if (!strncmp(ref->name, "refs/", 5) &&
+           check_ref_format(ref->name + 5)) {
+               error("refusing to create funny ref '%s' locally", ref->name);
+               return;
+       }
+
        if (safe_create_leading_directories(path))
                die("unable to create leading directory for %s", ref->name);
        fd = open(path, O_CREAT | O_EXCL | O_WRONLY, 0666);
@@ -51,6 +57,7 @@ static void write_refs(struct ref *ref)
        struct ref *head = NULL, *head_ptr, *master_ref;
        char *head_path;
 
+       /* Upload-pack must report HEAD first */
        if (!strcmp(ref->name, "HEAD")) {
                head = ref;
                ref = ref->next;
@@ -60,17 +67,21 @@ static void write_refs(struct ref *ref)
        while (ref) {
                if (is_master(ref))
                        master_ref = ref;
-               if (head && !memcmp(ref->old_sha1, head->old_sha1, 20)) {
-                       if (!head_ptr || ref == master_ref)
-                               head_ptr = ref;
-               }
+               if (head &&
+                   !memcmp(ref->old_sha1, head->old_sha1, 20) &&
+                   !strncmp(ref->name, "refs/heads/",11) &&
+                   (!head_ptr || ref == master_ref))
+                       head_ptr = ref;
+
                write_one_ref(ref);
                ref = ref->next;
        }
-       if (!head)
+       if (!head) {
+               fprintf(stderr, "No HEAD in remote.\n");
                return;
+       }
 
-       head_path = git_path("HEAD");
+       head_path = strdup(git_path("HEAD"));
        if (!head_ptr) {
                /*
                 * If we had a master ref, and it wasn't HEAD, we need to undo the
@@ -82,6 +93,7 @@ static void write_refs(struct ref *ref)
                        unlink(head_path);
                }
                write_one_ref(head);
+               free(head_path);
                return;
        }
 
@@ -89,56 +101,167 @@ static void write_refs(struct ref *ref)
        if (master_ref)
                return;
 
+       fprintf(stderr, "Setting HEAD to %s\n", head_ptr->name);
+
        /*
         * Uhhuh. Other end didn't have master. We start HEAD off with
         * the first branch with the same value.
         */
-       unlink(head_path);
-       if (symlink(head_ptr->name, head_path) < 0)
+       if (create_symref(head_path, head_ptr->name) < 0)
                die("unable to link HEAD to %s", head_ptr->name);
+       free(head_path);
+}
+
+static int finish_pack(const char *pack_tmp_name)
+{
+       int pipe_fd[2];
+       pid_t pid;
+       char idx[PATH_MAX];
+       char final[PATH_MAX];
+       char hash[41];
+       unsigned char sha1[20];
+       char *cp;
+       int err = 0;
+
+       if (pipe(pipe_fd) < 0)
+               die("git-clone-pack: unable to set up pipe");
+
+       strcpy(idx, pack_tmp_name); /* ".git/objects/pack-XXXXXX" */
+       cp = strrchr(idx, '/');
+       memcpy(cp, "/pidx", 5);
+
+       pid = fork();
+       if (pid < 0)
+               die("git-clone-pack: unable to fork off git-index-pack");
+       if (!pid) {
+               close(0);
+               dup2(pipe_fd[1], 1);
+               close(pipe_fd[0]);
+               close(pipe_fd[1]);
+               execlp("git-index-pack","git-index-pack",
+                      "-o", idx, pack_tmp_name, NULL);
+               error("cannot exec git-index-pack <%s> <%s>",
+                     idx, pack_tmp_name);
+               exit(1);
+       }
+       close(pipe_fd[1]);
+       if (read(pipe_fd[0], hash, 40) != 40) {
+               error("git-clone-pack: unable to read from git-index-pack");
+               err = 1;
+       }
+       close(pipe_fd[0]);
+
+       for (;;) {
+               int status, code;
+               int retval = waitpid(pid, &status, 0);
+
+               if (retval < 0) {
+                       if (errno == EINTR)
+                               continue;
+                       error("waitpid failed (%s)", strerror(retval));
+                       goto error_die;
+               }
+               if (WIFSIGNALED(status)) {
+                       int sig = WTERMSIG(status);
+                       error("git-index-pack died of signal %d", sig);
+                       goto error_die;
+               }
+               if (!WIFEXITED(status)) {
+                       error("git-index-pack died of unnatural causes %d",
+                             status);
+                       goto error_die;
+               }
+               code = WEXITSTATUS(status);
+               if (code) {
+                       error("git-index-pack died with error code %d", code);
+                       goto error_die;
+               }
+               if (err)
+                       goto error_die;
+               break;
+       }
+       hash[40] = 0;
+       if (get_sha1_hex(hash, sha1)) {
+               error("git-index-pack reported nonsense '%s'", hash);
+               goto error_die;
+       }
+       /* Now we have pack in pack_tmp_name[], and
+        * idx in idx[]; rename them to their final names.
+        */
+       snprintf(final, sizeof(final),
+                "%s/pack/pack-%s.pack", get_object_directory(), hash);
+       move_temp_to_file(pack_tmp_name, final);
+       chmod(final, 0444);
+       snprintf(final, sizeof(final),
+                "%s/pack/pack-%s.idx", get_object_directory(), hash);
+       move_temp_to_file(idx, final);
+       chmod(final, 0444);
+       return 0;
+
+ error_die:
+       unlink(idx);
+       unlink(pack_tmp_name);
+       exit(1);
+}
+
+static int clone_without_unpack(int fd[2])
+{
+       char tmpfile[PATH_MAX];
+       int ofd, ifd;
+
+       ifd = fd[0];
+       snprintf(tmpfile, sizeof(tmpfile),
+                "%s/pack/tmp-XXXXXX", get_object_directory());
+       ofd = mkstemp(tmpfile);
+       if (ofd < 0)
+               return error("unable to create temporary file %s", tmpfile);
+
+       while (1) {
+               char buf[8192];
+               ssize_t sz, wsz, pos;
+               sz = read(ifd, buf, sizeof(buf));
+               if (sz == 0)
+                       break;
+               if (sz < 0) {
+                       error("error reading pack (%s)", strerror(errno));
+                       close(ofd);
+                       unlink(tmpfile);
+                       return -1;
+               }
+               pos = 0;
+               while (pos < sz) {
+                       wsz = write(ofd, buf + pos, sz - pos);
+                       if (wsz < 0) {
+                               error("error writing pack (%s)",
+                                     strerror(errno));
+                               close(ofd);
+                               unlink(tmpfile);
+                               return -1;
+                       }
+                       pos += wsz;
+               }
+       }
+       close(ofd);
+       return finish_pack(tmpfile);
 }
 
 static int clone_pack(int fd[2], int nr_match, char **match)
 {
        struct ref *refs;
        int status;
-       pid_t pid;
 
-       get_remote_heads(fd[0], &refs, nr_match, match);
+       get_remote_heads(fd[0], &refs, nr_match, match, 1);
        if (!refs) {
                packet_flush(fd[1]);
                die("no matching remote head");
        }
        clone_handshake(fd, refs);
-       pid = fork();
-       if (pid < 0)
-               die("git-clone-pack: unable to fork off git-unpack-objects");
-       if (!pid) {
-               dup2(fd[0], 0);
-               close(fd[0]);
-               close(fd[1]);
-               execlp("git-unpack-objects", "git-unpack-objects",
-                       quiet ? "-q" : NULL, NULL);
-               die("git-unpack-objects exec failed");
-       }
-       close(fd[0]);
-       close(fd[1]);
-       while (waitpid(pid, &status, 0) < 0) {
-               if (errno != EINTR)
-                       die("waiting for git-unpack-objects: %s", strerror(errno));
-       }
-       if (WIFEXITED(status)) {
-               int code = WEXITSTATUS(status);
-               if (code)
-                       die("git-unpack-objects died with error code %d", code);
+
+       status = clone_without_unpack(fd);
+
+       if (!status)
                write_refs(refs);
-               return 0;
-       }
-       if (WIFSIGNALED(status)) {
-               int sig = WTERMSIG(status);
-               die("git-unpack-objects died of signal %d", sig);
-       }
-       die("Sherlock Holmes! git-unpack-objects died of unnatural causes %d!", status);
+       return status;
 }
 
 int main(int argc, char **argv)
@@ -154,14 +277,14 @@ int main(int argc, char **argv)
                char *arg = argv[i];
 
                if (*arg == '-') {
-                       if (!strcmp("-q", arg)) {
-                               quiet = 1;
+                       if (!strcmp("-q", arg))
                                continue;
-                       }
                        if (!strncmp("--exec=", arg, 7)) {
                                exec = arg + 7;
                                continue;
                        }
+                       if (!strcmp("--keep", arg))
+                               continue;
                        usage(clone_pack_usage);
                }
                dest = arg;
index ad3285b7977b7dd1a7f152652004b2731e75b8cb..992493de2218ad86d59e6a60e1ee3e01f5c7221d 100755 (executable)
@@ -1,12 +1,21 @@
 #!/bin/sh
+#
+# If you installed git by hand previously, you may find this
+# script useful to remove the symbolic links that we shipped
+# for backward compatibility.
+#
+# Running this script with the previous installation directory
+# like this:
+#
+# $ cmd-rename.sh /usr/local/bin/
+#
+# would clean them.
+
 d="$1"
 test -d "$d" || exit
 while read old new
 do
        rm -f "$d/$old"
-       if [ -x "$d/$new" ]; then
-       ln -s "$new" "$d/$old"
-       fi
 done <<\EOF
 git-add-script git-add
 git-archimport-script  git-archimport
@@ -51,7 +60,3 @@ git-update-cache      git-update-index
 git-convert-cache      git-convert-objects
 git-fsck-cache git-fsck-objects
 EOF
-
-# These two are a bit more than symlinks now.
-# git-ssh-push git-ssh-upload
-# git-ssh-pull git-ssh-fetch
index b1ef0b590ab879fbbc93d04a2f8f488a223ea58d..b60299fed0442edec21f123e92774238d4533bea 100644 (file)
@@ -5,10 +5,6 @@
  */
 #include "cache.h"
 
-#include <pwd.h>
-#include <time.h>
-#include <ctype.h>
-
 #define BLOCKING (1ul << 14)
 
 /*
@@ -89,6 +85,9 @@ int main(int argc, char **argv)
        char *buffer;
        unsigned int size;
 
+       setup_ident();
+       git_config(git_default_config);
+
        if (argc < 2 || get_sha1_hex(argv[1], tree_sha1) < 0)
                usage(commit_tree_usage);
 
@@ -104,7 +103,6 @@ int main(int argc, char **argv)
        }
        if (!parents)
                fprintf(stderr, "Committing initial tree %s\n", argv[1]);
-       setup_ident();
 
        init_buffer(&buffer, &size);
        add_buffer(&buffer, &size, "tree %s\n", sha1_to_hex(tree_sha1));
index f735f981bb2d4d7594e416bcb728ac06d09ebd0c..a8c9bfc8baf21a3d42164faa1577f74326b51df6 100644 (file)
--- a/commit.c
+++ b/commit.c
@@ -1,4 +1,3 @@
-#include <ctype.h>
 #include "tag.h"
 #include "commit.h"
 #include "cache.h"
@@ -56,7 +55,7 @@ static struct commit *check_commit(struct object *obj,
 struct commit *lookup_commit_reference_gently(const unsigned char *sha1,
                                              int quiet)
 {
-       struct object *obj = deref_tag(parse_object(sha1));
+       struct object *obj = deref_tag(parse_object(sha1), NULL, 0);
 
        if (!obj)
                return NULL;
diff --git a/compat/mmap.c b/compat/mmap.c
new file mode 100644 (file)
index 0000000..a051c47
--- /dev/null
@@ -0,0 +1,50 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include "../cache.h"
+
+void *gitfakemmap(void *start, size_t length, int prot , int flags, int fd, off_t offset)
+{
+       int n = 0;
+
+       if (start != NULL || !(flags & MAP_PRIVATE))
+               die("Invalid usage of gitfakemmap.");
+
+       if (lseek(fd, offset, SEEK_SET) < 0) {
+               errno = EINVAL;
+               return MAP_FAILED;
+       }
+
+       start = xmalloc(length);
+       if (start == NULL) {
+               errno = ENOMEM;
+               return MAP_FAILED;
+       }
+
+       while (n < length) {
+               int count = read(fd, start+n, length-n);
+
+               if (count == 0) {
+                       memset(start+n, 0, length-n);
+                       break;
+               }
+
+               if (count < 0) {
+                       free(start);
+                       errno = EACCES;
+                       return MAP_FAILED;
+               }
+
+               n += count;
+       }
+
+       return start;
+}
+
+int gitfakemunmap(void *start, size_t length)
+{
+       free(start);
+       return 0;
+}
+
diff --git a/config.c b/config.c
new file mode 100644 (file)
index 0000000..e89bab2
--- /dev/null
+++ b/config.c
@@ -0,0 +1,244 @@
+
+#include "cache.h"
+
+#define MAXNAME (256)
+
+static FILE *config_file;
+static int config_linenr;
+static int get_next_char(void)
+{
+       int c;
+       FILE *f;
+
+       c = '\n';
+       if ((f = config_file) != NULL) {
+               c = fgetc(f);
+               if (c == '\r') {
+                       /* DOS like systems */
+                       c = fgetc(f);
+                       if (c != '\n') {
+                               ungetc(c, f);
+                               c = '\r';
+                       }
+               }
+               if (c == '\n')
+                       config_linenr++;
+               if (c == EOF) {
+                       config_file = NULL;
+                       c = '\n';
+               }
+       }
+       return c;
+}
+
+static char *parse_value(void)
+{
+       static char value[1024];
+       int quote = 0, comment = 0, len = 0, space = 0;
+
+       for (;;) {
+               int c = get_next_char();
+               if (len >= sizeof(value))
+                       return NULL;
+               if (c == '\n') {
+                       if (quote)
+                               return NULL;
+                       value[len] = 0;
+                       return value;
+               }
+               if (comment)
+                       continue;
+               if (isspace(c) && !quote) {
+                       space = 1;
+                       continue;
+               }
+               if (space) {
+                       if (len)
+                               value[len++] = ' ';
+                       space = 0;
+               }
+               if (c == '\\') {
+                       c = get_next_char();
+                       switch (c) {
+                       case '\n':
+                               continue;
+                       case 't':
+                               c = '\t';
+                               break;
+                       case 'b':
+                               c = '\b';
+                               break;
+                       case 'n':
+                               c = '\n';
+                               break;
+                       /* Some characters escape as themselves */
+                       case '\\': case '"':
+                               break;
+                       /* Reject unknown escape sequences */
+                       default:
+                               return NULL;
+                       }
+                       value[len++] = c;
+                       continue;
+               }
+               if (c == '"') {
+                       quote = 1-quote;
+                       continue;
+               }
+               if (!quote) {
+                       if (c == ';' || c == '#') {
+                               comment = 1;
+                               continue;
+                       }
+               }
+               value[len++] = c;
+       }
+}
+
+static int get_value(config_fn_t fn, char *name, unsigned int len)
+{
+       int c;
+       char *value;
+
+       /* Get the full name */
+       for (;;) {
+               c = get_next_char();
+               if (c == EOF)
+                       break;
+               if (!isalnum(c))
+                       break;
+               name[len++] = tolower(c);
+               if (len >= MAXNAME)
+                       return -1;
+       }
+       name[len] = 0;
+       while (c == ' ' || c == '\t')
+               c = get_next_char();
+
+       value = NULL;
+       if (c != '\n') {
+               if (c != '=')
+                       return -1;
+               value = parse_value();
+               if (!value)
+                       return -1;
+       }
+       return fn(name, value);
+}
+
+static int get_base_var(char *name)
+{
+       int baselen = 0;
+
+       for (;;) {
+               int c = get_next_char();
+               if (c == EOF)
+                       return -1;
+               if (c == ']')
+                       return baselen;
+               if (!isalnum(c))
+                       return -1;
+               if (baselen > MAXNAME / 2)
+                       return -1;
+               name[baselen++] = tolower(c);
+       }
+}
+
+static int git_parse_file(config_fn_t fn)
+{
+       int comment = 0;
+       int baselen = 0;
+       static char var[MAXNAME];
+
+       for (;;) {
+               int c = get_next_char();
+               if (c == '\n') {
+                       /* EOF? */
+                       if (!config_file)
+                               return 0;
+                       comment = 0;
+                       continue;
+               }
+               if (comment || isspace(c))
+                       continue;
+               if (c == '#' || c == ';') {
+                       comment = 1;
+                       continue;
+               }
+               if (c == '[') {
+                       baselen = get_base_var(var);
+                       if (baselen <= 0)
+                               break;
+                       var[baselen++] = '.';
+                       var[baselen] = 0;
+                       continue;
+               }
+               if (!isalpha(c))
+                       break;
+               var[baselen] = tolower(c);
+               if (get_value(fn, var, baselen+1) < 0)
+                       break;
+       }
+       die("bad config file line %d", config_linenr);
+}
+
+int git_config_int(const char *name, const char *value)
+{
+       if (value && *value) {
+               char *end;
+               int val = strtol(value, &end, 0);
+               if (!*end)
+                       return val;
+       }
+       die("bad config value for '%s'", name);
+}
+
+int git_config_bool(const char *name, const char *value)
+{
+       if (!value)
+               return 1;
+       if (!*value)
+               return 0;
+       if (!strcasecmp(value, "true"))
+               return 1;
+       if (!strcasecmp(value, "false"))
+               return 0;
+       return git_config_int(name, value) != 0;
+}
+
+int git_default_config(const char *var, const char *value)
+{
+       /* This needs a better name */
+       if (!strcmp(var, "core.filemode")) {
+               trust_executable_bit = git_config_bool(var, value);
+               return 0;
+       }
+
+       if (!strcmp(var, "user.name")) {
+               strncpy(git_default_name, value, sizeof(git_default_name));
+               return 0;
+       }
+
+       if (!strcmp(var, "user.email")) {
+               strncpy(git_default_email, value, sizeof(git_default_email));
+               return 0;
+       }
+
+       /* Add other config variables here.. */
+       return 0;
+}
+
+int git_config(config_fn_t fn)
+{
+       int ret;
+       FILE *f = fopen(git_path("config"), "r");
+
+       ret = -1;
+       if (f) {
+               config_file = f;
+               config_linenr = 1;
+               ret = git_parse_file(fn);
+               fclose(f);
+       }
+       return ret;
+}
index 825c439accfbb4be4daa21a4d8b33addca0cd8b2..c2badc71aa2ccf63fa6d9ef02a5ee76feb54e5c9 100644 (file)
--- a/connect.c
+++ b/connect.c
@@ -1,16 +1,20 @@
 #include "cache.h"
 #include "pkt-line.h"
 #include "quote.h"
+#include "refs.h"
 #include <sys/wait.h>
 #include <sys/socket.h>
 #include <netinet/in.h>
 #include <arpa/inet.h>
 #include <netdb.h>
 
+static char *server_capabilities = NULL;
+
 /*
  * Read all the refs from the other end
  */
-struct ref **get_remote_heads(int in, struct ref **list, int nr_match, char **match)
+struct ref **get_remote_heads(int in, struct ref **list,
+                             int nr_match, char **match, int ignore_funny)
 {
        *list = NULL;
        for (;;) {
@@ -18,7 +22,7 @@ struct ref **get_remote_heads(int in, struct ref **list, int nr_match, char **ma
                unsigned char old_sha1[20];
                static char buffer[1000];
                char *name;
-               int len;
+               int len, name_len;
 
                len = packet_read_line(in, buffer, sizeof(buffer));
                if (!len)
@@ -29,6 +33,18 @@ struct ref **get_remote_heads(int in, struct ref **list, int nr_match, char **ma
                if (len < 42 || get_sha1_hex(buffer, old_sha1) || buffer[40] != ' ')
                        die("protocol error: expected sha/ref, got '%s'", buffer);
                name = buffer + 41;
+
+               if (ignore_funny && 45 < len && !memcmp(name, "refs/", 5) &&
+                   check_ref_format(name + 5))
+                       continue;
+
+               name_len = strlen(name);
+               if (len != name_len + 41) {
+                       if (server_capabilities)
+                               free(server_capabilities);
+                       server_capabilities = strdup(name + name_len + 1);
+               }
+
                if (nr_match && !path_match(name, nr_match, match))
                        continue;
                ref = xcalloc(1, sizeof(*ref) + len - 40);
@@ -40,6 +56,12 @@ struct ref **get_remote_heads(int in, struct ref **list, int nr_match, char **ma
        return list;
 }
 
+int server_supports(const char *feature)
+{
+       return server_capabilities &&
+               strstr(server_capabilities, feature) != NULL;
+}
+
 int get_ack(int fd, unsigned char *result_sha1)
 {
        static char line[1000];
@@ -52,8 +74,11 @@ int get_ack(int fd, unsigned char *result_sha1)
        if (!strcmp(line, "NAK"))
                return 0;
        if (!strncmp(line, "ACK ", 3)) {
-               if (!get_sha1_hex(line+4, result_sha1))
+               if (!get_sha1_hex(line+4, result_sha1)) {
+                       if (strstr(line+45, "continue"))
+                               return 2;
                        return 1;
+               }
        }
        die("git-fetch_pack: expected ACK/NAK, got '%s'", line);
 }
@@ -284,12 +309,18 @@ static enum protocol get_protocol(const char *name)
                return PROTO_SSH;
        if (!strcmp(name, "git"))
                return PROTO_GIT;
+       if (!strcmp(name, "git+ssh"))
+               return PROTO_SSH;
+       if (!strcmp(name, "ssh+git"))
+               return PROTO_SSH;
        die("I don't handle protocol '%s'", name);
 }
 
 #define STR_(s)        # s
 #define STR(s) STR_(s)
 
+#ifndef NO_IPV6
+
 static int git_tcp_connect(int fd[2], const char *prog, char *host, char *path)
 {
        int sockfd = -1;
@@ -346,6 +377,77 @@ static int git_tcp_connect(int fd[2], const char *prog, char *host, char *path)
        return 0;
 }
 
+#else /* NO_IPV6 */
+
+static int git_tcp_connect(int fd[2], const char *prog, char *host, char *path)
+{
+       int sockfd = -1;
+       char *colon, *end;
+       char *port = STR(DEFAULT_GIT_PORT), *ep;
+       struct hostent *he;
+       struct sockaddr_in sa;
+       char **ap;
+       unsigned int nport;
+
+       if (host[0] == '[') {
+               end = strchr(host + 1, ']');
+               if (end) {
+                       *end = 0;
+                       end++;
+                       host++;
+               } else
+                       end = host;
+       } else
+               end = host;
+       colon = strchr(end, ':');
+
+       if (colon) {
+               *colon = 0;
+               port = colon + 1;
+       }
+
+
+       he = gethostbyname(host);
+       if (!he)
+               die("Unable to look up %s (%s)", host, hstrerror(h_errno));
+       nport = strtoul(port, &ep, 10);
+       if ( ep == port || *ep ) {
+               /* Not numeric */
+               struct servent *se = getservbyname(port,"tcp");
+               if ( !se )
+                       die("Unknown port %s\n", port);
+               nport = se->s_port;
+       }
+
+       for (ap = he->h_addr_list; *ap; ap++) {
+               sockfd = socket(he->h_addrtype, SOCK_STREAM, 0);
+               if (sockfd < 0)
+                       continue;
+
+               memset(&sa, 0, sizeof sa);
+               sa.sin_family = he->h_addrtype;
+               sa.sin_port = htons(nport);
+               memcpy(&sa.sin_addr, ap, he->h_length);
+
+               if (connect(sockfd, (struct sockaddr *)&sa, sizeof sa) < 0) {
+                       close(sockfd);
+                       sockfd = -1;
+                       continue;
+               }
+               break;
+       }
+
+       if (sockfd < 0)
+               die("unable to connect a socket (%s)", strerror(errno));
+
+       fd[0] = sockfd;
+       fd[1] = sockfd;
+       packet_write(sockfd, "%s %s\n", prog, path);
+       return 0;
+}
+
+#endif /* NO_IPV6 */
+
 /*
  * Yeah, yeah, fixme. Need to pass in the heads etc.
  */
index 9ad0c77678a740c82c91b4f99de039e12605808d..a892013f0f37480fbf13a9511f2146d1681ba136 100644 (file)
@@ -1,6 +1,5 @@
 #define _XOPEN_SOURCE /* glibc2 needs this */
 #include <time.h>
-#include <ctype.h>
 #include "cache.h"
 
 struct entry {
diff --git a/copy.c b/copy.c
new file mode 100644 (file)
index 0000000..e1cd5d0
--- /dev/null
+++ b/copy.c
@@ -0,0 +1,40 @@
+#include "cache.h"
+
+int copy_fd(int ifd, int ofd)
+{
+       while (1) {
+               int len;
+               char buffer[8192];
+               char *buf = buffer;
+               len = read(ifd, buffer, sizeof(buffer));
+               if (!len)
+                       break;
+               if (len < 0) {
+                       int read_error;
+                       if (errno == EAGAIN)
+                               continue;
+                       read_error = errno;
+                       close(ifd);
+                       return error("copy-fd: read returned %s",
+                                    strerror(read_error));
+               }
+               while (1) {
+                       int written = write(ofd, buf, len);
+                       if (written > 0) {
+                               buf += written;
+                               len -= written;
+                               if (!len)
+                                       break;
+                       }
+                       if (!written)
+                               return error("copy-fd: write returned 0");
+                       if (errno == EAGAIN || errno == EINTR)
+                               continue;
+                       return error("copy-fd: write returned %s",
+                                    strerror(errno));
+               }
+       }
+       close(ifd);
+       return 0;
+}
+
diff --git a/ctype.c b/ctype.c
new file mode 100644 (file)
index 0000000..56bdffa
--- /dev/null
+++ b/ctype.c
@@ -0,0 +1,23 @@
+/*
+ * Sane locale-independent, ASCII ctype.
+ *
+ * No surprises, and works with signed and unsigned chars.
+ */
+#include "cache.h"
+
+#define SS GIT_SPACE
+#define AA GIT_ALPHA
+#define DD GIT_DIGIT
+
+unsigned char sane_ctype[256] = {
+        0,  0,  0,  0,  0,  0,  0,  0,  0, SS, SS,  0,  0, SS,  0,  0,         /* 0-15 */
+        0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,         /* 16-15 */
+       SS,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,         /* 32-15 */
+       DD, DD, DD, DD, DD, DD, DD, DD, DD, DD,  0,  0,  0,  0,  0,  0,         /* 48-15 */
+        0, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA,         /* 64-15 */
+       AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA,  0,  0,  0,  0,  0,         /* 80-15 */
+        0, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA,         /* 96-15 */
+       AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA,  0,  0,  0,  0,  0,         /* 112-15 */
+       /* Nothing in the 128.. range */
+};
+
index 5100cf2f44d735c0e46aba74dff1504cc3c36ec6..c3f86410d4fe392a2ced76b09e6af8ba8af534d5 100644 (file)
--- a/daemon.c
+++ b/daemon.c
-#include "cache.h"
-#include "pkt-line.h"
 #include <signal.h>
 #include <sys/wait.h>
 #include <sys/socket.h>
 #include <sys/time.h>
+#include <sys/poll.h>
 #include <netdb.h>
 #include <netinet/in.h>
+#include <arpa/inet.h>
+#include <syslog.h>
+#include "pkt-line.h"
+#include "cache.h"
+
+static int log_syslog;
+static int verbose;
+
+static const char daemon_usage[] =
+"git-daemon [--verbose] [--syslog] [--inetd | --port=n] [--export-all]\n"
+"           [--timeout=n] [--init-timeout=n] [directory...]";
+
+/* List of acceptable pathname prefixes */
+static char **ok_paths = NULL;
+
+/* If this is set, git-daemon-export-ok is not required */
+static int export_all_trees = 0;
 
-static const char daemon_usage[] = "git-daemon [--inetd | --port=n]";
+/* Timeout, and initial timeout */
+static unsigned int timeout = 0;
+static unsigned int init_timeout = 0;
 
-static int upload(char *dir, int dirlen)
+static void logreport(int priority, const char *err, va_list params)
 {
-       if (chdir(dir) < 0)
+       /* We should do a single write so that it is atomic and output
+        * of several processes do not get intermingled. */
+       char buf[1024];
+       int buflen;
+       int maxlen, msglen;
+
+       /* sizeof(buf) should be big enough for "[pid] \n" */
+       buflen = snprintf(buf, sizeof(buf), "[%ld] ", (long) getpid());
+
+       maxlen = sizeof(buf) - buflen - 1; /* -1 for our own LF */
+       msglen = vsnprintf(buf + buflen, maxlen, err, params);
+
+       if (log_syslog) {
+               syslog(priority, "%s", buf);
+               return;
+       }
+
+       /* maxlen counted our own LF but also counts space given to
+        * vsnprintf for the terminating NUL.  We want to make sure that
+        * we have space for our own LF and NUL after the "meat" of the
+        * message, so truncate it at maxlen - 1.
+        */
+       if (msglen > maxlen - 1)
+               msglen = maxlen - 1;
+       else if (msglen < 0)
+               msglen = 0; /* Protect against weird return values. */
+       buflen += msglen;
+
+       buf[buflen++] = '\n';
+       buf[buflen] = '\0';
+
+       write(2, buf, buflen);
+}
+
+static void logerror(const char *err, ...)
+{
+       va_list params;
+       va_start(params, err);
+       logreport(LOG_ERR, err, params);
+       va_end(params);
+}
+
+static void loginfo(const char *err, ...)
+{
+       va_list params;
+       if (!verbose)
+               return;
+       va_start(params, err);
+       logreport(LOG_INFO, err, params);
+       va_end(params);
+}
+
+static int path_ok(const char *dir)
+{
+       const char *p = dir;
+       char **pp;
+       int sl, ndot;
+
+       /* The pathname here should be an absolute path. */
+       if ( *p++ != '/' )
+               return 0;
+
+       sl = 1;  ndot = 0;
+
+       for (;;) {
+               if ( *p == '.' ) {
+                       ndot++;
+               } else if ( *p == '\0' ) {
+                       /* Reject "." and ".." at the end of the path */
+                       if ( sl && ndot > 0 && ndot < 3 )
+                               return 0;
+
+                       /* Otherwise OK */
+                       break;
+               } else if ( *p == '/' ) {
+                       /* Refuse "", "." or ".." */
+                       if ( sl && ndot < 3 )
+                               return 0;
+                       sl = 1;
+                       ndot = 0;
+               } else {
+                       sl = ndot = 0;
+               }
+               p++;
+       }
+
+       if ( ok_paths && *ok_paths ) {
+               int ok = 0;
+               int dirlen = strlen(dir);
+
+               for ( pp = ok_paths ; *pp ; pp++ ) {
+                       int len = strlen(*pp);
+                       if ( len <= dirlen &&
+                            !strncmp(*pp, dir, len) &&
+                            (dir[len] == '/' || dir[len] == '\0') ) {
+                               ok = 1;
+                               break;
+                       }
+               }
+
+               if ( !ok )
+                       return 0; /* Path not in whitelist */
+       }
+
+       return 1;               /* Path acceptable */
+}
+
+static int set_dir(const char *dir)
+{
+       if (!path_ok(dir)) {
+               errno = EACCES;
+               return -1;
+       }
+
+       if ( chdir(dir) )
                return -1;
-       chdir(".git");
 
        /*
         * Security on the cheap.
         *
-        * We want a readable HEAD, usable "objects" directory, and 
+        * We want a readable HEAD, usable "objects" directory, and
         * a "git-daemon-export-ok" flag that says that the other side
         * is ok with us doing this.
         */
-       if (access("git-daemon-export-ok", F_OK) ||
-           access("objects/00", X_OK) ||
-           access("HEAD", R_OK))
+       if (!export_all_trees && access("git-daemon-export-ok", F_OK)) {
+               errno = EACCES;
                return -1;
+       }
+
+       if (access("objects/", X_OK) || access("HEAD", R_OK)) {
+               errno = EINVAL;
+               return -1;
+       }
+
+       /* If all this passed, we're OK */
+       return 0;
+}
+
+static int upload(char *dir)
+{
+       /* Try paths in this order */
+       static const char *paths[] = { "%s", "%s/.git", "%s.git", "%s.git/.git", NULL };
+       const char **pp;
+       /* Enough for the longest path above including final null */
+       int buflen = strlen(dir)+10;
+       char *dirbuf = xmalloc(buflen);
+       /* Timeout as string */
+       char timeout_buf[64];
+
+       loginfo("Request for '%s'", dir);
+
+       for ( pp = paths ; *pp ; pp++ ) {
+               snprintf(dirbuf, buflen, *pp, dir);
+               if ( !set_dir(dirbuf) )
+                       break;
+       }
+
+       if ( !*pp ) {
+               logerror("Cannot set directory '%s': %s", dir, strerror(errno));
+               return -1;
+       }
 
        /*
         * We'll ignore SIGTERM from now on, we have a
@@ -33,8 +197,10 @@ static int upload(char *dir, int dirlen)
         */
        signal(SIGTERM, SIG_IGN);
 
+       snprintf(timeout_buf, sizeof timeout_buf, "--timeout=%u", timeout);
+
        /* git-upload-pack only ever reads stuff, so this is safe */
-       execlp("git-upload-pack", "git-upload-pack", ".", NULL);
+       execlp("git-upload-pack", "git-upload-pack", "--strict", timeout_buf, ".", NULL);
        return -1;
 }
 
@@ -43,15 +209,17 @@ static int execute(void)
        static char line[1000];
        int len;
 
+       alarm(init_timeout ? init_timeout : timeout);
        len = packet_read_line(0, line, sizeof(line));
+       alarm(0);
 
        if (len && line[len-1] == '\n')
                line[--len] = 0;
 
        if (!strncmp("git-upload-pack /", line, 17))
-               return upload(line + 16, len - 16);
+               return upload(line+16);
 
-       fprintf(stderr, "got bad connection '%s'\n", line);
+       logerror("Protocol error: '%s'", line);
        return -1;
 }
 
@@ -181,6 +349,8 @@ static void check_max_connections(void)
 static void handle(int incoming, struct sockaddr *addr, int addrlen)
 {
        pid_t pid = fork();
+       char addrbuf[256] = "";
+       int port = -1;
 
        if (pid) {
                unsigned idx;
@@ -200,34 +370,65 @@ static void handle(int incoming, struct sockaddr *addr, int addrlen)
        dup2(incoming, 0);
        dup2(incoming, 1);
        close(incoming);
+
+       if (addr->sa_family == AF_INET) {
+               struct sockaddr_in *sin_addr = (void *) addr;
+               inet_ntop(AF_INET, &sin_addr->sin_addr, addrbuf, sizeof(addrbuf));
+               port = sin_addr->sin_port;
+
+#ifndef NO_IPV6
+       } else if (addr->sa_family == AF_INET6) {
+               struct sockaddr_in6 *sin6_addr = (void *) addr;
+
+               char *buf = addrbuf;
+               *buf++ = '['; *buf = '\0'; /* stpcpy() is cool */
+               inet_ntop(AF_INET6, &sin6_addr->sin6_addr, buf, sizeof(addrbuf) - 1);
+               strcat(buf, "]");
+
+               port = sin6_addr->sin6_port;
+#endif
+       }
+       loginfo("Connection from %s:%d", addrbuf, port);
+
        exit(execute());
 }
 
 static void child_handler(int signo)
 {
        for (;;) {
-               pid_t pid = waitpid(-1, NULL, WNOHANG);
+               int status;
+               pid_t pid = waitpid(-1, &status, WNOHANG);
 
                if (pid > 0) {
                        unsigned reaped = children_reaped;
                        dead_child[reaped % MAX_CHILDREN] = pid;
                        children_reaped = reaped + 1;
+                       /* XXX: Custom logging, since we don't wanna getpid() */
+                       if (verbose) {
+                               char *dead = "";
+                               if (!WIFEXITED(status) || WEXITSTATUS(status) > 0)
+                                       dead = " (with error)";
+                               if (log_syslog)
+                                       syslog(LOG_INFO, "[%d] Disconnected%s", pid, dead);
+                               else
+                                       fprintf(stderr, "[%d] Disconnected%s\n", pid, dead);
+                       }
                        continue;
                }
                break;
        }
 }
 
-static int serve(int port)
+#ifndef NO_IPV6
+
+static int socksetup(int port, int **socklist_p)
 {
-       struct addrinfo hints, *ai0, *ai;
-       int gai;
        int socknum = 0, *socklist = NULL;
        int maxfd = -1;
-       fd_set fds_init, fds;
        char pbuf[NI_MAXSERV];
 
-       signal(SIGCHLD, child_handler);
+       struct addrinfo hints, *ai0, *ai;
+       int gai;
 
        sprintf(pbuf, "%d", port);
        memset(&hints, 0, sizeof(hints));
@@ -240,8 +441,6 @@ static int serve(int port)
        if (gai)
                die("getaddrinfo() failed: %s\n", gai_strerror(gai));
 
-       FD_ZERO(&fds_init);
-
        for (ai = ai0; ai; ai = ai->ai_next) {
                int sockfd;
                int *newlist;
@@ -280,23 +479,63 @@ static int serve(int port)
                socklist = newlist;
                socklist[socknum++] = sockfd;
 
-               FD_SET(sockfd, &fds_init);
                if (maxfd < sockfd)
                        maxfd = sockfd;
        }
 
        freeaddrinfo(ai0);
 
-       if (socknum == 0)
-               die("unable to allocate any listen sockets on port %u", port);
+       *socklist_p = socklist;
+       return socknum;
+}
+
+#else /* NO_IPV6 */
+
+static int socksetup(int port, int **socklist_p)
+{
+       struct sockaddr_in sin;
+       int sockfd;
+
+       sockfd = socket(AF_INET, SOCK_STREAM, 0);
+       if (sockfd < 0)
+               return 0;
+
+       memset(&sin, 0, sizeof sin);
+       sin.sin_family = AF_INET;
+       sin.sin_addr.s_addr = htonl(INADDR_ANY);
+       sin.sin_port = htons(port);
+
+       if ( bind(sockfd, (struct sockaddr *)&sin, sizeof sin) < 0 ) {
+               close(sockfd);
+               return 0;
+       }
+
+       *socklist_p = xmalloc(sizeof(int));
+       **socklist_p = sockfd;
+}
+
+#endif
+
+static int service_loop(int socknum, int *socklist)
+{
+       struct pollfd *pfd;
+       int i;
+
+       pfd = xcalloc(socknum, sizeof(struct pollfd));
+
+       for (i = 0; i < socknum; i++) {
+               pfd[i].fd = socklist[i];
+               pfd[i].events = POLLIN;
+       }
+
+       signal(SIGCHLD, child_handler);
 
        for (;;) {
                int i;
-               fds = fds_init;
-               
-               if (select(maxfd + 1, &fds, NULL, NULL, NULL) < 0) {
+
+               if (poll(pfd, socknum, -1) < 0) {
                        if (errno != EINTR) {
-                               error("select failed, resuming: %s",
+                               error("poll failed, resuming: %s",
                                      strerror(errno));
                                sleep(1);
                        }
@@ -304,12 +543,10 @@ static int serve(int port)
                }
 
                for (i = 0; i < socknum; i++) {
-                       int sockfd = socklist[i];
-
-                       if (FD_ISSET(sockfd, &fds)) {
+                       if (pfd[i].revents & POLLIN) {
                                struct sockaddr_storage ss;
-                               int sslen = sizeof(ss);
-                               int incoming = accept(sockfd, (struct sockaddr *)&ss, &sslen);
+                               unsigned int sslen = sizeof(ss);
+                               int incoming = accept(pfd[i].fd, (struct sockaddr *)&ss, &sslen);
                                if (incoming < 0) {
                                        switch (errno) {
                                        case EAGAIN:
@@ -326,6 +563,17 @@ static int serve(int port)
        }
 }
 
+static int serve(int port)
+{
+       int socknum, *socklist;
+
+       socknum = socksetup(port, &socklist);
+       if (socknum == 0)
+               die("unable to allocate any listen sockets on port %u", port);
+
+       return service_loop(socknum, socklist);
+}
+
 int main(int argc, char **argv)
 {
        int port = DEFAULT_GIT_PORT;
@@ -344,11 +592,36 @@ int main(int argc, char **argv)
                                continue;
                        }
                }
-
                if (!strcmp(arg, "--inetd")) {
                        inetd_mode = 1;
                        continue;
                }
+               if (!strcmp(arg, "--verbose")) {
+                       verbose = 1;
+                       continue;
+               }
+               if (!strcmp(arg, "--syslog")) {
+                       log_syslog = 1;
+                       openlog("git-daemon", 0, LOG_DAEMON);
+                       continue;
+               }
+               if (!strcmp(arg, "--export-all")) {
+                       export_all_trees = 1;
+                       continue;
+               }
+               if (!strncmp(arg, "--timeout=", 10)) {
+                       timeout = atoi(arg+10);
+               }
+               if (!strncmp(arg, "--init-timeout=", 15)) {
+                       init_timeout = atoi(arg+15);
+               }
+               if (!strcmp(arg, "--")) {
+                       ok_paths = &argv[i+1];
+                       break;
+               } else if (arg[0] != '-') {
+                       ok_paths = &argv[i];
+                       break;
+               }
 
                usage(daemon_usage);
        }
@@ -356,7 +629,7 @@ int main(int argc, char **argv)
        if (inetd_mode) {
                fclose(stderr); //FIXME: workaround
                return execute();
+       } else {
+               return serve(port);
        }
-
-       return serve(port);
 }
diff --git a/date.c b/date.c
index b46f2ce344d53585901820b39dffc8cb4c631364..63f5a0919768112c0d3301e7afaa59edcce7da36 100644 (file)
--- a/date.c
+++ b/date.c
@@ -4,7 +4,6 @@
  * Copyright (C) Linus Torvalds, 2005
  */
 
-#include <ctype.h>
 #include <time.h>
 
 #include "cache.h"
@@ -386,12 +385,23 @@ static int match_tz(const char *date, int *offp)
        return end - date;
 }
 
+static int date_string(unsigned long date, int offset, char *buf, int len)
+{
+       int sign = '+';
+
+       if (offset < 0) {
+               offset = -offset;
+               sign = '-';
+       }
+       return snprintf(buf, len, "%lu %c%02d%02d", date, sign, offset / 60, offset % 60);
+}
+
 /* Gr. strptime is crap for this; it doesn't have a way to require RFC2822
    (i.e. English) day/month names, and it doesn't work correctly with %z. */
-void parse_date(const char *date, char *result, int maxlen)
+int parse_date(const char *date, char *result, int maxlen)
 {
        struct tm tm;
-       int offset, sign, tm_gmt;
+       int offset, tm_gmt;
        time_t then;
 
        memset(&tm, 0, sizeof(tm));
@@ -431,18 +441,11 @@ void parse_date(const char *date, char *result, int maxlen)
                offset = (then - mktime(&tm)) / 60;
 
        if (then == -1)
-               return;
+               return -1;
 
        if (!tm_gmt)
                then -= offset * 60;
-
-       sign = '+';
-       if (offset < 0) {
-               offset = -offset;
-               sign = '-';
-       }
-
-       snprintf(result, maxlen, "%lu %c%02d%02d", then, sign, offset/60, offset % 60);
+       return date_string(then, offset, result, maxlen);
 }
 
 void datestamp(char *buf, int bufsize)
@@ -455,5 +458,5 @@ void datestamp(char *buf, int bufsize)
        offset = my_mktime(localtime(&now)) - now;
        offset /= 60;
 
-       snprintf(buf, bufsize, "%lu %+05d", now, offset/60*100 + offset%60);
+       date_string(now, offset, buf, bufsize);
 }
index a48c889d10d361c5d6c4161be3a9e8a1cde8d9d1..9cf900fb0892210206b134c65713d9dea33df1fa 100644 (file)
@@ -1,26 +1,57 @@
-git-core (0.99.7d-0) unstable; urgency=low
+git-core (0.99.9e-0) unstable; urgency=low
 
-  * GIT 0.99.7d
+  * GIT 0.99.9e
 
- -- Junio C Hamano <junkio@cox.net>  Sun, 25 Sep 2005 00:40:46 -0700
+ -- Junio C Hamano <junkio@cox.net>  Sun,  6 Nov 2005 18:37:18 -0800
 
-git-core (0.99.7c-0) unstable; urgency=low
+git-core (0.99.9d-0) unstable; urgency=low
 
-  * GIT 0.99.7c
+  * GIT 0.99.9d
 
- -- Junio C Hamano <junkio@cox.net>  Sat, 24 Sep 2005 11:33:36 -0700
+ -- Junio C Hamano <junkio@cox.net>  Sat,  5 Nov 2005 11:46:37 -0800
 
-git-core (0.99.7b-0) unstable; urgency=low
+git-core (0.99.9c-0) unstable; urgency=low
 
-  * GIT 0.99.7b
+  * GIT 0.99.9c
 
- -- Junio C Hamano <junkio@cox.net>  Thu, 22 Sep 2005 21:46:44 -0700
+ -- Junio C Hamano <junkio@cox.net>  Thu,  3 Nov 2005 15:44:54 -0800
 
-git-core (0.99.7a-0) unstable; urgency=low
+git-core (0.99.9b-0) unstable; urgency=low
 
-  * GIT 0.99.7a
+  * GIT 0.99.9b
 
- -- Junio C Hamano <junkio@cox.net>  Mon, 19 Sep 2005 19:29:07 -0700
+ -- Junio C Hamano <junkio@cox.net>  Tue,  1 Nov 2005 21:39:39 -0800
+
+git-core (0.99.9a-0) unstable; urgency=low
+
+  * GIT 0.99.9a
+
+ -- Junio C Hamano <junkio@cox.net>  Sun, 30 Oct 2005 15:03:32 -0800
+
+git-core (0.99.9.GIT-0) unstable; urgency=low
+
+  * Test Build.
+
+ -- Junio C Hamano <junkio@cox.net>  Sat,  5 Nov 2005 11:18:13 -0800
+
+git-core (0.99.9-1) unstable; urgency=low
+
+  * Split the git-core binary package into core, doc, and foreign SCM
+    interoperability modules.
+
+ -- Junio C Hamano <junkio@cox.net>  Sat,  5 Nov 2005 11:18:13 -0800
+
+git-core (0.99.9-0) unstable; urgency=low
+
+  * GIT 0.99.9
+
+ -- Junio C Hamano <junkio@cox.net>  Sat, 29 Oct 2005 14:34:30 -0700
+
+git-core (0.99.8-0) unstable; urgency=low
+
+  * GIT 0.99.8
+
+ -- Junio C Hamano <junkio@cox.net>  Sun,  2 Oct 2005 12:54:26 -0700
 
 git-core (0.99.7-0) unstable; urgency=low
 
index 5d75c325e2dad1393cf5845dc9ccc6d54743e0e1..8cc527e2daf7888ec60f2f0fe2fa8910ef770002 100644 (file)
@@ -2,14 +2,14 @@ Source: git-core
 Section: devel
 Priority: optional
 Maintainer: Junio C Hamano <junkio@cox.net>
-Build-Depends-Indep: libz-dev, libssl-dev, libcurl3-dev, asciidoc (>= 6.0.3), xmlto, debhelper (>= 4.0.0), bc
+Build-Depends-Indep: libz-dev, libssl-dev, libcurl3-dev|libcurl3-gnutls-dev|libcurl3-openssl-dev, asciidoc (>= 6.0.3), xmlto, debhelper (>= 4.0.0), bc
 Standards-Version: 3.6.1
 
 Package: git-core
 Architecture: any
-Depends: ${shlibs:Depends}, ${perl:Depends}, ${misc:Depends}, patch, rcs
-Recommends: rsync, curl, ssh, libmail-sendmail-perl, libemail-valid-perl, python (>= 2.4.0), less
-Suggests: cogito
+Depends: ${shlibs:Depends}, ${perl:Depends}, ${misc:Depends}, rcs
+Recommends: rsync, curl, ssh, python (>= 2.4.0), less
+Suggests: cogito, patch
 Conflicts: git, cogito (<< 0.13)
 Description: The git content addressable filesystem
  GIT comes in two layers. The bottom layer is merely an extremely fast
@@ -18,9 +18,46 @@ Description: The git content addressable filesystem
  enables human beings to work with the database in a manner to a degree
  similar to other SCM tools.
 
+Package: git-doc
+Architecture: all
+Depends: ${shlibs:Depends}, ${misc:Depends}, git-core
+Description: The git content addressable filesystem, Documentation
+ This package contains documentation for GIT.
+
 Package: git-tk
 Architecture: all
 Depends: ${shlibs:Depends}, ${misc:Depends}, git-core, tk8.4
 Description: The git content addressable filesystem, GUI add-on
  This package contains 'gitk', the git revision tree visualizer.
 
+Package: git-svn
+Architecture: all
+Depends: ${shlibs:Depends}, ${misc:Depends}, ${perl:Depends}, git-core, libsvn-core-perl (>= 1.2.1)
+Suggests: subversion
+Description: The git content addressable filesystem, SVN interoperability
+ This package contains 'git-svnimport', to import development history from
+ SVN repositories.
+
+Package: git-arch
+Architecture: all
+Depends: ${shlibs:Depends}, ${misc:Depends}, ${perl:Depends}, git-core
+Suggests: tla, bazaar
+Description: The git content addressable filesystem, GNUArch interoperability
+ This package contains 'git-archimport', to import development history from
+ GNUArch repositories.
+
+Package: git-cvs
+Architecture: all
+Depends: ${shlibs:Depends}, ${misc:Depends}, ${perl:Depends}, git-core
+Suggests: cvs
+Description: The git content addressable filesystem, CVS interoperability
+ This package contains 'git-cvsimport', to import development history from
+ CVS repositories.
+
+Package: git-email
+Architecture: all
+Depends: ${shlibs:Depends}, ${misc:Depends}, git-core, libmail-sendmail-perl, libemail-valid-perl
+Description: The git content addressable filesystem, e-mail add-on
+ This package contains 'git-send-email', to send a series of patch e-mails.
+
+
diff --git a/debian/git-arch.files b/debian/git-arch.files
new file mode 100644 (file)
index 0000000..1ad4656
--- /dev/null
@@ -0,0 +1,2 @@
+/usr/bin/git-archimport
+/usr/share/doc/git-core/git-archimport.*
diff --git a/debian/git-cvs.files b/debian/git-cvs.files
new file mode 100644 (file)
index 0000000..8bf5090
--- /dev/null
@@ -0,0 +1,2 @@
+/usr/bin/git-cvsimport
+/usr/share/doc/git-core/git-cvsimport.*
diff --git a/debian/git-doc.files b/debian/git-doc.files
new file mode 100644 (file)
index 0000000..567f5d7
--- /dev/null
@@ -0,0 +1,7 @@
+/usr/share/doc/git-core/*.txt
+/usr/share/doc/git-core/*.html
+/usr/share/doc/git-core/*/*.html
+/usr/share/doc/git-core/*/*.txt
+
+
+
diff --git a/debian/git-email.files b/debian/git-email.files
new file mode 100644 (file)
index 0000000..236754c
--- /dev/null
@@ -0,0 +1,2 @@
+/usr/bin/git-send-email
+/usr/share/doc/git-core/git-send-email.*
diff --git a/debian/git-svn.files b/debian/git-svn.files
new file mode 100644 (file)
index 0000000..317b12a
--- /dev/null
@@ -0,0 +1,2 @@
+/usr/bin/git-svnimport
+/usr/share/doc/git-core/git-svnimport.*
index 568d430932bf96efd9c6a448adbb42507c821a0f..4ab221ce9ea5a24201b1bf37c3c9d07eff243770 100755 (executable)
@@ -41,7 +41,7 @@ MAN_DESTDIR := $(DESTDIR)/$(MANDIR)
 build: debian/build-stamp
 debian/build-stamp:
        dh_testdir
-       $(MAKE) prefix=$(PREFIX) PYTHON_PATH=/usr/bin/python2.4 all doc test
+       $(MAKE) prefix=$(PREFIX) PYTHON_PATH=/usr/bin/python2.4 all test doc
        touch debian/build-stamp
 
 debian-clean:
@@ -62,10 +62,15 @@ install: build
        make DESTDIR=$(DESTDIR) prefix=$(PREFIX) mandir=$(MANDIR) \
                install install-doc
 
-       mkdir -p $(DOC_DESTDIR)
-       find $(DOC) '(' -name '*.txt' -o -name '*.html' ')' -exec install {} $(DOC_DESTDIR) ';'
+       make -C Documentation DESTDIR=$(DESTDIR) prefix=$(PREFIX) \
+               WEBDOC_DEST=$(DOC_DESTDIR) install-webdoc
 
+       dh_movefiles -p git-arch
+       dh_movefiles -p git-cvs
+       dh_movefiles -p git-svn
        dh_movefiles -p git-tk
+       dh_movefiles -p git-email
+       dh_movefiles -p git-doc
        dh_movefiles -p git-core
        find debian/tmp -type d -o -print | sed -e 's/^/? /'
 
index 89eb29b3e26678a294353ec4b6f52ca3285c7adc..17899390b80f4bfc03718e92029b5c4de65055b6 100644 (file)
@@ -11,87 +11,61 @@ static const char diff_files_usage[] =
 "[<common diff options>] [<path>...]"
 COMMON_DIFF_OPTIONS_HELP;
 
-static int diff_output_format = DIFF_FORMAT_RAW;
-static int diff_line_termination = '\n';
-static int detect_rename = 0;
-static int find_copies_harder = 0;
-static int diff_setup_opt = 0;
-static int diff_score_opt = 0;
-static const char *pickaxe = NULL;
-static int pickaxe_opts = 0;
-static int diff_break_opt = -1;
-static const char *orderfile = NULL;
-static const char *diff_filter = NULL;
+static struct diff_options diff_options;
 static int silent = 0;
 
 static void show_unmerge(const char *path)
 {
-       diff_unmerge(path);
+       diff_unmerge(&diff_options, path);
 }
 
 static void show_file(int pfx, struct cache_entry *ce)
 {
-       diff_addremove(pfx, ntohl(ce->ce_mode), ce->sha1, ce->name, NULL);
+       diff_addremove(&diff_options, pfx, ntohl(ce->ce_mode),
+                      ce->sha1, ce->name, NULL);
 }
 
 static void show_modified(int oldmode, int mode,
                          const unsigned char *old_sha1, const unsigned char *sha1,
                          char *path)
 {
-       diff_change(oldmode, mode, old_sha1, sha1, path, NULL);
+       diff_change(&diff_options, oldmode, mode, old_sha1, sha1, path, NULL);
 }
 
-int main(int argc, char **argv)
+int main(int argc, const char **argv)
 {
-       static const unsigned char null_sha1[20] = { 0, };
        const char **pathspec;
        const char *prefix = setup_git_directory();
        int entries, i;
 
+       git_config(git_default_config);
+       diff_setup(&diff_options);
        while (1 < argc && argv[1][0] == '-') {
-               if (!strcmp(argv[1], "-p") || !strcmp(argv[1], "-u"))
-                       diff_output_format = DIFF_FORMAT_PATCH;
-               else if (!strcmp(argv[1], "-q"))
+               if (!strcmp(argv[1], "--")) {
+                       argv++;
+                       argc--;
+                       break;
+               }
+               if (!strcmp(argv[1], "-q"))
                        silent = 1;
                else if (!strcmp(argv[1], "-r"))
                        ; /* no-op */
                else if (!strcmp(argv[1], "-s"))
                        ; /* no-op */
-               else if (!strcmp(argv[1], "-z"))
-                       diff_line_termination = 0;
-               else if (!strcmp(argv[1], "--name-only"))
-                       diff_output_format = DIFF_FORMAT_NAME;
-               else if (!strcmp(argv[1], "-R"))
-                       diff_setup_opt |= DIFF_SETUP_REVERSE;
-               else if (!strncmp(argv[1], "-S", 2))
-                       pickaxe = argv[1] + 2;
-               else if (!strncmp(argv[1], "-O", 2))
-                       orderfile = argv[1] + 2;
-               else if (!strncmp(argv[1], "--diff-filter=", 14))
-                       diff_filter = argv[1] + 14;
-               else if (!strcmp(argv[1], "--pickaxe-all"))
-                       pickaxe_opts = DIFF_PICKAXE_ALL;
-               else if (!strncmp(argv[1], "-B", 2)) {
-                       if ((diff_break_opt =
-                            diff_scoreopt_parse(argv[1])) == -1)
-                               usage(diff_files_usage);
-               }
-               else if (!strncmp(argv[1], "-M", 2)) {
-                       if ((diff_score_opt =
-                            diff_scoreopt_parse(argv[1])) == -1)
+               else {
+                       int diff_opt_cnt;
+                       diff_opt_cnt = diff_opt_parse(&diff_options,
+                                                     argv+1, argc-1);
+                       if (diff_opt_cnt < 0)
                                usage(diff_files_usage);
-                       detect_rename = DIFF_DETECT_RENAME;
-               }
-               else if (!strncmp(argv[1], "-C", 2)) {
-                       if ((diff_score_opt =
-                            diff_scoreopt_parse(argv[1])) == -1)
+                       else if (diff_opt_cnt) {
+                               argv += diff_opt_cnt;
+                               argc -= diff_opt_cnt;
+                               continue;
+                       }
+                       else
                                usage(diff_files_usage);
-                       detect_rename = DIFF_DETECT_COPY;
                }
-               else if (!strcmp(argv[1], "--find-copies-harder"))
-                       find_copies_harder = 1;
-               else
-                       usage(diff_files_usage);
                argv++; argc--;
        }
 
@@ -99,7 +73,7 @@ int main(int argc, char **argv)
        pathspec = get_pathspec(prefix, argv + 1);
        entries = read_cache();
 
-       if (find_copies_harder && detect_rename != DIFF_DETECT_COPY)
+       if (diff_setup_done(&diff_options) < 0)
                usage(diff_files_usage);
 
        /* At this point, if argc == 1, then we are doing everything.
@@ -110,11 +84,9 @@ int main(int argc, char **argv)
                exit(1);
        }
 
-       diff_setup(diff_setup_opt);
-
        for (i = 0; i < entries; i++) {
                struct stat st;
-               unsigned int oldmode;
+               unsigned int oldmode, newmode;
                struct cache_entry *ce = active_cache[i];
                int changed;
 
@@ -141,18 +113,20 @@ int main(int argc, char **argv)
                        continue;
                }
                changed = ce_match_stat(ce, &st);
-               if (!changed && !find_copies_harder)
+               if (!changed && !diff_options.find_copies_harder)
                        continue;
                oldmode = ntohl(ce->ce_mode);
-               show_modified(oldmode, DIFF_FILE_CANON_MODE(st.st_mode),
+
+               newmode = DIFF_FILE_CANON_MODE(st.st_mode);
+               if (!trust_executable_bit &&
+                   S_ISREG(newmode) && S_ISREG(oldmode) &&
+                   ((newmode ^ oldmode) == 0111))
+                       newmode = oldmode;
+               show_modified(oldmode, newmode,
                              ce->sha1, (changed ? null_sha1 : ce->sha1),
                              ce->name);
        }
-       diffcore_std(pathspec, 
-                    detect_rename, diff_score_opt,
-                    pickaxe, pickaxe_opts,
-                    diff_break_opt,
-                    orderfile, diff_filter);
-       diff_flush(diff_output_format, diff_line_termination);
+       diffcore_std(&diff_options);
+       diff_flush(&diff_options);
        return 0;
 }
diff --git a/diff-helper.c b/diff-helper.c
deleted file mode 100644 (file)
index 734956e..0000000
+++ /dev/null
@@ -1,154 +0,0 @@
-/*
- * Copyright (C) 2005 Junio C Hamano
- */
-#include "cache.h"
-#include "strbuf.h"
-#include "diff.h"
-
-static const char *pickaxe = NULL;
-static int pickaxe_opts = 0;
-static const char *orderfile = NULL;
-static const char *diff_filter = NULL;
-static int line_termination = '\n';
-static int inter_name_termination = '\t';
-
-static void flush_them(int ac, const char **av)
-{
-       diffcore_std_no_resolve(av + 1,
-                               pickaxe, pickaxe_opts,
-                               orderfile, diff_filter);
-       diff_flush(DIFF_FORMAT_PATCH, '\n');
-}
-
-static const char diff_helper_usage[] =
-"git-diff-helper [-z] [-O<orderfile>] [-S<string>] [--pickaxe-all] [<path>...]";
-
-int main(int ac, const char **av) {
-       struct strbuf sb;
-       const char *garbage_flush_format;
-
-       strbuf_init(&sb);
-
-       while (1 < ac && av[1][0] == '-') {
-               if (av[1][1] == 'z')
-                       line_termination = inter_name_termination = 0;
-               else if (av[1][1] == 'S') {
-                       pickaxe = av[1] + 2;
-               }
-               else if (!strcmp(av[1], "--pickaxe-all"))
-                       pickaxe_opts = DIFF_PICKAXE_ALL;
-               else if (!strncmp(av[1], "--diff-filter=", 14))
-                       diff_filter = av[1] + 14;
-               else if (!strncmp(av[1], "-O", 2))
-                       orderfile = av[1] + 2;
-               else
-                       usage(diff_helper_usage);
-               ac--; av++;
-       }
-       garbage_flush_format = (line_termination == 0) ? "%s" : "%s\n";
-
-       /* the remaining parameters are paths patterns */
-
-       diff_setup(0);
-       while (1) {
-               unsigned old_mode, new_mode;
-               unsigned char old_sha1[20], new_sha1[20];
-               char old_path[PATH_MAX];
-               int status, score, two_paths;
-               char new_path[PATH_MAX];
-
-               int ch;
-               char *cp, *ep;
-
-               read_line(&sb, stdin, line_termination);
-               if (sb.eof)
-                       break;
-               switch (sb.buf[0]) {
-               case ':':
-                       /* parse the first part up to the status */
-                       cp = sb.buf + 1;
-                       old_mode = new_mode = 0;
-                       while ((ch = *cp) && ('0' <= ch && ch <= '7')) {
-                               old_mode = (old_mode << 3) | (ch - '0');
-                               cp++;
-                       }
-                       if (*cp++ != ' ')
-                               break;
-                       while ((ch = *cp) && ('0' <= ch && ch <= '7')) {
-                               new_mode = (new_mode << 3) | (ch - '0');
-                               cp++;
-                       }
-                       if (*cp++ != ' ')
-                               break;
-                       if (get_sha1_hex(cp, old_sha1))
-                               break;
-                       cp += 40;
-                       if (*cp++ != ' ')
-                               break;
-                       if (get_sha1_hex(cp, new_sha1))
-                               break;
-                       cp += 40;
-                       if (*cp++ != ' ')
-                               break;
-                       status = *cp++;
-                       if (!strchr("AMCRDU", status))
-                               break;
-                       two_paths = score = 0;
-                       if (status == DIFF_STATUS_RENAMED ||
-                           status == DIFF_STATUS_COPIED)
-                               two_paths = 1;
-
-                       /* pick up score if exists */
-                       if (sscanf(cp, "%d", &score) != 1)
-                               score = 0;
-                       cp = strchr(cp,
-                                   inter_name_termination);
-                       if (!cp)
-                               break;
-                       if (*cp++ != inter_name_termination)
-                               break;
-
-                       /* first pathname */
-                       if (!line_termination) {
-                               read_line(&sb, stdin, line_termination);
-                               if (sb.eof)
-                                       break;
-                               strcpy(old_path, sb.buf);
-                       }
-                       else if (!two_paths)
-                               strcpy(old_path, cp);
-                       else {
-                               ep = strchr(cp, inter_name_termination);
-                               if (!ep)
-                                       break;
-                               strncpy(old_path, cp, ep-cp);
-                               old_path[ep-cp] = 0;
-                               cp = ep + 1;
-                       }
-
-                       /* second pathname */
-                       if (!two_paths)
-                               strcpy(new_path, old_path);
-                       else {
-                               if (!line_termination) {
-                                       read_line(&sb, stdin,
-                                                 line_termination);
-                                       if (sb.eof)
-                                               break;
-                                       strcpy(new_path, sb.buf);
-                               }
-                               else
-                                       strcpy(new_path, cp);
-                       }
-                       diff_helper_input(old_mode, new_mode,
-                                         old_sha1, new_sha1,
-                                         old_path, status, score,
-                                         new_path);
-                       continue;
-               }
-               flush_them(ac, av);
-               printf(garbage_flush_format, sb.buf);
-       }
-       flush_them(ac, av);
-       return 0;
-}
index bc41d54d903e90cc8c6bc56725c40e9a67916fd5..c9a9f4c74d96ea22b2a6c4c7593bccc3d5396457 100644 (file)
@@ -2,26 +2,20 @@
 #include "diff.h"
 
 static int cached_only = 0;
-static int diff_output_format = DIFF_FORMAT_RAW;
-static int diff_line_termination = '\n';
 static int match_nonexisting = 0;
-static int detect_rename = 0;
-static int find_copies_harder = 0;
-static int diff_setup_opt = 0;
-static int diff_score_opt = 0;
-static const char *pickaxe = NULL;
-static int pickaxe_opts = 0;
-static int diff_break_opt = -1;
-static const char *orderfile = NULL;
-static const char *diff_filter = NULL;
+static struct diff_options diff_options;
 
 /* A file entry went away or appeared */
-static void show_file(const char *prefix, struct cache_entry *ce, unsigned char *sha1, unsigned int mode)
+static void show_file(const char *prefix,
+                     struct cache_entry *ce,
+                     unsigned char *sha1, unsigned int mode)
 {
-       diff_addremove(prefix[0], ntohl(mode), sha1, ce->name, NULL);
+       diff_addremove(&diff_options, prefix[0], ntohl(mode),
+                      sha1, ce->name, NULL);
 }
 
-static int get_stat_data(struct cache_entry *ce, unsigned char **sha1p, unsigned int *modep)
+static int get_stat_data(struct cache_entry *ce,
+                        unsigned char ** sha1p, unsigned int *modep)
 {
        unsigned char *sha1 = ce->sha1;
        unsigned int mode = ce->ce_mode;
@@ -41,6 +35,10 @@ static int get_stat_data(struct cache_entry *ce, unsigned char **sha1p, unsigned
                changed = ce_match_stat(ce, &st);
                if (changed) {
                        mode = create_ce_mode(st.st_mode);
+                       if (!trust_executable_bit &&
+                           S_ISREG(mode) && S_ISREG(ce->ce_mode) &&
+                           ((mode ^ ce->ce_mode) == 0111))
+                               mode = ce->ce_mode;
                        sha1 = no_sha1;
                }
        }
@@ -55,7 +53,9 @@ static void show_new_file(struct cache_entry *new)
        unsigned char *sha1;
        unsigned int mode;
 
-       /* New file in the index: it might actually be different in the working copy */
+       /* New file in the index: it might actually be different in
+        * the working copy.
+        */
        if (get_stat_data(new, &sha1, &mode) < 0)
                return;
 
@@ -77,13 +77,13 @@ static int show_modified(struct cache_entry *old,
 
        oldmode = old->ce_mode;
        if (mode == oldmode && !memcmp(sha1, old->sha1, 20) &&
-           !find_copies_harder)
+           !diff_options.find_copies_harder)
                return 0;
 
        mode = ntohl(mode);
        oldmode = ntohl(oldmode);
 
-       diff_change(oldmode, mode,
+       diff_change(&diff_options, oldmode, mode,
                    old->sha1, sha1, old->name, NULL);
        return 0;
 }
@@ -127,7 +127,7 @@ static int diff_cache(struct cache_entry **ac, int entries, const char **pathspe
                                break;
                        /* fallthru */
                case 3:
-                       diff_unmerge(ce->name);
+                       diff_unmerge(&diff_options, ce->name);
                        break;
 
                default:
@@ -168,7 +168,7 @@ static const char diff_cache_usage[] =
 "[<common diff options>] <tree-ish> [<path>...]"
 COMMON_DIFF_OPTIONS_HELP;
 
-int main(int argc, char **argv)
+int main(int argc, const char **argv)
 {
        const char *tree_name = NULL;
        unsigned char sha1[20];
@@ -180,8 +180,11 @@ int main(int argc, char **argv)
        int allow_options = 1;
        int i;
 
+       git_config(git_default_config);
+       diff_setup(&diff_options);
        for (i = 1; i < argc; i++) {
                const char *arg = argv[i];
+               int diff_opt_cnt;
 
                if (!allow_options || *arg != '-') {
                        if (tree_name)
@@ -198,60 +201,15 @@ int main(int argc, char **argv)
                        /* We accept the -r flag just to look like git-diff-tree */
                        continue;
                }
-               /* We accept the -u flag as a synonym for "-p" */
-               if (!strcmp(arg, "-p") || !strcmp(arg, "-u")) {
-                       diff_output_format = DIFF_FORMAT_PATCH;
-                       continue;
-               }
-               if (!strncmp(arg, "-B", 2)) {
-                       if ((diff_break_opt = diff_scoreopt_parse(arg)) == -1)
-                               usage(diff_cache_usage);
-                       continue;
-               }
-               if (!strncmp(arg, "-M", 2)) {
-                       detect_rename = DIFF_DETECT_RENAME;
-                       if ((diff_score_opt = diff_scoreopt_parse(arg)) == -1)
-                               usage(diff_cache_usage);
-                       continue;
-               }
-               if (!strncmp(arg, "-C", 2)) {
-                       detect_rename = DIFF_DETECT_COPY;
-                       if ((diff_score_opt = diff_scoreopt_parse(arg)) == -1)
-                               usage(diff_cache_usage);
-                       continue;
-               }
-               if (!strcmp(arg, "--find-copies-harder")) {
-                       find_copies_harder = 1;
-                       continue;
-               }
-               if (!strcmp(arg, "-z")) {
-                       diff_line_termination = 0;
-                       continue;
-               }
-               if (!strcmp(arg, "--name-only")) {
-                       diff_output_format = DIFF_FORMAT_NAME;
-                       continue;
-               }
-               if (!strcmp(arg, "-R")) {
-                       diff_setup_opt |= DIFF_SETUP_REVERSE;
-                       continue;
-               }
-               if (!strncmp(arg, "-S", 2)) {
-                       pickaxe = arg + 2;
-                       continue;
-               }
-               if (!strncmp(arg, "--diff-filter=", 14)) {
-                       diff_filter = arg + 14;
-                       continue;
-               }
-               if (!strncmp(arg, "-O", 2)) {
-                       orderfile = arg + 2;
-                       continue;
-               }
-               if (!strcmp(arg, "--pickaxe-all")) {
-                       pickaxe_opts = DIFF_PICKAXE_ALL;
+               diff_opt_cnt = diff_opt_parse(&diff_options, argv + i,
+                                             argc - i);
+               if (diff_opt_cnt < 0)
+                       usage(diff_cache_usage);
+               else if (diff_opt_cnt) {
+                       i += diff_opt_cnt - 1;
                        continue;
                }
+
                if (!strcmp(arg, "-m")) {
                        match_nonexisting = 1;
                        continue;
@@ -265,7 +223,7 @@ int main(int argc, char **argv)
 
        pathspec = get_pathspec(prefix, argv + i);
 
-       if (find_copies_harder && detect_rename != DIFF_DETECT_COPY)
+       if (diff_setup_done(&diff_options) < 0)
                usage(diff_cache_usage);
 
        if (!tree_name || get_sha1(tree_name, sha1))
@@ -273,9 +231,6 @@ int main(int argc, char **argv)
 
        read_cache();
 
-       /* The rest is for paths restriction. */
-       diff_setup(diff_setup_opt);
-
        mark_merge_entries();
 
        tree = read_object_with_reference(sha1, "tree", &size, NULL);
@@ -286,11 +241,7 @@ int main(int argc, char **argv)
 
        ret = diff_cache(active_cache, active_nr, pathspec);
 
-       diffcore_std(pathspec,
-                    detect_rename, diff_score_opt,
-                    pickaxe, pickaxe_opts,
-                    diff_break_opt,
-                    orderfile, diff_filter);
-       diff_flush(diff_output_format, diff_line_termination);
+       diffcore_std(&diff_options);
+       diff_flush(&diff_options);
        return ret;
 }
index 2e9c0bce6e8a5019b85f2cb0683e0ca120699bfd..85170b21d632dbb490b28ad426069ce4e1011bc1 100644 (file)
@@ -5,17 +5,7 @@
 #include "cache.h"
 #include "diff.h"
 
-static int diff_output_format = DIFF_FORMAT_RAW;
-static int diff_line_termination = '\n';
-static int detect_rename = 0;
-static int find_copies_harder = 0;
-static int diff_setup_opt = 0;
-static int diff_score_opt = 0;
-static const char *pickaxe = NULL;
-static int pickaxe_opts = 0;
-static int diff_break_opt = -1;
-static const char *orderfile = NULL;
-static const char *diff_filter = NULL;
+static struct diff_options diff_options;
 
 static const char diff_stages_usage[] =
 "git-diff-stages [<common diff options>] <stage1> <stage2> [<path>...]"
@@ -47,15 +37,16 @@ static void diff_stages(int stage1, int stage2)
                if (!one && !two)
                        continue;
                if (!one)
-                       diff_addremove('+', ntohl(two->ce_mode),
+                       diff_addremove(&diff_options, '+', ntohl(two->ce_mode),
                                       two->sha1, name, NULL);
                else if (!two)
-                       diff_addremove('-', ntohl(one->ce_mode),
+                       diff_addremove(&diff_options, '-', ntohl(one->ce_mode),
                                       one->sha1, name, NULL);
                else if (memcmp(one->sha1, two->sha1, 20) ||
                         (one->ce_mode != two->ce_mode) ||
-                        find_copies_harder)
-                       diff_change(ntohl(one->ce_mode), ntohl(two->ce_mode),
+                        diff_options.find_copies_harder)
+                       diff_change(&diff_options,
+                                   ntohl(one->ce_mode), ntohl(two->ce_mode),
                                    one->sha1, two->sha1, name, NULL);
        }
 }
@@ -65,44 +56,25 @@ int main(int ac, const char **av)
        int stage1, stage2;
 
        read_cache();
+       diff_setup(&diff_options);
        while (1 < ac && av[1][0] == '-') {
                const char *arg = av[1];
                if (!strcmp(arg, "-r"))
                        ; /* as usual */
-               else if (!strcmp(arg, "-p") || !strcmp(arg, "-u"))
-                       diff_output_format = DIFF_FORMAT_PATCH;
-               else if (!strncmp(arg, "-B", 2)) {
-                       if ((diff_break_opt = diff_scoreopt_parse(arg)) == -1)
+               else {
+                       int diff_opt_cnt;
+                       diff_opt_cnt = diff_opt_parse(&diff_options,
+                                                     av+1, ac-1);
+                       if (diff_opt_cnt < 0)
                                usage(diff_stages_usage);
-               }
-               else if (!strncmp(arg, "-M", 2)) {
-                       detect_rename = DIFF_DETECT_RENAME;
-                       if ((diff_score_opt = diff_scoreopt_parse(arg)) == -1)
-                               usage(diff_stages_usage);
-               }
-               else if (!strncmp(arg, "-C", 2)) {
-                       detect_rename = DIFF_DETECT_COPY;
-                       if ((diff_score_opt = diff_scoreopt_parse(arg)) == -1)
+                       else if (diff_opt_cnt) {
+                               av += diff_opt_cnt;
+                               ac -= diff_opt_cnt;
+                               continue;
+                       }
+                       else
                                usage(diff_stages_usage);
                }
-               else if (!strcmp(arg, "--find-copies-harder"))
-                       find_copies_harder = 1;
-               else if (!strcmp(arg, "-z"))
-                       diff_line_termination = 0;
-               else if (!strcmp(arg, "--name-only"))
-                       diff_output_format = DIFF_FORMAT_NAME;
-               else if (!strcmp(arg, "-R"))
-                       diff_setup_opt |= DIFF_SETUP_REVERSE;
-               else if (!strncmp(arg, "-S", 2))
-                       pickaxe = arg + 2;
-               else if (!strncmp(arg, "-O", 2))
-                       orderfile = arg + 2;
-               else if (!strncmp(arg, "--diff-filter=", 14))
-                       diff_filter = arg + 14;
-               else if (!strcmp(arg, "--pickaxe-all"))
-                       pickaxe_opts = DIFF_PICKAXE_ALL;
-               else
-                       usage(diff_stages_usage);
                ac--; av++;
        }
 
@@ -110,21 +82,17 @@ int main(int ac, const char **av)
            sscanf(av[1], "%d", &stage1) != 1 ||
            ! (0 <= stage1 && stage1 <= 3) ||
            sscanf(av[2], "%d", &stage2) != 1 ||
-           ! (0 <= stage2 && stage2 <= 3) ||
-           (find_copies_harder && detect_rename != DIFF_DETECT_COPY))
+           ! (0 <= stage2 && stage2 <= 3))
                usage(diff_stages_usage);
 
        av += 3; /* The rest from av[0] are for paths restriction. */
-       diff_setup(diff_setup_opt);
+       diff_options.paths = av;
 
-       diff_stages(stage1, stage2);
+       if (diff_setup_done(&diff_options) < 0)
+               usage(diff_stages_usage);
 
-       diffcore_std(av,
-                    detect_rename, diff_score_opt,
-                    pickaxe, pickaxe_opts,
-                    diff_break_opt,
-                    orderfile,
-                    diff_filter);
-       diff_flush(diff_output_format, diff_line_termination);
+       diff_stages(stage1, stage2);
+       diffcore_std(&diff_options);
+       diff_flush(&diff_options);
        return 0;
 }
index e8f5d1b126179c705f6463c25216d90e3e376848..ed323d877cad1e78e08f84c1e9ee1ebb09f05d91 100644 (file)
@@ -1,4 +1,3 @@
-#include <ctype.h>
 #include "cache.h"
 #include "diff.h"
 #include "commit.h"
 static int show_root_diff = 0;
 static int verbose_header = 0;
 static int ignore_merges = 1;
-static int recursive = 0;
-static int show_tree_entry_in_recursive = 0;
 static int read_stdin = 0;
-static int diff_output_format = DIFF_FORMAT_RAW;
-static int diff_line_termination = '\n';
-static int detect_rename = 0;
-static int find_copies_harder = 0;
-static int diff_setup_opt = 0;
-static int diff_score_opt = 0;
-static const char *pickaxe = NULL;
-static int pickaxe_opts = 0;
-static int diff_break_opt = -1;
-static const char *orderfile = NULL;
-static const char *diff_filter = NULL;
+
 static const char *header = NULL;
 static const char *header_prefix = "";
 static enum cmit_fmt commit_format = CMIT_FMT_RAW;
 
-// What paths are we interested in?
-static int nr_paths = 0;
-static const char **paths = NULL;
-static int *pathlens = NULL;
-
-static int diff_tree_sha1(const unsigned char *old, const unsigned char *new, const char *base);
-
-static void update_tree_entry(void **bufp, unsigned long *sizep)
-{
-       void *buf = *bufp;
-       unsigned long size = *sizep;
-       int len = strlen(buf) + 1 + 20;
-
-       if (size < len)
-               die("corrupt tree file");
-       *bufp = buf + len;
-       *sizep = size - len;
-}
-
-static const unsigned char *extract(void *tree, unsigned long size, const char **pathp, unsigned int *modep)
-{
-       int len = strlen(tree)+1;
-       const unsigned char *sha1 = tree + len;
-       const char *path = strchr(tree, ' ');
-       unsigned int mode;
-
-       if (!path || size < len + 20 || sscanf(tree, "%o", &mode) != 1)
-               die("corrupt tree file");
-       *pathp = path+1;
-       *modep = DIFF_FILE_CANON_MODE(mode);
-       return sha1;
-}
-
-static char *malloc_base(const char *base, const char *path, int pathlen)
-{
-       int baselen = strlen(base);
-       char *newbase = xmalloc(baselen + pathlen + 2);
-       memcpy(newbase, base, baselen);
-       memcpy(newbase + baselen, path, pathlen);
-       memcpy(newbase + baselen + pathlen, "/", 2);
-       return newbase;
-}
-
-static void show_file(const char *prefix, void *tree, unsigned long size, const char *base);
-static void show_tree(const char *prefix, void *tree, unsigned long size, const char *base);
-
-/* A file entry went away or appeared */
-static void show_file(const char *prefix, void *tree, unsigned long size, const char *base)
-{
-       unsigned mode;
-       const char *path;
-       const unsigned char *sha1 = extract(tree, size, &path, &mode);
-
-       if (recursive && S_ISDIR(mode)) {
-               char type[20];
-               unsigned long size;
-               char *newbase = malloc_base(base, path, strlen(path));
-               void *tree;
-
-               tree = read_sha1_file(sha1, type, &size);
-               if (!tree || strcmp(type, "tree"))
-                       die("corrupt tree sha %s", sha1_to_hex(sha1));
-
-               show_tree(prefix, tree, size, newbase);
-
-               free(tree);
-               free(newbase);
-               return;
-       }
-
-       diff_addremove(prefix[0], mode, sha1, base, path);
-}
-
-static int compare_tree_entry(void *tree1, unsigned long size1, void *tree2, unsigned long size2, const char *base)
-{
-       unsigned mode1, mode2;
-       const char *path1, *path2;
-       const unsigned char *sha1, *sha2;
-       int cmp, pathlen1, pathlen2;
-
-       sha1 = extract(tree1, size1, &path1, &mode1);
-       sha2 = extract(tree2, size2, &path2, &mode2);
-
-       pathlen1 = strlen(path1);
-       pathlen2 = strlen(path2);
-       cmp = base_name_compare(path1, pathlen1, mode1, path2, pathlen2, mode2);
-       if (cmp < 0) {
-               show_file("-", tree1, size1, base);
-               return -1;
-       }
-       if (cmp > 0) {
-               show_file("+", tree2, size2, base);
-               return 1;
-       }
-       if (!find_copies_harder && !memcmp(sha1, sha2, 20) && mode1 == mode2)
-               return 0;
-
-       /*
-        * If the filemode has changed to/from a directory from/to a regular
-        * file, we need to consider it a remove and an add.
-        */
-       if (S_ISDIR(mode1) != S_ISDIR(mode2)) {
-               show_file("-", tree1, size1, base);
-               show_file("+", tree2, size2, base);
-               return 0;
-       }
-
-       if (recursive && S_ISDIR(mode1)) {
-               int retval;
-               char *newbase = malloc_base(base, path1, pathlen1);
-               if (show_tree_entry_in_recursive)
-                       diff_change(mode1, mode2, sha1, sha2, base, path1);
-               retval = diff_tree_sha1(sha1, sha2, newbase);
-               free(newbase);
-               return retval;
-       }
-
-       diff_change(mode1, mode2, sha1, sha2, base, path1);
-       return 0;
-}
-
-static int interesting(void *tree, unsigned long size, const char *base)
-{
-       const char *path;
-       unsigned mode;
-       int i;
-       int baselen, pathlen;
-
-       if (!nr_paths)
-               return 1;
-
-       (void)extract(tree, size, &path, &mode);
-
-       pathlen = strlen(path);
-       baselen = strlen(base);
-
-       for (i=0; i < nr_paths; i++) {
-               const char *match = paths[i];
-               int matchlen = pathlens[i];
-
-               if (baselen >= matchlen) {
-                       /* If it doesn't match, move along... */
-                       if (strncmp(base, match, matchlen))
-                               continue;
-
-                       /* The base is a subdirectory of a path which was specified. */
-                       return 1;
-               }
-
-               /* Does the base match? */
-               if (strncmp(base, match, baselen))
-                       continue;
-
-               match += baselen;
-               matchlen -= baselen;
-
-               if (pathlen > matchlen)
-                       continue;
-
-               if (matchlen > pathlen) {
-                       if (match[pathlen] != '/')
-                               continue;
-                       if (!S_ISDIR(mode))
-                               continue;
-               }
-
-               if (strncmp(path, match, pathlen))
-                       continue;
-
-               return 1;
-       }
-       return 0; /* No matches */
-}
-
-/* A whole sub-tree went away or appeared */
-static void show_tree(const char *prefix, void *tree, unsigned long size, const char *base)
-{
-       while (size) {
-               if (interesting(tree, size, base))
-                       show_file(prefix, tree, size, base);
-               update_tree_entry(&tree, &size);
-       }
-}
-
-static int diff_tree(void *tree1, unsigned long size1, void *tree2, unsigned long size2, const char *base)
-{
-       while (size1 | size2) {
-               if (nr_paths && size1 && !interesting(tree1, size1, base)) {
-                       update_tree_entry(&tree1, &size1);
-                       continue;
-               }
-               if (nr_paths && size2 && !interesting(tree2, size2, base)) {
-                       update_tree_entry(&tree2, &size2);
-                       continue;
-               }
-               if (!size1) {
-                       show_file("+", tree2, size2, base);
-                       update_tree_entry(&tree2, &size2);
-                       continue;
-               }
-               if (!size2) {
-                       show_file("-", tree1, size1, base);
-                       update_tree_entry(&tree1, &size1);
-                       continue;
-               }
-               switch (compare_tree_entry(tree1, size1, tree2, size2, base)) {
-               case -1:
-                       update_tree_entry(&tree1, &size1);
-                       continue;
-               case 0:
-                       update_tree_entry(&tree1, &size1);
-                       /* Fallthrough */
-               case 1:
-                       update_tree_entry(&tree2, &size2);
-                       continue;
-               }
-               die("git-diff-tree: internal error");
-       }
-       return 0;
-}
-
-static int diff_tree_sha1(const unsigned char *old, const unsigned char *new, const char *base)
-{
-       void *tree1, *tree2;
-       unsigned long size1, size2;
-       int retval;
+static struct diff_options diff_options;
 
-       tree1 = read_object_with_reference(old, "tree", &size1, NULL);
-       if (!tree1)
-               die("unable to read source tree (%s)", sha1_to_hex(old));
-       tree2 = read_object_with_reference(new, "tree", &size2, NULL);
-       if (!tree2)
-               die("unable to read destination tree (%s)", sha1_to_hex(new));
-       retval = diff_tree(tree1, size1, tree2, size2, base);
-       free(tree1);
-       free(tree2);
-       return retval;
-}
-
-static void call_diff_setup(void)
+static void call_diff_setup_done(void)
 {
-       diff_setup(diff_setup_opt);
+       diff_setup_done(&diff_options);
 }
 
 static int call_diff_flush(void)
 {
-       diffcore_std(NULL,
-                    detect_rename, diff_score_opt,
-                    pickaxe, pickaxe_opts,
-                    diff_break_opt,
-                    orderfile,
-                    diff_filter);
+       diffcore_std(&diff_options);
        if (diff_queue_is_empty()) {
-               diff_flush(DIFF_FORMAT_NO_OUTPUT, diff_line_termination);
+               int saved_fmt = diff_options.output_format;
+               diff_options.output_format = DIFF_FORMAT_NO_OUTPUT;
+               diff_flush(&diff_options);
+               diff_options.output_format = saved_fmt;
                return 0;
        }
        if (header) {
-               printf("%s%c", header, diff_line_termination);
+               printf("%s%c", header, diff_options.line_termination);
                header = NULL;
        }
-       diff_flush(diff_output_format, diff_line_termination);
+       diff_flush(&diff_options);
        return 1;
 }
 
@@ -293,8 +41,8 @@ static int diff_tree_sha1_top(const unsigned char *old,
 {
        int ret;
 
-       call_diff_setup();
-       ret = diff_tree_sha1(old, new, base);
+       call_diff_setup_done();
+       ret = diff_tree_sha1(old, new, base, &diff_options);
        call_diff_flush();
        return ret;
 }
@@ -303,13 +51,17 @@ static int diff_root_tree(const unsigned char *new, const char *base)
 {
        int retval;
        void *tree;
-       unsigned long size;
+       struct tree_desc empty, real;
 
-       call_diff_setup();
-       tree = read_object_with_reference(new, "tree", &size, NULL);
+       call_diff_setup_done();
+       tree = read_object_with_reference(new, "tree", &real.size, NULL);
        if (!tree)
                die("unable to read root tree (%s)", sha1_to_hex(new));
-       retval = diff_tree("", 0, tree, size, base);
+       real.buf = tree;
+
+       empty.buf = "";
+       empty.size = 0;
+       retval = diff_tree(&empty, &real, base, &diff_options);
        free(tree);
        call_diff_flush();
        return retval;
@@ -396,28 +148,26 @@ static int diff_tree_stdin(char *line)
        return diff_tree_commit(commit, line);
 }
 
-static int count_paths(const char **paths)
-{
-       int i = 0;
-       while (*paths++)
-               i++;
-       return i;
-}
-
 static const char diff_tree_usage[] =
-"git-diff-tree [--stdin] [-m] [-s] [-v] [--pretty] [-t] "
-"[<common diff options>] <tree-ish> <tree-ish>"
+"git-diff-tree [--stdin] [-m] [-s] [-v] [--pretty] [-t] [-r] [--root] "
+"[<common diff options>] <tree-ish> [<tree-ish>] [<path>...]\n"
+"  -r            diff recursively\n"
+"  --root        include the initial commit as diff against /dev/null\n"
 COMMON_DIFF_OPTIONS_HELP;
 
-int main(int argc, char **argv)
+int main(int argc, const char **argv)
 {
        int nr_sha1;
        char line[1000];
        unsigned char sha1[2][20];
        const char *prefix = setup_git_directory();
 
+       git_config(git_default_config);
        nr_sha1 = 0;
+       diff_setup(&diff_options);
+
        for (;;) {
+               int diff_opt_cnt;
                const char *arg;
 
                argv++;
@@ -434,81 +184,34 @@ int main(int argc, char **argv)
                        break;
                }
 
+               diff_opt_cnt = diff_opt_parse(&diff_options, argv, argc);
+               if (diff_opt_cnt < 0)
+                       usage(diff_tree_usage);
+               else if (diff_opt_cnt) {
+                       argv += diff_opt_cnt - 1;
+                       argc -= diff_opt_cnt - 1;
+                       continue;
+               }
+
+
                if (!strcmp(arg, "--")) {
                        argv++;
                        argc--;
                        break;
                }
                if (!strcmp(arg, "-r")) {
-                       recursive = 1;
+                       diff_options.recursive = 1;
                        continue;
                }
                if (!strcmp(arg, "-t")) {
-                       recursive = show_tree_entry_in_recursive = 1;
-                       continue;
-               }
-               if (!strcmp(arg, "-R")) {
-                       diff_setup_opt |= DIFF_SETUP_REVERSE;
-                       continue;
-               }
-               if (!strcmp(arg, "-p") || !strcmp(arg, "-u")) {
-                       diff_output_format = DIFF_FORMAT_PATCH;
-                       recursive = 1;
-                       continue;
-               }
-               if (!strncmp(arg, "-S", 2)) {
-                       pickaxe = arg + 2;
-                       continue;
-               }
-               if (!strncmp(arg, "-O", 2)) {
-                       orderfile = arg + 2;
-                       continue;
-               }
-               if (!strncmp(arg, "--diff-filter=", 14)) {
-                       diff_filter = arg + 14;
-                       continue;
-               }
-               if (!strcmp(arg, "--pickaxe-all")) {
-                       pickaxe_opts = DIFF_PICKAXE_ALL;
-                       continue;
-               }
-               if (!strncmp(arg, "-M", 2)) {
-                       detect_rename = DIFF_DETECT_RENAME;
-                       if ((diff_score_opt = diff_scoreopt_parse(arg)) == -1)
-                               usage(diff_tree_usage);
-                       continue;
-               }
-               if (!strncmp(arg, "-C", 2)) {
-                       detect_rename = DIFF_DETECT_COPY;
-                       if ((diff_score_opt = diff_scoreopt_parse(arg)) == -1)
-                               usage(diff_tree_usage);
-                       continue;
-               }
-               if (!strncmp(arg, "-B", 2)) {
-                       if ((diff_break_opt = diff_scoreopt_parse(arg)) == -1)
-                               usage(diff_tree_usage);
-                       continue;
-               }
-               if (!strcmp(arg, "--find-copies-harder")) {
-                       find_copies_harder = 1;
-                       continue;
-               }
-               if (!strcmp(arg, "--name-only")) {
-                       diff_output_format = DIFF_FORMAT_NAME;
-                       continue;
-               }
-               if (!strcmp(arg, "-z")) {
-                       diff_line_termination = 0;
+                       diff_options.recursive = 1;
+                       diff_options.tree_in_recursive = 1;
                        continue;
                }
                if (!strcmp(arg, "-m")) {
                        ignore_merges = 0;
                        continue;
                }
-               if (!strcmp(arg, "-s")) {
-                       diff_output_format = DIFF_FORMAT_NO_OUTPUT;
-                       continue;
-               }
                if (!strcmp(arg, "-v")) {
                        verbose_header = 1;
                        header_prefix = "diff-tree ";
@@ -530,18 +233,10 @@ int main(int argc, char **argv)
                }
                usage(diff_tree_usage);
        }
-       if (find_copies_harder && detect_rename != DIFF_DETECT_COPY)
-               usage(diff_tree_usage);
-
-       paths = get_pathspec(prefix, argv);
-       if (paths) {
-               int i;
+       if (diff_options.output_format == DIFF_FORMAT_PATCH)
+               diff_options.recursive = 1;
 
-               nr_paths = count_paths(paths);
-               pathlens = xmalloc(nr_paths * sizeof(int));
-               for (i=0; i<nr_paths; i++)
-                       pathlens[i] = strlen(paths[i]);
-       }
+       diff_tree_setup_paths(get_pathspec(prefix, argv));
 
        switch (nr_sha1) {
        case 0:
@@ -559,9 +254,9 @@ int main(int argc, char **argv)
        if (!read_stdin)
                return 0;
 
-       if (detect_rename)
-               diff_setup_opt |= (DIFF_SETUP_USE_SIZE_CACHE |
-                                  DIFF_SETUP_USE_CACHE);
+       if (diff_options.detect_rename)
+               diff_options.setup |= (DIFF_SETUP_USE_SIZE_CACHE |
+                                      DIFF_SETUP_USE_CACHE);
        while (fgets(line, sizeof(line), stdin))
                diff_tree_stdin(line);
 
diff --git a/diff.c b/diff.c
index d8d20c2fcb141124bf4c798eb054e5c1be451f68..ec94a96a5d02fe4204cc69f03de2966264378f75 100644 (file)
--- a/diff.c
+++ b/diff.c
 #include "diffcore.h"
 
 static const char *diff_opts = "-pu";
-static unsigned char null_sha1[20] = { 0, };
 
-static int reverse_diff;
 static int use_size_cache;
 
+static char *quote_one(const char *str)
+{
+       int needlen;
+       char *xp;
+
+       if (!str)
+               return NULL;
+       needlen = quote_c_style(str, NULL, NULL, 0);
+       if (!needlen)
+               return strdup(str);
+       xp = xmalloc(needlen + 1);
+       quote_c_style(str, xp, NULL, 0);
+       return xp;
+}
+
+static char *quote_two(const char *one, const char *two)
+{
+       int need_one = quote_c_style(one, NULL, NULL, 1);
+       int need_two = quote_c_style(two, NULL, NULL, 1);
+       char *xp;
+
+       if (need_one + need_two) {
+               if (!need_one) need_one = strlen(one);
+               if (!need_two) need_one = strlen(two);
+
+               xp = xmalloc(need_one + need_two + 3);
+               xp[0] = '"';
+               quote_c_style(one, xp + 1, NULL, 1);
+               quote_c_style(two, xp + need_one + 1, NULL, 1);
+               strcpy(xp + need_one + need_two + 1, "\"");
+               return xp;
+       }
+       need_one = strlen(one);
+       need_two = strlen(two);
+       xp = xmalloc(need_one + need_two + 1);
+       strcpy(xp, one);
+       strcpy(xp + need_one, two);
+       return xp;
+}
+
 static const char *external_diff(void)
 {
        static const char *external_diff_cmd = NULL;
@@ -135,55 +173,52 @@ static void builtin_diff(const char *name_a,
                         int complete_rewrite)
 {
        int i, next_at, cmd_size;
-       const char *const diff_cmd = "diff -L%s%s -L%s%s";
-       const char *const diff_arg  = "%s %s||:"; /* "||:" is to return 0 */
+       const char *const diff_cmd = "diff -L%s -L%s";
+       const char *const diff_arg  = "-- %s %s||:"; /* "||:" is to return 0 */
        const char *input_name_sq[2];
-       const char *path0[2];
-       const char *path1[2];
-       const char *name_sq[2];
+       const char *label_path[2];
        char *cmd;
 
-       name_sq[0] = sq_quote(name_a);
-       name_sq[1] = sq_quote(name_b);
-
-       /* diff_cmd and diff_arg have 6 %s in total which makes
-        * the sum of these strings 12 bytes larger than required.
+       /* diff_cmd and diff_arg have 4 %s in total which makes
+        * the sum of these strings 8 bytes larger than required.
         * we use 2 spaces around diff-opts, and we need to count
-        * terminating NUL, so we subtract 9 here.
+        * terminating NUL; we used to subtract 5 here, but we do not
+        * care about small leaks in this subprocess that is about
+        * to exec "diff" anymore.
         */
-       cmd_size = (strlen(diff_cmd) + strlen(diff_opts) +
-                       strlen(diff_arg) - 9);
+       cmd_size = (strlen(diff_cmd) + strlen(diff_opts) + strlen(diff_arg)
+                   + 128);
+
        for (i = 0; i < 2; i++) {
                input_name_sq[i] = sq_quote(temp[i].name);
-               if (!strcmp(temp[i].name, "/dev/null")) {
-                       path0[i] = "/dev/null";
-                       path1[i] = "";
-               } else {
-                       path0[i] = i ? "b/" : "a/";
-                       path1[i] = name_sq[i];
-               }
-               cmd_size += (strlen(path0[i]) + strlen(path1[i]) +
-                            strlen(input_name_sq[i]));
+               if (!strcmp(temp[i].name, "/dev/null"))
+                       label_path[i] = "/dev/null";
+               else if (!i)
+                       label_path[i] = sq_quote(quote_two("a/", name_a));
+               else
+                       label_path[i] = sq_quote(quote_two("b/", name_b));
+               cmd_size += (strlen(label_path[i]) + strlen(input_name_sq[i]));
        }
 
        cmd = xmalloc(cmd_size);
 
        next_at = 0;
        next_at += snprintf(cmd+next_at, cmd_size-next_at,
-                           diff_cmd,
-                           path0[0], path1[0], path0[1], path1[1]);
+                           diff_cmd, label_path[0], label_path[1]);
        next_at += snprintf(cmd+next_at, cmd_size-next_at,
                            " %s ", diff_opts);
        next_at += snprintf(cmd+next_at, cmd_size-next_at,
                            diff_arg, input_name_sq[0], input_name_sq[1]);
 
-       printf("diff --git a/%s b/%s\n", name_a, name_b);
-       if (!path1[0][0]) {
+       printf("diff --git %s %s\n",
+              quote_two("a/", name_a), quote_two("b/", name_b));
+       if (label_path[0][0] == '/') {
+               /* dev/null */
                printf("new file mode %s\n", temp[1].mode);
                if (xfrm_msg && xfrm_msg[0])
                        puts(xfrm_msg);
        }
-       else if (!path1[1][0]) {
+       else if (label_path[1][0] == '/') {
                printf("deleted file mode %s\n", temp[0].mode);
                if (xfrm_msg && xfrm_msg[0])
                        puts(xfrm_msg);
@@ -415,7 +450,7 @@ void diff_free_filespec_data(struct diff_filespec *s)
 static void prep_temp_blob(struct diff_tempfile *temp,
                           void *blob,
                           unsigned long size,
-                          unsigned char *sha1,
+                          const unsigned char *sha1,
                           int mode)
 {
        int fd;
@@ -598,15 +633,32 @@ static void run_external_diff(const char *pgm,
        remove_tempfile();
 }
 
+static void diff_fill_sha1_info(struct diff_filespec *one)
+{
+       if (DIFF_FILE_VALID(one)) {
+               if (!one->sha1_valid) {
+                       struct stat st;
+                       if (stat(one->path, &st) < 0)
+                               die("stat %s", one->path);
+                       if (index_path(one->sha1, one->path, &st, 0))
+                               die("cannot hash %s\n", one->path);
+               }
+       }
+       else
+               memset(one->sha1, 0, 20);
+}
+
 static void run_diff(struct diff_filepair *p)
 {
        const char *pgm = external_diff();
-       char msg_[PATH_MAX*2+200], *xfrm_msg;
+       char msg[PATH_MAX*2+300], *xfrm_msg;
        struct diff_filespec *one;
        struct diff_filespec *two;
        const char *name;
        const char *other;
+       char *name_munged, *other_munged;
        int complete_rewrite = 0;
+       int len;
 
        if (DIFF_PAIR_UNMERGED(p)) {
                /* unmerged */
@@ -617,40 +669,63 @@ static void run_diff(struct diff_filepair *p)
 
        name = p->one->path;
        other = (strcmp(name, p->two->path) ? p->two->path : NULL);
+       name_munged = quote_one(name);
+       other_munged = quote_one(other);
        one = p->one; two = p->two;
+
+       diff_fill_sha1_info(one);
+       diff_fill_sha1_info(two);
+
+       len = 0;
        switch (p->status) {
        case DIFF_STATUS_COPIED:
-               sprintf(msg_,
-                       "similarity index %d%%\n"
-                       "copy from %s\n"
-                       "copy to %s",
-                       (int)(0.5 + p->score * 100.0/MAX_SCORE),
-                       name, other);
-               xfrm_msg = msg_;
+               len += snprintf(msg + len, sizeof(msg) - len,
+                               "similarity index %d%%\n"
+                               "copy from %s\n"
+                               "copy to %s\n",
+                               (int)(0.5 + p->score * 100.0/MAX_SCORE),
+                               name_munged, other_munged);
                break;
        case DIFF_STATUS_RENAMED:
-               sprintf(msg_,
-                       "similarity index %d%%\n"
-                       "rename from %s\n"
-                       "rename to %s",
-                       (int)(0.5 + p->score * 100.0/MAX_SCORE),
-                       name, other);
-               xfrm_msg = msg_;
+               len += snprintf(msg + len, sizeof(msg) - len,
+                               "similarity index %d%%\n"
+                               "rename from %s\n"
+                               "rename to %s\n",
+                               (int)(0.5 + p->score * 100.0/MAX_SCORE),
+                               name_munged, other_munged);
                break;
        case DIFF_STATUS_MODIFIED:
                if (p->score) {
-                       sprintf(msg_,
-                               "dissimilarity index %d%%",
-                               (int)(0.5 + p->score * 100.0/MAX_SCORE));
-                       xfrm_msg = msg_;
+                       len += snprintf(msg + len, sizeof(msg) - len,
+                                       "dissimilarity index %d%%\n",
+                                       (int)(0.5 + p->score *
+                                             100.0/MAX_SCORE));
                        complete_rewrite = 1;
                        break;
                }
                /* fallthru */
        default:
-               xfrm_msg = NULL;
+               /* nothing */
+               ;
        }
 
+       if (memcmp(one->sha1, two->sha1, 20)) {
+               char one_sha1[41];
+               memcpy(one_sha1, sha1_to_hex(one->sha1), 41);
+
+               len += snprintf(msg + len, sizeof(msg) - len,
+                               "index %.7s..%.7s", one_sha1,
+                               sha1_to_hex(two->sha1));
+               if (one->mode == two->mode)
+                       len += snprintf(msg + len, sizeof(msg) - len,
+                                       " %06o", one->mode);
+               len += snprintf(msg + len, sizeof(msg) - len, "\n");
+       }
+
+       if (len)
+               msg[--len] = 0;
+       xfrm_msg = len ? msg : NULL;
+
        if (!pgm &&
            DIFF_FILE_VALID(one) && DIFF_FILE_VALID(two) &&
            (S_IFMT & one->mode) != (S_IFMT & two->mode)) {
@@ -667,13 +742,29 @@ static void run_diff(struct diff_filepair *p)
        else
                run_external_diff(pgm, name, other, one, two, xfrm_msg,
                                  complete_rewrite);
+
+       free(name_munged);
+       free(other_munged);
+}
+
+void diff_setup(struct diff_options *options)
+{
+       memset(options, 0, sizeof(*options));
+       options->output_format = DIFF_FORMAT_RAW;
+       options->line_termination = '\n';
+       options->break_opt = -1;
+       options->rename_limit = -1;
+
+       options->change = diff_change;
+       options->add_remove = diff_addremove;
 }
 
-void diff_setup(int flags)
+int diff_setup_done(struct diff_options *options)
 {
-       if (flags & DIFF_SETUP_REVERSE)
-               reverse_diff = 1;
-       if (flags & DIFF_SETUP_USE_CACHE) {
+       if ((options->find_copies_harder || 0 <= options->rename_limit) &&
+           options->detect_rename != DIFF_DETECT_COPY)
+               return -1;
+       if (options->setup & DIFF_SETUP_USE_CACHE) {
                if (!active_cache)
                        /* read-cache does not die even when it fails
                         * so it is safe for us to do this here.  Also
@@ -683,9 +774,59 @@ void diff_setup(int flags)
                         */
                        read_cache();
        }
-       if (flags & DIFF_SETUP_USE_SIZE_CACHE)
+       if (options->setup & DIFF_SETUP_USE_SIZE_CACHE)
                use_size_cache = 1;
-       
+
+       return 0;
+}
+
+int diff_opt_parse(struct diff_options *options, const char **av, int ac)
+{
+       const char *arg = av[0];
+       if (!strcmp(arg, "-p") || !strcmp(arg, "-u"))
+               options->output_format = DIFF_FORMAT_PATCH;
+       else if (!strcmp(arg, "-z"))
+               options->line_termination = 0;
+       else if (!strncmp(arg, "-l", 2))
+               options->rename_limit = strtoul(arg+2, NULL, 10);
+       else if (!strcmp(arg, "--name-only"))
+               options->output_format = DIFF_FORMAT_NAME;
+       else if (!strcmp(arg, "--name-status"))
+               options->output_format = DIFF_FORMAT_NAME_STATUS;
+       else if (!strcmp(arg, "-R"))
+               options->reverse_diff = 1;
+       else if (!strncmp(arg, "-S", 2))
+               options->pickaxe = arg + 2;
+       else if (!strcmp(arg, "-s"))
+               options->output_format = DIFF_FORMAT_NO_OUTPUT;
+       else if (!strncmp(arg, "-O", 2))
+               options->orderfile = arg + 2;
+       else if (!strncmp(arg, "--diff-filter=", 14))
+               options->filter = arg + 14;
+       else if (!strcmp(arg, "--pickaxe-all"))
+               options->pickaxe_opts = DIFF_PICKAXE_ALL;
+       else if (!strncmp(arg, "-B", 2)) {
+               if ((options->break_opt =
+                    diff_scoreopt_parse(arg)) == -1)
+                       return -1;
+       }
+       else if (!strncmp(arg, "-M", 2)) {
+               if ((options->rename_score =
+                    diff_scoreopt_parse(arg)) == -1)
+                       return -1;
+               options->detect_rename = DIFF_DETECT_RENAME;
+       }
+       else if (!strncmp(arg, "-C", 2)) {
+               if ((options->rename_score =
+                    diff_scoreopt_parse(arg)) == -1)
+                       return -1;
+               options->detect_rename = DIFF_DETECT_COPY;
+       }
+       else if (!strcmp(arg, "--find-copies-harder"))
+               options->find_copies_harder = 1;
+       else
+               return 0;
+       return 1;
 }
 
 static int parse_num(const char **cp_p)
@@ -778,20 +919,18 @@ void diff_free_filepair(struct diff_filepair *p)
 
 static void diff_flush_raw(struct diff_filepair *p,
                           int line_termination,
-                          int inter_name_termination)
+                          int inter_name_termination,
+                          int output_format)
 {
        int two_paths;
        char status[10];
+       const char *path_one, *path_two;
 
+       path_one = p->one->path;
+       path_two = p->two->path;
        if (line_termination) {
-               const char *const err =
-                       "path %s cannot be expressed without -z";
-               if (strchr(p->one->path, line_termination) ||
-                   strchr(p->one->path, inter_name_termination))
-                       die(err, p->one->path);
-               if (strchr(p->two->path, line_termination) ||
-                   strchr(p->two->path, inter_name_termination))
-                       die(err, p->two->path);
+               path_one = quote_one(path_one);
+               path_two = quote_one(path_two);
        }
 
        if (p->score)
@@ -814,22 +953,34 @@ static void diff_flush_raw(struct diff_filepair *p,
                two_paths = 0;
                break;
        }
-       printf(":%06o %06o %s ",
-              p->one->mode, p->two->mode, sha1_to_hex(p->one->sha1));
-       printf("%s %s%c%s",
-              sha1_to_hex(p->two->sha1),
-              status,
-              inter_name_termination,
-              p->one->path);
+       if (output_format != DIFF_FORMAT_NAME_STATUS) {
+               printf(":%06o %06o %s ",
+                      p->one->mode, p->two->mode, sha1_to_hex(p->one->sha1));
+               printf("%s ", sha1_to_hex(p->two->sha1));
+       }
+       printf("%s%c%s", status, inter_name_termination, path_one);
        if (two_paths)
-               printf("%c%s", inter_name_termination, p->two->path);
+               printf("%c%s", inter_name_termination, path_two);
        putchar(line_termination);
+       if (path_one != p->one->path)
+               free((void*)path_one);
+       if (path_two != p->two->path)
+               free((void*)path_two);
 }
 
 static void diff_flush_name(struct diff_filepair *p,
+                           int inter_name_termination,
                            int line_termination)
 {
-       printf("%s%c", p->two->path, line_termination);
+       char *path = p->two->path;
+
+       if (line_termination)
+               path = quote_one(p->two->path);
+       else
+               path = p->two->path;
+       printf("%s%c", path, line_termination);
+       if (p->two->path != path)
+               free(path);
 }
 
 int diff_unmodified_pair(struct diff_filepair *p)
@@ -987,32 +1138,38 @@ static void diff_resolve_rename_copy(void)
        diff_debug_queue("resolve-rename-copy done", q);
 }
 
-void diff_flush(int diff_output_style, int line_termination)
+void diff_flush(struct diff_options *options)
 {
        struct diff_queue_struct *q = &diff_queued_diff;
        int i;
        int inter_name_termination = '\t';
+       int diff_output_format = options->output_format;
+       int line_termination = options->line_termination;
 
        if (!line_termination)
                inter_name_termination = 0;
 
        for (i = 0; i < q->nr; i++) {
                struct diff_filepair *p = q->queue[i];
-               if ((diff_output_style == DIFF_FORMAT_NO_OUTPUT) ||
+               if ((diff_output_format == DIFF_FORMAT_NO_OUTPUT) ||
                    (p->status == DIFF_STATUS_UNKNOWN))
                        continue;
                if (p->status == 0)
                        die("internal error in diff-resolve-rename-copy");
-               switch (diff_output_style) {
+               switch (diff_output_format) {
                case DIFF_FORMAT_PATCH:
                        diff_flush_patch(p);
                        break;
                case DIFF_FORMAT_RAW:
+               case DIFF_FORMAT_NAME_STATUS:
                        diff_flush_raw(p, line_termination,
-                                      inter_name_termination);
+                                      inter_name_termination,
+                                      diff_output_format);
                        break;
                case DIFF_FORMAT_NAME:
-                       diff_flush_name(p, line_termination);
+                       diff_flush_name(p,
+                                       inter_name_termination,
+                                       line_termination);
                        break;
                }
                diff_free_filepair(q->queue[i]);
@@ -1078,45 +1235,36 @@ static void diffcore_apply_filter(const char *filter)
        *q = outq;
 }
 
-void diffcore_std(const char **paths,
-                 int detect_rename, int rename_score,
-                 const char *pickaxe, int pickaxe_opts,
-                 int break_opt,
-                 const char *orderfile,
-                 const char *filter)
+void diffcore_std(struct diff_options *options)
 {
-       if (paths && paths[0])
-               diffcore_pathspec(paths);
-       if (break_opt != -1)
-               diffcore_break(break_opt);
-       if (detect_rename)
-               diffcore_rename(detect_rename, rename_score);
-       if (break_opt != -1)
+       if (options->paths && options->paths[0])
+               diffcore_pathspec(options->paths);
+       if (options->break_opt != -1)
+               diffcore_break(options->break_opt);
+       if (options->detect_rename)
+               diffcore_rename(options);
+       if (options->break_opt != -1)
                diffcore_merge_broken();
-       if (pickaxe)
-               diffcore_pickaxe(pickaxe, pickaxe_opts);
-       if (orderfile)
-               diffcore_order(orderfile);
+       if (options->pickaxe)
+               diffcore_pickaxe(options->pickaxe, options->pickaxe_opts);
+       if (options->orderfile)
+               diffcore_order(options->orderfile);
        diff_resolve_rename_copy();
-       diffcore_apply_filter(filter);
+       diffcore_apply_filter(options->filter);
 }
 
 
-void diffcore_std_no_resolve(const char **paths,
-                            const char *pickaxe, int pickaxe_opts,
-                            const char *orderfile,
-                            const char *filter)
+void diffcore_std_no_resolve(struct diff_options *options)
 {
-       if (paths && paths[0])
-               diffcore_pathspec(paths);
-       if (pickaxe)
-               diffcore_pickaxe(pickaxe, pickaxe_opts);
-       if (orderfile)
-               diffcore_order(orderfile);
-       diffcore_apply_filter(filter);
+       if (options->pickaxe)
+               diffcore_pickaxe(options->pickaxe, options->pickaxe_opts);
+       if (options->orderfile)
+               diffcore_order(options->orderfile);
+       diffcore_apply_filter(options->filter);
 }
 
-void diff_addremove(int addremove, unsigned mode,
+void diff_addremove(struct diff_options *options,
+                   int addremove, unsigned mode,
                    const unsigned char *sha1,
                    const char *base, const char *path)
 {
@@ -1135,7 +1283,7 @@ void diff_addremove(int addremove, unsigned mode,
         * Before the final output happens, they are pruned after
         * merged into rename/copy pairs as appropriate.
         */
-       if (reverse_diff)
+       if (options->reverse_diff)
                addremove = (addremove == '+' ? '-' :
                             addremove == '-' ? '+' : addremove);
 
@@ -1152,30 +1300,8 @@ void diff_addremove(int addremove, unsigned mode,
        diff_queue(&diff_queued_diff, one, two);
 }
 
-void diff_helper_input(unsigned old_mode,
-                      unsigned new_mode,
-                      const unsigned char *old_sha1,
-                      const unsigned char *new_sha1,
-                      const char *old_path,
-                      int status,
-                      int score,
-                      const char *new_path)
-{
-       struct diff_filespec *one, *two;
-       struct diff_filepair *dp;
-
-       one = alloc_filespec(old_path);
-       two = alloc_filespec(new_path);
-       if (old_mode)
-               fill_filespec(one, old_sha1, old_mode);
-       if (new_mode)
-               fill_filespec(two, new_sha1, new_mode);
-       dp = diff_queue(&diff_queued_diff, one, two);
-       dp->score = score * MAX_SCORE / 100;
-       dp->status = status;
-}
-
-void diff_change(unsigned old_mode, unsigned new_mode,
+void diff_change(struct diff_options *options,
+                unsigned old_mode, unsigned new_mode,
                 const unsigned char *old_sha1,
                 const unsigned char *new_sha1,
                 const char *base, const char *path) 
@@ -1183,7 +1309,7 @@ void diff_change(unsigned old_mode, unsigned new_mode,
        char concatpath[PATH_MAX];
        struct diff_filespec *one, *two;
 
-       if (reverse_diff) {
+       if (options->reverse_diff) {
                unsigned tmp;
                const unsigned char *tmp_c;
                tmp = old_mode; old_mode = new_mode; new_mode = tmp;
@@ -1199,7 +1325,8 @@ void diff_change(unsigned old_mode, unsigned new_mode,
        diff_queue(&diff_queued_diff, one, two);
 }
 
-void diff_unmerge(const char *path)
+void diff_unmerge(struct diff_options *options,
+                 const char *path)
 {
        struct diff_filespec *one, *two;
        one = alloc_filespec(path);
diff --git a/diff.h b/diff.h
index 3deb7fa4e8b6a572d5ab8d2c6a4da80478074244..12590791cbefe8717891365b6d417f60c51f684f 100644 (file)
--- a/diff.h
+++ b/diff.h
@@ -8,27 +8,67 @@
        (S_ISREG(mode) ? (S_IFREG | ce_permissions(mode)) : \
        S_ISLNK(mode) ? S_IFLNK : S_IFDIR)
 
-extern void diff_addremove(int addremove,
+struct tree_desc {
+       void *buf;
+       unsigned long size;
+};
+
+struct diff_options;
+
+typedef void (*change_fn_t)(struct diff_options *options,
+                unsigned old_mode, unsigned new_mode,
+                const unsigned char *old_sha1,
+                const unsigned char *new_sha1,
+                const char *base, const char *path);
+
+typedef void (*add_remove_fn_t)(struct diff_options *options,
+                   int addremove, unsigned mode,
+                   const unsigned char *sha1,
+                   const char *base, const char *path);
+
+struct diff_options {
+       const char **paths;
+       const char *filter;
+       const char *orderfile;
+       const char *pickaxe;
+       unsigned recursive:1,
+                tree_in_recursive:1;
+       int break_opt;
+       int detect_rename;
+       int find_copies_harder;
+       int line_termination;
+       int output_format;
+       int pickaxe_opts;
+       int rename_score;
+       int reverse_diff;
+       int rename_limit;
+       int setup;
+
+       change_fn_t change;
+       add_remove_fn_t add_remove;
+};
+
+extern void diff_tree_setup_paths(const char **paths);
+extern int diff_tree(struct tree_desc *t1, struct tree_desc *t2,
+                    const char *base, struct diff_options *opt);
+extern int diff_tree_sha1(const unsigned char *old, const unsigned char *new,
+                         const char *base, struct diff_options *opt);
+
+extern void diff_addremove(struct diff_options *,
+                          int addremove,
                           unsigned mode,
                           const unsigned char *sha1,
                           const char *base,
                           const char *path);
 
-extern void diff_change(unsigned mode1, unsigned mode2,
-                            const unsigned char *sha1,
-                            const unsigned char *sha2,
-                            const char *base, const char *path);
+extern void diff_change(struct diff_options *,
+                       unsigned mode1, unsigned mode2,
+                       const unsigned char *sha1,
+                       const unsigned char *sha2,
+                       const char *base, const char *path);
 
-extern void diff_helper_input(unsigned mode1,
-                             unsigned mode2,
-                             const unsigned char *sha1,
-                             const unsigned char *sha2,
-                             const char *path1,
-                             int status,
-                             int score,
-                             const char *path2);
-
-extern void diff_unmerge(const char *path);
+extern void diff_unmerge(struct diff_options *,
+                        const char *path);
 
 extern int diff_scoreopt_parse(const char *opt);
 
@@ -36,42 +76,37 @@ extern int diff_scoreopt_parse(const char *opt);
 #define DIFF_SETUP_USE_CACHE           2
 #define DIFF_SETUP_USE_SIZE_CACHE      4
 
-extern void diff_setup(int flags);
+extern void diff_setup(struct diff_options *);
+extern int diff_opt_parse(struct diff_options *, const char **, int);
+extern int diff_setup_done(struct diff_options *);
 
 #define DIFF_DETECT_RENAME     1
 #define DIFF_DETECT_COPY       2
 
 #define DIFF_PICKAXE_ALL       1
 
-extern void diffcore_std(const char **paths,
-                        int detect_rename, int rename_score,
-                        const char *pickaxe, int pickaxe_opts,
-                        int break_opt,
-                        const char *orderfile, const char *filter);
+extern void diffcore_std(struct diff_options *);
 
-extern void diffcore_std_no_resolve(const char **paths,
-                                   const char *pickaxe, int pickaxe_opts,
-                                   const char *orderfile, const char *filter);
+extern void diffcore_std_no_resolve(struct diff_options *);
 
 #define COMMON_DIFF_OPTIONS_HELP \
 "\ncommon diff options:\n" \
-"  -r          diff recursively (only meaningful in diff-tree)\n" \
-"  -z          output diff-raw with lines terminated with NUL.\n" \
-"  -p          output patch format.\n" \
-"  -u          synonym for -p.\n" \
-"  --name-only show only names of changed files.\n" \
-"  --name-only-z\n" \
-"              same as --name-only but terminate lines with NUL.\n" \
-"  -R          swap input file pairs.\n" \
-"  -B          detect complete rewrites.\n" \
-"  -M          detect renames.\n" \
-"  -C          detect copies.\n" \
+"  -z            output diff-raw with lines terminated with NUL.\n" \
+"  -p            output patch format.\n" \
+"  -u            synonym for -p.\n" \
+"  --name-only   show only names of changed files.\n" \
+"  --name-status show names and status of changed files.\n" \
+"  -R            swap input file pairs.\n" \
+"  -B            detect complete rewrites.\n" \
+"  -M            detect renames.\n" \
+"  -C            detect copies.\n" \
 "  --find-copies-harder\n" \
-"              try unchanged files as candidate for copy detection.\n" \
-"  -O<file>    reorder diffs according to the <file>.\n" \
-"  -S<string>  find filepair whose only one side contains the string.\n" \
+"                try unchanged files as candidate for copy detection.\n" \
+"  -l<n>         limit rename attempts up to <n> paths.\n" \
+"  -O<file>      reorder diffs according to the <file>.\n" \
+"  -S<string>    find filepair whose only one side contains the string.\n" \
 "  --pickaxe-all\n" \
-"              show all files diff when -S is used and hit is found.\n"
+"                show all files diff when -S is used and hit is found.\n"
 
 extern int diff_queue_is_empty(void);
 
@@ -79,8 +114,9 @@ extern int diff_queue_is_empty(void);
 #define DIFF_FORMAT_PATCH      2
 #define DIFF_FORMAT_NO_OUTPUT  3
 #define DIFF_FORMAT_NAME       4
+#define DIFF_FORMAT_NAME_STATUS        5
 
-extern void diff_flush(int output_style, int line_terminator);
+extern void diff_flush(struct diff_options*);
 
 /* diff-raw status letters */
 #define DIFF_STATUS_ADDED              'A'
@@ -95,7 +131,7 @@ extern void diff_flush(int output_style, int line_terminator);
 /* these are not diff-raw status letters proper, but used by
  * diffcore-filter insn to specify additional restrictions.
  */
-#define DIFF_STATUS_FILTER_AON         'A'
+#define DIFF_STATUS_FILTER_AON         '*'
 #define DIFF_STATUS_FILTER_BROKEN      'B'
 
 #endif /* DIFF_H */
index 092cf68de6455b3f2dc639d9c7a79e574ff615db..e17dd90058443ea75321f8c48818bd0094bd76a3 100644 (file)
@@ -249,8 +249,11 @@ static int compute_stays(struct diff_queue_struct *q,
        return 1;
 }
 
-void diffcore_rename(int detect_rename, int minimum_score)
+void diffcore_rename(struct diff_options *options)
 {
+       int detect_rename = options->detect_rename;
+       int minimum_score = options->rename_score;
+       int rename_limit = options->rename_limit;
        struct diff_queue_struct *q = &diff_queued_diff;
        struct diff_queue_struct outq;
        struct diff_score *mx;
@@ -279,7 +282,8 @@ void diffcore_rename(int detect_rename, int minimum_score)
                else if (detect_rename == DIFF_DETECT_COPY)
                        register_rename_src(p->one, 1);
        }
-       if (rename_dst_nr == 0)
+       if (rename_dst_nr == 0 ||
+           (0 <= rename_limit && rename_limit < rename_dst_nr))
                goto cleanup; /* nothing to do */
 
        /* We really want to cull the candidates list early
index f1b5ca748cae8d40f599552c3605080808da7803..a38acb13e19e0b3bfe9a377fefe4810c9830a11e 100644 (file)
@@ -85,7 +85,7 @@ extern void diff_q(struct diff_queue_struct *, struct diff_filepair *);
 
 extern void diffcore_pathspec(const char **pathspec);
 extern void diffcore_break(int);
-extern void diffcore_rename(int rename_copy, int);
+extern void diffcore_rename(struct diff_options *);
 extern void diffcore_merge_broken(void);
 extern void diffcore_pickaxe(const char *needle, int opts);
 extern void diffcore_order(const char *orderfile);
diff --git a/entry.c b/entry.c
index b8426dbd0dae619b9f2023f9cf46f4d8c3df0a24..15b34eb6f9ac1db569487a3b732740f2d35831a7 100644 (file)
--- a/entry.c
+++ b/entry.c
@@ -132,7 +132,7 @@ int checkout_entry(struct cache_entry *ce, struct checkout *state)
                if (!state->force) {
                        if (!state->quiet)
                                fprintf(stderr, "git-checkout-index: %s already exists\n", path);
-                       return 0;
+                       return -1;
                }
 
                /*
diff --git a/environment.c b/environment.c
new file mode 100644 (file)
index 0000000..1dc7af5
--- /dev/null
@@ -0,0 +1,75 @@
+/*
+ * We put all the git config variables in this same object
+ * file, so that programs can link against the config parser
+ * without having to link against all the rest of git.
+ *
+ * In particular, no need to bring in libz etc unless needed,
+ * even if you might want to know where the git directory etc
+ * are.
+ */
+#include "cache.h"
+
+char git_default_email[MAX_GITNAME];
+char git_default_name[MAX_GITNAME];
+int trust_executable_bit = 1;
+
+static char *git_dir, *git_object_dir, *git_index_file, *git_refs_dir,
+       *git_graft_file;
+static void setup_git_env(void)
+{
+       git_dir = getenv(GIT_DIR_ENVIRONMENT);
+       if (!git_dir)
+               git_dir = DEFAULT_GIT_DIR_ENVIRONMENT;
+       git_object_dir = getenv(DB_ENVIRONMENT);
+       if (!git_object_dir) {
+               git_object_dir = xmalloc(strlen(git_dir) + 9);
+               sprintf(git_object_dir, "%s/objects", git_dir);
+       }
+       git_refs_dir = xmalloc(strlen(git_dir) + 6);
+       sprintf(git_refs_dir, "%s/refs", git_dir);
+       git_index_file = getenv(INDEX_ENVIRONMENT);
+       if (!git_index_file) {
+               git_index_file = xmalloc(strlen(git_dir) + 7);
+               sprintf(git_index_file, "%s/index", git_dir);
+       }
+       git_graft_file = getenv(GRAFT_ENVIRONMENT);
+       if (!git_graft_file)
+               git_graft_file = strdup(git_path("info/grafts"));
+}
+
+char *get_git_dir(void)
+{
+       if (!git_dir)
+               setup_git_env();
+       return git_dir;
+}
+
+char *get_object_directory(void)
+{
+       if (!git_object_dir)
+               setup_git_env();
+       return git_object_dir;
+}
+
+char *get_refs_directory(void)
+{
+       if (!git_refs_dir)
+               setup_git_env();
+       return git_refs_dir;
+}
+
+char *get_index_file(void)
+{
+       if (!git_index_file)
+               setup_git_env();
+       return git_index_file;
+}
+
+char *get_graft_file(void)
+{
+       if (!git_graft_file)
+               setup_git_env();
+       return git_graft_file;
+}
+
+
diff --git a/export.c b/export.c
deleted file mode 100644 (file)
index ce10b5a..0000000
--- a/export.c
+++ /dev/null
@@ -1,81 +0,0 @@
-#include "cache.h"
-#include "commit.h"
-
-/*
- * Show one commit
- */
-static void show_commit(struct commit *commit)
-{
-       char cmdline[400];
-       char hex[100];
-
-       strcpy(hex, sha1_to_hex(commit->object.sha1));
-       printf("Id: %s\n", hex);
-       fflush(NULL);
-       sprintf(cmdline, "git-cat-file commit %s", hex);
-       system(cmdline);
-       if (commit->parents) {
-               char *against = sha1_to_hex(commit->parents->item->object.sha1);
-               printf("\n\n======== diff against %s ========\n", against);
-               fflush(NULL);
-               sprintf(cmdline, "git-diff-tree -p %s %s", against, hex);
-               system(cmdline);
-       }
-       printf("======== end ========\n\n");
-}
-
-/*
- * Show all unseen commits, depth-first
- */
-static void show_unseen(struct commit *top)
-{
-       struct commit_list *parents;
-
-       if (top->object.flags & 2)
-               return;
-       top->object.flags |= 2;
-       parents = top->parents;
-       while (parents) {
-               show_unseen(parents->item);
-               parents = parents->next;
-       }
-       show_commit(top);
-}
-
-static void export(struct commit *top, struct commit *base)
-{
-       mark_reachable(&top->object, 1);
-       if (base)
-               mark_reachable(&base->object, 2);
-       show_unseen(top);
-}
-
-static struct commit *get_commit(unsigned char *sha1)
-{
-       struct commit *commit = lookup_commit(sha1);
-       if (!commit->object.parsed) {
-               struct commit_list *parents;
-
-               if (parse_commit(commit) < 0)
-                       die("unable to parse commit %s", sha1_to_hex(sha1));
-               parents = commit->parents;
-               while (parents) {
-                       get_commit(parents->item->object.sha1);
-                       parents = parents->next;
-               }
-       }
-       return commit;
-}
-
-int main(int argc, char **argv)
-{
-       unsigned char base_sha1[20];
-       unsigned char top_sha1[20];
-
-       if (argc < 2 || argc > 4 ||
-           get_sha1(argv[1], top_sha1) ||
-           (argc == 3 && get_sha1(argv[2], base_sha1)))
-               usage("git-export top [base]");
-       export(get_commit(top_sha1), argc==3 ? get_commit(base_sha1) : NULL);
-       return 0;
-}
index 582f967a7af68670a6bb174745e43df35a2d0eef..65659826601eb9c850266539f6c9e7807a0b467e 100644 (file)
@@ -1,6 +1,9 @@
 #include "cache.h"
 #include "refs.h"
 #include "pkt-line.h"
+#include "commit.h"
+#include "tag.h"
+#include <time.h>
 #include <sys/wait.h>
 
 static int quiet;
@@ -9,37 +12,168 @@ static const char fetch_pack_usage[] =
 "git-fetch-pack [-q] [-v] [--exec=upload-pack] [host:]directory <refs>...";
 static const char *exec = "git-upload-pack";
 
+#define COMPLETE       (1U << 0)
+#define COMMON         (1U << 1)
+#define COMMON_REF     (1U << 2)
+#define SEEN           (1U << 3)
+#define POPPED         (1U << 4)
+
+static struct commit_list *rev_list = NULL;
+static int non_common_revs = 0, multi_ack = 0;
+
+static void rev_list_push(struct commit *commit, int mark)
+{
+       if (!(commit->object.flags & mark)) {
+               commit->object.flags |= mark;
+
+               if (!(commit->object.parsed))
+                       parse_commit(commit);
+
+               insert_by_date(commit, &rev_list);
+
+               if (!(commit->object.flags & COMMON))
+                       non_common_revs++;
+       }
+}
+
+static int rev_list_insert_ref(const char *path, const unsigned char *sha1)
+{
+       struct object *o = deref_tag(parse_object(sha1), path, 0);
+
+       if (o && o->type == commit_type)
+               rev_list_push((struct commit *)o, SEEN);
+
+       return 0;
+}
+
+/*
+   This function marks a rev and its ancestors as common.
+   In some cases, it is desirable to mark only the ancestors (for example
+   when only the server does not yet know that they are common).
+*/
+
+static void mark_common(struct commit *commit,
+               int ancestors_only, int dont_parse)
+{
+       if (commit != NULL && !(commit->object.flags & COMMON)) {
+               struct object *o = (struct object *)commit;
+
+               if (!ancestors_only)
+                       o->flags |= COMMON;
+
+               if (!(o->flags & SEEN))
+                       rev_list_push(commit, SEEN);
+               else {
+                       struct commit_list *parents;
+
+                       if (!ancestors_only && !(o->flags & POPPED))
+                               non_common_revs--;
+                       if (!o->parsed && !dont_parse)
+                               parse_commit(commit);
+
+                       for (parents = commit->parents;
+                                       parents;
+                                       parents = parents->next)
+                               mark_common(parents->item, 0, dont_parse);
+               }
+       }
+}
+
+/*
+  Get the next rev to send, ignoring the common.
+*/
+
+static const unsigned char* get_rev()
+{
+       struct commit *commit = NULL;
+
+       while (commit == NULL) {
+               unsigned int mark;
+               struct commit_list* parents;
+
+               if (rev_list == NULL || non_common_revs == 0)
+                       return NULL;
+
+               commit = rev_list->item;
+               if (!(commit->object.parsed))
+                       parse_commit(commit);
+               commit->object.flags |= POPPED;
+               if (!(commit->object.flags & COMMON))
+                       non_common_revs--;
+       
+               parents = commit->parents;
+
+               if (commit->object.flags & COMMON) {
+                       /* do not send "have", and ignore ancestors */
+                       commit = NULL;
+                       mark = COMMON | SEEN;
+               } else if (commit->object.flags & COMMON_REF)
+                       /* send "have", and ignore ancestors */
+                       mark = COMMON | SEEN;
+               else
+                       /* send "have", also for its ancestors */
+                       mark = SEEN;
+
+               while (parents) {
+                       if (!(parents->item->object.flags & SEEN))
+                               rev_list_push(parents->item, mark);
+                       if (mark & COMMON)
+                               mark_common(parents->item, 1, 0);
+                       parents = parents->next;
+               }
+
+               rev_list = rev_list->next;
+       }
+
+       return commit->object.sha1;
+}
+
 static int find_common(int fd[2], unsigned char *result_sha1,
                       struct ref *refs)
 {
-       static char line[1000];
+       int fetching;
        int count = 0, flushes = 0, retval;
-       FILE *revs;
+       const unsigned char *sha1;
 
-       revs = popen("git-rev-list $(git-rev-parse --all)", "r");
-       if (!revs)
-               die("unable to run 'git-rev-list'");
+       for_each_ref(rev_list_insert_ref);
 
-       while (refs) {
+       fetching = 0;
+       for ( ; refs ; refs = refs->next) {
                unsigned char *remote = refs->old_sha1;
-               if (verbose)
-                       fprintf(stderr,
-                               "want %s (%s)\n", sha1_to_hex(remote),
-                               refs->name);
-               packet_write(fd[1], "want %s\n", sha1_to_hex(remote));
-               refs = refs->next;
+               struct object *o;
+
+               /*
+                * If that object is complete (i.e. it is an ancestor of a
+                * local ref), we tell them we have it but do not have to
+                * tell them about its ancestors, which they already know
+                * about.
+                *
+                * We use lookup_object here because we are only
+                * interested in the case we *know* the object is
+                * reachable and we have already scanned it.
+                */
+               if (((o = lookup_object(remote)) != NULL) &&
+                               (o->flags & COMPLETE)) {
+                       continue;
+               }
+
+               packet_write(fd[1], "want %s%s\n", sha1_to_hex(remote),
+                       multi_ack ? " multi_ack" : "");
+               fetching++;
        }
        packet_flush(fd[1]);
-       flushes = 1;
+       if (!fetching)
+               return 1;
+
+       flushes = 0;
        retval = -1;
-       while (fgets(line, sizeof(line), revs) != NULL) {
-               unsigned char sha1[20];
-               if (get_sha1_hex(line, sha1))
-                       die("git-fetch-pack: expected object name, got crud");
+       while ((sha1 = get_rev())) {
                packet_write(fd[1], "have %s\n", sha1_to_hex(sha1));
                if (verbose)
                        fprintf(stderr, "have %s\n", sha1_to_hex(sha1));
                if (!(31 & ++count)) {
+                       int ack;
+
                        packet_flush(fd[1]);
                        flushes++;
 
@@ -49,27 +183,177 @@ static int find_common(int fd[2], unsigned char *result_sha1,
                         */
                        if (count == 32)
                                continue;
-                       if (get_ack(fd[0], result_sha1)) {
-                               flushes = 0;
-                               retval = 0;
-                               if (verbose)
-                                       fprintf(stderr, "got ack\n");
-                               break;
-                       }
+
+                       do {
+                               ack = get_ack(fd[0], result_sha1);
+                               if (verbose && ack)
+                                       fprintf(stderr, "got ack %d %s\n", ack,
+                                                       sha1_to_hex(result_sha1));
+                               if (ack == 1) {
+                                       flushes = 0;
+                                       multi_ack = 0;
+                                       retval = 0;
+                                       goto done;
+                               } else if (ack == 2) {
+                                       struct commit *commit =
+                                               lookup_commit(result_sha1);
+                                       mark_common(commit, 0, 1);
+                                       retval = 0;
+                               }
+                       } while (ack);
                        flushes--;
                }
        }
-       pclose(revs);
+done:
        packet_write(fd[1], "done\n");
        if (verbose)
                fprintf(stderr, "done\n");
-       while (flushes) {
-               flushes--;
-               if (get_ack(fd[0], result_sha1)) {
+       if (retval != 0) {
+               multi_ack = 0;
+               flushes++;
+       }
+       while (flushes || multi_ack) {
+               int ack = get_ack(fd[0], result_sha1);
+               if (ack) {
                        if (verbose)
-                               fprintf(stderr, "got ack\n");
-                       return 0;
+                               fprintf(stderr, "got ack (%d) %s\n", ack,
+                                       sha1_to_hex(result_sha1));
+                       if (ack == 1)
+                               return 0;
+                       multi_ack = 1;
+                       continue;
+               }
+               flushes--;
+       }
+       return retval;
+}
+
+static struct commit_list *complete = NULL;
+
+static int mark_complete(const char *path, const unsigned char *sha1)
+{
+       struct object *o = parse_object(sha1);
+
+       while (o && o->type == tag_type) {
+               struct tag *t = (struct tag *) o;
+               if (!t->tagged)
+                       break; /* broken repository */
+               o->flags |= COMPLETE;
+               o = parse_object(t->tagged->sha1);
+       }
+       if (o && o->type == commit_type) {
+               struct commit *commit = (struct commit *)o;
+               commit->object.flags |= COMPLETE;
+               insert_by_date(commit, &complete);
+       }
+       return 0;
+}
+
+static void mark_recent_complete_commits(unsigned long cutoff)
+{
+       while (complete && cutoff <= complete->item->date) {
+               if (verbose)
+                       fprintf(stderr, "Marking %s as complete\n",
+                               sha1_to_hex(complete->item->object.sha1));
+               pop_most_recent_commit(&complete, COMPLETE);
+       }
+}
+
+static void filter_refs(struct ref **refs, int nr_match, char **match)
+{
+       struct ref *prev, *current, *next;
+
+       if (!nr_match)
+               return;
+
+       for (prev = NULL, current = *refs; current; current = next) {
+               next = current->next;
+               if ((!memcmp(current->name, "refs/", 5) &&
+                                       check_ref_format(current->name + 5)) ||
+                               !path_match(current->name, nr_match, match)) {
+                       if (prev == NULL)
+                               *refs = next;
+                       else
+                               prev->next = next;
+                       free(current);
+               } else
+                       prev = current;
+       }
+}
+
+static int everything_local(struct ref **refs, int nr_match, char **match)
+{
+       struct ref *ref;
+       int retval;
+       unsigned long cutoff = 0;
+
+       track_object_refs = 0;
+       save_commit_buffer = 0;
+
+       for (ref = *refs; ref; ref = ref->next) {
+               struct object *o;
+
+               o = parse_object(ref->old_sha1);
+               if (!o)
+                       continue;
+
+               /* We already have it -- which may mean that we were
+                * in sync with the other side at some time after
+                * that (it is OK if we guess wrong here).
+                */
+               if (o->type == commit_type) {
+                       struct commit *commit = (struct commit *)o;
+                       if (!cutoff || cutoff < commit->date)
+                               cutoff = commit->date;
+               }
+       }
+
+       for_each_ref(mark_complete);
+       if (cutoff)
+               mark_recent_complete_commits(cutoff);
+
+       /*
+        * Mark all complete remote refs as common refs.
+        * Don't mark them common yet; the server has to be told so first.
+        */
+       for (ref = *refs; ref; ref = ref->next) {
+               struct object *o = deref_tag(lookup_object(ref->old_sha1),
+                                            NULL, 0);
+
+               if (!o || o->type != commit_type || !(o->flags & COMPLETE))
+                       continue;
+
+               if (!(o->flags & SEEN)) {
+                       rev_list_push((struct commit *)o, COMMON_REF | SEEN);
+
+                       mark_common((struct commit *)o, 1, 1);
+               }
+       }
+
+       filter_refs(refs, nr_match, match);
+
+       for (retval = 1, ref = *refs; ref ; ref = ref->next) {
+               const unsigned char *remote = ref->old_sha1;
+               unsigned char local[20];
+               struct object *o;
+
+               o = lookup_object(remote);
+               if (!o || !(o->flags & COMPLETE)) {
+                       retval = 0;
+                       if (!verbose)
+                               continue;
+                       fprintf(stderr,
+                               "want %s (%s)\n", sha1_to_hex(remote),
+                               ref->name);
+                       continue;
                }
+
+               memcpy(ref->new_sha1, local, 20);
+               if (!verbose)
+                       continue;
+               fprintf(stderr,
+                       "already have %s (%s)\n", sha1_to_hex(remote),
+                       ref->name);
        }
        return retval;
 }
@@ -81,11 +365,20 @@ static int fetch_pack(int fd[2], int nr_match, char **match)
        int status;
        pid_t pid;
 
-       get_remote_heads(fd[0], &ref, nr_match, match);
+       get_remote_heads(fd[0], &ref, 0, NULL, 0);
+       if (server_supports("multi_ack")) {
+               if (verbose)
+                       fprintf(stderr, "Server supports multi_ack\n");
+               multi_ack = 1;
+       }
        if (!ref) {
                packet_flush(fd[1]);
                die("no matching remote head");
        }
+       if (everything_local(&ref, nr_match, match)) {
+               packet_flush(fd[1]);
+               goto all_done;
+       }
        if (find_common(fd, sha1, ref) < 0)
                fprintf(stderr, "warning: no common commits\n");
        pid = fork();
@@ -109,6 +402,7 @@ static int fetch_pack(int fd[2], int nr_match, char **match)
                int code = WEXITSTATUS(status);
                if (code)
                        die("git-unpack-objects died with error code %d", code);
+all_done:
                while (ref) {
                        printf("%s %s\n",
                               sha1_to_hex(ref->old_sha1), ref->name);
@@ -164,5 +458,19 @@ int main(int argc, char **argv)
        close(fd[0]);
        close(fd[1]);
        finish_connect(pid);
+
+       if (!ret && nr_heads) {
+               /* If the heads to pull were given, we should have
+                * consumed all of them by matching the remote.
+                * Otherwise, 'git-fetch remote no-such-ref' would
+                * silently succeed without issuing an error.
+                */
+               for (i = 0; i < nr_heads; i++)
+                       if (heads[i] && heads[i][0]) {
+                               error("no such remote ref %s", heads[i]);
+                               ret = 1;
+                       }
+       }
+
        return ret;
 }
diff --git a/fetch.c b/fetch.c
index 1a33ae984fe999096b0e06fb66155a19be001346..73bde07aeaeea67e44aef7aec1b79d5806e80a1b 100644 (file)
--- a/fetch.c
+++ b/fetch.c
@@ -15,6 +15,7 @@ int get_tree = 0;
 int get_history = 0;
 int get_all = 0;
 int get_verbosely = 0;
+int get_recover = 0;
 static unsigned char current_commit_sha1[20];
 
 void pull_say(const char *fmt, const char *hex) 
@@ -164,7 +165,7 @@ static int loop(void)
                 * the queue because we needed to fetch it first.
                 */
                if (! (obj->flags & TO_SCAN)) {
-                       if (!has_sha1_file(obj->sha1) && fetch(obj->sha1)) {
+                       if (fetch(obj->sha1)) {
                                report_missing(obj->type
                                               ? obj->type
                                               : "object", obj->sha1);
@@ -214,7 +215,9 @@ int pull(char *target)
                        return -1;
        }
 
-       for_each_ref(mark_complete);
+       if (!get_recover) {
+               for_each_ref(mark_complete);
+       }
 
        if (interpret_target(target, sha1))
                return error("Could not interpret %s as something to pull",
diff --git a/fetch.h b/fetch.h
index e7710e89e710979283f82fe3e67ca1296e70b556..9837a3d03527ed648370f62b706978ad14fd20e5 100644 (file)
--- a/fetch.h
+++ b/fetch.h
@@ -40,6 +40,9 @@ extern int get_all;
 /* Set to be verbose */
 extern int get_verbosely;
 
+/* Set to check on all reachable objects. */
+extern int get_recover;
+
 /* Report what we got under get_verbosely */
 extern void pull_say(const char *, const char *);
 
index 0c5c430ed1f54b981ffb1a3b7a1d73ca24a18561..17d05363e08b48275cb38631e82f0f48321fa728 100644 (file)
@@ -20,6 +20,35 @@ static int check_strict = 0;
 static int keep_cache_objects = 0; 
 static unsigned char head_sha1[20];
 
+
+static void objreport(struct object *obj, const char *severity,
+                      const char *err, va_list params)
+{
+       fprintf(stderr, "%s in %s %s: ",
+               severity, obj->type, sha1_to_hex(obj->sha1));
+       vfprintf(stderr, err, params);
+       fputs("\n", stderr);
+}
+
+static int objerror(struct object *obj, const char *err, ...)
+{
+       va_list params;
+       va_start(params, err);
+       objreport(obj, "error", err, params);
+       va_end(params);
+       return -1;
+}
+
+static int objwarning(struct object *obj, const char *err, ...)
+{
+       va_list params;
+       va_start(params, err);
+       objreport(obj, "warning", err, params);
+       va_end(params);
+       return -1;
+}
+
+
 static void check_connectivity(void)
 {
        int i;
@@ -162,31 +191,19 @@ static int fsck_tree(struct tree *item)
 
        retval = 0;
        if (has_full_path) {
-               fprintf(stderr, "warning: git-fsck-objects: tree %s "
-                       "has full pathnames in it\n", 
-                       sha1_to_hex(item->object.sha1));
+               objwarning(&item->object, "contains full pathnames");
        }
        if (has_zero_pad) {
-               fprintf(stderr, "warning: git-fsck-objects: tree %s "
-                       "has zero-padded file modes in it\n",
-                       sha1_to_hex(item->object.sha1));
+               objwarning(&item->object, "contains zero-padded file modes");
        }
        if (has_bad_modes) {
-               fprintf(stderr, "warning: git-fsck-objects: tree %s "
-                       "has bad file modes in it\n",
-                       sha1_to_hex(item->object.sha1));
+               objwarning(&item->object, "contains bad file modes");
        }
        if (has_dup_entries) {
-               fprintf(stderr, "error: git-fsck-objects: tree %s "
-                       "has duplicate file entries\n",
-                       sha1_to_hex(item->object.sha1));
-               retval = -1;
+               retval = objerror(&item->object, "contains duplicate file entries");
        }
        if (not_properly_sorted) {
-               fprintf(stderr, "error: git-fsck-objects: tree %s "
-                       "is not properly sorted\n",
-                       sha1_to_hex(item->object.sha1));
-               retval = -1;
+               retval = objerror(&item->object, "not properly sorted");
        }
        return retval;
 }
@@ -194,24 +211,24 @@ static int fsck_tree(struct tree *item)
 static int fsck_commit(struct commit *commit)
 {
        char *buffer = commit->buffer;
-       unsigned char sha1[20];
+       unsigned char tree_sha1[20], sha1[20];
 
        if (memcmp(buffer, "tree ", 5))
-               return -1;
-       if (get_sha1_hex(buffer+5, sha1) || buffer[45] != '\n')
-               return -1;
+               return objerror(&commit->object, "invalid format - expected 'tree' line");
+       if (get_sha1_hex(buffer+5, tree_sha1) || buffer[45] != '\n')
+               return objerror(&commit->object, "invalid 'tree' line format - bad sha1");
        buffer += 46;
        while (!memcmp(buffer, "parent ", 7)) {
                if (get_sha1_hex(buffer+7, sha1) || buffer[47] != '\n')
-                       return -1;
+                       return objerror(&commit->object, "invalid 'parent' line format - bad sha1");
                buffer += 48;
        }
        if (memcmp(buffer, "author ", 7))
-               return -1;
+               return objerror(&commit->object, "invalid format - expected 'author' line");
        free(commit->buffer);
        commit->buffer = NULL;
        if (!commit->tree)
-               return -1;
+               return objerror(&commit->object, "could not load commit's tree %s", tree_sha1);
        if (!commit->parents && show_root)
                printf("root %s\n", sha1_to_hex(commit->object.sha1));
        if (!commit->date)
@@ -225,8 +242,7 @@ static int fsck_tag(struct tag *tag)
        struct object *tagged = tag->tagged;
 
        if (!tagged) {
-               printf("bad object in tag %s\n", sha1_to_hex(tag->object.sha1));
-               return -1;
+               return objerror(&tag->object, "could not load tagged object");
        }
        if (!show_tags)
                return 0;
@@ -240,7 +256,7 @@ static int fsck_sha1(unsigned char *sha1)
 {
        struct object *obj = parse_object(sha1);
        if (!obj)
-               return -1;
+               return error("%s: object not found", sha1_to_hex(sha1));
        if (obj->type == blob_type)
                return 0;
        if (obj->type == tree_type)
@@ -249,7 +265,8 @@ static int fsck_sha1(unsigned char *sha1)
                return fsck_commit((struct commit *) obj);
        if (obj->type == tag_type)
                return fsck_tag((struct tag *) obj);
-       return -1;
+       /* By now, parse_object() would've returned NULL instead. */
+       return objerror(obj, "unknown type '%s' (internal fsck error)", obj->type);
 }
 
 /*
@@ -285,8 +302,7 @@ static void fsck_sha1_list(void)
                unsigned char *sha1 = entry->sha1;
 
                sha1_list.entry[i] = NULL;
-               if (fsck_sha1(sha1) < 0)
-                       fprintf(stderr, "bad sha1 entry '%s'\n", sha1_to_hex(sha1));
+               fsck_sha1(sha1);
                free(entry);
        }
        sha1_list.nr = 0;
@@ -313,9 +329,8 @@ static int fsck_dir(int i, char *path)
        DIR *dir = opendir(path);
        struct dirent *de;
 
-       if (!dir) {
-               return error("missing sha1 directory '%s'", path);
-       }
+       if (!dir)
+               return 0;
 
        while ((de = readdir(dir)) != NULL) {
                char name[100];
@@ -386,28 +401,17 @@ static void fsck_object_dir(const char *path)
 
 static int fsck_head_link(void)
 {
-       int fd, count;
-       char hex[40];
        unsigned char sha1[20];
-       static char path[PATH_MAX], link[PATH_MAX];
-       const char *git_dir;
-
-       git_dir = getenv(GIT_DIR_ENVIRONMENT);
-       if (!git_dir) git_dir = DEFAULT_GIT_DIR_ENVIRONMENT;
-
-       snprintf(path, sizeof(path), "%s/HEAD", git_dir);
-       if (readlink(path, link, sizeof(link)) < 0)
-               return error("HEAD is not a symlink");
-       if (strncmp("refs/heads/", link, 11))
-               return error("HEAD points to something strange (%s)", link);
-       fd = open(path, O_RDONLY);
-       if (fd < 0)
-               return error("HEAD: %s", strerror(errno));
-       count = read(fd, hex, sizeof(hex));
-       close(fd);
-       if (count < 0)
-               return error("HEAD: %s", strerror(errno));
-       if (count < 40 || get_sha1_hex(hex, sha1))
+       const char *git_HEAD = strdup(git_path("HEAD"));
+       const char *git_refs_heads_master = resolve_ref(git_HEAD, sha1, 1);
+       int pfxlen = strlen(git_HEAD) - 4; /* strip .../.git/ part */
+
+       if (!git_refs_heads_master)
+               return error("HEAD is not a symbolic ref");
+       if (strncmp(git_refs_heads_master + pfxlen, "refs/heads/", 11))
+               return error("HEAD points to something strange (%s)",
+                            git_refs_heads_master + pfxlen);
+       if (!memcmp(null_sha1, sha1, 20))
                return error("HEAD: not a valid git pointer");
        return 0;
 }
@@ -479,9 +483,7 @@ int main(int argc, char **argv)
                        for (i = 0; i < num; i++) {
                                unsigned char sha1[20];
                                nth_packed_object_sha1(p, i, sha1);
-                               if (fsck_sha1(sha1) < 0)
-                                       fprintf(stderr, "bad sha1 entry '%s'\n", sha1_to_hex(sha1));
-
+                               fsck_sha1(sha1);
                        }
                }
        }
@@ -505,7 +507,7 @@ int main(int argc, char **argv)
                        heads++;
                        continue;
                }
-               error("expected sha1, got %s", arg);
+               error("invalid parameter: expected sha1, got '%s'", arg);
        }
 
        /*
index 3d364db2517b6a19de6b8e26f6d61a86589c1fcc..b5fe46aa20865d6785390fd22406d32b00a77845 100755 (executable)
@@ -1,15 +1,21 @@
 #!/bin/sh
 
+usage() {
+    die "usage: git add [-n] [-v] <file>..."
+}
+
 show_only=
 verbose=
 while : ; do
   case "$1" in
     -n)
        show_only=true
-       verbose=true
        ;;
     -v)
-       verbose=true
+       verbose=--verbose
+       ;;
+    -*)
+       usage
        ;;
     *)
        break
@@ -19,14 +25,19 @@ while : ; do
 done
 
 GIT_DIR=$(git-rev-parse --git-dir) || exit
-global_exclude=
-if [ -f "$GIT_DIR/info/exclude" ]; then
-   global_exclude="--exclude-from=$GIT_DIR/info/exclude"
-fi
-for i in $(git-ls-files --others \
-       $global_exclude --exclude-per-directory=.gitignore \
-       "$@")
-do
-   [ "$verbose" ] && echo "  $i"
-   [ "$show_only" ] || git-update-index --add -- "$i" || exit
-done
+
+if test -f "$GIT_DIR/info/exclude"
+then
+       git-ls-files -z \
+       --exclude-from="$GIT_DIR/info/exclude" \
+       --others --exclude-per-directory=.gitignore -- "$@"
+else
+       git-ls-files -z \
+       --others --exclude-per-directory=.gitignore -- "$@"
+fi |
+case "$show_only" in
+true)
+       xargs -0 echo ;;
+*)
+       git-update-index --add $verbose -z --stdin ;;
+esac
diff --git a/git-am.sh b/git-am.sh
new file mode 100755 (executable)
index 0000000..115ebad
--- /dev/null
+++ b/git-am.sh
@@ -0,0 +1,351 @@
+#!/bin/sh
+#
+#
+. git-sh-setup || die "Not a git archive"
+
+files=$(git-diff-index --cached --name-only HEAD) || exit
+if [ "$files" ]; then
+   echo "Dirty index: cannot apply patches (dirty: $files)" >&2
+   exit 1
+fi
+
+usage () {
+    echo >&2 "usage: $0 [--signoff] [--dotest=<dir>] [--utf8] [--3way] <mbox>"
+    echo >&2 " or, when resuming"
+    echo >&2 " $0 [--skip]"
+    exit 1;
+}
+
+stop_here () {
+    echo "$1" >"$dotest/next"
+    exit 1
+}
+
+go_next () {
+       rm -f "$dotest/$msgnum" "$dotest/msg" "$dotest/msg-clean" \
+               "$dotest/patch" "$dotest/info"
+       echo "$next" >"$dotest/next"
+       this=$next
+}
+
+fall_back_3way () {
+    O_OBJECT=`cd "$GIT_OBJECT_DIRECTORY" && pwd`
+
+    rm -fr "$dotest"/patch-merge-*
+    mkdir "$dotest/patch-merge-tmp-dir"
+
+    # First see if the patch records the index info that we can use.
+    if git-apply -z --index-info "$dotest/patch" \
+       >"$dotest/patch-merge-index-info" 2>/dev/null &&
+       GIT_INDEX_FILE="$dotest/patch-merge-tmp-index" \
+       git-update-index -z --index-info <"$dotest/patch-merge-index-info" &&
+       GIT_INDEX_FILE="$dotest/patch-merge-tmp-index" \
+       git-write-tree >"$dotest/patch-merge-base+" &&
+       # index has the base tree now.
+       (
+           cd "$dotest/patch-merge-tmp-dir" &&
+           GIT_INDEX_FILE="../patch-merge-tmp-index" \
+           GIT_OBJECT_DIRECTORY="$O_OBJECT" \
+           git-apply --index <../patch
+        )
+    then
+       echo Using index info to reconstruct a base tree...
+       mv "$dotest/patch-merge-base+" "$dotest/patch-merge-base"
+       mv "$dotest/patch-merge-tmp-index" "$dotest/patch-merge-index"
+    else
+       # Otherwise, try nearby trees that can be used to apply the
+       # patch.
+       (
+           N=10
+
+           # Hoping the patch is against our recent commits...
+           git-rev-list --max-count=$N HEAD
+
+           # or hoping the patch is against known tags...
+           git-ls-remote --tags .
+       ) |
+       while read base junk
+       do
+           # See if we have it as a tree...
+           git-cat-file tree "$base" >/dev/null 2>&1 || continue
+
+           rm -fr "$dotest"/patch-merge-* &&
+           mkdir "$dotest/patch-merge-tmp-dir" || break
+           (
+               cd "$dotest/patch-merge-tmp-dir" &&
+               GIT_INDEX_FILE=../patch-merge-tmp-index &&
+               GIT_OBJECT_DIRECTORY="$O_OBJECT" &&
+               export GIT_INDEX_FILE GIT_OBJECT_DIRECTORY &&
+               git-read-tree "$base" &&
+               git-apply --index &&
+               mv ../patch-merge-tmp-index ../patch-merge-index &&
+               echo "$base" >../patch-merge-base
+           ) <"$dotest/patch"  2>/dev/null && break
+       done
+    fi
+
+    test -f "$dotest/patch-merge-index" &&
+    his_tree=$(GIT_INDEX_FILE="$dotest/patch-merge-index" git-write-tree) &&
+    orig_tree=$(cat "$dotest/patch-merge-base") &&
+    rm -fr "$dotest"/patch-merge-* || exit 1
+
+    echo Falling back to patching base and 3-way merge...
+
+    # This is not so wrong.  Depending on which base we picked,
+    # orig_tree may be wildly different from ours, but his_tree
+    # has the same set of wildly different changes in parts the
+    # patch did not touch, so resolve ends up cancelling them,
+    # saying that we reverted all those changes.
+
+    git-merge-resolve $orig_tree -- HEAD $his_tree || {
+           echo Failed to merge in the changes.
+           exit 1
+    }
+}
+
+prec=4
+dotest=.dotest sign= utf8= keep= skip= interactive=
+
+while case "$#" in 0) break;; esac
+do
+       case "$1" in
+       -d=*|--d=*|--do=*|--dot=*|--dote=*|--dotes=*|--dotest=*)
+       dotest=`expr "$1" : '-[^=]*=\(.*\)'`; shift ;;
+       -d|--d|--do|--dot|--dote|--dotes|--dotest)
+       case "$#" in 1) usage ;; esac; shift
+       dotest="$1"; shift;;
+
+       -i|--i|--in|--int|--inte|--inter|--intera|--interac|--interact|\
+       --interacti|--interactiv|--interactive)
+       interactive=t; shift ;;
+
+       -3|--3|--3w|--3wa|--3way)
+       threeway=t; shift ;;
+       -s|--s|--si|--sig|--sign|--signo|--signof|--signoff)
+       sign=t; shift ;;
+       -u|--u|--ut|--utf|--utf8)
+       utf8=t; shift ;;
+       -k|--k|--ke|--kee|--keep)
+       keep=t; shift ;;
+
+       --sk|--ski|--skip)
+       skip=t; shift ;;
+
+       --)
+       shift; break ;;
+       -*)
+       usage ;;
+       *)
+       break ;;
+       esac
+done
+
+if test -d "$dotest" &&
+   last=$(cat "$dotest/last") &&
+   next=$(cat "$dotest/next") &&
+   test $# != 0 &&
+   test "$next" -gt "$last"
+then
+   rm -fr "$dotest"
+fi
+
+if test -d "$dotest"
+then
+       test ",$#," = ",0," ||
+       die "previous dotest directory $dotest still exists but mbox given."
+       resume=yes
+else
+       # Make sure we are not given --skip
+       test ",$skip," = ,, ||
+       die "we are not resuming."
+
+       # Start afresh.
+       mkdir -p "$dotest" || exit
+
+       # cat does the right thing for us, including '-' to mean
+       # standard input.
+       cat "$@" |
+       git-mailsplit -d$prec "$dotest/" >"$dotest/last" || {
+               rm -fr "$dotest"
+               exit 1
+       }
+
+       echo "$sign" >"$dotest/sign"
+       echo "$utf8" >"$dotest/utf8"
+       echo "$keep" >"$dotest/keep"
+       echo 1 >"$dotest/next"
+fi
+
+if test "$(cat "$dotest/utf8")" = t
+then
+       utf8=-u
+fi
+if test "$(cat "$dotest/keep")" = t
+then
+       keep=-k
+fi
+if test "$(cat "$dotest/sign")" = t
+then
+       SIGNOFF=`git-var GIT_COMMITTER_IDENT | sed -e '
+                       s/>.*/>/
+                       s/^/Signed-off-by: /'
+               `
+else
+       SIGNOFF=
+fi
+
+last=`cat "$dotest/last"`
+this=`cat "$dotest/next"`
+if test "$skip" = t
+then
+       this=`expr "$this" + 1`
+fi
+
+if test "$this" -gt "$last"
+then
+       echo Nothing to do.
+       rm -fr "$dotest"
+       exit
+fi
+
+while test "$this" -le "$last"
+do
+       msgnum=`printf "%0${prec}d" $this`
+       next=`expr "$this" + 1`
+       test -f "$dotest/$msgnum" || {
+               go_next
+               continue
+       }
+       case "$resume" in
+       '')
+               git-mailinfo $keep $utf8 "$dotest/msg" "$dotest/patch" \
+                       <"$dotest/$msgnum" >"$dotest/info" ||
+                       stop_here $this
+               git-stripspace < "$dotest/msg" > "$dotest/msg-clean"
+               ;;
+       esac
+       resume=
+
+       GIT_AUTHOR_NAME="$(sed -n '/^Author/ s/Author: //p' "$dotest/info")"
+       GIT_AUTHOR_EMAIL="$(sed -n '/^Email/ s/Email: //p' "$dotest/info")"
+       GIT_AUTHOR_DATE="$(sed -n '/^Date/ s/Date: //p' "$dotest/info")"
+       SUBJECT="$(sed -n '/^Subject/ s/Subject: //p' "$dotest/info")"
+       export GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL GIT_AUTHOR_DATE
+
+       case "$keep_subject" in -k)  SUBJECT="[PATCH] $SUBJECT" ;; esac
+       if test '' != "$SIGNOFF"
+       then
+               LAST_SIGNED_OFF_BY=`
+                       sed -ne '/^Signed-off-by: /p' "$dotest/msg-clean" |
+                       tail -n 1
+               `
+               ADD_SIGNOFF=$(test "$LAST_SIGNED_OFF_BY" = "$SIGNOFF" || {
+                   test '' = "$LAST_SIGNED_OFF_BY" && echo
+                   echo "$SIGNOFF"
+               })
+       else
+               ADD_SIGNOFF=
+       fi
+       {
+               echo "$SUBJECT"
+               if test -s "$dotest/msg-clean"
+               then
+                       echo
+                       cat "$dotest/msg-clean"
+               fi
+               if test '' != "$ADD_SIGNOFF"
+               then
+                       echo "$ADD_SIGNOFF"
+               fi
+       } >"$dotest/final-commit"
+
+       if test "$interactive" = t
+       then
+           test -t 0 ||
+           die "cannot be interactive without stdin connected to a terminal."
+           action=again
+           while test "$action" = again
+           do
+               echo "Commit Body is:"
+               echo "--------------------------"
+               cat "$dotest/final-commit"
+               echo "--------------------------"
+               echo -n "Apply? [y]es/[n]o/[e]dit/[v]iew patch/[a]ccept all "
+               read reply
+               case "$reply" in
+               [yY]*) action=yes ;;
+               [aA]*) action=yes interactive= ;;
+               [nN]*) action=skip ;;
+               [eE]*) "${VISUAL:-${EDITOR:-vi}}" "$dotest/final-commit"
+                      action=again ;;
+               [vV]*) action=again
+                      LESS=-S ${PAGER:-less} "$dotest/patch" ;;
+               *)     action=again ;;
+               esac
+           done
+       else
+           action=yes
+       fi
+
+       if test $action = skip
+       then
+               go_next
+               continue
+       fi
+
+       if test -x "$GIT_DIR"/hooks/applypatch-msg
+       then
+               "$GIT_DIR"/hooks/applypatch-msg "$dotest/final-commit" ||
+               stop_here $this
+       fi
+
+       echo
+       echo "Applying '$SUBJECT'"
+       echo
+
+       git-apply --index "$dotest/patch"; apply_status=$?
+       if test $apply_status = 1 && test "$threeway" = t
+       then
+               if (fall_back_3way)
+               then
+                   # Applying the patch to an earlier tree and merging the
+                   # result may have produced the same tree as ours.
+                   changed="$(git-diff-index --cached --name-only -z HEAD)"
+                   if test '' = "$changed"
+                   then
+                           echo No changes -- Patch already applied.
+                           go_next
+                           continue
+                   fi
+                   # clear apply_status -- we have successfully merged.
+                   apply_status=0
+               fi
+       fi
+       if test $apply_status != 0
+       then
+               echo Patch failed at $msgnum.
+               stop_here $this
+       fi
+
+       if test -x "$GIT_DIR"/hooks/pre-applypatch
+       then
+               "$GIT_DIR"/hooks/pre-applypatch || stop_here $this
+       fi
+
+       tree=$(git-write-tree) &&
+       echo Wrote tree $tree &&
+       parent=$(git-rev-parse --verify HEAD) &&
+       commit=$(git-commit-tree $tree -p $parent <"$dotest/final-commit") &&
+       echo Committed: $commit &&
+       git-update-ref HEAD $commit $parent ||
+       stop_here $this
+
+       if test -x "$GIT_DIR"/hooks/post-applypatch
+       then
+               "$GIT_DIR"/hooks/post-applypatch
+       fi
+
+       go_next
+done
+
+rm -fr "$dotest"
index e2bfd0287057b5eb712018574bef1790993ca743..6de6932879e9f1298f69e2826f13e1ee231ecea0 100755 (executable)
@@ -9,19 +9,19 @@
 ## You give it a mbox-format collection of emails, and it will try to
 ## apply them to the kernel using "applypatch"
 ##
-## applymbox [-u] [-k] [-q] (-c .dotest/msg-number | mail_archive) [Signoff_file]"
-##
 ## The patch application may fail in the middle.  In which case:
 ## (1) look at .dotest/patch and fix it up to apply
 ## (2) re-run applymbox with -c .dotest/msg-number for the current one.
 ## Pay a special attention to the commit log message if you do this and
 ## use a Signoff_file, because applypatch wants to append the sign-off
 ## message to msg-clean every time it is run.
+##
+## git-am is supposed to be the newer and better tool for this job.
 
 . git-sh-setup || die "Not a git archive"
 
 usage () {
-    echo >&2 "applymbox [-u] [-k] [-q] (-c .dotest/<num> | mbox) [signoff]"
+    echo >&2 "applymbox [-u] [-k] [-q] [-m] (-c .dotest/<num> | mbox) [signoff]"
     exit 1
 }
 
@@ -33,6 +33,7 @@ do
        -k)     keep_subject=-k ;;
        -q)     query_apply=t ;;
        -c)     continue="$2"; resume=f; shift ;;
+       -m)     fallback_3way=t ;;
        -*)     usage ;;
        *)      break ;;
        esac
@@ -43,7 +44,8 @@ case "$continue" in
 '')
        rm -rf .dotest
        mkdir .dotest
-       git-mailsplit "$1" .dotest || exit 1
+       num_msgs=$(git-mailsplit "$1" .dotest) || exit 1
+       echo "$num_msgs patch(es) to process."
        shift
 esac
 
@@ -56,6 +58,9 @@ fi
 case "$query_apply" in
 t)     touch .dotest/.query_apply
 esac
+case "$fall_back_3way" in
+t)     : >.dotest/.3way
+esac
 case "$keep_subject" in
 -k)    : >.dotest/.keep_subject
 esac
@@ -80,7 +85,11 @@ do
     do
        git-applypatch .dotest/msg-clean .dotest/patch .dotest/info "$signoff"
        case "$?" in
-       0 | 2 )
+       0)
+               # Remove the cleanly applied one to reduce clutter.
+               rm -f .dotest/$i
+               ;;
+       2)
                # 2 is a special exit code from applypatch to indicate that
                # the patch wasn't applied, but continue anyway 
                ;;
index fd594ed4e48a1aee7f4e9c0a6bbb3a44f1bb2120..66fd19ae2df2e1f44b709a20802342c88d9d2cf2 100755 (executable)
@@ -22,6 +22,8 @@ query_apply=.dotest/.query_apply
 ## if this file exists.
 keep_subject=.dotest/.keep_subject
 
+## We do not attempt the 3-way merge fallback unless this file exists.
+fall_back_3way=.dotest/.3way
 
 MSGFILE=$1
 PATCHFILE=$2
@@ -29,10 +31,10 @@ INFO=$3
 SIGNOFF=$4
 EDIT=${VISUAL:-${EDITOR:-vi}}
 
-export GIT_AUTHOR_NAME="$(sed -n '/^Author/ s/Author: //p' .dotest/info)"
-export GIT_AUTHOR_EMAIL="$(sed -n '/^Email/ s/Email: //p' .dotest/info)"
-export GIT_AUTHOR_DATE="$(sed -n '/^Date/ s/Date: //p' .dotest/info)"
-export SUBJECT="$(sed -n '/^Subject/ s/Subject: //p' .dotest/info)"
+export GIT_AUTHOR_NAME="$(sed -n '/^Author/ s/Author: //p' "$INFO")"
+export GIT_AUTHOR_EMAIL="$(sed -n '/^Email/ s/Email: //p' "$INFO")"
+export GIT_AUTHOR_DATE="$(sed -n '/^Date/ s/Date: //p' "$INFO")"
+export SUBJECT="$(sed -n '/^Subject/ s/Subject: //p' "$INFO")"
 
 if test '' != "$SIGNOFF"
 then
@@ -54,8 +56,10 @@ then
                        sed -ne '/^Signed-off-by: /p' "$MSGFILE" |
                        tail -n 1
                `
-               test "$LAST_SIGNED_OFF_BY" = "$SIGNOFF" ||
-               echo "$SIGNOFF" >>"$MSGFILE"
+               test "$LAST_SIGNED_OFF_BY" = "$SIGNOFF" || {
+                   test '' = "$LAST_SIGNED_OFF_BY" && echo
+                   echo "$SIGNOFF"
+               } >>"$MSGFILE"
        fi
 fi
 
@@ -99,7 +103,81 @@ echo
 echo Applying "'$SUBJECT'"
 echo
 
-git-apply --index "$PATCHFILE" || exit 1
+git-apply --index "$PATCHFILE" || {
+
+       # git-apply exits with status 1 when the patch does not apply,
+       # but it die()s with other failures, most notably upon corrupt
+       # patch.  In the latter case, there is no point to try applying
+       # it to another tree and do 3-way merge.
+       test $? = 1 || exit 1
+
+       test -f "$fall_back_3way" || exit 1
+
+       # Here if we know which revision the patch applies to,
+       # we create a temporary working tree and index, apply the
+       # patch, and attempt 3-way merge with the resulting tree.
+
+       O_OBJECT=`cd "$GIT_OBJECT_DIRECTORY" && pwd`
+       rm -fr .patch-merge-*
+
+       (
+               N=10
+
+               # if the patch records the base tree...
+               sed -ne '
+                       /^diff /q
+                       /^applies-to: \([0-9a-f]*\)$/{
+                               s//\1/p
+                               q
+                       }
+               ' "$PATCHFILE"
+
+               # or hoping the patch is against our recent commits...
+               git-rev-list --max-count=$N HEAD
+
+               # or hoping the patch is against known tags...
+               git-ls-remote --tags .
+       ) |
+       while read base junk
+       do
+               # Try it if we have it as a tree.
+               git-cat-file tree "$base" >/dev/null 2>&1 || continue
+
+               rm -fr .patch-merge-tmp-* &&
+               mkdir .patch-merge-tmp-dir || break
+               (
+                       cd .patch-merge-tmp-dir &&
+                       GIT_INDEX_FILE=../.patch-merge-tmp-index &&
+                       GIT_OBJECT_DIRECTORY="$O_OBJECT" &&
+                       export GIT_INDEX_FILE GIT_OBJECT_DIRECTORY &&
+                       git-read-tree "$base" &&
+                       git-apply --index &&
+                       mv ../.patch-merge-tmp-index ../.patch-merge-index &&
+                       echo "$base" >../.patch-merge-base
+               ) <"$PATCHFILE"  2>/dev/null && break
+       done
+
+       test -f .patch-merge-index &&
+       his_tree=$(GIT_INDEX_FILE=.patch-merge-index git-write-tree) &&
+       orig_tree=$(cat .patch-merge-base) &&
+       rm -fr .patch-merge-* || exit 1
+
+       echo Falling back to patching base and 3-way merge using $orig_tree...
+
+       # This is not so wrong.  Depending on which base we picked,
+       # orig_tree may be wildly different from ours, but his_tree
+       # has the same set of wildly different changes in parts the
+       # patch did not touch, so resolve ends up cancelling them,
+       # saying that we reverted all those changes.
+
+       if git-merge-resolve $orig_tree -- HEAD $his_tree
+       then
+               echo Done.
+       else
+               echo Failed to merge in the changes.
+               exit 1
+       fi
+}
 
 if test -x "$GIT_DIR"/hooks/pre-applypatch
 then
@@ -108,9 +186,10 @@ fi
 
 tree=$(git-write-tree) || exit 1
 echo Wrote tree $tree
-commit=$(git-commit-tree $tree -p $(cat "$GIT_DIR"/HEAD) < "$final") || exit 1
+parent=$(git-rev-parse --verify HEAD) &&
+commit=$(git-commit-tree $tree -p $parent <"$final") || exit 1
 echo Committed: $commit
-echo $commit > "$GIT_DIR"/HEAD
+git-update-ref HEAD $commit $parent || exit
 
 if test -x "$GIT_DIR"/hooks/post-applypatch
 then
index 3749b8b5728f7bdb0f6adb317b25ecfa81902042..980e827b27ae0fa9a07667501d4cb26b5673cedf 100755 (executable)
@@ -228,10 +228,12 @@ END
     # skip commits already in repo
     #
     if (ptag($ps->{id})) {
-      $opt_v && print "Skipping already imported: $ps->{id}\n";
+      $opt_v && print " * Skipping already imported: $ps->{id}\n";
       next;
     }
 
+    print " * Starting to work on $ps->{id}\n";
+
     # 
     # create the branch if needed
     #
@@ -675,6 +677,10 @@ sub find_parents {
     # that branch.
     #
     foreach my $branch (keys %branches) {
+
+       # check that we actually know about the branch
+       next unless -e "$git_dir/refs/heads/$branch";
+
        my $mergebase = `git-merge-base $branch $ps->{branch}`;
        die "Cannot find merge base for $branch and $ps->{branch}" if $?;
        chomp $mergebase;
index 8dc77c991c0e8c5a618383052306f9ef1a018352..1ab2f187dcfd1ab9b81bbc3ebb3605601b4d29f0 100755 (executable)
@@ -38,7 +38,8 @@ bisect_start() {
        # Verify HEAD. If we were bisecting before this, reset to the
        # top-of-line master first!
        #
-       head=$(readlink $GIT_DIR/HEAD) || die "Bad HEAD - I need a symlink"
+       head=$(GIT_DIR="$GIT_DIR" git-symbolic-ref HEAD) ||
+       die "Bad HEAD - I need a symbolic ref"
        case "$head" in
        refs/heads/bisect*)
                git checkout master || exit
@@ -46,7 +47,7 @@ bisect_start() {
        refs/heads/*)
                ;;
        *)
-               die "Bad HEAD - strange symlink"
+               die "Bad HEAD - strange symbolic ref"
                ;;
        esac
 
@@ -135,7 +136,7 @@ bisect_next() {
        echo "$rev" > "$GIT_DIR/refs/heads/new-bisect"
        git checkout new-bisect || exit
        mv "$GIT_DIR/refs/heads/new-bisect" "$GIT_DIR/refs/heads/bisect" &&
-       ln -sf refs/heads/bisect "$GIT_DIR/HEAD"
+       GIT_DIR="$GIT_DIR" git-symbolic-ref HEAD refs/heads/bisect
        git-show-branch "$rev"
 }
 
index dcec2a9f2f0e379cff152cb70ddf6c2c942d1c99..67f113acb9abc2bbcd444de42326854bdd88ba4a 100755 (executable)
@@ -13,37 +13,42 @@ If two arguments, create a new branch <branchname> based off of <start-point>.
 }
 
 delete_branch () {
-    option="$1" branch_name="$2"
-    headref=$(readlink "$GIT_DIR/HEAD" | sed -e 's|^refs/heads/||')
-    case ",$headref," in
-    ",$branch_name,")
-       die "Cannot delete the branch you are on." ;;
-    ,,)
-       die "What branch are you on anyway?" ;;
-    esac
-    branch=$(cat "$GIT_DIR/refs/heads/$branch_name") &&
-       branch=$(git-rev-parse --verify "$branch^0") ||
-           die "Seriously, what branch are you talking about?"
-    case "$option" in
-    -D)
-       ;;
-    *)
-       mbs=$(git-merge-base -a "$branch" HEAD | tr '\012' ' ')
-       case " $mbs " in
-       *' '$branch' '*)
-           # the merge base of branch and HEAD contains branch --
-           # which means that the HEAD contains everything in the HEAD.
+    option="$1"
+    shift
+    headref=$(GIT_DIR="$GIT_DIR" git-symbolic-ref HEAD |
+              sed -e 's|^refs/heads/||')
+    for branch_name
+    do
+       case ",$headref," in
+       ",$branch_name,")
+           die "Cannot delete the branch you are on." ;;
+       ,,)
+           die "What branch are you on anyway?" ;;
+       esac
+       branch=$(cat "$GIT_DIR/refs/heads/$branch_name") &&
+           branch=$(git-rev-parse --verify "$branch^0") ||
+               die "Seriously, what branch are you talking about?"
+       case "$option" in
+       -D)
            ;;
        *)
-           echo >&2 "The branch '$branch_name' is not a strict subset of your current HEAD.
-If you are sure you want to delete it, run 'git branch -D $branch_name'."
-           exit 1
+           mbs=$(git-merge-base -a "$branch" HEAD | tr '\012' ' ')
+           case " $mbs " in
+           *' '$branch' '*)
+               # the merge base of branch and HEAD contains branch --
+               # which means that the HEAD contains everything in the HEAD.
+               ;;
+           *)
+               echo >&2 "The branch '$branch_name' is not a strict subset of your current HEAD.
+    If you are sure you want to delete it, run 'git branch -D $branch_name'."
+               exit 1
+               ;;
+           esac
            ;;
        esac
-       ;;
-    esac
-    rm -f "$GIT_DIR/refs/heads/$branch_name"
-    echo "Deleted branch $branch_name."
+       rm -f "$GIT_DIR/refs/heads/$branch_name"
+       echo "Deleted branch $branch_name."
+    done
     exit 0
 }
 
@@ -51,7 +56,7 @@ while case "$#,$1" in 0,*) break ;; *,-*) ;; *) break ;; esac
 do
        case "$1" in
        -d | -D)
-               delete_branch "$1" "$2"
+               delete_branch "$@"
                exit
                ;;
        --)
@@ -67,7 +72,8 @@ done
 
 case "$#" in
 0)
-       headref=$(readlink "$GIT_DIR/HEAD" | sed -e 's|^refs/heads/||')
+       headref=$(GIT_DIR="$GIT_DIR" git-symbolic-ref HEAD |
+                 sed -e 's|^refs/heads/||')
        git-rev-parse --symbolic --all |
        sed -ne 's|^refs/heads/||p' |
        sort |
@@ -91,6 +97,11 @@ branchname="$1"
 
 rev=$(git-rev-parse --verify "$head") || exit
 
-[ -e "$GIT_DIR/refs/heads/$branchname" ] && die "$branchname already exists"
+[ -e "$GIT_DIR/refs/heads/$branchname" ] &&
+       die "$branchname already exists."
+git-check-ref-format "heads/$branchname" ||
+       die "we do not like '$branchname' as a branch name."
 
+leading=`expr "refs/heads/$branchname" : '\(.*\)/'` &&
+mkdir -p "$GIT_DIR/$leading" &&
 echo $rev > "$GIT_DIR/refs/heads/$branchname"
index 37afcdda309ca8df52bb5033e5e7f0b771dfa067..4c08f36b591508b5d940384db603e2f4483116d6 100755 (executable)
@@ -1,6 +1,10 @@
 #!/bin/sh
 . git-sh-setup || die "Not a git archive"
 
+usage () {
+    die "usage: git checkout [-f] [-b <new_branch>] [<branch>] [<paths>...]"
+}
+
 old=$(git-rev-parse HEAD)
 new=
 force=
@@ -17,36 +21,90 @@ while [ "$#" != "0" ]; do
                        die "git checkout: -b needs a branch name"
                [ -e "$GIT_DIR/refs/heads/$newbranch" ] &&
                        die "git checkout: branch $newbranch already exists"
+               git-check-ref-format "heads/$newbranch" ||
+                       die "we do not like '$newbranch' as a branch name."
                ;;
        "-f")
                force=1
                ;;
+       --)
+               break
+               ;;
+       -*)
+               usage
+               ;;
        *)
-               rev=$(git-rev-parse --verify "$arg^0" 2>/dev/null) ||
-                       die "I don't know any '$arg'."
-               if [ -z "$rev" ]; then
-                       echo "unknown flag $arg"
-                       exit 1
-               fi
-               if [ "$new" ]; then
-                       echo "Multiple revisions?"
-                       exit 1
-               fi
-               new="$rev"
-               if [ -f "$GIT_DIR/refs/heads/$arg" ]; then
-                       branch="$arg"
+               if rev=$(git-rev-parse --verify "$arg^0" 2>/dev/null)
+               then
+                       if [ -z "$rev" ]; then
+                               echo "unknown flag $arg"
+                               exit 1
+                       fi
+                       new="$rev"
+                       if [ -f "$GIT_DIR/refs/heads/$arg" ]; then
+                               branch="$arg"
+                       fi
+               elif rev=$(git-rev-parse --verify "$arg^{tree}" 2>/dev/null)
+               then
+                       # checking out selected paths from a tree-ish.
+                       new="$rev"
+                       branch=
+               else
+                       new=
+                       branch=
+                       set x "$arg" "$@"
+                       shift
                fi
+               break
                ;;
     esac
 done
-[ -z "$new" ] && new=$old
 
+# The behaviour of the command with and without explicit path
+# parameters is quite different.
+#
+# Without paths, we are checking out everything in the work tree,
+# possibly switching branches.  This is the traditional behaviour.
 #
+# With paths, we are _never_ switching branch, but checking out
+# the named paths from either index (when no rev is given),
+# or the named tree-ish (when rev is given).
+
+if test "$#" -ge 1
+then
+       if test '' != "$newbranch$force"
+       then
+               die "updating paths and switching branches or forcing are incompatible."
+       fi
+       if test '' != "$new"
+       then
+               # from a specific tree-ish; note that this is for
+               # rescuing paths and is never meant to remove what
+               # is not in the named tree-ish.
+               git-ls-tree -r "$new" "$@" |
+               sed -ne 's/^\([0-7]*\) blob \(.*\)$/\1 \2/p' |
+               git-update-index --index-info || exit $?
+       fi
+       git-checkout-index -f -u -- "$@"
+       exit $?
+else
+       # Make sure we did not fall back on $arg^{tree} codepath
+       # since we are not checking out from an arbitrary tree-ish,
+       # but switching branches.
+       if test '' != "$new"
+       then
+               git-rev-parse --verify "$new^{commit}" >/dev/null 2>&1 ||
+               die "Cannot switch branch to a non-commit."
+       fi
+fi
+
+[ -z "$new" ] && new=$old
+
 # If we don't have an old branch that we're switching to,
 # and we don't have a new branch name for the target we
 # are switching to, then we'd better just be checking out
 # what we already had
-#
+
 [ -z "$branch$newbranch" ] &&
        [ "$new" != "$old" ] &&
        die "git checkout: you need to specify a new branch name"
@@ -68,10 +126,13 @@ fi
 #
 if [ "$?" -eq 0 ]; then
        if [ "$newbranch" ]; then
-               echo $new > "$GIT_DIR/refs/heads/$newbranch"
+               leading=`expr "refs/heads/$newbranch" : '\(.*\)/'` &&
+               mkdir -p "$GIT_DIR/$leading" &&
+               echo $new >"$GIT_DIR/refs/heads/$newbranch" || exit
                branch="$newbranch"
        fi
-       [ "$branch" ] && ln -sf "refs/heads/$branch" "$GIT_DIR/HEAD"
+       [ "$branch" ] &&
+       GIT_DIR="$GIT_DIR" git-symbolic-ref HEAD "refs/heads/$branch"
        rm -f "$GIT_DIR/MERGE_HEAD"
 else
        exit 1
index 44135f489d55c29eb2e7dce1b892471c55b9914b..4fdd6525148d50bd3a014bfc0f60eefd0f16efb9 100755 (executable)
@@ -9,7 +9,7 @@
 unset CDPATH
 
 usage() {
-       echo >&2 "* git clone [-l [-s]] [-q] [-u <upload-pack>] <repo> <dir>"
+       echo >&2 "* git clone [-l [-s]] [-q] [-u <upload-pack>] [-n] <repo> <dir>"
        exit 1
 }
 
@@ -53,7 +53,11 @@ Perhaps git-update-server-info needs to be run there?"
        while read sha1 refname
        do
                name=`expr "$refname" : 'refs/\(.*\)'` &&
-               git-http-fetch -v -a -w "$name" "$name" "$1/" || exit 1
+               case "$name" in
+               *^*)    ;;
+               *)
+                       git-http-fetch -v -a -w "$name" "$name" "$1/" || exit 1
+               esac
        done <"$clone_tmp/refs"
        rm -fr "$clone_tmp"
 }
@@ -61,10 +65,12 @@ Perhaps git-update-server-info needs to be run there?"
 quiet=
 use_local=no
 local_shared=no
+no_checkout=
 upload_pack=
 while
        case "$#,$1" in
        0,*) break ;;
+       *,-n) no_checkout=yes ;;
        *,-l|*,--l|*,--lo|*,--loc|*,--loca|*,--local) use_local=yes ;;
         *,-s|*,--s|*,--sh|*,--sha|*,--shar|*,--share|*,--shared) 
           local_shared=yes ;;
@@ -120,8 +126,7 @@ yes,yes)
            fi &&
            rm -f "$D/.git/objects/sample" &&
            cd "$repo" &&
-           find objects -type f -print |
-           cpio -puamd$l "$D/.git/" || exit 1
+           find objects -depth -print | cpio -puamd$l "$D/.git/" || exit 1
            ;;
        yes)
            mkdir -p "$D/.git/objects/info"
@@ -139,7 +144,8 @@ yes,yes)
        then
                HEAD=HEAD
        fi
-       tar Ccf "$repo" - refs $HEAD | tar Cxf "$D/.git" - || exit 1
+       (cd "$repo" && tar cf - refs $HEAD) |
+       (cd "$D/.git" && tar xf -) || exit 1
        ;;
 *)
        case "$repo" in
@@ -185,9 +191,31 @@ yes,yes)
        ;;
 esac
 
-# Update origin.
-mkdir -p "$D/.git/remotes/" &&
-rm -f "$D/.git/remotes/origin" &&
-echo >"$D/.git/remotes/origin" \
-"URL: $repo
-Pull: master:origin"
+cd $D || exit
+
+if test -f ".git/HEAD"
+then
+       head_points_at=`git-symbolic-ref HEAD`
+       case "$head_points_at" in
+       refs/heads/*)
+               head_points_at=`expr "$head_points_at" : 'refs/heads/\(.*\)'`
+               mkdir -p .git/remotes &&
+               echo >.git/remotes/origin \
+               "URL: $repo
+Pull: $head_points_at:origin" &&
+               cp ".git/refs/heads/$head_points_at" .git/refs/heads/origin &&
+               find .git/refs/heads -type f -print |
+               while read ref
+               do
+                       head=`expr "$ref" : '.git/refs/heads/\(.*\)'` &&
+                       test "$head_points_at" = "$head" ||
+                       test "origin" = "$head" ||
+                       echo "Pull: ${head}:${head}"
+               done >>.git/remotes/origin
+       esac
+
+       case "$no_checkout" in
+       '')
+               git checkout
+       esac
+fi
index d8bfc3c254929d3fb7a8aa702b4f4a86edefe8f0..daf90f1e58242f069e2fde5f44677f60a98e2a0c 100755 (executable)
@@ -6,7 +6,7 @@
 . git-sh-setup || die "Not a git archive"
 
 usage () {
-       die 'git commit [-a] [-v | --no-verify]  [-m <message>] [-F <logfile>] [(-C|-c) <commit>] [<path>...]'
+       die 'git commit [-a] [-s] [-v | --no-verify]  [-m <message> | -F <logfile> | (-C|-c) <commit>] [-e] [<path>...]'
 }
 
 all= logfile= use_commit= no_edit= log_given= log_message= verify=t signoff=
@@ -94,13 +94,13 @@ esac
 case "$all,$#" in
 t,*)
        git-diff-files --name-only -z |
-       xargs -0 git-update-index -q --remove --
+       git-update-index --remove -z --stdin
        ;;
 ,0)
        ;;
 *)
-       git-diff-files --name-only -z "$@" |
-       xargs -0 git-update-index -q --remove --
+       git-diff-files --name-only -z -- "$@" |
+       git-update-index --remove -z --stdin
        ;;
 esac || exit 1
 git-update-index -q --refresh || exit 1
@@ -129,14 +129,20 @@ then
 elif test "$use_commit" != ""
 then
        git-cat-file commit "$use_commit" | sed -e '1,/^$/d'
-fi | git-stripspace >.editmsg
+elif test -f "$GIT_DIR/MERGE_HEAD" && test -f "$GIT_DIR/MERGE_MSG"
+then
+       cat "$GIT_DIR/MERGE_MSG"
+fi | git-stripspace >"$GIT_DIR"/COMMIT_EDITMSG
 
 case "$signoff" in
 t)
-       git-var GIT_COMMITTER_IDENT | sed -e '
-               s/>.*/>/
-               s/^/Signed-off-by: /
-       ' >>.editmsg
+       {
+               echo
+               git-var GIT_COMMITTER_IDENT | sed -e '
+                       s/>.*/>/
+                       s/^/Signed-off-by: /
+               '
+       } >>"$GIT_DIR"/COMMIT_EDITMSG
        ;;
 esac
 
@@ -147,16 +153,11 @@ if [ -f "$GIT_DIR/MERGE_HEAD" ]; then
        echo "# $GIT_DIR/MERGE_HEAD"
        echo "# and try again"
        echo "#"
-fi >>.editmsg
+fi >>"$GIT_DIR"/COMMIT_EDITMSG
 
 PARENTS="-p HEAD"
-if [ ! -r "$GIT_DIR/HEAD" ]; then
-       if [ -z "$(git-ls-files)" ]; then
-               echo Nothing to commit 1>&2
-               exit 1
-       fi
-       PARENTS=""
-else
+if GIT_DIR="$GIT_DIR" git-rev-parse --verify HEAD >/dev/null 2>&1
+then
        if [ -f "$GIT_DIR/MERGE_HEAD" ]; then
                PARENTS="-p HEAD "`sed -e 's/^/-p /' "$GIT_DIR/MERGE_HEAD"`
        fi
@@ -189,17 +190,23 @@ else
                export GIT_AUTHOR_EMAIL
                export GIT_AUTHOR_DATE
        fi
+else
+       if [ -z "$(git-ls-files)" ]; then
+               echo Nothing to commit 1>&2
+               exit 1
+       fi
+       PARENTS=""
 fi
-git-status >>.editmsg
-if [ "$?" != "0" -a ! -f $GIT_DIR/MERGE_HEAD ]
+git-status >>"$GIT_DIR"/COMMIT_EDITMSG
+if [ "$?" != "0" -a ! -f "$GIT_DIR/MERGE_HEAD" ]
 then
-       rm -f .editmsg
+       rm -f "$GIT_DIR/COMMIT_EDITMSG"
        git-status
        exit 1
 fi
 case "$no_edit" in
 '')
-       ${VISUAL:-${EDITOR:-vi}} .editmsg
+       ${VISUAL:-${EDITOR:-vi}} "$GIT_DIR/COMMIT_EDITMSG"
        ;;
 esac
 
@@ -207,24 +214,28 @@ case "$verify" in
 t)
        if test -x "$GIT_DIR"/hooks/commit-msg
        then
-               "$GIT_DIR"/hooks/commit-msg .editmsg || exit
+               "$GIT_DIR"/hooks/commit-msg "$GIT_DIR"/COMMIT_EDITMSG || exit
        fi
 esac
 
-grep -v '^#' < .editmsg | git-stripspace > .cmitmsg
-grep -v -i '^Signed-off-by' .cmitmsg >.cmitchk
-if test -s .cmitchk
+grep -v '^#' < "$GIT_DIR"/COMMIT_EDITMSG |
+git-stripspace > "$GIT_DIR"/COMMIT_MSG
+
+if cnt=`grep -v -i '^Signed-off-by' "$GIT_DIR"/COMMIT_MSG |
+       git-stripspace |
+       wc -l` &&
+   test 0 -lt $cnt
 then
        tree=$(git-write-tree) &&
-       commit=$(cat .cmitmsg | git-commit-tree $tree $PARENTS) &&
-       echo $commit > "$GIT_DIR/HEAD" &&
+       commit=$(cat "$GIT_DIR"/COMMIT_MSG | git-commit-tree $tree $PARENTS) &&
+       git-update-ref HEAD $commit $current &&
        rm -f -- "$GIT_DIR/MERGE_HEAD"
 else
        echo >&2 "* no commit message?  aborting commit."
        false
 fi
 ret="$?"
-rm -f .cmitmsg .editmsg .cmitchk
+rm -f "$GIT_DIR/COMMIT_MSG" "$GIT_DIR/COMMIT_EDITMSG"
 
 if test -x "$GIT_DIR"/hooks/post-commit && test "$ret" = 0
 then
index 61beebd61c7cafad20b27ecf2bd787414b4001ee..5240dd2c296482a7ae5486e7688ef8ca944197e0 100644 (file)
@@ -9,7 +9,7 @@ URL:            http://kernel.org/pub/software/scm/git/
 Source:        http://kernel.org/pub/software/scm/git/%{name}-%{version}.tar.gz
 BuildRequires: zlib-devel, openssl-devel, curl-devel  %{!?_without_docs:, xmlto, asciidoc > 6.0.3}
 BuildRoot:     %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n)
-Requires:      rsync, rcs, curl, less, openssh-clients, python >= 2.3, tk
+Requires:      zlib >= 1.2, rsync, rcs, curl, less, openssh-clients, python >= 2.3, tk >= 8.4
 
 %description
 This is a stupid (but extremely fast) directory content manager.  It
@@ -23,12 +23,12 @@ elsewhere for tools for ordinary humans layered on top of this.
 %setup -q
 
 %build
-make COPTS="$RPM_OPT_FLAGS" WITH_OWN_SUBPROCESS_PY=YesPlease \
+make %{_smp_mflags} CFLAGS="$RPM_OPT_FLAGS" WITH_OWN_SUBPROCESS_PY=YesPlease \
      prefix=%{_prefix} all %{!?_without_docs: doc}
 
 %install
 rm -rf $RPM_BUILD_ROOT
-make DESTDIR=$RPM_BUILD_ROOT WITH_OWN_SUBPROCESS_PY=YesPlease \
+make %{_smp_mflags} DESTDIR=$RPM_BUILD_ROOT WITH_OWN_SUBPROCESS_PY=YesPlease \
      prefix=%{_prefix} mandir=%{_mandir} \
      install %{!?_without_docs: install-doc}
 
@@ -45,6 +45,10 @@ rm -rf $RPM_BUILD_ROOT
 %{!?_without_docs: %{_mandir}/man7/*.7*}
 
 %changelog
+* Tue Sep 27 2005 H. Peter Anvin <hpa@zytor.com>
+- parallelize build
+- COPTS -> CFLAGS
+
 * Fri Sep 16 2005 Chris Wright <chrisw@osdl.org> 0.99.6-1
 - update to 0.99.6
 
index 74ee4f371f749cb5ae9fdb7e789d341f60997e62..843d2fd9f2ef53ccd1600dd9b89ec70fbc639146 100755 (executable)
@@ -2,7 +2,7 @@
 
 . git-sh-setup
 
-echo $(find "$GIT_DIR/objects"/?? -type f -print | wc -l) objects, \
+echo $(find "$GIT_DIR/objects"/?? -type f -print 2>/dev/null | wc -l) objects, \
 $({
     echo 0
     # "no-such" is to help Darwin folks by not using xargs -r.
index 565f4f1b32c8b9d96df3602ab2d729cf3529836a..7bd9136205f46d4334b7272e6617c0f7f553f5e8 100755 (executable)
@@ -29,7 +29,7 @@
 $SIG{'PIPE'}="IGNORE";
 $ENV{'TZ'}="UTC";
 
-our($opt_h,$opt_o,$opt_v,$opt_k,$opt_u,$opt_d,$opt_p,$opt_C,$opt_z,$opt_i,$opt_s,$opt_m,$opt_M);
+our($opt_h,$opt_o,$opt_v,$opt_k,$opt_u,$opt_d,$opt_p,$opt_C,$opt_z,$opt_i,$opt_P, $opt_s,$opt_m,$opt_M);
 
 sub usage() {
        print STDERR <<END;
@@ -41,7 +41,7 @@ END
        exit(1);
 }
 
-getopts("hivmkuo:d:p:C:z:s:M:") or usage();
+getopts("hivmkuo:d:p:C:z:s:M:P:") or usage();
 usage if $opt_h;
 
 @ARGV <= 1 or usage();
@@ -337,6 +337,10 @@ sub file {
        }
        close ($fh);
 
+       if ($res eq '') {
+           die "Looks like the server has gone away while fetching $fn $rev -- exiting!";
+       }
+
        return ($name, $res);
 }
 
@@ -487,8 +491,16 @@ ($$)
        my @opt;
        @opt = split(/,/,$opt_p) if defined $opt_p;
        unshift @opt, '-z', $opt_z if defined $opt_z;
-       exec("cvsps",@opt,"-u","-A","--cvs-direct",'--root',$opt_d,$cvs_tree);
-       die "Could not start cvsps: $!\n";
+       unshift @opt, '-q'         unless defined $opt_v;
+       unless (defined($opt_p) && $opt_p =~ m/--no-cvs-direct/) {
+               push @opt, '--cvs-direct';
+       }
+       if ($opt_P) {
+           exec("cat", $opt_P);
+       } else {
+           exec("cvsps",@opt,"-u","-A",'--root',$opt_d,$cvs_tree);
+           die "Could not start cvsps: $!\n";
+       }
 }
 
 
@@ -510,7 +522,7 @@ ($$)
 
 my $state = 0;
 
-my($patchset,$date,$author,$branch,$ancestor,$tag,$logmsg);
+my($patchset,$date,$author_name,$author_email,$branch,$ancestor,$tag,$logmsg);
 my(@old,@new);
 my $commit = sub {
        my $pid;
@@ -567,6 +579,7 @@ ($$)
        unless($pid) {
                $pr->writer();
                $pw->reader();
+               open(OUT,">&STDOUT");
                dup2($pw->fileno(),0);
                dup2($pr->fileno(),1);
                $pr->close();
@@ -584,18 +597,17 @@ ($$)
                                if ( -e "$git_dir/refs/heads/$mparent") {
                                        $mparent = get_headref($mparent, $git_dir);
                                        push @par, '-p', $mparent;
-                                       # printing here breaks import # 
-                                       # # print "Merge parent branch: $mparent\n" if $opt_v;
+                                       print OUT "Merge parent branch: $mparent\n" if $opt_v;
                                }
-                       } 
+                       }
                }
 
                exec("env",
-                       "GIT_AUTHOR_NAME=$author",
-                       "GIT_AUTHOR_EMAIL=$author",
+                       "GIT_AUTHOR_NAME=$author_name",
+                       "GIT_AUTHOR_EMAIL=$author_email",
                        "GIT_AUTHOR_DATE=".strftime("+0000 %Y-%m-%d %H:%M:%S",gmtime($date)),
-                       "GIT_COMMITTER_NAME=$author",
-                       "GIT_COMMITTER_EMAIL=$author",
+                       "GIT_COMMITTER_NAME=$author_name",
+                       "GIT_COMMITTER_EMAIL=$author_email",
                        "GIT_COMMITTER_DATE=".strftime("+0000 %Y-%m-%d %H:%M:%S",gmtime($date)),
                        "git-commit-tree", $tree,@par);
                die "Cannot exec git-commit-tree: $!\n";
@@ -638,7 +650,7 @@ ($$)
                print $out "object $cid\n".
                    "type commit\n".
                    "tag $xtag\n".
-                   "tagger $author <$author>\n"
+                   "tagger $author_name <$author_email>\n"
                    or die "Cannot create tag object $xtag: $!\n";
                close($out)
                    or die "Cannot create tag object $xtag: $!\n";
@@ -683,7 +695,11 @@ ($$)
                $state=3;
        } elsif($state == 3 and s/^Author:\s+//) {
                s/\s+$//;
-               $author = $_;
+               if (/^(.*?)\s+<(.*)>/) {
+                   ($author_name, $author_email) = ($1, $2);
+               } else {
+                   $author_name = $author_email = $_;
+               }
                $state = 4;
        } elsif($state == 4 and s/^Branch:\s+//) {
                s/\s+$//;
index 84a152af206166e88701b27acdc7d2b033e90bab..b3ec84be698311b30e8c7c793873e6caa41daf71 100755 (executable)
@@ -28,16 +28,16 @@ case "$rev" in
 ?*' '^?*)
        begin=$(expr "$rev" : '.*^.\([0-9a-f]*\).*') &&
        end=$(expr "$rev" : '.\([0-9a-f]*\). .*') || exit
-       cmd="git-diff-tree $flags $begin $end $files"
+       cmd="git-diff-tree $flags $begin $end -- $files"
        ;;
 ?*' '?*)
-       cmd="git-diff-tree $flags $rev $files"
+       cmd="git-diff-tree $flags $rev -- $files"
        ;;
 ?*' ')
-       cmd="git-diff-index $flags $rev $files"
+       cmd="git-diff-index $flags $rev -- $files"
        ;;
 '')
-       cmd="git-diff-files $flags $files"
+       cmd="git-diff-files $flags -- $files"
        ;;
 *)
        die "I don't understand $*"
diff --git a/git-external-diff-script b/git-external-diff-script
deleted file mode 100755 (executable)
index 137280a..0000000
+++ /dev/null
@@ -1,67 +0,0 @@
-#!/bin/sh
-# Copyright (C) 2005 Junio C Hamano
-#
-# This script is designed to emulate what the built-in diff driver
-# does when set as GIT_EXTERNAL_SCRIPT.
-
-case "$#" in
-1)
-    echo "* Unmerged path $1"
-    exit 0 ;;
-*)
-    name1="$1" tmp1="$2" hex1="$3" mode1="$4" tmp2="$5" hex2="$6" mode2="$7"
-    case "$#" in
-    7)
-       name2="$name1" ;;
-    9)
-       name2="$8" xfrm_msg="$9" ;;
-    esac ;;    
-esac
-
-show_create () {
-    name_="$1" tmp_="$2" hex_="$3" mode_="$4"
-    echo "diff --git a/$name_ b/$name_"
-    echo "new file mode $mode_"
-    diff ${GIT_DIFF_OPTS-'-pu'} -L /dev/null -L "b/$name_" /dev/null "$tmp_"
-}
-
-show_delete () {
-    name_="$1" tmp_="$2" hex_="$3" mode_="$4"
-    echo "diff --git a/$name_ b/$name_"
-    echo "deleted file mode $mode_"
-    diff ${GIT_DIFF_OPTS-'-pu'} -L "a/$name_" -L /dev/null "$tmp_" /dev/null
-}
-
-case "$mode1" in
-120*) type1=l ;;
-100*) type1=f ;;
-.)    show_create "$name2" "$tmp2" "$hex2" "$mode2"
-      exit 0 ;;
-esac
-case "$mode2" in
-120*) type2=l ;;
-100*) type2=f ;;
-.)    show_delete "$name1" "$tmp1" "$hex1" "$mode1"
-      exit 0 ;;
-esac
-
-if test "$type1" != "$type2"
-then
-       show_delete "$name1" "$tmp1" "$hex1" "$mode1"
-       show_create "$name2" "$tmp2" "$hex2" "$mode2"
-       exit 0
-fi
-
-echo diff --git "a/$name1" "b/$name2"
-if test "$mode1" != "$mode2"
-then
-    echo "old mode $mode1"
-    echo "new mode $mode2"
-    if test "$xfrm_msg" != ""
-    then
-       echo "$xfrm_msg"
-    fi
-fi
-diff ${GIT_DIFF_OPTS-'-pu'} -L "a/$name1" -L "b/$name2" "$tmp1" "$tmp2"
-exit 0
-
index 822b4cd982c46feaeed9b94b16973b63a0c567c1..31e5f4c7225df3d279b78f67ebd0f7a8c87ede40 100755 (executable)
@@ -5,6 +5,11 @@
 _x40='[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]'
 _x40="$_x40$_x40$_x40$_x40$_x40$_x40$_x40$_x40"
 
+LF='
+'
+IFS="$LF"
+
+tags=
 append=
 force=
 update_head_ok=
@@ -17,6 +22,9 @@ do
        -f|--f|--fo|--for|--forc|--force)
                force=t
                ;;
+       -t|--t|--ta|--tag|--tags)
+               tags=t
+               ;;
        -u|--u|--up|--upd|--upda|--updat|--update|--update-|--update-h|\
        --update-he|--update-hea|--update-head|--update-head-|\
        --update-head-o|--update-head-ok)
@@ -45,7 +53,7 @@ rsync_slurped_objects=
 
 if test "" = "$append"
 then
-       : >$GIT_DIR/FETCH_HEAD
+       : >"$GIT_DIR/FETCH_HEAD"
 fi
 
 append_fetch_head () {
@@ -54,6 +62,10 @@ append_fetch_head () {
     remote_name_="$3"
     remote_nick_="$4"
     local_name_="$5"
+    case "$6" in
+    t) not_for_merge_='not-for-merge' ;;
+    '') not_for_merge_= ;;
+    esac
 
     # remote-nick is the URL given on the command line (or a shorthand)
     # remote-name is the $GIT_DIR relative refs/ path we computed
@@ -78,10 +90,11 @@ append_fetch_head () {
     if git-cat-file commit "$head_" >/dev/null 2>&1
     then
        headc_=$(git-rev-parse --verify "$head_^0") || exit
-       echo "$headc_   $note_" >>$GIT_DIR/FETCH_HEAD
+       echo "$headc_   $not_for_merge_ $note_" >>"$GIT_DIR/FETCH_HEAD"
        echo >&2 "* committish: $head_"
        echo >&2 "  $note_"
     else
+       echo "$head_    not-for-merge   $note_" >>"$GIT_DIR/FETCH_HEAD"
        echo >&2 "* non-commit: $head_"
        echo >&2 "  $note_"
     fi
@@ -101,18 +114,25 @@ fast_forward_local () {
        # is no way to guarantee "fast-forward" anyway.
        if test -f "$GIT_DIR/$1"
        then
-               echo >&2 "* $1: updating with $3"
+               if now_=$(cat "$GIT_DIR/$1") && test "$now_" = "$2"
+               then
+                       echo >&2 "* $1: same as $3"
+               else
+                       echo >&2 "* $1: updating with $3"
+               fi
        else
                echo >&2 "* $1: storing $3"
        fi
-       echo "$2" >"$GIT_DIR/$1" ;;
+       git-update-ref "$1" "$2" 
+       ;;
 
     refs/heads/*)
-       # NEEDSWORK: use the same cmpxchg protocol here.
-       echo "$2" >"$GIT_DIR/$1.lock"
-       if test -f "$GIT_DIR/$1"
+       # $1 is the ref being updated.
+       # $2 is the new value for the ref.
+       local=$(git-rev-parse --verify "$1^0" 2>/dev/null)
+       if test "$local"
        then
-           local=$(git-rev-parse --verify "$1^0") &&
+           # Require fast-forward.
            mb=$(git-merge-base "$local" "$2") &&
            case "$2,$mb" in
            $local,*)
@@ -120,43 +140,73 @@ fast_forward_local () {
                ;;
            *,$local)
                echo >&2 "* $1: fast forward to $3"
+               git-update-ref "$1" "$2" "$local"
                ;;
            *)
                false
                ;;
            esac || {
                echo >&2 "* $1: does not fast forward to $3;"
-               case "$force,$single_force" in
-               t,* | *,t)
+               case ",$force,$single_force," in
+               *,t,*)
                        echo >&2 "  forcing update."
+                       git-update-ref "$1" "$2" "$local"
                        ;;
                *)
-                       mv "$GIT_DIR/$1.lock" "$GIT_DIR/$1.remote"
-                       echo >&2 "  leaving it in '$1.remote'"
+                       echo >&2 "  not updating."
                        ;;
                esac
            }
        else
-               echo >&2 "* $1: storing $3"
+           echo >&2 "* $1: storing $3"
+           git-update-ref "$1" "$2"
        fi
-       test -f "$GIT_DIR/$1.lock" &&
-           mv "$GIT_DIR/$1.lock" "$GIT_DIR/$1"
        ;;
     esac
 }
 
 case "$update_head_ok" in
 '')
-       orig_head=$(cat "$GIT_DIR/HEAD" 2>/dev/null)
+       orig_head=$(git-rev-parse --verify HEAD 2>/dev/null)
        ;;
 esac
 
-for ref in $(get_remote_refs_for_fetch "$@")
+# If --tags (and later --heads or --all) is specified, then we are
+# not talking about defaults stored in Pull: line of remotes or
+# branches file, and just fetch those and refspecs explicitly given.
+# Otherwise we do what we always did.
+
+reflist=$(get_remote_refs_for_fetch "$@")
+if test "$tags"
+then
+       taglist=$(git-ls-remote --tags "$remote" |
+               sed -e '
+                       /\^/d
+                       s/^[^   ]*      //
+                       s/.*/&:&/')
+       if test "$#" -gt 1
+       then
+               # remote URL plus explicit refspecs; we need to merge them.
+               reflist="$reflist$LF$taglist"
+       else
+               # No explicit refspecs; fetch tags only.
+               reflist=$taglist
+       fi
+fi
+
+for ref in $reflist
 do
-    refs="$refs $ref"
+    refs="$refs$LF$ref"
 
     # These are relative path from $GIT_DIR, typically starting at refs/
     # but may be HEAD
+    if expr "$ref" : '\.' >/dev/null
+    then
+       not_for_merge=t
+       ref=$(expr "$ref" : '\.\(.*\)')
+    else
+       not_for_merge=
+    fi
     if expr "$ref" : '\+' >/dev/null
     then
        single_force=t
@@ -167,7 +217,7 @@ do
     remote_name=$(expr "$ref" : '\([^:]*\):')
     local_name=$(expr "$ref" : '[^:]*:\(.*\)')
 
-    rref="$rref $remote_name"
+    rref="$rref$LF$remote_name"
 
     # There are transports that can fetch only one head at a time...
     case "$remote" in
@@ -175,16 +225,21 @@ do
        if [ -n "$GIT_SSL_NO_VERIFY" ]; then
            curl_extra_args="-k"
        fi
-       head=$(curl -nsf $curl_extra_args "$remote/$remote_name") &&
+       remote_name_quoted=$(perl -e '
+           my $u = $ARGV[0];
+           $u =~ s{([^-a-zA-Z0-9/.])}{sprintf"%%%02x",ord($1)}eg;
+           print "$u";
+       ' "$remote_name")
+       head=$(curl -nsf $curl_extra_args "$remote/$remote_name_quoted") &&
        expr "$head" : "$_x40\$" >/dev/null ||
                die "Failed to fetch $remote_name from $remote"
-       echo Fetching "$remote_name from $remote" using http
+       echo >&2 Fetching "$remote_name from $remote" using http
        git-http-fetch -v -a "$head" "$remote/" || exit
        ;;
     rsync://*)
        TMP_HEAD="$GIT_DIR/TMP_HEAD"
        rsync -L -q "$remote/$remote_name" "$TMP_HEAD" || exit 1
-       head=$(git-rev-parse TMP_HEAD)
+       head=$(git-rev-parse --verify TMP_HEAD)
        rm -f "$TMP_HEAD"
        test "$rsync_slurped_objects" || {
            rsync -av --ignore-existing --exclude info \
@@ -216,7 +271,8 @@ do
        continue ;;
     esac
 
-    append_fetch_head "$head" "$remote" "$remote_name" "$remote_nick" "$local_name"
+    append_fetch_head "$head" "$remote" \
+       "$remote_name" "$remote_nick" "$local_name" "$not_for_merge"
 
 done
 
@@ -224,6 +280,7 @@ case "$remote" in
 http://* | https://* | rsync://* )
     ;; # we are already done.
 *)
+    IFS="      $LF"
     (
        git-fetch-pack "$remote" $rref || echo failed "$remote"
     ) |
@@ -241,16 +298,27 @@ http://* | https://* | rsync://* )
            case "$ref" in
            +$remote_name:*)
                single_force=t
+               not_for_merge=
+               found="$ref"
+               break ;;
+           .+$remote_name:*)
+               single_force=t
+               not_for_merge=t
+               found="$ref"
+               break ;;
+           .$remote_name:*)
+               not_for_merge=t
                found="$ref"
                break ;;
            $remote_name:*)
+               not_for_merge=
                found="$ref"
                break ;;
            esac
        done
-
        local_name=$(expr "$found" : '[^:]*:\(.*\)')
-       append_fetch_head "$sha1" "$remote" "$remote_name" "$remote_nick" "$local_name"
+       append_fetch_head "$sha1" "$remote" \
+               "$remote_name" "$remote_nick" "$local_name" "$not_for_merge"
     done || exit
     ;;
 esac
@@ -261,10 +329,10 @@ case ",$update_head_ok,$orig_head," in
 *,, | t,* )
        ;;
 *)
-       curr_head=$(cat "$GIT_DIR/HEAD" 2>/dev/null)
+       curr_head=$(git-rev-parse --verify HEAD 2>/dev/null)
        if test "$curr_head" != "$orig_head"
        then
-               echo "$orig_head" >$GIT_DIR/HEAD
+               git-update-ref HEAD "$orig_head"
                die "Cannot fetch into the current branch."
        fi
        ;;
diff --git a/git-fmt-merge-msg.perl b/git-fmt-merge-msg.perl
new file mode 100755 (executable)
index 0000000..778388e
--- /dev/null
@@ -0,0 +1,95 @@
+#!/usr/bin/perl -w
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+# Read .git/FETCH_HEAD and make a human readable merge message
+# by grouping branches and tags together to form a single line.
+
+use strict;
+
+my @src;
+my %src;
+sub andjoin {
+       my ($label, $labels, $stuff) = @_;
+       my $l = scalar @$stuff;
+       my $m = '';
+       if ($l == 0) {
+               return ();
+       }
+       if ($l == 1) {
+               $m = "$label$stuff->[0]";
+       }
+       else {
+               $m = ("$labels" .
+                     join (', ', @{$stuff}[0..$l-2]) .
+                     " and $stuff->[-1]");
+       }
+       return ($m);
+}
+
+while (<>) {
+       my ($bname, $tname, $gname, $src);
+       chomp;
+       s/^[0-9a-f]*    //;
+       next if (/^not-for-merge/);
+       s/^     //;
+       if (s/ of (.*)$//) {
+               $src = $1;
+       } else {
+               # Pulling HEAD
+               $src = $_;
+               $_ = 'HEAD';
+       }
+       if (! exists $src{$src}) {
+               push @src, $src;
+               $src{$src} = {
+                       BRANCH => [],
+                       TAG => [],
+                       GENERIC => [],
+                       # &1 == has HEAD.
+                       # &2 == has others.
+                       HEAD_STATUS => 0,
+               };
+       }
+       if (/^branch (.*)$/) {
+               push @{$src{$src}{BRANCH}}, $1;
+               $src{$src}{HEAD_STATUS} |= 2;
+       }
+       elsif (/^tag (.*)$/) {
+               push @{$src{$src}{TAG}}, $1;
+               $src{$src}{HEAD_STATUS} |= 2;
+       }
+       elsif (/^HEAD$/) {
+               $src{$src}{HEAD_STATUS} |= 1;
+       }
+       else {
+               push @{$src{$src}{GENERIC}}, $_;
+               $src{$src}{HEAD_STATUS} |= 2;
+       }
+}
+
+my @msg;
+for my $src (@src) {
+       if ($src{$src}{HEAD_STATUS} == 1) {
+               # Only HEAD is fetched, nothing else.
+               push @msg, $src;
+               next;
+       }
+       my @this;
+       if ($src{$src}{HEAD_STATUS} == 3) {
+               # HEAD is fetched among others.
+               push @this, andjoin('', '', ['HEAD']);
+       }
+       push @this, andjoin("branch ", "branches ",
+                          $src{$src}{BRANCH});
+       push @this, andjoin("tag ", "tags ",
+                          $src{$src}{TAG});
+       push @this, andjoin("commit ", "commits ",
+                           $src{$src}{GENERIC});
+       my $this = join(', ', @this);
+       if ($src ne '.') {
+               $this .= " of $src";
+       }
+       push @msg, $this;
+}
+print "Merge ", join("; ", @msg), "\n";
index 525a2f22126cfead751c30310ffe2b2fd971fdae..b43ba3909c200b84e23d678408a0bf876fd3f3c6 100755 (executable)
@@ -6,7 +6,9 @@
 . git-sh-setup || die "Not a git archive."
 
 usage () {
-    echo >&2 "usage: $0"' [-n] [-o dir] [--keep-subject] [--mbox] [--check] [--signoff] [-<diff options>...] upstream [ our-head ]
+    echo >&2 "usage: $0"' [-n] [-o dir | --stdout] [--keep-subject] [--mbox]
+    [--check] [--signoff] [-<diff options>...]
+    ( from..to ... | upstream [ our-head ] )
 
 Prepare each commit with its patch since our-head forked from upstream,
 one file per patch, for e-mail submission.  Each output file is
@@ -49,6 +51,8 @@ do
     numbered=t ;;
     -s|--s|--si|--sig|--sign|--signo|--signof|--signoff)
     signoff=t ;;
+    --st|--std|--stdo|--stdou|--stdout)
+    stdout=t mbox=t date=t author=t ;;
     -o=*|--o=*|--ou=*|--out=*|--outp=*|--outpu=*|--output=*|--output-=*|\
     --output-d=*|--output-di=*|--output-dir=*|--output-dire=*|\
     --output-direc=*|--output-direct=*|--output-directo=*|\
@@ -73,25 +77,77 @@ tt)
        die '--keep-subject and --numbered are incompatible.' ;;
 esac
 
-rev1= rev2=
-case "$#" in
-2)
-    rev1="$1" rev2="$2" ;;
-1)
-    case "$1" in
-    *..*)
-       rev1=`expr "$1" : '\(.*\)\.\.'`
-       rev2=`expr "$1" : '.*\.\.\(.*\)'`
+tmp=.tmp-series$$
+trap 'rm -f $tmp-*' 0 1 2 3 15
+
+series=$tmp-series
+commsg=$tmp-commsg
+filelist=$tmp-files
+
+# Backward compatible argument parsing hack.
+#
+# Historically, we supported:
+# 1. "rev1"            is equivalent to "rev1..HEAD"
+# 2. "rev1..rev2"
+# 3. "rev1" "rev2      is equivalent to "rev1..rev2"
+#
+# We want to take a sequence of "rev1..rev2" in general.
+# Also, "rev1.." should mean "rev1..HEAD"; git-diff users are
+# familiar with that syntax.
+
+case "$#,$1" in
+1,?*..?*)
+       # single "rev1..rev2"
        ;;
-    *)
-        rev1="$1"
-       rev2="HEAD"
+1,?*..)
+       # single "rev1.." should mean "rev1..HEAD"
+       set x "$1"HEAD
+       shift
+       ;;
+1,*)
+       # single rev1
+       set x "$1..HEAD"
+       shift
+       ;;
+2,?*..?*)
+       # not traditional "rev1" "rev2"
+       ;;
+2,*)
+       set x "$1..$2"
+       shift
        ;;
-    esac ;;
-*)
-    usage ;;
 esac
 
+# Now we have what we want in $@
+for revpair
+do
+       case "$revpair" in
+       ?*..?*)
+               rev1=`expr "$revpair" : '\(.*\)\.\.'`
+               rev2=`expr "$revpair" : '.*\.\.\(.*\)'`
+               ;;
+       *)
+               usage
+               ;;
+       esac
+       git-rev-parse --verify "$rev1^0" >/dev/null 2>&1 ||
+               die "Not a valid rev $rev1 ($revpair)"
+       git-rev-parse --verify "$rev2^0" >/dev/null 2>&1 ||
+               die "Not a valid rev $rev2 ($revpair)"
+       git-cherry -v "$rev1" "$rev2" |
+       while read sign rev comment
+       do
+               case "$sign" in
+               '-')
+                       echo >&2 "Merged already: $comment"
+                       ;;
+               *)
+                       echo $rev
+                       ;;
+               esac
+       done
+done >$series
+
 me=`git-var GIT_AUTHOR_IDENT | sed -e 's/>.*/>/'`
 
 case "$outdir" in
@@ -100,13 +156,6 @@ case "$outdir" in
 esac
 test -d "$outdir" || mkdir -p "$outdir" || exit
 
-tmp=.tmp-series$$
-trap 'rm -f $tmp-*' 0 1 2 3 15
-
-series=$tmp-series
-commsg=$tmp-commsg
-filelist=$tmp-files
-
 titleScript='
        /./d
        /^$/n
@@ -128,42 +177,7 @@ whosepatchScript='
        q
 }'
 
-_x40='[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]'
-_x40="$_x40$_x40$_x40$_x40$_x40$_x40$_x40$_x40"
-stripCommitHead='/^'"$_x40"' (from '"$_x40"')$/d'
-
-git-cherry -v "$rev1" "$rev2" |
-while read sign rev comment
-do
-       case "$sign" in
-       '-')
-               echo >&2 "Merged already: $comment"
-               ;;
-       *)
-               echo $rev
-               ;;
-       esac
-done >$series
-
-total=`wc -l <$series | tr -dc "[0-9]"`
-i=1
-while read commit
-do
-    git-cat-file commit "$commit" | git-stripspace >$commsg
-    title=`sed -ne "$titleScript" <$commsg`
-    case "$numbered" in
-    '') num= ;;
-    *)
-       case $total in
-       1) num= ;;
-       *) num=' '`printf "%d/%d" $i $total` ;;
-       esac
-    esac
-
-    file=`printf '%04d-%stxt' $i "$title"`
-    i=`expr "$i" + 1`
-    echo "* $file"
-    {
+process_one () {
        mailScript='
        /./d
        /^$/n'
@@ -182,6 +196,7 @@ do
            echo 'From nobody Mon Sep 17 00:00:00 2001' ;# UNIX "From" line
            ;;
        esac
+
        eval "$(sed -ne "$whosepatchScript" $commsg)"
        test "$author,$au" = ",$me" || {
                mailScript="$mailScript"'
@@ -200,7 +215,9 @@ Date: '"$ad"
        n
        b body'
 
-       sed -ne "$mailScript" <$commsg
+       (cat $commsg ; echo; echo) |
+       sed -ne "$mailScript" |
+       git-stripspace
 
        test "$signoff" = "t" && {
                offsigner=`git-var GIT_COMMITTER_IDENT | sed -e 's/>.*/>/'`
@@ -216,21 +233,49 @@ Date: '"$ad"
        echo
        git-diff-tree -p $diff_opts "$commit" | git-apply --stat --summary
        echo
-       git-diff-tree -p $diff_opts "$commit" | sed -e "$stripCommitHead"
+       git-cat-file commit "$commit^" | sed -e 's/^tree /applies-to: /' -e q
+       git-diff-tree -p $diff_opts "$commit"
+       echo "---"
+       echo "@@GIT_VERSION@@"
 
        case "$mbox" in
        t)
                echo
                ;;
        esac
-    } >"$outdir$file"
-    case "$check" in
-    t)
-       # This is slightly modified from Andrew Morton's Perfect Patch.
-       # Lines you introduce should not have trailing whitespace.
-       # Also check for an indentation that has SP before a TAB.
-        grep -n '^+\([         ]*      .*\|.*[         ]\)$' "$outdir$file"
-
-       : do not exit with non-zero because we saw no problem in the last one.
+}
+
+total=`wc -l <$series | tr -dc "[0-9]"`
+i=1
+while read commit
+do
+    git-cat-file commit "$commit" | git-stripspace >$commsg
+    title=`sed -ne "$titleScript" <$commsg`
+    case "$numbered" in
+    '') num= ;;
+    *)
+       case $total in
+       1) num= ;;
+       *) num=' '`printf "%d/%d" $i $total` ;;
+       esac
     esac
+
+    file=`printf '%04d-%stxt' $i "$title"`
+    if test '' = "$stdout"
+    then
+           echo "* $file"
+           process_one >"$outdir$file"
+           if test t = "$check"
+           then
+               # This is slightly modified from Andrew Morton's Perfect Patch.
+               # Lines you introduce should not have trailing whitespace.
+               # Also check for an indentation that has SP before a TAB.
+               grep -n '^+\([  ]*      .*\|.*[         ]\)$' "$outdir$file"
+               :
+           fi
+    else
+           echo >&2 "* $file"
+           process_one
+    fi
+    i=`expr "$i" + 1`
 done <$series
index bfbd5a4d5a1bb35f9f52e3a495d1830b76078a7c..f0f0b07f6f8c85219104303d65dabbba80fd3098 100755 (executable)
@@ -1,6 +1,6 @@
 #!/bin/sh
 #
-. git-sh-setup || die "Not a git archive"
+. git-sh-setup
 
 usage () {
     echo >&2 "usage: $0 [--heads] [--tags] <repository> <refs>..."
diff --git a/git-merge-ours.sh b/git-merge-ours.sh
new file mode 100755 (executable)
index 0000000..4f3d053
--- /dev/null
@@ -0,0 +1,14 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+# Pretend we resolved the heads, but declare our tree trumps everybody else.
+#
+
+# We need to exit with 2 if the index does not match our HEAD tree,
+# because the current index is what we will be committing as the
+# merge result.
+
+test "$(git-diff-index --cached --name-status HEAD)" = "" || exit 2
+
+exit 0
index 60e8b21b3b640868b027ba909a4221a5d1bdbf90..626d85493a64d798dedbdc52b2b0f68d56d447fd 100755 (executable)
@@ -4,12 +4,9 @@
 from heapq import heappush, heappop
 from sets import Set
 
-sys.path.append('@@GIT_PYTHON_PATH@@')
+sys.path.append('''@@GIT_PYTHON_PATH@@''')
 from gitMergeCommon import *
 
-# The actual merge code
-# ---------------------
-
 originalIndexFile = os.environ.get('GIT_INDEX_FILE',
                                    os.environ.get('GIT_DIR', '.git') + '/index')
 temporaryIndexFile = os.environ.get('GIT_DIR', '.git') + \
@@ -21,11 +18,23 @@ def setupIndex(temporary):
         pass
     if temporary:
         newIndex = temporaryIndexFile
-        os.environ
     else:
         newIndex = originalIndexFile
     os.environ['GIT_INDEX_FILE'] = newIndex
 
+# This is a global variable which is used in a number of places but
+# only written to in the 'merge' function.
+
+# cacheOnly == True  => Don't leave any non-stage 0 entries in the cache and
+#                       don't update the working directory.
+#              False => Leave unmerged entries in the cache and update
+#                       the working directory.
+
+cacheOnly = False
+
+# The entry point to the merge code
+# ---------------------------------
+
 def merge(h1, h2, branch1Name, branch2Name, graph, callDepth=0):
     '''Merge the commits h1 and h2, return the resulting virtual
     commit object and a flag indicating the cleaness of the merge.'''
@@ -35,6 +44,7 @@ def merge(h1, h2, branch1Name, branch2Name, graph, callDepth=0):
     def infoMsg(*args):
         sys.stdout.write('  '*callDepth)
         printList(args)
+
     infoMsg('Merging:')
     infoMsg(h1)
     infoMsg(h2)
@@ -46,27 +56,27 @@ def infoMsg(*args):
         infoMsg(x)
     sys.stdout.flush()
 
-    Ms = ca[0]
+    mergedCA = ca[0]
     for h in ca[1:]:
-        [Ms, ignore] = merge(Ms, h,
-                             'Temporary shared merge branch 1',
-                             'Temporary shared merge branch 2',
-                             graph, callDepth+1)
-        assert(isinstance(Ms, Commit))
+        [mergedCA, dummy] = merge(mergedCA, h,
+                                  'Temporary shared merge branch 1',
+                                  'Temporary shared merge branch 2',
+                                  graph, callDepth+1)
+        assert(isinstance(mergedCA, Commit))
 
+    global cacheOnly
     if callDepth == 0:
         setupIndex(False)
-        cleanCache = False
+        cacheOnly = False
     else:
         setupIndex(True)
         runProgram(['git-read-tree', h1.tree()])
-        cleanCache = True
+        cacheOnly = True
 
-    [shaRes, clean] = mergeTrees(h1.tree(), h2.tree(), Ms.tree(),
-                                 branch1Name, branch2Name,
-                                 cleanCache)
+    [shaRes, clean] = mergeTrees(h1.tree(), h2.tree(), mergedCA.tree(),
+                                 branch1Name, branch2Name)
 
-    if clean or cleanCache:
+    if clean or cacheOnly:
         res = Commit(None, [h1, h2], tree=shaRes)
         graph.addNode(res)
     else:
@@ -89,16 +99,255 @@ def getFilesAndDirs(tree):
 
     return [files, dirs]
 
+# Those two global variables are used in a number of places but only
+# written to in 'mergeTrees' and 'uniquePath'. They keep track of
+# every file and directory in the two branches that are about to be
+# merged.
+currentFileSet = None
+currentDirectorySet = None
+
+def mergeTrees(head, merge, common, branch1Name, branch2Name):
+    '''Merge the trees 'head' and 'merge' with the common ancestor
+    'common'. The name of the head branch is 'branch1Name' and the name of
+    the merge branch is 'branch2Name'. Return a tuple (tree, cleanMerge)
+    where tree is the resulting tree and cleanMerge is True iff the
+    merge was clean.'''
+    
+    assert(isSha(head) and isSha(merge) and isSha(common))
+
+    if common == merge:
+        print 'Already uptodate!'
+        return [head, True]
+
+    if cacheOnly:
+        updateArg = '-i'
+    else:
+        updateArg = '-u'
+
+    [out, code] = runProgram(['git-read-tree', updateArg, '-m',
+                                common, head, merge], returnCode = True)
+    if code != 0:
+        die('git-read-tree:', out)
+
+    [tree, code] = runProgram('git-write-tree', returnCode=True)
+    tree = tree.rstrip()
+    if code != 0:
+        global currentFileSet, currentDirectorySet
+        [currentFileSet, currentDirectorySet] = getFilesAndDirs(head)
+        [filesM, dirsM] = getFilesAndDirs(merge)
+        currentFileSet.union_update(filesM)
+        currentDirectorySet.union_update(dirsM)
+
+        entries = unmergedCacheEntries()
+        renamesHead =  getRenames(head, common, head, merge, entries)
+        renamesMerge = getRenames(merge, common, head, merge, entries)
+
+        cleanMerge = processRenames(renamesHead, renamesMerge,
+                                    branch1Name, branch2Name)
+        for entry in entries:
+            if entry.processed:
+                continue
+            if not processEntry(entry, branch1Name, branch2Name):
+                cleanMerge = False
+                
+        if cleanMerge or cacheOnly:
+            tree = runProgram('git-write-tree').rstrip()
+        else:
+            tree = None
+    else:
+        cleanMerge = True
+
+    return [tree, cleanMerge]
+
+# Low level file merging, update and removal
+# ------------------------------------------
+
+def mergeFile(oPath, oSha, oMode, aPath, aSha, aMode, bPath, bSha, bMode,
+              branch1Name, branch2Name):
+
+    merge = False
+    clean = True
+
+    if stat.S_IFMT(aMode) != stat.S_IFMT(bMode):
+        clean = False
+        if stat.S_ISREG(aMode):
+            mode = aMode
+            sha = aSha
+        else:
+            mode = bMode
+            sha = bSha
+    else:
+        if aSha != oSha and bSha != oSha:
+            merge = True
+
+        if aMode == oMode:
+            mode = bMode
+        else:
+            mode = aMode
+
+        if aSha == oSha:
+            sha = bSha
+        elif bSha == oSha:
+            sha = aSha
+        elif stat.S_ISREG(aMode):
+            assert(stat.S_ISREG(bMode))
+
+            orig = runProgram(['git-unpack-file', oSha]).rstrip()
+            src1 = runProgram(['git-unpack-file', aSha]).rstrip()
+            src2 = runProgram(['git-unpack-file', bSha]).rstrip()
+            [out, code] = runProgram(['merge',
+                                      '-L', branch1Name + '/' + aPath,
+                                      '-L', 'orig/' + oPath,
+                                      '-L', branch2Name + '/' + bPath,
+                                      src1, orig, src2], returnCode=True)
+
+            sha = runProgram(['git-hash-object', '-t', 'blob', '-w',
+                              src1]).rstrip()
+
+            os.unlink(orig)
+            os.unlink(src1)
+            os.unlink(src2)
+            
+            clean = (code == 0)
+        else:
+            assert(stat.S_ISLNK(aMode) and stat.S_ISLNK(bMode))
+            sha = aSha
+
+            if aSha != bSha:
+                clean = False
+
+    return [sha, mode, clean, merge]
+
+def updateFile(clean, sha, mode, path):
+    updateCache = cacheOnly or clean
+    updateWd = not cacheOnly
+
+    return updateFileExt(sha, mode, path, updateCache, updateWd)
+
+def updateFileExt(sha, mode, path, updateCache, updateWd):
+    if cacheOnly:
+        updateWd = False
+
+    if updateWd:
+        pathComponents = path.split('/')
+        for x in xrange(1, len(pathComponents)):
+            p = '/'.join(pathComponents[0:x])
+
+            try:
+                createDir = not stat.S_ISDIR(os.lstat(p).st_mode)
+            except: 
+                createDir = True
+            
+            if createDir:
+                try:
+                    os.mkdir(p)
+                except OSError, e:
+                    die("Couldn't create directory", p, e.strerror)
+
+        prog = ['git-cat-file', 'blob', sha]
+        if stat.S_ISREG(mode):
+            try:
+                os.unlink(path)
+            except OSError:
+                pass
+            if mode & 0100:
+                mode = 0777
+            else:
+                mode = 0666
+            fd = os.open(path, os.O_WRONLY | os.O_TRUNC | os.O_CREAT, mode)
+            proc = subprocess.Popen(prog, stdout=fd)
+            proc.wait()
+            os.close(fd)
+        elif stat.S_ISLNK(mode):
+            linkTarget = runProgram(prog)
+            os.symlink(linkTarget, path)
+        else:
+            assert(False)
+
+    if updateWd and updateCache:
+        runProgram(['git-update-index', '--add', '--', path])
+    elif updateCache:
+        runProgram(['git-update-index', '--add', '--cacheinfo',
+                    '0%o' % mode, sha, path])
+
+def removeFile(clean, path):
+    updateCache = cacheOnly or clean
+    updateWd = not cacheOnly
+
+    if updateCache:
+        runProgram(['git-update-index', '--force-remove', '--', path])
+
+    if updateWd:
+        try:
+            os.unlink(path)
+        except OSError, e:
+            if e.errno != errno.ENOENT and e.errno != errno.EISDIR:
+                raise
+
+def uniquePath(path, branch):
+    def fileExists(path):
+        try:
+            os.lstat(path)
+            return True
+        except OSError, e:
+            if e.errno == errno.ENOENT:
+                return False
+            else:
+                raise
+
+    newPath = path + '_' + branch
+    suffix = 0
+    while newPath in currentFileSet or \
+          newPath in currentDirectorySet  or \
+          fileExists(newPath):
+        suffix += 1
+        newPath = path + '_' + branch + '_' + str(suffix)
+    currentFileSet.add(newPath)
+    return newPath
+
+# Cache entry management
+# ----------------------
+
 class CacheEntry:
     def __init__(self, path):
         class Stage:
             def __init__(self):
                 self.sha1 = None
                 self.mode = None
+
+            # Used for debugging only
+            def __str__(self):
+                if self.mode != None:
+                    m = '0%o' % self.mode
+                else:
+                    m = 'None'
+
+                if self.sha1:
+                    sha1 = self.sha1
+                else:
+                    sha1 = 'None'
+                return 'sha1: ' + sha1 + ' mode: ' + m
         
-        self.stages = [Stage(), Stage(), Stage()]
+        self.stages = [Stage(), Stage(), Stage(), Stage()]
         self.path = path
+        self.processed = False
+
+    def __str__(self):
+        return 'path: ' + self.path + ' stages: ' + repr([str(x) for x in self.stages])
+
+class CacheEntryContainer:
+    def __init__(self):
+        self.entries = {}
 
+    def add(self, entry):
+        self.entries[entry.path] = entry
+
+    def get(self, path):
+        return self.entries.get(path)
+
+    def __iter__(self):
+        return self.entries.itervalues()
+    
 unmergedRE = re.compile(r'^([0-7]+) ([0-9a-f]{40}) ([1-3])\t(.*)$', re.S)
 def unmergedCacheEntries():
     '''Create a dictionary mapping file names to CacheEntry
@@ -108,152 +357,340 @@ def unmergedCacheEntries():
     lines = runProgram(['git-ls-files', '-z', '--unmerged']).split('\0')
     lines.pop()
 
-    res = {}
+    res = CacheEntryContainer()
     for l in lines:
         m = unmergedRE.match(l)
         if m:
             mode = int(m.group(1), 8)
             sha1 = m.group(2)
-            stage = int(m.group(3)) - 1
+            stage = int(m.group(3))
             path = m.group(4)
 
-            if res.has_key(path):
-                e = res[path]
-            else:
+            e = res.get(path)
+            if not e:
                 e = CacheEntry(path)
-                res[path] = e
-                
+                res.add(e)
+
             e.stages[stage].mode = mode
             e.stages[stage].sha1 = sha1
         else:
-            die('Error: Merge program failed: Unexpected output from', \
+            die('Error: Merge program failed: Unexpected output from',
                 'git-ls-files:', l)
     return res
 
-def mergeTrees(head, merge, common, branch1Name, branch2Name,
-               cleanCache):
-    '''Merge the trees 'head' and 'merge' with the common ancestor
-    'common'. The name of the head branch is 'branch1Name' and the name of
-    the merge branch is 'branch2Name'. Return a tuple (tree, cleanMerge)
-    where tree is the resulting tree and cleanMerge is True iff the
-    merge was clean.'''
+lsTreeRE = re.compile(r'^([0-7]+) (\S+) ([0-9a-f]{40})\t(.*)\n$', re.S)
+def getCacheEntry(path, origTree, aTree, bTree):
+    '''Returns a CacheEntry object which doesn't have to correspond to
+    a real cache entry in Git's index.'''
     
-    assert(isSha(head) and isSha(merge) and isSha(common))
+    def parse(out):
+        if out == '':
+            return [None, None]
+        else:
+            m = lsTreeRE.match(out)
+            if not m:
+                die('Unexpected output from git-ls-tree:', out)
+            elif m.group(2) == 'blob':
+                return [m.group(3), int(m.group(1), 8)]
+            else:
+                return [None, None]
 
-    if common == merge:
-        print 'Already uptodate!'
-        return [head, True]
+    res = CacheEntry(path)
 
-    if cleanCache:
-        updateArg = '-i'
-    else:
-        updateArg = '-u'
+    [oSha, oMode] = parse(runProgram(['git-ls-tree', origTree, '--', path]))
+    [aSha, aMode] = parse(runProgram(['git-ls-tree', aTree, '--', path]))
+    [bSha, bMode] = parse(runProgram(['git-ls-tree', bTree, '--', path]))
 
-    runProgram(['git-read-tree', updateArg, '-m', common, head, merge])
-    cleanMerge = True
+    res.stages[1].sha1 = oSha
+    res.stages[1].mode = oMode
+    res.stages[2].sha1 = aSha
+    res.stages[2].mode = aMode
+    res.stages[3].sha1 = bSha
+    res.stages[3].mode = bMode
 
-    [tree, code] = runProgram('git-write-tree', returnCode=True)
-    tree = tree.rstrip()
-    if code != 0:
-        [files, dirs] = getFilesAndDirs(head)
-        [filesM, dirsM] = getFilesAndDirs(merge)
-        files.union_update(filesM)
-        dirs.union_update(dirsM)
-        
-        cleanMerge = True
-        entries = unmergedCacheEntries()
-        for name in entries:
-            if not processEntry(entries[name], branch1Name, branch2Name,
-                                files, dirs, cleanCache):
-                cleanMerge = False
-                
-        if cleanMerge or cleanCache:
-            tree = runProgram('git-write-tree').rstrip()
+    return res
+
+# Rename detection and handling
+# -----------------------------
+
+class RenameEntry:
+    def __init__(self,
+                 src, srcSha, srcMode, srcCacheEntry,
+                 dst, dstSha, dstMode, dstCacheEntry,
+                 score):
+        self.srcName = src
+        self.srcSha = srcSha
+        self.srcMode = srcMode
+        self.srcCacheEntry = srcCacheEntry
+        self.dstName = dst
+        self.dstSha = dstSha
+        self.dstMode = dstMode
+        self.dstCacheEntry = dstCacheEntry
+        self.score = score
+
+        self.processed = False
+
+class RenameEntryContainer:
+    def __init__(self):
+        self.entriesSrc = {}
+        self.entriesDst = {}
+
+    def add(self, entry):
+        self.entriesSrc[entry.srcName] = entry
+        self.entriesDst[entry.dstName] = entry
+
+    def getSrc(self, path):
+        return self.entriesSrc.get(path)
+
+    def getDst(self, path):
+        return self.entriesDst.get(path)
+
+    def __iter__(self):
+        return self.entriesSrc.itervalues()
+
+parseDiffRenamesRE = re.compile('^:([0-7]+) ([0-7]+) ([0-9a-f]{40}) ([0-9a-f]{40}) R([0-9]*)$')
+def getRenames(tree, oTree, aTree, bTree, cacheEntries):
+    '''Get information of all renames which occured between 'oTree' and
+    'tree'. We need the three trees in the merge ('oTree', 'aTree' and
+    'bTree') to be able to associate the correct cache entries with
+    the rename information. 'tree' is always equal to either aTree or bTree.'''
+
+    assert(tree == aTree or tree == bTree)
+    inp = runProgram(['git-diff-tree', '-M', '--diff-filter=R', '-r',
+                      '-z', oTree, tree])
+
+    ret = RenameEntryContainer()
+    try:
+        recs = inp.split("\0")
+        recs.pop() # remove last entry (which is '')
+        it = recs.__iter__()
+        while True:
+            rec = it.next()
+            m = parseDiffRenamesRE.match(rec)
+
+            if not m:
+                die('Unexpected output from git-diff-tree:', rec)
+
+            srcMode = int(m.group(1), 8)
+            dstMode = int(m.group(2), 8)
+            srcSha = m.group(3)
+            dstSha = m.group(4)
+            score = m.group(5)
+            src = it.next()
+            dst = it.next()
+
+            srcCacheEntry = cacheEntries.get(src)
+            if not srcCacheEntry:
+                srcCacheEntry = getCacheEntry(src, oTree, aTree, bTree)
+                cacheEntries.add(srcCacheEntry)
+
+            dstCacheEntry = cacheEntries.get(dst)
+            if not dstCacheEntry:
+                dstCacheEntry = getCacheEntry(dst, oTree, aTree, bTree)
+                cacheEntries.add(dstCacheEntry)
+
+            ret.add(RenameEntry(src, srcSha, srcMode, srcCacheEntry,
+                                dst, dstSha, dstMode, dstCacheEntry,
+                                score))
+    except StopIteration:
+        pass
+    return ret
+
+def fmtRename(src, dst):
+    srcPath = src.split('/')
+    dstPath = dst.split('/')
+    path = []
+    endIndex = min(len(srcPath), len(dstPath)) - 1
+    for x in range(0, endIndex):
+        if srcPath[x] == dstPath[x]:
+            path.append(srcPath[x])
         else:
-            tree = None
+            endIndex = x
+            break
+
+    if len(path) > 0:
+        return '/'.join(path) + \
+               '/{' + '/'.join(srcPath[endIndex:]) + ' => ' + \
+               '/'.join(dstPath[endIndex:]) + '}'
     else:
-        cleanMerge = True
+        return src + ' => ' + dst
 
-    return [tree, cleanMerge]
+def processRenames(renamesA, renamesB, branchNameA, branchNameB):
+    srcNames = Set()
+    for x in renamesA:
+        srcNames.add(x.srcName)
+    for x in renamesB:
+        srcNames.add(x.srcName)
 
-def processEntry(entry, branch1Name, branch2Name, files, dirs, cleanCache):
-    '''Merge one cache entry. 'files' is a Set with the files in both of
-    the heads that we are going to merge. 'dirs' contains the
-    corresponding data for directories. If 'cleanCache' is True no
-    non-zero stages will be left in the cache for the path
-    corresponding to the entry 'entry'.'''
+    cleanMerge = True
+    for path in srcNames:
+        if renamesA.getSrc(path):
+            renames1 = renamesA
+            renames2 = renamesB
+            branchName1 = branchNameA
+            branchName2 = branchNameB
+        else:
+            renames1 = renamesB
+            renames2 = renamesA
+            branchName1 = branchNameB
+            branchName2 = branchNameA
+        
+        ren1 = renames1.getSrc(path)
+        ren2 = renames2.getSrc(path)
+
+        ren1.dstCacheEntry.processed = True
+        ren1.srcCacheEntry.processed = True
+
+        if ren1.processed:
+            continue
+
+        ren1.processed = True
+        removeFile(True, ren1.srcName)
+        if ren2:
+            # Renamed in 1 and renamed in 2
+            assert(ren1.srcName == ren2.srcName)
+            ren2.dstCacheEntry.processed = True
+            ren2.processed = True
+
+            if ren1.dstName != ren2.dstName:
+                print 'CONFLICT (rename/rename): Rename', \
+                      fmtRename(path, ren1.dstName), 'in branch', branchName1, \
+                      'rename', fmtRename(path, ren2.dstName), 'in', branchName2
+                cleanMerge = False
 
-# cleanCache == True  => Don't leave any non-stage 0 entries in the cache and
-#                        don't update the working directory
-#               False => Leave unmerged entries and update the working directory
+                if ren1.dstName in currentDirectorySet:
+                    dstName1 = uniquePath(ren1.dstName, branchName1)
+                    print ren1.dstName, 'is a directory in', branchName2, \
+                          'adding as', dstName1, 'instead.'
+                    removeFile(False, ren1.dstName)
+                else:
+                    dstName1 = ren1.dstName
 
-# clean     == True  => non-conflict case
-#              False => conflict case
+                if ren2.dstName in currentDirectorySet:
+                    dstName2 = uniquePath(ren2.dstName, branchName2)
+                    print ren2.dstName, 'is a directory in', branchName1, \
+                          'adding as', dstName2, 'instead.'
+                    removeFile(False, ren2.dstName)
+                else:
+                    dstName2 = ren1.dstName
 
-# If cleanCache == False then the cache shouldn't be updated if clean == False
+                updateFile(False, ren1.dstSha, ren1.dstMode, dstName1)
+                updateFile(False, ren2.dstSha, ren2.dstMode, dstName2)
+            else:
+                print 'Renaming', fmtRename(path, ren1.dstName)
+                [resSha, resMode, clean, merge] = \
+                         mergeFile(ren1.srcName, ren1.srcSha, ren1.srcMode,
+                                   ren1.dstName, ren1.dstSha, ren1.dstMode,
+                                   ren2.dstName, ren2.dstSha, ren2.dstMode,
+                                   branchName1, branchName2)
+
+                if merge:
+                    print 'Auto-merging', ren1.dstName
+
+                if not clean:
+                    print 'CONFLICT (content): merge conflict in', ren1.dstName
+                    cleanMerge = False
+
+                    if not cacheOnly:
+                        updateFileExt(ren1.dstSha, ren1.dstMode, ren1.dstName,
+                                      updateCache=True, updateWd=False)
+                updateFile(clean, resSha, resMode, ren1.dstName)
+        else:
+            # Renamed in 1, maybe changed in 2
+            if renamesA == renames1:
+                stage = 3
+            else:
+                stage = 2
+                
+            srcShaOtherBranch  = ren1.srcCacheEntry.stages[stage].sha1
+            srcModeOtherBranch = ren1.srcCacheEntry.stages[stage].mode
+
+            dstShaOtherBranch  = ren1.dstCacheEntry.stages[stage].sha1
+            dstModeOtherBranch = ren1.dstCacheEntry.stages[stage].mode
+
+            tryMerge = False
+            
+            if ren1.dstName in currentDirectorySet:
+                newPath = uniquePath(ren1.dstName, branchName1)
+                print 'CONFLICT (rename/directory): Rename', \
+                      fmtRename(ren1.srcName, ren1.dstName), 'in', branchName1,\
+                      'directory', ren1.dstName, 'added in', branchName2
+                print 'Renaming', ren1.srcName, 'to', newPath, 'instead'
+                cleanMerge = False
+                removeFile(False, ren1.dstName)
+                updateFile(False, ren1.dstSha, ren1.dstMode, newPath)
+            elif srcShaOtherBranch == None:
+                print 'CONFLICT (rename/delete): Rename', \
+                      fmtRename(ren1.srcName, ren1.dstName), 'in', \
+                      branchName1, 'and deleted in', branchName2
+                cleanMerge = False
+                updateFile(False, ren1.dstSha, ren1.dstMode, ren1.dstName)
+            elif dstShaOtherBranch:
+                newPath = uniquePath(ren1.dstName, branchName2)
+                print 'CONFLICT (rename/add): Rename', \
+                      fmtRename(ren1.srcName, ren1.dstName), 'in', \
+                      branchName1 + '.', ren1.dstName, 'added in', branchName2
+                print 'Adding as', newPath, 'instead'
+                updateFile(False, dstShaOtherBranch, dstModeOtherBranch, newPath)
+                cleanMerge = False
+                tryMerge = True
+            elif renames2.getDst(ren1.dstName):
+                dst2 = renames2.getDst(ren1.dstName)
+                newPath1 = uniquePath(ren1.dstName, branchName1)
+                newPath2 = uniquePath(dst2.dstName, branchName2)
+                print 'CONFLICT (rename/rename): Rename', \
+                      fmtRename(ren1.srcName, ren1.dstName), 'in', \
+                      branchName1+'. Rename', \
+                      fmtRename(dst2.srcName, dst2.dstName), 'in', branchName2
+                print 'Renaming', ren1.srcName, 'to', newPath1, 'and', \
+                      dst2.srcName, 'to', newPath2, 'instead'
+                removeFile(False, ren1.dstName)
+                updateFile(False, ren1.dstSha, ren1.dstMode, newPath1)
+                updateFile(False, dst2.dstSha, dst2.dstMode, newPath2)
+                dst2.processed = True
+                cleanMerge = False
+            else:
+                tryMerge = True
 
-    def updateFile(clean, sha, mode, path, onlyWd=False):
-        updateCache = not onlyWd and (cleanCache or (not cleanCache and clean))
-        updateWd = onlyWd or (not cleanCache and clean)
+            if tryMerge:
+                print 'Renaming', fmtRename(ren1.srcName, ren1.dstName)
+                [resSha, resMode, clean, merge] = \
+                         mergeFile(ren1.srcName, ren1.srcSha, ren1.srcMode,
+                                   ren1.dstName, ren1.dstSha, ren1.dstMode,
+                                   ren1.srcName, srcShaOtherBranch, srcModeOtherBranch,
+                                   branchName1, branchName2)
 
-        if updateWd:
-            prog = ['git-cat-file', 'blob', sha]
-            if stat.S_ISREG(mode):
-                try:
-                    os.unlink(path)
-                except OSError:
-                    pass
-                if mode & 0100:
-                    mode = 0777
-                else:
-                    mode = 0666
-                fd = os.open(path, os.O_WRONLY | os.O_TRUNC | os.O_CREAT, mode)
-                proc = subprocess.Popen(prog, stdout=fd)
-                proc.wait()
-                os.close(fd)
-            elif stat.S_ISLNK(mode):
-                linkTarget = runProgram(prog)
-                os.symlink(linkTarget, path)
-            else:
-                assert(False)
+                if merge:
+                    print 'Auto-merging', ren1.dstName
 
-        if updateWd and updateCache:
-            runProgram(['git-update-index', '--add', '--', path])
-        elif updateCache:
-            runProgram(['git-update-index', '--add', '--cacheinfo',
-                        '0%o' % mode, sha, path])
+                if not clean:
+                    print 'CONFLICT (rename/modify): Merge conflict in', ren1.dstName
+                    cleanMerge = False
 
-    def removeFile(clean, path):
-        if cleanCache or (not cleanCache and clean):
-            runProgram(['git-update-index', '--force-remove', '--', path])
+                    if not cacheOnly:
+                        updateFileExt(ren1.dstSha, ren1.dstMode, ren1.dstName,
+                                      updateCache=True, updateWd=False)
+                updateFile(clean, resSha, resMode, ren1.dstName)
 
-        if not cleanCache and clean:
-            try:
-                os.unlink(path)
-            except OSError, e:
-                if e.errno != errno.ENOENT and e.errno != errno.EISDIR:
-                    raise
+    return cleanMerge
+
+# Per entry merge function
+# ------------------------
 
-    def uniquePath(path, branch):
-        newPath = path + '_' + branch
-        suffix = 0
-        while newPath in files or newPath in dirs:
-            suffix += 1
-            newPath = path + '_' + branch + '_' + str(suffix)
-        files.add(newPath)
-        return newPath
+def processEntry(entry, branch1Name, branch2Name):
+    '''Merge one cache entry.'''
 
-    debug('processing', entry.path, 'clean cache:', cleanCache)
+    debug('processing', entry.path, 'clean cache:', cacheOnly)
 
     cleanMerge = True
 
     path = entry.path
-    oSha = entry.stages[0].sha1
-    oMode = entry.stages[0].mode
-    aSha = entry.stages[1].sha1
-    aMode = entry.stages[1].mode
-    bSha = entry.stages[2].sha1
-    bMode = entry.stages[2].mode
+    oSha = entry.stages[1].sha1
+    oMode = entry.stages[1].mode
+    aSha = entry.stages[2].sha1
+    aMode = entry.stages[2].mode
+    bSha = entry.stages[3].sha1
+    bMode = entry.stages[3].mode
 
     assert(oSha == None or isSha(oSha))
     assert(aSha == None or isSha(aSha))
@@ -272,28 +709,26 @@ def uniquePath(path, branch):
            (not aSha     and bSha == oSha):
     # Deleted in both or deleted in one and unchanged in the other
             if aSha:
-                print 'Removing ' + path
+                print 'Removing', path
             removeFile(True, path)
         else:
     # Deleted in one and changed in the other
             cleanMerge = False
             if not aSha:
-                print 'CONFLICT (del/mod): "' + path + '" deleted in', \
-                      branch1Name, 'and modified in', branch2Name, \
-                      '. Version', branch2Name, ' of "' + path + \
-                      '" left in tree'
+                print 'CONFLICT (delete/modify):', path, 'deleted in', \
+                      branch1Name, 'and modified in', branch2Name + '.', \
+                      'Version', branch2Name, 'of', path, 'left in tree.'
                 mode = bMode
                 sha = bSha
             else:
-                print 'CONFLICT (mod/del): "' + path + '" deleted in', \
-                      branch2Name, 'and modified in', branch1Name + \
-                      '. Version', branch1Name, 'of "' + path + \
-                      '" left in tree'
+                print 'CONFLICT (modify/delete):', path, 'deleted in', \
+                      branch2Name, 'and modified in', branch1Name + '.', \
+                      'Version', branch1Name, 'of', path, 'left in tree.'
                 mode = aMode
                 sha = aSha
 
             updateFile(False, sha, mode, path)
-    
+
     elif (not oSha and aSha     and not bSha) or \
          (not oSha and not aSha and bSha):
     #
@@ -304,27 +739,26 @@ def uniquePath(path, branch):
             otherBranch = branch2Name
             mode = aMode
             sha = aSha
-            conf = 'file/dir'
+            conf = 'file/directory'
         else:
             addBranch = branch2Name
             otherBranch = branch1Name
             mode = bMode
             sha = bSha
-            conf = 'dir/file'
+            conf = 'directory/file'
     
-        if path in dirs:
+        if path in currentDirectorySet:
             cleanMerge = False
             newPath = uniquePath(path, addBranch)
-            print 'CONFLICT (' + conf + \
-                  '): There is a directory with name "' + path + '" in', \
-                  otherBranch + '. Adding "' + path + '" as "' + newPath + '"'
+            print 'CONFLICT (' + conf + '):', \
+                  'There is a directory with name', path, 'in', \
+                  otherBranch + '. Adding', path, 'as', newPath
 
             removeFile(False, path)
-            path = newPath
+            updateFile(False, sha, mode, newPath)
         else:
-            print 'Adding "' + path + '"'
-
-        updateFile(True, sha, mode, path)
+            print 'Adding', path
+            updateFile(True, sha, mode, path)
     
     elif not oSha and aSha and bSha:
     #
@@ -333,10 +767,9 @@ def uniquePath(path, branch):
         if aSha == bSha:
             if aMode != bMode:
                 cleanMerge = False
-                print 'CONFLICT: File "' + path + \
-                      '" added identically in both branches,', \
-                      'but permissions conflict', '0%o' % aMode, '->', \
-                      '0%o' % bMode
+                print 'CONFLICT: File', path, \
+                      'added identically in both branches, but permissions', \
+                      'conflict', '0%o' % aMode, '->', '0%o' % bMode
                 print 'CONFLICT: adding with permission:', '0%o' % aMode
 
                 updateFile(False, aSha, aMode, path)
@@ -347,8 +780,9 @@ def uniquePath(path, branch):
             cleanMerge = False
             newPath1 = uniquePath(path, branch1Name)
             newPath2 = uniquePath(path, branch2Name)
-            print 'CONFLICT (add/add): File "' + path + \
-                  '" added non-identically in both branches.'
+            print 'CONFLICT (add/add): File', path, \
+                  'added non-identically in both branches. Adding as', \
+                  newPath1, 'and', newPath2, 'instead.'
             removeFile(False, path)
             updateFile(False, aSha, aMode, newPath1)
             updateFile(False, bSha, bMode, newPath2)
@@ -357,39 +791,24 @@ def uniquePath(path, branch):
     #
     # case D: Modified in both, but differently.
     #
-        print 'Auto-merging', path 
-        orig = runProgram(['git-unpack-file', oSha]).rstrip()
-        src1 = runProgram(['git-unpack-file', aSha]).rstrip()
-        src2 = runProgram(['git-unpack-file', bSha]).rstrip()
-        [out, ret] = runProgram(['merge',
-                                 '-L', branch1Name + '/' + path,
-                                 '-L', 'orig/' + path,
-                                 '-L', branch2Name + '/' + path,
-                                 src1, orig, src2], returnCode=True)
-
-        if aMode == oMode:
-            mode = bMode
+        print 'Auto-merging', path
+        [sha, mode, clean, dummy] = \
+              mergeFile(path, oSha, oMode,
+                        path, aSha, aMode,
+                        path, bSha, bMode,
+                        branch1Name, branch2Name)
+        if clean:
+            updateFile(True, sha, mode, path)
         else:
-            mode = aMode
-
-        sha = runProgram(['git-hash-object', '-t', 'blob', '-w',
-                          src1]).rstrip()
-
-        if ret != 0:
             cleanMerge = False
-            print 'CONFLICT (content): Merge conflict in "' + path + '".'
+            print 'CONFLICT (content): Merge conflict in', path
 
-            if cleanCache:
+            if cacheOnly:
                 updateFile(False, sha, mode, path)
             else:
-                updateFile(True, aSha, aMode, path)
-                updateFile(False, sha, mode, path, True)
-        else:
-            updateFile(True, sha, mode, path)
-
-        os.unlink(orig)
-        os.unlink(src1)
-        os.unlink(src2)
+                updateFileExt(aSha, aMode, path,
+                              updateCache=True, updateWd=False)
+                updateFileExt(sha, mode, path, updateCache=False, updateWd=True)
     else:
         die("ERROR: Fatal merge failure, shouldn't happen.")
 
@@ -413,7 +832,7 @@ def usage():
         try:
             h1 = firstBranch = sys.argv[nextArg + 1]
             h2 = secondBranch = sys.argv[nextArg + 2]
-       except IndexError:
+        except IndexError:
             usage()
         break
 
@@ -425,13 +844,16 @@ def usage():
 
     graph = buildGraph([h1, h2])
 
-    [res, clean] = merge(graph.shaMap[h1], graph.shaMap[h2],
-                         firstBranch, secondBranch, graph)
+    [dummy, clean] = merge(graph.shaMap[h1], graph.shaMap[h2],
+                           firstBranch, secondBranch, graph)
 
     print ''
 except:
-    traceback.print_exc(None, sys.stderr)
-    sys.exit(2)
+    if isinstance(sys.exc_info()[1], SystemExit):
+        raise
+    else:
+        traceback.print_exc(None, sys.stderr)
+        sys.exit(2)
 
 if clean:
     sys.exit(0)
index e3b04a0e959676e513a7b7e902ea51927d76e2be..966e81ff7d353beb15ac2c9ed9a50a1ea45c876c 100755 (executable)
@@ -31,6 +31,12 @@ case "$remotes" in
        exit 2 ;;
 esac
 
+# Give up if this is a baseless merge.
+if test '' = "$bases"
+then
+       exit 2
+fi
+
 git-update-index --refresh 2>/dev/null
 git-read-tree -u -m $bases $head $remotes || exit 2
 echo "Trying simple merge."
index 818e6b772d30c060020640deb051f2428e2c9fe5..b810fceaf8787f6c450abc628d0a329fe298b13f 100755 (executable)
@@ -9,23 +9,49 @@ LF='
 '
 
 usage () {
-    die "git-merge [-n] [-s <strategy>]... <merge-message> <head> <remote>+"
+    die "git-merge [-n] [--no-commit] [-s <strategy>]... <merge-message> <head> <remote>+"
 }
 
 # all_strategies='resolve recursive stupid octopus'
 
-all_strategies='recursive octopus resolve stupid'
+all_strategies='recursive octopus resolve stupid ours'
 default_strategies='resolve octopus'
 use_strategies=
 
-dropheads() {
-       rm -f -- "$GIT_DIR/MERGE_HEAD" || exit 1
+dropsave() {
+       rm -f -- "$GIT_DIR/MERGE_HEAD" "$GIT_DIR/MERGE_MSG" \
+                "$GIT_DIR/MERGE_SAVE" || exit 1
 }
 
-summary() {
+savestate() {
+       # Stash away any local modifications.
+       git-diff-index -z --name-only $head |
+       cpio -0 -o >"$GIT_DIR/MERGE_SAVE"
+}
+
+restorestate() {
+        if test -f "$GIT_DIR/MERGE_SAVE"
+       then
+               git reset --hard $head
+               cpio -iuv <"$GIT_DIR/MERGE_SAVE"
+               git-update-index --refresh >/dev/null
+       fi
+}
+
+finish () {
+       test '' = "$2" || echo "$2"
+       case "$merge_msg" in
+       '')
+               echo "No merge message -- not updating HEAD"
+               ;;
+       *)
+               git-update-ref HEAD "$1" "$head" || exit 1
+               ;;
+       esac
+
        case "$no_summary" in
        '')
-               git-diff-tree -p -M $head "$1" |
+               git-diff-tree -p -M "$head" "$1" |
                git-apply --stat --summary
                ;;
        esac
@@ -37,6 +63,8 @@ do
        -n|--n|--no|--no-|--no-s|--no-su|--no-sum|--no-summ|\
                --no-summa|--no-summar|--no-summary)
                no_summary=t ;;
+       --no-c|--no-co|--no-com|--no-comm|--no-commi|--no-commit)
+               no_commit=t ;;
        -s=*|--s=*|--st=*|--str=*|--stra=*|--strat=*|--strate=*|\
                --strateg=*|--strategy=*|\
        -s|--s|--st|--str|--stra|--strat|--strate|--strateg|--strategy)
@@ -71,6 +99,7 @@ test "$#" -le 2 && usage ;# we need at least two heads.
 
 merge_msg="$1"
 shift
+head_arg="$1"
 head=$(git-rev-parse --verify "$1"^0) || usage
 shift
 
@@ -84,31 +113,50 @@ done
 common=$(git-show-branch --merge-base $head "$@")
 echo "$head" >"$GIT_DIR/ORIG_HEAD"
 
-case "$#,$common" in
-*,'')
-       die "Unable to find common commit between $head and $*"
+case "$#,$common,$no_commit" in
+*,'',*)
+       # No common ancestors found. We need a real merge.
        ;;
-1,"$1")
+1,"$1",*)
        # If head can reach all the merge then we are up to date.
        # but first the most common case of merging one remote
-       echo "Already up-to-date. Yeeah!"
-       dropheads
+       echo "Already up-to-date."
+       dropsave
        exit 0
        ;;
-1,"$head")
+1,"$head",*)
        # Again the most common case of merging one remote.
        echo "Updating from $head to $1."
        git-update-index --refresh 2>/dev/null
-       git-read-tree -u -m $head "$1" || exit 1
-       git-rev-parse --verify "$1^0" > "$GIT_DIR/HEAD"
-       summary "$1"
-       dropheads
+       new_head=$(git-rev-parse --verify "$1^0") &&
+       git-read-tree -u -m $head "$new_head" &&
+       finish "$new_head" "Fast forward"
+       dropsave
        exit 0
        ;;
-1,*)
+1,?*"$LF"?*,*)
        # We are not doing octopus and not fast forward.  Need a
        # real merge.
        ;;
+1,*,)
+       # We are not doing octopus, not fast forward, and have only
+       # one common.  See if it is really trivial.
+       echo "Trying really trivial in-index merge..."
+       git-update-index --refresh 2>/dev/null
+       if git-read-tree --trivial -m -u $common $head "$1" &&
+          result_tree=$(git-write-tree)
+       then
+           echo "Wonderful."
+           result_commit=$(
+               echo "$merge_msg" |
+               git-commit-tree $result_tree -p HEAD -p "$1"
+           ) || exit
+           finish "$result_commit" "In-index merge"
+           dropsave
+           exit 0
+       fi
+       echo "Nope."
+       ;;
 *)
        # An octopus.  If we can reach all the remote we are up to date.
        up_to_date=t
@@ -124,35 +172,58 @@ case "$#,$common" in
        if test "$up_to_date" = t
        then
                echo "Already up-to-date. Yeeah!"
-               dropheads
+               dropsave
                exit 0
        fi
        ;;
 esac
 
-# At this point we need a real merge.  Require that the tree matches
-# exactly our head.
+# At this point, we need a real merge.  No matter what strategy
+# we use, it would operate on the index, possibly affecting the
+# working tree, and when resolved cleanly, have the desired tree
+# in the index -- this means that the index must be in sync with
+# the $head commit.  The strategies are responsible to ensure this.
 
-git-update-index --refresh &&
-test '' = "`git-diff-index --cached --name-only $head`" || {
-       die "Need real merge but the working tree has local changes."
-}
+case "$use_strategies" in
+?*' '?*)
+    # Stash away the local changes so that we can try more than one.
+    savestate
+    single_strategy=no
+    ;;
+*)
+    rm -f "$GIT_DIR/MERGE_SAVE"
+    single_strategy=yes
+    ;;
+esac
 
 result_tree= best_cnt=-1 best_strategy= wt_strategy=
 for strategy in $use_strategies
 do
     test "$wt_strategy" = '' || {
        echo "Rewinding the tree to pristine..."
-       git reset --hard $head
+       restorestate
     }
-    echo "Trying merge strategy $strategy..."
+    case "$single_strategy" in
+    no)
+       echo "Trying merge strategy $strategy..."
+       ;;
+    esac
+
+    # Remember which strategy left the state in the working tree
     wt_strategy=$strategy
-    git-merge-$strategy $common -- $head "$@" || {
+
+    git-merge-$strategy $common -- "$head_arg" "$@"
+    exit=$?
+    if test "$no_commit" = t && test "$exit" = 0
+    then
+       exit=1 ;# pretend it left conflicts.
+    fi
+
+    test "$exit" = 0 || {
 
        # The backend exits with 1 when conflicts are left to be resolved,
        # with 2 when it does not handle the given merge at all.
 
-       exit=$?
        if test "$exit" -eq 1
        then
            cnt=`{
@@ -181,18 +252,16 @@ then
     do
         parents="$parents -p $remote"
     done
-    result_commit=$(echo "$merge_msg" | git-commit-tree $result_tree $parents)
-    echo "Committed merge $result_commit, made by $wt_strategy."
-    echo $result_commit >"$GIT_DIR/HEAD"
-    summary $result_commit
-    dropheads
+    result_commit=$(echo "$merge_msg" | git-commit-tree $result_tree $parents) || exit
+    finish "$result_commit" "Merge $result_commit, made by $wt_strategy."
+    dropsave
     exit 0
 fi
 
 # Pick the result from the best strategy and have the user fix it up.
 case "$best_strategy" in
 '')
-       git reset --hard $head
+       restorestate
        die "No merge strategy handled the merge."
        ;;
 "$wt_strategy")
@@ -200,13 +269,15 @@ case "$best_strategy" in
        ;;
 *)
        echo "Rewinding the tree to pristine..."
-       git reset --hard $head
+       restorestate
        echo "Using the $best_strategy to prepare resolving by hand."
-       git-merge-$best_strategy $common -- $head "$@"
+       git-merge-$best_strategy $common -- "$head_arg" "$@"
        ;;
 esac
 for remote
 do
        echo $remote
 done >"$GIT_DIR/MERGE_HEAD"
-die "Automatic merge failed; fix up by hand"
+echo $merge_msg >"$GIT_DIR/MERGE_MSG"
+
+die "Automatic merge failed/prevented; fix up by hand"
diff --git a/git-mv.perl b/git-mv.perl
new file mode 100755 (executable)
index 0000000..a21d87e
--- /dev/null
@@ -0,0 +1,197 @@
+#!/usr/bin/perl
+#
+# Copyright 2005, Ryan Anderson <ryan@michonline.com>
+#                 Josef Weidendorfer <Josef.Weidendorfer@gmx.de>
+#
+# This file is licensed under the GPL v2, or a later version
+# at the discretion of Linus Torvalds.
+
+
+use warnings;
+use strict;
+use Getopt::Std;
+
+sub usage() {
+       print <<EOT;
+$0 [-f] [-n] <source> <dest>
+$0 [-f] [-k] [-n] <source> ... <dest directory>
+
+In the first form, source must exist and be either a file,
+symlink or directory, dest must not exist. It renames source to dest.
+In the second form, the last argument has to be an existing
+directory; the given sources will be moved into this directory.
+
+Updates the git cache to reflect the change.
+Use "git commit" to make the change permanently.
+
+Options:
+  -f   Force renaming/moving, even if target exists
+  -k   Continue on error by skipping
+       not-existing or not revision-controlled source
+  -n   Do nothing; show what would happen
+EOT
+       exit(1);
+}
+
+# Sanity checks:
+my $GIT_DIR = $ENV{'GIT_DIR'} || ".git";
+
+unless ( -d $GIT_DIR && -d $GIT_DIR . "/objects" && 
+       -d $GIT_DIR . "/objects/" && -d $GIT_DIR . "/refs") {
+    print "Git repository not found.";
+    usage();
+}
+
+
+our ($opt_n, $opt_f, $opt_h, $opt_k, $opt_v);
+getopts("hnfkv") || usage;
+usage() if $opt_h;
+@ARGV >= 1 or usage;
+
+my (@srcArgs, @dstArgs, @srcs, @dsts);
+my ($src, $dst, $base, $dstDir);
+
+my $argCount = scalar @ARGV;
+if (-d $ARGV[$argCount-1]) {
+       $dstDir = $ARGV[$argCount-1];
+       # remove any trailing slash
+       $dstDir =~ s/\/$//;
+       @srcArgs = @ARGV[0..$argCount-2];
+       
+       foreach $src (@srcArgs) {
+               $base = $src;
+               $base =~ s/^.*\///;
+               $dst = "$dstDir/". $base;
+               push @dstArgs, $dst;
+       }
+}
+else {
+    if ($argCount != 2) {
+       print "Error: moving to directory '"
+           . $ARGV[$argCount-1]
+           . "' not possible; not exisiting\n";
+       usage;
+    }
+    @srcArgs = ($ARGV[0]);
+    @dstArgs = ($ARGV[1]);
+    $dstDir = "";
+}
+
+my (@allfiles,@srcfiles,@dstfiles);
+my $safesrc;
+my (%overwritten, %srcForDst);
+
+$/ = "\0";
+open(F,"-|","git-ls-files","-z")
+        or die "Failed to open pipe from git-ls-files: " . $!;
+
+@allfiles = map { chomp; $_; } <F>;
+close(F);
+
+
+my ($i, $bad);
+while(scalar @srcArgs > 0) {
+    $src = shift @srcArgs;
+    $dst = shift @dstArgs;
+    $bad = "";
+
+    if ($opt_v) {
+       print "Checking rename of '$src' to '$dst'\n";
+    }
+
+    unless (-f $src || -l $src || -d $src) {
+       $bad = "bad source '$src'";
+    }
+
+    $overwritten{$dst} = 0;
+    if (($bad eq "") && -e $dst) {
+       $bad = "destination '$dst' already exists";
+       if (-f $dst && $opt_f) {
+           print "Warning: $bad; will overwrite!\n";
+           $bad = "";
+           $overwritten{$dst} = 1;
+       }
+    }
+    
+    if (($bad eq "") && ($src eq $dstDir)) {
+       $bad = "can not move directory '$src' into itself";
+    }
+
+    if ($bad eq "") {
+       $safesrc = quotemeta($src);
+       @srcfiles = grep /^$safesrc(\/|$)/, @allfiles;
+        if (scalar @srcfiles == 0) {
+           $bad = "'$src' not under version control";
+       }
+    }
+
+    if ($bad eq "") {
+       if (defined $srcForDst{$dst}) {
+           $bad = "can not move '$src' to '$dst'; already target of ";
+           $bad .= "'".$srcForDst{$dst}."'";
+       }
+       else {
+           $srcForDst{$dst} = $src;
+       }
+    }
+
+    if ($bad ne "") {
+       if ($opt_k) {
+           print "Warning: $bad; skipping\n";
+           next;
+       }
+       print "Error: $bad\n";
+       usage();
+    }
+    push @srcs, $src;
+    push @dsts, $dst;
+}
+
+# Final pass: rename/move
+my (@deletedfiles,@addedfiles,@changedfiles);
+while(scalar @srcs > 0) {
+    $src = shift @srcs;
+    $dst = shift @dsts;
+
+    if ($opt_n || $opt_v) { print "Renaming $src to $dst\n"; }
+    if (!$opt_n) {
+       rename($src,$dst)
+           or die "rename failed: $!";
+    }
+
+    $safesrc = quotemeta($src);
+    @srcfiles = grep /^$safesrc(\/|$)/, @allfiles;
+    @dstfiles = @srcfiles;
+    s/^$safesrc(\/|$)/$dst$1/ for @dstfiles;
+
+    push @deletedfiles, @srcfiles;
+    if (scalar @srcfiles == 1) {
+       if ($overwritten{$dst} ==1) {
+           push @changedfiles, $dst;
+       } else {
+           push @addedfiles, $dst;
+       }
+    }
+    else {
+       push @addedfiles, @dstfiles;
+    }
+}
+
+if ($opt_n) {
+       print "Changed  : ". join(", ", @changedfiles) ."\n";
+       print "Adding   : ". join(", ", @addedfiles) ."\n";
+       print "Deleting : ". join(", ", @deletedfiles) ."\n";
+       exit(1);
+}
+       
+my $rc;
+if (scalar @changedfiles >0) {
+       $rc = system("git-update-index","--",@changedfiles);
+       die "git-update-index failed to update changed files with code $?\n" if $rc;
+}
+if (scalar @addedfiles >0) {
+       $rc = system("git-update-index","--add","--",@addedfiles);
+       die "git-update-index failed to add new names with code $?\n" if $rc;
+}
+$rc = system("git-update-index","--remove","--",@deletedfiles);
+die "git-update-index failed to remove old names with code $?\n" if $rc;
index 521cc6f3611bcaf5454ad83baf0bec2e0b66483d..d2471af3c8a002c175bf9b1370039b9e7d4ab946 100755 (executable)
@@ -27,7 +27,7 @@ test "$(git-diff-index --cached "$head")" = "" ||
 # MRC is the current "merge reference commit"
 # MRT is the current "merge result tree"
 
-MRC=$head MSG= PARENT="-p $head"
+MRC=$head PARENT="-p $head"
 MRT=$(git-write-tree)
 CNT=1 ;# counting our head
 NON_FF_MERGE=0
@@ -44,8 +44,6 @@ do
 
        CNT=`expr $CNT + 1`
        PARENT="$PARENT -p $SHA1"
-       MSG="$MSG
-       $REPO"
 
        if test "$common,$NON_FF_MERGE" = "$MRC,0"
        then
@@ -84,20 +82,9 @@ case "$CNT" in
 1)
        echo "No changes."
        exit 0 ;;
-2)
-       echo "Not an Octopus; making an ordinary commit."
-       MSG="Merge "`expr "$MSG" : '.   \(.*\)'` ; # remove LF and TAB
-       ;;
-*)
-       # In an octopus, the original head is just one of the equals,
-       # so we should list it as such.
-       HEAD_LINK=`readlink "$GIT_DIR/HEAD"`
-       MSG="Octopus merge of the following:
-
-       $HEAD_LINK from .$MSG"
-       ;;
 esac
-result_commit=$(echo "$MSG" | git-commit-tree $MRT $PARENT)
+result_commit=$(git-fmt-merge-msg <"$GIT_DIR/FETCH_HEAD" |
+               git-commit-tree $MRT $PARENT)
 echo "Committed merge $result_commit"
-echo $result_commit >"$GIT_DIR"/HEAD
+git-update-ref HEAD $result_commit $head
 git-diff-tree -p $head $result_commit | git-apply --stat
index a9db0cd82558af9bebec083450b35348a1d0b29a..aea7b0e5497fb0727533ea750503193fe308374a 100755 (executable)
@@ -1,6 +1,6 @@
 #!/bin/sh
 
-. git-sh-setup || die "Not a git archive"
+. git-sh-setup
 
 get_data_source () {
        case "$1" in
@@ -65,8 +65,11 @@ get_remote_default_refs_for_push () {
        esac
 }
 
-# Subroutine to canonicalize remote:local notation
+# Subroutine to canonicalize remote:local notation.
 canon_refs_list_for_fetch () {
+       # Leave only the first one alone; add prefix . to the rest
+       # to prevent the secondary branches to be merged by default.
+       dot_prefix=
        for ref
        do
                force=
@@ -91,7 +94,14 @@ canon_refs_list_for_fetch () {
                heads/* | tags/* ) local="refs/$local" ;;
                *) local="refs/heads/$local" ;;
                esac
-               echo "${force}${remote}:${local}"
+
+               if local_ref_name=$(expr "$local" : 'refs/\(.*\)')
+               then
+                  git-check-ref-format "$local_ref_name" ||
+                  die "* refusing to create funny ref '$local_ref_name' locally"
+               fi
+               echo "${dot_prefix}${force}${remote}:${local}"
+               dot_prefix=.
        done
 }
 
@@ -107,6 +117,9 @@ get_remote_default_refs_for_fetch () {
                echo "refs/heads/${remote_branch}:refs/heads/$1"
                ;;
        remotes)
+               # This prefixes the second and later default refspecs
+               # with a '.', to signal git-fetch to mark them
+               # not-for-merge.
                canon_refs_list_for_fetch $(sed -ne '/^Pull: */{
                                                s///p
                                        }' "$GIT_DIR/remotes/$1")
index 9657dbf2711e40bfe035a22ec211811c88c89e8e..ef31bd2a6824104ba401bffbe41ecd50a81780e9 100755 (executable)
@@ -15,6 +15,7 @@ do
     shift;
 done
 
+sync
 git-fsck-objects --full --cache --unreachable "$@" |
 sed -ne '/unreachable /{
     s/unreachable [^ ][^ ]* //
@@ -22,6 +23,7 @@ sed -ne '/unreachable /{
 }' | {
        cd "$GIT_OBJECT_DIRECTORY" || exit
        xargs $echo rm -f
+       rmdir 2>/dev/null [0-9a-f][0-9a-f]
 }
 
 git-prune-packed $dryrun
index 8cf39e7f6495925c9f9824ebea518810554ba12b..e23d4f559790fed079bb5759fd5aa11a8804f191 100755 (executable)
@@ -6,10 +6,45 @@
 
 . git-sh-setup || die "Not a git archive"
 
-orig_head=$(cat "$GIT_DIR/HEAD") || die "Pulling into a black hole?"
+usage () {
+    die "git pull [-n] [--no-commit] [-s strategy]... <repo> <head>..."
+}
+
+strategy_args= no_summary= no_commit=
+while case "$#,$1" in 0) break ;; *,-*) ;; *) break ;; esac
+do
+       case "$1" in
+       -n|--n|--no|--no-|--no-s|--no-su|--no-sum|--no-summ|\
+               --no-summa|--no-summar|--no-summary)
+               no_summary=-n ;;
+       --no-c|--no-co|--no-com|--no-comm|--no-commi|--no-commit)
+               no_commit=--no-commit ;;
+       -s=*|--s=*|--st=*|--str=*|--stra=*|--strat=*|--strate=*|\
+               --strateg=*|--strategy=*|\
+       -s|--s|--st|--str|--stra|--strat|--strate|--strateg|--strategy)
+               case "$#,$1" in
+               *,*=*)
+                       strategy=`expr "$1" : '-[^=]*=\(.*\)'` ;;
+               1,*)
+                       usage ;;
+               *)
+                       strategy="$2"
+                       shift ;;
+               esac
+               strategy_args="${strategy_args}-s $strategy "
+               ;;
+       -*)
+               # Pass thru anything that is meant for fetch.
+               break
+               ;;
+       esac
+       shift
+done
+
+orig_head=$(git-rev-parse --verify HEAD) || die "Pulling into a black hole?"
 git-fetch --update-head-ok "$@" || exit 1
 
-curr_head=$(cat "$GIT_DIR/HEAD")
+curr_head=$(git-rev-parse --verify HEAD)
 if test "$curr_head" != "$orig_head"
 then
        # The fetch involved updating the current branch.
@@ -24,23 +59,28 @@ then
                die "You need to first update your working tree."
 fi
 
-merge_head=$(sed -e 's/        .*//' "$GIT_DIR"/FETCH_HEAD | tr '\012' ' ')
-merge_name=$(
-    perl -e 'print join("; ", map { chomp; s/^[0-9a-f]*        //; $_ } <>)' \
-    "$GIT_DIR"/FETCH_HEAD
-)
+merge_head=$(sed -e '/ not-for-merge   /d' \
+       -e 's/  .*//' "$GIT_DIR"/FETCH_HEAD | \
+       tr '\012' ' ')
 
 case "$merge_head" in
 '')
        echo >&2 "No changes."
        exit 0
        ;;
-*' '?*)
-       echo >&2 "Pulling more than one heads; making an Octopus."
-       exec git-octopus
+?*' '?*)
+       strategy_default_args='-s octopus'
+       ;;
+*)
+       strategy_default_args='-s resolve'
+       ;;
+esac
+
+case "$strategy_args" in
+'')
+       strategy_args=$strategy_default_args
        ;;
 esac
 
-git-resolve \
-       "$(cat "$GIT_DIR"/HEAD)" \
-       $merge_head "Merge $merge_name"
+merge_name=$(git-fmt-merge-msg <"$GIT_DIR/FETCH_HEAD")
+git-merge $no_summary $no_commit $strategy_args "$merge_name" HEAD $merge_head
index 00d715059c4b751bef9adaf3debbe56ae1cf19ac..5aa6531945c9e3494e34abb08d81e9ad03864521 100755 (executable)
@@ -1,6 +1,11 @@
 #!/bin/sh
 . git-sh-setup || die "Not a git archive"
 
+usage () {
+    die "Usage: git push [--all] [--force] <repository> [<refspec>]"
+}
+
+
 # Parse out parameters and then stop at remote, so that we can
 # translate it using .git/branches information
 has_all=
@@ -18,7 +23,7 @@ do
        --exec=*)
                has_exec="$1" ;;
        -*)
-               die "Unknown parameter $1" ;;
+                usage ;;
         *)
                set x "$@"
                shift
@@ -28,7 +33,8 @@ do
 done
 case "$#" in
 0)
-       die "Where would you want to push today?" ;;
+       echo "Where would you want to push today?"
+        usage ;;
 esac
 
 . git-parse-remote
@@ -40,8 +46,10 @@ esac
 shift
 
 case "$remote" in
-http://* | https://* | git://* | rsync://* )
-       die "Cannot push to $remote" ;;
+http://* | https://* | git://*)
+       die "Cannot use READ-ONLY transport to push to $remote" ;;
+rsync://*)
+        die "Pushing with rsync transport is deprecated" ;;
 esac
 
 set x "$remote" "$@"; shift
index 49c8f12e51153f5c7ecbdc5a1778c24ba28f428f..fa95009091525eb9ea65bce1e08f293dfd1eddca 100755 (executable)
@@ -33,7 +33,8 @@ test "$different1$different2" = "" ||
 die "Your working tree does not match $ours_symbolic."
 
 git-read-tree -m -u $ours $upstream &&
-git-rev-parse --verify "$upstream^0" >"$GIT_DIR/HEAD" || exit
+new_head=$(git-rev-parse --verify "$upstream^0") &&
+git-update-ref HEAD "$new_head" || exit
 
 tmp=.rebase-tmp$$
 fail=$tmp-fail
@@ -50,7 +51,7 @@ do
                continue ;;
        esac
        echo >&2 "* Applying: $msg"
-       S=`cat "$GIT_DIR/HEAD"` &&
+       S=$(git-rev-parse --verify HEAD) &&
        git-cherry-pick --replay $commit || {
                echo >&2 "* Not applying the patch and continuing."
                echo $commit >>$fail
index a28c8c83bb98e81635ce5cb629921e13eca1e3c2..3b1127b1b20280cc4c6c657168dba963f31fa067 100755 (executable)
@@ -15,7 +15,7 @@
 my $GIT_DIR = $ENV{'GIT_DIR'} || ".git";
 
 unless ( -d $GIT_DIR && -d $GIT_DIR . "/objects" && 
-       -d $GIT_DIR . "/objects/00" && -d $GIT_DIR . "/refs") {
+       -d $GIT_DIR . "/objects/" && -d $GIT_DIR . "/refs") {
        usage("Git repository not found.");
 }
 
index b395d0ef34758f2e3e3129a5131832e932ab7875..d341966efba783b294792c9a13915982f70a1f73 100755 (executable)
@@ -5,13 +5,14 @@
 
 . git-sh-setup || die "Not a git archive"
        
-no_update_info= all_into_one= remove_redundant=
+no_update_info= all_into_one= remove_redundant= local=
 while case "$#" in 0) break ;; esac
 do
        case "$1" in
        -n)     no_update_info=t ;;
        -a)     all_into_one=t ;;
        -d)     remove_redandant=t ;;
+       -l)     local=t ;;
        *)      break ;;
        esac
        shift
@@ -37,6 +38,9 @@ case ",$all_into_one," in
            find . -type f \( -name '*.pack' -o -name '*.idx' \) -print`
        ;;
 esac
+if [ "$local" ]; then
+       pack_objects="$pack_objects --local"
+fi
 name=$(git-rev-list --objects $rev_list $(git-rev-parse $rev_parse) |
        git-pack-objects --non-empty $pack_objects .tmp-pack) ||
        exit 1
@@ -58,6 +62,7 @@ then
        # all-into-one is used.
        if test "$all_into_one" != '' && test "$existing" != ''
        then
+               sync
                ( cd "$PACKDIR" &&
                  for e in $existing
                  do
index e028ff65dba75699580d47fbbe3fa6825774af83..2086d26d343c59d36df79d874d65f58d6d7d8b10 100755 (executable)
@@ -1,6 +1,10 @@
 #!/bin/sh
 . git-sh-setup || die "Not a git archive"
 
+usage () {
+       die 'Usage: git reset [--mixed | --soft | --hard]  [<commit-ish>]'
+}
+
 tmp=/var/tmp/reset.$$
 trap 'rm -f $tmp-*' 0 1 2 3 15
 
@@ -10,6 +14,8 @@ case "$1" in
        reset_type="$1"
        shift
        ;;
+-*)
+        usage ;;
 esac
 
 rev=$(git-rev-parse --verify --default HEAD "$@") || exit
@@ -60,7 +66,7 @@ then
 else
        rm -f "$GIT_DIR/ORIG_HEAD"
 fi
-echo "$rev" >"$GIT_DIR/HEAD"
+git-update-ref HEAD "$rev"
 
 case "$reset_type" in
 --hard )
@@ -81,10 +87,12 @@ case "$reset_type" in
                while (<$fh>) {
                        chomp;
                        if (! exists $keep{$_}) {
-                               print "$_\0";
+                               # it is ok if this fails -- it may already
+                               # have been culled by checkout-index.
+                               unlink $_;
                        }
                }
-       ' $tmp-exists | xargs -0 rm -f --
+       ' $tmp-exists
        ;;
 --soft )
        ;; # Nothing else to do
index 1f559d8cb91bf88e7ddc0abad55db7ad4550aeef..7d8fb54f952e3f85c8c77b8fd9c67afbe76f0316 100755 (executable)
@@ -45,7 +45,7 @@ case "$common" in
 "$head")
        echo "Updating from $head to $merge."
        git-read-tree -u -m $head $merge || exit 1
-       echo $merge > "$GIT_DIR"/HEAD
+       git-update-ref HEAD "$merge" "$head"
        git-diff-tree -p $head $merge | git-apply --stat
        dropheads
        exit 0
@@ -99,6 +99,6 @@ if [ $? -ne 0 ]; then
 fi
 result_commit=$(echo "$merge_msg" | git-commit-tree $result_tree -p $head -p $merge)
 echo "Committed merge $result_commit"
-echo $result_commit > "$GIT_DIR"/HEAD
+git-update-ref HEAD "$result_commit" "$head"
 git-diff-tree -p $head $result_commit | git-apply --stat
 dropheads
index 722c4f755af5ea07981712d8abf6d6eed81c5d56..dfd914cf561c10f92ef2f3207446800de70c4834 100755 (executable)
@@ -56,9 +56,12 @@ t)
                die "Your index file is unmerged."
        ;;
 *)
-       check_clean_tree || die "Cannot run $me from a dirty tree."
        head=$(git-rev-parse --verify HEAD) ||
                die "You do not have a valid HEAD"
+       files=$(git-diff-index --cached --name-only $head) || exit
+       if [ "$files" ]; then
+               die "Dirty index: cannot $me (dirty: $files)"
+       fi
        ;;
 esac
 
index d5bfa62dee0e85d404f955edefd2151e8c0e7aab..dbb98842bf6327210c604ae65a128cdfc785b32c 100755 (executable)
 unset CDPATH
 
 die() {
-       echo "$@" >&2
+       echo >&2 "$@"
        exit 1
 }
 
-check_clean_tree() {
-    dirty1_=`git-update-index -q --refresh` && {
-    dirty2_=`git-diff-index --name-only --cached HEAD`
-    case "$dirty2_" in '') : ;; *) (exit 1) ;; esac
-    } || {
-       echo >&2 "$dirty1_"
-       echo "$dirty2_" | sed >&2 -e 's/^/modified: /'
-       (exit 1)
-    }
-}
-
-[ -h "$GIT_DIR/HEAD" ] &&
+case "$(GIT_DIR="$GIT_DIR" git-symbolic-ref HEAD 2>/dev/null)" in
+refs/*)        : ;;
+*)     false ;;
+esac &&
 [ -d "$GIT_DIR/refs" ] &&
-[ -d "$GIT_OBJECT_DIRECTORY/00" ]
+[ -d "$GIT_OBJECT_DIRECTORY/" ]
index 8f0984be02c56b23edf4c705fa8a75b95c66edcc..0b14f833ee97a0e5b589098914e52c49b0be50d0 100755 (executable)
@@ -2,55 +2,13 @@
 
 use strict;
 
-#
-# Even with git, we don't always have name translations.
-# So have an email->real name table to translate the
-# (hopefully few) missing names
-#
-my %mailmap = (
-       'R.Marek@sh.cvut.cz' => 'Rudolf Marek',
-       'Ralf.Wildenhues@gmx.de' => 'Ralf Wildenhues',
-       'aherrman@de.ibm.com' => 'Andreas Herrmann',
-       'akpm@osdl.org' => 'Andrew Morton',
-       'andrew.vasquez@qlogic.com' => 'Andrew Vasquez',
-       'aquynh@gmail.com' => 'Nguyen Anh Quynh',
-       'axboe@suse.de' => 'Jens Axboe',
-       'blaisorblade@yahoo.it' => 'Paolo \'Blaisorblade\' Giarrusso',
-       'bunk@stusta.de' => 'Adrian Bunk',
-       'domen@coderock.org' => 'Domen Puncer',
-       'dougg@torque.net' => 'Douglas Gilbert',
-       'dwmw2@shinybook.infradead.org' => 'David Woodhouse',
-       'ecashin@coraid.com' => 'Ed L Cashin',
-       'felix@derklecks.de' => 'Felix Moeller',
-       'fzago@systemfabricworks.com' => 'Frank Zago',
-       'gregkh@suse.de' => 'Greg Kroah-Hartman',
-       'hch@lst.de' => 'Christoph Hellwig',
-       'htejun@gmail.com' => 'Tejun Heo',
-       'jejb@mulgrave.(none)' => 'James Bottomley',
-       'jejb@titanic.il.steeleye.com' => 'James Bottomley',
-       'jgarzik@pretzel.yyz.us' => 'Jeff Garzik',
-       'johnpol@2ka.mipt.ru' => 'Evgeniy Polyakov',
-       'kay.sievers@vrfy.org' => 'Kay Sievers',
-       'minyard@acm.org' => 'Corey Minyard',
-       'mshah@teja.com' => 'Mitesh shah',
-       'pj@ludd.ltu.se' => 'Peter A Jonsson',
-       'rmps@joel.ist.utl.pt' => 'Rui Saraiva',
-       'santtu.hyrkko@gmail.com' => 'Santtu Hyrkkö',
-       'simon@thekelleys.org.uk' => 'Simon Kelley',
-       'ssant@in.ibm.com' => 'Sachin P Sant',
-       'terra@gnome.org' => 'Morten Welinder',
-       'tony.luck@intel.com' => 'Tony Luck',
-       'welinder@anemone.rentec.com' => 'Morten Welinder',
-       'welinder@darter.rentec.com' => 'Morten Welinder',
-       'welinder@troll.com' => 'Morten Welinder',
-);
-
+my (%mailmap);
+my (%email);
 my (%map);
 my $pstate = 1;
 my $n_records = 0;
 my $n_output = 0;
 
-
 sub shortlog_entry($$) {
        my ($name, $desc) = @_;
        my $key = $name;
@@ -108,41 +66,35 @@ sub changelog_input {
                if ($pstate == 1) {
                        my ($email);
 
-                       next unless /^[Aa]uthor:? (.*)<(.*)>.*$/;
-       
+                       next unless /^[Aa]uthor:?\s*(.*?)\s*<(.*)>/;
+
                        $n_records++;
-       
+
                        $author = $1;
                        $email = $2;
                        $desc = undef;
 
-                       # trim trailing whitespace.
-                       # why doesn't chomp work?
-                       while ($author && ($author =~ /\s$/)) {
-                               chop $author;
-                       }
-       
                        # cset author fixups
                        if (exists $mailmap{$email}) {
                                $author = $mailmap{$email};
                        } elsif (exists $mailmap{$author}) {
                                $author = $mailmap{$author};
-                       } elsif ((!$author) || ($author eq "")) {
+                       } elsif (!$author) {
                                $author = $email;
                        }
-       
+                       $email{$author}{$email}++;
                        $pstate++;
                }
-       
+
                # skip to blank line
                elsif ($pstate == 2) {
                        next unless /^\s*$/;
                        $pstate++;
                }
-       
+
                # skip to non-blank line
                elsif ($pstate == 3) {
-                       next unless /^\s*(\S.*)$/;
+                       next unless /^\s*?(.*)/;
 
                        # skip lines that are obviously not
                        # a 1-line cset description
@@ -150,9 +102,9 @@ sub changelog_input {
 
                        chomp;
                        $desc = $1;
-       
+
                        &shortlog_entry($author, $desc);
-       
+
                        $pstate = 1;
                }
        
@@ -162,16 +114,87 @@ sub changelog_input {
        }
 }
 
+sub read_mailmap {
+       my ($fh, $mailmap) = @_;
+       while (<$fh>) {
+               chomp;
+               if (/^([^#].*?)\s*<(.*)>/) {
+                       $mailmap->{$2} = $1;
+               }
+       }
+}
+
+sub setup_mailmap {
+       read_mailmap(\*DATA, \%mailmap);
+       if (-f '.mailmap') {
+               my $fh = undef;
+               open $fh, '<', '.mailmap';
+               read_mailmap($fh, \%mailmap);
+               close $fh;
+       }
+}
+
 sub finalize {
        #print "\n$n_records records parsed.\n";
 
        if ($n_records != $n_output) {
                die "parse error: input records != output records\n";
        }
+       if (0) {
+               for my $author (sort keys %email) {
+                       my $e = $email{$author};
+                       for my $email (sort keys %$e) {
+                               print STDERR "$author <$email>\n";
+                       }
+               }
+       }
 }
 
+&setup_mailmap;
 &changelog_input;
 &shortlog_output;
 &finalize;
 exit(0);
 
+
+__DATA__
+#
+# Even with git, we don't always have name translations.
+# So have an email->real name table to translate the
+# (hopefully few) missing names
+#
+Adrian Bunk <bunk@stusta.de>
+Andreas Herrmann <aherrman@de.ibm.com>
+Andrew Morton <akpm@osdl.org>
+Andrew Vasquez <andrew.vasquez@qlogic.com>
+Christoph Hellwig <hch@lst.de>
+Corey Minyard <minyard@acm.org>
+David Woodhouse <dwmw2@shinybook.infradead.org>
+Domen Puncer <domen@coderock.org>
+Douglas Gilbert <dougg@torque.net>
+Ed L Cashin <ecashin@coraid.com>
+Evgeniy Polyakov <johnpol@2ka.mipt.ru>
+Felix Moeller <felix@derklecks.de>
+Frank Zago <fzago@systemfabricworks.com>
+Greg Kroah-Hartman <gregkh@suse.de>
+James Bottomley <jejb@mulgrave.(none)>
+James Bottomley <jejb@titanic.il.steeleye.com>
+Jeff Garzik <jgarzik@pretzel.yyz.us>
+Jens Axboe <axboe@suse.de>
+Kay Sievers <kay.sievers@vrfy.org>
+Mitesh shah <mshah@teja.com>
+Morten Welinder <terra@gnome.org>
+Morten Welinder <welinder@anemone.rentec.com>
+Morten Welinder <welinder@darter.rentec.com>
+Morten Welinder <welinder@troll.com>
+Nguyen Anh Quynh <aquynh@gmail.com>
+Paolo 'Blaisorblade' Giarrusso <blaisorblade@yahoo.it>
+Peter A Jonsson <pj@ludd.ltu.se>
+Ralf Wildenhues <Ralf.Wildenhues@gmx.de>
+Rudolf Marek <R.Marek@sh.cvut.cz>
+Rui Saraiva <rmps@joel.ist.utl.pt>
+Sachin P Sant <ssant@in.ibm.com>
+Santtu Hyrkk\e,Av\e(B <santtu.hyrkko@gmail.com>
+Simon Kelley <simon@thekelleys.org.uk>
+Tejun Heo <htejun@gmail.com>
+Tony Luck <tony.luck@intel.com>
index 621fa49d2bcad6c5343ac5c172fb9ca6a855c18d..837f334d8760c9ff4af4de5d057e97d7af3cfc61 100755 (executable)
@@ -11,7 +11,7 @@ report () {
 #
 "
   trailer=""
-  while read oldmode mode oldsha sha status name newname
+  while read status name newname
   do
     echo -n "$header"
     header=""
@@ -31,18 +31,21 @@ report () {
   [ "$header" ]
 }
 
-branch=`readlink "$GIT_DIR/HEAD"`
+branch=$(GIT_DIR="$GIT_DIR" git-symbolic-ref HEAD)
 case "$branch" in
 refs/heads/master) ;;
 *)     echo "# On branch $branch" ;;
 esac
 
-git-update-index --refresh >/dev/null 2>&1
+git-update-index -q --unmerged --refresh || exit
 
-if test -f "$GIT_DIR/HEAD"
+if GIT_DIR="$GIT_DIR" git-rev-parse --verify HEAD >/dev/null 2>&1
 then
-       git-diff-index -M --cached HEAD |
-       sed 's/^://' |
+       git-diff-index -M --cached --name-status --diff-filter=MDTCRA HEAD |
+       sed -e '
+               s/\\/\\\\/g
+               s/ /\\ /g
+       ' |
        report "Updated but not checked in" "will commit"
 
        committable="$?"
@@ -51,31 +54,49 @@ else
 # Initial commit
 #'
        git-ls-files |
-       sed 's/^/o o o o A /' |
+       sed -e '
+               s/\\/\\\\/g
+               s/ /\\ /g
+               s/^/A /
+       ' |
        report "Updated but not checked in" "will commit"
 
        committable="$?"
 fi
 
-git-diff-files |
-sed 's/^://' |
+git-diff-files  --name-status |
+sed -e '
+       s/\\/\\\\/g
+       s/ /\\ /g
+' |
 report "Changed but not updated" "use git-update-index to mark for commit"
 
-if grep -v '^#' "$GIT_DIR/info/exclude" >/dev/null 2>&1
+
+if test -f "$GIT_DIR/info/exclude"
 then
-       git-ls-files --others \
-           --exclude-from="$GIT_DIR/info/exclude" \
-           --exclude-per-directory=.gitignore |
-       sed -e '
-       1i\
-#\
-# Ignored files:\
-#   (use "git add" to add to commit)\
-#
-       s/^/#   /
-       $a\
-#'
-fi
+    git-ls-files -z --others \
+       --exclude-from="$GIT_DIR/info/exclude" \
+        --exclude-per-directory=.gitignore
+else
+    git-ls-files -z --others \
+        --exclude-per-directory=.gitignore
+fi |
+perl -e '$/ = "\0";
+       my $shown = 0;
+       while (<>) {
+               chomp;
+               s|\\|\\\\|g;
+               s|\t|\\t|g;
+               s|\n|\\n|g;
+               s/^/#   /;
+               if (!$shown) {
+                       print "#\n# Untracked files:\n";
+                       print "#   (use \"git add\" to add to commit)\n#\n";
+                       $shown = 1;
+               }
+               print "$_\n";
+       }
+'
 
 case "$committable" in
 0)
diff --git a/git-svnimport.perl b/git-svnimport.perl
new file mode 100755 (executable)
index 0000000..45b6a19
--- /dev/null
@@ -0,0 +1,717 @@
+#!/usr/bin/perl -w
+
+# This tool is copyright (c) 2005, Matthias Urlichs.
+# It is released under the Gnu Public License, version 2.
+#
+# The basic idea is to pull and analyze SVN changes.
+#
+# Checking out the files is done by a single long-running SVN connection.
+#
+# 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;
+use File::Spec;
+use File::Temp qw(tempfile);
+use File::Path qw(mkpath);
+use File::Basename qw(basename dirname);
+use Time::Local;
+use IO::Pipe;
+use POSIX qw(strftime dup2);
+use IPC::Open2;
+use SVN::Core;
+use SVN::Ra;
+
+die "Need CVN:Core 1.2.1 or better" if $SVN::Core::VERSION lt "1.2.1";
+
+$SIG{'PIPE'}="IGNORE";
+$ENV{'TZ'}="UTC";
+
+our($opt_h,$opt_o,$opt_v,$opt_u,$opt_C,$opt_i,$opt_m,$opt_M,$opt_t,$opt_T,$opt_b,$opt_s,$opt_l,$opt_d,$opt_D);
+
+sub usage() {
+       print STDERR <<END;
+Usage: ${\basename $0}     # fetch/update GIT from CVS
+       [-o branch-for-HEAD] [-h] [-v] [-l max_num_changes]
+       [-C GIT_repository] [-t tagname] [-T trunkname] [-b branchname]
+       [-d|-D] [-i] [-u] [-s start_chg] [-m] [-M regex] [SVN_URL]
+END
+       exit(1);
+}
+
+getopts("b:C:dDhil:mM:o:s:t:T:uv") or usage();
+usage if $opt_h;
+
+my $tag_name = $opt_t || "tags";
+my $trunk_name = $opt_T || "trunk";
+my $branch_name = $opt_b || "branches";
+
+@ARGV == 1 or @ARGV == 2 or usage();
+
+$opt_o ||= "origin";
+$opt_s ||= 1;
+$opt_l = 100 unless defined $opt_l;
+my $git_tree = $opt_C;
+$git_tree ||= ".";
+
+my $svn_url = $ARGV[0];
+my $svn_dir = $ARGV[1];
+
+our @mergerx = ();
+if ($opt_m) {
+       @mergerx = ( qr/\W(?:from|of|merge|merging|merged) (\w+)/i );
+}
+if ($opt_M) {
+       push (@mergerx, qr/$opt_M/);
+}
+
+select(STDERR); $|=1; select(STDOUT);
+
+
+package SVNconn;
+# Basic SVN connection.
+# We're only interested in connecting and downloading, so ...
+
+use File::Spec;
+use File::Temp qw(tempfile);
+use POSIX qw(strftime dup2);
+
+sub new {
+       my($what,$repo) = @_;
+       $what=ref($what) if ref($what);
+
+       my $self = {};
+       $self->{'buffer'} = "";
+       bless($self,$what);
+
+       $repo =~ s#/+$##;
+       $self->{'fullrep'} = $repo;
+       $self->conn();
+
+       return $self;
+}
+
+sub conn {
+       my $self = shift;
+       my $repo = $self->{'fullrep'};
+       my $s = SVN::Ra->new($repo);
+
+       die "SVN connection to $repo: $!\n" unless defined $s;
+       $self->{'svn'} = $s;
+       $self->{'repo'} = $repo;
+       $self->{'maxrev'} = $s->get_latest_revnum();
+}
+
+sub file {
+       my($self,$path,$rev) = @_;
+
+       my ($fh, $name) = tempfile('gitsvn.XXXXXX',
+                   DIR => File::Spec->tmpdir(), UNLINK => 1);
+
+       print "... $rev $path ...\n" if $opt_v;
+       eval { $self->{'svn'}->get_file($path,$rev,$fh); };
+       if($@) {
+               return undef if $@ =~ /Attempted to get checksum/;
+               die $@;
+       }
+       close ($fh);
+
+       return $name;
+}
+
+package main;
+use URI;
+
+my $svn = $svn_url;
+$svn .= "/$svn_dir" if defined $svn_dir;
+$svn = SVNconn->new($svn);
+
+my $lwp_ua;
+if($opt_d or $opt_D) {
+       $svn_url = URI->new($svn_url)->canonical;
+       if($opt_D) {
+               $svn_dir =~ s#/*$#/#;
+       } else {
+               $svn_dir = "";
+       }
+       if ($svn_url->scheme eq "http") {
+               use LWP::UserAgent;
+               $lwp_ua = LWP::UserAgent->new(keep_alive => 1, requests_redirectable => []);
+       } else {
+               print STDERR "Warning: not HTTP; turning off direct file access\n";
+               $opt_d=0;
+       }
+}
+
+sub pdate($) {
+       my($d) = @_;
+       $d =~ m#(\d\d\d\d)-(\d\d)-(\d\d)T(\d\d):(\d\d):(\d\d)#
+               or die "Unparseable date: $d\n";
+       my $y=$1; $y-=1900 if $y>1900;
+       return timegm($6||0,$5,$4,$3,$2-1,$y);
+}
+
+sub getwd() {
+       my $pwd = `pwd`;
+       chomp $pwd;
+       return $pwd;
+}
+
+
+sub get_headref($$) {
+    my $name    = shift;
+    my $git_dir = shift;
+    my $sha;
+
+    if (open(C,"$git_dir/refs/heads/$name")) {
+       chomp($sha = <C>);
+       close(C);
+       length($sha) == 40
+           or die "Cannot get head id for $name ($sha): $!\n";
+    }
+    return $sha;
+}
+
+
+-d $git_tree
+       or mkdir($git_tree,0777)
+       or die "Could not create $git_tree: $!";
+chdir($git_tree);
+
+my $orig_branch = "";
+my $forward_master = 0;
+my %branches;
+
+my $git_dir = $ENV{"GIT_DIR"} || ".git";
+$git_dir = getwd()."/".$git_dir unless $git_dir =~ m#^/#;
+$ENV{"GIT_DIR"} = $git_dir;
+my $orig_git_index;
+$orig_git_index = $ENV{GIT_INDEX_FILE} if exists $ENV{GIT_INDEX_FILE};
+my ($git_ih, $git_index) = tempfile('gitXXXXXX', SUFFIX => '.idx',
+                                   DIR => File::Spec->tmpdir());
+close ($git_ih);
+$ENV{GIT_INDEX_FILE} = $git_index;
+my $maxnum = 0;
+my $last_rev = "";
+my $last_branch;
+my $current_rev = $opt_s-1;
+unless(-d $git_dir) {
+       system("git-init-db");
+       die "Cannot init the GIT db at $git_tree: $?\n" if $?;
+       system("git-read-tree");
+       die "Cannot init an empty tree: $?\n" if $?;
+
+       $last_branch = $opt_o;
+       $orig_branch = "";
+} else {
+       -f "$git_dir/refs/heads/$opt_o"
+               or die "Branch '$opt_o' does not exist.\n".
+                      "Either use the correct '-o branch' option,\n".
+                      "or import to a new repository.\n";
+
+       -f "$git_dir/svn2git"
+               or die "'$git_dir/svn2git' does not exist.\n".
+                      "You need that file for incremental imports.\n";
+       $last_branch = basename(readlink("$git_dir/HEAD"));
+       unless($last_branch) {
+               warn "Cannot read the last branch name: $! -- assuming 'master'\n";
+               $last_branch = "master";
+       }
+       $orig_branch = $last_branch;
+       $last_rev = get_headref($orig_branch, $git_dir);
+       if (-f "$git_dir/SVN2GIT_HEAD") {
+               die <<EOM;
+SVN2GIT_HEAD exists.
+Make sure your working directory corresponds to HEAD and remove SVN2GIT_HEAD.
+You may need to run
+
+    git-read-tree -m -u SVN2GIT_HEAD HEAD
+EOM
+       }
+       system('cp', "$git_dir/HEAD", "$git_dir/SVN2GIT_HEAD");
+
+       $forward_master =
+           $opt_o ne 'master' && -f "$git_dir/refs/heads/master" &&
+           system('cmp', '-s', "$git_dir/refs/heads/master",
+                               "$git_dir/refs/heads/$opt_o") == 0;
+
+       # populate index
+       system('git-read-tree', $last_rev);
+       die "read-tree failed: $?\n" if $?;
+
+       # Get the last import timestamps
+       open my $B,"<", "$git_dir/svn2git";
+       while(<$B>) {
+               chomp;
+               my($num,$branch,$ref) = split;
+               $branches{$branch}{$num} = $ref;
+               $branches{$branch}{"LAST"} = $ref;
+               $current_rev = $num if $current_rev < $num;
+       }
+       close($B);
+}
+-d $git_dir
+       or die "Could not create git subdir ($git_dir).\n";
+
+open BRANCHES,">>", "$git_dir/svn2git";
+
+sub get_file($$$) {
+       my($rev,$branch,$path) = @_;
+
+       # revert split_path(), below
+       my $svnpath;
+       $path = "" if $path eq "/"; # this should not happen, but ...
+       if($branch eq "/") {
+               $svnpath = "$trunk_name/$path";
+       } elsif($branch =~ m#^/#) {
+               $svnpath = "$tag_name$branch/$path";
+       } else {
+               $svnpath = "$branch_name/$branch/$path";
+       }
+
+       # now get it
+       my $name;
+       if($opt_d) {
+               my($req,$res);
+
+               # /svn/!svn/bc/2/django/trunk/django-docs/build.py
+               my $url=$svn_url->clone();
+               $url->path($url->path."/!svn/bc/$rev/$svn_dir$svnpath");
+               print "... $path...\n" if $opt_v;
+               $req = HTTP::Request->new(GET => $url);
+               $res = $lwp_ua->request($req);
+               if ($res->is_success) {
+                       my $fh;
+                       ($fh, $name) = tempfile('gitsvn.XXXXXX',
+                       DIR => File::Spec->tmpdir(), UNLINK => 1);
+                       print $fh $res->content;
+                       close($fh) or die "Could not write $name: $!\n";
+               } else {
+                       return undef if $res->code == 301; # directory?
+                       die $res->status_line." at $url\n";
+               }
+       } else {
+               $name = $svn->file("/$svnpath",$rev);
+               return undef unless defined $name;
+       }
+
+       open my $F, '-|', "git-hash-object", "-w", $name
+               or die "Cannot create object: $!\n";
+       my $sha = <$F>;
+       chomp $sha;
+       close $F;
+       unlink $name;
+       my $mode = "0644"; # SV does not seem to store any file modes
+       return [$mode, $sha, $path];
+}
+
+sub split_path($$) {
+       my($rev,$path) = @_;
+       my $branch;
+
+       if($path =~ s#^/\Q$tag_name\E/([^/]+)/?##) {
+               $branch = "/$1";
+       } elsif($path =~ s#^/\Q$trunk_name\E/?##) {
+               $branch = "/";
+       } elsif($path =~ s#^/\Q$branch_name\E/([^/]+)/?##) {
+               $branch = $1;
+       } else {
+               print STDERR "$rev: Unrecognized path: $path\n";
+               return ()
+       }
+       $path = "/" if $path eq "";
+       return ($branch,$path);
+}
+
+sub copy_subdir($$$$$$) {
+       # Somebody copied a whole subdirectory.
+       # We need to find the index entries from the old version which the
+       # SVN log entry points to, and add them to the new place.
+
+       my($newrev,$newbranch,$path,$oldpath,$rev,$new) = @_;
+       my($branch,$srcpath) = split_path($rev,$oldpath);
+
+       my $gitrev = $branches{$branch}{$rev};
+       unless($gitrev) {
+               print STDERR "$newrev:$newbranch: could not find $oldpath \@ $rev\n";
+               return;
+       }
+       print "$newrev:$newbranch:$path: copying from $branch:$srcpath @ $rev\n" if $opt_v;
+       $srcpath =~ s#/*$#/#;
+       open my $f,"-|","git-ls-tree","-r","-z",$gitrev,$srcpath;
+       local $/ = "\0";
+       while(<$f>) {
+               chomp;
+               my($m,$p) = split(/\t/,$_,2);
+               my($mode,$type,$sha1) = split(/ /,$m);
+               next if $type ne "blob";
+               $p = substr($p,length($srcpath)-1);
+               print "... found $path$p ...\n" if $opt_v;
+               push(@$new,[$mode,$sha1,$path.$p]);
+       }
+       close($f) or
+               print STDERR "$newrev:$newbranch: could not list files in $oldpath \@ $rev\n";
+}
+
+sub commit {
+       my($branch, $changed_paths, $revision, $author, $date, $message) = @_;
+       my($author_name,$author_email,$dest);
+       my(@old,@new);
+
+       if (not defined $author) {
+               $author_name = $author_email = "unknown";
+       } elsif ($author =~ /^(.*?)\s+<(.*)>$/) {
+               ($author_name, $author_email) = ($1, $2);
+       } else {
+               $author =~ s/^<(.*)>$/$1/;
+               $author_name = $author_email = $author;
+       }
+       $date = pdate($date);
+
+       my $tag;
+       my $parent;
+       if($branch eq "/") { # trunk
+               $parent = $opt_o;
+       } elsif($branch =~ m#^/(.+)#) { # tag
+               $tag = 1;
+               $parent = $1;
+       } else { # "normal" branch
+               # nothing to do
+               $parent = $branch;
+       }
+       $dest = $parent;
+
+       my $prev = $changed_paths->{"/"};
+       if($prev and $prev->[0] eq "A") {
+               delete $changed_paths->{"/"};
+               my $oldpath = $prev->[1];
+               my $rev;
+               if(defined $oldpath) {
+                       my $p;
+                       ($parent,$p) = split_path($revision,$oldpath);
+                       if($parent eq "/") {
+                               $parent = $opt_o;
+                       } else {
+                               $parent =~ s#^/##; # if it's a tag
+                       }
+               } else {
+                       $parent = undef;
+               }
+       }
+
+       my $rev;
+       if($revision > $opt_s and defined $parent) {
+               open(H,"git-rev-parse --verify $parent |");
+               $rev = <H>;
+               close(H) or do {
+                       print STDERR "$revision: cannot find commit '$parent'!\n";
+                       return;
+               };
+               chop $rev;
+               if(length($rev) != 40) {
+                       print STDERR "$revision: cannot find commit '$parent'!\n";
+                       return;
+               }
+               $rev = $branches{($parent eq $opt_o) ? "/" : $parent}{"LAST"};
+               if($revision != $opt_s and not $rev) {
+                       print STDERR "$revision: do not know ancestor for '$parent'!\n";
+                       return;
+               }
+       } else {
+               $rev = undef;
+       }
+
+#      if($prev and $prev->[0] eq "A") {
+#              if(not $tag) {
+#                      unless(open(H,"> $git_dir/refs/heads/$branch")) {
+#                              print STDERR "$revision: Could not create branch $branch: $!\n";
+#                              $state=11;
+#                              next;
+#                      }
+#                      print H "$rev\n"
+#                              or die "Could not write branch $branch: $!";
+#                      close(H)
+#                              or die "Could not write branch $branch: $!";
+#              }
+#      }
+       if(not defined $rev) {
+               unlink($git_index);
+       } elsif ($rev ne $last_rev) {
+               print "Switching from $last_rev to $rev ($branch)\n" if $opt_v;
+               system("git-read-tree", $rev);
+               die "read-tree failed for $rev: $?\n" if $?;
+               $last_rev = $rev;
+       }
+
+       my $cid;
+       if($tag and not %$changed_paths) {
+               $cid = $rev;
+       } else {
+               my @paths = sort keys %$changed_paths;
+               foreach my $path(@paths) {
+                       my $action = $changed_paths->{$path};
+
+                       if ($action->[0] eq "A") {
+                               my $f = get_file($revision,$branch,$path);
+                               if($f) {
+                                       push(@new,$f) if $f;
+                               } elsif($action->[1]) {
+                                       copy_subdir($revision,$branch,$path,$action->[1],$action->[2],\@new);
+                               } else {
+                                       my $opath = $action->[3];
+                                       print STDERR "$revision: $branch: could not fetch '$opath'\n";
+                               }
+                       } elsif ($action->[0] eq "D") {
+                               push(@old,$path);
+                       } elsif ($action->[0] eq "M") {
+                               my $f = get_file($revision,$branch,$path);
+                               push(@new,$f) if $f;
+                       } elsif ($action->[0] eq "R") {
+                               # refer to a file/tree in an earlier commit
+                               push(@old,$path); # remove any old stuff
+
+                               # ... and add any new stuff
+                               my($b,$srcpath) = split_path($revision,$action->[1]);
+                               $srcpath =~ s#/*$#/#;
+                               open my $F,"-|","git-ls-tree","-r","-z", $branches{$b}{$action->[2]}, $srcpath;
+                               local $/ = "\0";
+                               while(<$F>) {
+                                       chomp;
+                                       my($m,$p) = split(/\t/,$_,2);
+                                       my($mode,$type,$sha1) = split(/ /,$m);
+                                       next if $type ne "blob";
+                                       $p = substr($p,length($srcpath)-1);
+                                       push(@new,[$mode,$sha1,$path.$p]);
+                               }
+                               close($F);
+                       } else {
+                               die "$revision: unknown action '".$action->[0]."' for $path\n";
+                       }
+               }
+
+               if(@old) {
+                       open my $F, "-|", "git-ls-files", "-z", @old or die $!;
+                       @old = ();
+                       local $/ = "\0";
+                       while(<$F>) {
+                               chomp;
+                               push(@old,$_);
+                       }
+                       close($F);
+
+                       while(@old) {
+                               my @o2;
+                               if(@old > 55) {
+                                       @o2 = splice(@old,0,50);
+                               } else {
+                                       @o2 = @old;
+                                       @old = ();
+                               }
+                               system("git-update-index","--force-remove","--",@o2);
+                               die "Cannot remove files: $?\n" if $?;
+                       }
+               }
+               while(@new) {
+                       my @n2;
+                       if(@new > 12) {
+                               @n2 = splice(@new,0,10);
+                       } else {
+                               @n2 = @new;
+                               @new = ();
+                       }
+                       system("git-update-index","--add",
+                               (map { ('--cacheinfo', @$_) } @n2));
+                       die "Cannot add files: $?\n" if $?;
+               }
+
+               my $pid = open(C,"-|");
+               die "Cannot fork: $!" unless defined $pid;
+               unless($pid) {
+                       exec("git-write-tree");
+                       die "Cannot exec git-write-tree: $!\n";
+               }
+               chomp(my $tree = <C>);
+               length($tree) == 40
+                       or die "Cannot get tree id ($tree): $!\n";
+               close(C)
+                       or die "Error running git-write-tree: $?\n";
+               print "Tree ID $tree\n" if $opt_v;
+
+               my $pr = IO::Pipe->new() or die "Cannot open pipe: $!\n";
+               my $pw = IO::Pipe->new() or die "Cannot open pipe: $!\n";
+               $pid = fork();
+               die "Fork: $!\n" unless defined $pid;
+               unless($pid) {
+                       $pr->writer();
+                       $pw->reader();
+                       open(OUT,">&STDOUT");
+                       dup2($pw->fileno(),0);
+                       dup2($pr->fileno(),1);
+                       $pr->close();
+                       $pw->close();
+
+                       my @par = ();
+                       @par = ("-p",$rev) if defined $rev;
+
+                       # loose detection of merges
+                       # based on the commit msg
+                       foreach my $rx (@mergerx) {
+                               if ($message =~ $rx) {
+                                       my $mparent = $1;
+                                       if ($mparent eq 'HEAD') { $mparent = $opt_o };
+                                       if ( -e "$git_dir/refs/heads/$mparent") {
+                                               $mparent = get_headref($mparent, $git_dir);
+                                               push @par, '-p', $mparent;
+                                               print OUT "Merge parent branch: $mparent\n" if $opt_v;
+                                       }
+                               }
+                       }
+
+                       exec("env",
+                               "GIT_AUTHOR_NAME=$author_name",
+                               "GIT_AUTHOR_EMAIL=$author_email",
+                               "GIT_AUTHOR_DATE=".strftime("+0000 %Y-%m-%d %H:%M:%S",gmtime($date)),
+                               "GIT_COMMITTER_NAME=$author_name",
+                               "GIT_COMMITTER_EMAIL=$author_email",
+                               "GIT_COMMITTER_DATE=".strftime("+0000 %Y-%m-%d %H:%M:%S",gmtime($date)),
+                               "git-commit-tree", $tree,@par);
+                       die "Cannot exec git-commit-tree: $!\n";
+               }
+               $pw->writer();
+               $pr->reader();
+
+               $message =~ s/[\s\n]+\z//;
+
+               print $pw "$message\n"
+                       or die "Error writing to git-commit-tree: $!\n";
+               $pw->close();
+
+               print "Committed change $revision:$branch ".strftime("%Y-%m-%d %H:%M:%S",gmtime($date)).")\n" if $opt_v;
+               chomp($cid = <$pr>);
+               length($cid) == 40
+                       or die "Cannot get commit id ($cid): $!\n";
+               print "Commit ID $cid\n" if $opt_v;
+               $pr->close();
+
+               waitpid($pid,0);
+               die "Error running git-commit-tree: $?\n" if $?;
+       }
+
+       if(not defined $dest) {
+               print "... no known parent\n" if $opt_v;
+       } elsif(not $tag) {
+               print "Writing to refs/heads/$dest\n" if $opt_v;
+               open(C,">$git_dir/refs/heads/$dest") and
+               print C ("$cid\n") and
+               close(C)
+                       or die "Cannot write branch $dest for update: $!\n";
+       }
+
+       if($tag) {
+               my($in, $out) = ('','');
+               $last_rev = "-" if %$changed_paths;
+               # the tag was 'complex', i.e. did not refer to a "real" revision
+
+               $dest =~ tr/_/\./ if $opt_u;
+
+               my $pid = open2($in, $out, 'git-mktag');
+               print $out ("object $cid\n".
+                   "type commit\n".
+                   "tag $dest\n".
+                   "tagger $author_name <$author_email>\n") and
+               close($out)
+                   or die "Cannot create tag object $dest: $!\n";
+
+               my $tagobj = <$in>;
+               chomp $tagobj;
+
+               if ( !close($in) or waitpid($pid, 0) != $pid or
+                               $? != 0 or $tagobj !~ /^[0123456789abcdef]{40}$/ ) {
+                       die "Cannot create tag object $dest: $!\n";
+               }
+
+               open(C,">$git_dir/refs/tags/$dest") and
+               print C ("$tagobj\n") and
+               close(C)
+                       or die "Cannot create tag $branch: $!\n";
+
+               print "Created tag '$dest' on '$branch'\n" if $opt_v;
+       }
+       $branches{$branch}{"LAST"} = $cid;
+       $branches{$branch}{$revision} = $cid;
+       $last_rev = $cid;
+       print BRANCHES "$revision $branch $cid\n";
+       print "DONE: $revision $dest $cid\n" if $opt_v;
+}
+
+my ($changed_paths, $revision, $author, $date, $message, $pool) = @_;
+sub _commit_all {
+       ($changed_paths, $revision, $author, $date, $message, $pool) = @_;
+       my %p;
+       while(my($path,$action) = each %$changed_paths) {
+               $p{$path} = [ $action->action,$action->copyfrom_path, $action->copyfrom_rev, $path ];
+       }
+       $changed_paths = \%p;
+}
+
+sub commit_all {
+       my %done;
+       my @col;
+       my $pref;
+       my $branch;
+
+       while(my($path,$action) = each %$changed_paths) {
+               ($branch,$path) = split_path($revision,$path);
+               next if not defined $branch;
+               $done{$branch}{$path} = $action;
+       }
+       while(($branch,$changed_paths) = each %done) {
+               commit($branch, $changed_paths, $revision, $author, $date, $message);
+       }
+}
+
+while(++$current_rev <= $svn->{'maxrev'}) {
+       $svn->{'svn'}->get_log("/",$current_rev,$current_rev,$current_rev,1,1,\&_commit_all,"");
+       commit_all();
+       if($opt_l and not --$opt_l) {
+               print STDERR "Stopping, because there is a memory leak (in the SVN library).\n";
+               print STDERR "Please repeat this command; it will continue safely\n";
+               last;
+       }
+}
+
+
+unlink($git_index);
+
+if (defined $orig_git_index) {
+       $ENV{GIT_INDEX_FILE} = $orig_git_index;
+} else {
+       delete $ENV{GIT_INDEX_FILE};
+}
+
+# Now switch back to the branch we were in before all of this happened
+if($orig_branch) {
+       print "DONE\n" if $opt_v and (not defined $opt_l or $opt_l > 0);
+       system("cp","$git_dir/refs/heads/$opt_o","$git_dir/refs/heads/master")
+               if $forward_master;
+       unless ($opt_i) {
+               system('git-read-tree', '-m', '-u', 'SVN2GIT_HEAD', 'HEAD');
+               die "read-tree failed: $?\n" if $?;
+       }
+} else {
+       $orig_branch = "master";
+       print "DONE; creating $orig_branch branch\n" if $opt_v and (not defined $opt_l or $opt_l > 0);
+       system("cp","$git_dir/refs/heads/$opt_o","$git_dir/refs/heads/master")
+               unless -f "$git_dir/refs/heads/master";
+       unlink("$git_dir/HEAD");
+       symlink("refs/heads/$orig_branch","$git_dir/HEAD");
+       unless ($opt_i) {
+               system('git checkout');
+               die "checkout failed: $?\n" if $?;
+       }
+}
+unlink("$git_dir/SVN2GIT_HEAD");
+close(BRANCHES);
index 76c1bcd8c9b5a42d4dedab2f3ea7261d91427538..bd9275367405fa0b8c10a2e1394195cdbcfc632c 100755 (executable)
@@ -4,7 +4,7 @@
 . git-sh-setup || die "Not a git archive"
 
 usage () {
-    echo >&2 "Usage: git-tag [-a | -s] [-f] [-m "tag message"] tagname"
+    echo >&2 "Usage: git-tag [-a | -s | -u <key-id>] [-f] [-m <msg>] <tagname> [<head>]"
     exit 1
 }
 
@@ -12,6 +12,7 @@ annotate=
 signed=
 force=
 message=
+username=
 while case "$#" in 0) break ;; esac
 do
     case "$1" in
@@ -30,6 +31,12 @@ do
        shift
        message="$1"
        ;;
+    -u)
+       annotate=1
+       signed=1
+       shift
+       username="$1"
+       ;;
     -*)
         usage
        ;;
@@ -46,36 +53,43 @@ if [ -e "$GIT_DIR/refs/tags/$name" -a -z "$force" ]; then
     die "tag '$name' already exists"
 fi
 shift
+git-check-ref-format "tags/$name" ||
+       die "we do not like '$name' as a tag name."
 
 object=$(git-rev-parse --verify --default HEAD "$@") || exit 1
 type=$(git-cat-file -t $object) || exit 1
 tagger=$(git-var GIT_COMMITTER_IDENT) || exit 1
+: ${username:=$(expr "$tagger" : '\(.*>\)')}
 
-trap 'rm -f .tmp-tag* .tagmsg .editmsg' 0
+trap 'rm -f "$GIT_DIR"/TAG_TMP* "$GIT_DIR"/TAG_FINALMSG "$GIT_DIR"/TAG_EDITMSG' 0
 
 if [ "$annotate" ]; then
     if [ -z "$message" ]; then
         ( echo "#"
           echo "# Write a tag message"
-          echo "#" ) > .editmsg
-        ${VISUAL:-${EDITOR:-vi}} .editmsg || exit
+          echo "#" ) > "$GIT_DIR"/TAG_EDITMSG
+        ${VISUAL:-${EDITOR:-vi}} "$GIT_DIR"/TAG_EDITMSG || exit
     else
-        echo "$message" > .editmsg
+        echo "$message" >"$GIT_DIR"/TAG_EDITMSG
     fi
 
-    grep -v '^#' < .editmsg | git-stripspace > .tagmsg
+    grep -v '^#' <"$GIT_DIR"/TAG_EDITMSG |
+    git-stripspace >"$GIT_DIR"/TAG_FINALMSG
 
-    [ -s .tagmsg ] || exit
+    [ -s "$GIT_DIR"/TAG_FINALMSG ] || {
+       echo >&2 "No tag message?"
+       exit 1
+    }
 
-    ( echo -e "object $object\ntype $type\ntag $name\ntagger $tagger\n"; cat .tagmsg ) > .tmp-tag
-    rm -f .tmp-tag.asc .tagmsg
+    ( echo -e "object $object\ntype $type\ntag $name\ntagger $tagger\n";
+      cat "$GIT_DIR"/TAG_FINALMSG ) >"$GIT_DIR"/TAG_TMP
+    rm -f "$GIT_DIR"/TAG_TMP.asc "$GIT_DIR"/TAG_FINALMSG
     if [ "$signed" ]; then
-       me=$(expr "$tagger" : '\(.*>\)') &&
-       gpg -bsa -u "$me" .tmp-tag &&
-       cat .tmp-tag.asc >>.tmp-tag ||
+       gpg -bsa -u "$username" "$GIT_DIR"/TAG_TMP &&
+       cat "$GIT_DIR"/TAG_TMP.asc >>"$GIT_DIR"/TAG_TMP ||
        die "failed to sign the tag with GPG."
     fi
-    object=$(git-mktag < .tmp-tag)
+    object=$(git-mktag < "$GIT_DIR"/TAG_TMP)
 fi
 
 mkdir -p "$GIT_DIR/refs/tags"
index 156c75bb3e4fb0c46ed9bff67b3fb48d795b41fb..ed4c89396841d8292185b0504ef62ef36007f354 100755 (executable)
@@ -1,8 +1,12 @@
 #!/bin/sh
 . git-sh-setup || die "Not a git archive"
 
-tag=$(git-rev-parse $1) || exit 1
+type="$(git-cat-file -t "$1" 2>/dev/null)" ||
+       die "$1: no such object."
 
-git-cat-file tag $tag > .tmp-vtag || exit 1
+test "$type" = tag ||
+       die "$1: cannot verify a non-tag object of type $type."
+
+git-cat-file tag "$1" > .tmp-vtag || exit 1
 cat .tmp-vtag | sed '/-----BEGIN PGP/Q' | gpg --verify .tmp-vtag - || exit 1
 rm -f .tmp-vtag
diff --git a/git.sh b/git.sh
index 178d0f0c09cad4859656dd4480ad9c096032f545..94940aea28a45294126b9065e09017ae302e7a51 100755 (executable)
--- a/git.sh
+++ b/git.sh
@@ -1,7 +1,7 @@
 #!/bin/sh
 
 cmd=
-path=$(dirname $0)
+path=$(dirname "$0")
 case "$#" in
 0)     ;;
 *)     cmd="$1"
@@ -11,22 +11,66 @@ case "$#" in
                echo "git version @@GIT_VERSION@@"
                exit 0 ;;
        esac
-       test -x $path/git-$cmd && exec $path/git-$cmd "$@" ;;
+       
+       test -x "$path/git-$cmd" && exec "$path/git-$cmd" "$@"
+       
+       case '@@X@@' in
+           '')
+               ;;
+           *)
+               test -x "$path/git-$cmd@@X@@" &&
+               exec "$path/git-$cmd@@X@@" "$@"
+               ;;
+       esac
+       ;;
 esac
 
 echo "Usage: git COMMAND [OPTIONS] [TARGET]"
 if [ -n "$cmd" ]; then
-    echo " git command '$cmd' not found: commands are:"
-else
-    echo " git commands are:"
+    echo "git command '$cmd' not found."
 fi
+echo "git commands are:"
 
-cat <<\EOF
-    add apply archimport bisect branch checkout cherry clone
-    commit count-objects cvsimport diff fetch format-patch
-    fsck-cache get-tar-commit-id init-db log ls-remote octopus
-    pack-objects parse-remote patch-id prune pull push rebase
-    relink rename repack request-pull reset resolve revert
-    send-email shortlog show-branch status tag verify-tag
-    whatchanged
+fmt <<\EOF | sed -e 's/^/    /'
+add
+apply
+archimport
+bisect
+branch
+checkout
+cherry
+clone
+commit
+count-objects
+cvsimport
+diff
+fetch
+format-patch
+fsck-objects
+get-tar-commit-id
+init-db
+log
+ls-remote
+octopus
+pack-objects
+parse-remote
+patch-id
+prune
+pull
+push
+rebase
+relink
+rename
+repack
+request-pull
+reset
+resolve
+revert
+send-email
+shortlog
+show-branch
+status
+tag
+verify-tag
+whatchanged
 EOF
index 7e8855c617ac3391a698e61023d9ca465b4b295e..1b5bddd467c3d2265e8204bb90c80a2c53742b4a 100644 (file)
@@ -213,7 +213,7 @@ def buildGraph(heads):
 
 # Write the empty tree to the object database and return its SHA1
 def writeEmptyTree():
-    tmpIndex = os.environ['GIT_DIR'] + '/merge-tmp-index'
+    tmpIndex = os.environ.get('GIT_DIR', '.git') + '/merge-tmp-index'
     def delTmpIndex():
         try:
             os.unlink(tmpIndex)
diff --git a/gitk b/gitk
index df86dceba0c097a6e2b1be9bf296ce3de014808e..a9d37d9c73e5aae166fc748160df9a5a3ebbad4a 100755 (executable)
--- a/gitk
+++ b/gitk
@@ -1,6 +1,6 @@
 #!/bin/sh
 # Tcl ignores the next line -*- tcl -*- \
-exec wish "$0" -- "${1+$@}"
+exec wish "$0" -- "$@"
 
 # Copyright (C) 2005 Paul Mackerras.  All rights reserved.
 # This program is free software; it may be used, copied, modified
@@ -486,6 +486,8 @@ proc makewindow {} {
     bindall <B2-Motion> "allcanvs scan dragto 0 %y"
     bind . <Key-Up> "selnextline -1"
     bind . <Key-Down> "selnextline 1"
+    bind . <Key-Right> "goforw"
+    bind . <Key-Left> "goback"
     bind . <Key-Prior> "allcanvs yview scroll -1 pages"
     bind . <Key-Next> "allcanvs yview scroll 1 pages"
     bindkey <Key-Delete> "$ctext yview scroll -1 pages"
@@ -493,6 +495,12 @@ proc makewindow {} {
     bindkey <Key-space> "$ctext yview scroll 1 pages"
     bindkey p "selnextline -1"
     bindkey n "selnextline 1"
+    bindkey z "goback"
+    bindkey x "goforw"
+    bindkey i "selnextline -1"
+    bindkey k "selnextline 1"
+    bindkey j "goback"
+    bindkey l "goforw"
     bindkey b "$ctext yview scroll -1 pages"
     bindkey d "$ctext yview scroll 18 units"
     bindkey u "$ctext yview scroll -18 units"
@@ -2798,7 +2806,7 @@ proc gettreediffs {ids} {
     set treediff {}
     set id [lindex $ids 0]
     set p [lindex $ids 1]
-    if [catch {set gdtf [open "|git-diff-tree -r $p $id" r]}] return
+    if [catch {set gdtf [open "|git-diff-tree -r $id" r]}] return
     fconfigure $gdtf -blocking 0
     fileevent $gdtf readable [list gettreediffline $gdtf $ids]
 }
@@ -2834,7 +2842,7 @@ proc getblobdiffs {ids} {
     set id [lindex $ids 0]
     set p [lindex $ids 1]
     set env(GIT_DIFF_OPTS) $diffopts
-    set cmd [list | git-diff-tree -r -p -C $p $id]
+    set cmd [list | git-diff-tree -r -p -C $id]
     if {[catch {set bdf [open $cmd r]} err]} {
        puts "error getting diffs: $err"
        return
index 57141a8a295dbabee74df4af1f8fd2a41b311130..ea8af1b2defaf7e11dff0a09952234f3c7626376 100644 (file)
@@ -1,11 +1,16 @@
 #include "cache.h"
 #include "commit.h"
-
+#include "pack.h"
 #include "fetch.h"
 
 #include <curl/curl.h>
 #include <curl/easy.h>
 
+#if LIBCURL_VERSION_NUM >= 0x070908
+#define USE_CURL_MULTI
+#define DEFAULT_MAX_REQUESTS 5
+#endif
+
 #if LIBCURL_VERSION_NUM < 0x070704
 #define curl_global_cleanup() do { /* nothing */ } while(0)
 #endif
 #define curl_global_init(a) do { /* nothing */ } while(0)
 #endif
 
-static CURL *curl;
-static struct curl_slist *no_pragma_header;
+#if LIBCURL_VERSION_NUM < 0x070c04
+#define NO_CURL_EASY_DUPHANDLE
+#endif
 
-static char *initial_base;
+#define PREV_BUF_SIZE 4096
+#define RANGE_HEADER_SIZE 30
+
+static int got_alternates = 0;
+static int active_requests = 0;
+static int data_received;
+
+#ifdef USE_CURL_MULTI
+static int max_requests = -1;
+static CURLM *curlm;
+#endif
+#ifndef NO_CURL_EASY_DUPHANDLE
+static CURL *curl_default;
+#endif
+static struct curl_slist *pragma_header;
+static struct curl_slist *no_pragma_header;
+static struct curl_slist *no_range_header;
+static char curl_errorstr[CURL_ERROR_SIZE];
 
 struct alt_base
 {
@@ -26,15 +49,61 @@ struct alt_base
        struct alt_base *next;
 };
 
-struct alt_base *alt = NULL;
+static struct alt_base *alt = NULL;
+
+enum transfer_state {
+       WAITING,
+       ABORTED,
+       ACTIVE,
+       COMPLETE,
+};
+
+struct transfer_request
+{
+       unsigned char sha1[20];
+       struct alt_base *repo;
+       char *url;
+       char filename[PATH_MAX];
+       char tmpfile[PATH_MAX];
+       int local;
+       enum transfer_state state;
+       CURLcode curl_result;
+       char errorstr[CURL_ERROR_SIZE];
+       long http_code;
+       unsigned char real_sha1[20];
+       SHA_CTX c;
+       z_stream stream;
+       int zret;
+       int rename;
+       struct active_request_slot *slot;
+       struct transfer_request *next;
+};
 
-static SHA_CTX c;
-static z_stream stream;
+struct active_request_slot
+{
+       CURL *curl;
+       FILE *local;
+       int in_use;
+       int done;
+       CURLcode curl_result;
+       long http_code;
+       struct active_request_slot *next;
+};
 
-static int local;
-static int zret;
+static struct transfer_request *request_queue_head = NULL;
+static struct active_request_slot *active_queue_head = NULL;
 
-static int curl_ssl_verify;
+static int curl_ssl_verify = -1;
+static char *ssl_cert = NULL;
+#if LIBCURL_VERSION_NUM >= 0x070902
+static char *ssl_key = NULL;
+#endif
+#if LIBCURL_VERSION_NUM >= 0x070908
+static char *ssl_capath = NULL;
+#endif
+static char *ssl_cainfo = NULL;
+static long curl_low_speed_limit = -1;
+static long curl_low_speed_time = -1;
 
 struct buffer
 {
@@ -43,6 +112,71 @@ struct buffer
         void *buffer;
 };
 
+static int http_options(const char *var, const char *value)
+{
+       if (!strcmp("http.sslverify", var)) {
+               if (curl_ssl_verify == -1) {
+                       curl_ssl_verify = git_config_bool(var, value);
+               }
+               return 0;
+       }
+
+       if (!strcmp("http.sslcert", var)) {
+               if (ssl_cert == NULL) {
+                       ssl_cert = xmalloc(strlen(value)+1);
+                       strcpy(ssl_cert, value);
+               }
+               return 0;
+       }
+#if LIBCURL_VERSION_NUM >= 0x070902
+       if (!strcmp("http.sslkey", var)) {
+               if (ssl_key == NULL) {
+                       ssl_key = xmalloc(strlen(value)+1);
+                       strcpy(ssl_key, value);
+               }
+               return 0;
+       }
+#endif
+#if LIBCURL_VERSION_NUM >= 0x070908
+       if (!strcmp("http.sslcapath", var)) {
+               if (ssl_capath == NULL) {
+                       ssl_capath = xmalloc(strlen(value)+1);
+                       strcpy(ssl_capath, value);
+               }
+               return 0;
+       }
+#endif
+       if (!strcmp("http.sslcainfo", var)) {
+               if (ssl_cainfo == NULL) {
+                       ssl_cainfo = xmalloc(strlen(value)+1);
+                       strcpy(ssl_cainfo, value);
+               }
+               return 0;
+       }
+
+#ifdef USE_CURL_MULTI  
+       if (!strcmp("http.maxrequests", var)) {
+               if (max_requests == -1)
+                       max_requests = git_config_int(var, value);
+               return 0;
+       }
+#endif
+
+       if (!strcmp("http.lowspeedlimit", var)) {
+               if (curl_low_speed_limit == -1)
+                       curl_low_speed_limit = (long)git_config_int(var, value);
+               return 0;
+       }
+       if (!strcmp("http.lowspeedtime", var)) {
+               if (curl_low_speed_time == -1)
+                       curl_low_speed_time = (long)git_config_int(var, value);
+               return 0;
+       }
+
+       /* Fall back on the default ones */
+       return git_default_config(var, value);
+}
+
 static size_t fwrite_buffer(void *ptr, size_t eltsize, size_t nmemb,
                             struct buffer *buffer)
 {
@@ -51,75 +185,568 @@ static size_t fwrite_buffer(void *ptr, size_t eltsize, size_t nmemb,
                 size = buffer->size - buffer->posn;
         memcpy(buffer->buffer + buffer->posn, ptr, size);
         buffer->posn += size;
+       data_received++;
         return size;
 }
 
+static size_t fwrite_buffer_dynamic(const void *ptr, size_t eltsize,
+                                   size_t nmemb, struct buffer *buffer)
+{
+       size_t size = eltsize * nmemb;
+       if (size > buffer->size - buffer->posn) {
+               buffer->size = buffer->size * 3 / 2;
+               if (buffer->size < buffer->posn + size)
+                       buffer->size = buffer->posn + size;
+               buffer->buffer = xrealloc(buffer->buffer, buffer->size);
+       }
+       memcpy(buffer->buffer + buffer->posn, ptr, size);
+       buffer->posn += size;
+       data_received++;
+       return size;
+}
+
 static size_t fwrite_sha1_file(void *ptr, size_t eltsize, size_t nmemb,
                               void *data)
 {
        unsigned char expn[4096];
        size_t size = eltsize * nmemb;
        int posn = 0;
+       struct transfer_request *request = (struct transfer_request *)data;
        do {
-               ssize_t retval = write(local, ptr + posn, size - posn);
+               ssize_t retval = write(request->local,
+                                      ptr + posn, size - posn);
                if (retval < 0)
                        return posn;
                posn += retval;
        } while (posn < size);
 
-       stream.avail_in = size;
-       stream.next_in = ptr;
+       request->stream.avail_in = size;
+       request->stream.next_in = ptr;
        do {
-               stream.next_out = expn;
-               stream.avail_out = sizeof(expn);
-               zret = inflate(&stream, Z_SYNC_FLUSH);
-               SHA1_Update(&c, expn, sizeof(expn) - stream.avail_out);
-       } while (stream.avail_in && zret == Z_OK);
+               request->stream.next_out = expn;
+               request->stream.avail_out = sizeof(expn);
+               request->zret = inflate(&request->stream, Z_SYNC_FLUSH);
+               SHA1_Update(&request->c, expn,
+                           sizeof(expn) - request->stream.avail_out);
+       } while (request->stream.avail_in && request->zret == Z_OK);
+       data_received++;
        return size;
 }
 
-void prefetch(unsigned char *sha1)
+#ifdef USE_CURL_MULTI
+static void process_curl_messages(void);
+static void process_request_queue(void);
+#endif
+static int fetch_alternates(char *base);
+
+static CURL* get_curl_handle(void)
+{
+       CURL* result = curl_easy_init();
+
+       curl_easy_setopt(result, CURLOPT_SSL_VERIFYPEER, curl_ssl_verify);
+#if LIBCURL_VERSION_NUM >= 0x070907
+       curl_easy_setopt(result, CURLOPT_NETRC, CURL_NETRC_OPTIONAL);
+#endif
+
+       if (ssl_cert != NULL)
+               curl_easy_setopt(result, CURLOPT_SSLCERT, ssl_cert);
+#if LIBCURL_VERSION_NUM >= 0x070902
+       if (ssl_key != NULL)
+               curl_easy_setopt(result, CURLOPT_SSLKEY, ssl_key);
+#endif
+#if LIBCURL_VERSION_NUM >= 0x070908
+       if (ssl_capath != NULL)
+               curl_easy_setopt(result, CURLOPT_CAPATH, ssl_capath);
+#endif
+       if (ssl_cainfo != NULL)
+               curl_easy_setopt(result, CURLOPT_CAINFO, ssl_cainfo);
+       curl_easy_setopt(result, CURLOPT_FAILONERROR, 1);
+
+       if (curl_low_speed_limit > 0 && curl_low_speed_time > 0) {
+               curl_easy_setopt(result, CURLOPT_LOW_SPEED_LIMIT,
+                                curl_low_speed_limit);
+               curl_easy_setopt(result, CURLOPT_LOW_SPEED_TIME,
+                                curl_low_speed_time);
+       }
+
+       return result;
+}
+
+static struct active_request_slot *get_active_slot(void)
+{
+       struct active_request_slot *slot = active_queue_head;
+       struct active_request_slot *newslot;
+
+#ifdef USE_CURL_MULTI
+       int num_transfers;
+
+       /* Wait for a slot to open up if the queue is full */
+       while (active_requests >= max_requests) {
+               curl_multi_perform(curlm, &num_transfers);
+               if (num_transfers < active_requests) {
+                       process_curl_messages();
+               }
+       }
+#endif
+
+       while (slot != NULL && slot->in_use) {
+               slot = slot->next;
+       }
+       if (slot == NULL) {
+               newslot = xmalloc(sizeof(*newslot));
+               newslot->curl = NULL;
+               newslot->in_use = 0;
+               newslot->next = NULL;
+
+               slot = active_queue_head;
+               if (slot == NULL) {
+                       active_queue_head = newslot;
+               } else {
+                       while (slot->next != NULL) {
+                               slot = slot->next;
+                       }
+                       slot->next = newslot;
+               }
+               slot = newslot;
+       }
+
+       if (slot->curl == NULL) {
+#ifdef NO_CURL_EASY_DUPHANDLE
+               slot->curl = get_curl_handle();
+#else
+               slot->curl = curl_easy_duphandle(curl_default);
+#endif
+       }
+
+       active_requests++;
+       slot->in_use = 1;
+       slot->done = 0;
+       slot->local = NULL;
+       curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, pragma_header);
+       curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, no_range_header);
+       curl_easy_setopt(slot->curl, CURLOPT_ERRORBUFFER, curl_errorstr);
+
+       return slot;
+}
+
+static int start_active_slot(struct active_request_slot *slot)
 {
+#ifdef USE_CURL_MULTI
+       CURLMcode curlm_result = curl_multi_add_handle(curlm, slot->curl);
+
+       if (curlm_result != CURLM_OK &&
+           curlm_result != CURLM_CALL_MULTI_PERFORM) {
+               active_requests--;
+               slot->in_use = 0;
+               return 0;
+       }
+#endif
+       return 1;
 }
 
-static int got_alternates = 0;
+static void run_active_slot(struct active_request_slot *slot)
+{
+#ifdef USE_CURL_MULTI
+       int num_transfers;
+       long last_pos = 0;
+       long current_pos;
+       fd_set readfds;
+       fd_set writefds;
+       fd_set excfds;
+       int max_fd;
+       struct timeval select_timeout;
+       CURLMcode curlm_result;
+
+       while (!slot->done) {
+               data_received = 0;
+               do {
+                       curlm_result = curl_multi_perform(curlm,
+                                                         &num_transfers);
+               } while (curlm_result == CURLM_CALL_MULTI_PERFORM);
+               if (num_transfers < active_requests) {
+                       process_curl_messages();
+                       process_request_queue();
+               }
+
+               if (!data_received && slot->local != NULL) {
+                       current_pos = ftell(slot->local);
+                       if (current_pos > last_pos)
+                               data_received++;
+                       last_pos = current_pos;
+               }
+
+               if (!slot->done && !data_received) {
+                       max_fd = 0;
+                       FD_ZERO(&readfds);
+                       FD_ZERO(&writefds);
+                       FD_ZERO(&excfds);
+                       select_timeout.tv_sec = 0;
+                       select_timeout.tv_usec = 50000;
+                       select(max_fd, &readfds, &writefds,
+                              &excfds, &select_timeout);
+               }
+       }
+#else
+       slot->curl_result = curl_easy_perform(slot->curl);
+       active_requests--;
+#endif
+}
+
+static void start_request(struct transfer_request *request)
+{
+       char *hex = sha1_to_hex(request->sha1);
+       char prevfile[PATH_MAX];
+       char *url;
+       char *posn;
+       int prevlocal;
+       unsigned char prev_buf[PREV_BUF_SIZE];
+       ssize_t prev_read = 0;
+       long prev_posn = 0;
+       char range[RANGE_HEADER_SIZE];
+       struct curl_slist *range_header = NULL;
+       struct active_request_slot *slot;
+
+       snprintf(prevfile, sizeof(prevfile), "%s.prev", request->filename);
+       unlink(prevfile);
+       rename(request->tmpfile, prevfile);
+       unlink(request->tmpfile);
+
+       request->local = open(request->tmpfile,
+                             O_WRONLY | O_CREAT | O_EXCL, 0666);
+       /* This could have failed due to the "lazy directory creation";
+        * try to mkdir the last path component.
+        */
+       if (request->local < 0 && errno == ENOENT) {
+               char *dir = strrchr(request->tmpfile, '/');
+               if (dir) {
+                       *dir = 0;
+                       mkdir(request->tmpfile, 0777);
+                       *dir = '/';
+               }
+               request->local = open(request->tmpfile,
+                                     O_WRONLY | O_CREAT | O_EXCL, 0666);
+       }
+
+       if (request->local < 0) {
+               request->state = ABORTED;
+               error("Couldn't create temporary file %s for %s: %s\n",
+                     request->tmpfile, request->filename, strerror(errno));
+               return;
+       }
+
+       memset(&request->stream, 0, sizeof(request->stream));
+
+       inflateInit(&request->stream);
+
+       SHA1_Init(&request->c);
+
+       url = xmalloc(strlen(request->repo->base) + 50);
+       request->url = xmalloc(strlen(request->repo->base) + 50);
+       strcpy(url, request->repo->base);
+       posn = url + strlen(request->repo->base);
+       strcpy(posn, "objects/");
+       posn += 8;
+       memcpy(posn, hex, 2);
+       posn += 2;
+       *(posn++) = '/';
+       strcpy(posn, hex + 2);
+       strcpy(request->url, url);
+
+       /* If a previous temp file is present, process what was already
+          fetched. */
+       prevlocal = open(prevfile, O_RDONLY);
+       if (prevlocal != -1) {
+               do {
+                       prev_read = read(prevlocal, prev_buf, PREV_BUF_SIZE);
+                       if (prev_read>0) {
+                               if (fwrite_sha1_file(prev_buf,
+                                                    1,
+                                                    prev_read,
+                                                    request) == prev_read) {
+                                       prev_posn += prev_read;
+                               } else {
+                                       prev_read = -1;
+                               }
+                       }
+               } while (prev_read > 0);
+               close(prevlocal);
+       }
+       unlink(prevfile);
+
+       /* Reset inflate/SHA1 if there was an error reading the previous temp
+          file; also rewind to the beginning of the local file. */
+       if (prev_read == -1) {
+               memset(&request->stream, 0, sizeof(request->stream));
+               inflateInit(&request->stream);
+               SHA1_Init(&request->c);
+               if (prev_posn>0) {
+                       prev_posn = 0;
+                       lseek(request->local, SEEK_SET, 0);
+                       ftruncate(request->local, 0);
+               }
+       }
+
+       slot = get_active_slot();
+       curl_easy_setopt(slot->curl, CURLOPT_FILE, request);
+       curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_sha1_file);
+       curl_easy_setopt(slot->curl, CURLOPT_ERRORBUFFER, request->errorstr);
+       curl_easy_setopt(slot->curl, CURLOPT_URL, url);
+       curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, no_pragma_header);
+
+       /* If we have successfully processed data from a previous fetch
+          attempt, only fetch the data we don't already have. */
+       if (prev_posn>0) {
+               if (get_verbosely)
+                       fprintf(stderr,
+                               "Resuming fetch of object %s at byte %ld\n",
+                               hex, prev_posn);
+               sprintf(range, "Range: bytes=%ld-", prev_posn);
+               range_header = curl_slist_append(range_header, range);
+               curl_easy_setopt(slot->curl,
+                                CURLOPT_HTTPHEADER, range_header);
+       }
+
+       /* Try to get the request started, abort the request on error */
+       if (!start_active_slot(slot)) {
+               request->state = ABORTED;
+               close(request->local);
+               free(request->url);
+               return;
+       }
+       
+       request->slot = slot;
+       request->state = ACTIVE;
+}
+
+static void finish_request(struct transfer_request *request)
+{
+       struct stat st;
+
+       fchmod(request->local, 0444);
+       close(request->local);
+
+       if (request->http_code == 416) {
+               fprintf(stderr, "Warning: requested range invalid; we may already have all the data.\n");
+       } else if (request->curl_result != CURLE_OK) {
+               if (stat(request->tmpfile, &st) == 0)
+                       if (st.st_size == 0)
+                               unlink(request->tmpfile);
+               return;
+       }
+
+       inflateEnd(&request->stream);
+       SHA1_Final(request->real_sha1, &request->c);
+       if (request->zret != Z_STREAM_END) {
+               unlink(request->tmpfile);
+               return;
+       }
+       if (memcmp(request->sha1, request->real_sha1, 20)) {
+               unlink(request->tmpfile);
+               return;
+       }
+       request->rename =
+               move_temp_to_file(request->tmpfile, request->filename);
+
+       if (request->rename == 0)
+               pull_say("got %s\n", sha1_to_hex(request->sha1));
+}
+
+static void release_request(struct transfer_request *request)
+{
+       struct transfer_request *entry = request_queue_head;
+
+       if (request == request_queue_head) {
+               request_queue_head = request->next;
+       } else {
+               while (entry->next != NULL && entry->next != request)
+                       entry = entry->next;
+               if (entry->next == request)
+                       entry->next = entry->next->next;
+       }
+
+       free(request->url);
+       free(request);
+}
+
+#ifdef USE_CURL_MULTI
+void process_curl_messages(void)
+{
+       int num_messages;
+       struct active_request_slot *slot;
+       struct transfer_request *request = NULL;
+       CURLMsg *curl_message = curl_multi_info_read(curlm, &num_messages);
+
+       while (curl_message != NULL) {
+               if (curl_message->msg == CURLMSG_DONE) {
+                       int curl_result = curl_message->data.result;
+                       slot = active_queue_head;
+                       while (slot != NULL &&
+                              slot->curl != curl_message->easy_handle)
+                               slot = slot->next;
+                       if (slot != NULL) {
+                               curl_multi_remove_handle(curlm, slot->curl);
+                               active_requests--;
+                               slot->done = 1;
+                               slot->in_use = 0;
+                               slot->curl_result = curl_result;
+                               curl_easy_getinfo(slot->curl,
+                                                 CURLINFO_HTTP_CODE,
+                                                 &slot->http_code);
+                               request = request_queue_head;
+                               while (request != NULL &&
+                                      request->slot != slot)
+                                       request = request->next;
+                       } else {
+                               fprintf(stderr, "Received DONE message for unknown request!\n");
+                       }
+                       if (request != NULL) {
+                               request->curl_result = curl_result;
+                               request->http_code = slot->http_code;
+                               request->slot = NULL;
+                               request->state = COMPLETE;
+
+                               /* Use alternates if necessary */
+                               if (request->http_code == 404) {
+                                       fetch_alternates(alt->base);
+                                       if (request->repo->next != NULL) {
+                                               request->repo =
+                                                       request->repo->next;
+                                               start_request(request);
+                                       }
+                               } else {
+                                       finish_request(request);
+                               }
+                       }
+               } else {
+                       fprintf(stderr, "Unknown CURL message received: %d\n",
+                               (int)curl_message->msg);
+               }
+               curl_message = curl_multi_info_read(curlm, &num_messages);
+       }
+}
+
+void process_request_queue(void)
+{
+       struct transfer_request *request = request_queue_head;
+       struct active_request_slot *slot = active_queue_head;
+       int num_transfers;
+
+       while (active_requests < max_requests && request != NULL) {
+               if (request->state == WAITING) {
+                       if (has_sha1_file(request->sha1))
+                               release_request(request);
+                       else
+                               start_request(request);
+                       curl_multi_perform(curlm, &num_transfers);
+               }
+               request = request->next;
+       }
+
+       while (slot != NULL) {
+               if (!slot->in_use && slot->curl != NULL) {
+                       curl_easy_cleanup(slot->curl);
+                       slot->curl = NULL;
+               }
+               slot = slot->next;
+       }                               
+}
+#endif
+
+void prefetch(unsigned char *sha1)
+{
+       struct transfer_request *newreq;
+       struct transfer_request *tail;
+       char *filename = sha1_file_name(sha1);
+
+       newreq = xmalloc(sizeof(*newreq));
+       memcpy(newreq->sha1, sha1, 20);
+       newreq->repo = alt;
+       newreq->url = NULL;
+       newreq->local = -1;
+       newreq->state = WAITING;
+       snprintf(newreq->filename, sizeof(newreq->filename), "%s", filename);
+       snprintf(newreq->tmpfile, sizeof(newreq->tmpfile),
+                "%s.temp", filename);
+       newreq->next = NULL;
+
+       if (request_queue_head == NULL) {
+               request_queue_head = newreq;
+       } else {
+               tail = request_queue_head;
+               while (tail->next != NULL) {
+                       tail = tail->next;
+               }
+               tail->next = newreq;
+       }
+#ifdef USE_CURL_MULTI
+       process_request_queue();
+       process_curl_messages();
+#endif
+}
 
 static int fetch_index(struct alt_base *repo, unsigned char *sha1)
 {
+       char *hex = sha1_to_hex(sha1);
        char *filename;
        char *url;
+       char tmpfile[PATH_MAX];
+       long prev_posn = 0;
+       char range[RANGE_HEADER_SIZE];
+       struct curl_slist *range_header = NULL;
 
        FILE *indexfile;
+       struct active_request_slot *slot;
 
        if (has_pack_index(sha1))
                return 0;
 
        if (get_verbosely)
-               fprintf(stderr, "Getting index for pack %s\n",
-                       sha1_to_hex(sha1));
+               fprintf(stderr, "Getting index for pack %s\n", hex);
        
        url = xmalloc(strlen(repo->base) + 64);
-       sprintf(url, "%s/objects/pack/pack-%s.idx",
-               repo->base, sha1_to_hex(sha1));
+       sprintf(url, "%s/objects/pack/pack-%s.idx", repo->base, hex);
        
        filename = sha1_pack_index_name(sha1);
-       indexfile = fopen(filename, "w");
+       snprintf(tmpfile, sizeof(tmpfile), "%s.temp", filename);
+       indexfile = fopen(tmpfile, "a");
        if (!indexfile)
                return error("Unable to open local file %s for pack index",
                             filename);
 
-       curl_easy_setopt(curl, CURLOPT_FILE, indexfile);
-       curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, fwrite);
-       curl_easy_setopt(curl, CURLOPT_URL, url);
-       curl_easy_setopt(curl, CURLOPT_HTTPHEADER, no_pragma_header);
-       
-       if (curl_easy_perform(curl)) {
-               fclose(indexfile);
-               return error("Unable to get pack index %s", url);
+       slot = get_active_slot();
+       curl_easy_setopt(slot->curl, CURLOPT_FILE, indexfile);
+       curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite);
+       curl_easy_setopt(slot->curl, CURLOPT_URL, url);
+       curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, no_pragma_header);
+       slot->local = indexfile;
+
+       /* If there is data present from a previous transfer attempt,
+          resume where it left off */
+       prev_posn = ftell(indexfile);
+       if (prev_posn>0) {
+               if (get_verbosely)
+                       fprintf(stderr,
+                               "Resuming fetch of index for pack %s at byte %ld\n",
+                               hex, prev_posn);
+               sprintf(range, "Range: bytes=%ld-", prev_posn);
+               range_header = curl_slist_append(range_header, range);
+               curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, range_header);
+       }
+
+       if (start_active_slot(slot)) {
+               run_active_slot(slot);
+               if (slot->curl_result != CURLE_OK) {
+                       fclose(indexfile);
+                       return error("Unable to get pack index %s\n%s", url,
+                                    curl_errorstr);
+               }
+       } else {
+               return error("Unable to start request");
        }
 
        fclose(indexfile);
-       return 0;
+
+       return move_temp_to_file(tmpfile, filename);
 }
 
 static int setup_index(struct alt_base *repo, unsigned char *sha1)
@@ -145,10 +772,16 @@ static int fetch_alternates(char *base)
        char *data;
        int i = 0;
        int http_specific = 1;
+       struct alt_base *tail = alt;
+       static const char null_byte = '\0';
+
+       struct active_request_slot *slot;
+
        if (got_alternates)
                return 0;
+
        data = xmalloc(4096);
-       buffer.size = 4095;
+       buffer.size = 4096;
        buffer.posn = 0;
        buffer.buffer = data;
 
@@ -158,25 +791,41 @@ static int fetch_alternates(char *base)
        url = xmalloc(strlen(base) + 31);
        sprintf(url, "%s/objects/info/http-alternates", base);
 
-       curl_easy_setopt(curl, CURLOPT_FILE, &buffer);
-       curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, fwrite_buffer);
-       curl_easy_setopt(curl, CURLOPT_URL, url);
-
-       if (curl_easy_perform(curl) || !buffer.posn) {
-               http_specific = 0;
-
-               sprintf(url, "%s/objects/info/alternates", base);
-               
-               curl_easy_setopt(curl, CURLOPT_FILE, &buffer);
-               curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, fwrite_buffer);
-               curl_easy_setopt(curl, CURLOPT_URL, url);
-               
-               if (curl_easy_perform(curl)) {
-                       return 0;
+       slot = get_active_slot();
+       curl_easy_setopt(slot->curl, CURLOPT_FILE, &buffer);
+       curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION,
+                        fwrite_buffer_dynamic);
+       curl_easy_setopt(slot->curl, CURLOPT_URL, url);
+       if (start_active_slot(slot)) {
+               run_active_slot(slot);
+               if (slot->curl_result != CURLE_OK || !buffer.posn) {
+                       http_specific = 0;
+
+                       sprintf(url, "%s/objects/info/alternates", base);
+
+                       slot = get_active_slot();
+                       curl_easy_setopt(slot->curl, CURLOPT_FILE, &buffer);
+                       curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION,
+                                        fwrite_buffer_dynamic);
+                       curl_easy_setopt(slot->curl, CURLOPT_URL, url);
+                       if (start_active_slot(slot)) {
+                               run_active_slot(slot);
+                               if (slot->curl_result != CURLE_OK) {
+                                       free(buffer.buffer);
+                                       if (slot->http_code == 404)
+                                               got_alternates = 1;
+                                       return 0;
+                               }
+                       }
                }
+       } else {
+               free(buffer.buffer);
+               return 0;
        }
 
-       data[buffer.posn] = '\0';
+       fwrite_buffer_dynamic(&null_byte, 1, 1, &buffer);
+       buffer.posn--;
+       data = buffer.buffer;
 
        while (i < buffer.posn) {
                int posn = i;
@@ -223,18 +872,21 @@ static int fetch_alternates(char *base)
                                        fprintf(stderr, 
                                                "Also look at %s\n", target);
                                newalt = xmalloc(sizeof(*newalt));
-                               newalt->next = alt;
+                               newalt->next = NULL;
                                newalt->base = target;
                                newalt->got_indices = 0;
                                newalt->packs = NULL;
-                               alt = newalt;
+                               while (tail->next != NULL)
+                                       tail = tail->next;
+                               tail->next = newalt;
                                ret++;
                        }
                }
                i = posn + 1;
        }
+
        got_alternates = 1;
-       
+       free(buffer.buffer);
        return ret;
 }
 
@@ -246,6 +898,8 @@ static int fetch_indices(struct alt_base *repo)
        char *data;
        int i = 0;
 
+       struct active_request_slot *slot;
+
        if (repo->got_indices)
                return 0;
 
@@ -260,15 +914,24 @@ static int fetch_indices(struct alt_base *repo)
        url = xmalloc(strlen(repo->base) + 21);
        sprintf(url, "%s/objects/info/packs", repo->base);
 
-       curl_easy_setopt(curl, CURLOPT_FILE, &buffer);
-       curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, fwrite_buffer);
-       curl_easy_setopt(curl, CURLOPT_URL, url);
-       curl_easy_setopt(curl, CURLOPT_HTTPHEADER, NULL);
-       
-       if (curl_easy_perform(curl)) {
-               return -1;
+       slot = get_active_slot();
+       curl_easy_setopt(slot->curl, CURLOPT_FILE, &buffer);
+       curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION,
+                        fwrite_buffer_dynamic);
+       curl_easy_setopt(slot->curl, CURLOPT_URL, url);
+       curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, NULL);
+       if (start_active_slot(slot)) {
+               run_active_slot(slot);
+               if (slot->curl_result != CURLE_OK) {
+                       free(buffer.buffer);
+                       return error("%s", curl_errorstr);
+               }
+       } else {
+               free(buffer.buffer);
+               return error("Unable to start request");
        }
 
+       data = buffer.buffer;
        while (i < buffer.posn) {
                switch (data[i]) {
                case 'P':
@@ -288,6 +951,7 @@ static int fetch_indices(struct alt_base *repo)
                i++;
        }
 
+       free(buffer.buffer);
        repo->got_indices = 1;
        return 0;
 }
@@ -299,6 +963,13 @@ static int fetch_pack(struct alt_base *repo, unsigned char *sha1)
        struct packed_git **lst;
        FILE *packfile;
        char *filename;
+       char tmpfile[PATH_MAX];
+       int ret;
+       long prev_posn = 0;
+       char range[RANGE_HEADER_SIZE];
+       struct curl_slist *range_header = NULL;
+
+       struct active_request_slot *slot;
 
        if (fetch_indices(repo))
                return -1;
@@ -318,153 +989,237 @@ static int fetch_pack(struct alt_base *repo, unsigned char *sha1)
                repo->base, sha1_to_hex(target->sha1));
 
        filename = sha1_pack_name(target->sha1);
-       packfile = fopen(filename, "w");
+       snprintf(tmpfile, sizeof(tmpfile), "%s.temp", filename);
+       packfile = fopen(tmpfile, "a");
        if (!packfile)
                return error("Unable to open local file %s for pack",
                             filename);
 
-       curl_easy_setopt(curl, CURLOPT_FILE, packfile);
-       curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, fwrite);
-       curl_easy_setopt(curl, CURLOPT_URL, url);
-       curl_easy_setopt(curl, CURLOPT_HTTPHEADER, no_pragma_header);
-       
-       if (curl_easy_perform(curl)) {
-               fclose(packfile);
-               return error("Unable to get pack file %s", url);
+       slot = get_active_slot();
+       curl_easy_setopt(slot->curl, CURLOPT_FILE, packfile);
+       curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite);
+       curl_easy_setopt(slot->curl, CURLOPT_URL, url);
+       curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, no_pragma_header);
+       slot->local = packfile;
+
+       /* If there is data present from a previous transfer attempt,
+          resume where it left off */
+       prev_posn = ftell(packfile);
+       if (prev_posn>0) {
+               if (get_verbosely)
+                       fprintf(stderr,
+                               "Resuming fetch of pack %s at byte %ld\n",
+                               sha1_to_hex(target->sha1), prev_posn);
+               sprintf(range, "Range: bytes=%ld-", prev_posn);
+               range_header = curl_slist_append(range_header, range);
+               curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, range_header);
+       }
+
+       if (start_active_slot(slot)) {
+               run_active_slot(slot);
+               if (slot->curl_result != CURLE_OK) {
+                       fclose(packfile);
+                       return error("Unable to get pack file %s\n%s", url,
+                                    curl_errorstr);
+               }
+       } else {
+               return error("Unable to start request");
        }
 
        fclose(packfile);
 
+       ret = move_temp_to_file(tmpfile, filename);
+       if (ret)
+               return ret;
+
        lst = &repo->packs;
        while (*lst != target)
                lst = &((*lst)->next);
        *lst = (*lst)->next;
 
+       if (verify_pack(target, 0))
+               return -1;
        install_packed_git(target);
 
        return 0;
 }
 
-int fetch_object(struct alt_base *repo, unsigned char *sha1)
+static int fetch_object(struct alt_base *repo, unsigned char *sha1)
 {
        char *hex = sha1_to_hex(sha1);
-       char *filename = sha1_file_name(sha1);
-       unsigned char real_sha1[20];
-       char tmpfile[PATH_MAX];
        int ret;
-       char *url;
-       char *posn;
+       struct transfer_request *request = request_queue_head;
 
-       snprintf(tmpfile, sizeof(tmpfile), "%s/obj_XXXXXX",
-                get_object_directory());
+       while (request != NULL && memcmp(request->sha1, sha1, 20))
+               request = request->next;
+       if (request == NULL)
+               return error("Couldn't find request for %s in the queue", hex);
 
-       local = mkstemp(tmpfile);
-       if (local < 0)
-               return error("Couldn't create temporary file %s for %s: %s\n",
-                            tmpfile, filename, strerror(errno));
-
-       memset(&stream, 0, sizeof(stream));
-
-       inflateInit(&stream);
-
-       SHA1_Init(&c);
+       if (has_sha1_file(request->sha1)) {
+               release_request(request);
+               return 0;
+       }
 
-       curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1);
-       curl_easy_setopt(curl, CURLOPT_FILE, NULL);
-       curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, fwrite_sha1_file);
-       curl_easy_setopt(curl, CURLOPT_HTTPHEADER, no_pragma_header);
+#ifdef USE_CURL_MULTI
+       while (request->state == WAITING) {
+               int num_transfers;
+               curl_multi_perform(curlm, &num_transfers);
+               if (num_transfers < active_requests) {
+                       process_curl_messages();
+                       process_request_queue();
+               }
+       }
+#else
+       start_request(request);
+#endif
 
-       url = xmalloc(strlen(repo->base) + 50);
-       strcpy(url, repo->base);
-       posn = url + strlen(repo->base);
-       strcpy(posn, "objects/");
-       posn += 8;
-       memcpy(posn, hex, 2);
-       posn += 2;
-       *(posn++) = '/';
-       strcpy(posn, hex + 2);
+       while (request->state == ACTIVE) {
+               run_active_slot(request->slot);
+#ifndef USE_CURL_MULTI
+               request->curl_result = request->slot->curl_result;
+               request->http_code = request->slot->http_code;
+               request->slot = NULL;
+
+               /* Use alternates if necessary */
+               if (request->http_code == 404) {
+                       fetch_alternates(alt->base);
+                       if (request->repo->next != NULL) {
+                               request->repo = request->repo->next;
+                               start_request(request);
+                       }
+               } else {
+                       finish_request(request);
+                       request->state = COMPLETE;
+               }
+#endif
+       }
 
-       curl_easy_setopt(curl, CURLOPT_URL, url);
+       if (request->state == ABORTED) {
+               release_request(request);
+               return error("Request for %s aborted", hex);
+       }
 
-       if (curl_easy_perform(curl)) {
-               unlink(filename);
-               return -1;
+       if (request->curl_result != CURLE_OK && request->http_code != 416) {
+               if (request->http_code == 404)
+                       ret = -1; /* Be silent, it is probably in a pack. */
+               else
+                       ret = error("%s (curl_result = %d, http_code = %ld, sha1 = %s)",
+                                   request->errorstr, request->curl_result,
+                                   request->http_code, hex);
+               release_request(request);
+               return ret;
        }
 
-       fchmod(local, 0444);
-       close(local);
-       inflateEnd(&stream);
-       SHA1_Final(real_sha1, &c);
-       if (zret != Z_STREAM_END) {
-               unlink(tmpfile);
-               return error("File %s (%s) corrupt\n", hex, url);
+       if (request->zret != Z_STREAM_END) {
+               ret = error("File %s (%s) corrupt\n", hex, request->url);
+               release_request(request);
+               return ret;
        }
-       if (memcmp(sha1, real_sha1, 20)) {
-               unlink(tmpfile);
+
+       if (memcmp(request->sha1, request->real_sha1, 20)) {
+               release_request(request);
                return error("File %s has bad hash\n", hex);
        }
-       ret = link(tmpfile, filename);
-       if (ret < 0) {
-               /* Same Coda hack as in write_sha1_file(sha1_file.c) */
-               ret = errno;
-               if (ret == EXDEV && !rename(tmpfile, filename))
-                       goto out;
-       }
-       unlink(tmpfile);
-       if (ret) {
-               if (ret != EEXIST)
-                       return error("unable to write sha1 filename %s: %s",
-                                    filename, strerror(ret));
-       }
- out:
-       pull_say("got %s\n", hex);
+
+       if (request->rename < 0) {
+               ret = error("unable to write sha1 filename %s: %s",
+                           request->filename,
+                           strerror(request->rename));
+               release_request(request);
+               return ret;
+       }
+
+       release_request(request);
        return 0;
 }
 
 int fetch(unsigned char *sha1)
 {
        struct alt_base *altbase = alt;
+
+       if (!fetch_object(altbase, sha1))
+               return 0;
        while (altbase) {
-               if (!fetch_object(altbase, sha1))
-                       return 0;
                if (!fetch_pack(altbase, sha1))
                        return 0;
-               if (fetch_alternates(altbase->base) > 0) {
-                       altbase = alt;
-                       continue;
-               }
+               fetch_alternates(alt->base);
                altbase = altbase->next;
        }
        return error("Unable to find %s under %s\n", sha1_to_hex(sha1), 
-                    initial_base);
+                    alt->base);
+}
+
+static inline int needs_quote(int ch)
+{
+       switch (ch) {
+       case '/': case '-': case '.':
+       case 'A'...'Z': case 'a'...'z': case '0'...'9':
+               return 0;
+       default:
+               return 1;
+       }
+}
+
+static inline int hex(int v)
+{
+       if (v < 10) return '0' + v;
+       else return 'A' + v - 10;
+}
+
+static char *quote_ref_url(const char *base, const char *ref)
+{
+       const char *cp;
+       char *dp, *qref;
+       int len, baselen, ch;
+
+       baselen = strlen(base);
+       len = baselen + 6; /* "refs/" + NUL */
+       for (cp = ref; (ch = *cp) != 0; cp++, len++)
+               if (needs_quote(ch))
+                       len += 2; /* extra two hex plus replacement % */
+       qref = xmalloc(len);
+       memcpy(qref, base, baselen);
+       memcpy(qref + baselen, "refs/", 5);
+       for (cp = ref, dp = qref + baselen + 5; (ch = *cp) != 0; cp++) {
+               if (needs_quote(ch)) {
+                       *dp++ = '%';
+                       *dp++ = hex((ch >> 4) & 0xF);
+                       *dp++ = hex(ch & 0xF);
+               }
+               else
+                       *dp++ = ch;
+       }
+       *dp = 0;
+
+       return qref;
 }
 
 int fetch_ref(char *ref, unsigned char *sha1)
 {
-        char *url, *posn;
+        char *url;
         char hex[42];
         struct buffer buffer;
-       char *base = initial_base;
+       char *base = alt->base;
+       struct active_request_slot *slot;
         buffer.size = 41;
         buffer.posn = 0;
         buffer.buffer = hex;
         hex[41] = '\0';
         
-        curl_easy_setopt(curl, CURLOPT_FILE, &buffer);
-        curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, fwrite_buffer);
-       curl_easy_setopt(curl, CURLOPT_HTTPHEADER, NULL);
-
-        url = xmalloc(strlen(base) + 6 + strlen(ref));
-        strcpy(url, base);
-        posn = url + strlen(base);
-        strcpy(posn, "refs/");
-        posn += 5;
-        strcpy(posn, ref);
-
-        curl_easy_setopt(curl, CURLOPT_URL, url);
-
-        if (curl_easy_perform(curl))
-                return error("Couldn't get %s for %s\n", url, ref);
+       url = quote_ref_url(base, ref);
+       slot = get_active_slot();
+       curl_easy_setopt(slot->curl, CURLOPT_FILE, &buffer);
+       curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_buffer);
+       curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, NULL);
+       curl_easy_setopt(slot->curl, CURLOPT_URL, url);
+       if (start_active_slot(slot)) {
+               run_active_slot(slot);
+               if (slot->curl_result != CURLE_OK)
+                       return error("Couldn't get %s for %s\n%s",
+                                    url, ref, curl_errorstr);
+       } else {
+               return error("Unable to start request");
+       }
 
         hex[40] = '\0';
         get_sha1_hex(hex, sha1);
@@ -476,6 +1231,11 @@ int main(int argc, char **argv)
        char *commit_id;
        char *url;
        int arg = 1;
+       struct active_request_slot *slot;
+       char *low_speed_limit;
+       char *low_speed_time;
+       char *wait_url;
+       int rc = 0;
 
        while (arg < argc && argv[arg][0] == '-') {
                if (argv[arg][1] == 't') {
@@ -491,6 +1251,8 @@ int main(int argc, char **argv)
                } else if (argv[arg][1] == 'w') {
                        write_ref = argv[arg + 1];
                        arg++;
+               } else if (!strcmp(argv[arg], "--recover")) {
+                       get_recover = 1;
                }
                arg++;
        }
@@ -503,13 +1265,55 @@ int main(int argc, char **argv)
 
        curl_global_init(CURL_GLOBAL_ALL);
 
-       curl = curl_easy_init();
+#ifdef USE_CURL_MULTI
+       {
+               char *http_max_requests = getenv("GIT_HTTP_MAX_REQUESTS");
+               if (http_max_requests != NULL)
+                       max_requests = atoi(http_max_requests);
+       }
+
+       curlm = curl_multi_init();
+       if (curlm == NULL) {
+               fprintf(stderr, "Error creating curl multi handle.\n");
+               return 1;
+       }
+#endif
+
+       if (getenv("GIT_SSL_NO_VERIFY"))
+               curl_ssl_verify = 0;
+
+       ssl_cert = getenv("GIT_SSL_CERT");
+#if LIBCURL_VERSION_NUM >= 0x070902
+       ssl_key = getenv("GIT_SSL_KEY");
+#endif
+#if LIBCURL_VERSION_NUM >= 0x070908
+       ssl_capath = getenv("GIT_SSL_CAPATH");
+#endif
+       ssl_cainfo = getenv("GIT_SSL_CAINFO");
+
+       low_speed_limit = getenv("GIT_HTTP_LOW_SPEED_LIMIT");
+       if (low_speed_limit != NULL)
+               curl_low_speed_limit = strtol(low_speed_limit, NULL, 10);
+       low_speed_time = getenv("GIT_HTTP_LOW_SPEED_TIME");
+       if (low_speed_time != NULL)
+               curl_low_speed_time = strtol(low_speed_time, NULL, 10);
+
+       git_config(http_options);
+
+       if (curl_ssl_verify == -1)
+               curl_ssl_verify = 1;
+
+#ifdef USE_CURL_MULTI
+       if (max_requests < 1)
+               max_requests = DEFAULT_MAX_REQUESTS;
+#endif
+
+       pragma_header = curl_slist_append(pragma_header, "Pragma: no-cache");
        no_pragma_header = curl_slist_append(no_pragma_header, "Pragma:");
+       no_range_header = curl_slist_append(no_range_header, "Range:");
 
-       curl_ssl_verify = getenv("GIT_SSL_NO_VERIFY") ? 0 : 1;
-       curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, curl_ssl_verify);
-#if LIBCURL_VERSION_NUM >= 0x070907
-       curl_easy_setopt(curl, CURLOPT_NETRC, CURL_NETRC_OPTIONAL);
+#ifndef NO_CURL_EASY_DUPHANDLE
+       curl_default = get_curl_handle();
 #endif
 
        alt = xmalloc(sizeof(*alt));
@@ -517,12 +1321,34 @@ int main(int argc, char **argv)
        alt->got_indices = 0;
        alt->packs = NULL;
        alt->next = NULL;
-       initial_base = url;
 
        if (pull(commit_id))
-               return 1;
+               rc = 1;
 
+       curl_slist_free_all(pragma_header);
        curl_slist_free_all(no_pragma_header);
+       curl_slist_free_all(no_range_header);
+#ifndef NO_CURL_EASY_DUPHANDLE
+       curl_easy_cleanup(curl_default);
+#endif
+       slot = active_queue_head;
+       while (slot != NULL) {
+               if (slot->in_use) {
+                       if (get_verbosely) {
+                               curl_easy_getinfo(slot->curl,
+                                                 CURLINFO_EFFECTIVE_URL,
+                                                 &wait_url);
+                               fprintf(stderr, "Waiting for %s\n", wait_url);
+                       }
+                       run_active_slot(slot);
+               }
+               if (slot->curl != NULL)
+                       curl_easy_cleanup(slot->curl);
+               slot = slot->next;
+       }
+#ifdef USE_CURL_MULTI
+       curl_multi_cleanup(curlm);
+#endif
        curl_global_cleanup();
-       return 0;
+       return rc;
 }
diff --git a/http-push.c b/http-push.c
new file mode 100644 (file)
index 0000000..89fda42
--- /dev/null
@@ -0,0 +1,1811 @@
+#include "cache.h"
+#include "commit.h"
+#include "pack.h"
+#include "fetch.h"
+#include "tag.h"
+#include "blob.h"
+
+#include <curl/curl.h>
+#include <curl/easy.h>
+#include <expat.h>
+
+static const char http_push_usage[] =
+"git-http-push [--complete] [--force] [--verbose] <url> <ref> [<ref>...]\n";
+
+#if LIBCURL_VERSION_NUM >= 0x070908
+#define USE_CURL_MULTI
+#define DEFAULT_MAX_REQUESTS 5
+#endif
+
+#if LIBCURL_VERSION_NUM < 0x070704
+#define curl_global_cleanup() do { /* nothing */ } while(0)
+#endif
+#if LIBCURL_VERSION_NUM < 0x070800
+#define curl_global_init(a) do { /* nothing */ } while(0)
+#endif
+
+#if LIBCURL_VERSION_NUM < 0x070c04
+#define NO_CURL_EASY_DUPHANDLE
+#endif
+
+#define RANGE_HEADER_SIZE 30
+
+/* DAV method names and request body templates */
+#define DAV_LOCK "LOCK"
+#define DAV_MKCOL "MKCOL"
+#define DAV_MOVE "MOVE"
+#define DAV_PROPFIND "PROPFIND"
+#define DAV_PUT "PUT"
+#define DAV_UNLOCK "UNLOCK"
+#define PROPFIND_REQUEST "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n<D:propfind xmlns:D=\"DAV:\">\n<D:prop xmlns:R=\"%s\">\n<D:supportedlock/>\n</D:prop>\n</D:propfind>"
+#define LOCK_REQUEST "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n<D:lockinfo xmlns:D=\"DAV:\">\n<D:lockscope><D:exclusive/></D:lockscope>\n<D:locktype><D:write/></D:locktype>\n<D:owner>\n<D:href>mailto:%s</D:href>\n</D:owner>\n</D:lockinfo>"
+
+#define LOCK_TIME 600
+#define LOCK_REFRESH 30
+
+static int active_requests = 0;
+static int data_received;
+static int pushing = 0;
+static int aborted = 0;
+
+#ifdef USE_CURL_MULTI
+static int max_requests = -1;
+static CURLM *curlm;
+#endif
+#ifndef NO_CURL_EASY_DUPHANDLE
+static CURL *curl_default;
+#endif
+static struct curl_slist *no_pragma_header;
+static struct curl_slist *default_headers;
+static char curl_errorstr[CURL_ERROR_SIZE];
+
+static int push_verbosely = 0;
+static int push_all = 0;
+static int force_all = 0;
+
+struct buffer
+{
+        size_t posn;
+        size_t size;
+        void *buffer;
+};
+
+struct repo
+{
+       char *url;
+       struct packed_git *packs;
+};
+
+static struct repo *remote = NULL;
+
+enum transfer_state {
+       NEED_CHECK,
+       RUN_HEAD,
+       NEED_PUSH,
+       RUN_MKCOL,
+       RUN_PUT,
+       RUN_MOVE,
+       ABORTED,
+       COMPLETE,
+};
+
+struct transfer_request
+{
+       unsigned char sha1[20];
+       char *url;
+       char *dest;
+       struct active_lock *lock;
+       struct curl_slist *headers;
+       struct buffer buffer;
+       char filename[PATH_MAX];
+       char tmpfile[PATH_MAX];
+       enum transfer_state state;
+       CURLcode curl_result;
+       char errorstr[CURL_ERROR_SIZE];
+       long http_code;
+       unsigned char real_sha1[20];
+       SHA_CTX c;
+       z_stream stream;
+       int zret;
+       int rename;
+       struct active_request_slot *slot;
+       struct transfer_request *next;
+};
+
+struct active_request_slot
+{
+       CURL *curl;
+       FILE *local;
+       int in_use;
+       int done;
+       CURLcode curl_result;
+       long http_code;
+       struct active_request_slot *next;
+};
+
+static struct transfer_request *request_queue_head = NULL;
+static struct active_request_slot *active_queue_head = NULL;
+
+static int curl_ssl_verify = -1;
+static char *ssl_cert = NULL;
+#if LIBCURL_VERSION_NUM >= 0x070902
+static char *ssl_key = NULL;
+#endif
+#if LIBCURL_VERSION_NUM >= 0x070908
+static char *ssl_capath = NULL;
+#endif
+static char *ssl_cainfo = NULL;
+static long curl_low_speed_limit = -1;
+static long curl_low_speed_time = -1;
+
+struct active_lock
+{
+       int ctx_activelock;
+       int ctx_owner;
+       int ctx_owner_href;
+       int ctx_timeout;
+       int ctx_locktoken;
+       int ctx_locktoken_href;
+       char *url;
+       char *owner;
+       char *token;
+       time_t start_time;
+       long timeout;
+       int refreshing;
+};
+
+struct lockprop
+{
+       int supported_lock;
+       int lock_entry;
+       int lock_scope;
+       int lock_type;
+       int lock_exclusive;
+       int lock_exclusive_write;
+};
+
+static int http_options(const char *var, const char *value)
+{
+       if (!strcmp("http.sslverify", var)) {
+               if (curl_ssl_verify == -1) {
+                       curl_ssl_verify = git_config_bool(var, value);
+               }
+               return 0;
+       }
+
+       if (!strcmp("http.sslcert", var)) {
+               if (ssl_cert == NULL) {
+                       ssl_cert = xmalloc(strlen(value)+1);
+                       strcpy(ssl_cert, value);
+               }
+               return 0;
+       }
+#if LIBCURL_VERSION_NUM >= 0x070902
+       if (!strcmp("http.sslkey", var)) {
+               if (ssl_key == NULL) {
+                       ssl_key = xmalloc(strlen(value)+1);
+                       strcpy(ssl_key, value);
+               }
+               return 0;
+       }
+#endif
+#if LIBCURL_VERSION_NUM >= 0x070908
+       if (!strcmp("http.sslcapath", var)) {
+               if (ssl_capath == NULL) {
+                       ssl_capath = xmalloc(strlen(value)+1);
+                       strcpy(ssl_capath, value);
+               }
+               return 0;
+       }
+#endif
+       if (!strcmp("http.sslcainfo", var)) {
+               if (ssl_cainfo == NULL) {
+                       ssl_cainfo = xmalloc(strlen(value)+1);
+                       strcpy(ssl_cainfo, value);
+               }
+               return 0;
+       }
+
+#ifdef USE_CURL_MULTI  
+       if (!strcmp("http.maxrequests", var)) {
+               if (max_requests == -1)
+                       max_requests = git_config_int(var, value);
+               return 0;
+       }
+#endif
+
+       if (!strcmp("http.lowspeedlimit", var)) {
+               if (curl_low_speed_limit == -1)
+                       curl_low_speed_limit = (long)git_config_int(var, value);
+               return 0;
+       }
+       if (!strcmp("http.lowspeedtime", var)) {
+               if (curl_low_speed_time == -1)
+                       curl_low_speed_time = (long)git_config_int(var, value);
+               return 0;
+       }
+
+       /* Fall back on the default ones */
+       return git_default_config(var, value);
+}
+
+static size_t fread_buffer(void *ptr, size_t eltsize, size_t nmemb,
+                          struct buffer *buffer)
+{
+       size_t size = eltsize * nmemb;
+       if (size > buffer->size - buffer->posn)
+               size = buffer->size - buffer->posn;
+       memcpy(ptr, buffer->buffer + buffer->posn, size);
+       buffer->posn += size;
+       return size;
+}
+
+static size_t fwrite_buffer_dynamic(const void *ptr, size_t eltsize,
+                                   size_t nmemb, struct buffer *buffer)
+{
+       size_t size = eltsize * nmemb;
+       if (size > buffer->size - buffer->posn) {
+               buffer->size = buffer->size * 3 / 2;
+               if (buffer->size < buffer->posn + size)
+                       buffer->size = buffer->posn + size;
+               buffer->buffer = xrealloc(buffer->buffer, buffer->size);
+       }
+       memcpy(buffer->buffer + buffer->posn, ptr, size);
+       buffer->posn += size;
+       data_received++;
+       return size;
+}
+
+static size_t fwrite_null(const void *ptr, size_t eltsize,
+                         size_t nmemb, struct buffer *buffer)
+{
+       data_received++;
+       return eltsize * nmemb;
+}
+
+#ifdef USE_CURL_MULTI
+static void process_curl_messages(void);
+static void process_request_queue(void);
+#endif
+
+static CURL* get_curl_handle(void)
+{
+       CURL* result = curl_easy_init();
+
+       curl_easy_setopt(result, CURLOPT_SSL_VERIFYPEER, curl_ssl_verify);
+#if LIBCURL_VERSION_NUM >= 0x070907
+       curl_easy_setopt(result, CURLOPT_NETRC, CURL_NETRC_OPTIONAL);
+#endif
+
+       if (ssl_cert != NULL)
+               curl_easy_setopt(result, CURLOPT_SSLCERT, ssl_cert);
+#if LIBCURL_VERSION_NUM >= 0x070902
+       if (ssl_key != NULL)
+               curl_easy_setopt(result, CURLOPT_SSLKEY, ssl_key);
+#endif
+#if LIBCURL_VERSION_NUM >= 0x070908
+       if (ssl_capath != NULL)
+               curl_easy_setopt(result, CURLOPT_CAPATH, ssl_capath);
+#endif
+       if (ssl_cainfo != NULL)
+               curl_easy_setopt(result, CURLOPT_CAINFO, ssl_cainfo);
+       curl_easy_setopt(result, CURLOPT_FAILONERROR, 1);
+
+       if (curl_low_speed_limit > 0 && curl_low_speed_time > 0) {
+               curl_easy_setopt(result, CURLOPT_LOW_SPEED_LIMIT,
+                                curl_low_speed_limit);
+               curl_easy_setopt(result, CURLOPT_LOW_SPEED_TIME,
+                                curl_low_speed_time);
+       }
+
+       return result;
+}
+
+static struct active_request_slot *get_active_slot(void)
+{
+       struct active_request_slot *slot = active_queue_head;
+       struct active_request_slot *newslot;
+
+#ifdef USE_CURL_MULTI
+       int num_transfers;
+
+       /* Wait for a slot to open up if the queue is full */
+       while (active_requests >= max_requests) {
+               curl_multi_perform(curlm, &num_transfers);
+               if (num_transfers < active_requests) {
+                       process_curl_messages();
+               }
+       }
+#endif
+
+       while (slot != NULL && slot->in_use) {
+               slot = slot->next;
+       }
+       if (slot == NULL) {
+               newslot = xmalloc(sizeof(*newslot));
+               newslot->curl = NULL;
+               newslot->in_use = 0;
+               newslot->next = NULL;
+
+               slot = active_queue_head;
+               if (slot == NULL) {
+                       active_queue_head = newslot;
+               } else {
+                       while (slot->next != NULL) {
+                               slot = slot->next;
+                       }
+                       slot->next = newslot;
+               }
+               slot = newslot;
+       }
+
+       if (slot->curl == NULL) {
+#ifdef NO_CURL_EASY_DUPHANDLE
+               slot->curl = get_curl_handle();
+#else
+               slot->curl = curl_easy_duphandle(curl_default);
+#endif
+       }
+
+       active_requests++;
+       slot->in_use = 1;
+       slot->done = 0;
+       slot->local = NULL;
+       curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, default_headers);
+       curl_easy_setopt(slot->curl, CURLOPT_ERRORBUFFER, curl_errorstr);
+
+       return slot;
+}
+
+static int start_active_slot(struct active_request_slot *slot)
+{
+#ifdef USE_CURL_MULTI
+       CURLMcode curlm_result = curl_multi_add_handle(curlm, slot->curl);
+
+       if (curlm_result != CURLM_OK &&
+           curlm_result != CURLM_CALL_MULTI_PERFORM) {
+               active_requests--;
+               slot->in_use = 0;
+               return 0;
+       }
+#endif
+       return 1;
+}
+
+static void run_active_slot(struct active_request_slot *slot)
+{
+#ifdef USE_CURL_MULTI
+       int num_transfers;
+       long last_pos = 0;
+       long current_pos;
+       fd_set readfds;
+       fd_set writefds;
+       fd_set excfds;
+       int max_fd;
+       struct timeval select_timeout;
+       CURLMcode curlm_result;
+
+       while (!slot->done) {
+               data_received = 0;
+               do {
+                       curlm_result = curl_multi_perform(curlm,
+                                                         &num_transfers);
+               } while (curlm_result == CURLM_CALL_MULTI_PERFORM);
+               if (num_transfers < active_requests) {
+                       process_curl_messages();
+                       process_request_queue();
+               }
+
+               if (!data_received && slot->local != NULL) {
+                       current_pos = ftell(slot->local);
+                       if (current_pos > last_pos)
+                               data_received++;
+                       last_pos = current_pos;
+               }
+
+               if (!slot->done && !data_received) {
+                       max_fd = 0;
+                       FD_ZERO(&readfds);
+                       FD_ZERO(&writefds);
+                       FD_ZERO(&excfds);
+                       select_timeout.tv_sec = 0;
+                       select_timeout.tv_usec = 50000;
+                       select(max_fd, &readfds, &writefds,
+                              &excfds, &select_timeout);
+               }
+       }
+#else
+       slot->curl_result = curl_easy_perform(slot->curl);
+       active_requests--;
+#endif
+}
+
+static void start_check(struct transfer_request *request)
+{
+       char *hex = sha1_to_hex(request->sha1);
+       struct active_request_slot *slot;
+       char *posn;
+
+       request->url = xmalloc(strlen(remote->url) + 55);
+       strcpy(request->url, remote->url);
+       posn = request->url + strlen(remote->url);
+       strcpy(posn, "objects/");
+       posn += 8;
+       memcpy(posn, hex, 2);
+       posn += 2;
+       *(posn++) = '/';
+       strcpy(posn, hex + 2);
+
+       slot = get_active_slot();
+       curl_easy_setopt(slot->curl, CURLOPT_ERRORBUFFER, request->errorstr);
+       curl_easy_setopt(slot->curl, CURLOPT_URL, request->url);
+       curl_easy_setopt(slot->curl, CURLOPT_NOBODY, 1);
+
+       if (start_active_slot(slot)) {
+               request->slot = slot;
+               request->state = RUN_HEAD;
+       } else {
+               request->state = ABORTED;
+               free(request->url);
+       }
+}
+
+static void start_mkcol(struct transfer_request *request)
+{
+       char *hex = sha1_to_hex(request->sha1);
+       struct active_request_slot *slot;
+       char *posn;
+
+       request->url = xmalloc(strlen(remote->url) + 13);
+       strcpy(request->url, remote->url);
+       posn = request->url + strlen(remote->url);
+       strcpy(posn, "objects/");
+       posn += 8;
+       memcpy(posn, hex, 2);
+       posn += 2;
+       strcpy(posn, "/");
+
+       slot = get_active_slot();
+       curl_easy_setopt(slot->curl, CURLOPT_HTTPGET, 1); /* undo PUT setup */
+       curl_easy_setopt(slot->curl, CURLOPT_URL, request->url);
+       curl_easy_setopt(slot->curl, CURLOPT_ERRORBUFFER, request->errorstr);
+       curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, DAV_MKCOL);
+       curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_null);
+
+       if (start_active_slot(slot)) {
+               request->slot = slot;
+               request->state = RUN_MKCOL;
+       } else {
+               request->state = ABORTED;
+               free(request->url);
+       }
+}
+
+static void start_put(struct transfer_request *request)
+{
+       char *hex = sha1_to_hex(request->sha1);
+       struct active_request_slot *slot;
+       char *posn;
+       char type[20];
+       char hdr[50];
+       void *unpacked;
+       unsigned long len;
+       int hdrlen;
+       ssize_t size;
+       z_stream stream;
+
+       unpacked = read_sha1_file(request->sha1, type, &len);
+       hdrlen = sprintf(hdr, "%s %lu", type, len) + 1;
+
+       /* Set it up */
+       memset(&stream, 0, sizeof(stream));
+       deflateInit(&stream, Z_BEST_COMPRESSION);
+       size = deflateBound(&stream, len + hdrlen);
+       request->buffer.buffer = xmalloc(size);
+
+       /* Compress it */
+       stream.next_out = request->buffer.buffer;
+       stream.avail_out = size;
+
+       /* First header.. */
+       stream.next_in = (void *)hdr;
+       stream.avail_in = hdrlen;
+       while (deflate(&stream, 0) == Z_OK)
+               /* nothing */;
+
+       /* Then the data itself.. */
+       stream.next_in = unpacked;
+       stream.avail_in = len;
+       while (deflate(&stream, Z_FINISH) == Z_OK)
+               /* nothing */;
+       deflateEnd(&stream);
+       free(unpacked);
+
+       request->buffer.size = stream.total_out;
+       request->buffer.posn = 0;
+
+       if (request->url != NULL)
+               free(request->url);
+       request->url = xmalloc(strlen(remote->url) + 
+                              strlen(request->lock->token) + 51);
+       strcpy(request->url, remote->url);
+       posn = request->url + strlen(remote->url);
+       strcpy(posn, "objects/");
+       posn += 8;
+       memcpy(posn, hex, 2);
+       posn += 2;
+       *(posn++) = '/';
+       strcpy(posn, hex + 2);
+       request->dest = xmalloc(strlen(request->url) + 14);
+       sprintf(request->dest, "Destination: %s", request->url);
+       posn += 38;
+       *(posn++) = '.';
+       strcpy(posn, request->lock->token);
+
+       slot = get_active_slot();
+       curl_easy_setopt(slot->curl, CURLOPT_INFILE, &request->buffer);
+       curl_easy_setopt(slot->curl, CURLOPT_INFILESIZE, request->buffer.size);
+       curl_easy_setopt(slot->curl, CURLOPT_READFUNCTION, fread_buffer);
+       curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_null);
+       curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, DAV_PUT);
+       curl_easy_setopt(slot->curl, CURLOPT_UPLOAD, 1);
+       curl_easy_setopt(slot->curl, CURLOPT_PUT, 1);
+       curl_easy_setopt(slot->curl, CURLOPT_NOBODY, 0);
+       curl_easy_setopt(slot->curl, CURLOPT_URL, request->url);
+
+       if (start_active_slot(slot)) {
+               request->slot = slot;
+               request->state = RUN_PUT;
+       } else {
+               request->state = ABORTED;
+               free(request->url);
+       }
+}
+
+static void start_move(struct transfer_request *request)
+{
+       struct active_request_slot *slot;
+       struct curl_slist *dav_headers = NULL;
+
+       slot = get_active_slot();
+       curl_easy_setopt(slot->curl, CURLOPT_HTTPGET, 1); /* undo PUT setup */
+       curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, DAV_MOVE);
+       dav_headers = curl_slist_append(dav_headers, request->dest);
+       dav_headers = curl_slist_append(dav_headers, "Overwrite: T");
+       curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, dav_headers);
+       curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_null);
+       curl_easy_setopt(slot->curl, CURLOPT_URL, request->url);
+
+       if (start_active_slot(slot)) {
+               request->slot = slot;
+               request->state = RUN_MOVE;
+       } else {
+               request->state = ABORTED;
+               free(request->url);
+       }
+}
+
+int refresh_lock(struct active_lock *lock)
+{
+       struct active_request_slot *slot;
+       char *if_header;
+       char timeout_header[25];
+       struct curl_slist *dav_headers = NULL;
+       int rc = 0;
+
+       lock->refreshing = 1;
+
+       if_header = xmalloc(strlen(lock->token) + 25);
+       sprintf(if_header, "If: (<opaquelocktoken:%s>)", lock->token);
+       sprintf(timeout_header, "Timeout: Second-%ld", lock->timeout);
+       dav_headers = curl_slist_append(dav_headers, if_header);
+       dav_headers = curl_slist_append(dav_headers, timeout_header);
+
+       slot = get_active_slot();
+       curl_easy_setopt(slot->curl, CURLOPT_HTTPGET, 1);
+       curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_null);
+       curl_easy_setopt(slot->curl, CURLOPT_URL, lock->url);
+       curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, DAV_LOCK);
+       curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, dav_headers);
+
+       if (start_active_slot(slot)) {
+               run_active_slot(slot);
+               if (slot->curl_result != CURLE_OK) {
+                       fprintf(stderr, "Got HTTP error %ld\n", slot->http_code);
+               } else {
+                       lock->start_time = time(NULL);
+                       rc = 1;
+               }
+       }
+
+       lock->refreshing = 0;
+       curl_slist_free_all(dav_headers);
+       free(if_header);
+
+       return rc;
+}
+
+static void finish_request(struct transfer_request *request)
+{
+       time_t current_time = time(NULL);
+       int time_remaining;
+
+       request->curl_result =  request->slot->curl_result;
+       request->http_code = request->slot->http_code;
+       request->slot = NULL;
+
+       /* Refresh the lock if it is close to timing out */
+       time_remaining = request->lock->start_time + request->lock->timeout
+               - current_time;
+       if (time_remaining < LOCK_REFRESH && !request->lock->refreshing) {
+               if (!refresh_lock(request->lock)) {
+                       fprintf(stderr, "Unable to refresh remote lock\n");
+                       aborted = 1;
+               }
+       }
+
+       if (request->headers != NULL)
+               curl_slist_free_all(request->headers);
+       if (request->state == RUN_HEAD) {
+               if (request->http_code == 404) {
+                       request->state = NEED_PUSH;
+               } else if (request->curl_result == CURLE_OK) {
+                       request->state = COMPLETE;
+               } else {
+                       fprintf(stderr, "HEAD %s failed, aborting (%d/%ld)\n",
+                               sha1_to_hex(request->sha1),
+                               request->curl_result, request->http_code);
+                       request->state = ABORTED;
+                       aborted = 1;
+               }
+       } else if (request->state == RUN_MKCOL) {
+               if (request->curl_result == CURLE_OK ||
+                   request->http_code == 405) {
+                       start_put(request);
+               } else {
+                       fprintf(stderr, "MKCOL %s failed, aborting (%d/%ld)\n",
+                               sha1_to_hex(request->sha1),
+                               request->curl_result, request->http_code);
+                       request->state = ABORTED;
+                       aborted = 1;
+               }
+       } else if (request->state == RUN_PUT) {
+               if (request->curl_result == CURLE_OK) {
+                       start_move(request);
+               } else {
+                       fprintf(stderr, "PUT %s failed, aborting (%d/%ld)\n",
+                               sha1_to_hex(request->sha1),
+                               request->curl_result, request->http_code);
+                       request->state = ABORTED;
+                       aborted = 1;
+               }
+       } else if (request->state == RUN_MOVE) {
+               if (request->curl_result == CURLE_OK) {
+                       if (push_verbosely)
+                               fprintf(stderr,
+                                       "sent %s\n",
+                                       sha1_to_hex(request->sha1));
+                       request->state = COMPLETE;
+               } else {
+                       fprintf(stderr, "MOVE %s failed, aborting (%d/%ld)\n",
+                               sha1_to_hex(request->sha1),
+                               request->curl_result, request->http_code);
+                       request->state = ABORTED;
+                       aborted = 1;
+               }
+       }
+}
+
+static void release_request(struct transfer_request *request)
+{
+       struct transfer_request *entry = request_queue_head;
+
+       if (request == request_queue_head) {
+               request_queue_head = request->next;
+       } else {
+               while (entry->next != NULL && entry->next != request)
+                       entry = entry->next;
+               if (entry->next == request)
+                       entry->next = entry->next->next;
+       }
+
+       free(request->url);
+       free(request);
+}
+
+#ifdef USE_CURL_MULTI
+void process_curl_messages(void)
+{
+       int num_messages;
+       struct active_request_slot *slot;
+       struct transfer_request *request = NULL;
+       CURLMsg *curl_message = curl_multi_info_read(curlm, &num_messages);
+
+       while (curl_message != NULL) {
+               if (curl_message->msg == CURLMSG_DONE) {
+                       slot = active_queue_head;
+                       while (slot != NULL &&
+                              slot->curl != curl_message->easy_handle)
+                               slot = slot->next;
+                       if (slot != NULL) {
+                               curl_multi_remove_handle(curlm, slot->curl);
+                               active_requests--;
+                               slot->done = 1;
+                               slot->in_use = 0;
+                               slot->curl_result = curl_message->data.result;
+                               curl_easy_getinfo(slot->curl,
+                                                 CURLINFO_HTTP_CODE,
+                                                 &slot->http_code);
+                               request = request_queue_head;
+                               while (request != NULL &&
+                                      request->slot != slot)
+                                       request = request->next;
+                               if (request != NULL)
+                                       finish_request(request);
+                       } else {
+                               fprintf(stderr, "Received DONE message for unknown request!\n");
+                       }
+               } else {
+                       fprintf(stderr, "Unknown CURL message received: %d\n",
+                               (int)curl_message->msg);
+               }
+               curl_message = curl_multi_info_read(curlm, &num_messages);
+       }
+}
+
+void process_request_queue(void)
+{
+       struct transfer_request *request = request_queue_head;
+       struct active_request_slot *slot = active_queue_head;
+       int num_transfers;
+
+       if (aborted)
+               return;
+
+       while (active_requests < max_requests && request != NULL) {
+               if (!pushing && request->state == NEED_CHECK) {
+                       start_check(request);
+                       curl_multi_perform(curlm, &num_transfers);
+               } else if (pushing && request->state == NEED_PUSH) {
+                       start_mkcol(request);
+                       curl_multi_perform(curlm, &num_transfers);
+               }
+               request = request->next;
+       }
+
+       while (slot != NULL) {
+               if (!slot->in_use && slot->curl != NULL) {
+                       curl_easy_cleanup(slot->curl);
+                       slot->curl = NULL;
+               }
+               slot = slot->next;
+       }                               
+}
+#endif
+
+void process_waiting_requests(void)
+{
+       struct active_request_slot *slot = active_queue_head;
+
+       while (slot != NULL)
+               if (slot->in_use) {
+                       run_active_slot(slot);
+                       slot = active_queue_head;
+               } else {
+                       slot = slot->next;
+               }
+}
+
+void add_request(unsigned char *sha1, struct active_lock *lock)
+{
+       struct transfer_request *request = request_queue_head;
+       struct packed_git *target;
+       
+       while (request != NULL && memcmp(request->sha1, sha1, 20))
+               request = request->next;
+       if (request != NULL)
+               return;
+
+       target = find_sha1_pack(sha1, remote->packs);
+       if (target)
+               return;
+
+       request = xmalloc(sizeof(*request));
+       memcpy(request->sha1, sha1, 20);
+       request->url = NULL;
+       request->lock = lock;
+       request->headers = NULL;
+       request->state = NEED_CHECK;
+       request->next = request_queue_head;
+       request_queue_head = request;
+#ifdef USE_CURL_MULTI
+       process_request_queue();
+       process_curl_messages();
+#endif
+}
+
+static int fetch_index(unsigned char *sha1)
+{
+       char *hex = sha1_to_hex(sha1);
+       char *filename;
+       char *url;
+       char tmpfile[PATH_MAX];
+       long prev_posn = 0;
+       char range[RANGE_HEADER_SIZE];
+       struct curl_slist *range_header = NULL;
+
+       FILE *indexfile;
+       struct active_request_slot *slot;
+
+       /* Don't use the index if the pack isn't there */
+       url = xmalloc(strlen(remote->url) + 65);
+       sprintf(url, "%s/objects/pack/pack-%s.pack", remote->url, hex);
+       slot = get_active_slot();
+       curl_easy_setopt(slot->curl, CURLOPT_URL, url);
+       curl_easy_setopt(slot->curl, CURLOPT_NOBODY, 1);
+       if (start_active_slot(slot)) {
+               run_active_slot(slot);
+               if (slot->curl_result != CURLE_OK) {
+                       free(url);
+                       return error("Unable to verify pack %s is available",
+                                    hex);
+               }
+       } else {
+               return error("Unable to start request");
+       }
+
+       if (has_pack_index(sha1))
+               return 0;
+
+       if (push_verbosely)
+               fprintf(stderr, "Getting index for pack %s\n", hex);
+       
+       sprintf(url, "%s/objects/pack/pack-%s.idx", remote->url, hex);
+       
+       filename = sha1_pack_index_name(sha1);
+       snprintf(tmpfile, sizeof(tmpfile), "%s.temp", filename);
+       indexfile = fopen(tmpfile, "a");
+       if (!indexfile)
+               return error("Unable to open local file %s for pack index",
+                            filename);
+
+       slot = get_active_slot();
+       curl_easy_setopt(slot->curl, CURLOPT_NOBODY, 0);
+       curl_easy_setopt(slot->curl, CURLOPT_HTTPGET, 1);
+       curl_easy_setopt(slot->curl, CURLOPT_FILE, indexfile);
+       curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite);
+       curl_easy_setopt(slot->curl, CURLOPT_URL, url);
+       curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, no_pragma_header);
+       slot->local = indexfile;
+
+       /* If there is data present from a previous transfer attempt,
+          resume where it left off */
+       prev_posn = ftell(indexfile);
+       if (prev_posn>0) {
+               if (push_verbosely)
+                       fprintf(stderr,
+                               "Resuming fetch of index for pack %s at byte %ld\n",
+                               hex, prev_posn);
+               sprintf(range, "Range: bytes=%ld-", prev_posn);
+               range_header = curl_slist_append(range_header, range);
+               curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, range_header);
+       }
+
+       if (start_active_slot(slot)) {
+               run_active_slot(slot);
+               if (slot->curl_result != CURLE_OK) {
+                       free(url);
+                       fclose(indexfile);
+                       return error("Unable to get pack index %s\n%s", url,
+                                    curl_errorstr);
+               }
+       } else {
+               free(url);
+               return error("Unable to start request");
+       }
+
+       free(url);
+       fclose(indexfile);
+
+       return move_temp_to_file(tmpfile, filename);
+}
+
+static int setup_index(unsigned char *sha1)
+{
+       struct packed_git *new_pack;
+
+       if (fetch_index(sha1))
+               return -1;
+
+       new_pack = parse_pack_index(sha1);
+       new_pack->next = remote->packs;
+       remote->packs = new_pack;
+       return 0;
+}
+
+static int fetch_indices()
+{
+       unsigned char sha1[20];
+       char *url;
+       struct buffer buffer;
+       char *data;
+       int i = 0;
+
+       struct active_request_slot *slot;
+
+       data = xmalloc(4096);
+       memset(data, 0, 4096);
+       buffer.size = 4096;
+       buffer.posn = 0;
+       buffer.buffer = data;
+
+       if (push_verbosely)
+               fprintf(stderr, "Getting pack list\n");
+       
+       url = xmalloc(strlen(remote->url) + 21);
+       sprintf(url, "%s/objects/info/packs", remote->url);
+
+       slot = get_active_slot();
+       curl_easy_setopt(slot->curl, CURLOPT_FILE, &buffer);
+       curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION,
+                        fwrite_buffer_dynamic);
+       curl_easy_setopt(slot->curl, CURLOPT_URL, url);
+       curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, NULL);
+       if (start_active_slot(slot)) {
+               run_active_slot(slot);
+               if (slot->curl_result != CURLE_OK) {
+                       free(buffer.buffer);
+                       free(url);
+                       if (slot->http_code == 404)
+                               return 0;
+                       else
+                               return error("%s", curl_errorstr);
+               }
+       } else {
+               free(buffer.buffer);
+               free(url);
+               return error("Unable to start request");
+       }
+       free(url);
+
+       data = buffer.buffer;
+       while (i < buffer.posn) {
+               switch (data[i]) {
+               case 'P':
+                       i++;
+                       if (i + 52 < buffer.posn &&
+                           !strncmp(data + i, " pack-", 6) &&
+                           !strncmp(data + i + 46, ".pack\n", 6)) {
+                               get_sha1_hex(data + i + 6, sha1);
+                               setup_index(sha1);
+                               i += 51;
+                               break;
+                       }
+               default:
+                       while (data[i] != '\n')
+                               i++;
+               }
+               i++;
+       }
+
+       free(buffer.buffer);
+       return 0;
+}
+
+static inline int needs_quote(int ch)
+{
+       switch (ch) {
+       case '/': case '-': case '.':
+       case 'A'...'Z': case 'a'...'z': case '0'...'9':
+               return 0;
+       default:
+               return 1;
+       }
+}
+
+static inline int hex(int v)
+{
+       if (v < 10) return '0' + v;
+       else return 'A' + v - 10;
+}
+
+static char *quote_ref_url(const char *base, const char *ref)
+{
+       const char *cp;
+       char *dp, *qref;
+       int len, baselen, ch;
+
+       baselen = strlen(base);
+       len = baselen + 12; /* "refs/heads/" + NUL */
+       for (cp = ref; (ch = *cp) != 0; cp++, len++)
+               if (needs_quote(ch))
+                       len += 2; /* extra two hex plus replacement % */
+       qref = xmalloc(len);
+       memcpy(qref, base, baselen);
+       memcpy(qref + baselen, "refs/heads/", 11);
+       for (cp = ref, dp = qref + baselen + 11; (ch = *cp) != 0; cp++) {
+               if (needs_quote(ch)) {
+                       *dp++ = '%';
+                       *dp++ = hex((ch >> 4) & 0xF);
+                       *dp++ = hex(ch & 0xF);
+               }
+               else
+                       *dp++ = ch;
+       }
+       *dp = 0;
+
+       return qref;
+}
+
+int fetch_ref(char *ref, unsigned char *sha1)
+{
+        char *url;
+        char hex[42];
+        struct buffer buffer;
+       char *base = remote->url;
+       struct active_request_slot *slot;
+        buffer.size = 41;
+        buffer.posn = 0;
+        buffer.buffer = hex;
+        hex[41] = '\0';
+        
+       url = quote_ref_url(base, ref);
+       slot = get_active_slot();
+       curl_easy_setopt(slot->curl, CURLOPT_FILE, &buffer);
+       curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION,
+                        fwrite_buffer_dynamic);
+       curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, NULL);
+       curl_easy_setopt(slot->curl, CURLOPT_URL, url);
+       if (start_active_slot(slot)) {
+               run_active_slot(slot);
+               if (slot->curl_result != CURLE_OK)
+                       return error("Couldn't get %s for %s\n%s",
+                                    url, ref, curl_errorstr);
+       } else {
+               return error("Unable to start request");
+       }
+
+        hex[40] = '\0';
+        get_sha1_hex(hex, sha1);
+        return 0;
+}
+
+static void
+start_activelock_element(void *userData, const char *name, const char **atts)
+{
+       struct active_lock *lock = (struct active_lock *)userData;
+
+       if (lock->ctx_activelock && !strcmp(name, "D:timeout"))
+               lock->ctx_timeout = 1;
+       else if (lock->ctx_owner && strstr(name, "href"))
+               lock->ctx_owner_href = 1;
+       else if (lock->ctx_activelock && strstr(name, "owner"))
+               lock->ctx_owner = 1;
+       else if (lock->ctx_locktoken && !strcmp(name, "D:href"))
+               lock->ctx_locktoken_href = 1;
+       else if (lock->ctx_activelock && !strcmp(name, "D:locktoken"))
+               lock->ctx_locktoken = 1;
+       else if (!strcmp(name, "D:activelock"))
+               lock->ctx_activelock = 1;
+}
+
+static void
+end_activelock_element(void *userData, const char *name)
+{
+       struct active_lock *lock = (struct active_lock *)userData;
+
+       if (lock->ctx_timeout && !strcmp(name, "D:timeout")) {
+               lock->ctx_timeout = 0;
+       } else if (lock->ctx_owner_href && strstr(name, "href")) {
+               lock->ctx_owner_href = 0;
+       } else if (lock->ctx_owner && strstr(name, "owner")) {
+               lock->ctx_owner = 0;
+       } else if (lock->ctx_locktoken_href && !strcmp(name, "D:href")) {
+               lock->ctx_locktoken_href = 0;
+       } else if (lock->ctx_locktoken && !strcmp(name, "D:locktoken")) {
+               lock->ctx_locktoken = 0;
+       } else if (lock->ctx_activelock && !strcmp(name, "D:activelock")) {
+               lock->ctx_activelock = 0;
+       }
+}
+
+static void
+activelock_cdata(void *userData, const XML_Char *s, int len)
+{
+       struct active_lock *lock = (struct active_lock *)userData;
+       char *this = malloc(len+1);
+       strncpy(this, s, len);
+
+       if (lock->ctx_owner_href) {
+               lock->owner = malloc(len+1);
+               strcpy(lock->owner, this);
+       } else if (lock->ctx_locktoken_href) {
+               if (!strncmp(this, "opaquelocktoken:", 16)) {
+                       lock->token = malloc(len-15);
+                       strcpy(lock->token, this+16);
+               }
+       } else if (lock->ctx_timeout) {
+               if (!strncmp(this, "Second-", 7))
+                       lock->timeout = strtol(this+7, NULL, 10);
+       }
+
+       free(this);
+}
+
+static void
+start_lockprop_element(void *userData, const char *name, const char **atts)
+{
+       struct lockprop *prop = (struct lockprop *)userData;
+
+       if (prop->lock_type && !strcmp(name, "D:write")) {
+               if (prop->lock_exclusive) {
+                       prop->lock_exclusive_write = 1;
+               }
+       } else if (prop->lock_scope && !strcmp(name, "D:exclusive")) {
+               prop->lock_exclusive = 1;
+       } else if (prop->lock_entry) {
+               if (!strcmp(name, "D:lockscope")) {
+                       prop->lock_scope = 1;
+               } else if (!strcmp(name, "D:locktype")) {
+                       prop->lock_type = 1;
+               }
+       } else if (prop->supported_lock) {
+               if (!strcmp(name, "D:lockentry")) {
+                       prop->lock_entry = 1;
+               }
+       } else if (!strcmp(name, "D:supportedlock")) {
+               prop->supported_lock = 1;
+       }
+}
+
+static void
+end_lockprop_element(void *userData, const char *name)
+{
+       struct lockprop *prop = (struct lockprop *)userData;
+
+       if (!strcmp(name, "D:lockentry")) {
+               prop->lock_entry = 0;
+               prop->lock_scope = 0;
+               prop->lock_type = 0;
+               prop->lock_exclusive = 0;
+       } else if (!strcmp(name, "D:supportedlock")) {
+               prop->supported_lock = 0;
+       }
+}
+
+struct active_lock *lock_remote(char *file, long timeout)
+{
+       struct active_request_slot *slot;
+       struct buffer out_buffer;
+       struct buffer in_buffer;
+       char *out_data;
+       char *in_data;
+       char *url;
+       char *ep;
+       char timeout_header[25];
+       struct active_lock *new_lock;
+       XML_Parser parser = XML_ParserCreate(NULL);
+       enum XML_Status result;
+       struct curl_slist *dav_headers = NULL;
+
+       url = xmalloc(strlen(remote->url) + strlen(file) + 1);
+       sprintf(url, "%s%s", remote->url, file);
+
+       /* Make sure leading directories exist for the remote ref */
+       ep = strchr(url + strlen(remote->url) + 11, '/');
+       while (ep) {
+               *ep = 0;
+               slot = get_active_slot();
+               curl_easy_setopt(slot->curl, CURLOPT_HTTPGET, 1);
+               curl_easy_setopt(slot->curl, CURLOPT_URL, url);
+               curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, DAV_MKCOL);
+               curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_null);
+               if (start_active_slot(slot)) {
+                       run_active_slot(slot);
+                       if (slot->curl_result != CURLE_OK &&
+                           slot->http_code != 405) {
+                               fprintf(stderr,
+                                       "Unable to create branch path %s\n",
+                                       url);
+                               free(url);
+                               return NULL;
+                       }
+               } else {
+                       fprintf(stderr, "Unable to start request\n");
+                       free(url);
+                       return NULL;
+               }
+               *ep = '/';
+               ep = strchr(ep + 1, '/');
+       }
+
+       out_buffer.size = strlen(LOCK_REQUEST) + strlen(git_default_email) - 2;
+       out_data = xmalloc(out_buffer.size + 1);
+       snprintf(out_data, out_buffer.size + 1, LOCK_REQUEST, git_default_email);
+       out_buffer.posn = 0;
+       out_buffer.buffer = out_data;
+
+       in_buffer.size = 4096;
+       in_data = xmalloc(in_buffer.size);
+       in_buffer.posn = 0;
+       in_buffer.buffer = in_data;
+
+       new_lock = xmalloc(sizeof(*new_lock));
+       new_lock->owner = NULL;
+       new_lock->token = NULL;
+       new_lock->timeout = -1;
+       new_lock->refreshing = 0;
+
+       sprintf(timeout_header, "Timeout: Second-%ld", timeout);
+       dav_headers = curl_slist_append(dav_headers, timeout_header);
+       dav_headers = curl_slist_append(dav_headers, "Content-Type: text/xml");
+
+       slot = get_active_slot();
+       curl_easy_setopt(slot->curl, CURLOPT_INFILE, &out_buffer);
+       curl_easy_setopt(slot->curl, CURLOPT_INFILESIZE, out_buffer.size);
+       curl_easy_setopt(slot->curl, CURLOPT_READFUNCTION, fread_buffer);
+       curl_easy_setopt(slot->curl, CURLOPT_FILE, &in_buffer);
+       curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION,
+                        fwrite_buffer_dynamic);
+       curl_easy_setopt(slot->curl, CURLOPT_URL, url);
+       curl_easy_setopt(slot->curl, CURLOPT_UPLOAD, 1);
+       curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, DAV_LOCK);
+       curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, dav_headers);
+
+       if (start_active_slot(slot)) {
+               run_active_slot(slot);
+               if (slot->curl_result != CURLE_OK) {
+                       fprintf(stderr, "Got HTTP error %ld\n", slot->http_code);
+                       free(new_lock);
+                       free(url);
+                       free(out_data);
+                       free(in_data);
+                       return NULL;
+               }
+       } else {
+               free(new_lock);
+               free(url);
+               free(out_data);
+               free(in_data);
+               fprintf(stderr, "Unable to start request\n");
+               return NULL;
+       }
+
+       free(out_data);
+
+       XML_SetUserData(parser, new_lock);
+       XML_SetElementHandler(parser, start_activelock_element,
+                                     end_activelock_element);
+       XML_SetCharacterDataHandler(parser, activelock_cdata);
+       result = XML_Parse(parser, in_buffer.buffer, in_buffer.posn, 1);
+       free(in_data);
+       if (result != XML_STATUS_OK) {
+               fprintf(stderr, "%s", XML_ErrorString(
+                               XML_GetErrorCode(parser)));
+               free(url);
+               free(new_lock);
+               return NULL;
+       }
+
+       if (new_lock->token == NULL || new_lock->timeout <= 0) {
+               if (new_lock->token != NULL)
+                       free(new_lock->token);
+               if (new_lock->owner != NULL)
+                       free(new_lock->owner);
+               free(url);
+               free(new_lock);
+               return NULL;
+       }
+
+       new_lock->url = url;
+       new_lock->start_time = time(NULL);
+       return new_lock;
+}
+
+int unlock_remote(struct active_lock *lock)
+{
+       struct active_request_slot *slot;
+       char *lock_token_header;
+       struct curl_slist *dav_headers = NULL;
+       int rc = 0;
+
+       lock_token_header = xmalloc(strlen(lock->token) + 31);
+       sprintf(lock_token_header, "Lock-Token: <opaquelocktoken:%s>",
+               lock->token);
+       dav_headers = curl_slist_append(dav_headers, lock_token_header);
+
+       slot = get_active_slot();
+       curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_null);
+       curl_easy_setopt(slot->curl, CURLOPT_URL, lock->url);
+       curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, DAV_UNLOCK);
+       curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, dav_headers);
+
+       if (start_active_slot(slot)) {
+               run_active_slot(slot);
+               if (slot->curl_result == CURLE_OK)
+                       rc = 1;
+               else
+                       fprintf(stderr, "Got HTTP error %ld\n",
+                               slot->http_code);
+       } else {
+               fprintf(stderr, "Unable to start request\n");
+       }
+
+       curl_slist_free_all(dav_headers);
+       free(lock_token_header);
+
+       if (lock->owner != NULL)
+               free(lock->owner);
+       free(lock->url);
+       free(lock->token);
+       free(lock);
+
+       return rc;
+}
+
+int check_locking()
+{
+       struct active_request_slot *slot;
+       struct buffer in_buffer;
+       struct buffer out_buffer;
+       char *in_data;
+       char *out_data;
+       XML_Parser parser = XML_ParserCreate(NULL);
+       enum XML_Status result;
+       struct lockprop supported_lock;
+       struct curl_slist *dav_headers = NULL;
+
+       out_buffer.size = strlen(PROPFIND_REQUEST) + strlen(remote->url) - 2;
+       out_data = xmalloc(out_buffer.size + 1);
+       snprintf(out_data, out_buffer.size + 1, PROPFIND_REQUEST, remote->url);
+       out_buffer.posn = 0;
+       out_buffer.buffer = out_data;
+
+       in_buffer.size = 4096;
+       in_data = xmalloc(in_buffer.size);
+       in_buffer.posn = 0;
+       in_buffer.buffer = in_data;
+
+       dav_headers = curl_slist_append(dav_headers, "Depth: 0");
+       dav_headers = curl_slist_append(dav_headers, "Content-Type: text/xml");
+       
+       slot = get_active_slot();
+       curl_easy_setopt(slot->curl, CURLOPT_INFILE, &out_buffer);
+       curl_easy_setopt(slot->curl, CURLOPT_INFILESIZE, out_buffer.size);
+       curl_easy_setopt(slot->curl, CURLOPT_READFUNCTION, fread_buffer);
+       curl_easy_setopt(slot->curl, CURLOPT_FILE, &in_buffer);
+       curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION,
+                        fwrite_buffer_dynamic);
+       curl_easy_setopt(slot->curl, CURLOPT_URL, remote->url);
+       curl_easy_setopt(slot->curl, CURLOPT_UPLOAD, 1);
+       curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, DAV_PROPFIND);
+       curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, dav_headers);
+
+       if (start_active_slot(slot)) {
+               run_active_slot(slot);
+               free(out_data);
+               if (slot->curl_result != CURLE_OK) {
+                       free(in_buffer.buffer);
+                       return -1;
+               }
+
+               XML_SetUserData(parser, &supported_lock);
+               XML_SetElementHandler(parser, start_lockprop_element,
+                                     end_lockprop_element);
+               result = XML_Parse(parser, in_buffer.buffer, in_buffer.posn, 1);
+               free(in_buffer.buffer);
+               if (result != XML_STATUS_OK)
+                       return error("%s", XML_ErrorString(
+                                            XML_GetErrorCode(parser)));
+       } else {
+               free(out_data);
+               free(in_buffer.buffer);
+               return error("Unable to start request");
+       }
+
+       if (supported_lock.lock_exclusive_write)
+               return 0;
+       else
+               return 1;
+}
+
+int is_ancestor(unsigned char *sha1, struct commit *commit)
+{
+       struct commit_list *parents;
+
+       if (parse_commit(commit))
+               return 0;
+       parents = commit->parents;
+       for (; parents; parents = parents->next) {
+               if (!memcmp(sha1, parents->item->object.sha1, 20)) {
+                       return 1;
+               } else if (parents->item->object.type == commit_type) {
+                       if (is_ancestor(
+                                   sha1,
+                                   (struct commit *)&parents->item->object
+                                   ))
+                               return 1;
+               }
+       }
+       return 0;
+}
+
+void get_delta(unsigned char *sha1, struct object *obj,
+              struct active_lock *lock)
+{
+       struct commit *commit;
+       struct commit_list *parents;
+       struct tree *tree;
+       struct tree_entry_list *entry;
+
+       if (sha1 && !memcmp(sha1, obj->sha1, 20))
+               return;
+
+       if (aborted)
+               return;
+
+       if (obj->type == commit_type) {
+               if (push_verbosely)
+                       fprintf(stderr, "walk %s\n", sha1_to_hex(obj->sha1));
+               add_request(obj->sha1, lock);
+               commit = (struct commit *)obj;
+               if (parse_commit(commit)) {
+                       fprintf(stderr, "Error parsing commit %s\n",
+                               sha1_to_hex(obj->sha1));
+                       aborted = 1;
+                       return;
+               }
+               parents = commit->parents;
+               for (; parents; parents = parents->next)
+                       if (sha1 == NULL ||
+                           memcmp(sha1, parents->item->object.sha1, 20))
+                               get_delta(sha1, &parents->item->object,
+                                         lock);
+               get_delta(sha1, &commit->tree->object, lock);
+       } else if (obj->type == tree_type) {
+               if (push_verbosely)
+                       fprintf(stderr, "walk %s\n", sha1_to_hex(obj->sha1));
+               add_request(obj->sha1, lock);
+               tree = (struct tree *)obj;
+               if (parse_tree(tree)) {
+                       fprintf(stderr, "Error parsing tree %s\n",
+                               sha1_to_hex(obj->sha1));
+                       aborted = 1;
+                       return;
+               }
+               entry = tree->entries;
+               tree->entries = NULL;
+               while (entry) {
+                       struct tree_entry_list *next = entry->next;
+                       get_delta(sha1, entry->item.any, lock);
+                       free(entry->name);
+                       free(entry);
+                       entry = next;
+               }
+       } else if (obj->type == blob_type || obj->type == tag_type) {
+               add_request(obj->sha1, lock);
+       }
+}
+
+int update_remote(unsigned char *sha1, struct active_lock *lock)
+{
+       struct active_request_slot *slot;
+       char *out_data;
+       char *if_header;
+       struct buffer out_buffer;
+       struct curl_slist *dav_headers = NULL;
+       int i;
+
+       if_header = xmalloc(strlen(lock->token) + 25);
+       sprintf(if_header, "If: (<opaquelocktoken:%s>)", lock->token);
+       dav_headers = curl_slist_append(dav_headers, if_header);
+
+       out_buffer.size = 41;
+       out_data = xmalloc(out_buffer.size + 1);
+       i = snprintf(out_data, out_buffer.size + 1, "%s\n", sha1_to_hex(sha1));
+       if (i != out_buffer.size) {
+               fprintf(stderr, "Unable to initialize PUT request body\n");
+               return 0;
+       }
+       out_buffer.posn = 0;
+       out_buffer.buffer = out_data;
+
+       slot = get_active_slot();
+       curl_easy_setopt(slot->curl, CURLOPT_INFILE, &out_buffer);
+       curl_easy_setopt(slot->curl, CURLOPT_INFILESIZE, out_buffer.size);
+       curl_easy_setopt(slot->curl, CURLOPT_READFUNCTION, fread_buffer);
+       curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_null);
+       curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, DAV_PUT);
+       curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, dav_headers);
+       curl_easy_setopt(slot->curl, CURLOPT_UPLOAD, 1);
+       curl_easy_setopt(slot->curl, CURLOPT_PUT, 1);
+       curl_easy_setopt(slot->curl, CURLOPT_URL, lock->url);
+
+       if (start_active_slot(slot)) {
+               run_active_slot(slot);
+               free(out_data);
+               free(if_header);
+               if (slot->curl_result != CURLE_OK) {
+                       fprintf(stderr,
+                               "PUT error: curl result=%d, HTTP code=%ld\n",
+                               slot->curl_result, slot->http_code);
+                       /* We should attempt recovery? */
+                       return 0;
+               }
+       } else {
+               free(out_data);
+               free(if_header);
+               fprintf(stderr, "Unable to start PUT request\n");
+               return 0;
+       }
+
+       return 1;
+}
+
+int main(int argc, char **argv)
+{
+       struct active_request_slot *slot;
+       struct active_request_slot *next_slot;
+       struct transfer_request *request;
+       struct transfer_request *next_request;
+       int nr_refspec = 0;
+       char **refspec = NULL;
+       int do_remote_update;
+       int new_branch;
+       int force_this;
+       char *local_ref;
+       unsigned char local_sha1[20];
+       struct object *local_object = NULL;
+       char *remote_ref = NULL;
+       unsigned char remote_sha1[20];
+       struct active_lock *remote_lock;
+       char *remote_path = NULL;
+       char *low_speed_limit;
+       char *low_speed_time;
+       int rc = 0;
+       int i;
+
+       setup_ident();
+
+       remote = xmalloc(sizeof(*remote));
+       remote->url = NULL;
+       remote->packs = NULL;
+
+       argv++;
+       for (i = 1; i < argc; i++, argv++) {
+               char *arg = *argv;
+
+               if (*arg == '-') {
+                       if (!strcmp(arg, "--complete")) {
+                               push_all = 1;
+                               continue;
+                       }
+                       if (!strcmp(arg, "--force")) {
+                               force_all = 1;
+                               continue;
+                       }
+                       if (!strcmp(arg, "--verbose")) {
+                               push_verbosely = 1;
+                               continue;
+                       }
+                       usage(http_push_usage);
+               }
+               if (!remote->url) {
+                       remote->url = arg;
+                       continue;
+               }
+               refspec = argv;
+               nr_refspec = argc - i;
+               break;
+       }
+
+       curl_global_init(CURL_GLOBAL_ALL);
+
+#ifdef USE_CURL_MULTI
+       {
+               char *http_max_requests = getenv("GIT_HTTP_MAX_REQUESTS");
+               if (http_max_requests != NULL)
+                       max_requests = atoi(http_max_requests);
+       }
+
+       curlm = curl_multi_init();
+       if (curlm == NULL) {
+               fprintf(stderr, "Error creating curl multi handle.\n");
+               return 1;
+       }
+#endif
+
+       if (getenv("GIT_SSL_NO_VERIFY"))
+               curl_ssl_verify = 0;
+
+       ssl_cert = getenv("GIT_SSL_CERT");
+#if LIBCURL_VERSION_NUM >= 0x070902
+       ssl_key = getenv("GIT_SSL_KEY");
+#endif
+#if LIBCURL_VERSION_NUM >= 0x070908
+       ssl_capath = getenv("GIT_SSL_CAPATH");
+#endif
+       ssl_cainfo = getenv("GIT_SSL_CAINFO");
+
+       low_speed_limit = getenv("GIT_HTTP_LOW_SPEED_LIMIT");
+       if (low_speed_limit != NULL)
+               curl_low_speed_limit = strtol(low_speed_limit, NULL, 10);
+       low_speed_time = getenv("GIT_HTTP_LOW_SPEED_TIME");
+       if (low_speed_time != NULL)
+               curl_low_speed_time = strtol(low_speed_time, NULL, 10);
+
+       git_config(http_options);
+
+       if (curl_ssl_verify == -1)
+               curl_ssl_verify = 1;
+
+#ifdef USE_CURL_MULTI
+       if (max_requests < 1)
+               max_requests = DEFAULT_MAX_REQUESTS;
+#endif
+
+       no_pragma_header = curl_slist_append(no_pragma_header, "Pragma:");
+       default_headers = curl_slist_append(default_headers, "Range:");
+       default_headers = curl_slist_append(default_headers, "Destination:");
+       default_headers = curl_slist_append(default_headers, "If:");
+       default_headers = curl_slist_append(default_headers,
+                                           "Pragma: no-cache");
+
+#ifndef NO_CURL_EASY_DUPHANDLE
+       curl_default = get_curl_handle();
+#endif
+
+       /* Verify DAV compliance/lock support */
+       if (check_locking() != 0) {
+               fprintf(stderr, "Error: no DAV locking support on remote repo %s\n", remote->url);
+               rc = 1;
+               goto cleanup;
+       }
+
+       /* Process each refspec */
+       for (i = 0; i < nr_refspec; i++) {
+               char *ep;
+               force_this = 0;
+               do_remote_update = 0;
+               new_branch = 0;
+               local_ref = refspec[i];
+               if (*local_ref == '+') {
+                       force_this = 1;
+                       local_ref++;
+               }
+               ep = strchr(local_ref, ':');
+               if (ep) {
+                       remote_ref = ep + 1;
+                       *ep = 0;
+               }
+               else
+                       remote_ref = local_ref;
+
+               /* Lock remote branch ref */
+               if (remote_path)
+                       free(remote_path);
+               remote_path = xmalloc(strlen(remote_ref) + 12);
+               sprintf(remote_path, "refs/heads/%s", remote_ref);
+               remote_lock = lock_remote(remote_path, LOCK_TIME);
+               if (remote_lock == NULL) {
+                       fprintf(stderr, "Unable to lock remote branch %s\n",
+                               remote_ref);
+                       rc = 1;
+                       continue;
+               }
+
+               /* Resolve local and remote refs */
+               if (fetch_ref(remote_ref, remote_sha1) != 0) {
+                       fprintf(stderr,
+                               "Remote branch %s does not exist on %s\n",
+                               remote_ref, remote->url);
+                       new_branch = 1;
+               }
+               if (get_sha1(local_ref, local_sha1) != 0) {
+                       fprintf(stderr, "Error resolving local branch %s\n",
+                               local_ref);
+                       rc = 1;
+                       goto unlock;
+               }
+       
+               /* Find relationship between local and remote */
+               local_object = parse_object(local_sha1);
+               if (!local_object) {
+                       fprintf(stderr, "Unable to parse local object %s\n",
+                               sha1_to_hex(local_sha1));
+                       rc = 1;
+                       goto unlock;
+               } else if (new_branch) {
+                       do_remote_update = 1;
+               } else {
+                       if (!memcmp(local_sha1, remote_sha1, 20)) {
+                               fprintf(stderr,
+                                       "* %s: same as branch '%s' of %s\n",
+                                       local_ref, remote_ref, remote->url);
+                       } else if (is_ancestor(remote_sha1,
+                                              (struct commit *)local_object)) {
+                               fprintf(stderr,
+                                       "Remote %s will fast-forward to local %s\n",
+                                       remote_ref, local_ref);
+                               do_remote_update = 1;
+                       } else if (force_all || force_this) {
+                               fprintf(stderr,
+                                       "* %s on %s does not fast forward to local branch '%s', overwriting\n",
+                                       remote_ref, remote->url, local_ref);
+                               do_remote_update = 1;
+                       } else {
+                               fprintf(stderr,
+                                       "* %s on %s does not fast forward to local branch '%s'\n",
+                                       remote_ref, remote->url, local_ref);
+                               rc = 1;
+                               goto unlock;
+                       }
+               }
+
+               /* Generate and check list of required objects */
+               pushing = 0;
+               if (do_remote_update || push_all)
+                       fetch_indices();
+               get_delta(push_all ? NULL : remote_sha1,
+                         local_object, remote_lock);
+               process_waiting_requests();
+
+               /* Push missing objects to remote, this would be a
+                  convenient time to pack them first if appropriate. */
+               pushing = 1;
+               process_request_queue();
+               process_waiting_requests();
+
+               /* Update the remote branch if all went well */
+               if (do_remote_update) {
+                       if (!aborted && update_remote(local_sha1,
+                                                     remote_lock)) {
+                               fprintf(stderr, "%s remote branch %s\n",
+                                       new_branch ? "Created" : "Updated",
+                                       remote_ref);
+                       } else {
+                               fprintf(stderr,
+                                       "Unable to %s remote branch %s\n",
+                                       new_branch ? "create" : "update",
+                                       remote_ref);
+                               rc = 1;
+                               goto unlock;
+                       }
+               }
+
+       unlock:
+               unlock_remote(remote_lock);
+               free(remote_path);
+       }
+
+ cleanup:
+       free(remote);
+
+       curl_slist_free_all(no_pragma_header);
+       curl_slist_free_all(default_headers);
+
+       slot = active_queue_head;
+       while (slot != NULL) {
+               next_slot = slot->next;
+               if (slot->curl != NULL)
+                       curl_easy_cleanup(slot->curl);
+               free(slot);
+               slot = next_slot;
+       }
+
+       request = request_queue_head;
+       while (request != NULL) {
+               next_request = request->next;
+               release_request(request);
+               request = next_request;
+       }
+
+#ifndef NO_CURL_EASY_DUPHANDLE
+       curl_easy_cleanup(curl_default);
+#endif
+#ifdef USE_CURL_MULTI
+       curl_multi_cleanup(curlm);
+#endif
+       curl_global_cleanup();
+       return rc;
+}
diff --git a/ident.c b/ident.c
index a2d241fba025f82fa4c77aeb345103db66168fa9..bc89e1d04c63563c051005754a50247f22256974 100644 (file)
--- a/ident.c
+++ b/ident.c
@@ -8,12 +8,43 @@
 #include "cache.h"
 
 #include <pwd.h>
-#include <time.h>
-#include <ctype.h>
+#include <netdb.h>
 
-static char real_email[1000];
-static char real_name[1000];
-static char real_date[50];
+static char git_default_date[50];
+
+static void copy_gecos(struct passwd *w, char *name, int sz)
+{
+       char *src, *dst;
+       int len, nlen;
+
+       nlen = strlen(w->pw_name);
+
+       /* Traditionally GECOS field had office phone numbers etc, separated
+        * with commas.  Also & stands for capitalized form of the login name.
+        */
+
+       for (len = 0, dst = name, src = w->pw_gecos; len < sz; src++) {
+               int ch = *src;
+               if (ch != '&') {
+                       *dst++ = ch;
+                       if (ch == 0 || ch == ',')
+                               break;
+                       len++;
+                       continue;
+               }
+               if (len + nlen < sz) {
+                       /* Sorry, Mr. McDonald... */
+                       *dst++ = toupper(*w->pw_name);
+                       memcpy(dst, w->pw_name + 1, nlen - 1);
+                       dst += nlen - 1;
+               }
+       }
+       if (len < sz)
+               name[len] = 0;
+       else
+               die("Your parents must have hated you!");
+
+}
 
 int setup_ident(void)
 {
@@ -24,25 +55,29 @@ int setup_ident(void)
                die("You don't exist. Go away!");
 
        /* Get the name ("gecos") */
-       len = strlen(pw->pw_gecos);
-       if (len >= sizeof(real_name))
-               die("Your parents must have hated you!");
-       memcpy(real_name, pw->pw_gecos, len+1);
+       copy_gecos(pw, git_default_name, sizeof(git_default_name));
 
        /* Make up a fake email address (name + '@' + hostname [+ '.' + domainname]) */
        len = strlen(pw->pw_name);
-       if (len > sizeof(real_email)/2)
+       if (len > sizeof(git_default_email)/2)
                die("Your sysadmin must hate you!");
-       memcpy(real_email, pw->pw_name, len);
-       real_email[len++] = '@';
-       gethostname(real_email + len, sizeof(real_email) - len);
-       if (!strchr(real_email+len, '.')) {
-               len = strlen(real_email);
-               real_email[len++] = '.';
-               getdomainname(real_email+len, sizeof(real_email)-len);
+       memcpy(git_default_email, pw->pw_name, len);
+       git_default_email[len++] = '@';
+       gethostname(git_default_email + len, sizeof(git_default_email) - len);
+       if (!strchr(git_default_email+len, '.')) {
+               struct hostent *he = gethostbyname(git_default_email + len);
+               char *domainname;
+
+               len = strlen(git_default_email);
+               git_default_email[len++] = '.';
+               if (he && (domainname = strchr(he->h_name, '.')))
+                       strncpy(git_default_email + len, domainname + 1, sizeof(git_default_email) - len);
+               else
+                       strncpy(git_default_email + len, "(none)", sizeof(git_default_email) - len);
+               git_default_email[sizeof(git_default_email) - 1] = 0;
        }
        /* And set the default date */
-       datestamp(real_date, sizeof(real_date));
+       datestamp(git_default_date, sizeof(git_default_date));
        return 0;
 }
 
@@ -128,10 +163,10 @@ char *get_ident(const char *name, const char *email, const char *date_str)
        int i;
 
        if (!name)
-               name = real_name;
+               name = git_default_name;
        if (!email)
-               email = real_email;
-       strcpy(date, real_date);
+               email = git_default_email;
+       strcpy(date, git_default_date);
        if (date_str)
                parse_date(date_str, date, sizeof(date));
 
diff --git a/index-pack.c b/index-pack.c
new file mode 100644 (file)
index 0000000..785fe71
--- /dev/null
@@ -0,0 +1,462 @@
+#include "cache.h"
+#include "delta.h"
+#include "pack.h"
+#include "csum-file.h"
+
+static const char index_pack_usage[] =
+"git-index-pack [-o index-file] pack-file";
+
+struct object_entry
+{
+       unsigned long offset;
+       enum object_type type;
+       enum object_type real_type;
+       unsigned char sha1[20];
+};
+
+struct delta_entry
+{
+       struct object_entry *obj;
+       unsigned char base_sha1[20];
+};
+
+static const char *pack_name;
+static unsigned char *pack_base;
+static unsigned long pack_size;
+static struct object_entry *objects;
+static struct delta_entry *deltas;
+static int nr_objects;
+static int nr_deltas;
+
+static void open_pack_file(void)
+{
+       int fd;
+       struct stat st;
+
+       fd = open(pack_name, O_RDONLY);
+       if (fd < 0)
+               die("cannot open packfile '%s': %s", pack_name,
+                   strerror(errno));
+       if (fstat(fd, &st)) {
+               int err = errno;
+               close(fd);
+               die("cannot fstat packfile '%s': %s", pack_name,
+                   strerror(err));
+       }
+       pack_size = st.st_size;
+       pack_base = mmap(NULL, pack_size, PROT_READ, MAP_PRIVATE, fd, 0);
+       if (pack_base == MAP_FAILED) {
+               int err = errno;
+               close(fd);
+               die("cannot mmap packfile '%s': %s", pack_name,
+                   strerror(err));
+       }
+       close(fd);
+}
+
+static void parse_pack_header(void)
+{
+       const struct pack_header *hdr;
+       unsigned char sha1[20];
+       SHA_CTX ctx;
+
+       /* Ensure there are enough bytes for the header and final SHA1 */
+       if (pack_size < sizeof(struct pack_header) + 20)
+               die("packfile '%s' is too small", pack_name);
+
+       /* Header consistency check */
+       hdr = (void *)pack_base;
+       if (hdr->hdr_signature != htonl(PACK_SIGNATURE))
+               die("packfile '%s' signature mismatch", pack_name);
+       if (hdr->hdr_version != htonl(PACK_VERSION))
+               die("packfile '%s' version %d different from ours %d",
+                   pack_name, ntohl(hdr->hdr_version), PACK_VERSION);
+
+       nr_objects = ntohl(hdr->hdr_entries);
+
+       /* Check packfile integrity */
+       SHA1_Init(&ctx);
+       SHA1_Update(&ctx, pack_base, pack_size - 20);
+       SHA1_Final(sha1, &ctx);
+       if (memcmp(sha1, pack_base + pack_size - 20, 20))
+               die("packfile '%s' SHA1 mismatch", pack_name);
+}
+
+static void bad_object(unsigned long offset, const char *format,
+                      ...) NORETURN __attribute__((format (printf, 2, 3)));
+
+static void bad_object(unsigned long offset, const char *format, ...)
+{
+       va_list params;
+       char buf[1024];
+
+       va_start(params, format);
+       vsnprintf(buf, sizeof(buf), format, params);
+       va_end(params);
+       die("packfile '%s': bad object at offset %lu: %s",
+           pack_name, offset, buf);
+}
+
+static void *unpack_entry_data(unsigned long offset,
+                              unsigned long *current_pos, unsigned long size)
+{
+       unsigned long pack_limit = pack_size - 20;
+       unsigned long pos = *current_pos;
+       z_stream stream;
+       void *buf = xmalloc(size);
+
+       memset(&stream, 0, sizeof(stream));
+       stream.next_out = buf;
+       stream.avail_out = size;
+       stream.next_in = pack_base + pos;
+       stream.avail_in = pack_limit - pos;
+       inflateInit(&stream);
+
+       for (;;) {
+               int ret = inflate(&stream, 0);
+               if (ret == Z_STREAM_END)
+                       break;
+               if (ret != Z_OK)
+                       bad_object(offset, "inflate returned %d", ret);
+       }
+       inflateEnd(&stream);
+       if (stream.total_out != size)
+               bad_object(offset, "size mismatch (expected %lu, got %lu)",
+                          size, stream.total_out);
+       *current_pos = pack_limit - stream.avail_in;
+       return buf;
+}
+
+static void *unpack_raw_entry(unsigned long offset,
+                             enum object_type *obj_type,
+                             unsigned long *obj_size,
+                             unsigned char *delta_base,
+                             unsigned long *next_obj_offset)
+{
+       unsigned long pack_limit = pack_size - 20;
+       unsigned long pos = offset;
+       unsigned char c;
+       unsigned long size;
+       unsigned shift;
+       enum object_type type;
+       void *data;
+
+       c = pack_base[pos++];
+       type = (c >> 4) & 7;
+       size = (c & 15);
+       shift = 4;
+       while (c & 0x80) {
+               if (pos >= pack_limit)
+                       bad_object(offset, "object extends past end of pack");
+               c = pack_base[pos++];
+               size += (c & 0x7fUL) << shift;
+               shift += 7;
+       }
+
+       switch (type) {
+       case OBJ_DELTA:
+               if (pos + 20 >= pack_limit)
+                       bad_object(offset, "object extends past end of pack");
+               memcpy(delta_base, pack_base + pos, 20);
+               pos += 20;
+               /* fallthru */
+       case OBJ_COMMIT:
+       case OBJ_TREE:
+       case OBJ_BLOB:
+       case OBJ_TAG:
+               data = unpack_entry_data(offset, &pos, size);
+               break;
+       default:
+               bad_object(offset, "bad object type %d", type);
+       }
+
+       *obj_type = type;
+       *obj_size = size;
+       *next_obj_offset = pos;
+       return data;
+}
+
+static int find_delta(const unsigned char *base_sha1)
+{
+       int first = 0, last = nr_deltas;
+
+        while (first < last) {
+                int next = (first + last) / 2;
+                struct delta_entry *delta = &deltas[next];
+                int cmp;
+
+                cmp = memcmp(base_sha1, delta->base_sha1, 20);
+                if (!cmp)
+                        return next;
+                if (cmp < 0) {
+                        last = next;
+                        continue;
+                }
+                first = next+1;
+        }
+        return -first-1;
+}
+
+static int find_deltas_based_on_sha1(const unsigned char *base_sha1,
+                                    int *first_index, int *last_index)
+{
+       int first = find_delta(base_sha1);
+       int last = first;
+       int end = nr_deltas - 1;
+
+       if (first < 0)
+               return -1;
+       while (first > 0 && !memcmp(deltas[first-1].base_sha1, base_sha1, 20))
+               --first;
+       while (last < end && !memcmp(deltas[last+1].base_sha1, base_sha1, 20))
+               ++last;
+       *first_index = first;
+       *last_index = last;
+       return 0;
+}
+
+static void sha1_object(const void *data, unsigned long size,
+                       enum object_type type, unsigned char *sha1)
+{
+       SHA_CTX ctx;
+       char header[50];
+       int header_size;
+       const char *type_str;
+
+       switch (type) {
+       case OBJ_COMMIT: type_str = "commit"; break;
+       case OBJ_TREE:   type_str = "tree"; break;
+       case OBJ_BLOB:   type_str = "blob"; break;
+       case OBJ_TAG:    type_str = "tag"; break;
+       default:
+               die("bad type %d", type);
+       }
+
+       header_size = sprintf(header, "%s %lu", type_str, size) + 1;
+
+       SHA1_Init(&ctx);
+       SHA1_Update(&ctx, header, header_size);
+       SHA1_Update(&ctx, data, size);
+       SHA1_Final(sha1, &ctx);
+}
+
+static void resolve_delta(struct delta_entry *delta, void *base_data,
+                         unsigned long base_size, enum object_type type)
+{
+       struct object_entry *obj = delta->obj;
+       void *delta_data;
+       unsigned long delta_size;
+       void *result;
+       unsigned long result_size;
+       enum object_type delta_type;
+       unsigned char base_sha1[20];
+       unsigned long next_obj_offset;
+       int j, first, last;
+
+       obj->real_type = type;
+       delta_data = unpack_raw_entry(obj->offset, &delta_type,
+                                     &delta_size, base_sha1,
+                                     &next_obj_offset);
+       result = patch_delta(base_data, base_size, delta_data, delta_size,
+                            &result_size);
+       free(delta_data);
+       if (!result)
+               bad_object(obj->offset, "failed to apply delta");
+       sha1_object(result, result_size, type, obj->sha1);
+       if (!find_deltas_based_on_sha1(obj->sha1, &first, &last)) {
+               for (j = first; j <= last; j++)
+                       resolve_delta(&deltas[j], result, result_size, type);
+       }
+       free(result);
+}
+
+static int compare_delta_entry(const void *a, const void *b)
+{
+       const struct delta_entry *delta_a = a;
+       const struct delta_entry *delta_b = b;
+       return memcmp(delta_a->base_sha1, delta_b->base_sha1, 20);
+}
+
+static void parse_pack_objects(void)
+{
+       int i;
+       unsigned long offset = sizeof(struct pack_header);
+       unsigned char base_sha1[20];
+       void *data;
+       unsigned long data_size;
+
+       /*
+        * First pass:
+        * - find locations of all objects;
+        * - calculate SHA1 of all non-delta objects;
+        * - remember base SHA1 for all deltas.
+        */
+       for (i = 0; i < nr_objects; i++) {
+               struct object_entry *obj = &objects[i];
+               obj->offset = offset;
+               data = unpack_raw_entry(offset, &obj->type, &data_size,
+                                       base_sha1, &offset);
+               obj->real_type = obj->type;
+               if (obj->type == OBJ_DELTA) {
+                       struct delta_entry *delta = &deltas[nr_deltas++];
+                       delta->obj = obj;
+                       memcpy(delta->base_sha1, base_sha1, 20);
+               } else
+                       sha1_object(data, data_size, obj->type, obj->sha1);
+               free(data);
+       }
+       if (offset != pack_size - 20)
+               die("packfile '%s' has junk at the end", pack_name);
+
+       /* Sort deltas by base SHA1 for fast searching */
+       qsort(deltas, nr_deltas, sizeof(struct delta_entry),
+             compare_delta_entry);
+
+       /*
+        * Second pass:
+        * - for all non-delta objects, look if it is used as a base for
+        *   deltas;
+        * - if used as a base, uncompress the object and apply all deltas,
+        *   recursively checking if the resulting object is used as a base
+        *   for some more deltas.
+        */
+       for (i = 0; i < nr_objects; i++) {
+               struct object_entry *obj = &objects[i];
+               int j, first, last;
+
+               if (obj->type == OBJ_DELTA)
+                       continue;
+               if (find_deltas_based_on_sha1(obj->sha1, &first, &last))
+                       continue;
+               data = unpack_raw_entry(obj->offset, &obj->type, &data_size,
+                                       base_sha1, &offset);
+               for (j = first; j <= last; j++)
+                       resolve_delta(&deltas[j], data, data_size, obj->type);
+               free(data);
+       }
+
+       /* Check for unresolved deltas */
+       for (i = 0; i < nr_deltas; i++) {
+               if (deltas[i].obj->real_type == OBJ_DELTA)
+                       die("packfile '%s' has unresolved deltas",  pack_name);
+       }
+}
+
+static int sha1_compare(const void *_a, const void *_b)
+{
+       struct object_entry *a = *(struct object_entry **)_a;
+       struct object_entry *b = *(struct object_entry **)_b;
+       return memcmp(a->sha1, b->sha1, 20);
+}
+
+static void write_index_file(const char *index_name, unsigned char *sha1)
+{
+       struct sha1file *f;
+       struct object_entry **sorted_by_sha =
+               xcalloc(nr_objects, sizeof(struct object_entry *));
+       struct object_entry **list = sorted_by_sha;
+       struct object_entry **last = sorted_by_sha + nr_objects;
+       unsigned int array[256];
+       int i;
+       SHA_CTX ctx;
+
+       for (i = 0; i < nr_objects; ++i)
+               sorted_by_sha[i] = &objects[i];
+       qsort(sorted_by_sha, nr_objects, sizeof(sorted_by_sha[0]),
+             sha1_compare);
+
+       unlink(index_name);
+       f = sha1create("%s", index_name);
+
+       /*
+        * Write the first-level table (the list is sorted,
+        * but we use a 256-entry lookup to be able to avoid
+        * having to do eight extra binary search iterations).
+        */
+       for (i = 0; i < 256; i++) {
+               struct object_entry **next = list;
+               while (next < last) {
+                       struct object_entry *obj = *next;
+                       if (obj->sha1[0] != i)
+                               break;
+                       next++;
+               }
+               array[i] = htonl(next - sorted_by_sha);
+               list = next;
+       }
+       sha1write(f, array, 256 * sizeof(int));
+
+       /* recompute the SHA1 hash of sorted object names.
+        * currently pack-objects does not do this, but that
+        * can be fixed.
+        */
+       SHA1_Init(&ctx);
+       /*
+        * Write the actual SHA1 entries..
+        */
+       list = sorted_by_sha;
+       for (i = 0; i < nr_objects; i++) {
+               struct object_entry *obj = *list++;
+               unsigned int offset = htonl(obj->offset);
+               sha1write(f, &offset, 4);
+               sha1write(f, obj->sha1, 20);
+               SHA1_Update(&ctx, obj->sha1, 20);
+       }
+       sha1write(f, pack_base + pack_size - 20, 20);
+       sha1close(f, NULL, 1);
+       free(sorted_by_sha);
+       SHA1_Final(sha1, &ctx);
+}
+
+int main(int argc, char **argv)
+{
+       int i;
+       char *index_name = NULL;
+       char *index_name_buf = NULL;
+       unsigned char sha1[20];
+
+       for (i = 1; i < argc; i++) {
+               const char *arg = argv[i];
+
+               if (*arg == '-') {
+                       if (!strcmp(arg, "-o")) {
+                               if (index_name || (i+1) >= argc)
+                                       usage(index_pack_usage);
+                               index_name = argv[++i];
+                       } else
+                               usage(index_pack_usage);
+                       continue;
+               }
+
+               if (pack_name)
+                       usage(index_pack_usage);
+               pack_name = arg;
+       }
+
+       if (!pack_name)
+               usage(index_pack_usage);
+       if (!index_name) {
+               int len = strlen(pack_name);
+               if (len < 5 || strcmp(pack_name + len - 5, ".pack"))
+                       die("packfile name '%s' does not end with '.pack'",
+                           pack_name);
+               index_name_buf = xmalloc(len - 1);
+               memcpy(index_name_buf, pack_name, len - 5);
+               strcpy(index_name_buf + len - 5, ".idx");
+               index_name = index_name_buf;
+       }
+
+       open_pack_file();
+       parse_pack_header();
+       objects = xcalloc(nr_objects, sizeof(struct object_entry));
+       deltas = xcalloc(nr_objects, sizeof(struct delta_entry));
+       parse_pack_objects();
+       free(deltas);
+       write_index_file(index_name, sha1);
+       free(objects);
+       free(index_name_buf);
+
+       printf("%s\n", sha1_to_hex(sha1));
+
+       return 0;
+}
diff --git a/index.c b/index.c
index 87fc7b0387b818001fb0e53fdb713fe1bad4f822..ad0eafedc288f2cf6b0d7c2493495087451ef466 100644 (file)
--- a/index.c
+++ b/index.c
@@ -22,14 +22,16 @@ static void remove_lock_file_on_signal(int signo)
 
 int hold_index_file_for_update(struct cache_file *cf, const char *path)
 {
+       int fd;
        sprintf(cf->lockfile, "%s.lock", path);
-       cf->next = cache_file_list;
-       cache_file_list = cf;
-       if (!cf->next) {
+       fd = open(cf->lockfile, O_RDWR | O_CREAT | O_EXCL, 0666);
+       if (fd >=0 && !cf->next) {
+               cf->next = cache_file_list;
+               cache_file_list = cf;
                signal(SIGINT, remove_lock_file_on_signal);
                atexit(remove_lock_file);
        }
-       return open(cf->lockfile, O_RDWR | O_CREAT | O_EXCL, 0600);
+       return fd;
 }
 
 int commit_index_file(struct cache_file *cf)
index da2bc8f42b2ea6804c79097778f3105afcd87da6..bd88291b0efd3eb9c46195d413db1aba820db2dd 100644 (file)
--- a/init-db.c
+++ b/init-db.c
@@ -21,7 +21,7 @@ static void safe_create_dir(const char *dir)
 
 static int copy_file(const char *dst, const char *src, int mode)
 {
-       int fdi, fdo;
+       int fdi, fdo, status;
 
        mode = (mode & 0111) ? 0777 : 0666;
        if ((fdi = open(src, O_RDONLY)) < 0)
@@ -30,30 +30,9 @@ static int copy_file(const char *dst, const char *src, int mode)
                close(fdi);
                return fdo;
        }
-       while (1) {
-               char buf[BUFSIZ];
-               ssize_t leni, leno, ofs;
-               leni = read(fdi, buf, sizeof(buf));
-               if (leni < 0) {
-               error_return:
-                       close(fdo);
-                       close(fdi);
-                       return -1;
-               }
-               if (!leni)
-                       break;
-               ofs = 0;
-               do {
-                       leno = write(fdo, buf+ofs, leni);
-                       if (leno < 0)
-                               goto error_return;
-                       leni -= leno;
-                       ofs += leno;
-               } while (0 < leni);
-       }
+       status = copy_fd(fdi, fdo);
        close(fdo);
-       close(fdi);
-       return 0;
+       return status;
 }
 
 static void copy_templates_1(char *path, int baselen,
@@ -166,6 +145,7 @@ static void create_default_files(const char *git_dir,
 {
        unsigned len = strlen(git_dir);
        static char path[PATH_MAX];
+       unsigned char sha1[20];
 
        if (len > sizeof(path)-50)
                die("insane git directory %s", git_dir);
@@ -186,16 +166,52 @@ static void create_default_files(const char *git_dir,
 
        /*
         * Create the default symlink from ".git/HEAD" to the "master"
-        * branch
+        * branch, if it does not exist yet.
         */
        strcpy(path + len, "HEAD");
-       if (symlink("refs/heads/master", path) < 0) {
-               if (errno != EEXIST) {
-                       perror(path);
+       if (read_ref(path, sha1) < 0) {
+               if (create_symref(path, "refs/heads/master") < 0)
                        exit(1);
-               }
        }
+       path[len] = 0;
        copy_templates(path, len, template_path);
+
+       /*
+        * Find out if we can trust the executable bit.
+        */
+       safe_create_dir(path);
+       strcpy(path + len, "config");
+       if (access(path, R_OK) < 0) {
+               static const char contents[] =
+                       "#\n"
+                       "# This is the config file\n"
+                       "#\n"
+                       "\n"
+                       "; core variables\n"
+                       "[core]\n"
+                       "       ; Don't trust file modes\n"
+                       "       filemode = false\n"
+                       "\n";
+               FILE *config = fopen(path, "w");
+               struct stat st;
+
+               if (!config)
+                       die("Can not write to %s?", path);
+
+               fwrite(contents, sizeof(contents)-1, 1, config);
+
+               fclose(config);
+
+               if (!lstat(path, &st)) {
+                       struct stat st2;
+                       if (!chmod(path, st.st_mode ^ S_IXUSR) &&
+                                       !lstat(path, &st2) &&
+                                       st.st_mode != st2.st_mode)
+                               unlink(path);
+                       else
+                               fprintf(stderr, "Ignoring file modes\n");
+               }
+       }
 }
 
 static const char init_db_usage[] =
@@ -244,10 +260,6 @@ int main(int argc, char **argv)
        memcpy(path, sha1_dir, len);
 
        safe_create_dir(sha1_dir);
-       for (i = 0; i < 256; i++) {
-               sprintf(path+len, "/%02x", i);
-               safe_create_dir(path);
-       }
        strcpy(path+len, "/pack");
        safe_create_dir(path);
        strcpy(path+len, "/info");
index 0dbed8910bbc3aa570c512eff893a4c603fdb5f7..0931109143e0ce1c2f03f654b3ab67965220011a 100644 (file)
@@ -52,9 +52,10 @@ static int setup_indices(void)
        return 0;
 }
 
-static int copy_file(const char *source, const char *dest, const char *hex,
+static int copy_file(const char *source, char *dest, const char *hex,
                     int warn_if_not_exists)
 {
+       safe_create_leading_directories(dest);
        if (use_link) {
                if (!link(source, dest)) {
                        pull_say("link %s\n", hex);
@@ -82,31 +83,23 @@ static int copy_file(const char *source, const char *dest, const char *hex,
                }
        }
        if (use_filecopy) {
-               int ifd, ofd, status;
-               struct stat st;
-               void *map;
+               int ifd, ofd, status = 0;
+
                ifd = open(source, O_RDONLY);
-               if (ifd < 0 || fstat(ifd, &st) < 0) {
-                       int err = errno;
-                       if (ifd >= 0)
-                               close(ifd);
-                       if (!warn_if_not_exists && err == ENOENT)
+               if (ifd < 0) {
+                       if (!warn_if_not_exists && errno == ENOENT)
                                return -1;
                        fprintf(stderr, "cannot open %s\n", source);
                        return -1;
                }
-               map = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, ifd, 0);
-               close(ifd);
-               if (map == MAP_FAILED) {
-                       fprintf(stderr, "cannot mmap %s\n", source);
+               ofd = open(dest, O_WRONLY | O_CREAT | O_EXCL, 0666);
+               if (ofd < 0) {
+                       fprintf(stderr, "cannot open %s\n", dest);
+                       close(ifd);
                        return -1;
                }
-               ofd = open(dest, O_WRONLY | O_CREAT | O_EXCL, 0666);
-               status = ((ofd < 0) ||
-                         (write(ofd, map, st.st_size) != st.st_size));
-               munmap(map, st.st_size);
-               if (ofd >= 0)
-                       close(ofd);
+               status = copy_fd(ifd, ofd);
+               close(ofd);
                if (status)
                        fprintf(stderr, "cannot write %s\n", dest);
                else
@@ -150,7 +143,7 @@ static int fetch_file(const unsigned char *sha1)
        static int object_name_start = -1;
        static char filename[PATH_MAX];
        char *hex = sha1_to_hex(sha1);
-       const char *dest_filename = sha1_file_name(sha1);
+       char *dest_filename = sha1_file_name(sha1);
 
        if (object_name_start < 0) {
                strcpy(filename, path); /* e.g. git.git */
@@ -166,7 +159,10 @@ static int fetch_file(const unsigned char *sha1)
 
 int fetch(unsigned char *sha1)
 {
-       return fetch_file(sha1) && fetch_pack(sha1);
+       if (has_sha1_file(sha1))
+               return 0;
+       else
+               return fetch_file(sha1) && fetch_pack(sha1);
 }
 
 int fetch_ref(char *ref, unsigned char *sha1)
@@ -231,6 +227,8 @@ int main(int argc, char **argv)
                        get_verbosely = 1;
                else if (argv[arg][1] == 'w')
                        write_ref = argv[++arg];
+               else if (!strcmp(argv[arg], "--recover"))
+                       get_recover = 1;
                else
                        usage(local_pull_usage);
                arg++;
index e53d245884ab14f85824fd6c1b891481c8b1f849..f7653e7d32aa6fa72d9c0e3fb1ad618b9536538c 100644 (file)
@@ -9,6 +9,7 @@
 #include <fnmatch.h>
 
 #include "cache.h"
+#include "quote.h"
 
 static int show_deleted = 0;
 static int show_cached = 0;
@@ -16,6 +17,7 @@ static int show_others = 0;
 static int show_ignored = 0;
 static int show_stage = 0;
 static int show_unmerged = 0;
+static int show_modified = 0;
 static int show_killed = 0;
 static int line_terminator = '\n';
 
@@ -28,8 +30,9 @@ static const char *tag_unmerged = "";
 static const char *tag_removed = "";
 static const char *tag_other = "";
 static const char *tag_killed = "";
+static const char *tag_modified = "";
 
-static char *exclude_per_dir = NULL;
+static const char *exclude_per_dir = NULL;
 
 /* We maintain three exclude pattern lists:
  * EXC_CMDL lists patterns explicitly given on the command line.
@@ -94,7 +97,7 @@ static int add_excludes_from_file_1(const char *fname,
        for (i = 0; i < size; i++) {
                if (buf[i] == '\n') {
                        if (entry != buf + i && entry[0] != '#') {
-                               buf[i] = 0;
+                               buf[i - (i && buf[i-1] == '\r')] = 0;
                                add_exclude(entry, base, baselen, which);
                        }
                        entry = buf + i + 1;
@@ -340,7 +343,32 @@ static void show_dir_entry(const char *tag, struct nond_on_fs *ent)
        if (pathspec && !match(pathspec, ent->name, len))
                return;
 
-       printf("%s%s%c", tag, ent->name + offset, line_terminator);
+       fputs(tag, stdout);
+       write_name_quoted("", ent->name + offset, line_terminator, stdout);
+       putchar(line_terminator);
+}
+
+static void show_other_files(void)
+{
+       int i;
+       for (i = 0; i < nr_dir; i++) {
+               /* We should not have a matching entry, but we
+                * may have an unmerged entry for this path.
+                */
+               struct nond_on_fs *ent = dir[i];
+               int pos = cache_name_pos(ent->name, ent->len);
+               struct cache_entry *ce;
+               if (0 <= pos)
+                       die("bug in show-other-files");
+               pos = -pos - 1;
+               if (pos < active_nr) { 
+                       ce = active_cache[pos];
+                       if (ce_namelen(ce) == ent->len &&
+                           !memcmp(ce->name, ent->name, ent->len))
+                               continue; /* Yup, this one exists unmerged */
+               }
+               show_dir_entry(tag_other, ent);
+       }
 }
 
 static void show_killed_files(void)
@@ -403,15 +431,20 @@ static void show_ce_entry(const char *tag, struct cache_entry *ce)
        if (pathspec && !match(pathspec, ce->name, len))
                return;
 
-       if (!show_stage)
-               printf("%s%s%c", tag, ce->name + offset, line_terminator);
-       else
-               printf("%s%06o %s %d\t%s%c",
+       if (!show_stage) {
+               fputs(tag, stdout);
+               write_name_quoted("", ce->name + offset, line_terminator, stdout);
+               putchar(line_terminator);
+       }
+       else {
+               printf("%s%06o %s %d\t",
                       tag,
                       ntohl(ce->ce_mode),
                       sha1_to_hex(ce->sha1),
-                      ce_stage(ce),
-                      ce->name + offset, line_terminator); 
+                      ce_stage(ce));
+               write_name_quoted("", ce->name + offset, line_terminator, stdout);
+               putchar(line_terminator);
+       }
 }
 
 static void show_files(void)
@@ -428,8 +461,7 @@ static void show_files(void)
                read_directory(path, base, baselen);
                qsort(dir, nr_dir, sizeof(struct nond_on_fs *), cmp_name);
                if (show_others)
-                       for (i = 0; i < nr_dir; i++)
-                               show_dir_entry(tag_other, dir[i]);
+                       show_other_files();
                if (show_killed)
                        show_killed_files();
        }
@@ -443,15 +475,18 @@ static void show_files(void)
                        show_ce_entry(ce_stage(ce) ? tag_unmerged : tag_cached, ce);
                }
        }
-       if (show_deleted) {
+       if (show_deleted | show_modified) {
                for (i = 0; i < active_nr; i++) {
                        struct cache_entry *ce = active_cache[i];
                        struct stat st;
+                       int err;
                        if (excluded(ce->name) != show_ignored)
                                continue;
-                       if (!lstat(ce->name, &st))
-                               continue;
-                       show_ce_entry(tag_removed, ce);
+                       err = lstat(ce->name, &st);
+                       if (show_deleted && err)
+                               show_ce_entry(tag_removed, ce);
+                       if (show_modified && ce_modified(ce, &st))
+                               show_ce_entry(tag_modified, ce);
                }
        }
 }
@@ -523,11 +558,11 @@ static void verify_pathspec(void)
 }
 
 static const char ls_files_usage[] =
-       "git-ls-files [-z] [-t] (--[cached|deleted|others|stage|unmerged|killed])* "
+       "git-ls-files [-z] [-t] (--[cached|deleted|others|stage|unmerged|killed|modified])* "
        "[ --ignored ] [--exclude=<pattern>] [--exclude-from=<file>] "
-       "[ --exclude-per-directory=<filename> ]";
+       "[ --exclude-per-directory=<filename> ] [--] [<file>]*";
 
-int main(int argc, char **argv)
+int main(int argc, const char **argv)
 {
        int i;
        int exc_given = 0;
@@ -537,8 +572,12 @@ int main(int argc, char **argv)
                prefix_offset = strlen(prefix);
 
        for (i = 1; i < argc; i++) {
-               char *arg = argv[i];
+               const char *arg = argv[i];
 
+               if (!strcmp(arg, "--")) {
+                       i++;
+                       break;
+               }
                if (!strcmp(arg, "-z")) {
                        line_terminator = 0;
                        continue;
@@ -547,6 +586,7 @@ int main(int argc, char **argv)
                        tag_cached = "H ";
                        tag_unmerged = "M ";
                        tag_removed = "R ";
+                       tag_modified = "C ";
                        tag_other = "? ";
                        tag_killed = "K ";
                        continue;
@@ -559,6 +599,10 @@ int main(int argc, char **argv)
                        show_deleted = 1;
                        continue;
                }
+               if (!strcmp(arg, "-m") || !strcmp(arg, "--modified")) {
+                       show_modified = 1;
+                       continue;
+               }
                if (!strcmp(arg, "-o") || !strcmp(arg, "--others")) {
                        show_others = 1;
                        continue;
@@ -630,7 +674,8 @@ int main(int argc, char **argv)
        }
 
        /* With no flags, we default to showing the cached files */
-       if (!(show_stage | show_deleted | show_others | show_unmerged | show_killed))
+       if (!(show_stage | show_deleted | show_others | show_unmerged |
+             show_killed | show_modified))
                show_cached = 1;
 
        read_cache();
index e198a20cb3dd88ae46da1aaf005846fe42843ea7..d9f15e349cb833401eea38d21fb050b10f9678d4 100644 (file)
--- a/ls-tree.c
+++ b/ls-tree.c
@@ -6,6 +6,7 @@
 #include "cache.h"
 #include "blob.h"
 #include "tree.h"
+#include "quote.h"
 
 static int line_termination = '\n';
 #define LS_RECURSIVE 1
@@ -54,11 +55,13 @@ static int prepare_children(struct tree_entry_list *elem)
        return 0;
 }
 
-static struct tree_entry_list *find_entry(const char *path)
+static struct tree_entry_list *find_entry(const char *path, char *pathbuf)
 {
        const char *next, *slash;
        int len;
-       struct tree_entry_list *elem = &root_entry;
+       struct tree_entry_list *elem = &root_entry, *oldelem = NULL;
+
+       *(pathbuf) = '\0';
 
        /* Find tree element, descending from root, that
         * corresponds to the named path, lazily expanding
@@ -86,6 +89,10 @@ static struct tree_entry_list *find_entry(const char *path)
                        len = slash - path;
                }
                if (len) {
+                       if (oldelem) {
+                               pathbuf += sprintf(pathbuf, "%s/", oldelem->name);
+                       }
+
                        /* (len == 0) if the original path was "drivers/char/"
                         * and we have run already two rounds, having elem
                         * pointing at the drivers/char directory.
@@ -101,6 +108,8 @@ static struct tree_entry_list *find_entry(const char *path)
                        }
                        if (!elem)
                                return NULL;
+
+                       oldelem = elem;
                }
                path = next;
        }
@@ -108,19 +117,6 @@ static struct tree_entry_list *find_entry(const char *path)
        return elem;
 }
 
-static void show_entry_name(struct tree_entry_list *e)
-{
-       /* This is yucky.  The root level is there for
-        * our convenience but we really want to do a
-        * forest.
-        */
-       if (e->parent && e->parent != &root_entry) {
-               show_entry_name(e->parent);
-               putchar('/');
-       }
-       printf("%s", e->name);
-}
-
 static const char *entry_type(struct tree_entry_list *e)
 {
        return (e->directory ? "tree" : "blob");
@@ -134,28 +130,36 @@ static const char *entry_hex(struct tree_entry_list *e)
 }
 
 /* forward declaration for mutually recursive routines */
-static int show_entry(struct tree_entry_list *, int);
+static int show_entry(struct tree_entry_list *, int, char *pathbuf);
 
-static int show_children(struct tree_entry_list *e, int level)
+static int show_children(struct tree_entry_list *e, int level, char *pathbuf)
 {
+       int oldlen = strlen(pathbuf);
+
+       if (e != &root_entry)
+               sprintf(pathbuf + oldlen, "%s/", e->name);
+
        if (prepare_children(e))
                die("internal error: ls-tree show_children called with non tree");
        e = e->item.tree->entries;
        while (e) {
-               show_entry(e, level);
+               show_entry(e, level, pathbuf);
                e = e->next;
        }
+
+       pathbuf[oldlen] = '\0';
+
        return 0;
 }
 
-static int show_entry(struct tree_entry_list *e, int level)
+static int show_entry(struct tree_entry_list *e, int level, char *pathbuf)
 {
        int err = 0; 
 
        if (e != &root_entry) {
-               printf("%06o %s %s      ", e->mode, entry_type(e),
-                      entry_hex(e));
-               show_entry_name(e);
+               printf("%06o %s %s      ",
+                      e->mode, entry_type(e), entry_hex(e));
+               write_name_quoted(pathbuf, e->name, line_termination, stdout);
                putchar(line_termination);
        }
 
@@ -176,10 +180,10 @@ static int show_entry(struct tree_entry_list *e, int level)
                 */
                if (level == 0 && !(ls_options & LS_TREE_ONLY))
                        /* case (1)-a and (1)-b */
-                       err = err | show_children(e, level+1);
+                       err = err | show_children(e, level+1, pathbuf);
                else if (level && ls_options & LS_RECURSIVE)
                        /* case (2)-b */
-                       err = err | show_children(e, level+1);
+                       err = err | show_children(e, level+1, pathbuf);
        }
        return err;
 }
@@ -187,7 +191,8 @@ static int show_entry(struct tree_entry_list *e, int level)
 static int list_one(const char *path)
 {
        int err = 0;
-       struct tree_entry_list *e = find_entry(path);
+       char pathbuf[MAXPATHLEN + 1];
+       struct tree_entry_list *e = find_entry(path, pathbuf);
        if (!e) {
                /* traditionally ls-tree does not complain about
                 * missing path.  We may change this later to match
@@ -195,7 +200,7 @@ static int list_one(const char *path)
                 */
                return err;
        }
-       err = err | show_entry(e, 0);
+       err = err | show_entry(e, 0, pathbuf);
        return err;
 }
 
index df470bb9c2296d32e3b1d2b7489c712b01c3b0ed..cb853df993f37d74576e308008379d709361a447 100644 (file)
@@ -9,6 +9,10 @@
 #include <ctype.h>
 #include <iconv.h>
 
+#ifdef NO_STRCASESTR
+extern char *gitstrcasestr(const char *haystack, const char *needle);
+#endif
+
 static FILE *cmitmsg, *patchfile;
 
 static int keep_subject = 0;
index a3238c20da27c3348d406066b595cd085e600558..189f4ed724cba345eeb80cd41a9b7ae67068a037 100644 (file)
@@ -9,30 +9,13 @@
 #include <fcntl.h>
 #include <sys/types.h>
 #include <sys/stat.h>
-#include <sys/mman.h>
 #include <string.h>
 #include <stdio.h>
-#include <ctype.h>
 #include <assert.h>
+#include "cache.h"
 
-static int usage(void)
-{
-       fprintf(stderr, "mailsplit <mbox> <directory>\n");
-       exit(1);
-}
-
-static int linelen(const char *map, unsigned long size)
-{
-       int len = 0, c;
-
-       do {
-               c = *map;
-               map++;
-               size--;
-               len++;
-       } while (size && c != '\n');
-       return len;
-}
+static const char git_mailsplit_usage[] =
+"git-mailsplit [-d<prec>] [<mbox>] <directory>";
 
 static int is_from_line(const char *line, int len)
 {
@@ -65,81 +48,110 @@ static int is_from_line(const char *line, int len)
        return 1;
 }
 
-static int parse_email(const void *map, unsigned long size)
+/* Could be as small as 64, enough to hold a Unix "From " line. */
+static char buf[4096];
+
+/* Called with the first line (potentially partial)
+ * already in buf[] -- normally that should begin with
+ * the Unix "From " line.  Write it into the specified
+ * file.
+ */
+static int split_one(FILE *mbox, const char *name)
 {
-       unsigned long offset;
+       FILE *output = NULL;
+       int len = strlen(buf);
+       int fd;
+       int status = 0;
 
-       if (size < 6 || memcmp("From ", map, 5))
+       if (!is_from_line(buf, len))
                goto corrupt;
 
-       /* Make sure we don't trigger on this first line */
-       map++; size--; offset=1;
+       fd = open(name, O_WRONLY | O_CREAT | O_EXCL, 0666);
+       if (fd < 0)
+               die("cannot open output file %s", name);
+       output = fdopen(fd, "w");
 
-       /*
-        * Search for a line beginning with "From ", and 
-        * having something that looks like a date format.
+       /* Copy it out, while searching for a line that begins with
+        * "From " and having something that looks like a date format.
         */
-       do {
-               int len = linelen(map, size);
-               if (is_from_line(map, len))
-                       return offset;
-               map += len;
-               size -= len;
-               offset += len;
-       } while (size);
-       return offset;
-
-corrupt:
+       for (;;) {
+               int is_partial = (buf[len-1] != '\n');
+
+               if (fputs(buf, output) == EOF)
+                       die("cannot write output");
+
+               if (fgets(buf, sizeof(buf), mbox) == NULL) {
+                       if (feof(mbox)) {
+                               status = 1;
+                               break;
+                       }
+                       die("cannot read mbox");
+               }
+               len = strlen(buf);
+               if (!is_partial && is_from_line(buf, len))
+                       break; /* done with one message */
+       }
+       fclose(output);
+       return status;
+
+ corrupt:
+       if (output)
+               fclose(output);
+       unlink(name);
        fprintf(stderr, "corrupt mailbox\n");
        exit(1);
 }
 
-int main(int argc, char **argv)
+int main(int argc, const char **argv)
 {
-       int fd, nr;
-       struct stat st;
-       unsigned long size;
-       void *map;
-
-       if (argc != 3)
-               usage();
-       fd = open(argv[1], O_RDONLY);
-       if (fd < 0) {
-               perror(argv[1]);
-               exit(1);
-       }
-       if (chdir(argv[2]) < 0)
-               usage();
-       if (fstat(fd, &st) < 0) {
-               perror("stat");
-               exit(1);
+       int i, nr, nr_prec = 4;
+       FILE *mbox = NULL;
+
+       for (i = 1; i < argc; i++) {
+               const char *arg = argv[i];
+
+               if (arg[0] != '-')
+                       break;
+               /* do flags here */
+               if (!strncmp(arg, "-d", 2)) {
+                       nr_prec = strtol(arg + 2, NULL, 10);
+                       if (nr_prec < 3 || 10 <= nr_prec)
+                               usage(git_mailsplit_usage);
+                       continue;
+               }
        }
-       size = st.st_size;
-       map = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0);
-       if (map == MAP_FAILED) {
-               perror("mmap");
-               close(fd);
-               exit(1);
+
+       /* Either one remaining arg (dir), or two (mbox and dir) */
+       switch (argc - i) {
+       case 1:
+               mbox = stdin;
+               break;
+       case 2:
+               if ((mbox = fopen(argv[i], "r")) == NULL)
+                       die("cannot open mbox %s for reading", argv[i]);
+               break;
+       default:
+               usage(git_mailsplit_usage);
        }
-       close(fd);
+       if (chdir(argv[argc - 1]) < 0)
+               usage(git_mailsplit_usage);
+
        nr = 0;
-       do {
+       if (fgets(buf, sizeof(buf), mbox) == NULL)
+               die("cannot read mbox");
+
+       for (;;) {
                char name[10];
-               unsigned long len = parse_email(map, size);
-               assert(len <= size);
-               sprintf(name, "%04d", ++nr);
-               fd = open(name, O_WRONLY | O_CREAT | O_EXCL, 0600);
-               if (fd < 0) {
-                       perror(name);
-                       exit(1);
-               }
-               if (write(fd, map, len) != len) {
-                       perror("write");
+
+               sprintf(name, "%0*d", nr_prec, ++nr);
+               switch (split_one(mbox, name)) {
+               case 0:
+                       break;
+               case 1:
+                       printf("%d\n", nr);
+                       return 0;
+               default:
                        exit(1);
                }
-               close(fd);
-               map += len;
-               size -= len;
-       } while (size > 0);
-       return 0;
+       }
 }
index 7f6fc05e06e30b2f7b3eda6015043f5ebcfd34c1..847531d19f88e420ad68f95f11ab2f1f77876d08 100644 (file)
@@ -56,8 +56,8 @@ void SHA1_Init(SHA_CTX *ctx) {
 }
 
 
-void SHA1_Update(SHA_CTX *ctx, void *_dataIn, int len) {
-  unsigned char *dataIn = _dataIn;
+void SHA1_Update(SHA_CTX *ctx, const void *_dataIn, int len) {
+  const unsigned char *dataIn = _dataIn;
   int i;
 
   /* Read the data into W and process blocks as they get full
index f5decbf43b259232d3ec4a665712df61f946a7f5..5d82afa3bdd21a2855c569a73cf3277b5c6a41bc 100644 (file)
@@ -41,5 +41,5 @@ typedef struct {
 } SHA_CTX;
 
 void SHA1_Init(SHA_CTX *ctx);
-void SHA1_Update(SHA_CTX *ctx, void *dataIn, int len);
+void SHA1_Update(SHA_CTX *ctx, const void *dataIn, int len);
 void SHA1_Final(unsigned char hashout[20], SHA_CTX *ctx);
diff --git a/name-rev.c b/name-rev.c
new file mode 100644 (file)
index 0000000..59194f1
--- /dev/null
@@ -0,0 +1,246 @@
+#include <stdlib.h>
+#include "cache.h"
+#include "commit.h"
+#include "tag.h"
+#include "refs.h"
+
+static const char name_rev_usage[] =
+       "git-name-rev [--tags] ( --all | --stdin | commitish [commitish...] )\n";
+
+typedef struct rev_name {
+       const char *tip_name;
+       int merge_traversals;
+       int generation;
+} rev_name;
+
+static long cutoff = LONG_MAX;
+
+static void name_rev(struct commit *commit,
+               const char *tip_name, int merge_traversals, int generation,
+               int deref)
+{
+       struct rev_name *name = (struct rev_name *)commit->object.util;
+       struct commit_list *parents;
+       int parent_number = 0;
+
+       if (!commit->object.parsed)
+               parse_commit(commit);
+
+       if (commit->date < cutoff)
+               return;
+
+       if (deref) {
+               char *new_name = xmalloc(strlen(tip_name)+3);
+               strcpy(new_name, tip_name);
+               strcat(new_name, "^0");
+               tip_name = new_name;
+
+               if (generation)
+                       die("generation: %d, but deref?", generation);
+       }
+
+       if (name == NULL) {
+               name = xmalloc(sizeof(rev_name));
+               commit->object.util = name;
+               goto copy_data;
+       } else if (name->merge_traversals > merge_traversals ||
+                       (name->merge_traversals == merge_traversals &&
+                        name->generation > generation)) {
+copy_data:
+               name->tip_name = tip_name;
+               name->merge_traversals = merge_traversals;
+               name->generation = generation;
+       } else
+               return;
+
+       for (parents = commit->parents;
+                       parents;
+                       parents = parents->next, parent_number++) {
+               if (parent_number > 0) {
+                       char *new_name = xmalloc(strlen(tip_name)+8);
+
+                       if (generation > 0)
+                               sprintf(new_name, "%s~%d^%d", tip_name,
+                                               generation, parent_number);
+                       else
+                               sprintf(new_name, "%s^%d", tip_name, parent_number);
+
+                       name_rev(parents->item, new_name,
+                               merge_traversals + 1 , 0, 0);
+               } else {
+                       name_rev(parents->item, tip_name, merge_traversals,
+                               generation + 1, 0);
+               }
+       }
+}
+
+static int tags_only = 0;
+
+static int name_ref(const char *path, const unsigned char *sha1)
+{
+       struct object *o = parse_object(sha1);
+       int deref = 0;
+
+       if (tags_only && strncmp(path, "refs/tags/", 10))
+               return 0;
+
+       while (o && o->type == tag_type) {
+               struct tag *t = (struct tag *) o;
+               if (!t->tagged)
+                       break; /* broken repository */
+               o = parse_object(t->tagged->sha1);
+               deref = 1;
+       }
+       if (o && o->type == commit_type) {
+               struct commit *commit = (struct commit *)o;
+               const char *p;
+
+               while ((p = strchr(path, '/')))
+                       path = p+1;
+
+               name_rev(commit, strdup(path), 0, 0, deref);
+       }
+       return 0;
+}
+
+/* returns a static buffer */
+static const char* get_rev_name(struct object *o)
+{
+       static char buffer[1024];
+       struct rev_name *n = (struct rev_name *)o->util;
+       if (!n)
+               return "undefined";
+
+       if (!n->generation)
+               return n->tip_name;
+
+       snprintf(buffer, sizeof(buffer), "%s~%d", n->tip_name, n->generation);
+
+       return buffer;
+}
+       
+int main(int argc, char **argv)
+{
+       struct object_list *revs = NULL;
+       struct object_list **walker = &revs;
+       int as_is = 0, all = 0, transform_stdin = 0;
+
+       setup_git_directory();
+
+       if (argc < 2)
+               usage(name_rev_usage);
+
+       for (--argc, ++argv; argc; --argc, ++argv) {
+               unsigned char sha1[20];
+               struct object *o;
+               struct commit *commit;
+
+               if (!as_is && (*argv)[0] == '-') {
+                       if (!strcmp(*argv, "--")) {
+                               as_is = 1;
+                               continue;
+                       } else if (!strcmp(*argv, "--tags")) {
+                               tags_only = 1;
+                               continue;
+                       } else if (!strcmp(*argv, "--all")) {
+                               if (argc > 1)
+                                       die("Specify either a list, or --all, not both!");
+                               all = 1;
+                               cutoff = 0;
+                               continue;
+                       } else if (!strcmp(*argv, "--stdin")) {
+                               if (argc > 1)
+                                       die("Specify either a list, or --stdin, not both!");
+                               transform_stdin = 1;
+                               cutoff = 0;
+                               continue;
+                       }
+                       usage(name_rev_usage);
+               }
+
+               if (get_sha1(*argv, sha1)) {
+                       fprintf(stderr, "Could not get sha1 for %s. Skipping.\n",
+                                       *argv);
+                       continue;
+               }
+
+               o = deref_tag(parse_object(sha1), *argv, 0);
+               if (!o || o->type != commit_type) {
+                       fprintf(stderr, "Could not get commit for %s. Skipping.\n",
+                                       *argv);
+                       continue;
+               }
+
+               commit = (struct commit *)o;
+
+               if (cutoff > commit->date)
+                       cutoff = commit->date;
+
+               object_list_append((struct object *)commit, walker);
+               (*walker)->name = *argv;
+               walker = &((*walker)->next);
+       }
+
+       for_each_ref(name_ref);
+
+       if (transform_stdin) {
+               char buffer[2048];
+               char *p, *p_start;
+
+               while (!feof(stdin)) {
+                       int forty = 0;
+                       p = fgets(buffer, sizeof(buffer), stdin);
+                       if (!p)
+                               break;
+
+                       for (p_start = p; *p; p++) {
+#define ishex(x) (isdigit((x)) || ((x) >= 'a' && (x) <= 'f'))
+                               if (!ishex(*p))
+                                       forty = 0;
+                               else if (++forty == 40 &&
+                                               !ishex(*(p+1))) {
+                                       unsigned char sha1[40];
+                                       const char *name = "undefined";
+                                       char c = *(p+1);
+
+                                       forty = 0;
+
+                                       *(p+1) = 0;
+                                       if (!get_sha1(p - 39, sha1)) {
+                                               struct object *o =
+                                                       lookup_object(sha1);
+                                               if (o)
+                                                       name = get_rev_name(o);
+                                       }
+                                       *(p+1) = c;
+
+                                       if (!strcmp(name, "undefined"))
+                                               continue;
+
+                                       fwrite(p_start, p - p_start, 1, stdout);
+                                       fputc('(', stdout);
+                                       fputs(name, stdout);
+                                       fputc(')', stdout);
+                                       p_start = p + 1;
+                               }
+                       }
+
+                       /* flush */
+                       if (p_start != p)
+                               fwrite(p_start, p - p_start, 1, stdout);
+               }
+       } else if (all) {
+               extern struct object **objs;
+               extern int nr_objs;
+               int i;
+
+               for (i = 0; i < nr_objs; i++)
+                       printf("%s %s\n", sha1_to_hex(objs[i]->sha1),
+                                       get_rev_name(objs[i]));
+       } else
+               for ( ; revs; revs = revs->next)
+                       printf("%s %s\n", revs->name, get_rev_name(revs->item));
+
+       return 0;
+}
+
index 3d622787cc554eb2936995441e4591ec1986bded..4e941e7392077dca9b511282be13e2b559589e5b 100644 (file)
@@ -1,11 +1,10 @@
-#include <ctype.h>
 #include "cache.h"
 #include "object.h"
 #include "delta.h"
 #include "pack.h"
 #include "csum-file.h"
 
-static const char pack_usage[] = "git-pack-objects [--incremental] [--window=N] [--depth=N] {--stdout | base-name} < object-list";
+static const char pack_usage[] = "git-pack-objects [--local] [--incremental] [--window=N] [--depth=N] {--stdout | base-name} < object-list";
 
 struct object_entry {
        unsigned char sha1[20];
@@ -20,6 +19,7 @@ struct object_entry {
 
 static unsigned char object_list_sha1[20];
 static int non_empty = 0;
+static int local = 0;
 static int incremental = 0;
 static struct object_entry **sorted_by_sha, **sorted_by_type;
 static struct object_entry *objects = NULL;
@@ -195,8 +195,20 @@ static int add_object_entry(unsigned char *sha1, unsigned int hash)
        unsigned int idx = nr_objects;
        struct object_entry *entry;
 
-       if (incremental && has_sha1_pack(sha1))
-               return 0;
+       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;
+                       }
+               }
+       }
 
        if (idx >= nr_alloc) {
                unsigned int needed = (idx + 1024) * 3 / 2;
@@ -388,11 +400,77 @@ static void find_deltas(struct object_entry **list, int window, int depth)
        free(array);
 }
 
+static void prepare_pack(int window, int depth)
+{
+       get_object_details();
+
+       fprintf(stderr, "Packing %d objects\n", nr_objects);
+
+       sorted_by_type = create_sorted_list(type_size_sort);
+       if (window && depth)
+               find_deltas(sorted_by_type, window+1, depth);
+       write_pack_file();
+}
+
+static int reuse_cached_pack(unsigned char *sha1, int pack_to_stdout)
+{
+       static const char cache[] = "pack-cache/pack-%s.%s";
+       char *cached_pack, *cached_idx;
+       int ifd, ofd, ifd_ix = -1;
+
+       cached_pack = git_path(cache, sha1_to_hex(sha1), "pack");
+       ifd = open(cached_pack, O_RDONLY);
+       if (ifd < 0)
+               return 0;
+
+       if (!pack_to_stdout) {
+               cached_idx = git_path(cache, sha1_to_hex(sha1), "idx");
+               ifd_ix = open(cached_idx, O_RDONLY);
+               if (ifd_ix < 0) {
+                       close(ifd);
+                       return 0;
+               }
+       }
+
+       fprintf(stderr, "Reusing %d objects pack %s\n", nr_objects,
+               sha1_to_hex(sha1));
+
+       if (pack_to_stdout) {
+               if (copy_fd(ifd, 1))
+                       exit(1);
+               close(ifd);
+       }
+       else {
+               char name[PATH_MAX];
+               snprintf(name, sizeof(name),
+                        "%s-%s.%s", base_name, sha1_to_hex(sha1), "pack");
+               ofd = open(name, O_CREAT | O_EXCL | O_WRONLY, 0666);
+               if (ofd < 0)
+                       die("unable to open %s (%s)", name, strerror(errno));
+               if (copy_fd(ifd, ofd))
+                       exit(1);
+               close(ifd);
+
+               snprintf(name, sizeof(name),
+                        "%s-%s.%s", base_name, sha1_to_hex(sha1), "idx");
+               ofd = open(name, O_CREAT | O_EXCL | O_WRONLY, 0666);
+               if (ofd < 0)
+                       die("unable to open %s (%s)", name, strerror(errno));
+               if (copy_fd(ifd_ix, ofd))
+                       exit(1);
+               close(ifd_ix);
+               puts(sha1_to_hex(sha1));
+       }
+
+       return 1;
+}
+
 int main(int argc, char **argv)
 {
        SHA_CTX ctx;
        char line[PATH_MAX + 20];
        int window = 10, depth = 10, pack_to_stdout = 0;
+       struct object_entry **list;
        int i;
 
        for (i = 1; i < argc; i++) {
@@ -403,6 +481,10 @@ int main(int argc, char **argv)
                                non_empty = 1;
                                continue;
                        }
+                       if (!strcmp("--local", arg)) {
+                               local = 1;
+                               continue;
+                       }
                        if (!strcmp("--incremental", arg)) {
                                incremental = 1;
                                continue;
@@ -435,7 +517,7 @@ int main(int argc, char **argv)
        if (pack_to_stdout != !base_name)
                usage(pack_usage);
 
-       SHA1_Init(&ctx);
+       prepare_packed_git();
        while (fgets(line, sizeof(line), stdin) != NULL) {
                unsigned int hash;
                char *p;
@@ -451,25 +533,28 @@ int main(int argc, char **argv)
                                continue;
                        hash = hash * 11 + c;
                }
-               if (add_object_entry(sha1, hash))
-                       SHA1_Update(&ctx, sha1, 20);
+               add_object_entry(sha1, hash);
        }
-       SHA1_Final(object_list_sha1, &ctx);
        if (non_empty && !nr_objects)
                return 0;
-       get_object_details();
-
-       fprintf(stderr, "Packing %d objects\n", nr_objects);
 
        sorted_by_sha = create_sorted_list(sha1_sort);
-       sorted_by_type = create_sorted_list(type_size_sort);
-       if (window && depth)
-               find_deltas(sorted_by_type, window+1, depth);
+       SHA1_Init(&ctx);
+       list = sorted_by_sha;
+       for (i = 0; i < nr_objects; i++) {
+               struct object_entry *entry = *list++;
+               SHA1_Update(&ctx, entry->sha1, 20);
+       }
+       SHA1_Final(object_list_sha1, &ctx);
 
-       write_pack_file();
-       if (!pack_to_stdout) {
-               write_index_file();
-               puts(sha1_to_hex(object_list_sha1));
+       if (reuse_cached_pack(object_list_sha1, pack_to_stdout))
+               ;
+       else {
+               prepare_pack(window, depth);
+               if (!pack_to_stdout) {
+                       write_index_file();
+                       puts(sha1_to_hex(object_list_sha1));
+               }
        }
        return 0;
 }
index 5a8dc75d0e0184f8a4afa53e22b44d20f060685f..edbc4aa3e82974168f2d4c21085bdd43b774d55e 100644 (file)
@@ -1,4 +1,3 @@
-#include <ctype.h>
 #include "cache.h"
 
 static void flush_current_id(int patchlen, unsigned char *id, SHA_CTX *c)
@@ -55,6 +54,10 @@ static void generate_id_list(void)
                if (!patchlen && memcmp(line, "diff ", 5))
                        continue;
 
+               /* Ignore git-diff index header */
+               if (!memcmp(line, "index ", 6))
+                       continue;
+
                /* Ignore line numbers when computing the SHA1 of the patch */
                if (!memcmp(line, "@@ -", 4))
                        continue;
diff --git a/path.c b/path.c
index f788028a63dbd9b34563d2589697f4b5d65b8648..495d17ca4ca371f4b5bdb6f1f3de9a6a9d84280b 100644 (file)
--- a/path.c
+++ b/path.c
@@ -41,12 +41,10 @@ char *mkpath(const char *fmt, ...)
 
 char *git_path(const char *fmt, ...)
 {
-       const char *git_dir;
+       const char *git_dir = get_git_dir();
        va_list args;
        unsigned len;
 
-       git_dir = getenv(GIT_DIR_ENVIRONMENT);
-       if (!git_dir) git_dir = DEFAULT_GIT_DIR_ENVIRONMENT;
        len = strlen(git_dir);
        if (len > PATH_MAX-100)
                return bad_path;
index 4b1d0d5ba8ebb61c50a8766c73c6ba19e6efdef9..ee49bf3b7b123359153a961c56f5aa3d58515864 100644 (file)
@@ -11,7 +11,7 @@ static int peek_remote(int fd[2])
 {
        struct ref *ref;
 
-       get_remote_heads(fd[0], &ref, 0, NULL);
+       get_remote_heads(fd[0], &ref, 0, NULL, 0);
        packet_flush(fd[1]);
 
        while (ref) {
index 5306e8e5ef411bfa7e6bcf4ba0639e640592818b..26123f7f6bb8bf9cc7fc27905fe2b67d66646f05 100644 (file)
@@ -26,6 +26,8 @@ static void prune_dir(int i, DIR *dir, char *pathname, int len)
                else if (unlink(pathname) < 0)
                        error("unable to unlink %s", pathname);
        }
+       pathname[len] = 0;
+       rmdir(pathname);
 }
 
 static void prune_packed_objects(void)
@@ -46,7 +48,7 @@ static void prune_packed_objects(void)
                sprintf(pathname + len, "%02x/", i);
                d = opendir(pathname);
                if (!d)
-                       die("unable to open %s", pathname);
+                       continue;
                prune_dir(i, d, pathname, len + 3);
                closedir(d);
        }
@@ -69,6 +71,7 @@ int main(int argc, char **argv)
                /* Handle arguments here .. */
                usage(prune_packed_usage);
        }
+       sync();
        prune_packed_objects();
        return 0;
 }
diff --git a/quote.c b/quote.c
index 5e6fda311c5f5fdaa20b7e9d64e46c13b7d9cce8..e662a7da71e39c1ffc3d1a4bf485fda17df5e907 100644 (file)
--- a/quote.c
+++ b/quote.c
 #include "quote.h"
 
 /* Help to copy the thing properly quoted for the shell safety.
- * any single quote is replaced with '\'', and the caller is
- * expected to enclose the result within a single quote pair.
+ * any single quote is replaced with '\'', any exclamation point
+ * is replaced with '\!', and the whole thing is enclosed in a
  *
  * E.g.
  *  original     sq_quote     result
  *  name     ==> name      ==> 'name'
  *  a b      ==> a b       ==> 'a b'
  *  a'b      ==> a'\''b    ==> 'a'\''b'
+ *  a!b      ==> a'\!'b    ==> 'a'\!'b'
  */
-char *sq_quote(const char *src)
+#undef EMIT
+#define EMIT(x) ( (++len < n) && (*bp++ = (x)) )
+
+static inline int need_bs_quote(char c)
 {
-       static char *buf = NULL;
-       int cnt, c;
-       const char *cp;
-       char *bp;
+       return (c == '\'' || c == '!');
+}
 
-       /* count bytes needed to store the quoted string. */
-       for (cnt = 3, cp = src; *cp; cnt++, cp++)
-               if (*cp == '\'')
-                       cnt += 3;
+size_t sq_quote_buf(char *dst, size_t n, const char *src)
+{
+       char c;
+       char *bp = dst;
+       size_t len = 0;
 
-       buf = xmalloc(cnt);
-       bp = buf;
-       *bp++ = '\'';
+       EMIT('\'');
        while ((c = *src++)) {
-               if (c != '\'')
-                       *bp++ = c;
-               else {
-                       bp = strcpy(bp, "'\\''");
-                       bp += 4;
+               if (need_bs_quote(c)) {
+                       EMIT('\'');
+                       EMIT('\\');
+                       EMIT(c);
+                       EMIT('\'');
+               } else {
+                       EMIT(c);
                }
        }
-       *bp++ = '\'';
-       *bp = 0;
+       EMIT('\'');
+
+       if ( n )
+               *bp = 0;
+
+       return len;
+}
+
+char *sq_quote(const char *src)
+{
+       char *buf;
+       size_t cnt;
+
+       cnt = sq_quote_buf(NULL, 0, src) + 1;
+       buf = xmalloc(cnt);
+       sq_quote_buf(buf, cnt, src);
+
        return buf;
 }
 
+char *sq_dequote(char *arg)
+{
+       char *dst = arg;
+       char *src = arg;
+       char c;
+
+       if (*src != '\'')
+               return NULL;
+       for (;;) {
+               c = *++src;
+               if (!c)
+                       return NULL;
+               if (c != '\'') {
+                       *dst++ = c;
+                       continue;
+               }
+               /* We stepped out of sq */
+               switch (*++src) {
+               case '\0':
+                       *dst = 0;
+                       return arg;
+               case '\\':
+                       c = *++src;
+                       if (need_bs_quote(c) && *++src == '\'') {
+                               *dst++ = c;
+                               continue;
+                       }
+               /* Fallthrough */
+               default:
+                       return NULL;
+               }
+       }
+}
+
+/*
+ * C-style name quoting.
+ *
+ * Does one of three things:
+ *
+ * (1) if outbuf and outfp are both NULL, inspect the input name and
+ *     counts the number of bytes that are needed to hold c_style
+ *     quoted version of name, counting the double quotes around
+ *     it but not terminating NUL, and returns it.  However, if name
+ *     does not need c_style quoting, it returns 0.
+ *
+ * (2) if outbuf is not NULL, it must point at a buffer large enough
+ *     to hold the c_style quoted version of name, enclosing double
+ *     quotes, and terminating NUL.  Fills outbuf with c_style quoted
+ *     version of name enclosed in double-quote pair.  Return value
+ *     is undefined.
+ *
+ * (3) if outfp is not NULL, outputs c_style quoted version of name,
+ *     but not enclosed in double-quote pair.  Return value is undefined.
+ */
+
+int quote_c_style(const char *name, char *outbuf, FILE *outfp, int no_dq)
+{
+#undef EMIT
+#define EMIT(c) \
+       (outbuf ? (*outbuf++ = (c)) : outfp ? fputc(c, outfp) : (count++))
+
+#define EMITQ() EMIT('\\')
+
+       const char *sp;
+       int ch, count = 0, needquote = 0;
+
+       if (!no_dq)
+               EMIT('"');
+       for (sp = name; (ch = *sp++); ) {
+
+               if ((ch < ' ') || (ch == '"') || (ch == '\\') ||
+                   (ch == 0177)) {
+                       needquote = 1;
+                       switch (ch) {
+                       case '\a': EMITQ(); ch = 'a'; break;
+                       case '\b': EMITQ(); ch = 'b'; break;
+                       case '\f': EMITQ(); ch = 'f'; break;
+                       case '\n': EMITQ(); ch = 'n'; break;
+                       case '\r': EMITQ(); ch = 'r'; break;
+                       case '\t': EMITQ(); ch = 't'; break;
+                       case '\v': EMITQ(); ch = 'v'; break;
+
+                       case '\\': /* fallthru */
+                       case '"': EMITQ(); break;
+                       case ' ':
+                               break;
+                       default:
+                               /* octal */
+                               EMITQ();
+                               EMIT(((ch >> 6) & 03) + '0');
+                               EMIT(((ch >> 3) & 07) + '0');
+                               ch = (ch & 07) + '0';
+                               break;
+                       }
+               }
+               EMIT(ch);
+       }
+       if (!no_dq)
+               EMIT('"');
+       if (outbuf)
+               *outbuf = 0;
+
+       return needquote ? count : 0;
+}
+
+/*
+ * C-style name unquoting.
+ *
+ * Quoted should point at the opening double quote.  Returns
+ * an allocated memory that holds unquoted name, which the caller
+ * should free when done.  Updates endp pointer to point at
+ * one past the ending double quote if given.
+ */
+
+char *unquote_c_style(const char *quoted, const char **endp)
+{
+       const char *sp;
+       char *name = NULL, *outp = NULL;
+       int count = 0, ch, ac;
+
+#undef EMIT
+#define EMIT(c) (outp ? (*outp++ = (c)) : (count++))
+
+       if (*quoted++ != '"')
+               return NULL;
+
+       while (1) {
+               /* first pass counts and allocates, second pass fills */
+               for (sp = quoted; (ch = *sp++) != '"'; ) {
+                       if (ch == '\\') {
+                               switch (ch = *sp++) {
+                               case 'a': ch = '\a'; break;
+                               case 'b': ch = '\b'; break;
+                               case 'f': ch = '\f'; break;
+                               case 'n': ch = '\n'; break;
+                               case 'r': ch = '\r'; break;
+                               case 't': ch = '\t'; break;
+                               case 'v': ch = '\v'; break;
+
+                               case '\\': case '"':
+                                       break; /* verbatim */
+
+                               case '0'...'7':
+                                       /* octal */
+                                       ac = ((ch - '0') << 6);
+                                       if ((ch = *sp++) < '0' || '7' < ch)
+                                               return NULL;
+                                       ac |= ((ch - '0') << 3);
+                                       if ((ch = *sp++) < '0' || '7' < ch)
+                                               return NULL;
+                                       ac |= (ch - '0');
+                                       ch = ac;
+                                       break;
+                               default:
+                                       return NULL; /* malformed */
+                               }
+                       }
+                       EMIT(ch);
+               }
+
+               if (name) {
+                       *outp = 0;
+                       if (endp)
+                               *endp = sp;
+                       return name;
+               }
+               outp = name = xmalloc(count + 1);
+       }
+}
+
+void write_name_quoted(const char *prefix, const char *name,
+                      int quote, FILE *out)
+{
+       int needquote;
+
+       if (!quote) {
+       no_quote:
+               if (prefix && prefix[0])
+                       fputs(prefix, out);
+               fputs(name, out);
+               return;
+       }
+
+       needquote = 0;
+       if (prefix && prefix[0])
+               needquote = quote_c_style(prefix, NULL, NULL, 0);
+       if (!needquote)
+               needquote = quote_c_style(name, NULL, NULL, 0);
+       if (needquote) {
+               fputc('"', out);
+               if (prefix && prefix[0])
+                       quote_c_style(prefix, NULL, out, 1);
+               quote_c_style(name, NULL, out, 1);
+               fputc('"', out);
+       }
+       else
+               goto no_quote;
+}
diff --git a/quote.h b/quote.h
index c8cfb3a12482b0a2398d7d9e164c37e5473b44b8..2486e6e68c2d7fa7bc2f78c18df323cc9adcc894 100644 (file)
--- a/quote.h
+++ b/quote.h
@@ -1,10 +1,13 @@
 #ifndef QUOTE_H
 #define QUOTE_H
 
+#include <stddef.h>
+#include <stdio.h>
 
 /* Help to copy the thing properly quoted for the shell safety.
- * any single quote is replaced with '\'', and the whole thing
- * is enclosed in a single quote pair.
+ * any single quote is replaced with '\'', any exclamation point
+ * is replaced with '\!', and the whole thing is enclosed in a
+ * single quote pair.
  *
  * For example, if you are passing the result to system() as an
  * argument:
  *
  * Note that the above examples leak memory!  Remember to free result from
  * sq_quote() in a real application.
+ *
+ * sq_quote_buf() writes to an existing buffer of specified size; it
+ * will return the number of characters that would have been written
+ * excluding the final null regardless of the buffer size.
  */
 
-char *sq_quote(const char *src);
+extern char *sq_quote(const char *src);
+extern size_t sq_quote_buf(char *dst, size_t n, const char *src);
+
+/* This unwraps what sq_quote() produces in place, but returns
+ * NULL if the input does not look like what sq_quote would have
+ * produced.
+ */
+extern char *sq_dequote(char *);
+
+extern int quote_c_style(const char *name, char *outbuf, FILE *outfp,
+                        int nodq);
+extern char *unquote_c_style(const char *quoted, const char **endp);
+
+extern void write_name_quoted(const char *prefix, const char *name,
+                             int quote, FILE *out);
 
 #endif
index 6eff4c8401b820b001a3ed48e8086c4cf1cd8f5c..693273620370e0500bc9ea2adba98a88cafc39cd 100644 (file)
@@ -35,8 +35,11 @@ int ce_match_stat(struct cache_entry *ce, struct stat *st)
        switch (ntohl(ce->ce_mode) & S_IFMT) {
        case S_IFREG:
                changed |= !S_ISREG(st->st_mode) ? TYPE_CHANGED : 0;
-               /* We consider only the owner x bit to be relevant for "mode changes" */
-               if (0100 & (ntohl(ce->ce_mode) ^ st->st_mode))
+               /* We consider only the owner x bit to be relevant for
+                * "mode changes"
+                */
+               if (trust_executable_bit &&
+                   (0100 & (ntohl(ce->ce_mode) ^ st->st_mode)))
                        changed |= MODE_CHANGED;
                break;
        case S_IFLNK:
@@ -83,6 +86,83 @@ int ce_match_stat(struct cache_entry *ce, struct stat *st)
        return changed;
 }
 
+static int ce_compare_data(struct cache_entry *ce, struct stat *st)
+{
+       int match = -1;
+       int fd = open(ce->name, O_RDONLY);
+
+       if (fd >= 0) {
+               unsigned char sha1[20];
+               if (!index_fd(sha1, fd, st, 0, NULL))
+                       match = memcmp(sha1, ce->sha1, 20);
+               close(fd);
+       }
+       return match;
+}
+
+static int ce_compare_link(struct cache_entry *ce, unsigned long expected_size)
+{
+       int match = -1;
+       char *target;
+       void *buffer;
+       unsigned long size;
+       char type[10];
+       int len;
+
+       target = xmalloc(expected_size);
+       len = readlink(ce->name, target, expected_size);
+       if (len != expected_size) {
+               free(target);
+               return -1;
+       }
+       buffer = read_sha1_file(ce->sha1, type, &size);
+       if (!buffer) {
+               free(target);
+               return -1;
+       }
+       if (size == expected_size)
+               match = memcmp(buffer, target, size);
+       free(buffer);
+       free(target);
+       return match;
+}
+
+int ce_modified(struct cache_entry *ce, struct stat *st)
+{
+       int changed;
+       changed = ce_match_stat(ce, st);
+       if (!changed)
+               return 0;
+
+       /*
+        * If the mode or type has changed, there's no point in trying
+        * to refresh the entry - it's not going to match
+        */
+       if (changed & (MODE_CHANGED | TYPE_CHANGED))
+               return changed;
+
+       /* Immediately after read-tree or update-index --cacheinfo,
+        * the length field is zero.  For other cases the ce_size
+        * should match the SHA1 recorded in the index entry.
+        */
+       if ((changed & DATA_CHANGED) && ce->ce_size != htonl(0))
+               return changed;
+
+       switch (st->st_mode & S_IFMT) {
+       case S_IFREG:
+               if (ce_compare_data(ce, st))
+                       return changed | DATA_CHANGED;
+               break;
+       case S_IFLNK:
+               if (ce_compare_link(ce, st->st_size))
+                       return changed | DATA_CHANGED;
+               break;
+       default:
+               return changed | TYPE_CHANGED;
+       }
+       return 0;
+}
+
 int base_name_compare(const char *name1, int len1, int mode1,
                      const char *name2, int len2, int mode2)
 {
@@ -155,7 +235,7 @@ int remove_cache_entry_at(int pos)
        return 1;
 }
 
-int remove_file_from_cache(char *path)
+int remove_file_from_cache(const char *path)
 {
        int pos = cache_name_pos(path, strlen(path));
        if (pos < 0)
@@ -315,7 +395,7 @@ int add_cache_entry(struct cache_entry *ce, int option)
        int skip_df_check = option & ADD_CACHE_SKIP_DFCHECK;
        pos = cache_name_pos(ce->name, ntohs(ce->ce_flags));
 
-       /* existing match? Just replace it */
+       /* existing match? Just replace it. */
        if (pos >= 0) {
                active_cache_changed = 1;
                active_cache[pos] = ce;
@@ -338,7 +418,8 @@ int add_cache_entry(struct cache_entry *ce, int option)
        if (!ok_to_add)
                return -1;
 
-       if (!skip_df_check && check_file_directory_conflict(ce, pos, ok_to_replace)) {
+       if (!skip_df_check &&
+           check_file_directory_conflict(ce, pos, ok_to_replace)) {
                if (!ok_to_replace)
                        return -1;
                pos = cache_name_pos(ce->name, ntohs(ce->ce_flags));
@@ -387,11 +468,15 @@ int read_cache(void)
 
        errno = EBUSY;
        if (active_cache)
-               return error("more than one cachefile");
+               return active_nr;
+
        errno = ENOENT;
        fd = open(get_index_file(), O_RDONLY);
-       if (fd < 0)
-               return (errno == ENOENT) ? 0 : error("open failed");
+       if (fd < 0) {
+               if (errno == ENOENT)
+                       return 0;
+               die("index file open failed (%s)", strerror(errno));
+       }
 
        size = 0; // avoid gcc warning
        map = MAP_FAILED;
@@ -403,7 +488,7 @@ int read_cache(void)
        }
        close(fd);
        if (map == MAP_FAILED)
-               return error("mmap failed");
+               die("index file mmap failed (%s)", strerror(errno));
 
        hdr = map;
        if (verify_hdr(hdr, size) < 0)
@@ -424,7 +509,7 @@ int read_cache(void)
 unmap:
        munmap(map, size);
        errno = EINVAL;
-       return error("verify header failed");
+       die("index file corrupt");
 }
 
 #define WRITE_BUFFER_SIZE 8192
index 6a5c08c4c6903fec45cbe37a63f0bc42fbae53bc..6a456ae61100f8e45f97394d7ea23be4faa63fc4 100644 (file)
@@ -13,6 +13,8 @@
 static int merge = 0;
 static int update = 0;
 static int index_only = 0;
+static int nontrivial_merge = 0;
+static int trivial_merges_only = 0;
 
 static int head_idx = -1;
 static int merge_size = 0;
@@ -235,6 +237,35 @@ static void reject_merge(struct cache_entry *ce)
            ce->name);
 }
 
+/* Unlink the last component and attempt to remove leading
+ * directories, in case this unlink is the removal of the
+ * last entry in the directory -- empty directories are removed.
+ */
+static void unlink_entry(char *name)
+{
+       char *cp, *prev;
+
+       if (unlink(name))
+               return;
+       prev = NULL;
+       while (1) {
+               int status;
+               cp = strrchr(name, '/');
+               if (prev)
+                       *prev = '/';
+               if (!cp)
+                       break;
+
+               *cp = 0;
+               status = rmdir(name);
+               if (status) {
+                       *cp = '/';
+                       break;
+               }
+               prev = cp;
+       }
+}
+
 static void check_updates(struct cache_entry **src, int nr)
 {
        static struct checkout state = {
@@ -248,7 +279,7 @@ static void check_updates(struct cache_entry **src, int nr)
                struct cache_entry *ce = *src++;
                if (!ce->ce_mode) {
                        if (update)
-                               unlink(ce->name);
+                               unlink_entry(ce->name);
                        continue;
                }
                if (ce->ce_flags & mask) {
@@ -275,6 +306,9 @@ static int unpack_trees(merge_fn_t fn)
        if (unpack_trees_rec(posns, len, "", fn, &indpos))
                return -1;
 
+       if (trivial_merges_only && nontrivial_merge)
+               die("Merge requires file-level merging");
+
        check_updates(active_cache, active_nr);
        return 0;
 }
@@ -460,6 +494,8 @@ static int threeway_merge(struct cache_entry **stages)
                verify_uptodate(index);
        }
 
+       nontrivial_merge = 1;
+
        /* #2, #3, #4, #6, #7, #9, #11. */
        count = 0;
        if (!head_match || !remote_match) {
@@ -629,9 +665,9 @@ int main(int argc, char **argv)
                        continue;
                }
 
-               if (!strcmp(arg, "--head")) {
-                       head_idx = stage - 1;
-                       fn = threeway_merge;
+               if (!strcmp(arg, "--trivial")) {
+                       trivial_merges_only = 1;
+                       continue;
                }
 
                /* "-m" stands for "merge", meaning we start in stage 1 */
@@ -655,9 +691,10 @@ int main(int argc, char **argv)
                        die("failed to unpack tree object %s", arg);
                stage++;
        }
-       if (update && !merge)
+       if ((update||index_only) && !merge)
                usage(read_tree_usage);
-       if (merge && !fn) {
+
+       if (merge) {
                if (stage < 2)
                        die("just how do you expect me to merge %d trees?", stage-1);
                switch (stage - 1) {
@@ -674,9 +711,7 @@ int main(int argc, char **argv)
                        fn = threeway_merge;
                        break;
                }
-       }
 
-       if (head_idx < 0) {
                if (stage - 1 >= 3)
                        head_idx = stage - 2;
                else
index 06857eb77fef93788f54d17cca68924f5213c9db..8f157bc3f0ef1f9d96bca18fc81854fc24bff936 100644 (file)
@@ -95,6 +95,10 @@ static int update(const char *name,
        char new_hex[60], *old_hex, *lock_name;
        int newfd, namelen, written;
 
+       if (!strncmp(name, "refs/", 5) && check_ref_format(name + 5))
+               return error("refusing to create funny ref '%s' locally",
+                            name);
+
        namelen = strlen(name);
        lock_name = xmalloc(namelen + 10);
        memcpy(lock_name, name, namelen);
diff --git a/refs.c b/refs.c
index 161018097def05f717ae20e59e3df6cc1b01ab77..a52b038eefdf22fce20d1a81180c97def0613cdb 100644 (file)
--- a/refs.c
+++ b/refs.c
 
 #include <errno.h>
 
-static int read_ref(const char *refname, unsigned char *sha1)
+/* We allow "recursive" symbolic refs. Only within reason, though */
+#define MAXDEPTH 5
+
+#ifndef USE_SYMLINK_HEAD
+#define USE_SYMLINK_HEAD 1
+#endif
+
+int validate_symref(const char *path)
+{
+       struct stat st;
+       char *buf, buffer[256];
+       int len, fd;
+
+       if (lstat(path, &st) < 0)
+               return -1;
+
+       /* Make sure it is a "refs/.." symlink */
+       if (S_ISLNK(st.st_mode)) {
+               len = readlink(path, buffer, sizeof(buffer)-1);
+               if (len >= 5 && !memcmp("refs/", buffer, 5))
+                       return 0;
+               return -1;
+       }
+
+       /*
+        * Anything else, just open it and try to see if it is a symbolic ref.
+        */
+       fd = open(path, O_RDONLY);
+       if (fd < 0)
+               return -1;
+       len = read(fd, buffer, sizeof(buffer)-1);
+       close(fd);
+
+       /*
+        * Is it a symbolic ref?
+        */
+       if (len < 4 || memcmp("ref:", buffer, 4))
+               return -1;
+       buf = buffer + 4;
+       len -= 4;
+       while (len && isspace(*buf))
+               buf++, len--;
+       if (len >= 5 && !memcmp("refs/", buf, 5))
+               return 0;
+       return -1;
+}
+
+const char *resolve_ref(const char *path, unsigned char *sha1, int reading)
 {
-       int ret = -1;
-       int fd = open(git_path("%s", refname), O_RDONLY);
+       int depth = MAXDEPTH, len;
+       char buffer[256];
+
+       for (;;) {
+               struct stat st;
+               char *buf;
+               int fd;
+
+               if (--depth < 0)
+                       return NULL;
 
-       if (fd >= 0) {
-               char buffer[60];
-               if (read(fd, buffer, sizeof(buffer)) >= 40)
-                       ret = get_sha1_hex(buffer, sha1);
+               /* Special case: non-existing file.
+                * Not having the refs/heads/new-branch is OK
+                * if we are writing into it, so is .git/HEAD
+                * that points at refs/heads/master still to be
+                * born.  It is NOT OK if we are resolving for
+                * reading.
+                */
+               if (lstat(path, &st) < 0) {
+                       if (reading || errno != ENOENT)
+                               return NULL;
+                       memset(sha1, 0, 20);
+                       return path;
+               }
+
+               /* Follow "normalized" - ie "refs/.." symlinks by hand */
+               if (S_ISLNK(st.st_mode)) {
+                       len = readlink(path, buffer, sizeof(buffer)-1);
+                       if (len >= 5 && !memcmp("refs/", buffer, 5)) {
+                               path = git_path("%.*s", len, buffer);
+                               continue;
+                       }
+               }
+
+               /*
+                * Anything else, just open it and try to use it as
+                * a ref
+                */
+               fd = open(path, O_RDONLY);
+               if (fd < 0)
+                       return NULL;
+               len = read(fd, buffer, sizeof(buffer)-1);
                close(fd);
+
+               /*
+                * Is it a symbolic ref?
+                */
+               if (len < 4 || memcmp("ref:", buffer, 4))
+                       break;
+               buf = buffer + 4;
+               len -= 4;
+               while (len && isspace(*buf))
+                       buf++, len--;
+               while (len && isspace(buf[len-1]))
+                       buf[--len] = 0;
+               path = git_path("%.*s", len, buf);
        }
-       return ret;
+       if (len < 40 || get_sha1_hex(buffer, sha1))
+               return NULL;
+       return path;
+}
+
+int create_symref(const char *git_HEAD, const char *refs_heads_master)
+{
+       const char *lockpath;
+       char ref[1000];
+       int fd, len, written;
+
+#if USE_SYMLINK_HEAD
+       unlink(git_HEAD);
+       if (!symlink(refs_heads_master, git_HEAD))
+               return 0;
+       fprintf(stderr, "no symlink - falling back to symbolic ref\n");
+#endif
+
+       len = snprintf(ref, sizeof(ref), "ref: %s\n", refs_heads_master);
+       if (sizeof(ref) <= len) {
+               error("refname too long: %s", refs_heads_master);
+               return -1;
+       }
+       lockpath = mkpath("%s.lock", git_HEAD);
+       fd = open(lockpath, O_CREAT | O_EXCL | O_WRONLY, 0666); 
+       written = write(fd, ref, len);
+       close(fd);
+       if (written != len) {
+               unlink(lockpath);
+               error("Unable to write to %s", lockpath);
+               return -2;
+       }
+       if (rename(lockpath, git_HEAD) < 0) {
+               unlink(lockpath);
+               error("Unable to create %s", git_HEAD);
+               return -3;
+       }
+       return 0;
+}
+
+int read_ref(const char *filename, unsigned char *sha1)
+{
+       if (resolve_ref(filename, sha1, 1))
+               return 0;
+       return -1;
 }
 
 static int do_for_each_ref(const char *base, int (*fn)(const char *path, const unsigned char *sha1))
@@ -54,7 +193,7 @@ static int do_for_each_ref(const char *base, int (*fn)(const char *path, const u
                                        break;
                                continue;
                        }
-                       if (read_ref(path, sha1) < 0)
+                       if (read_ref(git_path("%s", path), sha1) < 0)
                                continue;
                        if (!has_sha1_file(sha1))
                                continue;
@@ -71,7 +210,7 @@ static int do_for_each_ref(const char *base, int (*fn)(const char *path, const u
 int head_ref(int (*fn)(const char *path, const unsigned char *sha1))
 {
        unsigned char sha1[20];
-       if (!read_ref("HEAD", sha1))
+       if (!read_ref(git_path("HEAD"), sha1))
                return fn("HEAD", sha1);
        return 0;
 }
@@ -101,33 +240,14 @@ static char *ref_lock_file_name(const char *ref)
        return ret;
 }
 
-static int read_ref_file(const char *filename, unsigned char *sha1) {
-       int fd = open(filename, O_RDONLY);
-       char hex[41];
-       if (fd < 0) {
-               return error("Couldn't open %s\n", filename);
-       }
-       if ((read(fd, hex, 41) < 41) ||
-           (hex[40] != '\n') ||
-           get_sha1_hex(hex, sha1)) {
-               error("Couldn't read a hash from %s\n", filename);
-               close(fd);
-               return -1;
-       }
-       close(fd);
-       return 0;
-}
-
 int get_ref_sha1(const char *ref, unsigned char *sha1)
 {
-       char *filename;
-       int retval;
+       const char *filename;
+
        if (check_ref_format(ref))
                return -1;
-       filename = ref_file_name(ref);
-       retval = read_ref_file(filename, sha1);
-       free(filename);
-       return retval;
+       filename = git_path("refs/%s", ref);
+       return read_ref(filename, sha1);
 }
 
 static int lock_ref_file(const char *filename, const char *lock_filename,
@@ -140,7 +260,7 @@ static int lock_ref_file(const char *filename, const char *lock_filename,
                return error("Couldn't open lock file for %s: %s",
                             filename, strerror(errno));
        }
-       retval = read_ref_file(filename, current_sha1);
+       retval = read_ref(filename, current_sha1);
        if (old_sha1) {
                if (retval) {
                        close(fd);
@@ -216,17 +336,54 @@ int write_ref_sha1(const char *ref, int fd, const unsigned char *sha1)
        return retval;
 }
 
+/*
+ * Make sure "ref" is something reasonable to have under ".git/refs/";
+ * We do not like it if:
+ *
+ * - any path component of it begins with ".", or
+ * - it has double dots "..", or
+ * - it has ASCII control character, "~", "^", ":" or SP, anywhere, or
+ * - it ends with a "/".
+ */
+
+static inline int bad_ref_char(int ch)
+{
+       return (((unsigned) ch) <= ' ' ||
+               ch == '~' || ch == '^' || ch == ':');
+}
+
 int check_ref_format(const char *ref)
 {
-       char *middle;
-       if (ref[0] == '.' || ref[0] == '/')
-               return -1;
-       middle = strchr(ref, '/');
-       if (!middle || !middle[1])
-               return -1;
-       if (strchr(middle + 1, '/'))
-               return -1;
-       return 0;
+       int ch, level;
+       const char *cp = ref;
+
+       level = 0;
+       while (1) {
+               while ((ch = *cp++) == '/')
+                       ; /* tolerate duplicated slashes */
+               if (!ch)
+                       return -1; /* should not end with slashes */
+
+               /* we are at the beginning of the path component */
+               if (ch == '.' || bad_ref_char(ch))
+                       return -1;
+
+               /* scan the rest of the path component */
+               while ((ch = *cp++) != 0) {
+                       if (bad_ref_char(ch))
+                               return -1;
+                       if (ch == '/')
+                               break;
+                       if (ch == '.' && *cp == '.')
+                               return -1;
+               }
+               level++;
+               if (!ch) {
+                       if (level < 2)
+                               return -1; /* at least of form "heads/blah" */
+                       return 0;
+               }
+       }
 }
 
 int write_ref_sha1_unlocked(const char *ref, const unsigned char *sha1)
index e41d5a045c3192f4759aa5b2e4881f28f92ed68f..6e6ffde39600f048f5c365dd478256feba321db9 100644 (file)
@@ -1,30 +1,40 @@
 #include "cache.h"
+#include "refs.h"
 #include "tag.h"
 #include "commit.h"
 #include "tree.h"
 #include "blob.h"
 #include "epoch.h"
+#include "diff.h"
 
 #define SEEN           (1u << 0)
 #define INTERESTING    (1u << 1)
 #define COUNTED                (1u << 2)
 #define SHOWN          (1u << 3)
+#define TREECHANGE     (1u << 4)
 
 static const char rev_list_usage[] =
-       "git-rev-list [OPTION] commit-id <commit-id>\n"
-                     "  --max-count=nr\n"
-                     "  --max-age=epoch\n"
-                     "  --min-age=epoch\n"
-                     "  --parents\n"
-                     "  --bisect\n"
-                     "  --objects\n"
-                     "  --unpacked\n"
-                     "  --header\n"
-                     "  --pretty\n"
-                     "  --no-merges\n"
-                     "  --merge-order [ --show-breaks ]\n"
-                     "  --topo-order";
-
+"git-rev-list [OPTION] <commit-id>... [ -- paths... ]\n"
+"  limiting output:\n"
+"    --max-count=nr\n"
+"    --max-age=epoch\n"
+"    --min-age=epoch\n"
+"    --sparse\n"
+"    --no-merges\n"
+"    --all\n"
+"  ordering output:\n"
+"    --merge-order [ --show-breaks ]\n"
+"    --topo-order\n"
+"  formatting output:\n"
+"    --parents\n"
+"    --objects\n"
+"    --unpacked\n"
+"    --header | --pretty\n"
+"  special purpose:\n"
+"    --bisect"
+;
+
+static int dense = 1;
 static int unpacked = 0;
 static int bisect_list = 0;
 static int tag_objects = 0;
@@ -43,6 +53,7 @@ static int show_breaks = 0;
 static int stop_traversal = 0;
 static int topo_order = 0;
 static int no_merges = 0;
+static const char **paths = NULL;
 
 static void show_commit(struct commit *commit)
 {
@@ -76,6 +87,31 @@ static void show_commit(struct commit *commit)
        fflush(stdout);
 }
 
+static int rewrite_one(struct commit **pp)
+{
+       for (;;) {
+               struct commit *p = *pp;
+               if (p->object.flags & (TREECHANGE | UNINTERESTING))
+                       return 0;
+               if (!p->parents)
+                       return -1;
+               *pp = p->parents->item;
+       }
+}
+
+static void rewrite_parents(struct commit *commit)
+{
+       struct commit_list **pp = &commit->parents;
+       while (*pp) {
+               struct commit_list *parent = *pp;
+               if (rewrite_one(&parent->item) < 0) {
+                       *pp = parent->next;
+                       continue;
+               }
+               pp = &parent->next;
+       }
+}
+
 static int filter_commit(struct commit * commit)
 {
        if (stop_traversal && (commit->object.flags & BOUNDARY))
@@ -86,12 +122,17 @@ static int filter_commit(struct commit * commit)
                return CONTINUE;
        if (max_age != -1 && (commit->date < max_age)) {
                stop_traversal=1;
-               return merge_order?CONTINUE:STOP;
+               return CONTINUE;
        }
        if (max_count != -1 && !max_count--)
                return STOP;
        if (no_merges && (commit->parents && commit->parents->next))
                return CONTINUE;
+       if (paths && dense) {
+               if (!(commit->object.flags & TREECHANGE))
+                       return CONTINUE;
+               rewrite_parents(commit);
+       }
        return DO;
 }
 
@@ -194,7 +235,17 @@ static void show_commit_list(struct commit_list *list)
                die("unknown pending object %s (%s)", sha1_to_hex(obj->sha1), name);
        }
        while (objects) {
-               printf("%s %s\n", sha1_to_hex(objects->item->sha1), objects->name);
+               /* An object with name "foo\n0000000000000000000000000000000000000000"
+                * can be used confuse downstream git-pack-objects very badly.
+                */
+               const char *ep = strchr(objects->name, '\n');
+               if (ep) {
+                       printf("%s %.*s\n", sha1_to_hex(objects->item->sha1),
+                              (int) (ep - objects->name),
+                              objects->name);
+               }
+               else
+                       printf("%s %s\n", sha1_to_hex(objects->item->sha1), objects->name);
                objects = objects->next;
        }
 }
@@ -366,26 +417,198 @@ static void mark_edges_uninteresting(struct commit_list *list)
        }
 }
 
+static int is_different = 0;
+
+static void file_add_remove(struct diff_options *options,
+                   int addremove, unsigned mode,
+                   const unsigned char *sha1,
+                   const char *base, const char *path)
+{
+       is_different = 1;
+}
+
+static void file_change(struct diff_options *options,
+                unsigned old_mode, unsigned new_mode,
+                const unsigned char *old_sha1,
+                const unsigned char *new_sha1,
+                const char *base, const char *path)
+{
+       is_different = 1;
+}
+
+static struct diff_options diff_opt = {
+       .recursive = 1,
+       .add_remove = file_add_remove,
+       .change = file_change,
+};
+
+static int same_tree(struct tree *t1, struct tree *t2)
+{
+       is_different = 0;
+       if (diff_tree_sha1(t1->object.sha1, t2->object.sha1, "", &diff_opt) < 0)
+               return 0;
+       return !is_different;
+}
+
+static int same_tree_as_empty(struct tree *t1)
+{
+       int retval;
+       void *tree;
+       struct tree_desc empty, real;
+
+       if (!t1)
+               return 0;
+
+       tree = read_object_with_reference(t1->object.sha1, "tree", &real.size, NULL);
+       if (!tree)
+               return 0;
+       real.buf = tree;
+
+       empty.buf = "";
+       empty.size = 0;
+
+       is_different = 0;
+       retval = diff_tree(&empty, &real, "", &diff_opt);
+       free(tree);
+
+       return retval >= 0 && !is_different;
+}
+
+static struct commit *try_to_simplify_merge(struct commit *commit, struct commit_list *parent)
+{
+       if (!commit->tree)
+               return NULL;
+
+       while (parent) {
+               struct commit *p = parent->item;
+               parent = parent->next;
+               parse_commit(p);
+               if (!p->tree)
+                       continue;
+               if (same_tree(commit->tree, p->tree))
+                       return p;
+       }
+       return NULL;
+}
+
+static void add_parents_to_list(struct commit *commit, struct commit_list **list)
+{
+       struct commit_list *parent = commit->parents;
+
+       /*
+        * If the commit is uninteresting, don't try to
+        * prune parents - we want the maximal uninteresting
+        * set.
+        *
+        * Normally we haven't parsed the parent
+        * yet, so we won't have a parent of a parent
+        * here. However, it may turn out that we've
+        * reached this commit some other way (where it
+        * wasn't uninteresting), in which case we need
+        * to mark its parents recursively too..
+        */
+       if (commit->object.flags & UNINTERESTING) {
+               while (parent) {
+                       struct commit *p = parent->item;
+                       parent = parent->next;
+                       parse_commit(p);
+                       p->object.flags |= UNINTERESTING;
+                       if (p->parents)
+                               mark_parents_uninteresting(p);
+                       if (p->object.flags & SEEN)
+                               continue;
+                       p->object.flags |= SEEN;
+                       insert_by_date(p, list);
+               }
+               return;
+       }
+
+       /*
+        * Ok, the commit wasn't uninteresting. If it
+        * is a merge, try to find the parent that has
+        * no differences in the path set if one exists.
+        */
+       if (paths && parent && parent->next) {
+               struct commit *preferred;
+
+               preferred = try_to_simplify_merge(commit, parent);
+               if (preferred) {
+                       parent->item = preferred;
+                       parent->next = NULL;
+               }
+       }
+
+       while (parent) {
+               struct commit *p = parent->item;
+
+               parent = parent->next;
+
+               parse_commit(p);
+               if (p->object.flags & SEEN)
+                       continue;
+               p->object.flags |= SEEN;
+               insert_by_date(p, list);
+       }
+}
+
+static void compress_list(struct commit_list *list)
+{
+       while (list) {
+               struct commit *commit = list->item;
+               struct commit_list *parent = commit->parents;
+               list = list->next;
+
+               if (!parent) {
+                       if (!same_tree_as_empty(commit->tree))
+                               commit->object.flags |= TREECHANGE;
+                       continue;
+               }
+
+               /*
+                * Exactly one parent? Check if it leaves the tree
+                * unchanged
+                */
+               if (!parent->next) {
+                       struct tree *t1 = commit->tree;
+                       struct tree *t2 = parent->item->tree;
+                       if (!t1 || !t2 || same_tree(t1, t2))
+                               continue;
+               }
+               commit->object.flags |= TREECHANGE;
+       }
+}
+
 static struct commit_list *limit_list(struct commit_list *list)
 {
        struct commit_list *newlist = NULL;
        struct commit_list **p = &newlist;
        while (list) {
-               struct commit *commit = pop_most_recent_commit(&list, SEEN);
+               struct commit_list *entry = list;
+               struct commit *commit = list->item;
                struct object *obj = &commit->object;
 
+               list = list->next;
+               free(entry);
+
+               if (max_age != -1 && (commit->date < max_age))
+                       obj->flags |= UNINTERESTING;
                if (unpacked && has_sha1_pack(obj->sha1))
                        obj->flags |= UNINTERESTING;
+               add_parents_to_list(commit, &list);
                if (obj->flags & UNINTERESTING) {
                        mark_parents_uninteresting(commit);
                        if (everybody_uninteresting(list))
                                break;
                        continue;
                }
+               if (min_age != -1 && (commit->date > min_age))
+                       continue;
                p = &commit_list_insert(commit, p)->next;
        }
        if (tree_objects)
                mark_edges_uninteresting(newlist);
+       if (paths && dense)
+               compress_list(newlist);
        if (bisect_list)
                newlist = find_bisection(newlist);
        return newlist;
@@ -396,13 +619,10 @@ static void add_pending_object(struct object *obj, const char *name)
        add_object(obj, &pending_objects, name);
 }
 
-static struct commit *get_commit_reference(const char *name, unsigned int flags)
+static struct commit *get_commit_reference(const char *name, const unsigned char *sha1, unsigned int flags)
 {
-       unsigned char sha1[20];
        struct object *object;
 
-       if (get_sha1(name, sha1))
-               usage(rev_list_usage);
        object = parse_object(sha1);
        if (!object)
                die("bad object %s", name);
@@ -475,18 +695,35 @@ static void handle_one_commit(struct commit *com, struct commit_list **lst)
        commit_list_insert(com, lst);
 }
 
+/* for_each_ref() callback does not allow user data -- Yuck. */
+static struct commit_list **global_lst;
+
+static int include_one_commit(const char *path, const unsigned char *sha1)
+{
+       struct commit *com = get_commit_reference(path, sha1, 0);
+       handle_one_commit(com, global_lst);
+       return 0;
+}
+
+static void handle_all(struct commit_list **lst)
+{
+       global_lst = lst;
+       for_each_ref(include_one_commit);
+       global_lst = NULL;
+}
 
-int main(int argc, char **argv)
+int main(int argc, const char **argv)
 {
+       const char *prefix = setup_git_directory();
        struct commit_list *list = NULL;
        int i, limited = 0;
 
-       setup_git_directory();
        for (i = 1 ; i < argc; i++) {
                int flags;
-               char *arg = argv[i];
+               const char *arg = argv[i];
                char *dotdot;
                struct commit *commit;
+               unsigned char sha1[20];
 
                if (!strncmp(arg, "--max-count=", 12)) {
                        max_count = atoi(arg + 12);
@@ -494,10 +731,12 @@ int main(int argc, char **argv)
                }
                if (!strncmp(arg, "--max-age=", 10)) {
                        max_age = atoi(arg + 10);
+                       limited = 1;
                        continue;
                }
                if (!strncmp(arg, "--min-age=", 10)) {
                        min_age = atoi(arg + 10);
+                       limited = 1;
                        continue;
                }
                if (!strcmp(arg, "--header")) {
@@ -526,6 +765,10 @@ int main(int argc, char **argv)
                        bisect_list = 1;
                        continue;
                }
+               if (!strcmp(arg, "--all")) {
+                       handle_all(&list);
+                       continue;
+               }
                if (!strcmp(arg, "--objects")) {
                        tag_objects = 1;
                        tree_objects = 1;
@@ -550,6 +793,18 @@ int main(int argc, char **argv)
                        limited = 1;
                        continue;
                }
+               if (!strcmp(arg, "--dense")) {
+                       dense = 1;
+                       continue;
+               }
+               if (!strcmp(arg, "--sparse")) {
+                       dense = 0;
+                       continue;
+               }
+               if (!strcmp(arg, "--")) {
+                       i++;
+                       break;
+               }
 
                if (show_breaks && !merge_order)
                        usage(rev_list_usage);
@@ -557,15 +812,19 @@ int main(int argc, char **argv)
                flags = 0;
                dotdot = strstr(arg, "..");
                if (dotdot) {
+                       unsigned char from_sha1[20];
                        char *next = dotdot + 2;
-                       struct commit *exclude = NULL;
-                       struct commit *include = NULL;
                        *dotdot = 0;
                        if (!*next)
                                next = "HEAD";
-                       exclude = get_commit_reference(arg, UNINTERESTING);
-                       include = get_commit_reference(next, 0);
-                       if (exclude && include) {
+                       if (!get_sha1(arg, from_sha1) && !get_sha1(next, sha1)) {
+                               struct commit *exclude;
+                               struct commit *include;
+                               
+                               exclude = get_commit_reference(arg, from_sha1, UNINTERESTING);
+                               include = get_commit_reference(next, sha1, 0);
+                               if (!exclude || !include)
+                                       die("Invalid revision range %s..%s", arg, next);
                                limited = 1;
                                handle_one_commit(exclude, &list);
                                handle_one_commit(include, &list);
@@ -578,15 +837,31 @@ int main(int argc, char **argv)
                        arg++;
                        limited = 1;
                }
-               commit = get_commit_reference(arg, flags);
+               if (get_sha1(arg, sha1) < 0)
+                       break;
+               commit = get_commit_reference(arg, sha1, flags);
                handle_one_commit(commit, &list);
        }
 
+       if (!list)
+               usage(rev_list_usage);
+
+       paths = get_pathspec(prefix, argv + i);
+       if (paths) {
+               limited = 1;
+               diff_tree_setup_paths(paths);
+       }
+
        save_commit_buffer = verbose_header;
        track_object_refs = 0;
 
        if (!merge_order) {             
                sort_by_date(&list);
+               if (list && !limited && max_count == 1 &&
+                   !tag_objects && !tree_objects && !blob_objects) {
+                       show_commit(list->item);
+                       return 0;
+               }
                if (limited)
                        list = limit_list(list);
                if (topo_order)
index 0f5630f95c4dc9ac257ea6c3b2f0e178af667ae5..5a989825113b122d130448726c8eb97ffeb21ad1 100644 (file)
@@ -6,6 +6,7 @@
 #include "cache.h"
 #include "commit.h"
 #include "refs.h"
+#include "quote.h"
 
 #define DO_REVS                1
 #define DO_NOREV       2
@@ -31,7 +32,9 @@ static int revs_count = 0;
 static int is_rev_argument(const char *arg)
 {
        static const char *rev_args[] = {
+               "--all",
                "--bisect",
+               "--dense",
                "--header",
                "--max-age=",
                "--max-count=",
@@ -42,6 +45,7 @@ static int is_rev_argument(const char *arg)
                "--parents",
                "--pretty",
                "--show-breaks",
+               "--sparse",
                "--topo-order",
                "--unpacked",
                NULL
@@ -125,6 +129,37 @@ static int show_reference(const char *refname, const unsigned char *sha1)
        return 0;
 }
 
+static void show_datestring(const char *flag, const char *datestr)
+{
+       FILE *date;
+       static char buffer[100];
+       static char cmd[1000];
+       int len;
+
+       /* date handling requires both flags and revs */
+       if ((filter & (DO_FLAGS | DO_REVS)) != (DO_FLAGS | DO_REVS))
+               return;
+       len = strlen(flag);
+       memcpy(buffer, flag, len);
+
+       snprintf(cmd, sizeof(cmd), "date --date=%s +%%s", sq_quote(datestr));
+       date = popen(cmd, "r");
+       if (!date || !fgets(buffer + len, sizeof(buffer) - len, date))
+               die("git-rev-list: bad date string");
+       pclose(date);
+       len = strlen(buffer);
+       if (buffer[len-1] == '\n')
+               buffer[--len] = 0;
+       show(buffer);
+}
+
+static void show_file(const char *arg)
+{
+       show_default();
+       if ((filter & (DO_NONFLAGS|DO_NOREV)) == (DO_NONFLAGS|DO_NOREV))
+               show(arg);
+}
+
 int main(int argc, char **argv)
 {
        int i, as_is = 0, verify = 0;
@@ -136,12 +171,15 @@ int main(int argc, char **argv)
                char *dotdot;
        
                if (as_is) {
-                       show(arg);
+                       show_file(arg);
                        continue;
                }
                if (*arg == '-') {
                        if (!strcmp(arg, "--")) {
                                as_is = 1;
+                               /* Pass on the "--" if we show anything but files.. */
+                               if (filter & (DO_FLAGS | DO_REVS))
+                                       show_file(arg);
                                continue;
                        }
                        if (!strcmp(arg, "--default")) {
@@ -207,6 +245,22 @@ int main(int argc, char **argv)
                                printf("%s/.git\n", cwd);
                                continue;
                        }
+                       if (!strncmp(arg, "--since=", 8)) {
+                               show_datestring("--max-age=", arg+8);
+                               continue;
+                       }
+                       if (!strncmp(arg, "--after=", 8)) {
+                               show_datestring("--max-age=", arg+8);
+                               continue;
+                       }
+                       if (!strncmp(arg, "--before=", 9)) {
+                               show_datestring("--min-age=", arg+9);
+                               continue;
+                       }
+                       if (!strncmp(arg, "--until=", 8)) {
+                               show_datestring("--min-age=", arg+8);
+                               continue;
+                       }
                        if (verify)
                                die("Needed a single revision");
                        show_flag(arg);
@@ -240,9 +294,8 @@ int main(int argc, char **argv)
                }
                if (verify)
                        die("Needed a single revision");
-               if ((filter & (DO_NONFLAGS|DO_NOREV)) ==
-                   (DO_NONFLAGS|DO_NOREV))
-                       show(arg);
+               as_is = 1;
+               show_file(arg);
        }
        show_default();
        if (verify && revs_count != 1)
diff --git a/rev-tree.c b/rev-tree.c
deleted file mode 100644 (file)
index 7f92819..0000000
+++ /dev/null
@@ -1,140 +0,0 @@
-#include "cache.h"
-#include "commit.h"
-
-/*
- * revision.h leaves the low 16 bits of the "flags" field of the
- * revision data structure unused. We use it for a "reachable from
- * this commit <N>" bitmask.
- */
-#define MAX_COMMITS 16
-#define REACHABLE (1U << 16)
-
-#define cmit_flags(cmit) ((cmit)->object.flags & ~REACHABLE)
-
-static int show_edges = 0;
-static int basemask = 0;
-
-static void read_cache_file(const char *path)
-{
-       die("no revtree cache file yet");
-}
-
-/*
- * Some revisions are less interesting than others.
- *
- * For example, if we use a cache-file, that one may contain
- * revisions that were never used. They are never interesting.
- *
- * And sometimes we're only interested in "edge" commits, ie
- * places where the marking changes between parent and child.
- */
-static int interesting(struct commit *rev)
-{
-       unsigned mask = cmit_flags(rev);
-
-       if (!mask)
-               return 0;
-       if (show_edges) {
-               struct commit_list *p = rev->parents;
-               while (p) {
-                       if (mask != cmit_flags(p->item))
-                               return 1;
-                       p = p->next;
-               }
-               return 0;
-       }
-       if (mask & basemask)
-               return 0;
-
-       return 1;
-}
-
-/*
- * Usage: git-rev-tree [--edges] [--cache <cache-file>] <commit-id> [<commit-id2>]
- *
- * The cache-file can be quite important for big trees. This is an
- * expensive operation if you have to walk the whole chain of
- * parents in a tree with a long revision history.
- */
-int main(int argc, char **argv)
-{
-       int i;
-       int nr = 0;
-       unsigned char sha1[MAX_COMMITS][20];
-       struct commit_list *list = NULL;
-
-       /*
-        * First - pick up all the revisions we can (both from
-        * caches and from commit file chains).
-        */
-       for (i = 1; i < argc ; i++) {
-               char *arg = argv[i];
-               struct commit *commit;
-
-               if (!strcmp(arg, "--cache")) {
-                       read_cache_file(argv[++i]);
-                       continue;
-               }
-
-               if (!strcmp(arg, "--edges")) {
-                       show_edges = 1;
-                       continue;
-               }
-
-               if (arg[0] == '^') {
-                       arg++;
-                       basemask |= 1<<nr;
-               }
-               if (nr >= MAX_COMMITS || get_sha1(arg, sha1[nr]))
-                       usage("git-rev-tree [--edges] [--cache <cache-file>] <commit-id> [<commit-id>]");
-
-               commit = lookup_commit_reference(sha1[nr]);
-               if (!commit || parse_commit(commit) < 0)
-                       die("bad commit object");
-               commit_list_insert(commit, &list);
-               nr++;
-       }
-
-       /*
-        * Parse all the commits in date order.
-        *
-        * We really should stop once we know enough, but that's a
-        * decision that isn't trivial to make.
-        */
-       while (list)
-               pop_most_recent_commit(&list, REACHABLE);
-
-       /*
-        * Now we have the maximal tree. Walk the different sha files back to the root.
-        */
-       for (i = 0; i < nr; i++)
-               mark_reachable(&lookup_commit_reference(sha1[i])->object, 1 << i);
-
-       /*
-        * Now print out the results..
-        */
-       for (i = 0; i < nr_objs; i++) {
-               struct object *obj = objs[i];
-               struct commit *commit;
-               struct commit_list *p;
-
-               if (obj->type != commit_type)
-                       continue;
-
-               commit = (struct commit *) obj;
-
-               if (!interesting(commit))
-                       continue;
-
-               printf("%lu %s:%d", commit->date, sha1_to_hex(obj->sha1),
-                                   cmit_flags(commit));
-               p = commit->parents;
-               while (p) {
-                       printf(" %s:%d", sha1_to_hex(p->item->object.sha1), 
-                              cmit_flags(p->item));
-                       p = p->next;
-               }
-               printf("\n");
-       }
-       return 0;
-}
diff --git a/rsh.c b/rsh.c
index bad5cff2c287d3c7dd288308428b89e0cb12299d..d66526941fbe45f99e51babc2c55a63b4baa027c 100644 (file)
--- a/rsh.c
+++ b/rsh.c
@@ -1,52 +1,16 @@
-#include "rsh.h"
-
 #include <string.h>
 #include <sys/types.h>
 #include <sys/socket.h>
 
+#include "rsh.h"
+#include "quote.h"
 #include "cache.h"
 
 #define COMMAND_SIZE 4096
 
 /*
- * Write a shell-quoted version of a string into a buffer, and
- * return bytes that ought to be output excluding final null.
- */
-static int shell_quote(char *buf, int nmax, const char *str)
-{
-       char ch;
-       int nq;
-       int oc = 0;
-
-       while ( (ch = *str++) ) {
-               nq = 0;
-               if ( strchr(" !\"#$%&\'()*;<=>?[\\]^`{|}", ch) )
-                       nq = 1;
-
-               if ( nq ) {
-                       if ( nmax > 1 ) {
-                               *buf++ = '\\';
-                               nmax--;
-                       }
-                       oc++;
-               }
-
-               if ( nmax > 1 ) {
-                       *buf++ = ch;
-                       nmax--;
-               }
-               oc++;
-       }
-
-       if ( nmax )
-               *buf = '\0';
-
-       return oc;
-}
-                       
-/*
- * Append a string to a string buffer, with or without quoting.  Return true
- * if the buffer overflowed.
+ * Append a string to a string buffer, with or without shell quoting.
+ * Return true if the buffer overflowed.
  */
 static int add_to_string(char **ptrp, int *sizep, const char *str, int quote)
 {
@@ -56,7 +20,7 @@ static int add_to_string(char **ptrp, int *sizep, const char *str, int quote)
        int err = 0;
 
        if ( quote ) {
-               oc = shell_quote(p, size, str);
+               oc = sq_quote_buf(p, size, str);
        } else {
                oc = strlen(str);
                memcpy(p, str, (oc >= size) ? size-1 : oc);
@@ -104,13 +68,12 @@ int setup_connection(int *fd_in, int *fd_out, const char *remote_prog,
        if (!path) {
                return error("Bad URL: %s", url);
        }
-       /* $GIT_RSH <host> "env GIR_DIR=<path> <remote_prog> <args...>" */
+       /* $GIT_RSH <host> "env GIT_DIR=<path> <remote_prog> <args...>" */
        sizen = COMMAND_SIZE;
        posn = command;
        of = 0;
        of |= add_to_string(&posn, &sizen, "env ", 0);
-       of |= add_to_string(&posn, &sizen, GIT_DIR_ENVIRONMENT, 0);
-       of |= add_to_string(&posn, &sizen, "=", 0);
+       of |= add_to_string(&posn, &sizen, GIT_DIR_ENVIRONMENT "=", 0);
        of |= add_to_string(&posn, &sizen, path, 1);
        of |= add_to_string(&posn, &sizen, " ", 0);
        of |= add_to_string(&posn, &sizen, remote_prog, 1);
index 55d8ff7e102fa6cc369c993035c0579c2d33b775..3eeb18f7c7d32412003ea665a47081dc06d84c95 100644 (file)
@@ -126,12 +126,12 @@ static int ref_newer(const unsigned char *new_sha1,
        /* Both new and old must be commit-ish and new is descendant of
         * old.  Otherwise we require --force.
         */
-       o = deref_tag(parse_object(old_sha1));
+       o = deref_tag(parse_object(old_sha1), NULL, 0);
        if (!o || o->type != commit_type)
                return 0;
        old = (struct commit *) o;
 
-       o = deref_tag(parse_object(new_sha1));
+       o = deref_tag(parse_object(new_sha1), NULL, 0);
        if (!o || o->type != commit_type)
                return 0;
        new = (struct commit *) o;
@@ -181,7 +181,7 @@ static int send_pack(int in, int out, int nr_refspec, char **refspec)
        int new_refs;
 
        /* No funny business with the matcher */
-       remote_tail = get_remote_heads(in, &remote_refs, 0, NULL);
+       remote_tail = get_remote_heads(in, &remote_refs, 0, NULL, 1);
        get_local_heads();
 
        /* match them up */
index a9e5607f2f5668411e5742243372bf570c2bda72..0cba8e19f7582fa5771ed6ab94a76e1d87efc53a 100644 (file)
@@ -9,7 +9,15 @@ static FILE *info_ref_fp;
 
 static int add_info_ref(const char *path, const unsigned char *sha1)
 {
+       struct object *o = parse_object(sha1);
+
        fprintf(info_ref_fp, "%s        %s\n", sha1_to_hex(sha1), path);
+       if (o->type == tag_type) {
+               o = deref_tag(o, path, 0);
+               if (o)
+                       fprintf(info_ref_fp, "%s        %s^{}\n",
+                               sha1_to_hex(o->sha1), path);
+       }
        return 0;
 }
 
@@ -59,6 +67,16 @@ static struct object *parse_object_cheap(const unsigned char *sha1)
                struct commit *commit = (struct commit *)o;
                free(commit->buffer);
                commit->buffer = NULL;
+       } else if (o->type == tree_type) {
+               struct tree *tree = (struct tree *)o;
+               struct tree_entry_list *e, *n;
+               for (e = tree->entries; e; e = n) {
+                       free(e->name);
+                       e->name = NULL;
+                       n = e->next;
+                       free(e);
+               }
+               tree->entries = NULL;
        }
        return o;
 }
diff --git a/setup.c b/setup.c
index 3973668f017e209e714950f4175d26f8a409cce3..c487d7eb9de70c651ef06a6bfd4a7571337fad84 100644 (file)
--- a/setup.c
+++ b/setup.c
@@ -1,8 +1,8 @@
 #include "cache.h"
 
-char *prefix_path(const char *prefix, int len, char *path)
+const char *prefix_path(const char *prefix, int len, const char *path)
 {
-       char *orig = path;
+       const char *orig = path;
        for (;;) {
                char c;
                if (*path != '.')
@@ -47,10 +47,10 @@ char *prefix_path(const char *prefix, int len, char *path)
        return path;
 }
 
-const char **get_pathspec(const char *prefix, char **pathspec)
+const char **get_pathspec(const char *prefix, const char **pathspec)
 {
-       char *entry = *pathspec;
-       char **p;
+       const char *entry = *pathspec;
+       const char **p;
        int prefixlen;
 
        if (!prefix && !entry)
@@ -76,18 +76,20 @@ const char **get_pathspec(const char *prefix, char **pathspec)
  * Test it it looks like we're at the top
  * level git directory. We want to see a
  *
- *  - a HEAD symlink and a refs/ directory under ".git"
  *  - either a .git/objects/ directory _or_ the proper
  *    GIT_OBJECT_DIRECTORY environment variable
+ *  - a refs/ directory under ".git"
+ *  - either a HEAD symlink or a HEAD file that is formatted as
+ *    a proper "ref:".
  */
 static int is_toplevel_directory(void)
 {
-       struct stat st;
-
-       return  !lstat(".git/HEAD", &st) &&
-               S_ISLNK(st.st_mode) &&
-               !access(".git/refs/", X_OK) &&
-               (getenv(DB_ENVIRONMENT) || !access(".git/objects/", X_OK));
+       if (access(".git/refs/", X_OK) ||
+           access(getenv(DB_ENVIRONMENT) ?
+                  getenv(DB_ENVIRONMENT) : ".git/objects/", X_OK) ||
+           validate_symref(".git/HEAD"))
+               return 0;
+       return 1;
 }
 
 const char *setup_git_directory(void)
index 66382027816e8ec3a97c40f6849c62454f2ad0f2..946a35346bdaf21432b7f463712f5bf8881fee39 100644 (file)
@@ -20,6 +20,8 @@
 #endif
 #endif
 
+const unsigned char null_sha1[20] = { 0, };
+
 static unsigned int sha1_file_open_flag = O_NOATIME;
 
 static unsigned hexval(char c)
@@ -46,61 +48,11 @@ int get_sha1_hex(const char *hex, unsigned char *sha1)
        return 0;
 }
 
-static char *git_dir, *git_object_dir, *git_index_file, *git_refs_dir,
-       *git_graft_file;
-static void setup_git_env(void)
-{
-       git_dir = getenv(GIT_DIR_ENVIRONMENT);
-       if (!git_dir)
-               git_dir = DEFAULT_GIT_DIR_ENVIRONMENT;
-       git_object_dir = getenv(DB_ENVIRONMENT);
-       if (!git_object_dir) {
-               git_object_dir = xmalloc(strlen(git_dir) + 9);
-               sprintf(git_object_dir, "%s/objects", git_dir);
-       }
-       git_refs_dir = xmalloc(strlen(git_dir) + 6);
-       sprintf(git_refs_dir, "%s/refs", git_dir);
-       git_index_file = getenv(INDEX_ENVIRONMENT);
-       if (!git_index_file) {
-               git_index_file = xmalloc(strlen(git_dir) + 7);
-               sprintf(git_index_file, "%s/index", git_dir);
-       }
-       git_graft_file = getenv(GRAFT_ENVIRONMENT);
-       if (!git_graft_file)
-               git_graft_file = strdup(git_path("info/grafts"));
-}
-
-char *get_object_directory(void)
-{
-       if (!git_object_dir)
-               setup_git_env();
-       return git_object_dir;
-}
-
-char *get_refs_directory(void)
-{
-       if (!git_refs_dir)
-               setup_git_env();
-       return git_refs_dir;
-}
-
-char *get_index_file(void)
-{
-       if (!git_index_file)
-               setup_git_env();
-       return git_index_file;
-}
-
-char *get_graft_file(void)
-{
-       if (!git_graft_file)
-               setup_git_env();
-       return git_graft_file;
-}
-
 int safe_create_leading_directories(char *path)
 {
        char *pos = path;
+       if (*pos == '/')
+               pos++;
 
        while (pos) {
                pos = strchr(pos, '/');
@@ -466,7 +418,7 @@ int use_packed_git(struct packed_git *p)
        return 0;
 }
 
-struct packed_git *add_packed_git(char *path, int path_len)
+struct packed_git *add_packed_git(char *path, int path_len, int local)
 {
        struct stat st;
        struct packed_git *p;
@@ -494,6 +446,7 @@ struct packed_git *add_packed_git(char *path, int path_len)
        p->pack_base = NULL;
        p->pack_last_used = 0;
        p->pack_use_cnt = 0;
+       p->pack_local = local;
        return p;
 }
 
@@ -534,7 +487,7 @@ void install_packed_git(struct packed_git *pack)
        packed_git = pack;
 }
 
-static void prepare_packed_git_one(char *objdir)
+static void prepare_packed_git_one(char *objdir, int local)
 {
        char path[PATH_MAX];
        int len;
@@ -556,7 +509,7 @@ static void prepare_packed_git_one(char *objdir)
 
                /* we have .idx.  Is it a file we can map? */
                strcpy(path + len, de->d_name);
-               p = add_packed_git(path, len + namelen);
+               p = add_packed_git(path, len + namelen, local);
                if (!p)
                        continue;
                p->next = packed_git;
@@ -572,11 +525,11 @@ void prepare_packed_git(void)
 
        if (run_once)
                return;
-       prepare_packed_git_one(get_object_directory());
+       prepare_packed_git_one(get_object_directory(), 1);
        prepare_alt_odb();
        for (alt = alt_odb_list; alt; alt = alt->next) {
                alt->name[0] = 0;
-               prepare_packed_git_one(alt->base);
+               prepare_packed_git_one(alt->base, 0);
        }
        run_once = 1;
 }
@@ -864,7 +817,8 @@ void packed_object_info_detail(struct pack_entry *e,
                strcpy(type, "tag");
                break;
        default:
-               die("corrupted pack file");
+               die("corrupted pack file %s containing object of kind %d",
+                   p->pack_name, kind);
        }
        *store_size = 0; /* notyet */
 }
@@ -903,7 +857,8 @@ static int packed_object_info(struct pack_entry *entry,
                strcpy(type, "tag");
                break;
        default:
-               die("corrupted pack file");
+               die("corrupted pack file %s containing object of kind %d",
+                   p->pack_name, kind);
        }
        if (sizep)
                *sizep = size;
@@ -1003,7 +958,7 @@ static void *unpack_entry(struct pack_entry *entry,
        retval = unpack_entry_gently(entry, type, sizep);
        unuse_packed_git(p);
        if (!retval)
-               die("corrupted pack file");
+               die("corrupted pack file %s", p->pack_name);
        return retval;
 }
 
@@ -1237,6 +1192,77 @@ char *write_sha1_file_prepare(void *buf,
        return sha1_file_name(sha1);
 }
 
+/*
+ * Link the tempfile to the final place, possibly creating the
+ * last directory level as you do so.
+ *
+ * Returns the errno on failure, 0 on success.
+ */
+static int link_temp_to_file(const char *tmpfile, char *filename)
+{
+       int ret;
+
+       if (!link(tmpfile, filename))
+               return 0;
+
+       /*
+        * Try to mkdir the last path component if that failed
+        * with an ENOENT.
+        *
+        * Re-try the "link()" regardless of whether the mkdir
+        * succeeds, since a race might mean that somebody
+        * else succeeded.
+        */
+       ret = errno;
+       if (ret == ENOENT) {
+               char *dir = strrchr(filename, '/');
+               if (dir) {
+                       *dir = 0;
+                       mkdir(filename, 0777);
+                       *dir = '/';
+                       if (!link(tmpfile, filename))
+                               return 0;
+                       ret = errno;
+               }
+       }
+       return ret;
+}
+
+/*
+ * Move the just written object into its final resting place
+ */
+int move_temp_to_file(const char *tmpfile, char *filename)
+{
+       int ret = link_temp_to_file(tmpfile, filename);
+
+       /*
+        * Coda hack - coda doesn't like cross-directory links,
+        * so we fall back to a rename, which will mean that it
+        * won't be able to check collisions, but that's not a
+        * big deal.
+        *
+        * The same holds for FAT formatted media.
+        *
+        * When this succeeds, we just return 0. We have nothing
+        * left to unlink.
+        */
+       if (ret && ret != EEXIST) {
+               if (!rename(tmpfile, filename))
+                       return 0;
+               ret = errno;
+       }
+       unlink(tmpfile);
+       if (ret) {
+               if (ret != EEXIST) {
+                       fprintf(stderr, "unable to write sha1 filename %s: %s", filename, strerror(ret));
+                       return -1;
+               }
+               /* FIXME!!! Collision check here ? */
+       }
+
+       return 0;
+}
+
 int write_sha1_file(void *buf, unsigned long len, const char *type, unsigned char *returnsha1)
 {
        int size;
@@ -1246,7 +1272,7 @@ int write_sha1_file(void *buf, unsigned long len, const char *type, unsigned cha
        char *filename;
        static char tmpfile[PATH_MAX];
        unsigned char hdr[50];
-       int fd, hdrlen, ret;
+       int fd, hdrlen;
 
        /* Normally if we have it in the pack then we do not bother writing
         * it out into .git/objects/??/?{38} file.
@@ -1309,32 +1335,7 @@ int write_sha1_file(void *buf, unsigned long len, const char *type, unsigned cha
        close(fd);
        free(compressed);
 
-       ret = link(tmpfile, filename);
-       if (ret < 0) {
-               ret = errno;
-
-               /*
-                * Coda hack - coda doesn't like cross-directory links,
-                * so we fall back to a rename, which will mean that it
-                * won't be able to check collisions, but that's not a
-                * big deal.
-                *
-                * When this succeeds, we just return 0. We have nothing
-                * left to unlink.
-                */
-               if (ret == EXDEV && !rename(tmpfile, filename))
-                       return 0;
-       }
-       unlink(tmpfile);
-       if (ret) {
-               if (ret != EEXIST) {
-                       fprintf(stderr, "unable to write sha1 filename %s: %s", filename, strerror(ret));
-                       return -1;
-               }
-               /* FIXME!!! Collision check here ? */
-       }
-
-       return 0;
+       return move_temp_to_file(tmpfile, filename);
 }
 
 int write_sha1_to_fd(int fd, const unsigned char *sha1)
@@ -1409,8 +1410,7 @@ int write_sha1_to_fd(int fd, const unsigned char *sha1)
 int write_sha1_from_fd(const unsigned char *sha1, int fd, char *buffer,
                       size_t bufsize, size_t *bufposn)
 {
-       char *filename = sha1_file_name(sha1);
-
+       char tmpfile[PATH_MAX];
        int local;
        z_stream stream;
        unsigned char real_sha1[20];
@@ -1418,10 +1418,11 @@ int write_sha1_from_fd(const unsigned char *sha1, int fd, char *buffer,
        int ret;
        SHA_CTX c;
 
-       local = open(filename, O_WRONLY | O_CREAT | O_EXCL, 0666);
+       snprintf(tmpfile, sizeof(tmpfile), "%s/obj_XXXXXX", get_object_directory());
 
+       local = mkstemp(tmpfile);
        if (local < 0)
-               return error("Couldn't open %s\n", filename);
+               return error("Couldn't open %s for %s\n", tmpfile, sha1_to_hex(sha1));
 
        memset(&stream, 0, sizeof(stream));
 
@@ -1451,7 +1452,7 @@ int write_sha1_from_fd(const unsigned char *sha1, int fd, char *buffer,
                size = read(fd, buffer + *bufposn, bufsize - *bufposn);
                if (size <= 0) {
                        close(local);
-                       unlink(filename);
+                       unlink(tmpfile);
                        if (!size)
                                return error("Connection closed?");
                        perror("Reading from connection");
@@ -1464,15 +1465,15 @@ int write_sha1_from_fd(const unsigned char *sha1, int fd, char *buffer,
        close(local);
        SHA1_Final(real_sha1, &c);
        if (ret != Z_STREAM_END) {
-               unlink(filename);
+               unlink(tmpfile);
                return error("File %s corrupted", sha1_to_hex(sha1));
        }
        if (memcmp(sha1, real_sha1, 20)) {
-               unlink(filename);
+               unlink(tmpfile);
                return error("File %s has bad hash\n", sha1_to_hex(sha1));
        }
-       
-       return 0;
+
+       return move_temp_to_file(tmpfile, sha1_file_name(sha1));
 }
 
 int has_pack_index(const unsigned char *sha1)
@@ -1534,3 +1535,42 @@ int index_fd(unsigned char *sha1, int fd, struct stat *st, int write_object, con
                munmap(buf, size);
        return ret;
 }
+
+int index_path(unsigned char *sha1, const char *path, struct stat *st, int write_object)
+{
+       int fd;
+       char *target;
+
+       switch (st->st_mode & S_IFMT) {
+       case S_IFREG:
+               fd = open(path, O_RDONLY);
+               if (fd < 0)
+                       return error("open(\"%s\"): %s", path,
+                                    strerror(errno));
+               if (index_fd(sha1, fd, st, write_object, NULL) < 0)
+                       return error("%s: failed to insert into database",
+                                    path);
+               break;
+       case S_IFLNK:
+               target = xmalloc(st->st_size+1);
+               if (readlink(path, target, st->st_size+1) != st->st_size) {
+                       char *errstr = strerror(errno);
+                       free(target);
+                       return error("readlink(\"%s\"): %s", path,
+                                    errstr);
+               }
+               if (!write_object) {
+                       unsigned char hdr[50];
+                       int hdrlen;
+                       write_sha1_file_prepare(target, st->st_size, "blob",
+                                               sha1, hdr, &hdrlen);
+               } else if (write_sha1_file(target, st->st_size, "blob", sha1))
+                       return error("%s: failed to insert into database",
+                                    path);
+               free(target);
+               break;
+       default:
+               return error("%s: unsupported file type", path);
+       }
+       return 0;
+}
index b4fed924f7ac95571904874fffe8012940f9febb..be1755a70b2c53fc102db41a62e6099fa0adac1b 100644 (file)
@@ -1,33 +1,54 @@
 #include "cache.h"
+#include "tag.h"
 #include "commit.h"
+#include "tree.h"
+#include "blob.h"
 
 static int find_short_object_filename(int len, const char *name, unsigned char *sha1)
 {
-       static char dirname[PATH_MAX];
+       struct alternate_object_database *alt;
        char hex[40];
-       DIR *dir;
-       int found;
+       int found = 0;
+       static struct alternate_object_database *fakeent;
+
+       if (!fakeent) {
+               const char *objdir = get_object_directory();
+               int objdir_len = strlen(objdir);
+               int entlen = objdir_len + 43;
+               fakeent = xmalloc(sizeof(*fakeent) + entlen);
+               memcpy(fakeent->base, objdir, objdir_len);
+               fakeent->name = fakeent->base + objdir_len + 1;
+               fakeent->name[-1] = '/';
+       }
+       fakeent->next = alt_odb_list;
 
-       snprintf(dirname, sizeof(dirname), "%s/%.2s", get_object_directory(), name);
-       dir = opendir(dirname);
        sprintf(hex, "%.2s", name);
-       found = 0;
-       if (dir) {
+       for (alt = fakeent; alt && found < 2; alt = alt->next) {
                struct dirent *de;
+               DIR *dir;
+               sprintf(alt->name, "%.2s/", name);
+               dir = opendir(alt->base);
+               if (!dir)
+                       continue;
                while ((de = readdir(dir)) != NULL) {
                        if (strlen(de->d_name) != 38)
                                continue;
-                       if (memcmp(de->d_name, name + 2, len-2))
+                       if (memcmp(de->d_name, name + 2, len - 2))
                                continue;
-                       memcpy(hex + 2, de->d_name, 38);
-                       if (++found > 1)
+                       if (!found) {
+                               memcpy(hex + 2, de->d_name, 38);
+                               found++;
+                       }
+                       else if (memcmp(hex + 2, de->d_name, 38)) {
+                               found = 2;
                                break;
+                       }
                }
                closedir(dir);
        }
        if (found == 1)
                return get_sha1_hex(hex, sha1) == 0;
-       return 0;
+       return found;
 }
 
 static int match_sha(unsigned len, const unsigned char *a, const unsigned char *b)
@@ -48,9 +69,11 @@ static int match_sha(unsigned len, const unsigned char *a, const unsigned char *
 static int find_short_packed_object(int len, const unsigned char *match, unsigned char *sha1)
 {
        struct packed_git *p;
+       unsigned char found_sha1[20];
+       int found = 0;
 
        prepare_packed_git();
-       for (p = packed_git; p; p = p->next) {
+       for (p = packed_git; p && found < 2; p = p->next) {
                unsigned num = num_packed_objects(p);
                unsigned first = 0, last = num;
                while (first < last) {
@@ -74,19 +97,61 @@ static int find_short_packed_object(int len, const unsigned char *match, unsigne
                        unsigned char now[20], next[20];
                        nth_packed_object_sha1(p, first, now);
                        if (match_sha(len, match, now)) {
-                               if (nth_packed_object_sha1(p, first+1, next) || !match_sha(len, match, next)) {
-                                       memcpy(sha1, now, 20);
-                                       return 1;
+                               if (nth_packed_object_sha1(p, first+1, next) ||
+                                   !match_sha(len, match, next)) {
+                                       /* unique within this pack */
+                                       if (!found) {
+                                               memcpy(found_sha1, now, 20);
+                                               found++;
+                                       }
+                                       else if (memcmp(found_sha1, now, 20)) {
+                                               found = 2;
+                                               break;
+                                       }
+                               }
+                               else {
+                                       /* not even unique within this pack */
+                                       found = 2;
+                                       break;
                                }
                        }
                }
        }
+       if (found == 1)
+               memcpy(sha1, found_sha1, 20);
+       return found;
+}
+
+#define SHORT_NAME_NOT_FOUND (-1)
+#define SHORT_NAME_AMBIGUOUS (-2)
+
+static int find_unique_short_object(int len, char *canonical,
+                                   unsigned char *res, unsigned char *sha1)
+{
+       int has_unpacked, has_packed;
+       unsigned char unpacked_sha1[20], packed_sha1[20];
+
+       has_unpacked = find_short_object_filename(len, canonical, unpacked_sha1);
+       has_packed = find_short_packed_object(len, res, packed_sha1);
+       if (!has_unpacked && !has_packed)
+               return SHORT_NAME_NOT_FOUND;
+       if (1 < has_unpacked || 1 < has_packed)
+               return SHORT_NAME_AMBIGUOUS;
+       if (has_unpacked != has_packed) {
+               memcpy(sha1, (has_packed ? packed_sha1 : unpacked_sha1), 20);
+               return 0;
+       }
+       /* Both have unique ones -- do they match? */
+       if (memcmp(packed_sha1, unpacked_sha1, 20))
+               return -2;
+       memcpy(sha1, packed_sha1, 20);
        return 0;
 }
 
-static int get_short_sha1(const char *name, int len, unsigned char *sha1)
+static int get_short_sha1(const char *name, int len, unsigned char *sha1,
+                         int quietly)
 {
-       int i;
+       int i, status;
        char canonical[40];
        unsigned char res[20];
 
@@ -112,26 +177,53 @@ static int get_short_sha1(const char *name, int len, unsigned char *sha1)
                        val <<= 4;
                res[i >> 1] |= val;
        }
-       if (find_short_object_filename(i, canonical, sha1))
-               return 0;
-       if (find_short_packed_object(i, res, sha1))
-               return 0;
-       return -1;
+
+       status = find_unique_short_object(i, canonical, res, sha1);
+       if (!quietly && (status == SHORT_NAME_AMBIGUOUS))
+               return error("short SHA1 %.*s is ambiguous.", len, canonical);
+       return status;
 }
 
-static int get_sha1_file(const char *path, unsigned char *result)
+const char *find_unique_abbrev(const unsigned char *sha1, int len)
 {
-       char buffer[60];
-       int fd = open(path, O_RDONLY);
-       int len;
+       int status;
+       static char hex[41];
+       memcpy(hex, sha1_to_hex(sha1), 40);
+       while (len < 40) {
+               unsigned char sha1_ret[20];
+               status = get_short_sha1(hex, len, sha1_ret, 1);
+               if (!status) {
+                       hex[len] = 0;
+                       return hex;
+               }
+               if (status != SHORT_NAME_AMBIGUOUS)
+                       return NULL;
+               len++;
+       }
+       return NULL;
+}
 
-       if (fd < 0)
-               return -1;
-       len = read(fd, buffer, sizeof(buffer));
-       close(fd);
-       if (len < 40)
-               return -1;
-       return get_sha1_hex(buffer, result);
+static int ambiguous_path(const char *path)
+{
+       int slash = 1;
+
+       for (;;) {
+               switch (*path++) {
+               case '\0':
+                       break;
+               case '/':
+                       if (slash)
+                               break;
+                       slash = 1;
+                       continue;
+               case '.':
+                       continue;
+               default:
+                       slash = 0;
+                       continue;
+               }
+               return slash;
+       }
 }
 
 static int get_sha1_basic(const char *str, int len, unsigned char *sha1)
@@ -148,9 +240,13 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1)
        if (len == 40 && !get_sha1_hex(str, sha1))
                return 0;
 
+       /* Accept only unambiguous ref paths. */
+       if (ambiguous_path(str))
+               return -1;
+
        for (p = prefix; *p; p++) {
                char *pathname = git_path("%s/%.*s", *p, len, str);
-               if (!get_sha1_file(pathname, sha1))
+               if (!read_ref(pathname, sha1))
                        return 0;
        }
 
@@ -208,6 +304,84 @@ static int get_nth_ancestor(const char *name, int len,
        return 0;
 }
 
+static int peel_onion(const char *name, int len, unsigned char *sha1)
+{
+       unsigned char outer[20];
+       const char *sp;
+       const char *type_string = NULL;
+       struct object *o;
+
+       /*
+        * "ref^{type}" dereferences ref repeatedly until you cannot
+        * dereference anymore, or you get an object of given type,
+        * whichever comes first.  "ref^{}" means just dereference
+        * tags until you get a non-tag.  "ref^0" is a shorthand for
+        * "ref^{commit}".  "commit^{tree}" could be used to find the
+        * top-level tree of the given commit.
+        */
+       if (len < 4 || name[len-1] != '}')
+               return -1;
+
+       for (sp = name + len - 1; name <= sp; sp--) {
+               int ch = *sp;
+               if (ch == '{' && name < sp && sp[-1] == '^')
+                       break;
+       }
+       if (sp <= name)
+               return -1;
+
+       sp++; /* beginning of type name, or closing brace for empty */
+       if (!strncmp(commit_type, sp, 6) && sp[6] == '}')
+               type_string = commit_type;
+       else if (!strncmp(tree_type, sp, 4) && sp[4] == '}')
+               type_string = tree_type;
+       else if (!strncmp(blob_type, sp, 4) && sp[4] == '}')
+               type_string = blob_type;
+       else if (sp[0] == '}')
+               type_string = NULL;
+       else
+               return -1;
+
+       if (get_sha1_1(name, sp - name - 2, outer))
+               return -1;
+
+       o = parse_object(outer);
+       if (!o)
+               return -1;
+       if (!type_string) {
+               o = deref_tag(o, name, sp - name - 2);
+               if (!o || (!o->parsed && !parse_object(o->sha1)))
+                       return -1;
+               memcpy(sha1, o->sha1, 20);
+       }
+       else {
+               /* At this point, the syntax look correct, so
+                * if we do not get the needed object, we should
+                * barf.
+                */
+
+               while (1) {
+                       if (!o || (!o->parsed && !parse_object(o->sha1)))
+                               return -1;
+                       if (o->type == type_string) {
+                               memcpy(sha1, o->sha1, 20);
+                               return 0;
+                       }
+                       if (o->type == tag_type)
+                               o = ((struct tag*) o)->tagged;
+                       else if (o->type == commit_type)
+                               o = &(((struct commit *) o)->tree->object);
+                       else
+                               return error("%.*s: expected %s type, but the object dereferences to %s type",
+                                            len, name, type_string,
+                                            o->type);
+                       if (!o->parsed)
+                               parse_object(o->sha1);
+               }
+       }
+       return 0;
+}
+
 static int get_sha1_1(const char *name, int len, unsigned char *sha1)
 {
        int parent, ret;
@@ -249,10 +423,14 @@ static int get_sha1_1(const char *name, int len, unsigned char *sha1)
                return get_nth_ancestor(name, len1, sha1, parent);
        }
 
+       ret = peel_onion(name, len, sha1);
+       if (!ret)
+               return 0;
+
        ret = get_sha1_basic(name, len, sha1);
        if (!ret)
                return 0;
-       return get_short_sha1(name, len, sha1);
+       return get_short_sha1(name, len, sha1, 0);
 }
 
 /*
@@ -261,5 +439,6 @@ static int get_sha1_1(const char *name, int len, unsigned char *sha1)
  */
 int get_sha1(const char *name, unsigned char *sha1)
 {
+       prepare_alt_odb();
        return get_sha1_1(name, strlen(name), sha1);
 }
diff --git a/shell.c b/shell.c
new file mode 100644 (file)
index 0000000..2c4789e
--- /dev/null
+++ b/shell.c
@@ -0,0 +1,59 @@
+#include "cache.h"
+#include "quote.h"
+
+static int do_generic_cmd(const char *me, char *arg)
+{
+       const char *my_argv[4];
+
+       arg = sq_dequote(arg);
+       if (!arg)
+               die("bad argument");
+
+       my_argv[0] = me;
+       my_argv[1] = arg;
+       my_argv[2] = NULL;
+
+       return execvp(me, (char**) my_argv);
+}
+
+static struct commands {
+       const char *name;
+       int (*exec)(const char *me, char *arg);
+} cmd_list[] = {
+       { "git-receive-pack", do_generic_cmd },
+       { "git-upload-pack", do_generic_cmd },
+       { NULL },
+};
+
+int main(int argc, char **argv)
+{
+       char *prog;
+       struct commands *cmd;
+
+       /* We want to see "-c cmd args", and nothing else */
+       if (argc != 3 || strcmp(argv[1], "-c"))
+               die("What do you think I am? A shell?");
+
+       prog = argv[2];
+       argv += 2;
+       argc -= 2;
+       for (cmd = cmd_list ; cmd->name ; cmd++) {
+               int len = strlen(cmd->name);
+               char *arg;
+               if (strncmp(cmd->name, prog, len))
+                       continue;
+               arg = NULL;
+               switch (prog[len]) {
+               case '\0':
+                       arg = NULL;
+                       break;
+               case ' ':
+                       arg = prog + len + 1;
+                       break;
+               default:
+                       continue;
+               }
+               exit(cmd->exec(cmd->name, arg));
+       }
+       die("unrecognized command '%s'", prog);
+}
index 5778a594f44bd5fe06cd4b69eb37b3007a551385..70120005be00bb38feb6d56740a9340f2f353373 100644 (file)
@@ -133,25 +133,28 @@ static void name_commits(struct commit_list *list,
                        nth = 0;
                        while (parents) {
                                struct commit *p = parents->item;
-                               char newname[1000];
+                               char newname[1000], *en;
                                parents = parents->next;
                                nth++;
                                if (p->object.util)
                                        continue;
+                               en = newname;
                                switch (n->generation) {
                                case 0:
-                                       sprintf(newname, "%s^%d",
-                                               n->head_name, nth);
+                                       en += sprintf(en, "%s", n->head_name);
                                        break;
                                case 1:
-                                       sprintf(newname, "%s^^%d",
-                                               n->head_name, nth);
+                                       en += sprintf(en, "%s^", n->head_name);
                                        break;
                                default:
-                                       sprintf(newname, "%s~%d^%d",
-                                               n->head_name, n->generation,
-                                               nth);
+                                       en += sprintf(en, "%s~%d",
+                                               n->head_name, n->generation);
+                                       break;
                                }
+                               if (nth == 1)
+                                       en += sprintf(en, "^");
+                               else
+                                       en += sprintf(en, "^%d", nth);
                                name_commit(p, strdup(newname), 0);
                                i++;
                                name_first_parent_chain(p);
@@ -205,7 +208,7 @@ static void join_revs(struct commit_list **list_p,
        }
 }
 
-static void show_one_commit(struct commit *commit)
+static void show_one_commit(struct commit *commit, int no_name)
 {
        char pretty[128], *cp;
        struct commit_name *name = commit->object.util;
@@ -218,11 +221,21 @@ static void show_one_commit(struct commit *commit)
                cp = pretty + 8;
        else
                cp = pretty;
-       if (name && name->head_name) {
-               printf("[%s", name->head_name);
-               if (name->generation)
-                       printf("~%d", name->generation);
-               printf("] ");
+
+       if (!no_name) {
+               if (name && name->head_name) {
+                       printf("[%s", name->head_name);
+                       if (name->generation) {
+                               if (name->generation == 1)
+                                       printf("^");
+                               else
+                                       printf("~%d", name->generation);
+                       }
+                       printf("] ");
+               }
+               else
+                       printf("[%s] ",
+                              find_unique_abbrev(commit->object.sha1, 7));
        }
        puts(cp);
 }
@@ -247,7 +260,7 @@ static int append_ref(const char *refname, const unsigned char *sha1)
        struct commit *commit = lookup_commit_reference_gently(sha1, 1);
        if (!commit)
                return 0;
-       if (MAX_REVS < ref_name_cnt) {
+       if (MAX_REVS <= ref_name_cnt) {
                fprintf(stderr, "warning: ignoring %s; "
                        "cannot handle more than %d refs",
                        refname, MAX_REVS);
@@ -349,11 +362,13 @@ int main(int ac, char **av)
        int all_heads = 0, all_tags = 0;
        int all_mask, all_revs, shown_merge_point;
        char head_path[128];
+       const char *head_path_p;
        int head_path_len;
        unsigned char head_sha1[20];
        int merge_base = 0;
        int independent = 0;
-       char **label;
+       int no_name = 0;
+       int sha1_name = 0;
 
        setup_git_directory();
 
@@ -369,6 +384,10 @@ int main(int ac, char **av)
                        extra = 1;
                else if (!strcmp(arg, "--list"))
                        extra = -1;
+               else if (!strcmp(arg, "--no-name"))
+                       no_name = 1;
+               else if (!strcmp(arg, "--sha1-name"))
+                       sha1_name = 1;
                else if (!strncmp(arg, "--more=", 7))
                        extra = atoi(arg + 7);
                else if (!strcmp(arg, "--merge-base"))
@@ -430,11 +449,15 @@ int main(int ac, char **av)
        if (0 <= extra)
                join_revs(&list, &seen, num_rev, extra);
 
-       head_path_len = readlink(".git/HEAD", head_path, sizeof(head_path)-1);
-       if ((head_path_len < 0) || get_sha1("HEAD", head_sha1))
+       head_path_p = resolve_ref(git_path("HEAD"), head_sha1, 1);
+       if (head_path_p) {
+               head_path_len = strlen(head_path_p);
+               memcpy(head_path, head_path_p, head_path_len + 1);
+       }
+       else {
+               head_path_len = 0;
                head_path[0] = 0;
-       else
-               head_path[head_path_len] = 0;
+       }
 
        if (merge_base)
                return show_merge_base(seen, num_rev);
@@ -460,7 +483,8 @@ int main(int ac, char **av)
                                printf("%c [%s] ",
                                       is_head ? '*' : '!', ref_name[i]);
                        }
-                       show_one_commit(rev[i]);
+                       /* header lines never need name */
+                       show_one_commit(rev[i], 1);
                }
                if (0 <= extra) {
                        for (i = 0; i < num_rev; i++)
@@ -475,7 +499,8 @@ int main(int ac, char **av)
        sort_in_topological_order(&seen);
 
        /* Give names to commits */
-       name_commits(seen, rev, ref_name, num_rev);
+       if (!sha1_name && !no_name)
+               name_commits(seen, rev, ref_name, num_rev);
 
        all_mask = ((1u << (REV_SHIFT + num_rev)) - 1);
        all_revs = all_mask & ~((1u << REV_SHIFT) - 1);
@@ -485,7 +510,6 @@ int main(int ac, char **av)
                struct commit *commit = pop_one_commit(&seen);
                int this_flag = commit->object.flags;
                int is_merge_point = (this_flag & all_revs) == all_revs;
-               static char *obvious[] = { "" };
 
                if (is_merge_point)
                        shown_merge_point = 1;
@@ -496,9 +520,7 @@ int main(int ac, char **av)
                                        ? '+' : ' ');
                        putchar(' ');
                }
-               show_one_commit(commit);
-               if (num_rev == 1)
-                       label = obvious;
+               show_one_commit(commit, no_name);
                if (shown_merge_point && is_merge_point)
                        if (--extra < 0)
                                break;
index 683a1e4a01f250043324ae49fe61cc33470ceb9c..bf01fbc00d649231cb7ba49aba9432afe570b319 100644 (file)
@@ -36,12 +36,26 @@ static ssize_t force_write(int fd, void *buffer, size_t length)
        return ret;
 }
 
+static int prefetches = 0;
+
+static struct object_list *in_transit = NULL;
+static struct object_list **end_of_transit = &in_transit;
+
 void prefetch(unsigned char *sha1)
 {
        char type = 'o';
+       struct object_list *node;
+       if (prefetches > 100) {
+               fetch(in_transit->item->sha1);
+       }
+       node = xmalloc(sizeof(struct object_list));
+       node->next = NULL;
+       node->item = lookup_unknown_object(sha1);
+       *end_of_transit = node;
+       end_of_transit = &node->next;
        force_write(fd_out, &type, 1);
        force_write(fd_out, sha1, 20);
-       //memcpy(requested + 20 * prefetches++, sha1, 20);
+       prefetches++;
 }
 
 static char conn_buf[4096];
@@ -51,6 +65,18 @@ int fetch(unsigned char *sha1)
 {
        int ret;
        signed char remote;
+       struct object_list *temp;
+
+       if (memcmp(sha1, in_transit->item->sha1, 20)) {
+               // we must have already fetched it to clean the queue
+               return has_sha1_file(sha1) ? 0 : -1;
+       }
+       prefetches--;
+       temp = in_transit;
+       in_transit = in_transit->next;
+       if (!in_transit)
+               end_of_transit = &in_transit;
+       free(temp);
 
        if (conn_buf_posn) {
                remote = conn_buf[0];
@@ -119,6 +145,8 @@ int main(int argc, char **argv)
                } else if (argv[arg][1] == 'w') {
                        write_ref = argv[arg + 1];
                        arg++;
+               } else if (!strcmp(argv[arg], "--recover")) {
+                       get_recover = 1;
                }
                arg++;
        }
diff --git a/symbolic-ref.c b/symbolic-ref.c
new file mode 100644 (file)
index 0000000..a72d7ac
--- /dev/null
@@ -0,0 +1,34 @@
+#include "cache.h"
+
+static const char git_symbolic_ref_usage[] =
+"git-symbolic-ref name [ref]";
+
+static void check_symref(const char *HEAD)
+{
+       unsigned char sha1[20];
+       const char *git_HEAD = strdup(git_path("%s", HEAD));
+       const char *git_refs_heads_master = resolve_ref(git_HEAD, sha1, 0);
+       if (git_refs_heads_master) {
+               /* we want to strip the .git/ part */
+               int pfxlen = strlen(git_HEAD) - strlen(HEAD);
+               puts(git_refs_heads_master + pfxlen);
+       }
+       else
+               die("No such ref: %s", HEAD);
+}
+
+int main(int argc, const char **argv)
+{
+       setup_git_directory();
+       switch (argc) {
+       case 2:
+               check_symref(argv[1]);
+               break;
+       case 3:
+               create_symref(strdup(git_path("%s", argv[1])), argv[2]);
+               break;
+       default:
+               usage(git_symbolic_ref_usage);
+       }
+       return 0;
+}
index 6882e23be568ccf14f3adb0c766139086f2ee952..5c76afff83087ee4d6324699f7ba376c18201716 100644 (file)
@@ -4,11 +4,19 @@
 #
 
 #GIT_TEST_OPTS=--verbose --debug
+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))'
 
 T = $(wildcard t[0-9][0-9][0-9][0-9]-*.sh)
 
 all:
-       @$(foreach t,$T,echo "*** $t ***"; sh $t $(GIT_TEST_OPTS) || exit; )
+       @$(foreach t,$T,echo "*** $t ***"; $(call shellquote,$(SHELL_PATH)) $t $(GIT_TEST_OPTS) || exit; )
        @rm -fr trash
 
 clean:
index a912f435aa67d7b2cde09f0b8c9855442c6c2377..745a1b03116a58f89cc9ac533f948fa91c795518 100755 (executable)
@@ -29,7 +29,13 @@ compare_diff_raw_z () {
 compare_diff_patch () {
     # When heuristics are improved, the score numbers would change.
     # Ignore them while comparing.
-    sed -e '/^[dis]*imilarity index [0-9]*%$/d' <"$1" >.tmp-1
-    sed -e '/^[dis]*imilarity index [0-9]*%$/d' <"$2" >.tmp-2
+    sed -e '
+       /^[dis]*imilarity index [0-9]*%$/d
+       /^index [0-9a-f]*\.\.[0-9a-f]/d
+    ' <"$1" >.tmp-1
+    sed -e '
+       /^[dis]*imilarity index [0-9]*%$/d
+       /^index [0-9a-f]*\.\.[0-9a-f]/d
+    ' <"$2" >.tmp-2
     diff -u .tmp-1 .tmp-2 && rm -f .tmp-1 .tmp-2
 }
index 3649f0f74570ac9d7a65287604663cb52567a6f0..dff7d6916374d6f1c9302970cef7d0ab7e0c75ad 100755 (executable)
@@ -28,12 +28,12 @@ test_expect_success \
     '.git/objects should be empty after git-init-db in an empty repo.' \
     'cmp -s /dev/null should-be-empty' 
 
-# also it should have 258 subdirectories; 256 fan-out, pack, and info.
-# 259 is counting "objects" itself
+# also it should have 2 subdirectories; no fan-out anymore, pack, and info.
+# 3 is counting "objects" itself
 find .git/objects -type d -print >full-of-directories
 test_expect_success \
-    '.git/objects should have 258 subdirectories.' \
-    'test $(wc -l < full-of-directories) = 259'
+    '.git/objects should have 3 subdirectories.' \
+    'test $(wc -l < full-of-directories) = 3'
 
 ################################################################
 # Basics of the basics
@@ -85,7 +85,7 @@ do
 done
 test_expect_success \
     'adding various types of objects with git-update-index --add.' \
-    'find path* ! -type d -print0 | xargs -0 git-update-index --add'
+    'find path* ! -type d -print | xargs git-update-index --add'
 
 # Show them and see that matches what we expect.
 test_expect_success \
index c387e9ea08483ef9cc9fb3a61790eaa544c4973b..d0af8c3d52573fea203aab13612a9103cca78dd9 100755 (executable)
@@ -158,49 +158,33 @@ test_expect_success \
 
 We have so far tested only empty index and clean-and-matching-A index
 case which are trivial.  Make sure index requirements are also
-checked.  The table also lists alternative semantics which is not
-currently implemented.
+checked.
 
-"git-diff-tree -m O A B"
+"git-read-tree -m O A B"
 
      O       A       B         result      index requirements
 -------------------------------------------------------------------
   1  missing missing missing   -           must not exist.
  ------------------------------------------------------------------
-  2  missing missing exists    no merge    must not exist.
-                               ------------------------------------
-    (ALT)                      take B*     must match B, if exists.
+  2  missing missing exists    take B*     must match B, if exists.
  ------------------------------------------------------------------
-  3  missing exists  missing   no merge    must match A and be
-                                           up-to-date, if exists.
-                               ------------------------------------
-    (ALT)                      take A*     must match A, if exists.
+  3  missing exists  missing   take A*     must match A, if exists.
  ------------------------------------------------------------------
   4  missing exists  A!=B      no merge    must match A and be
                                            up-to-date, if exists.
  ------------------------------------------------------------------
-  5  missing exists  A==B      no merge    must match A and be
-                                           up-to-date, if exists.
-                               ------------------------------------
-    (ALT)                      take A      must match A, if exists.
+  5  missing exists  A==B      take A      must match A, if exists.
  ------------------------------------------------------------------
-  6  exists  missing missing   no merge    must not exist.
-                               ------------------------------------
-    (ALT)                      remove      must not exist.
+  6  exists  missing missing   remove      must not exist.
  ------------------------------------------------------------------
   7  exists  missing O!=B      no merge    must not exist.
  ------------------------------------------------------------------
-  8  exists  missing O==B      no merge    must not exist.
-                               ------------------------------------
-    (ALT)                      remove      must not exist.
+  8  exists  missing O==B      remove      must not exist.
  ------------------------------------------------------------------
   9  exists  O!=A    missing   no merge    must match A and be
                                            up-to-date, if exists.
  ------------------------------------------------------------------
- 10  exists  O==A    missing   no merge    must match A and be
-                                           up-to-date, if exists.
-                               ------------------------------------
-    (ALT)                      remove      ditto
+ 10  exists  O==A    missing   remove      ditto
  ------------------------------------------------------------------
  11  exists  O!=A    O!=B      no merge    must match A and be
                      A!=B                  up-to-date, if exists.
@@ -210,10 +194,7 @@ currently implemented.
  ------------------------------------------------------------------
  13  exists  O!=A    O==B      take A      must match A, if exists.
  ------------------------------------------------------------------
- 14  exists  O==A    O!=B      take B      must match A and be
-                                           be up-to-date, if exists.
-                               ------------------------------------
-    (ALT)                      take B      if exists, must either (1)
+ 14  exists  O==A    O!=B      take B      if exists, must either (1)
                                            match A and be up-to-date,
                                            or (2) match B.
  ------------------------------------------------------------------
@@ -223,9 +204,9 @@ currently implemented.
      *multi* in one  in another
 -------------------------------------------------------------------
 
-Note: if we want to implement 2ALT and 3ALT we need to be careful.
-The tree A may contain DF (file) when tree B require DF to be a
-directory by having DF/DF (file).
+Note: we need to be careful in case 2 and 3.  The tree A may contain
+DF (file) when tree B require DF to be a directory by having DF/DF
+(file).
 
 END_OF_CASE_TABLE
 
diff --git a/t/t1200-tutorial.sh b/t/t1200-tutorial.sh
new file mode 100644 (file)
index 0000000..35db799
--- /dev/null
@@ -0,0 +1,160 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Johannes Schindelin
+#
+
+test_description='Test git-rev-parse with different parent options'
+
+. ./test-lib.sh
+
+echo "Hello World" > hello
+echo "Silly example" > example
+
+git-update-index --add hello example
+
+test_expect_success 'blob' "test blob = \"$(git-cat-file -t 557db03)\""
+
+test_expect_success 'blob 557db03' "test \"Hello World\" = \"$(git-cat-file blob 557db03)\""
+
+echo "It's a new day for git" >>hello
+cat > diff.expect << EOF
+diff --git a/hello b/hello
+index 557db03..263414f 100644
+--- a/hello
++++ b/hello
+@@ -1 +1,2 @@
+ Hello World
++It's a new day for git
+EOF
+git-diff-files -p > diff.output
+test_expect_success 'git-diff-files -p' 'cmp diff.expect diff.output'
+git diff > diff.output
+test_expect_success 'git diff' 'cmp diff.expect diff.output'
+
+tree=$(git-write-tree 2>/dev/null)
+
+test_expect_success 'tree' "test 8988da15d077d4829fc51d8544c097def6644dbb = $tree"
+
+output="$(echo "Initial commit" | git-commit-tree $(git-write-tree) 2>&1 > .git/refs/heads/master)"
+
+test_expect_success 'commit' "test 'Committing initial tree 8988da15d077d4829fc51d8544c097def6644dbb' = \"$output\""
+
+git-diff-index -p HEAD > diff.output
+test_expect_success 'git-diff-index -p HEAD' 'cmp diff.expect diff.output'
+
+git diff HEAD > diff.output
+test_expect_success 'git diff HEAD' 'cmp diff.expect diff.output'
+
+#rm hello
+#test_expect_success 'git-read-tree --reset HEAD' "git-read-tree --reset HEAD ; test \"hello: needs update\" = \"$(git-update-index --refresh)\""
+
+cat > whatchanged.expect << EOF
+diff-tree VARIABLE (from root)
+Author: VARIABLE
+Date:   VARIABLE
+
+    Initial commit
+
+diff --git a/example b/example
+new file mode 100644
+index 0000000..f24c74a
+--- /dev/null
++++ b/example
+@@ -0,0 +1 @@
++Silly example
+diff --git a/hello b/hello
+new file mode 100644
+index 0000000..557db03
+--- /dev/null
++++ b/hello
+@@ -0,0 +1 @@
++Hello World
+EOF
+
+git-whatchanged -p --root | \
+       sed -e "1s/^\(.\{10\}\).\{40\}/\1VARIABLE/" \
+               -e "2,3s/^\(.\{8\}\).*$/\1VARIABLE/" \
+> whatchanged.output
+test_expect_success 'git-whatchanged -p --root' 'cmp whatchanged.expect whatchanged.output'
+
+git tag my-first-tag
+test_expect_success 'git tag my-first-tag' 'cmp .git/refs/heads/master .git/refs/tags/my-first-tag'
+
+# TODO: test git-clone
+
+git checkout -b mybranch
+test_expect_success 'git checkout -b mybranch' 'cmp .git/refs/heads/master .git/refs/heads/mybranch'
+
+cat > branch.expect <<EOF
+  master
+* mybranch
+EOF
+
+git branch > branch.output
+test_expect_success 'git branch' 'cmp branch.expect branch.output'
+
+git checkout mybranch
+echo "Work, work, work" >>hello
+git commit -m 'Some work.' hello
+
+git checkout master
+
+echo "Play, play, play" >>hello
+echo "Lots of fun" >>example
+git commit -m 'Some fun.' hello example
+
+test_expect_failure 'git resolve now fails' 'git resolve HEAD mybranch "Merge work in mybranch"'
+
+cat > hello << EOF
+Hello World
+It's a new day for git
+Play, play, play
+Work, work, work
+EOF
+
+git commit -m 'Merged "mybranch" changes.' hello
+
+cat > show-branch.expect << EOF
+* [master] Merged "mybranch" changes.
+ ! [mybranch] Some work.
+--
++  [master] Merged "mybranch" changes.
+++ [mybranch] Some work.
+EOF
+
+git show-branch master mybranch > show-branch.output
+test_expect_success 'git show-branch' 'cmp show-branch.expect show-branch.output'
+
+git checkout mybranch
+
+cat > resolve.expect << EOF
+Updating from VARIABLE to VARIABLE.
+ example |    1 +
+ hello   |    1 +
+ 2 files changed, 2 insertions(+), 0 deletions(-)
+EOF
+
+git resolve HEAD master "Merge upstream changes." | \
+       sed -e "1s/[0-9a-f]\{40\}/VARIABLE/g" > resolve.output
+test_expect_success 'git resolve' 'cmp resolve.expect resolve.output'
+
+cat > show-branch2.expect << EOF
+! [master] Merged "mybranch" changes.
+ * [mybranch] Merged "mybranch" changes.
+--
+++ [master] Merged "mybranch" changes.
+EOF
+
+git show-branch master mybranch > show-branch2.output
+test_expect_success 'git show-branch' 'cmp show-branch2.expect show-branch2.output'
+
+# TODO: test git fetch
+
+# TODO: test git push
+
+test_expect_success 'git repack' 'git repack'
+test_expect_success 'git prune-packed' 'git prune-packed'
+test_expect_failure '-> only packed objects' 'find -type f .git/objects/[0-9a-f][0-9a-f]'
+
+test_done
+
index 5beaaa33752daceb216ec7dec50727b9bd03682c..fde2bb25fa0fb41c09b4e4c87de8b9d1c0f9c237 100755 (executable)
@@ -67,4 +67,16 @@ test_expect_success \
        >output &&
      diff -u expect output'
 
+# Test \r\n (MSDOS-like systems)
+echo -ne '*.1\r\n/*.3\r\n!*.6\r\n' >.gitignore
+
+test_expect_success \
+    'git-ls-files --others with \r\n line endings.' \
+    'git-ls-files --others \
+       --exclude=\*.6 \
+       --exclude-per-directory=.gitignore \
+       --exclude-from=.git/ignore \
+       >output &&
+     diff -u expect output'
+
 test_done
diff --git a/t/t3002-ls-files-dashpath.sh b/t/t3002-ls-files-dashpath.sh
new file mode 100755 (executable)
index 0000000..b42f138
--- /dev/null
@@ -0,0 +1,69 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+
+test_description='git-ls-files test (-- to terminate the path list).
+
+This test runs git-ls-files --others with the following on the
+filesystem.
+
+    path0       - a file
+    -foo       - a file with a funny name.
+    --         - another file with a funny name.
+'
+. ./test-lib.sh
+
+test_expect_success \
+       setup \
+       'echo frotz >path0 &&
+       echo frotz >./-foo &&
+       echo frotz >./--'
+
+test_expect_success \
+    'git-ls-files without path restriction.' \
+    'git-ls-files --others >output &&
+     diff -u output - <<EOF
+--
+-foo
+output
+path0
+EOF
+'
+
+test_expect_success \
+    'git-ls-files with path restriction.' \
+    'git-ls-files --others path0 >output &&
+       diff -u output - <<EOF
+path0
+EOF
+'
+
+test_expect_success \
+    'git-ls-files with path restriction with --.' \
+    'git-ls-files --others -- path0 >output &&
+       diff -u output - <<EOF
+path0
+EOF
+'
+
+test_expect_success \
+    'git-ls-files with path restriction with -- --.' \
+    'git-ls-files --others -- -- >output &&
+       diff -u output - <<EOF
+--
+EOF
+'
+
+test_expect_success \
+    'git-ls-files with no path restriction.' \
+    'git-ls-files --others -- >output &&
+       diff -u output - <<EOF
+--
+-foo
+output
+path0
+EOF
+'
+
+test_done
diff --git a/t/t3010-ls-files-killed-modified.sh b/t/t3010-ls-files-killed-modified.sh
new file mode 100755 (executable)
index 0000000..5fc1976
--- /dev/null
@@ -0,0 +1,96 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+
+test_description='git-ls-files -k and -m flags test.
+
+This test prepares the following in the cache:
+
+    path0       - a file
+    path1       - a symlink
+    path2/file2 - a file in a directory
+    path3/file3 - a file in a directory
+
+and the following on the filesystem:
+
+    path0/file0 - a file in a directory
+    path1/file1 - a file in a directory
+    path2       - a file
+    path3       - a symlink
+    path4      - a file
+    path5      - a symlink
+    path6/file6 - a file in a directory
+
+git-ls-files -k should report that existing filesystem
+objects except path4, path5 and path6/file6 to be killed.
+
+Also for modification test, the cache and working tree have:
+
+    path7       - an empty file, modified to a non-empty file.
+    path8       - a non-empty file, modified to an empty file.
+    path9      - an empty file, cache dirtied.
+    path10     - a non-empty file, cache dirtied.
+
+We should report path0, path1, path2/file2, path3/file3, path7 and path8
+modified without reporting path9 and path10.
+'
+. ./test-lib.sh
+
+date >path0
+ln -s xyzzy path1
+mkdir path2 path3
+date >path2/file2
+date >path3/file3
+: >path7
+date >path8
+: >path9
+date >path10
+test_expect_success \
+    'git-update-index --add to add various paths.' \
+    "git-update-index --add -- path0 path1 path?/file? path7 path8 path9 path10"
+
+rm -fr path? ;# leave path10 alone
+date >path2
+ln -s frotz path3
+ln -s nitfol path5
+mkdir path0 path1 path6
+date >path0/file0
+date >path1/file1
+date >path6/file6
+date >path7
+: >path8
+: >path9
+touch path10
+
+test_expect_success \
+    'git-ls-files -k to show killed files.' \
+    'git-ls-files -k >.output'
+cat >.expected <<EOF
+path0/file0
+path1/file1
+path2
+path3
+EOF
+
+test_expect_success \
+    'validate git-ls-files -k output.' \
+    'diff .output .expected'
+
+test_expect_success \
+    'git-ls-files -m to show modified files.' \
+    'git-ls-files -m >.output'
+cat >.expected <<EOF
+path0
+path1
+path2/file2
+path3/file3
+path7
+path8
+EOF
+
+test_expect_success \
+    'validate git-ls-files -m output.' \
+    'diff .output .expected'
+
+test_done
diff --git a/t/t3010-ls-files-killed.sh b/t/t3010-ls-files-killed.sh
deleted file mode 100755 (executable)
index 2e18baa..0000000
+++ /dev/null
@@ -1,61 +0,0 @@
-#!/bin/sh
-#
-# Copyright (c) 2005 Junio C Hamano
-#
-
-test_description='git-ls-files -k flag test.
-
-This test prepares the following in the cache:
-
-    path0       - a file
-    path1       - a symlink
-    path2/file2 - a file in a directory
-    path3/file3 - a file in a directory
-
-and the following on the filesystem:
-
-    path0/file0 - a file in a directory
-    path1/file1 - a file in a directory
-    path2       - a file
-    path3       - a symlink
-    path4      - a file
-    path5      - a symlink
-    path6/file6 - a file in a directory
-
-git-ls-files -k should report that existing filesystem
-objects except path4, path5 and path6/file6 to be killed.
-'
-. ./test-lib.sh
-
-date >path0
-ln -s xyzzy path1
-mkdir path2 path3
-date >path2/file2
-date >path3/file3
-test_expect_success \
-    'git-update-index --add to add various paths.' \
-    "git-update-index --add -- path0 path1 path?/file?"
-
-rm -fr path?
-date >path2
-ln -s frotz path3
-ln -s nitfol path5
-mkdir path0 path1 path6
-date >path0/file0
-date >path1/file1
-date >path6/file6
-
-test_expect_success \
-    'git-ls-files -k to show killed files.' \
-    'git-ls-files -k >.output'
-cat >.expected <<EOF
-path0/file0
-path1/file1
-path2
-path3
-EOF
-
-test_expect_success \
-    'validate git-ls-files -k output.' \
-    'diff .output .expected'
-test_done
diff --git a/t/t3101-ls-tree-dirname.sh b/t/t3101-ls-tree-dirname.sh
new file mode 100644 (file)
index 0000000..5410368
--- /dev/null
@@ -0,0 +1,160 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+# Copyright (c) 2005 Robert Fitzsimons
+#
+
+test_description='git-ls-tree directory and filenames handling.
+
+This test runs git-ls-tree with the following in a tree.
+
+    1.txt              - a file
+    2.txt              - a file
+    path0/a/b/c/1.txt  - a file in a directory
+    path1/b/c/1.txt    - a file in a directory
+    path2/1.txt        - a file in a directory
+    path3/1.txt        - a file in a directory
+    path3/2.txt        - a file in a directory
+
+Test the handling of mulitple directories which have matching file
+entries.  Also test odd filename and missing entries handling.
+'
+. ./test-lib.sh
+
+test_expect_success \
+    'setup' \
+    'echo 111 >1.txt &&
+     echo 222 >2.txt &&
+     mkdir path0 path0/a path0/a/b path0/a/b/c &&
+     echo 111 >path0/a/b/c/1.txt &&
+     mkdir path1 path1/b path1/b/c &&
+     echo 111 >path1/b/c/1.txt &&
+     mkdir path2 &&
+     echo 111 >path2/1.txt &&
+     mkdir path3 &&
+     echo 111 >path3/1.txt &&
+     echo 222 >path3/2.txt &&
+     find *.txt path* \( -type f -o -type l \) -print |
+     xargs git-update-index --add &&
+     tree=`git-write-tree` &&
+     echo $tree'
+
+_x40='[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]'
+_x40="$_x40$_x40$_x40$_x40$_x40$_x40$_x40$_x40"
+test_output () {
+    sed -e "s/ $_x40   / X     /" <current >check
+    diff -u expected check
+}
+
+test_expect_success \
+    'ls-tree plain' \
+    'git-ls-tree $tree >current &&
+     cat >expected <<\EOF &&
+100644 blob X  1.txt
+100644 blob X  2.txt
+040000 tree X  path0
+040000 tree X  path1
+040000 tree X  path2
+040000 tree X  path3
+EOF
+     test_output'
+
+test_expect_success \
+    'ls-tree recursive' \
+    'git-ls-tree -r $tree >current &&
+     cat >expected <<\EOF &&
+100644 blob X  1.txt
+100644 blob X  2.txt
+040000 tree X  path0
+040000 tree X  path0/a
+040000 tree X  path0/a/b
+040000 tree X  path0/a/b/c
+100644 blob X  path0/a/b/c/1.txt
+040000 tree X  path1
+040000 tree X  path1/b
+040000 tree X  path1/b/c
+100644 blob X  path1/b/c/1.txt
+040000 tree X  path2
+100644 blob X  path2/1.txt
+040000 tree X  path3
+100644 blob X  path3/1.txt
+100644 blob X  path3/2.txt
+EOF
+     test_output'
+
+test_expect_success \
+    'ls-tree filter 1.txt' \
+    'git-ls-tree $tree 1.txt >current &&
+     cat >expected <<\EOF &&
+100644 blob X  1.txt
+EOF
+     test_output'
+
+test_expect_success \
+    'ls-tree filter path1/b/c/1.txt' \
+    'git-ls-tree $tree path1/b/c/1.txt >current &&
+     cat >expected <<\EOF &&
+100644 blob X  path1/b/c/1.txt
+EOF
+     test_output'
+
+test_expect_success \
+    'ls-tree filter all 1.txt files' \
+    'git-ls-tree $tree 1.txt path0/a/b/c/1.txt path1/b/c/1.txt path2/1.txt path3/1.txt >current &&
+     cat >expected <<\EOF &&
+100644 blob X  1.txt
+100644 blob X  path0/a/b/c/1.txt
+100644 blob X  path1/b/c/1.txt
+100644 blob X  path2/1.txt
+100644 blob X  path3/1.txt
+EOF
+     test_output'
+
+test_expect_success \
+    'ls-tree filter directories' \
+    'git-ls-tree $tree path3 path2 path0/a/b/c path1/b/c path0/a >current &&
+     cat >expected <<\EOF &&
+040000 tree X  path3
+100644 blob X  path3/1.txt
+100644 blob X  path3/2.txt
+040000 tree X  path2
+100644 blob X  path2/1.txt
+040000 tree X  path0/a/b/c
+100644 blob X  path0/a/b/c/1.txt
+040000 tree X  path1/b/c
+100644 blob X  path1/b/c/1.txt
+040000 tree X  path0/a
+040000 tree X  path0/a/b
+EOF
+     test_output'
+
+test_expect_success \
+    'ls-tree filter odd names' \
+    'git-ls-tree $tree 1.txt /1.txt //1.txt path3/1.txt /path3/1.txt //path3//1.txt path3 /path3/ path3// >current &&
+     cat >expected <<\EOF &&
+100644 blob X  1.txt
+100644 blob X  1.txt
+100644 blob X  1.txt
+100644 blob X  path3/1.txt
+100644 blob X  path3/1.txt
+100644 blob X  path3/1.txt
+040000 tree X  path3
+100644 blob X  path3/1.txt
+100644 blob X  path3/2.txt
+040000 tree X  path3
+100644 blob X  path3/1.txt
+100644 blob X  path3/2.txt
+040000 tree X  path3
+100644 blob X  path3/1.txt
+100644 blob X  path3/2.txt
+EOF
+     test_output'
+
+test_expect_success \
+    'ls-tree filter missing files and extra slashes' \
+    'git-ls-tree $tree 1.txt/ abc.txt path3//23.txt path3/2.txt/// >current &&
+     cat >expected <<\EOF &&
+EOF
+     test_output'
+
+test_done
diff --git a/t/t3300-funny-names.sh b/t/t3300-funny-names.sh
new file mode 100755 (executable)
index 0000000..897c378
--- /dev/null
@@ -0,0 +1,142 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+
+test_description='Pathnames with funny characters.
+
+This test tries pathnames with funny characters in the working
+tree, index, and tree objects.
+'
+
+# since FAT/NTFS does not allow tabs in filenames, skip this test
+test "$(uname -o 2>/dev/null)" = Cygwin && exit 0
+
+. ./test-lib.sh
+
+p0='no-funny'
+p1='tabs       and spaces'
+p2='just space'
+
+cat >"$p0" <<\EOF
+1. A quick brown fox jumps over the lazy cat, oops dog.
+2. A quick brown fox jumps over the lazy cat, oops dog.
+3. A quick brown fox jumps over the lazy cat, oops dog.
+EOF
+
+cat >"$p1" "$p0"
+echo 'Foo Bar Baz' >"$p2"
+
+echo 'just space
+no-funny' >expected
+test_expect_success 'git-ls-files no-funny' \
+       'git-update-index --add "$p0" "$p2" &&
+       git-ls-files >current &&
+       diff -u expected current'
+
+t0=`git-write-tree`
+echo "$t0" >t0
+
+echo 'just space
+no-funny
+"tabs\tand spaces"' >expected
+test_expect_success 'git-ls-files with-funny' \
+       'git-update-index --add "$p1" &&
+       git-ls-files >current &&
+       diff -u expected current'
+
+echo 'just space
+no-funny
+tabs   and spaces' >expected
+test_expect_success 'git-ls-files -z with-funny' \
+       'git-ls-files -z | tr \\0 \\012 >current &&
+       diff -u expected current'
+
+t1=`git-write-tree`
+echo "$t1" >t1
+
+echo 'just space
+no-funny
+"tabs\tand spaces"' >expected
+test_expect_success 'git-ls-tree with funny' \
+       'git-ls-tree -r $t1 | sed -e "s/^[^     ]*      //" >current &&
+        diff -u expected current'
+
+echo 'A        "tabs\tand spaces"' >expected
+test_expect_success 'git-diff-index with-funny' \
+       'git-diff-index --name-status $t0 >current &&
+       diff -u expected current'
+
+test_expect_success 'git-diff-tree with-funny' \
+       'git-diff-tree --name-status $t0 $t1 >current &&
+       diff -u expected current'
+
+echo 'A
+tabs   and spaces' >expected
+test_expect_success 'git-diff-index -z with-funny' \
+       'git-diff-index -z --name-status $t0 | tr \\0 \\012 >current &&
+       diff -u expected current'
+
+test_expect_success 'git-diff-tree -z with-funny' \
+       'git-diff-tree -z --name-status $t0 $t1 | tr \\0 \\012 >current &&
+       diff -u expected current'
+
+echo 'CNUM     no-funny        "tabs\tand spaces"' >expected
+test_expect_success 'git-diff-tree -C with-funny' \
+       'git-diff-tree -C --find-copies-harder --name-status \
+               $t0 $t1 | sed -e 's/^C[0-9]*/CNUM/' >current &&
+       diff -u expected current'
+
+echo 'RNUM     no-funny        "tabs\tand spaces"' >expected
+test_expect_success 'git-diff-tree delete with-funny' \
+       'git-update-index --force-remove "$p0" &&
+       git-diff-index -M --name-status \
+               $t0 | sed -e 's/^R[0-9]*/RNUM/' >current &&
+       diff -u expected current'
+
+echo 'diff --git a/no-funny "b/tabs\tand spaces"
+similarity index NUM%
+rename from no-funny
+rename to "tabs\tand spaces"' >expected
+
+test_expect_success 'git-diff-tree delete with-funny' \
+       'git-diff-index -M -p $t0 |
+        sed -e "s/index [0-9]*%/index NUM%/" >current &&
+        diff -u expected current'
+
+chmod +x "$p1"
+echo 'diff --git a/no-funny "b/tabs\tand spaces"
+old mode 100644
+new mode 100755
+similarity index NUM%
+rename from no-funny
+rename to "tabs\tand spaces"' >expected
+
+test_expect_success 'git-diff-tree delete with-funny' \
+       'git-diff-index -M -p $t0 |
+        sed -e "s/index [0-9]*%/index NUM%/" >current &&
+        diff -u expected current'
+
+echo >expected ' "tabs\tand spaces"
+ 1 files changed, 0 insertions(+), 0 deletions(-)'
+test_expect_success 'git-diff-tree rename with-funny applied' \
+       'git-diff-index -M -p $t0 |
+        git-apply --stat | sed -e "s/|.*//" -e "s/ *\$//" >current &&
+        diff -u expected current'
+
+echo >expected ' no-funny
+ "tabs\tand spaces"
+ 2 files changed, 3 insertions(+), 3 deletions(-)'
+
+test_expect_success 'git-diff-tree delete with-funny applied' \
+       'git-diff-index -p $t0 |
+        git-apply --stat | sed -e "s/|.*//" -e "s/ *\$//" >current &&
+        diff -u expected current'
+
+test_expect_success 'git-apply non-git diff' \
+       'git-diff-index -p $t0 |
+        sed -ne "/^[-+@]/p" |
+        git-apply --stat | sed -e "s/|.*//" -e "s/ *\$//" >current &&
+        diff -u expected current'
+
+test_done
index 91015d76fc5b63e0ffa68eb121bb514aa9a74f8f..beb6d8f4877c157bdf9e3efa2c914e74da863cd5 100755 (executable)
@@ -7,6 +7,7 @@ test_description='Test built-in diff output engine.
 
 '
 . ./test-lib.sh
+. ../diff-lib.sh
 
 echo >path0 'Line 1
 Line 2
@@ -48,15 +49,6 @@ EOF
 
 test_expect_success \
     'validate git-diff-files -p output.' \
-    'cmp -s current expected'
-
-test_expect_success \
-    'build same diff using git-diff-helper.' \
-    'git-diff-files -z | git-diff-helper -z >current'
-
-
-test_expect_success \
-    'validate git-diff-helper output.' \
-    'cmp -s current expected'
+    'compare_diff_patch current expected'
 
 test_done
index be474856824f8e6659151590ed5acff38187e97d..2e3c20d6b9468bf413e97d422e7dbe13ac4238cd 100755 (executable)
@@ -7,6 +7,7 @@ test_description='Test rename detection in diff engine.
 
 '
 . ./test-lib.sh
+. ../diff-lib.sh
 
 echo >path0 'Line 1
 Line 2
@@ -61,6 +62,6 @@ EOF
 
 test_expect_success \
     'validate the output.' \
-    'diff -I "similarity.*" >/dev/null current expected'
+    'compare_diff_patch current expected'
 
 test_done
index f59614ae255b33f450a784200716c9fd63b0a054..a23aaa0a9471c68b233480cf34c7115d1f40e154 100755 (executable)
@@ -10,6 +10,7 @@ copy of symbolic links, but should not produce rename/copy followed
 by an edit for them.
 '
 . ./test-lib.sh
+. ../diff-lib.sh
 
 test_expect_success \
     'prepare reference tree' \
@@ -61,6 +62,6 @@ EOF
 
 test_expect_success \
     'validate diff output' \
-    'diff -u current expected'
+    'compare_diff_patch current expected'
 
 test_done
index 5636f4f2cdcff671d4b43dc1253f2507aedfa7b0..684fd23a419a2449a1a656783b09e0566c56c916 100755 (executable)
@@ -40,38 +40,6 @@ test_expect_success \
     'validate output from rename/copy detection (#1)' \
     'compare_diff_raw current expected'
 
-# make sure diff-helper can grok it.
-mv expected diff-raw
-GIT_DIFF_OPTS=--unified=0 git-diff-helper <diff-raw >current
-cat >expected <<\EOF
-diff --git a/COPYING b/COPYING.1
-copy from COPYING
-copy to COPYING.1
---- a/COPYING
-+++ b/COPYING.1
-@@ -6 +6 @@
-- HOWEVER, in order to allow a migration to GPLv3 if that seems like
-+ However, in order to allow a migration to GPLv3 if that seems like
-diff --git a/COPYING b/COPYING.2
-rename from COPYING
-rename to COPYING.2
---- a/COPYING
-+++ b/COPYING.2
-@@ -2 +2 @@
-- Note that the only valid version of the GPL as far as this project
-+ Note that the only valid version of the G.P.L as far as this project
-@@ -6 +6 @@
-- HOWEVER, in order to allow a migration to GPLv3 if that seems like
-+ HOWEVER, in order to allow a migration to G.P.Lv3 if that seems like
-@@ -12 +12 @@
--      This file is licensed under the GPL v2, or a later version
-+      This file is licensed under the G.P.L v2, or a later version
-EOF
-
-test_expect_success \
-    'validate output from diff-helper (#1)' \
-    'compare_diff_patch current expected'
-
 ################################################################
 
 test_expect_success \
@@ -94,36 +62,6 @@ test_expect_success \
     'validate output from rename/copy detection (#2)' \
     'compare_diff_raw current expected'
 
-# make sure diff-helper can grok it.
-mv expected diff-raw
-GIT_DIFF_OPTS=--unified=0 git-diff-helper <diff-raw >current
-cat >expected <<\EOF
-diff --git a/COPYING b/COPYING
---- a/COPYING
-+++ b/COPYING
-@@ -2 +2 @@
-- Note that the only valid version of the GPL as far as this project
-+ Note that the only valid version of the G.P.L as far as this project
-@@ -6 +6 @@
-- HOWEVER, in order to allow a migration to GPLv3 if that seems like
-+ HOWEVER, in order to allow a migration to G.P.Lv3 if that seems like
-@@ -12 +12 @@
--      This file is licensed under the GPL v2, or a later version
-+      This file is licensed under the G.P.L v2, or a later version
-diff --git a/COPYING b/COPYING.1
-copy from COPYING
-copy to COPYING.1
---- a/COPYING
-+++ b/COPYING.1
-@@ -6 +6 @@
-- HOWEVER, in order to allow a migration to GPLv3 if that seems like
-+ However, in order to allow a migration to GPLv3 if that seems like
-EOF
-
-test_expect_success \
-    'validate output from diff-helper (#2)' \
-    'compare_diff_patch current expected'
-
 ################################################################
 
 # tree has COPYING and rezrov.  work tree has the same COPYING and
@@ -145,22 +83,4 @@ test_expect_success \
     'validate output from rename/copy detection (#3)' \
     'compare_diff_raw current expected'
 
-# make sure diff-helper can grok it.
-mv expected diff-raw
-GIT_DIFF_OPTS=--unified=0 git-diff-helper <diff-raw >current
-cat >expected <<\EOF
-diff --git a/COPYING b/COPYING.1
-copy from COPYING
-copy to COPYING.1
---- a/COPYING
-+++ b/COPYING.1
-@@ -6 +6 @@
-- HOWEVER, in order to allow a migration to GPLv3 if that seems like
-+ However, in order to allow a migration to GPLv3 if that seems like
-EOF
-
-test_expect_success \
-    'validate output from diff-helper (#3)' \
-    'compare_diff_patch current expected'
-
 test_done
index ea811529cc6ff21a33ebafdd1e331aee3f6ac388..2f2f8b121663a3647e88c308b541c6106f5d9039 100755 (executable)
@@ -44,38 +44,6 @@ test_expect_success \
     'validate output from rename/copy detection (#1)' \
     'compare_diff_raw_z current expected'
 
-# make sure diff-helper can grok it.
-mv current diff-raw
-GIT_DIFF_OPTS=--unified=0 git-diff-helper -z <diff-raw >current
-cat >expected <<\EOF
-diff --git a/COPYING b/COPYING.1
-copy from COPYING
-copy to COPYING.1
---- a/COPYING
-+++ b/COPYING.1
-@@ -6 +6 @@
-- HOWEVER, in order to allow a migration to GPLv3 if that seems like
-+ However, in order to allow a migration to GPLv3 if that seems like
-diff --git a/COPYING b/COPYING.2
-rename from COPYING
-rename to COPYING.2
---- a/COPYING
-+++ b/COPYING.2
-@@ -2 +2 @@
-- Note that the only valid version of the GPL as far as this project
-+ Note that the only valid version of the G.P.L as far as this project
-@@ -6 +6 @@
-- HOWEVER, in order to allow a migration to GPLv3 if that seems like
-+ HOWEVER, in order to allow a migration to G.P.Lv3 if that seems like
-@@ -12 +12 @@
--      This file is licensed under the GPL v2, or a later version
-+      This file is licensed under the G.P.L v2, or a later version
-EOF
-
-test_expect_success \
-    'validate output from diff-helper (#1)' \
-    'compare_diff_patch current expected'
-
 ################################################################
 
 test_expect_success \
@@ -101,36 +69,6 @@ test_expect_success \
     'validate output from rename/copy detection (#2)' \
     'compare_diff_raw_z current expected'
 
-# make sure diff-helper can grok it.
-mv current diff-raw
-GIT_DIFF_OPTS=--unified=0 git-diff-helper -z <diff-raw >current
-cat >expected <<\EOF
-diff --git a/COPYING b/COPYING
---- a/COPYING
-+++ b/COPYING
-@@ -2 +2 @@
-- Note that the only valid version of the GPL as far as this project
-+ Note that the only valid version of the G.P.L as far as this project
-@@ -6 +6 @@
-- HOWEVER, in order to allow a migration to GPLv3 if that seems like
-+ HOWEVER, in order to allow a migration to G.P.Lv3 if that seems like
-@@ -12 +12 @@
--      This file is licensed under the GPL v2, or a later version
-+      This file is licensed under the G.P.L v2, or a later version
-diff --git a/COPYING b/COPYING.1
-copy from COPYING
-copy to COPYING.1
---- a/COPYING
-+++ b/COPYING.1
-@@ -6 +6 @@
-- HOWEVER, in order to allow a migration to GPLv3 if that seems like
-+ However, in order to allow a migration to GPLv3 if that seems like
-EOF
-
-test_expect_success \
-    'validate output from diff-helper (#2)' \
-    'compare_diff_patch current expected'
-
 ################################################################
 
 # tree has COPYING and rezrov.  work tree has the same COPYING and
@@ -154,22 +92,4 @@ test_expect_success \
     'validate output from rename/copy detection (#3)' \
     'compare_diff_raw_z current expected'
 
-# make sure diff-helper can grok it.
-mv current diff-raw
-GIT_DIFF_OPTS=--unified=0 git-diff-helper -z <diff-raw >current
-cat >expected <<\EOF
-diff --git a/COPYING b/COPYING.1
-copy from COPYING
-copy to COPYING.1
---- a/COPYING
-+++ b/COPYING.1
-@@ -6 +6 @@
-- HOWEVER, in order to allow a migration to GPLv3 if that seems like
-+ However, in order to allow a migration to GPLv3 if that seems like
-EOF
-
-test_expect_success \
-    'validate output from diff-helper (#3)' \
-    'compare_diff_patch current expected'
-
 test_done
index 530cc4d2a38c6f4af92097dfe524029ef6a3a6bf..0401d7bbc6e416d2d44b8315ffd70125edca55c0 100755 (executable)
@@ -32,6 +32,6 @@ test_expect_success apply \
     'git-apply --index --stat --summary --apply test-patch'
 
 test_expect_success validate \
-    'test -f bar && ls -l bar | grep "^-..x..x..x"'
+    'test -f bar && ls -l bar | grep "^-..x......"'
 
 test_done
index 6bf34066e11d06902ed839acafd79835ce09c9c7..4db1bb11425797b5b105fcb7b92f443616d58355 100755 (executable)
@@ -41,8 +41,8 @@ test_expect_success \
      find a -type l | xargs git-update-index --add &&
      treeid=`git-write-tree` &&
      echo $treeid >treeid &&
-     TZ=GMT GIT_COMMITTER_DATE="2005-05-27 22:00:00" \
-     git-commit-tree $treeid </dev/null >.git/HEAD'
+     git-update-ref HEAD $(TZ=GMT GIT_COMMITTER_DATE="2005-05-27 22:00:00" \
+     git-commit-tree $treeid </dev/null)'
 
 test_expect_success \
     'git-tar-tree' \
@@ -50,7 +50,7 @@ test_expect_success \
 
 test_expect_success \
     'validate file modification time' \
-    'TZ=GMT tar tvf b.tar a/a |
+    'TZ=GMT $TAR tvf b.tar a/a |
      awk \{print\ \$4,\ \(length\(\$5\)\<7\)\ ?\ \$5\":00\"\ :\ \$5\} \
      >b.mtime &&
      echo "2005-05-27 22:00:00" >expected.mtime &&
@@ -59,11 +59,11 @@ test_expect_success \
 test_expect_success \
     'git-get-tar-commit-id' \
     'git-get-tar-commit-id <b.tar >b.commitid &&
-     diff .git/HEAD b.commitid'
+     diff .git/$(git-symbolic-ref HEAD) b.commitid'
 
 test_expect_success \
     'extract tar archive' \
-    '(cd b && tar xf -) <b.tar'
+    '(cd b && $TAR xf -) <b.tar'
 
 test_expect_success \
     'validate filenames' \
@@ -80,7 +80,7 @@ test_expect_success \
 
 test_expect_success \
     'extract tar archive with prefix' \
-    '(cd c && tar xf -) <c.tar'
+    '(cd c && $TAR xf -) <c.tar'
 
 test_expect_success \
     'validate filenames with prefix' \
index bb62336f267086cc85bf0c6d5133560c581f5973..5b50536b54ff84b6a6e51c210be39773c6879443 100755 (executable)
@@ -49,7 +49,7 @@ test_expect_success \
      git-unpack-objects <test-1-${packname_1}.pack"
 
 unset GIT_OBJECT_DIRECTORY
-cd $TRASH/.git2
+cd "$TRASH/.git2"
 
 test_expect_success \
     'check unpack without delta' \
@@ -61,7 +61,7 @@ test_expect_success \
             return 1
         }
      done'
-cd $TRASH
+cd "$TRASH"
 
 test_expect_success \
     'pack with delta' \
@@ -80,7 +80,7 @@ test_expect_success \
      git-unpack-objects <test-2-${packname_2}.pack'
 
 unset GIT_OBJECT_DIRECTORY
-cd $TRASH/.git2
+cd "$TRASH/.git2"
 test_expect_success \
     'check unpack with delta' \
     '(cd ../.git && find objects -type f -print) |
@@ -91,7 +91,7 @@ test_expect_success \
             return 1
         }
      done'
-cd $TRASH
+cd "$TRASH"
 
 rm -fr .git2
 mkdir .git2
@@ -165,4 +165,22 @@ test_expect_success \
 
      :'
 
+test_expect_success \
+    'build pack index for an existing pack' \
+    'cp test-1-${packname_1}.pack test-3.pack &&
+     git-index-pack -o tmp.idx test-3.pack &&
+     cmp tmp.idx test-1-${packname_1}.idx &&
+
+     git-index-pack test-3.pack &&
+     cmp test-3.idx test-1-${packname_1}.idx &&
+
+     cp test-2-${packname_2}.pack test-3.pack &&
+     git-index-pack -o tmp.idx test-2-${packname_2}.pack &&
+     cmp tmp.idx test-2-${packname_2}.idx &&
+
+     git-index-pack test-3.pack &&
+     cmp test-3.idx test-2-${packname_2}.idx &&
+
+     :'
+
 test_done
index 59ce77b6b42eceaf953e955b79723753deb10033..7fc3bd7d3e9c7761a1c64073be95129d3e93bae8 100755 (executable)
@@ -8,6 +8,9 @@ test_description='See why rewinding head breaks send-pack
 '
 . ./test-lib.sh
 
+touch cpio-test
+test_expect_success 'working cpio' 'echo cpio-test | cpio -o > /dev/null'
+
 cnt='1'
 test_expect_success setup '
        tree=$(git-write-tree) &&
@@ -20,12 +23,12 @@ test_expect_success setup '
            commit=$(echo "Commit #$i" | git-commit-tree $tree -p $parent) &&
            parent=$commit || return 1
        done &&
-       echo "$commit" >.git/HEAD &&
+       git-update-ref HEAD "$commit" &&
        git-clone -l ./. victim &&
        cd victim &&
        git-log &&
        cd .. &&
-       echo $zero >.git/HEAD &&
+       git-update-ref HEAD "$zero" &&
        parent=$zero &&
        for i in $cnt
        do
@@ -33,7 +36,7 @@ test_expect_success setup '
            commit=$(echo "Rebase #$i" | git-commit-tree $tree -p $parent) &&
            parent=$commit || return 1
        done &&
-       echo "$commit" >.git/HEAD &&
+       git-update-ref HEAD "$commit" &&
        echo Rebase &&
        git-log'
 
diff --git a/t/t5500-fetch-pack.sh b/t/t5500-fetch-pack.sh
new file mode 100644 (file)
index 0000000..0781bd2
--- /dev/null
@@ -0,0 +1,136 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Johannes Schindelin
+#
+
+test_description='Testing multi_ack pack fetching
+
+'
+. ./test-lib.sh
+
+# Test fetch-pack/upload-pack pair.
+
+# Some convenience functions
+
+function show_count () {
+       commit_count=$(($commit_count+1))
+       printf "      %d\r" $commit_count
+}
+
+function add () {
+       local name=$1
+       local text="$@"
+       local branch=${name:0:1}
+       local parents=""
+
+       shift
+       while test $1; do
+               parents="$parents -p $1"
+               shift
+       done
+
+       echo "$text" > test.txt
+       git-update-index --add test.txt
+       tree=$(git-write-tree)
+       # make sure timestamps are in correct order
+       sec=$(($sec+1))
+       commit=$(echo "$text" | GIT_AUTHOR_DATE=$sec \
+               git-commit-tree $tree $parents 2>>log2.txt)
+       export $name=$commit
+       echo $commit > .git/refs/heads/$branch
+       eval ${branch}TIP=$commit
+}
+
+function count_objects () {
+       ls .git/objects/??/* 2>>log2.txt | wc -l | tr -d " "
+}
+
+function test_expect_object_count () {
+       local message=$1
+       local count=$2
+
+       output="$(count_objects)"
+       test_expect_success \
+               "new object count $message" \
+               "test $count = $output"
+}
+
+function test_repack () {
+       local rep=$1
+
+       test_expect_success "repack && prune-packed in $rep" \
+               '(git-repack && git-prune-packed)2>>log.txt'
+}
+
+function pull_to_client () {
+       local number=$1
+       local heads=$2
+       local count=$3
+       local no_strict_count_check=$4
+
+       cd client
+       test_expect_success "$number pull" \
+               "git-fetch-pack -v .. $heads > log.txt 2>&1"
+       case "$heads" in *A*) echo $ATIP > .git/refs/heads/A;; esac
+       case "$heads" in *B*) echo $BTIP > .git/refs/heads/B;; esac
+       git-symbolic-ref HEAD refs/heads/${heads:0:1}
+       test_expect_success "fsck" 'git-fsck-objects --full > fsck.txt 2>&1'
+       test_expect_object_count "after $number pull" $count
+       pack_count=$(grep Packing log.txt|tr -dc "0-9")
+       test -z "$pack_count" && pack_count=0
+       if [ -z "$no_strict_count_check" ]; then
+               test_expect_success "minimal count" "test $count = $pack_count"
+       else
+               test $count != $pack_count && \
+                       echo "WARNING: $pack_count objects transmitted, only $count of which were needed"
+       fi
+       cd ..
+}
+
+# Here begins the actual testing
+
+# A1 - ... - A20 - A21
+#    \
+#      B1  -   B2 - .. - B70
+
+# client pulls A20, B1. Then tracks only B. Then pulls A.
+
+(
+       mkdir client &&
+       cd client &&
+       git-init-db 2>> log2.txt
+)
+
+add A1
+
+prev=1; cur=2; while [ $cur -le 10 ]; do
+       add A$cur $(eval echo \$A$prev)
+       prev=$cur
+       cur=$(($cur+1))
+done
+
+add B1 $A1
+
+echo $ATIP > .git/refs/heads/A
+echo $BTIP > .git/refs/heads/B
+git-symbolic-ref HEAD refs/heads/B
+
+pull_to_client 1st "B A" $((11*3))
+
+(cd client; test_repack client)
+
+add A11 $A10
+
+prev=1; cur=2; while [ $cur -le 65 ]; do
+       add B$cur $(eval echo \$B$prev)
+       prev=$cur
+       cur=$(($cur+1))
+done
+
+pull_to_client 2nd "B" $((64*3))
+
+(cd client; test_repack client)
+
+pull_to_client 3rd "A" $((1*3)) # old fails
+
+test_done
diff --git a/t/t5501-old-fetch-and-upload.sh b/t/t5501-old-fetch-and-upload.sh
new file mode 100755 (executable)
index 0000000..ada5130
--- /dev/null
@@ -0,0 +1,43 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Johannes Schindelin
+#
+
+# Test that the current fetch-pack/upload-pack plays nicely with
+# an old counterpart
+
+cd $(dirname $0) || exit 1
+
+tmp=$(mktemp /tmp/tmp-XXXXXXXX)
+
+retval=0
+
+if [ -z "$1" ]; then
+       list="fetch upload"
+else
+       list="$@"
+fi
+
+for i in $list; do
+       case "$i" in
+       fetch) pgm="old-git-fetch-pack"; replace="$pgm";;
+       upload) pgm="old-git-upload-pack"; replace="git-fetch-pack --exec=$pgm";;
+       both) pgm="old-git-upload-pack"; replace="old-git-fetch-pack --exec=$pgm";;
+       esac
+
+       if which $pgm 2>/dev/null; then
+               echo "Testing with $pgm"
+               sed -e "s/git-fetch-pack/$replace/g" \
+                       -e "s/# old fails/warn/" < t5500-fetch-pack.sh > $tmp
+
+               sh $tmp || retval=$?
+               rm $tmp
+
+               test $retval != 0 && exit $retval
+       else
+               echo "Skipping test for $i, since I cannot find $pgm"
+       fi
+done
+
+exit 0
+
index 010124238257f7bc0ae8eb75c5d71a79c7fdef7d..8ec9ebb98abf00d5b433e11b56efbd31bbce2c40 100755 (executable)
@@ -108,7 +108,7 @@ save_tag h2 unique_commit g4 tree -p g2
 save_tag g3 unique_commit g5 tree -p g2
 save_tag g4 unique_commit g6 tree -p g3 -p h2
 
-tag l5 > .git/HEAD
+git-update-ref HEAD $(tag l5)
 
 test_expect_success 'rev-list has correct number of entries' 'git-rev-list HEAD | wc -l | tr -s " "' <<EOF
 19
index d0a4ff29c97ffde5f994971e80a8e094d8a8ccd0..693de9b32dea04140d58ba98d0db07ac0e724a6b 100755 (executable)
@@ -7,20 +7,6 @@ test_description='Tests git-rev-list --bisect functionality'
 . ./test-lib.sh
 . ../t6000lib.sh # t6xxx specific functions
 
-bc_expr()
-{
-bc <<EOF
-scale=1
-define abs(x) {
-       if (x>=0) { return (x); } else { return (-x); }
-}
-define floor(x) {
-       save=scale; scale=0; result=x/1; scale=save; return (result);
-}
-$*
-EOF
-}
-
 # usage: test_bisection max-diff bisect-option head ^prune...
 #
 # e.g. test_bisection 1 --bisect l1 ^l0
@@ -35,8 +21,19 @@ test_bisection_diff()
         _head=$1
        shift 1
        _bisection_size=$(git-rev-list $_bisection "$@" | wc -l)
-       [ -n "$_list_size" -a -n "$_bisection_size" ] || error "test_bisection_diff failed"
-       test_expect_success "bisection diff $_bisect_option $_head $* <= $_max_diff" "[ $(bc_expr "floor(abs($_list_size/2)-$_bisection_size)") -le $_max_diff ]"
+       [ -n "$_list_size" -a -n "$_bisection_size" ] ||
+       error "test_bisection_diff failed"
+
+       # Test if bisection size is close to half of list size within
+       # tolerance.
+       # 
+       _bisect_err=`expr $_list_size - $_bisection_size \* 2`
+       test "$_bisect_err" -lt 0 && _bisect_err=`expr 0 - $_bisect_err`
+       _bisect_err=`expr $_bisect_err / 2` ; # floor
+
+       test_expect_success \
+       "bisection diff $_bisect_option $_head $* <= $_max_diff" \
+       'test $_bisect_err -le $_max_diff'
 }
 
 date >path0
@@ -61,7 +58,7 @@ on_committer_date "1971-08-16 00:00:15" save_tag a4 unique_commit a4 tree -p a3
 on_committer_date "1971-08-16 00:00:16" save_tag l3 unique_commit l3 tree -p a4
 on_committer_date "1971-08-16 00:00:17" save_tag l4 unique_commit l4 tree -p l3
 on_committer_date "1971-08-16 00:00:18" save_tag l5 unique_commit l5 tree -p l4
-tag l5 > .git/HEAD
+git-update-ref HEAD $(tag l5)
 
 
 #     E
index 88d14ee1a3419f06c45a30e42a79934c4284c465..3c4c44c24d0781a1f876e7d4d016bf95f070e232 100755 (executable)
@@ -77,7 +77,7 @@ save_tag h2 unique_commit g4 tree -p g2
 save_tag g3 unique_commit g5 tree -p g2
 save_tag g4 unique_commit g6 tree -p g3 -p h2
 
-tag l5 > .git/HEAD
+git-update-ref HEAD $(tag l5)
 
 test_expect_success 'rev-list has correct number of entries' 'git-rev-list HEAD | wc -l | tr -s " "' <<EOF
 19
index 1523d2ebbf1f355fe60ad0d5ef948d24cebc85ad..a8f239df8fc45575ca543208c2bcad771d3f4d7e 100755 (executable)
@@ -5,9 +5,10 @@
 
 # For repeatability, reset the environment to known value.
 LANG=C
+LC_ALL=C
 PAGER=cat
 TZ=UTC
-export LANG PAGER TZ
+export LANG LC_ALL PAGER TZ
 unset AUTHOR_DATE
 unset AUTHOR_EMAIL
 unset AUTHOR_NAME
@@ -163,4 +164,8 @@ test=trash
 rm -fr "$test"
 mkdir "$test"
 cd "$test"
-git-init-db 2>/dev/null || error "cannot run git-init-db"
+git-init-db --template=../../templates/blt/ 2>/dev/null ||
+error "cannot run git-init-db"
+
+mv .git/hooks .git/hooks-disabled
+
diff --git a/tag.c b/tag.c
index b1ab75ff0164b0090f43ba55d2a70239ca2fdb7a..e574c4b7a496d3a1d113801214197a04d5c74d42 100644 (file)
--- a/tag.c
+++ b/tag.c
@@ -3,10 +3,15 @@
 
 const char *tag_type = "tag";
 
-struct object *deref_tag(struct object *o)
+struct object *deref_tag(struct object *o, const char *warn, int warnlen)
 {
        while (o && o->type == tag_type)
                o = parse_object(((struct tag *)o)->tagged->sha1);
+       if (!o && warn) {
+               if (!warnlen)
+                       warnlen = strlen(warn);
+               error("missing object referenced by '%.*s'", warnlen, warn);
+       }
        return o;
 }
 
diff --git a/tag.h b/tag.h
index 36e532401fe253a61a26f8b0adfba2cf1009fb3d..7a0cb0070d46ba8c49d71029dc0704188805ea62 100644 (file)
--- a/tag.h
+++ b/tag.h
@@ -15,6 +15,6 @@ struct tag {
 extern struct tag *lookup_tag(const unsigned char *sha1);
 extern int parse_tag_buffer(struct tag *item, void *data, unsigned long size);
 extern int parse_tag(struct tag *item);
-extern struct object *deref_tag(struct object *);
+extern struct object *deref_tag(struct object *, const char *, int);
 
 #endif /* TAG_H */
index 2716ae3eb1430abfc43d980d904fc192f8dcbbf4..970c4bb54e148282a89f1249a96deca880bccafd 100644 (file)
@@ -353,6 +353,8 @@ static void traverse_tree(void *buffer, unsigned long size,
 
                if (size < namelen + 20 || sscanf(buffer, "%o", &mode) != 1)
                        die("corrupt 'tree' file");
+               if (S_ISDIR(mode) || S_ISREG(mode))
+                       mode |= (mode & 0100) ? 0777 : 0666;
                buffer = sha1 + 20;
                size -= namelen + 20;
 
index ca680c5b9c59ed5327f8ba447c1525253c8d2222..6759ecbf98f8a90d96b4918c130babdd87889f69 100644 (file)
@@ -1 +1,2 @@
 blt
+boilerplates.made
index 776e6c80097ff5e56659b36c8bf22194c8b9dace..07e928e56dd00678fdbe092b65bd16f7b7edfcc7 100644 (file)
@@ -1,17 +1,26 @@
 # make and install sample templates
 
-INSTALL=install
-prefix=$(HOME)
-template_dir=$(prefix)/share/git-core/templates/
+INSTALL ?= install
+TAR ?= tar
+prefix ?= $(HOME)
+template_dir ?= $(prefix)/share/git-core/templates/
 # DESTDIR=
 
-all: boilerplates custom
+# Shell quote;
+# Result of this needs to be placed inside ''
+shq = $(subst ','\'',$(1))
+# This has surrounding ''
+shellquote = '$(call shq,$(1))'
+
+all: boilerplates.made custom
        find blt
 
 # Put templates that can be copied straight from the source
 # in a file direc--tory--file in the source.  They will be
 # just copied to the destination.
-boilerplates:
+
+bpsrc = $(filter-out %~,$(wildcard *--*))
+boilerplates.made : $(bpsrc)
        ls *--* 2>/dev/null | \
        while read boilerplate; \
        do \
@@ -24,6 +33,7 @@ boilerplates:
                *) cp $$boilerplate blt/$$dst ;; \
                esac || exit; \
        done || exit
+       date >$@
 
 # If you need build-tailored templates, build them into blt/
 # directory yourself here.
@@ -31,8 +41,9 @@ custom:
        : no custom templates yet
 
 clean:
-       rm -rf blt
+       rm -rf blt boilerplates.made
 
 install: all
-       $(INSTALL) -d -m755 $(DESTDIR)$(template_dir)
-       tar Ccf blt - . | tar Cxf $(DESTDIR)$(template_dir) -
+       $(INSTALL) -d -m755 $(call shellquote,$(DESTDIR)$(template_dir))
+       (cd blt && $(TAR) cf - .) | \
+       (cd $(call shellquote,$(DESTDIR)$(template_dir)) && $(TAR) xf -)
diff --git a/tree-diff.c b/tree-diff.c
new file mode 100644 (file)
index 0000000..0ef06a9
--- /dev/null
@@ -0,0 +1,270 @@
+/*
+ * Helper functions for tree diff generation
+ */
+#include "cache.h"
+#include "diff.h"
+
+// What paths are we interested in?
+static int nr_paths = 0;
+static const char **paths = NULL;
+static int *pathlens = NULL;
+
+static void update_tree_entry(struct tree_desc *desc)
+{
+       void *buf = desc->buf;
+       unsigned long size = desc->size;
+       int len = strlen(buf) + 1 + 20;
+
+       if (size < len)
+               die("corrupt tree file");
+       desc->buf = buf + len;
+       desc->size = size - len;
+}
+
+static const unsigned char *extract(struct tree_desc *desc, const char **pathp, unsigned int *modep)
+{
+       void *tree = desc->buf;
+       unsigned long size = desc->size;
+       int len = strlen(tree)+1;
+       const unsigned char *sha1 = tree + len;
+       const char *path = strchr(tree, ' ');
+       unsigned int mode;
+
+       if (!path || size < len + 20 || sscanf(tree, "%o", &mode) != 1)
+               die("corrupt tree file");
+       *pathp = path+1;
+       *modep = DIFF_FILE_CANON_MODE(mode);
+       return sha1;
+}
+
+static char *malloc_base(const char *base, const char *path, int pathlen)
+{
+       int baselen = strlen(base);
+       char *newbase = xmalloc(baselen + pathlen + 2);
+       memcpy(newbase, base, baselen);
+       memcpy(newbase + baselen, path, pathlen);
+       memcpy(newbase + baselen + pathlen, "/", 2);
+       return newbase;
+}
+
+static int show_entry(struct diff_options *opt, const char *prefix, struct tree_desc *desc, const char *base);
+
+static int compare_tree_entry(struct tree_desc *t1, struct tree_desc *t2, const char *base, struct diff_options *opt)
+{
+       unsigned mode1, mode2;
+       const char *path1, *path2;
+       const unsigned char *sha1, *sha2;
+       int cmp, pathlen1, pathlen2;
+
+       sha1 = extract(t1, &path1, &mode1);
+       sha2 = extract(t2, &path2, &mode2);
+
+       pathlen1 = strlen(path1);
+       pathlen2 = strlen(path2);
+       cmp = base_name_compare(path1, pathlen1, mode1, path2, pathlen2, mode2);
+       if (cmp < 0) {
+               show_entry(opt, "-", t1, base);
+               return -1;
+       }
+       if (cmp > 0) {
+               show_entry(opt, "+", t2, base);
+               return 1;
+       }
+       if (!opt->find_copies_harder &&
+           !memcmp(sha1, sha2, 20) && mode1 == mode2)
+               return 0;
+
+       /*
+        * If the filemode has changed to/from a directory from/to a regular
+        * file, we need to consider it a remove and an add.
+        */
+       if (S_ISDIR(mode1) != S_ISDIR(mode2)) {
+               show_entry(opt, "-", t1, base);
+               show_entry(opt, "+", t2, base);
+               return 0;
+       }
+
+       if (opt->recursive && S_ISDIR(mode1)) {
+               int retval;
+               char *newbase = malloc_base(base, path1, pathlen1);
+               if (opt->tree_in_recursive)
+                       opt->change(opt, mode1, mode2,
+                                   sha1, sha2, base, path1);
+               retval = diff_tree_sha1(sha1, sha2, newbase, opt);
+               free(newbase);
+               return retval;
+       }
+
+       opt->change(opt, mode1, mode2, sha1, sha2, base, path1);
+       return 0;
+}
+
+static int interesting(struct tree_desc *desc, const char *base)
+{
+       const char *path;
+       unsigned mode;
+       int i;
+       int baselen, pathlen;
+
+       if (!nr_paths)
+               return 1;
+
+       (void)extract(desc, &path, &mode);
+
+       pathlen = strlen(path);
+       baselen = strlen(base);
+
+       for (i=0; i < nr_paths; i++) {
+               const char *match = paths[i];
+               int matchlen = pathlens[i];
+
+               if (baselen >= matchlen) {
+                       /* If it doesn't match, move along... */
+                       if (strncmp(base, match, matchlen))
+                               continue;
+
+                       /* The base is a subdirectory of a path which was specified. */
+                       return 1;
+               }
+
+               /* Does the base match? */
+               if (strncmp(base, match, baselen))
+                       continue;
+
+               match += baselen;
+               matchlen -= baselen;
+
+               if (pathlen > matchlen)
+                       continue;
+
+               if (matchlen > pathlen) {
+                       if (match[pathlen] != '/')
+                               continue;
+                       if (!S_ISDIR(mode))
+                               continue;
+               }
+
+               if (strncmp(path, match, pathlen))
+                       continue;
+
+               return 1;
+       }
+       return 0; /* No matches */
+}
+
+/* A whole sub-tree went away or appeared */
+static void show_tree(struct diff_options *opt, const char *prefix, struct tree_desc *desc, const char *base)
+{
+       while (desc->size) {
+               if (interesting(desc, base))
+                       show_entry(opt, prefix, desc, base);
+               update_tree_entry(desc);
+       }
+}
+
+/* A file entry went away or appeared */
+static int show_entry(struct diff_options *opt, const char *prefix, struct tree_desc *desc, const char *base)
+{
+       unsigned mode;
+       const char *path;
+       const unsigned char *sha1 = extract(desc, &path, &mode);
+
+       if (opt->recursive && S_ISDIR(mode)) {
+               char type[20];
+               char *newbase = malloc_base(base, path, strlen(path));
+               struct tree_desc inner;
+               void *tree;
+
+               tree = read_sha1_file(sha1, type, &inner.size);
+               if (!tree || strcmp(type, "tree"))
+                       die("corrupt tree sha %s", sha1_to_hex(sha1));
+
+               inner.buf = tree;
+               show_tree(opt, prefix, &inner, newbase);
+
+               free(tree);
+               free(newbase);
+               return 0;
+       }
+
+       opt->add_remove(opt, prefix[0], mode, sha1, base, path);
+       return 0;
+}
+
+int diff_tree(struct tree_desc *t1, struct tree_desc *t2, const char *base, struct diff_options *opt)
+{
+       while (t1->size | t2->size) {
+               if (nr_paths && t1->size && !interesting(t1, base)) {
+                       update_tree_entry(t1);
+                       continue;
+               }
+               if (nr_paths && t2->size && !interesting(t2, base)) {
+                       update_tree_entry(t2);
+                       continue;
+               }
+               if (!t1->size) {
+                       show_entry(opt, "+", t2, base);
+                       update_tree_entry(t2);
+                       continue;
+               }
+               if (!t2->size) {
+                       show_entry(opt, "-", t1, base);
+                       update_tree_entry(t1);
+                       continue;
+               }
+               switch (compare_tree_entry(t1, t2, base, opt)) {
+               case -1:
+                       update_tree_entry(t1);
+                       continue;
+               case 0:
+                       update_tree_entry(t1);
+                       /* Fallthrough */
+               case 1:
+                       update_tree_entry(t2);
+                       continue;
+               }
+               die("git-diff-tree: internal error");
+       }
+       return 0;
+}
+
+int diff_tree_sha1(const unsigned char *old, const unsigned char *new, const char *base, struct diff_options *opt)
+{
+       void *tree1, *tree2;
+       struct tree_desc t1, t2;
+       int retval;
+
+       tree1 = read_object_with_reference(old, "tree", &t1.size, NULL);
+       if (!tree1)
+               die("unable to read source tree (%s)", sha1_to_hex(old));
+       tree2 = read_object_with_reference(new, "tree", &t2.size, NULL);
+       if (!tree2)
+               die("unable to read destination tree (%s)", sha1_to_hex(new));
+       t1.buf = tree1;
+       t2.buf = tree2;
+       retval = diff_tree(&t1, &t2, base, opt);
+       free(tree1);
+       free(tree2);
+       return retval;
+}
+
+static int count_paths(const char **paths)
+{
+       int i = 0;
+       while (*paths++)
+               i++;
+       return i;
+}
+
+void diff_tree_setup_paths(const char **p)
+{
+       if (p) {
+               int i;
+
+               paths = p;
+               nr_paths = count_paths(paths);
+               pathlens = xmalloc(nr_paths * sizeof(int));
+               for (i=0; i<nr_paths; i++)
+                       pathlens[i] = strlen(paths[i]);
+       }
+}
index 8fe015b49946173881bf73f5453a04468db93352..5bbc3de2898caddfd019fac77c6a1daf922250cf 100644 (file)
@@ -4,6 +4,8 @@
  * Copyright (C) Linus Torvalds, 2005
  */
 #include "cache.h"
+#include "strbuf.h"
+#include "quote.h"
 
 /*
  * Default to not allowing changes to the list of files. The
  * like "git-update-index *" and suddenly having all the object
  * files be revision controlled.
  */
-static int allow_add = 0, allow_remove = 0, allow_replace = 0, not_new = 0, quiet = 0, info_only = 0;
+static int allow_add;
+static int allow_remove;
+static int allow_replace;
+static int allow_unmerged; /* --refresh needing merge is not error */
+static int not_new; /* --refresh not having working tree files is not error */
+static int quiet; /* --refresh needing update is not error */
+static int info_only;
 static int force_remove;
+static int verbose;
 
 /* Three functions to allow overloaded pointer return; see linux/err.h */
 static inline void *ERR_PTR(long error)
@@ -31,13 +40,24 @@ static inline long IS_ERR(const void *ptr)
        return (unsigned long)ptr > (unsigned long)-1000L;
 }
 
-static int add_file_to_cache(char *path)
+static void report(const char *fmt, ...)
+{
+       va_list vp;
+
+       if (!verbose)
+               return;
+
+       va_start(vp, fmt);
+       vprintf(fmt, vp);
+       putchar('\n');
+       va_end(vp);
+}
+
+static int add_file_to_cache(const char *path)
 {
        int size, namelen, option, status;
        struct cache_entry *ce;
        struct stat st;
-       int fd;
-       char *target;
 
        status = lstat(path, &st);
        if (status < 0 || S_ISDIR(st.st_mode)) {
@@ -68,42 +88,27 @@ static int add_file_to_cache(char *path)
                        return error("lstat(\"%s\"): %s", path,
                                     strerror(errno));
        }
+
        namelen = strlen(path);
        size = cache_entry_size(namelen);
        ce = xmalloc(size);
        memset(ce, 0, size);
        memcpy(ce->name, path, namelen);
        fill_stat_cache_info(ce, &st);
+
        ce->ce_mode = create_ce_mode(st.st_mode);
-       ce->ce_flags = htons(namelen);
-       switch (st.st_mode & S_IFMT) {
-       case S_IFREG:
-               fd = open(path, O_RDONLY);
-               if (fd < 0)
-                       return error("open(\"%s\"): %s", path, strerror(errno));
-               if (index_fd(ce->sha1, fd, &st, !info_only, NULL) < 0)
-                       return error("%s: failed to insert into database", path);
-               break;
-       case S_IFLNK:
-               target = xmalloc(st.st_size+1);
-               if (readlink(path, target, st.st_size+1) != st.st_size) {
-                       char *errstr = strerror(errno);
-                       free(target);
-                       return error("readlink(\"%s\"): %s", path,
-                                    errstr);
-               }
-               if (info_only) {
-                       unsigned char hdr[50];
-                       int hdrlen;
-                       write_sha1_file_prepare(target, st.st_size, "blob",
-                                               ce->sha1, hdr, &hdrlen);
-               } else if (write_sha1_file(target, st.st_size, "blob", ce->sha1))
-                       return error("%s: failed to insert into database", path);
-               free(target);
-               break;
-       default:
-               return error("%s: unsupported file type", path);
+       if (!trust_executable_bit) {
+               /* If there is an existing entry, pick the mode bits
+                * from it.
+                */
+               int pos = cache_name_pos(path, namelen);
+               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;
        option = allow_add ? ADD_CACHE_OK_TO_ADD : 0;
        option |= allow_replace ? ADD_CACHE_OK_TO_REPLACE : 0;
        if (add_cache_entry(ce, option))
@@ -112,47 +117,6 @@ static int add_file_to_cache(char *path)
        return 0;
 }
 
-static int compare_data(struct cache_entry *ce, struct stat *st)
-{
-       int match = -1;
-       int fd = open(ce->name, O_RDONLY);
-
-       if (fd >= 0) {
-               unsigned char sha1[20];
-               if (!index_fd(sha1, fd, st, 0, NULL))
-                       match = memcmp(sha1, ce->sha1, 20);
-               close(fd);
-       }
-       return match;
-}
-
-static int compare_link(struct cache_entry *ce, unsigned long expected_size)
-{
-       int match = -1;
-       char *target;
-       void *buffer;
-       unsigned long size;
-       char type[10];
-       int len;
-
-       target = xmalloc(expected_size);
-       len = readlink(ce->name, target, expected_size);
-       if (len != expected_size) {
-               free(target);
-               return -1;
-       }
-       buffer = read_sha1_file(ce->sha1, type, &size);
-       if (!buffer) {
-               free(target);
-               return -1;
-       }
-       if (size == expected_size)
-               match = memcmp(buffer, target, size);
-       free(buffer);
-       free(target);
-       return match;
-}
-
 /*
  * "refresh" does not calculate a new sha1 file or bring the
  * cache up-to-date for mode/content changes. But what it
@@ -175,27 +139,10 @@ static struct cache_entry *refresh_entry(struct cache_entry *ce)
 
        changed = ce_match_stat(ce, &st);
        if (!changed)
-               return ce;
-
-       /*
-        * If the mode or type has changed, there's no point in trying
-        * to refresh the entry - it's not going to match
-        */
-       if (changed & (MODE_CHANGED | TYPE_CHANGED))
-               return ERR_PTR(-EINVAL);
+               return NULL;
 
-       switch (st.st_mode & S_IFMT) {
-       case S_IFREG:
-               if (compare_data(ce, &st))
-                       return ERR_PTR(-EINVAL);
-               break;
-       case S_IFLNK:
-               if (compare_link(ce, st.st_size))
-                       return ERR_PTR(-EINVAL);
-               break;
-       default:
+       if (ce_modified(ce, &st))
                return ERR_PTR(-EINVAL);
-       }
 
        size = ce_size(ce);
        updated = xmalloc(size);
@@ -213,16 +160,20 @@ static int refresh_cache(void)
                struct cache_entry *ce, *new;
                ce = active_cache[i];
                if (ce_stage(ce)) {
-                       printf("%s: needs merge\n", ce->name);
-                       has_errors = 1;
                        while ((i < active_nr) &&
                               ! strcmp(active_cache[i]->name, ce->name))
                                i++;
                        i--;
+                       if (allow_unmerged)
+                               continue;
+                       printf("%s: needs merge\n", ce->name);
+                       has_errors = 1;
                        continue;
                }
 
                new = refresh_entry(ce);
+               if (!new)
+                       continue;
                if (IS_ERR(new)) {
                        if (not_new && PTR_ERR(new) == -ENOENT)
                                continue;
@@ -279,7 +230,7 @@ static int verify_dotfile(const char *rest)
        return 1;
 }
 
-static int verify_path(char *path)
+static int verify_path(const char *path)
 {
        char c;
 
@@ -305,7 +256,7 @@ static int verify_path(char *path)
        }
 }
 
-static int add_cacheinfo(char *arg1, char *arg2, char *arg3)
+static int add_cacheinfo(const char *arg1, const char *arg2, const char *arg3)
 {
        int size, len, option;
        unsigned int mode;
@@ -330,16 +281,129 @@ static int add_cacheinfo(char *arg1, char *arg2, char *arg3)
        ce->ce_mode = create_ce_mode(mode);
        option = allow_add ? ADD_CACHE_OK_TO_ADD : 0;
        option |= allow_replace ? ADD_CACHE_OK_TO_REPLACE : 0;
-       return add_cache_entry(ce, option);
+       if (add_cache_entry(ce, option))
+               return error("%s: cannot add to the index - missing --add option?",
+                            arg3);
+       report("add '%s'", arg3);
+       return 0;
+}
+
+static int chmod_path(int flip, const char *path)
+{
+       int pos;
+       struct cache_entry *ce;
+       unsigned int mode;
+
+       pos = cache_name_pos(path, strlen(path));
+       if (pos < 0)
+               return -1;
+       ce = active_cache[pos];
+       mode = ntohl(ce->ce_mode);
+       if (!S_ISREG(mode))
+               return -1;
+       switch (flip) {
+       case '+':
+               ce->ce_mode |= htonl(0111); break;
+       case '-':
+               ce->ce_mode &= htonl(~0111); break;
+       default:
+               return -1;
+       }
+       active_cache_changed = 1;
+       return 0;
 }
 
 static struct cache_file cache_file;
 
-int main(int argc, char **argv)
+static void update_one(const char *path, const char *prefix, int prefix_length)
 {
-       int i, newfd, entries, has_errors = 0;
+       const char *p = prefix_path(prefix, prefix_length, path);
+       if (!verify_path(p)) {
+               fprintf(stderr, "Ignoring path %s\n", path);
+               return;
+       }
+       if (force_remove) {
+               if (remove_file_from_cache(p))
+                       die("git-update-index: unable to remove %s", path);
+               report("remove '%s'", path);
+               return;
+       }
+       if (add_file_to_cache(p))
+               die("Unable to process file %s", path);
+       report("add '%s'", path);
+}
+
+static void read_index_info(int line_termination)
+{
+       struct strbuf buf;
+       strbuf_init(&buf);
+       while (1) {
+               char *ptr;
+               char *path_name;
+               unsigned char sha1[20];
+               unsigned int mode;
+
+               read_line(&buf, stdin, line_termination);
+               if (buf.eof)
+                       break;
+
+               mode = strtoul(buf.buf, &ptr, 8);
+               if (ptr == buf.buf || *ptr != ' ' ||
+                   get_sha1_hex(ptr + 1, sha1) ||
+                   ptr[41] != '\t')
+                       goto bad_line;
+
+               ptr += 42;
+
+               if (line_termination && ptr[0] == '"')
+                       path_name = unquote_c_style(ptr, NULL);
+               else
+                       path_name = ptr;
+
+               if (!verify_path(path_name)) {
+                       fprintf(stderr, "Ignoring path %s\n", path_name);
+                       if (path_name != ptr)
+                               free(path_name);
+                       continue;
+               }
+
+               if (!mode) {
+                       /* mode == 0 means there is no such path -- remove */
+                       if (remove_file_from_cache(path_name))
+                               die("git-update-index: unable to remove %s",
+                                   ptr);
+               }
+               else {
+                       /* mode ' ' sha1 '\t' name
+                        * ptr[-1] points at tab,
+                        * ptr[-41] is at the beginning of sha1
+                        */
+                       ptr[-42] = ptr[-1] = 0;
+                       if (add_cacheinfo(buf.buf, ptr-41, path_name))
+                               die("git-update-index: unable to update %s",
+                                   path_name);
+               }
+               if (path_name != ptr)
+                       free(path_name);
+               continue;
+
+       bad_line:
+               die("malformed index info %s", buf.buf);
+       }
+}
+
+static const char update_index_usage[] =
+"git-update-index [-q] [--add] [--replace] [--remove] [--unmerged] [--refresh] [--cacheinfo] [--chmod=(+|-)x] [--info-only] [--force-remove] [--stdin] [--index-info] [--ignore-missing] [-z] [--verbose] [--] <file>...";
+
+int main(int argc, const char **argv)
+{
+       int i, newfd, entries, has_errors = 0, line_termination = '\n';
        int allow_options = 1;
+       int read_from_stdin = 0;
        const char *prefix = setup_git_directory();
+       int prefix_length = prefix ? strlen(prefix) : 0;
+
+       git_config(git_default_config);
 
        newfd = hold_index_file_for_update(&cache_file, get_index_file());
        if (newfd < 0)
@@ -350,7 +414,7 @@ int main(int argc, char **argv)
                die("cache corrupted");
 
        for (i = 1 ; i < argc; i++) {
-               char *path = argv[i];
+               const char *path = argv[i];
 
                if (allow_options && *path == '-') {
                        if (!strcmp(path, "--")) {
@@ -373,6 +437,10 @@ int main(int argc, char **argv)
                                allow_remove = 1;
                                continue;
                        }
+                       if (!strcmp(path, "--unmerged")) {
+                               allow_unmerged = 1;
+                               continue;
+                       }
                        if (!strcmp(path, "--refresh")) {
                                has_errors |= refresh_cache();
                                continue;
@@ -385,6 +453,14 @@ int main(int argc, char **argv)
                                i += 3;
                                continue;
                        }
+                       if (!strcmp(path, "--chmod=-x") ||
+                           !strcmp(path, "--chmod=+x")) {
+                               if (argc <= i+1)
+                                       die("git-update-index: %s <path>", path);
+                               if (chmod_path(path[8], argv[++i]))
+                                       die("git-update-index: %s cannot chmod %s", path, argv[i]);
+                               continue;
+                       }
                        if (!strcmp(path, "--info-only")) {
                                info_only = 1;
                                continue;
@@ -393,29 +469,50 @@ int main(int argc, char **argv)
                                force_remove = 1;
                                continue;
                        }
-
+                       if (!strcmp(path, "-z")) {
+                               line_termination = 0;
+                               continue;
+                       }
+                       if (!strcmp(path, "--stdin")) {
+                               if (i != argc - 1)
+                                       die("--stdin must be at the end");
+                               read_from_stdin = 1;
+                               break;
+                       }
+                       if (!strcmp(path, "--index-info")) {
+                               allow_add = allow_replace = allow_remove = 1;
+                               read_index_info(line_termination);
+                               continue;
+                       }
                        if (!strcmp(path, "--ignore-missing")) {
                                not_new = 1;
                                continue;
                        }
+                       if (!strcmp(path, "--verbose")) {
+                               verbose = 1;
+                               continue;
+                       }
+                       if (!strcmp(path, "-h") || !strcmp(path, "--help"))
+                               usage(update_index_usage);
                        die("unknown option %s", path);
                }
-               path = prefix_path(prefix, prefix ? strlen(prefix) : 0, path);
-               if (!verify_path(path)) {
-                       fprintf(stderr, "Ignoring path %s\n", argv[i]);
-                       continue;
-               }
-               if (force_remove) {
-                       if (remove_file_from_cache(path))
-                               die("git-update-index: unable to remove %s", path);
-                       continue;
+               update_one(path, prefix, prefix_length);
+       }
+       if (read_from_stdin) {
+               struct strbuf buf;
+               strbuf_init(&buf);
+               while (1) {
+                       read_line(&buf, stdin, line_termination);
+                       if (buf.eof)
+                               break;
+                       update_one(buf.buf, prefix, prefix_length);
                }
-               if (add_file_to_cache(path))
-                       die("Unable to process file %s", path);
        }
-       if (write_cache(newfd, active_cache, active_nr) ||
-           commit_index_file(&cache_file))
-               die("Unable to write new cachefile");
+       if (active_cache_changed) {
+               if (write_cache(newfd, active_cache, active_nr) ||
+                   commit_index_file(&cache_file))
+                       die("Unable to write new cachefile");
+       }
 
        return has_errors ? 1 : 0;
 }
diff --git a/update-ref.c b/update-ref.c
new file mode 100644 (file)
index 0000000..65dc3d6
--- /dev/null
@@ -0,0 +1,81 @@
+#include "cache.h"
+#include "refs.h"
+
+static const char git_update_ref_usage[] = "git-update-ref <refname> <value> [<oldval>]";
+
+static int re_verify(const char *path, unsigned char *oldsha1, unsigned char *currsha1)
+{
+       char buf[40];
+       int fd = open(path, O_RDONLY), nr;
+       if (fd < 0)
+               return -1;
+       nr = read(fd, buf, 40);
+       close(fd);
+       if (nr != 40 || get_sha1_hex(buf, currsha1) < 0)
+               return -1;
+       return memcmp(oldsha1, currsha1, 20) ? -1 : 0;
+}
+
+int main(int argc, char **argv)
+{
+       char *hex;
+       const char *refname, *value, *oldval, *path, *lockpath;
+       unsigned char sha1[20], oldsha1[20], currsha1[20];
+       int fd, written;
+
+       setup_git_directory();
+       if (argc < 3 || argc > 4)
+               usage(git_update_ref_usage);
+
+       refname = argv[1];
+       value = argv[2];
+       oldval = argv[3];
+       if (get_sha1(value, sha1) < 0)
+               die("%s: not a valid SHA1", value);
+       memset(oldsha1, 0, 20);
+       if (oldval && get_sha1(oldval, oldsha1) < 0)
+               die("%s: not a valid old SHA1", oldval);
+
+       path = resolve_ref(git_path("%s", refname), currsha1, !!oldval);
+       if (!path)
+               die("No such ref: %s", refname);
+
+       if (oldval) {
+               if (memcmp(currsha1, oldsha1, 20))
+                       die("Ref %s changed to %s", refname, sha1_to_hex(currsha1));
+               /* Nothing to do? */
+               if (!memcmp(oldsha1, sha1, 20))
+                       exit(0);
+       }
+       path = strdup(path);
+       lockpath = mkpath("%s.lock", path);
+
+       fd = open(lockpath, O_CREAT | O_EXCL | O_WRONLY, 0666);
+       if (fd < 0)
+               die("Unable to create %s", lockpath);
+       hex = sha1_to_hex(sha1);
+       hex[40] = '\n';
+       written = write(fd, hex, 41);
+       close(fd);
+       if (written != 41) {
+               unlink(lockpath);
+               die("Unable to write to %s", lockpath);
+       }
+
+       /*
+        * Re-read the ref after getting the lock to verify
+        */
+       if (oldval && re_verify(path, oldsha1, currsha1) < 0) {
+               unlink(lockpath);
+               die("Ref lock failed");
+       }
+
+       /*
+        * Finally, replace the old ref with the new one
+        */
+       if (rename(lockpath, path) < 0) {
+               unlink(lockpath);
+               die("Unable to create %s", path);
+       }
+       return 0;
+}
index da10742c44201cfb6f86eb8d80f3be27940335ff..be63132804252e9a2f94ce1166ef9fa319a845fd 100644 (file)
@@ -1,14 +1,26 @@
 #include "cache.h"
 #include "refs.h"
 #include "pkt-line.h"
+#include "tag.h"
+#include "object.h"
+#include "commit.h"
 
-static const char upload_pack_usage[] = "git-upload-pack <dir>";
+static const char upload_pack_usage[] = "git-upload-pack [--strict] [--timeout=nn] <dir>";
 
-#define MAX_HAS (16)
-#define MAX_NEEDS (256)
-static int nr_has = 0, nr_needs = 0;
+#define THEY_HAVE (1U << 0)
+#define OUR_REF (1U << 1)
+#define WANTED (1U << 2)
+#define MAX_HAS 256
+#define MAX_NEEDS 256
+static int nr_has = 0, nr_needs = 0, multi_ack = 0, nr_our_refs = 0;
 static unsigned char has_sha1[MAX_HAS][20];
 static unsigned char needs_sha1[MAX_NEEDS][20];
+static unsigned int timeout = 0;
+
+static void reset_timeout(void)
+{
+       alarm(timeout);
+}
 
 static int strip(char *line, int len)
 {
@@ -21,6 +33,7 @@ static void create_pack_file(void)
 {
        int fd[2];
        pid_t pid;
+       int create_full_pack = (nr_our_refs == nr_needs && !nr_has);
 
        if (pipe(fd) < 0)
                die("git-upload-pack: unable to create pipe");
@@ -30,10 +43,18 @@ static void create_pack_file(void)
 
        if (!pid) {
                int i;
-               int args = nr_has + nr_needs + 5;
-               char **argv = xmalloc(args * sizeof(char *));
-               char *buf = xmalloc(args * 45);
-               char **p = argv;
+               int args;
+               char **argv;
+               char *buf;
+               char **p;
+
+               if (create_full_pack)
+                       args = 10;
+               else
+                       args = nr_has + nr_needs + 5;
+               argv = xmalloc(args * sizeof(char *));
+               buf = xmalloc(args * 45);
+               p = argv;
 
                dup2(fd[1], 1);
                close(0);
@@ -41,17 +62,22 @@ static void create_pack_file(void)
                close(fd[1]);
                *p++ = "git-rev-list";
                *p++ = "--objects";
-               for (i = 0; i < nr_needs; i++) {
-                       *p++ = buf;
-                       memcpy(buf, sha1_to_hex(needs_sha1[i]), 41);
-                       buf += 41;
-               }
-               for (i = 0; i < nr_has; i++) {
-                       *p++ = buf;
-                       *buf++ = '^';
-                       memcpy(buf, sha1_to_hex(has_sha1[i]), 41);
-                       buf += 41;
+               if (create_full_pack || MAX_NEEDS <= nr_needs)
+                       *p++ = "--all";
+               else {
+                       for (i = 0; i < nr_needs; i++) {
+                               *p++ = buf;
+                               memcpy(buf, sha1_to_hex(needs_sha1[i]), 41);
+                               buf += 41;
+                       }
                }
+               if (!create_full_pack)
+                       for (i = 0; i < nr_has; i++) {
+                               *p++ = buf;
+                               *buf++ = '^';
+                               memcpy(buf, sha1_to_hex(has_sha1[i]), 41);
+                               buf += 41;
+                       }
                *p++ = NULL;
                execvp("git-rev-list", argv);
                die("git-upload-pack: unable to exec git-rev-list");
@@ -65,15 +91,27 @@ static void create_pack_file(void)
 
 static int got_sha1(char *hex, unsigned char *sha1)
 {
-       int nr;
        if (get_sha1_hex(hex, sha1))
                die("git-upload-pack: expected SHA1 object, got '%s'", hex);
        if (!has_sha1_file(sha1))
                return 0;
-       nr = nr_has;
-       if (nr < MAX_HAS) {
-               memcpy(has_sha1[nr], sha1, 20);
-               nr_has = nr+1;
+       if (nr_has < MAX_HAS) {
+               struct object *o = lookup_object(sha1);
+               if (!(o && o->parsed))
+                       o = parse_object(sha1);
+               if (!o)
+                       die("oops (%s)", sha1_to_hex(sha1));
+               if (o->type == commit_type) {
+                       struct commit_list *parents;
+                       if (o->flags & THEY_HAVE)
+                               return 0;
+                       o->flags |= THEY_HAVE;
+                       for (parents = ((struct commit*)o)->parents;
+                            parents;
+                            parents = parents->next)
+                               parents->item->object.flags |= THEY_HAVE;
+               }
+               memcpy(has_sha1[nr_has++], sha1, 20);
        }
        return 1;
 }
@@ -81,45 +119,47 @@ static int got_sha1(char *hex, unsigned char *sha1)
 static int get_common_commits(void)
 {
        static char line[1000];
-       unsigned char sha1[20];
+       unsigned char sha1[20], last_sha1[20];
        int len;
 
+       track_object_refs = 0;
+       save_commit_buffer = 0;
+
        for(;;) {
                len = packet_read_line(0, line, sizeof(line));
+               reset_timeout();
 
                if (!len) {
-                       packet_write(1, "NAK\n");
+                       if (nr_has == 0 || multi_ack)
+                               packet_write(1, "NAK\n");
                        continue;
                }
                len = strip(line, len);
                if (!strncmp(line, "have ", 5)) {
-                       if (got_sha1(line+5, sha1)) {
-                               packet_write(1, "ACK %s\n", sha1_to_hex(sha1));
-                               break;
+                       if (got_sha1(line+5, sha1) &&
+                                       (multi_ack || nr_has == 1)) {
+                               if (nr_has >= MAX_HAS)
+                                       multi_ack = 0;
+                               packet_write(1, "ACK %s%s\n",
+                                       sha1_to_hex(sha1),
+                                       multi_ack ?  " continue" : "");
+                               if (multi_ack)
+                                       memcpy(last_sha1, sha1, 20);
                        }
                        continue;
                }
                if (!strcmp(line, "done")) {
+                       if (nr_has > 0) {
+                               if (multi_ack)
+                                       packet_write(1, "ACK %s\n",
+                                                       sha1_to_hex(last_sha1));
+                               return 0;
+                       }
                        packet_write(1, "NAK\n");
                        return -1;
                }
                die("git-upload-pack: expected SHA1 list, got '%s'", line);
        }
-
-       for (;;) {
-               len = packet_read_line(0, line, sizeof(line));
-               if (!len)
-                       continue;
-               len = strip(line, len);
-               if (!strncmp(line, "have ", 5)) {
-                       got_sha1(line+5, sha1);
-                       continue;
-               }
-               if (!strcmp(line, "done"))
-                       break;
-               die("git-upload-pack: expected SHA1 list, got '%s'", line);
-       }
-       return 0;
 }
 
 static int receive_needs(void)
@@ -129,30 +169,72 @@ static int receive_needs(void)
 
        needs = 0;
        for (;;) {
+               struct object *o;
+               unsigned char dummy[20], *sha1_buf;
                len = packet_read_line(0, line, sizeof(line));
+               reset_timeout();
                if (!len)
                        return needs;
 
-               /*
-                * This is purely theoretical right now: git-fetch-pack only
-                * ever asks for a single HEAD
+               sha1_buf = dummy;
+               if (needs == MAX_NEEDS) {
+                       fprintf(stderr,
+                               "warning: supporting only a max of %d requests. "
+                               "sending everything instead.\n",
+                               MAX_NEEDS);
+               }
+               else if (needs < MAX_NEEDS)
+                       sha1_buf = needs_sha1[needs];
+
+               if (strncmp("want ", line, 5) || get_sha1_hex(line+5, sha1_buf))
+                       die("git-upload-pack: protocol error, "
+                           "expected to get sha, not '%s'", line);
+               if (strstr(line+45, "multi_ack"))
+                       multi_ack = 1;
+
+               /* We have sent all our refs already, and the other end
+                * should have chosen out of them; otherwise they are
+                * asking for nonsense.
+                *
+                * Hmph.  We may later want to allow "want" line that
+                * asks for something like "master~10" (symbolic)...
+                * would it make sense?  I don't know.
                 */
-               if (needs >= MAX_NEEDS)
-                       die("I'm only doing a max of %d requests", MAX_NEEDS);
-               if (strncmp("want ", line, 5) || get_sha1_hex(line+5, needs_sha1[needs]))
-                       die("git-upload-pack: protocol error, expected to get sha, not '%s'", line);
-               needs++;
+               o = lookup_object(sha1_buf);
+               if (!o || !(o->flags & OUR_REF))
+                       die("git-upload-pack: not our ref %s", line+5);
+               if (!(o->flags & WANTED)) {
+                       o->flags |= WANTED;
+                       needs++;
+               }
        }
 }
 
 static int send_ref(const char *refname, const unsigned char *sha1)
 {
-       packet_write(1, "%s %s\n", sha1_to_hex(sha1), refname);
+       static char *capabilities = "multi_ack";
+       struct object *o = parse_object(sha1);
+
+       if (capabilities)
+               packet_write(1, "%s %s%c%s\n", sha1_to_hex(sha1), refname,
+                       0, capabilities);
+       else
+               packet_write(1, "%s %s\n", sha1_to_hex(sha1), refname);
+       capabilities = NULL;
+       if (!(o->flags & OUR_REF)) {
+               o->flags |= OUR_REF;
+               nr_our_refs++;
+       }
+       if (o->type == tag_type) {
+               o = deref_tag(o, refname, 0);
+               packet_write(1, "%s %s^{}\n", sha1_to_hex(o->sha1), refname);
+       }
        return 0;
 }
 
 static int upload_pack(void)
 {
+       reset_timeout();
        head_ref(send_ref);
        for_each_ref(send_ref);
        packet_flush(1);
@@ -167,18 +249,43 @@ static int upload_pack(void)
 int main(int argc, char **argv)
 {
        const char *dir;
-       if (argc != 2)
+       int i;
+       int strict = 0;
+
+       for (i = 1; i < argc; i++) {
+               char *arg = argv[i];
+
+               if (arg[0] != '-')
+                       break;
+               if (!strcmp(arg, "--strict")) {
+                       strict = 1;
+                       continue;
+               }
+               if (!strncmp(arg, "--timeout=", 10)) {
+                       timeout = atoi(arg+10);
+                       continue;
+               }
+               if (!strcmp(arg, "--")) {
+                       i++;
+                       break;
+               }
+       }
+       
+       if (i != argc-1)
                usage(upload_pack_usage);
-       dir = argv[1];
+       dir = argv[i];
 
        /* chdir to the directory. If that fails, try appending ".git" */
        if (chdir(dir) < 0) {
-               if (chdir(mkpath("%s.git", dir)) < 0)
+               if (strict || chdir(mkpath("%s.git", dir)) < 0)
                        die("git-upload-pack unable to chdir to %s", dir);
        }
-       chdir(".git");
+       if (!strict)
+               chdir(".git");
+
        if (access("objects", X_OK) || access("refs", X_OK))
                die("git-upload-pack: %s doesn't seem to be a git archive", dir);
+
        putenv("GIT_DIR=.");
        upload_pack();
        return 0;
diff --git a/usage.c b/usage.c
index 86211c9141b788f6dbdf7b192f0ac4f4e2527d64..dfa87fe1191862b2c650e55fdd727802ef00cf34 100644 (file)
--- a/usage.c
+++ b/usage.c
@@ -15,7 +15,7 @@ static void report(const char *prefix, const char *err, va_list params)
 void usage(const char *err)
 {
        fprintf(stderr, "usage: %s\n", err);
-       exit(1);
+       exit(129);
 }
 
 void die(const char *err, ...)
@@ -25,7 +25,7 @@ void die(const char *err, ...)
        va_start(params, err);
        report("fatal: ", err, params);
        va_end(params);
-       exit(1);
+       exit(128);
 }
 
 int error(const char *err, ...)
diff --git a/var.c b/var.c
index 3f13126cb8b88593fa81dda875e8e4080a27ae03..51cf86a5843acc3b6bc3d8c4be9fec0fdd0a0df5 100644 (file)
--- a/var.c
+++ b/var.c
@@ -42,6 +42,15 @@ static const char *read_var(const char *var)
        return val;
 }
 
+static int show_config(const char *var, const char *value)
+{
+       if (value)
+               printf("%s=%s\n", var, value);
+       else
+               printf("%s\n", var);
+       return git_default_config(var, value);
+}
+
 int main(int argc, char **argv)
 {
        const char *val;
@@ -52,9 +61,11 @@ int main(int argc, char **argv)
        val = NULL;
 
        if (strcmp(argv[1], "-l") == 0) {
+               git_config(show_config);
                list_vars();
                return 0;
        }
+       git_config(git_default_config);
        val = read_var(argv[1]);
        if (!val)
                usage(var_usage);
index 80b60a6b7cdfb2f45bb34331dbc372fcbabe8c5a..c99db9dd79315dff4ac19c79b35275cd02397e60 100644 (file)
@@ -15,12 +15,12 @@ static int verify_one_pack(char *arg, int verbose)
                        len--;
                }
                /* Should name foo.idx now */
-               if ((g = add_packed_git(arg, len)))
+               if ((g = add_packed_git(arg, len, 1)))
                        break;
                /* No?  did you name just foo? */
                strcpy(arg + len, ".idx");
                len += 4;
-               if ((g = add_packed_git(arg, len)))
+               if ((g = add_packed_git(arg, len, 1)))
                        break;
                return error("packfile %s not found.", arg);
        }