Merge with gitk.
authorJunio C Hamano <junkio@cox.net>
Fri, 12 Aug 2005 01:24:35 +0000 (18:24 -0700)
committerJunio C Hamano <junkio@cox.net>
Fri, 12 Aug 2005 01:24:35 +0000 (18:24 -0700)
This merges commit
    b664550c066810b770ad3e19cafe2fbdd42c6793
from gitk into our head commit
    bf570303153902ec3d85570ed24515bcf8948848

Sincerely,
    jit-merge command.

298 files changed:
COPYING [new file with mode: 0644]
Documentation/Makefile [new file with mode: 0644]
Documentation/cvs-migration.txt [new file with mode: 0644]
Documentation/diff-format.txt [new file with mode: 0644]
Documentation/diff-options.txt [new file with mode: 0644]
Documentation/diffcore.txt [new file with mode: 0644]
Documentation/git-apply-patch-script.txt [new file with mode: 0644]
Documentation/git-apply.txt [new file with mode: 0644]
Documentation/git-cat-file.txt [new file with mode: 0644]
Documentation/git-checkout-cache.txt [new file with mode: 0644]
Documentation/git-clone-pack.txt [new file with mode: 0644]
Documentation/git-clone-script.txt [new file with mode: 0644]
Documentation/git-commit-script.txt [new file with mode: 0644]
Documentation/git-commit-tree.txt [new file with mode: 0644]
Documentation/git-convert-cache.txt [new file with mode: 0644]
Documentation/git-cvsimport-script.txt [new file with mode: 0644]
Documentation/git-diff-cache.txt [new file with mode: 0644]
Documentation/git-diff-files.txt [new file with mode: 0644]
Documentation/git-diff-helper.txt [new file with mode: 0644]
Documentation/git-diff-stages.txt [new file with mode: 0644]
Documentation/git-diff-tree.txt [new file with mode: 0644]
Documentation/git-export.txt [new file with mode: 0644]
Documentation/git-fetch-pack.txt [new file with mode: 0644]
Documentation/git-fetch-script.txt [new file with mode: 0644]
Documentation/git-fsck-cache.txt [new file with mode: 0644]
Documentation/git-hash-object.txt [new file with mode: 0644]
Documentation/git-http-pull.txt [new file with mode: 0644]
Documentation/git-init-db.txt [new file with mode: 0644]
Documentation/git-local-pull.txt [new file with mode: 0644]
Documentation/git-ls-files.txt [new file with mode: 0644]
Documentation/git-ls-remote-script.txt [new file with mode: 0644]
Documentation/git-ls-tree.txt [new file with mode: 0644]
Documentation/git-merge-base.txt [new file with mode: 0644]
Documentation/git-merge-cache.txt [new file with mode: 0644]
Documentation/git-merge-one-file-script.txt [new file with mode: 0644]
Documentation/git-mktag.txt [new file with mode: 0644]
Documentation/git-pack-objects.txt [new file with mode: 0644]
Documentation/git-peek-remote.txt [new file with mode: 0644]
Documentation/git-prune-script.txt [new file with mode: 0644]
Documentation/git-pull-script.txt [new file with mode: 0644]
Documentation/git-read-tree.txt [new file with mode: 0644]
Documentation/git-receive-pack.txt [new file with mode: 0644]
Documentation/git-rename-script.txt [new file with mode: 0644]
Documentation/git-resolve-script.txt [new file with mode: 0644]
Documentation/git-rev-list.txt [new file with mode: 0644]
Documentation/git-rev-tree.txt [new file with mode: 0644]
Documentation/git-send-email-script.txt [new file with mode: 0644]
Documentation/git-send-pack.txt [new file with mode: 0644]
Documentation/git-show-index.txt [new file with mode: 0644]
Documentation/git-ssh-pull.txt [new file with mode: 0644]
Documentation/git-ssh-push.txt [new file with mode: 0644]
Documentation/git-tag-script.txt [new file with mode: 0644]
Documentation/git-tar-tree.txt [new file with mode: 0644]
Documentation/git-unpack-file.txt [new file with mode: 0644]
Documentation/git-unpack-objects.txt [new file with mode: 0644]
Documentation/git-update-cache.txt [new file with mode: 0644]
Documentation/git-update-server-info.txt [new file with mode: 0644]
Documentation/git-upload-pack.txt [new file with mode: 0644]
Documentation/git-var.txt [new file with mode: 0644]
Documentation/git-verify-pack.txt [new file with mode: 0644]
Documentation/git-write-tree.txt [new file with mode: 0644]
Documentation/git.txt [new file with mode: 0644]
Documentation/pack-protocol.txt [new file with mode: 0644]
Documentation/pull-fetch-param.txt [new file with mode: 0644]
Documentation/tutorial.txt [new file with mode: 0644]
INSTALL [new file with mode: 0644]
Makefile [new file with mode: 0644]
README [new file with mode: 0644]
apply.c [new file with mode: 0644]
blob.c [new file with mode: 0644]
blob.h [new file with mode: 0644]
build-rev-cache.c [new file with mode: 0644]
cache.h [new file with mode: 0644]
cat-file.c [new file with mode: 0644]
checkout-cache.c [new file with mode: 0644]
clone-pack.c [new file with mode: 0644]
commit-tree.c [new file with mode: 0644]
commit.c [new file with mode: 0644]
commit.h [new file with mode: 0644]
connect.c [new file with mode: 0644]
convert-cache.c [new file with mode: 0644]
count-delta.c [new file with mode: 0644]
count-delta.h [new file with mode: 0644]
csum-file.c [new file with mode: 0644]
csum-file.h [new file with mode: 0644]
daemon.c [new file with mode: 0644]
date.c [new file with mode: 0644]
debian/changelog [new file with mode: 0644]
debian/compat [new file with mode: 0644]
debian/control [new file with mode: 0644]
debian/copyright [new file with mode: 0644]
debian/docs [new file with mode: 0644]
debian/git-core.doc-base [new file with mode: 0644]
debian/git-core.install [new file with mode: 0644]
debian/rules [new file with mode: 0755]
delta.h [new file with mode: 0644]
diff-cache.c [new file with mode: 0644]
diff-delta.c [new file with mode: 0644]
diff-files.c [new file with mode: 0644]
diff-helper.c [new file with mode: 0644]
diff-stages.c [new file with mode: 0644]
diff-tree.c [new file with mode: 0644]
diff.c [new file with mode: 0644]
diff.h [new file with mode: 0644]
diffcore-break.c [new file with mode: 0644]
diffcore-order.c [new file with mode: 0644]
diffcore-pathspec.c [new file with mode: 0644]
diffcore-pickaxe.c [new file with mode: 0644]
diffcore-rename.c [new file with mode: 0644]
diffcore.h [new file with mode: 0644]
entry.c [new file with mode: 0644]
epoch.c [new file with mode: 0644]
epoch.h [new file with mode: 0644]
export.c [new file with mode: 0644]
fetch-pack.c [new file with mode: 0644]
fsck-cache.c [new file with mode: 0644]
get-tar-commit-id.c [new file with mode: 0644]
git [new file with mode: 0755]
git-add-script [new file with mode: 0755]
git-apply-patch-script [new file with mode: 0755]
git-bisect-script [new file with mode: 0755]
git-branch-script [new file with mode: 0755]
git-checkout-script [new file with mode: 0755]
git-cherry [new file with mode: 0755]
git-clone-dumb-http [new file with mode: 0755]
git-clone-script [new file with mode: 0755]
git-commit-script [new file with mode: 0755]
git-core.spec.in [new file with mode: 0644]
git-count-objects-script [new file with mode: 0755]
git-cvsimport-script [new file with mode: 0755]
git-diff-script [new file with mode: 0755]
git-external-diff-script [new file with mode: 0755]
git-fetch-script [new file with mode: 0755]
git-format-patch-script [new file with mode: 0755]
git-log-script [new file with mode: 0755]
git-ls-remote-script [new file with mode: 0755]
git-merge-one-file-script [new file with mode: 0755]
git-parse-remote [new file with mode: 0755]
git-prune-script [new file with mode: 0755]
git-pull-script [new file with mode: 0755]
git-push-script [new file with mode: 0755]
git-rebase-script [new file with mode: 0755]
git-relink-script [new file with mode: 0755]
git-rename-script [new file with mode: 0755]
git-repack-script [new file with mode: 0755]
git-request-pull-script [new file with mode: 0755]
git-reset-script [new file with mode: 0755]
git-resolve-script [new file with mode: 0755]
git-revert-script [new file with mode: 0755]
git-send-email-script [new file with mode: 0755]
git-sh-setup-script [new file with mode: 0755]
git-shortlog [new file with mode: 0755]
git-status-script [new file with mode: 0755]
git-tag-script [new file with mode: 0755]
git-verify-tag-script [new file with mode: 0755]
git-whatchanged [new file with mode: 0755]
gitenv.c [new file with mode: 0644]
hash-object.c [new file with mode: 0644]
http-pull.c [new file with mode: 0644]
ident.c [new file with mode: 0644]
index.c [new file with mode: 0644]
init-db.c [new file with mode: 0644]
local-pull.c [new file with mode: 0644]
ls-files.c [new file with mode: 0644]
ls-tree.c [new file with mode: 0644]
merge-base.c [new file with mode: 0644]
merge-cache.c [new file with mode: 0644]
mktag.c [new file with mode: 0644]
mozilla-sha1/sha1.c [new file with mode: 0644]
mozilla-sha1/sha1.h [new file with mode: 0644]
object.c [new file with mode: 0644]
object.h [new file with mode: 0644]
pack-check.c [new file with mode: 0644]
pack-objects.c [new file with mode: 0644]
pack.h [new file with mode: 0644]
patch-delta.c [new file with mode: 0644]
patch-id.c [new file with mode: 0644]
path.c [new file with mode: 0644]
peek-remote.c [new file with mode: 0644]
pkt-line.c [new file with mode: 0644]
pkt-line.h [new file with mode: 0644]
ppc/sha1.c [new file with mode: 0644]
ppc/sha1.h [new file with mode: 0644]
ppc/sha1ppc.S [new file with mode: 0644]
prune-packed.c [new file with mode: 0644]
pull.c [new file with mode: 0644]
pull.h [new file with mode: 0644]
quote.c [new file with mode: 0644]
quote.h [new file with mode: 0644]
read-cache.c [new file with mode: 0644]
read-tree.c [new file with mode: 0644]
receive-pack.c [new file with mode: 0644]
refs.c [new file with mode: 0644]
refs.h [new file with mode: 0644]
rev-cache.c [new file with mode: 0644]
rev-cache.h [new file with mode: 0644]
rev-list.c [new file with mode: 0644]
rev-parse.c [new file with mode: 0644]
rev-tree.c [new file with mode: 0644]
rsh.c [new file with mode: 0644]
rsh.h [new file with mode: 0644]
run-command.c [new file with mode: 0644]
run-command.h [new file with mode: 0644]
send-pack.c [new file with mode: 0644]
server-info.c [new file with mode: 0644]
sha1_file.c [new file with mode: 0644]
sha1_name.c [new file with mode: 0644]
show-index.c [new file with mode: 0644]
show-rev-cache.c [new file with mode: 0644]
ssh-pull.c [new file with mode: 0644]
ssh-push.c [new file with mode: 0644]
strbuf.c [new file with mode: 0644]
strbuf.h [new file with mode: 0644]
stripspace.c [new file with mode: 0644]
t/Makefile [new file with mode: 0644]
t/README [new file with mode: 0644]
t/diff-lib.sh [new file with mode: 0755]
t/lib-read-tree-m-3way.sh [new file with mode: 0755]
t/t0000-basic.sh [new file with mode: 0755]
t/t0100-environment-names.sh [new file with mode: 0755]
t/t0110-environment-names-old.sh [new file with mode: 0755]
t/t1000-read-tree-m-3way.sh [new file with mode: 0755]
t/t1001-read-tree-m-2way.sh [new file with mode: 0755]
t/t1002-read-tree-m-u-2way.sh [new file with mode: 0755]
t/t1005-read-tree-m-2way-emu23.sh [new file with mode: 0755]
t/t1100-commit-tree-options.sh [new file with mode: 0755]
t/t2000-checkout-cache-clash.sh [new file with mode: 0755]
t/t2001-checkout-cache-clash.sh [new file with mode: 0755]
t/t2002-checkout-cache-u.sh [new file with mode: 0755]
t/t2003-checkout-cache-mkdir.sh [new file with mode: 0755]
t/t2100-update-cache-badpath.sh [new file with mode: 0755]
t/t3000-ls-files-others.sh [new file with mode: 0755]
t/t3001-ls-files-others-exclude.sh [new file with mode: 0755]
t/t3010-ls-files-killed.sh [new file with mode: 0755]
t/t3100-ls-tree-restrict.sh [new file with mode: 0755]
t/t4000-diff-format.sh [new file with mode: 0755]
t/t4001-diff-rename.sh [new file with mode: 0755]
t/t4002-diff-basic.sh [new file with mode: 0755]
t/t4003-diff-rename-1.sh [new file with mode: 0755]
t/t4004-diff-rename-symlink.sh [new file with mode: 0755]
t/t4005-diff-rename-2.sh [new file with mode: 0755]
t/t4006-diff-mode.sh [new file with mode: 0755]
t/t4007-rename-3.sh [new file with mode: 0755]
t/t4008-diff-break-rewrite.sh [new file with mode: 0755]
t/t4009-diff-rename-4.sh [new file with mode: 0755]
t/t4010-diff-pathspec.sh [new file with mode: 0755]
t/t4100-apply-stat.sh [new file with mode: 0755]
t/t4100/t-apply-1.expect [new file with mode: 0644]
t/t4100/t-apply-1.patch [new file with mode: 0644]
t/t4100/t-apply-2.expect [new file with mode: 0644]
t/t4100/t-apply-2.patch [new file with mode: 0644]
t/t4100/t-apply-3.expect [new file with mode: 0644]
t/t4100/t-apply-3.patch [new file with mode: 0644]
t/t4100/t-apply-4.expect [new file with mode: 0644]
t/t4100/t-apply-4.patch [new file with mode: 0644]
t/t4100/t-apply-5.expect [new file with mode: 0644]
t/t4100/t-apply-5.patch [new file with mode: 0644]
t/t4100/t-apply-6.expect [new file with mode: 0644]
t/t4100/t-apply-6.patch [new file with mode: 0644]
t/t4100/t-apply-7.expect [new file with mode: 0644]
t/t4100/t-apply-7.patch [new file with mode: 0644]
t/t4101-apply-nonl.sh [new file with mode: 0755]
t/t5000-tar-tree.sh [new file with mode: 0755]
t/t5300-pack-object.sh [new file with mode: 0755]
t/t5400-send-pack.sh [new file with mode: 0755]
t/t6000lib.sh [new file with mode: 0755]
t/t6001-rev-list-merge-order.sh [new file with mode: 0755]
t/t6002-rev-list-bisect.sh [new file with mode: 0755]
t/t6003-rev-list-topo-order.sh [new file with mode: 0755]
t/t6101-rev-parse-parents.sh [new file with mode: 0644]
t/test-lib.sh [new file with mode: 0755]
tag.c [new file with mode: 0644]
tag.h [new file with mode: 0644]
tar-tree.c [new file with mode: 0644]
templates/Makefile [new file with mode: 0644]
templates/branches-- [new file with mode: 0644]
templates/hooks--post-update [new file with mode: 0644]
templates/hooks--update [new file with mode: 0644]
templates/info--exclude [new file with mode: 0644]
templates/this--description [new file with mode: 0644]
test-date.c [new file with mode: 0644]
test-delta.c [new file with mode: 0644]
tools/Makefile [new file with mode: 0644]
tools/git-applymbox [new file with mode: 0755]
tools/git-applypatch [new file with mode: 0755]
tools/mailinfo.c [new file with mode: 0644]
tools/mailsplit.c [new file with mode: 0644]
tree.c [new file with mode: 0644]
tree.h [new file with mode: 0644]
unpack-file.c [new file with mode: 0644]
unpack-objects.c [new file with mode: 0644]
update-cache.c [new file with mode: 0644]
update-server-info.c [new file with mode: 0644]
upload-pack.c [new file with mode: 0644]
usage.c [new file with mode: 0644]
var.c [new file with mode: 0644]
verify-pack.c [new file with mode: 0644]
write-tree.c [new file with mode: 0644]

diff --git a/COPYING b/COPYING
new file mode 100644 (file)
index 0000000..6ff87c4
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,361 @@
+
+ Note that the only valid version of the GPL as far as this project
+ is concerned is _this_ particular version of the license (ie v2, not
+ v2.2 or v3.x or whatever), unless explicitly otherwise stated.
+
+ HOWEVER, in order to allow a migration to GPLv3 if that seems like
+ a good idea, I also ask that people involved with the project make
+ their preferences known. In particular, if you trust me to make that
+ decision, you might note so in your copyright message, ie something
+ like
+
+       This file is licensed under the GPL v2, or a later version
+       at the discretion of Linus.
+
+  might avoid issues. But we can also just decide to synchronize and
+  contact all copyright holders on record if/when the occasion arises.
+
+                       Linus Torvalds
+
+----------------------------------------
+
+                   GNU GENERAL PUBLIC LICENSE
+                      Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+                       59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                           Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Library General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+\f
+                   GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+\f
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+\f
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+\f
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+                           NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+                    END OF TERMS AND CONDITIONS
+\f
+           How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) year name of author
+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+  <signature of Ty Coon>, 1 April 1989
+  Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Library General
+Public License instead of this License.
diff --git a/Documentation/Makefile b/Documentation/Makefile
new file mode 100644 (file)
index 0000000..4798933
--- /dev/null
@@ -0,0 +1,54 @@
+MAN1_TXT=$(wildcard git-*.txt)
+MAN7_TXT=git.txt
+
+DOC_HTML=$(patsubst %.txt,%.html,$(MAN1_TXT) $(MAN7_TXT))
+
+DOC_MAN1=$(patsubst %.txt,%.1,$(MAN1_TXT))
+DOC_MAN7=$(patsubst %.txt,%.7,$(MAN7_TXT))
+
+prefix=$(HOME)
+bin=$(prefix)/bin
+mandir=$(prefix)/man
+man1=$(mandir)/man1
+man7=$(mandir)/man7
+
+INSTALL=install
+
+#
+# Please note that there is a minor bug in asciidoc.
+# The version after 6.0.3 _will_ include the patch found here:
+#   http://marc.theaimsgroup.com/?l=git&m=111558757202243&w=2
+#
+# Until that version is released you may have to apply the patch
+# yourself - yes, all 6 characters of it!
+#
+
+all: html man
+
+html: $(DOC_HTML)
+
+
+man: man1 man7
+man1: $(DOC_MAN1)
+man7: $(DOC_MAN7)
+
+install:
+       $(INSTALL) -m755 -d $(dest)/$(man1) $(dest)/$(man7)
+       $(INSTALL) $(DOC_MAN1) $(dest)/$(man1)
+       $(INSTALL) $(DOC_MAN7) $(dest)/$(man7)
+
+# 'include' dependencies
+git-diff-%.txt: diff-format.txt diff-options.txt
+       touch $@
+
+clean:
+       rm -f *.xml *.html *.1 *.7
+
+%.html : %.txt
+       asciidoc -b xhtml11 -d manpage $<
+
+%.1 %.7 : %.xml
+       xmlto man $<
+
+%.xml : %.txt
+       asciidoc -b docbook -d manpage $<
diff --git a/Documentation/cvs-migration.txt b/Documentation/cvs-migration.txt
new file mode 100644 (file)
index 0000000..8db1409
--- /dev/null
@@ -0,0 +1,246 @@
+Git for CVS users
+=================
+
+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
+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,
+however, may be a bit _too_ different (read: "good") for your taste, and
+does a lot of things differently. 
+
+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
+track that, since git never tracks things on the level of an individual
+file, only on the whole project level. 
+
+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. 
+
+First off: this is not a git tutorial. See Documentation/tutorial.txt
+for how git actually works. This is more of a random collection of
+gotcha's 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
+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
+just keep in mind that with git, every checked out working tree will
+have a full revision control history of its own.
+
+
+Importing a CVS archive
+-----------------------
+
+Ok, you have an old project, and you want to at least give git a chance
+to see how it performs. The first thing you want to do (after you've
+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
+will need the help of a program called "cvsps":
+
+       http://www.cobite.com/cvsps/
+
+which is not actually related to git at all, but which makes CVS usage
+look almost sane (ie you almost certainly want to have it even if you
+decide to stay with CVS). However, git will want at _least_ version 2.1
+of cvsps (available at the address above), and in fact will currently
+refuse to work with anything else.
+
+Once you've gotten (and installed) cvsps, you may or may not want to get
+any more familiar with it, but make sure it is in your path. After that,
+the magic command line is
+
+       git cvsimport -v -d <cvsroot> -C <destination> <module>
+
+which will do exactly what you'd think it does: it will create a git
+archive of the named CVS module. The new archive will be created in the
+subdirectory named <destination>; it'll be created if it doesn't exist.
+Default is the local directory.
+
+It can take some time to actually do the conversion for a large archive
+since it involves checking out from CVS every revision of every file,
+and the conversion script is reasonably chatty unless you omit the '-v'
+option, but on some not very scientific tests it averaged about twenty
+revisions per second, so a medium-sized project should not take more
+than a couple of minutes.  For larger projects or remote repositories,
+the process may take longer.
+
+After the (initial) import is done, the CVS archive's current head
+revision will be checked out -- thus, you can start adding your own
+changes right away.
+
+The import is incremental, i.e. if you call it again next month it'll
+fetch any CVS updates that have been happening in the meantime. The
+cut-off is date-based, so don't change the branches that were imported
+from CVS.
+
+You can merge those updates (or, in fact, a different CVS branch) into
+your main branch:
+
+       git resolve HEAD origin "merge with current CVS HEAD"
+
+The HEAD revision from CVS is named "origin", not "HEAD", because git
+already uses "HEAD". (If you don't like 'origin', use cvsimport's
+'-o' option to change it.)
+
+
+Emulating CVS behaviour
+-----------------------
+
+
+So, by now you are convinced you absolutely want to work with git, but
+at the same time you absolutely have to have a central repository.
+Step back and think again. Okay, you still need a single central
+repository? There are several ways to go about that:
+
+1. Designate a person responsible to pull all branches. Make the
+repository of this person public, and make every team member
+pull regularly from it.
+
+2. Set up a public repository with read/write access for every team
+member. Use "git pull/push" as you used "cvs update/commit".  Be
+sure that your repository is up to date before pushing, just
+like you used to do with "cvs commit"; your push will fail if
+what you are pushing is not up to date.
+
+3. Make the repository of every team member public. It is the
+responsibility of each single member to pull from every other
+team member.
+
+
+CVS annotate
+------------
+
+So, something has gone wrong, and you don't know whom to blame, and
+you're an ex-CVS user and used to do "cvs annotate" to see who caused
+the breakage. You're looking for the "git annotate", and it's just
+claiming not to find such a script. You're annoyed.
+
+Yes, that's right.  Core git doesn't do "annotate", although it's
+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
+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"). 
+
+The "git-whatchanged" script is a truly trivial script that can give you
+a good overview of what has changed in a file or a directory (or an
+arbitrary list of files or directories).  The "pickaxe" support is an
+additional layer that can be used to further specify exactly what you're
+looking for, if you already know the specific area that changed.
+
+Let's step back a bit and think about the reason why you would
+want to do "cvs annotate a-file.c" to begin with.
+
+You would use "cvs annotate" on a file when you have trouble
+with a function (or even a single "if" statement in a function)
+that happens to be defined in the file, which does not do what
+you want it to do.  And you would want to find out why it was
+written that way, because you are about to modify it to suit
+your needs, and at the same time you do not want to break its
+current callers.  For that, you are trying to find out why the
+original author did things that way in the original context.
+
+Many times, it may be enough to see the commit log messages of
+commits that touch the file in question, possibly along with the
+patches themselves, like this:
+
+       $ git-whatchanged -p a-file.c
+
+This will show log messages and patches for each commit that
+touches a-file.
+
+This, however, may not be very useful when this file has many
+modifications that are not related to the piece of code you are
+interested in.  You would see many log messages and patches that
+do not have anything to do with the piece of code you are
+interested in.  As an example, assuming that you have this piece
+of code that you are interested in in the HEAD version:
+
+       if (frotz) {
+               nitfol();
+       }
+
+you would use git-rev-list and git-diff-tree like this:
+
+       $ git-rev-list HEAD |
+         git-diff-tree --stdin -v -p -S'if (frotz) {
+               nitfol();
+       }'
+
+We have already talked about the "--stdin" form of git-diff-tree
+command that reads the list of commits and compares each commit
+with its parents.  The git-whatchanged command internally runs
+the equivalent of the above command, and can be used like this:
+
+       $ git-whatchanged -p -S'if (frotz) {
+               nitfol();
+       }'
+
+When the -S option is used, git-diff-tree command outputs
+differences between two commits only if one tree has the
+specified string in a file and the corresponding file in the
+other tree does not.  The above example looks for a commit that
+has the "if" statement in it in a file, but its parent commit
+does not have it in the same shape in the corresponding file (or
+the other way around, where the parent has it and the commit
+does not), and the differences between them are shown, along
+with the commit message (thanks to the -v flag).  It does not
+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
+a situation.  For that, you can give the -C flag to
+git-diff-tree, like this:
+
+       $ git-whatchanged -p -C -S'if (frotz) {
+               nitfol();
+       }'
+
+When the -C flag is used, file renames and copies are followed.
+So if the "if" statement in question happens to be in "a-file.c"
+in the current HEAD commit, even if the file was originally
+called "o-file.c" and then renamed in an earlier commit, or if
+the file was created by copying an existing "o-file.c" in an
+earlier commit, you will not lose track.  If the "if" statement
+did not change across such a rename or copy, then the commit that
+does rename or copy would not show in the output, and if the
+"if" statement was modified while the file was still called
+"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
+  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.]
+
+You can use the --pickaxe-all flag in addition to the -S flag.
+This causes the differences from all the files contained in
+those two commits, not just the differences between the files
+that contain this changed "if" statement:
+
+       $ git-whatchanged -p -C -S'if (frotz) {
+               nitfol();
+       }' --pickaxe-all
+
+[ Side note.  This option is called "--pickaxe-all" because -S
+  option is internally called "pickaxe", a tool for software
+  archaeologists.]
diff --git a/Documentation/diff-format.txt b/Documentation/diff-format.txt
new file mode 100644 (file)
index 0000000..9298d79
--- /dev/null
@@ -0,0 +1,135 @@
+The output format from "git-diff-cache", "git-diff-tree" and
+"git-diff-files" is very similar.
+
+These commands all compare two sets of things; what are
+compared are different:
+
+git-diff-cache <tree-ish>::
+        compares the <tree-ish> and the files on the filesystem.
+
+git-diff-cache --cached <tree-ish>::
+        compares the <tree-ish> and the cache.
+
+git-diff-tree [-r] <tree-ish-1> <tree-ish-2> [<pattern>...]::
+        compares the trees named by the two arguments.
+
+git-diff-files [<pattern>...]::
+        compares the cache and the files on the filesystem.
+
+
+An output line is formatted this way:
+
+in-place edit  :100644 100644 bcd1234... 0123456... M file0
+copy-edit      :100644 100644 abcd123... 1234567... C68 file1 file2
+rename-edit    :100644 100644 abcd123... 1234567... R86 file1 file3
+create         :000000 100644 0000000... 1234567... N file4
+delete         :100644 000000 1234567... 0000000... D file5
+unmerged       :000000 000000 0000000... 0000000... U file6
+
+That is, from the left to the right:
+
+  (1) a colon.
+  (2) mode for "src"; 000000 if creation or unmerged.
+  (3) a space.
+  (4) mode for "dst"; 000000 if deletion or unmerged.
+  (5) a space.
+  (6) sha1 for "src"; 0{40} if creation or unmerged.
+  (7) a space.
+  (8) sha1 for "dst"; 0{40} if creation, unmerged or "look at work tree".
+  (9) a space.
+ (10) status, followed by optional "score" number.
+ (11) a tab or a NUL when '-z' option is used.
+ (12) path for "src"
+ (13) a tab or a NUL when '-z' option is used; only exists for C or R.
+ (14) path for "dst"; only exists for C or R.
+ (15) 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
+and it is out of sync with the cache.  Example:
+
+  :100644 100644 5be4a4...... 000000...... M file.c
+
+Generating patches with -p
+--------------------------
+
+When "git-diff-cache", "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".
+
+1. When the environment variable 'GIT_EXTERNAL_DIFF' is not set,
+   these commands internally invoke "diff" like this:
+
+      diff -L a/<path> -L b/<path> -pu <old> <new>
++
+For added files, `/dev/null` is used for <old>.  For removed
+files, `/dev/null` is used for <new>
++
+The "diff" formatting options can be customized via the
+environment variable 'GIT_DIFF_OPTS'.  For example, if you
+prefer context diff:
+
+      GIT_DIFF_OPTS=-c git-diff-cache -p $(cat .git/HEAD)
+
+
+2. When the environment variable 'GIT_EXTERNAL_DIFF' is set, the
+   program named by it is called, instead of the diff invocation
+   described above.
++
+For a path that is added, removed, or modified,
+'GIT_EXTERNAL_DIFF' is called with 7 parameters:
+
+     path old-file old-hex old-mode new-file new-hex new-mode
++
+where:
+
+     <old|new>-file:: are files GIT_EXTERNAL_DIFF can use to read the
+                     contents of <old|ne>,
+     <old|new>-hex:: are the 40-hexdigit SHA1 hashes,
+     <old|new>-mode:: are the octal representation of the file modes.
+
++ 
+The file parameters can point at the user's working file
+(e.g. `new-file` in "git-diff-files"), `/dev/null` (e.g. `old-file`
+when a new file is added), or a temporary file (e.g. `old-file` in the
+cache).  'GIT_EXTERNAL_DIFF' should not worry about unlinking the
+temporary file --- it is removed when 'GIT_EXTERNAL_DIFF' exits.
+
+For a path that is unmerged, 'GIT_EXTERNAL_DIFF' is called with 1
+parameter, <path>.
+
+
+Git specific extension to diff format
+-------------------------------------
+
+What -p option produces is slightly different from the
+traditional diff format.
+
+ (1) It is preceeded with a "git diff" header, that looks like
+     this:
+
+     diff --git a/file1 b/file2
+
+     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/ filename.
+
+     When rename/copy is involved, file1 and file2 shows 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:
+
+       old mode <mode>
+       new mode <mode>
+       deleted file mode <mode>
+       new file mode <mode>
+       copy from <path>
+       copy to <path>
+       rename from <path>
+       rename to <path>
+       similarity index <number>
+       dissimilarity index <number>
diff --git a/Documentation/diff-options.txt b/Documentation/diff-options.txt
new file mode 100644 (file)
index 0000000..b28496e
--- /dev/null
@@ -0,0 +1,53 @@
+-p::
+       Generate patch (see section on generating patches)
+
+-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.
+
+-B::
+       Break complete rewrite changes into pairs of delete and create.
+
+-M::
+       Detect renames.
+
+-C::
+       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
+       inspect unmodified files as candidates for the source of
+       copy.  This is a very expensive operation for large
+       projects, so use it with caution.
+
+-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.
+
+-R::
+       Swap two inputs; that is, show differences from cache or
+       on-disk file to tree contents.
+
diff --git a/Documentation/diffcore.txt b/Documentation/diffcore.txt
new file mode 100644 (file)
index 0000000..6c474d1
--- /dev/null
@@ -0,0 +1,248 @@
+Tweaking diff output
+====================
+June 2005
+
+
+Introduction
+------------
+
+The diff commands git-diff-cache, 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 chain of operation
+----------------------
+
+The git-diff-* family works by first comparing two sets of
+files:
+
+ - git-diff-cache compares contents of a "tree" object and the
+   working directory (when --cached flag is not used) or a
+   "tree" object and the index file (when --cached flag is
+   used);
+
+ - git-diff-files compares contents of the index file and the
+   working directory;
+
+ - git-diff-tree compares contents of two "tree" objects.
+
+In all of these cases, the commands themselves compare
+corresponding paths in the two sets of files.  The result of
+comparison is passed from these commands to what is internally
+called "diffcore", in a format similar to what is output when
+the -p option is not used.  E.g.
+
+    in-place edit  :100644 100644 bcd1234... 0123456... M file0
+    create         :000000 100644 0000000... 1234567... N file4
+    delete         :100644 000000 1234567... 0000000... D file5
+    unmerged       :000000 000000 0000000... 0000000... U file6
+
+The diffcore mechanism is fed a list of such comparison results
+(each of which is called "filepair", although at this point each
+of them talks about a single file), and transforms such a list
+into another list.  There are currently 6 such transformations:
+
+ - diffcore-pathspec
+ - diffcore-break
+ - diffcore-rename
+ - diffcore-merge-broken
+ - diffcore-pickaxe
+ - diffcore-order
+
+These are applied in sequence.  The set of filepairs git-diff-*
+commands find are used as the input to diffcore-pathspec, and
+the output from diffcore-pathspec is used as the input to the
+next transformation.  The final result is then passed to the
+output routine and generates either diff-raw format (see Output
+format sections of the manual for git-diff-* commands) or
+diff-patch format.
+
+
+diffcore-pathspec
+-----------------
+
+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.
+
+Implementation note.  For performance reasons, git-diff-tree
+uses the pathname parameters on the command line to cull set of
+filepairs it feeds the diffcore mechanism itself, and does not
+use diffcore-pathspec, but the end result is the same.
+
+
+diffcore-break
+--------------
+
+The second transformation in the chain is diffcore-break, and is
+controlled by the -B option to the git-diff-* commands.  This is
+used to detect a filepair that represents "complete rewrite" and
+break such filepair into two filepairs that represent delete and
+create.  E.g.  If the input contained this filepair:
+
+    :100644 100644 bcd1234... 0123456... M file0
+
+and if it detects that the file "file0" is completely rewritten,
+it changes it to:
+
+    :100644 000000 bcd1234... 0000000... D file0
+    :000000 100644 0000000... 0123456... N file0
+
+For the purpose of breaking a filepair, diffcore-break examines
+the extent of changes between the contents of the files before
+and after modification (i.e. the contents that have "bcd1234..."
+and "0123456..." as their SHA1 content ID, in the above
+example).  The amount of deletion of original contents and
+insertion of new material are added together, and if it exceeds
+the "break score", the filepair is broken into two.  The break
+score defaults to 50% of the size of the smaller of the original
+and the result (i.e. if the edit shrinks the file, the size of
+the result is used; if the edit lengthens the file, the size of
+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
+---------------
+
+This transformation is used to detect renames and copies, and is
+controlled by the -M option (to detect renames) and the -C option
+(to detect copies as well) to the git-diff-* commands.  If the
+input contained these filepairs:
+
+    :100644 000000 0123456... 0000000... D fileX
+    :000000 100644 0000000... 0123456... N file0
+
+and the contents of the deleted file fileX is similar enough to
+the contents of the created file file0, then rename detection
+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
+created file file0:
+
+    :100644 100644 0123456... 1234567... M fileY
+    :000000 100644 0000000... 0123456... N file0
+
+the original contents of fileY and the resulting contents of
+file0 are compared, and if they are similar enough, they are
+changed to:
+
+    :100644 100644 0123456... 1234567... M fileY
+    :100644 100644 0123456... 0123456... 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
+8/10 = 80%).
+
+Note.  When the "-C" option is used with --find-copies-harder
+option, git-diff-* commands feed unmodified filepairs to
+diffcore mechanism as well as modified ones.  This lets the copy
+detector consider unmodified files as copy source candidates at
+the expense of making it slower.  Without --find-copies-harder,
+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
+---------------------
+
+This transformation is used to merge filepairs broken by
+diffcore-break, and were not transformed into rename/copy by
+diffcore-rename, back into a single modification.  This always
+runs when diffcore-break is used.
+
+For the purpose of merging broken filepairs back, it uses a
+different "extent of changes" computation from the ones used by
+diffcore-break and diffcore-rename.  It counts only the deletion
+from the original, and does not count insertion.  If you removed
+only 10 lines from a 100-line document, even if you added 910
+new lines to make a new 1000-line document, you did not do a
+complete rewrite.  diffcore-break breaks such a case in order to
+help diffcore-rename to consider such filepairs as candidate of
+rename/copy detection, but if filepairs broken that way were not
+matched with other filepairs to create rename/copy, then this
+transformation merges them back into the original
+"modification".
+
+The "extent of changes" parameter can be tweaked from the
+default 80% (that is, unless more than 80% of the original
+material is deleted, the broken pairs are merged back into a
+single modification) by giving a second number to -B option,
+like these:
+
+       -B50/60 (give 50% "break score" to diffcore-break, use
+                 60% for diffcore-merge-broken).
+       -B/60   (the same as above, since diffcore-break defautls to
+                50%).
+
+Note that earlier implementation left a broken pair as a separate
+creation and deletion patches.  This was 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
+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
+----------------
+
+This transformation is used to find filepairs that represent
+changes that touch a specified string, and is controlled by the
+-S option and the --pickaxe-all option to the git-diff-*
+commands.
+
+When diffcore-pickaxe is in use, it checks if there are
+filepairs whose "original" side has the specified string and
+whose "result" side does not.  Such a filepair represents "the
+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
+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
+make reviewing of the changes in the context of the whole
+changeset easier.
+
+
+diffcore-order
+--------------
+
+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
+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
+should look like this:
+
+    README
+    Makefile
+    Documentation
+    *.h
+    *.c
+    t
+
diff --git a/Documentation/git-apply-patch-script.txt b/Documentation/git-apply-patch-script.txt
new file mode 100644 (file)
index 0000000..a6f860d
--- /dev/null
@@ -0,0 +1,32 @@
+git-apply-patch-script(1)
+=========================
+v0.1, May 2005
+
+NAME
+----
+git-apply-patch-script - Sample script to apply the diffs from git-diff-*
+
+
+SYNOPSIS
+--------
+'git-apply-patch-script'
+
+DESCRIPTION
+-----------
+This is a sample script to be used via the 'GIT_EXTERNAL_DIFF'
+environment variable to apply the differences that the "git-diff-*"
+family of commands report to the current work tree.
+
+
+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
+
diff --git a/Documentation/git-apply.txt b/Documentation/git-apply.txt
new file mode 100644 (file)
index 0000000..391d6f5
--- /dev/null
@@ -0,0 +1,76 @@
+git-apply(1)
+============
+v0.1, June 2005
+
+NAME
+----
+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>...]
+
+DESCRIPTION
+-----------
+Reads supplied diff output and applies it on a GIT index file
+and a work tree.
+
+OPTIONS
+-------
+<patch>...::
+       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".
+
+--summary::
+       Instead of applying the patch, output a condensed
+       summary of information obtained from git diff extended
+       headers, such as creations, renames and mode changes.
+       Turns off "apply".
+
+--check::
+       Instead of applying the patch, see if the patch is
+       applicable to the current work tree and/or the index
+       file and detects errors.  Turns off "apply".
+
+--index::
+       When --check is in effect, or when applying the patch
+       (which is the default when none of the options that
+       disables it is in effect), make sure the patch is
+       applicable to what the current index file records.  If
+       the file to be patched in the work tree is not
+       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.
+
+--apply::
+       If you use any of the options marked ``Turns off
+       "apply"'' above, git-apply reads and outputs the
+       information you asked without actually applying the
+       patch.  Give this flag after those flags to also apply
+       the patch.
+
+
+Author
+------
+Written by Linus Torvalds <torvalds@osdl.org>
+
+Documentation
+--------------
+Documentation by Junio C Hamano
+
+GIT
+---
+Part of the link:git.html[git] suite
+
diff --git a/Documentation/git-cat-file.txt b/Documentation/git-cat-file.txt
new file mode 100644 (file)
index 0000000..2131a29
--- /dev/null
@@ -0,0 +1,61 @@
+git-cat-file(1)
+===============
+v0.1, May 2005
+
+NAME
+----
+git-cat-file - Provide content or type information for repository objects
+
+
+SYNOPSIS
+--------
+'git-cat-file' (-t | -s | <type>) <object>
+
+DESCRIPTION
+-----------
+Provides content or type of objects in the repository. The type
+is required unless '-t' is used to find the object type,
+or '-s' is used to find the object size.
+
+OPTIONS
+-------
+<object>::
+       The sha1 identifier of the object.
+
+-t::
+       Instead of the content, show the object type identified by
+       <object>.
+
+-s::
+       Instead of the content, show the object size identified by
+       <object>.
+
+<type>::
+       Typically this matches the real type of <object> but asking
+       for a type that can trivially 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
+       points at it.
+
+OUTPUT
+------
+If '-t' is specified, one of the <type>.  If '-s' is specified,
+the size of the <object> in bytes.
+
+Otherwise the raw (though uncompressed) contents of the <object> will
+be returned.
+
+
+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
+
diff --git a/Documentation/git-checkout-cache.txt b/Documentation/git-checkout-cache.txt
new file mode 100644 (file)
index 0000000..321a00c
--- /dev/null
@@ -0,0 +1,106 @@
+git-checkout-cache(1)
+=====================
+v0.1, May 2005
+
+NAME
+----
+git-checkout-cache - Copy files from the cache to the working directory
+
+
+SYNOPSIS
+--------
+'git-checkout-cache' [-u] [-q] [-a] [-f] [-n] [--prefix=<string>]
+                  [--] <file>...
+
+DESCRIPTION
+-----------
+Will copy all files listed from the cache to the working directory
+(not overwriting existing files).
+
+OPTIONS
+-------
+-u::
+       update stat information for the checked out entries in
+       the cache file.
+
+-q::
+       be quiet if files exist or are not in the cache
+
+-f::
+       forces overwrite of existing files
+
+-a::
+       checks out all files in the cache (will then continue to
+       process listed files).
+
+-n::
+       Don't checkout new files, only refresh files already checked
+       out.
+
+--prefix=<string>::
+       When creating files, prepend <string> (usually a directory
+       including a trailing /)
+
+--::
+       Do not interpret any more arguments as options.
+
+Note that the order of the flags matters:
+
+     git-checkout-cache -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-cache" does nothing. You probably meant
+"git-checkout-cache -a". And if you want to force it, you want
+"git-checkout-cache -f -a".
+
+Intuitiveness is not the goal here. Repeatability is. The reason for
+the "no arguments means no work" thing is that from scripts you are
+supposed to be able to do things like:
+
+       find . -name '*.h' -print0 | xargs -0 git-checkout-cache -f --
+
+which will force all existing `*.h` files to be replaced with their
+cached copies. If an empty command line implied "all", then this would
+force-refresh everything in the cache, which was not the point.
+
+To update and refresh only the files already checked out:
+
+        git-checkout-cache -n -f -a && git-update-cache --ignore-missing --refresh
+
+Oh, and the "--" is just a good idea when you know the rest will be
+filenames. Just so that you wouldn't have a filename of "-a" causing
+problems (not possible in the above example, but get used to it in
+scripting!).
+
+The prefix ability basically makes it trivial to use
+git-checkout-cache as an "export as tree" function. Just read the
+desired tree into the index, and do a
+  
+        git-checkout-cache --prefix=git-export-dir/ -a
+  
+and git-checkout-cache 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
+
+    git-checkout-cache --prefix=.merged- Makefile
+
+to check out the currently cached copy of `Makefile` into the file
+`.merged-Makefile`
+
+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
+
diff --git a/Documentation/git-clone-pack.txt b/Documentation/git-clone-pack.txt
new file mode 100644 (file)
index 0000000..0dc89a9
--- /dev/null
@@ -0,0 +1,65 @@
+git-clone-pack(1)
+=================
+v0.1, July 2005
+
+NAME
+----
+git-clone-pack - Clones a repository by receiving packed objects.
+
+
+SYNOPSIS
+--------
+'git-clone-pack' [-q] [--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.
+
+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
+       setup scripts for login shells (e.g. .bash_profile) and
+       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
+       shells by having a lean .bashrc file (they set most of
+       the things up in .bash_profile).
+
+<host>::
+       A remote host that houses the repository.  When this
+       part is specified, 'git-upload-pack' is invoked via
+       ssh.
+
+<directory>::
+       The repository to sync from.
+
+<head>...::
+       The heads to update.  This is relative to $GIT_DIR
+       (e.g. "HEAD", "refs/heads/master").  When unspecified,
+       all heads are updated to match the remote repository.
+
+
+Author
+------
+Written by Linus Torvalds <torvalds@osdl.org>
+
+Documentation
+--------------
+Documentation by Junio C Hamano.
+
+
+GIT
+---
+Part of the link:git.html[git] suite
+
diff --git a/Documentation/git-clone-script.txt b/Documentation/git-clone-script.txt
new file mode 100644 (file)
index 0000000..afb25bd
--- /dev/null
@@ -0,0 +1,60 @@
+git-clone-script(1)
+===================
+v0.1, July 2005
+
+NAME
+----
+git-clone-script - Clones a repository.
+
+
+SYNOPSIS
+--------
+'git clone' [-l] [-u <upload-pack>] [-q] <repository> <directory>
+
+DESCRIPTION
+-----------
+Clones a repository into a newly created directory.
+
+OPTIONS
+-------
+-l::
+       When the repository to clone from is on a local machine,
+       this flag bypasses normal "git aware" transport
+       mechanism and clones the repository by making a copy of
+       HEAD and everything under objects and refs directories.
+       The files under .git/objects/ directory are hardlinked
+       to save space when possible.
+
+-q::
+       Operate quietly.  This flag is passed to "rsync" and
+       "git-clone-pack" commands when given.
+
+-u <upload-pack>::
+       When given, and the repository to clone from is handled
+       by 'git-clone-pack', '--exec=<upload-pack>' is passed to
+       the command to specify non-default path for the command
+       run on the other end.
+
+<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.
+
+<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>
+
+Documentation
+--------------
+Documentation by Junio C Hamano.
+
+
+GIT
+---
+Part of the link:git.html[git] suite
+
diff --git a/Documentation/git-commit-script.txt b/Documentation/git-commit-script.txt
new file mode 100644 (file)
index 0000000..bb559d7
--- /dev/null
@@ -0,0 +1,49 @@
+git-commit-script(1)
+====================
+v0.99.4, Aug 2005
+
+NAME
+----
+git-commit-script - Record your changes
+
+SYNOPSIS
+--------
+'git commit' [-a] [(-c | -C) <commit> | -F <file> | -m <msg>] <file>...
+
+DESCRIPTION
+-----------
+Updates the index file for given paths, or all modified files if
+'-a' is specified, and makes a commit object.  The command
+VISUAL and EDITOR environment variables to edit the commit log
+message.
+
+OPTIONS
+-------
+-a::
+       Update all paths in the index file.
+
+-c or -C <commit>::
+       Take existing commit object, and reuse the log message
+       and the authorship information (including the timestamp)
+       when creating the commit.  With '-C', the editor is not
+       invoked; with '-c' the user can further edit the commit
+       message.
+
+-F <file>::
+       Take the commit message from the given file.  Use '-' to
+       read the message from the standard input.
+
+-m <msg>::
+       Use the given <msg> as the commit message.
+
+<file>...::
+       Update specified paths in the index file.
+
+
+Author
+------
+Written by Linus Torvalds <torvalds@osdl.org>
+
+GIT
+---
+Part of the link:git.html[git] suite
diff --git a/Documentation/git-commit-tree.txt b/Documentation/git-commit-tree.txt
new file mode 100644 (file)
index 0000000..5536668
--- /dev/null
@@ -0,0 +1,92 @@
+git-commit-tree(1)
+==================
+v0.1, May 2005
+
+NAME
+----
+git-commit-tree - Creates a new commit object
+
+
+SYNOPSIS
+--------
+'git-commit-tree' <tree> [-p <parent commit>]\   < changelog
+
+DESCRIPTION
+-----------
+Creates a new commit object based on the provided tree object and
+emits the new commit object id on stdout. If no parent is given then
+it is considered to be an initial tree.
+
+A commit object usually has 1 parent (a commit after a change) or up
+to 16 parents.  More than one parent represents a merge of branches
+that led to them.
+
+While a tree represents a particular directory state of a working
+directory, a commit represents that state in "time", and explains how
+to get there.
+
+Normally a commit would identify a new "HEAD" state, and while git
+doesn't care where you 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.
+
+OPTIONS
+-------
+<tree>::
+       An existing tree object
+
+-p <parent commit>::
+       Each '-p' indicates a the id of a parent commit object.
+       
+
+Commit Information
+------------------
+
+A commit encapsulates:
+
+- all parent object ids
+- author name, email and date
+- 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.
+
+       GIT_AUTHOR_NAME
+       GIT_AUTHOR_EMAIL
+       GIT_AUTHOR_DATE
+       GIT_COMMITTER_NAME
+       GIT_COMMITTER_EMAIL
+
+(nb <,> and '\n's are stripped)
+
+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
+
+Diagnostics
+-----------
+You don't exist. Go away!::
+    The passwd(5) gecos field couldn't be read
+Your parents must have hated you!::
+    The password(5) gecos field is longer than a giant static buffer.
+Your sysadmin must hate you!::
+    The password(5) name field is longer than a giant static buffer.
+
+See Also
+--------
+link:git-write-tree.html[git-write-tree]
+
+
+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
+
diff --git a/Documentation/git-convert-cache.txt b/Documentation/git-convert-cache.txt
new file mode 100644 (file)
index 0000000..66d7fe7
--- /dev/null
@@ -0,0 +1,30 @@
+git-convert-cache(1)
+====================
+v0.1, May 2005
+
+NAME
+----
+git-convert-cache - Converts old-style GIT repository
+
+
+SYNOPSIS
+--------
+'git-convert-cache'
+
+DESCRIPTION
+-----------
+Converts old-style GIT repository to the latest format
+
+
+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
+
diff --git a/Documentation/git-cvsimport-script.txt b/Documentation/git-cvsimport-script.txt
new file mode 100644 (file)
index 0000000..05145e9
--- /dev/null
@@ -0,0 +1,80 @@
+git-cvsimport-script(1)
+=======================
+v0.1, July 2005
+
+NAME
+----
+git-cvsimport-script - Import a CVS repository into git
+
+
+SYNOPSIS
+--------
+'git-cvsimport-script' [ -o <branch-for-HEAD> ] [ -h ] [ -v ]
+                       [ -d <CVSROOT> ] [ -p <options-for-cvsps> ]
+                       [ -C <GIT_repository> ] [ -i ] [ <CVS_module> ]
+
+
+DESCRIPTION
+-----------
+Imports a CVS repository into git. It will either create a new
+repository, or incrementally import into an existing one.
+
+Splitting the CVS log into patch sets is done by 'cvsps'.
+At least version 2.1 is required.
+
+OPTIONS
+-------
+-d <CVSROOT>::
+       The root of the CVS archive. May be local (a simple path) or remote;
+       currently, only the :local:, :ext: and :pserver: access methods 
+       are supported.
+
+-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.
+
+-o <branch-for-HEAD>::
+       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.
+
+-p <options-for-cvsps>::
+       Additional options for cvsps.
+       The options '-x' and '-A' are implicit and should not be used here.
+
+       If you need to pass multiple options, separate them with a comma.
+
+-v::
+       Verbosity: let 'cvsimport' report what it is doing.
+
+<CVS_module>::
+       The CVS module you want to import. Relative to <CVSROOT>.
+
+-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>.
+
+Documentation
+--------------
+Documentation by Matthias Urlichs <smurf@smurf.noris.de>.
+
+GIT
+---
+Part of the link:git.html[git] suite
+
diff --git a/Documentation/git-diff-cache.txt b/Documentation/git-diff-cache.txt
new file mode 100644 (file)
index 0000000..a353d12
--- /dev/null
@@ -0,0 +1,134 @@
+git-diff-cache(1)
+=================
+v0.1, May 2005
+
+NAME
+----
+git-diff-cache - Compares content and mode of blobs between the cache and repository
+
+
+SYNOPSIS
+--------
+'git-diff-cache' [-m] [--cached] [<common diff options>] <tree-ish> [<path>...]
+
+DESCRIPTION
+-----------
+Compares the content and mode of the blobs found via a tree
+object with the content of the current cache and, optionally
+ignoring the stat state of the file on disk.  When paths are
+specified, compares only those named paths.  Otherwise all
+entries in the cache are compared.
+
+OPTIONS
+-------
+include::diff-options.txt[]
+
+<tree-ish>::
+       The id of a tree object to diff against.
+
+--cached::
+       do not consider the on-disk file at all
+
+-m::
+       By default, files recorded in the index but not checked
+       out are reported as deleted.  This flag makes
+       "git-diff-cache" say that all non-checked-out files are up
+       to date.
+
+Output format
+-------------
+include::diff-format.txt[]
+
+Operating Modes
+---------------
+You can choose whether you want to trust the index file entirely
+(using the '--cached' flag) or ask the diff logic to show any files
+that don't match the stat state as being "tentatively changed".  Both
+of these operations are very useful indeed.
+
+Cached Mode
+-----------
+If '--cached' is specified, it allows you to ask:
+
+       show me the differences between HEAD and the current cache
+       contents (the ones I'd write with a "git-write-tree")
+
+For example, let's say that you have worked on your working directory, updated
+some files in the cache and are ready to commit. You want to see eactly
+*what* you are going to commit is without having to write a new tree
+object and compare it that way, and to do that, you just do
+
+       git-diff-cache --cached $(cat .git/HEAD)
+
+Example: let's say I had renamed `commit.c` to `git-commit.c`, and I had
+done an "git-update-cache" to make that effective in the index file.
+"git-diff-files" wouldn't show anything at all, since the index file
+matches my working directory. But doing a "git-diff-cache" does:
+
+  torvalds@ppc970:~/git> git-diff-cache --cached $(cat .git/HEAD)
+  -100644 blob    4161aecc6700a2eb579e842af0b7f22b98443f74        commit.c
+  +100644 blob    4161aecc6700a2eb579e842af0b7f22b98443f74        git-commit.c
+
+You can trivially see that the above is a rename.
+
+In fact, "git-diff-cache --cached" *should* always be entirely equivalent to
+actually doing a "git-write-tree" and comparing that. Except this one is much
+nicer for the case where you just want to check where you are.
+
+So doing a "git-diff-cache --cached" is basically very useful when you are 
+asking yourself "what have I already marked for being committed, and 
+what's the difference to a previous tree".
+
+Non-cached Mode
+---------------
+The "non-cached" mode takes a different approach, and is potentially
+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
+
+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"
+output to a tee, but with a twist.
+
+The twist is that if some file doesn't match the cache, we don't have
+a backing store thing for it, and we use the magic "all-zero" sha1 to
+show that. So let's say that you have edited `kernel/sched.c`, but
+have not actually done a "git-update-cache" on it yet - there is no
+"object" associated with the new state, and you get:
+
+  torvalds@ppc970:~/v2.6/linux> git-diff-cache $(cat .git/HEAD )
+  *100644->100664 blob    7476bb......->000000......      kernel/sched.c
+
+ie it shows that the tree has changed, and that `kernel/sched.c` has is
+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-cache" 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"
+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
+always have the special all-zero sha1.
+
+
+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
+
diff --git a/Documentation/git-diff-files.txt b/Documentation/git-diff-files.txt
new file mode 100644 (file)
index 0000000..3e1244f
--- /dev/null
@@ -0,0 +1,44 @@
+git-diff-files(1)
+=================
+v0.1, May 2005
+
+NAME
+----
+git-diff-files - Compares files in the working tree and the cache
+
+
+SYNOPSIS
+--------
+'git-diff-files' [-q] [<common diff options>] [<path>...]
+
+DESCRIPTION
+-----------
+Compares the files in the working tree and the cache.  When paths
+are specified, compares only those named paths.  Otherwise all
+entries in the cache are compared.  The output format is the
+same as "git-diff-cache" and "git-diff-tree".
+
+OPTIONS
+-------
+include::diff-options.txt[]
+
+-q::
+       Remain silent even on nonexisting files
+
+Output format
+-------------
+include::diff-format.txt[]
+
+
+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
+
diff --git a/Documentation/git-diff-helper.txt b/Documentation/git-diff-helper.txt
new file mode 100644 (file)
index 0000000..d826deb
--- /dev/null
@@ -0,0 +1,53 @@
+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-cache", "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-cache.html[git-diff-cache]
+
+
+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
+
diff --git a/Documentation/git-diff-stages.txt b/Documentation/git-diff-stages.txt
new file mode 100644 (file)
index 0000000..2084c70
--- /dev/null
@@ -0,0 +1,41 @@
+git-diff-stages(1)
+==================
+v0.1, June 2005
+
+NAME
+----
+git-diff-stages - Compares content and mode of blobs between stages in an unmerged index file.
+
+
+SYNOPSIS
+--------
+'git-diff-stages' [<common diff options>] <stage1> <stage2> [<path>...]
+
+DESCRIPTION
+-----------
+Compares the content and mode of the blobs in two stages in an
+unmerged index file.
+
+OPTIONS
+-------
+include::diff-options.txt[]
+
+<stage1>,<stage2>::
+       The stage number to be compared.
+
+Output format
+-------------
+include::diff-format.txt[]
+
+
+Author
+------
+Written by Junio C Hamano <junkio@cox.net>
+
+Documentation
+--------------
+Documentation by Junio C Hamano.
+
+GIT
+---
+Part of the link:git.html[git] suite
diff --git a/Documentation/git-diff-tree.txt b/Documentation/git-diff-tree.txt
new file mode 100644 (file)
index 0000000..5ccf0ba
--- /dev/null
@@ -0,0 +1,130 @@
+git-diff-tree(1)
+================
+v0.1, May 2005
+
+NAME
+----
+git-diff-tree - Compares the content and mode of blobs found via two tree objects
+
+
+SYNOPSIS
+--------
+'git-diff-tree' [--stdin] [-m] [-s] [-v] [--pretty] [-t] [<common diff options>] <tree-ish> <tree-ish> [<path>...]
+
+DESCRIPTION
+-----------
+Compares the content and mode of the blobs found via two tree objects.
+
+Note that "git-diff-tree" can use the tree encapsulated in a commit object.
+
+OPTIONS
+-------
+include::diff-options.txt[]
+
+<tree-ish>::
+       The id of a tree object.
+
+<path>...::
+       If provided, the results are limited to a subset of files
+       matching one of these prefix strings.
+       ie file matches `/^<pattern1>|<pattern2>|.../`
+       Note that this parameter does not provide any wildcard or regexp
+       features.
+
+-t::
+       show tree entry itself as well as subtrees.  Implies -r.
+
+--root::
+       When '--root' is specified the initial commit will be showed as a big
+       creation event. This is equivalent to a diff against the NULL tree.
+
+--stdin::
+       When '--stdin' is specified, the command does not take
+       <tree-ish> arguments from the command line.  Instead, it
+       reads either one <commit> or a pair of <tree-ish>
+       separated with a single space from its standard input.
++
+When a single commit is given on one line of such input, it compares
+the commit with its parents.  The following flags further affects its
+behaviour.  This does not apply to the case where two <tree-ish>
+separated with a single space are given.
+
+-m::
+       By default, "git-diff-tree --stdin" does not show
+       differences for merge commits.  With this flag, it shows
+       differences to that commit from all of its parents.
+
+-s::
+       By default, "git-diff-tree --stdin" shows differences,
+       either in machine-readable form (without '-p') or in patch
+       form (with '-p').  This output can be supressed.  It is
+       only useful with '-v' flag.
+
+-v::
+       This flag causes "git-diff-tree --stdin" to also show
+       the commit message before the differences.
+
+--pretty[=(raw|medium|short)]::
+       This is used to control "pretty printing" format of the
+       commit message.  Without "=<style>", it defaults to
+       medium.
+
+
+Limiting Output
+---------------
+If you're only interested in differences in a subset of files, for
+example some architecture-specific files, you might do:
+
+       git-diff-tree -r <tree-ish> <tree-ish> arch/ia64 include/asm-ia64
+
+and it will only show you what changed in those two directories.
+
+Or if you are searching for what changed in just `kernel/sched.c`, just do
+
+       git-diff-tree -r <tree-ish> <tree-ish> kernel/sched.c
+
+and it will ignore all differences to other files.
+
+The pattern is always the prefix, and is matched exactly.  There are no
+wildcards.  Even stricter, it has to match a complete path component.
+I.e. "foo" does not pick up `foobar.h`.  "foo" does match `foo/bar.h`
+so it can be used to name subdirectories.
+
+An example of normal usage is:
+
+  torvalds@ppc970:~/git> git-diff-tree 5319e4......
+  *100664->100664 blob    ac348b.......->a01513.......      git-fsck-cache.c
+
+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
+
+  Make "git-fsck-cache" 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.
+
+in case you care).
+
+Output format
+-------------
+include::diff-format.txt[]
+
+
+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
+
diff --git a/Documentation/git-export.txt b/Documentation/git-export.txt
new file mode 100644 (file)
index 0000000..d2d0dc4
--- /dev/null
@@ -0,0 +1,31 @@
+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
+
diff --git a/Documentation/git-fetch-pack.txt b/Documentation/git-fetch-pack.txt
new file mode 100644 (file)
index 0000000..884a26b
--- /dev/null
@@ -0,0 +1,73 @@
+git-fetch-pack(1)
+=================
+v0.1, July 2005
+
+NAME
+----
+git-fetch-pack - Receive missing objects from another repository.
+
+
+SYNOPSIS
+--------
+git-fetch-pack [-q] [--exec=<git-upload-pack>] [<host>:]<directory> [<head>...] < <commit-list>
+
+DESCRIPTION
+-----------
+Invokes 'git-upload-pack' on a potentially remote repository,
+and asks it to send objects missing from this repository, to
+update the named heads.  The list of commits available locally
+is fed from the standard input, to be sent to 'git-upload-pack'
+running on the other end.
+
+This command can be used only when the local side has a common
+(ancestor) commit with the remote head that is being pulled
+from.  Use 'git-clone-pack' for that.
+
+
+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
+       setup scripts for login shells (e.g. .bash_profile) and
+       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
+       shells by having a lean .bashrc file (they set most of
+       the things up in .bash_profile).
+
+<host>::
+       A remote host that houses the repository.  When this
+       part is specified, 'git-upload-pack' is invoked via
+       ssh.
+
+<directory>::
+       The repository to sync from.
+
+<head>...::
+       The remote heads to update from. This is relative to
+       $GIT_DIR (e.g. "HEAD", "refs/heads/master").  When
+       unspecified, update from all heads the remote side has.
+
+       However the program refuses to work if more than one
+       remote head matches the specified heads.  I am not sure
+       what this means... Help!!!!!
+
+
+Author
+------
+Written by Linus Torvalds <torvalds@osdl.org>
+
+Documentation
+--------------
+Documentation by Junio C Hamano.
+
+GIT
+---
+Part of the link:git.html[git] suite
diff --git a/Documentation/git-fetch-script.txt b/Documentation/git-fetch-script.txt
new file mode 100644 (file)
index 0000000..937df05
--- /dev/null
@@ -0,0 +1,47 @@
+git-fetch-script(1)
+===================
+v0.1, July 2005
+
+NAME
+----
+git-fetch-script - Download objects and a head from another repository.
+
+
+SYNOPSIS
+--------
+'git-fetch-script' <repository> [ <head> | tag <tag> ]
+
+
+DESCRIPTION
+-----------
+Fetches a named head or a tag from another repository, along
+with the objects necessary to complete that head or tag.  The
+head to pull defaults to HEAD if unspecified.  The head or tag
+fetched from the remote repository is stored in
+$GIT_DIR/FETCH_HEAD.
+
+When a <tag> is specified, the <tag> fetched from the remote is
+also copied to the local $GIT_DIR/tags/<tag> file.  When no
+<head> nor <tag> is specified, and <repository> was specified
+with the short-hand notation (i.e. naming a file under the
+$GIT_DIR/branches directory), the head fetched from the remote
+repository is also copied to the local $GIT_DIR/heads/<repository>
+file.
+
+
+OPTIONS
+-------
+include::pull-fetch-param.txt[]
+
+
+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
diff --git a/Documentation/git-fsck-cache.txt b/Documentation/git-fsck-cache.txt
new file mode 100644 (file)
index 0000000..13f1965
--- /dev/null
@@ -0,0 +1,145 @@
+git-fsck-cache(1)
+=================
+v0.1, May 2005
+
+NAME
+----
+git-fsck-cache - Verifies the connectivity and validity of the objects in the database
+
+
+SYNOPSIS
+--------
+'git-fsck-cache' [--tags] [--root] [--unreachable] [--cache] [--standalone | --full] [--strict] [<object>*]
+
+DESCRIPTION
+-----------
+Verifies the connectivity and validity of the objects in the database.
+
+OPTIONS
+-------
+<object>::
+       An object to treat as the head of an unreachability trace.
+
+       If no objects are given, git-fsck-cache 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
+       of the reference nodes.
+
+--root::
+       Report root nodes.
+
+--tags::
+       Report tags.
+
+--cache::
+       Consider any object recorded in the cache also as a head node for
+       an unreachability trace.
+
+--standalone::
+       Limit checks to the contents of GIT_OBJECT_DIRECTORY
+       ($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;
+       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 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
+       objects that triggers this check, but it is recommended
+       to check new projects with this flag.
+
+It tests SHA1 and general object sanity, and it does full tracking of
+the resulting reachability and everything else. It prints out any
+corruption it finds (missing or bad objects), and if you use the
+'--unreachable' flag it will also print out objects that exist but
+that aren't readable from any of the specified head nodes.
+
+So for example
+
+       git-fsck-cache --unreachable $(cat .git/HEAD .git/refs/heads/*)
+
+will do quite a _lot_ of verification on the tree. There are a few
+extra validity tests to be added (make sure that tree objects are
+sorted properly etc), but on the whole if "git-fsck-cache" is happy, you
+do have a valid tree.
+
+Any corrupt objects you will have to find in backups or other archives
+(ie you can just remove them and do an "rsync" with some other site in
+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
+tracking system, not a quality assurance system ;)
+
+Extracted Diagnostics
+---------------------
+
+expect dangling commits - potential heads - due to lack of head information::
+       You haven't specified any nodes as heads so it won't be
+       possible to differentiate between un-parented commits and
+       root nodes.
+
+missing sha1 directory '<dir>'::
+       The directory holding the sha1 objects is missing.
+
+unreachable <type> <object>::
+       The <type> object <object>, isn't actually referred to directly
+       or indirectly in any of the trees or commits seen. This can
+       mean that there's another root node that you're not specifying
+       or that the tree is corrupt. If you haven't missed a root node
+       then you might as well delete unreachable nodes since they
+       can't be used.
+
+missing <type> <object>::
+       The <type> object <object>, is referred to but isn't present in
+       the database.
+
+dangling <type> <object>::
+       The <type> object <object>, is present in the database but never
+       'directly' used. A dangling commit could be a root node.
+
+warning: git-fsck-cache: tree <tree> has full pathnames in it::
+       And it shouldn't...
+
+sha1 mismatch <object>::
+       The database has an object who's sha1 doesn't match the
+       database value.
+       This indicates a serious data integrity problem.
+
+Environment Variables
+---------------------
+
+GIT_OBJECT_DIRECTORY::
+       used to specify the object database root (usually $GIT_DIR/objects)
+
+GIT_INDEX_FILE::
+       used to specify the index file of the cache
+
+GIT_ALTERNATE_OBJECT_DIRECTORIES:
+       used to specify additional object database roots (usually unset)
+
+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
+
diff --git a/Documentation/git-hash-object.txt b/Documentation/git-hash-object.txt
new file mode 100644 (file)
index 0000000..a77adab
--- /dev/null
@@ -0,0 +1,44 @@
+git-hash-object(1)
+==================
+v0.1, May 2005
+
+NAME
+----
+git-hash-object - Computes object ID and optionally creates a blob from a file.
+
+
+SYNOPSIS
+--------
+'git-hash-object' [-t <type>] [-w] <any-file-on-the-filesystem>
+
+DESCRIPTION
+-----------
+Computes the object ID value for an object with specified type
+with the contents of the named file (which can be outside of the
+work tree), and optionally writes the resulting object into the
+object database.  Reports its object ID to its standard output.
+This is used by "git-cvsimport-script" to update the cache
+without modifying files in the work tree.  When <type> is not
+specified, it defaults to "blob". 
+
+OPTIONS
+-------
+
+-t <type>::
+       Specify the type (default: "blob").
+
+-w::
+       Actually write the object into the object database.
+
+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
+
diff --git a/Documentation/git-http-pull.txt b/Documentation/git-http-pull.txt
new file mode 100644 (file)
index 0000000..edc5ceb
--- /dev/null
@@ -0,0 +1,42 @@
+git-http-pull(1)
+================
+v0.1, May 2005
+
+NAME
+----
+git-http-pull - Downloads a remote GIT repository via HTTP
+
+
+SYNOPSIS
+--------
+'git-http-pull' [-c] [-t] [-a] [-d] [-v] [-w filename] [--recover] commit-id url
+
+DESCRIPTION
+-----------
+Downloads a remote GIT repository via HTTP.
+
+-c::
+       Get the commit objects.
+-t::
+       Get trees associated with the commit objects.
+-a::
+       Get all the objects.
+-v::
+       Report what is downloaded.
+
+-w <filename>::
+        Writes the commit-id into the filename under $GIT_DIR/refs/<filename> on
+        the local end after the transfer is complete.
+
+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
+
diff --git a/Documentation/git-init-db.txt b/Documentation/git-init-db.txt
new file mode 100644 (file)
index 0000000..fb8b522
--- /dev/null
@@ -0,0 +1,41 @@
+git-init-db(1)
+==============
+v0.1, May 2005
+
+NAME
+----
+git-init-db - Creates an empty git repository
+
+
+SYNOPSIS
+--------
+'git-init-db'
+
+DESCRIPTION
+-----------
+This simply creates an empty git repository - basically a `.git` directory
+and `.git/object/??/`, `.git/refs/heads` and `.git/refs/tags` directories,
+and links `.git/HEAD` symbolically to `.git/refs/heads/master`.
+
+If the 'GIT_DIR' environment variable is set then it specifies a path
+to use instead of `./.git` for the base of the repository.
+
+If the object storage directory is specified via the 'GIT_OBJECT_DIRECTORY'
+environment variable then the sha1 directories are created underneath -
+otherwise the default `$GIT_DIR/objects` directory is used.
+
+"git-init-db" won't hurt an existing repository.
+
+
+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
+
diff --git a/Documentation/git-local-pull.txt b/Documentation/git-local-pull.txt
new file mode 100644 (file)
index 0000000..4651e81
--- /dev/null
@@ -0,0 +1,44 @@
+git-local-pull(1)
+=================
+v0.1, May 2005
+
+NAME
+----
+git-local-pull - Duplicates another GIT repository on a local system
+
+
+SYNOPSIS
+--------
+'git-local-pull' [-c] [-t] [-a] [-d] [-v] [-w filename] [--recover] [-l] [-s] [-n] commit-id path
+
+DESCRIPTION
+-----------
+Duplicates another GIT repository on a local system.
+
+OPTIONS
+-------
+-c::
+       Get the commit objects.
+-t::
+       Get trees associated with the commit objects.
+-a::
+       Get all the objects.
+-v::
+       Report what is downloaded.
+
+-w <filename>::
+        Writes the commit-id into the filename under $GIT_DIR/refs/<filename> on
+        the local end after the transfer is complete.
+
+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
+
diff --git a/Documentation/git-ls-files.txt b/Documentation/git-ls-files.txt
new file mode 100644 (file)
index 0000000..d05827f
--- /dev/null
@@ -0,0 +1,197 @@
+git-ls-files(1)
+===============
+v0.1, May 2005
+
+NAME
+----
+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])\*
+               [-x <pattern>|--exclude=<pattern>]
+               [-X <file>|--exclude-from=<file>]
+               [--exclude-per-directory=<file>]
+
+DESCRIPTION
+-----------
+This merges the file listing in the directory cache index with the
+actual working directory list, and shows different combinations of the
+two.
+
+One or more of the options below may be used to determine the files
+shown:
+
+OPTIONS
+-------
+-c|--cached::
+       Show cached files in the output (default)
+
+-d|--deleted::
+       Show deleted files in the output
+
+-o|--others::
+       Show other files in the output
+
+-i|--ignored::
+       Show ignored files in the output
+       Note the this also reverses any exclude list present.
+
+-s|--stage::
+       Show stage files in the output
+
+-u|--unmerged::
+       Show unmerged files in the output (forces --stage)
+
+-k|--killed::
+       Show files on the filesystem that need to be removed due
+       to file/directory conflicts for checkout-cache to
+       succeed.
+
+-z::
+       \0 line termination on output
+
+-x|--exclude=<pattern>::
+       Skips files matching pattern.
+       Note that pattern is a shell wildcard pattern.
+
+-X|--exclude-from=<file>::
+       exclude patterns are read from <file>; 1 per line.
+
+--exclude-per-directory=<file>::
+       read additional exclude patterns that apply only to the
+       directory and its subdirectories in <file>.
+
+-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
+       ?       other
+
+Output
+------
+show files just outputs the filename unless '--stage' is specified in
+which case it outputs:
+
+        [<tag> ]<mode> <object> <stage> <file>
+
+"git-ls-files --unmerged" and "git-ls-files --stage" can be used to examine
+detailed information on unmerged paths.
+
+For an unmerged path, instead of recording a single mode/SHA1 pair,
+the dircache records up to three such pairs; one from tree O in stage
+1, A in stage 2, and B in stage 3.  This information can be used by
+the user (or the porcelain) to see what should eventually be recorded at the
+path. (see read-cache for more information on state)
+
+
+Exclude Patterns
+----------------
+
+'git-ls-files' can use a list of "exclude patterns" when
+traversing the directory tree and finding files to show when the
+flags --others or --ignored are specified.
+
+These exclude patterns come from these places:
+
+ (1) command line flag --exclude=<pattern> specifies a single
+     pattern.
+
+ (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
+     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.
+
+An exclude pattern file used by (2) and (3) contains one pattern
+per line.  A line that starts with a '#' can be used as comment
+for readability.
+
+There are three lists of patterns that are in effect at a given
+time.  They are built and ordered in the following way:
+
+ * --exclude=<pattern> from the command line; patterns are
+   ordered in the same order as they appear on the command line.
+
+ * lines read from --exclude-from=<file>; patterns are ordered
+   in the same order as they appear in the file.
+
+ * When --exclude-per-directory=<name> is specified, upon
+   entering a directory that has such a file, its contents are
+   appended at the end of the current "list of patterns".  They
+   are popped off when leaving the directory.
+
+Each pattern in the pattern list specifies "a match pattern" and
+optionally the fate; either a file that matches the pattern is
+considered excluded or included.  A filename is matched against
+the patterns in the three lists; the --exclude-from list is
+checked first, then the --exclude-per-directory list, and then
+finally the --exclude list. The last match determines its fate.
+If there is no match in the three lists, the fate is "included".
+
+A pattern specified on the command line with --exclude or read
+from the file specified with --exclude-from is relative to the
+top of the directory tree.  A pattern read from a file specified
+by --exclude-per-directory is relative to the directory that the
+pattern file appears in.
+
+An exclude pattern is of the following format:
+
+ - an optional prefix '!' which means that the fate this pattern
+   specifies is "include", not the usual "exclude"; the
+   remainder of the pattern string is interpreted according to
+   the following rules.
+
+ - if it does not contain a slash '/', it is a shell glob
+   pattern and used to match against the filename without
+   leading directories (i.e. the same way as the current
+   implementation).
+
+ - 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
+   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]
+    $ cat Documentation/.gitignore
+    # ignore generated html files,
+    *.html
+    # except foo.html which is maintained by hand
+    !foo.html
+    $ git-ls-files --ignored \
+        --exclude='Documentation/*.[0-9]' \
+        --exclude-from=.git/ignore \
+        --exclude-per-directory=.gitignore
+
+
+See Also
+--------
+link:read-cache.html[read-cache]
+
+
+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
+
diff --git a/Documentation/git-ls-remote-script.txt b/Documentation/git-ls-remote-script.txt
new file mode 100644 (file)
index 0000000..0b98e6a
--- /dev/null
@@ -0,0 +1,64 @@
+git-ls-remote-script(1)
+=======================
+v0.1, May 2005
+
+NAME
+----
+git-ls-remote-script - Look at references other repository has.
+
+
+SYNOPSIS
+--------
+'git-ls-remote' [--heads] [--tags] <repository> <refs>...
+
+DESCRIPTION
+-----------
+Displays the references other repository has.
+
+
+OPTIONS
+-------
+--heads --tags::
+       Limit to only refs/heads and refs/tags, respectively.
+       These options are _not_ mutually exclusive; when given
+       both, references stored in refs/heads and refs/tags are
+       displayed.
+
+<repository>::
+       Location of the repository.  The shorthand defined in
+       $GIT_DIR/branches/ can be used.
+
+<refs>...::
+       When unspecified, all references, after filtering done
+       with --heads and --tags, are shown.  When <refs>... are
+       specified, only references matching the given patterns
+       are displayed.
+
+EXAMPLES
+--------
+
+       $ git ls-remote --tags ./.
+       d6602ec5194c87b0fc87103ca4d67251c76f233a        refs/tags/v0.99
+       f25a265a342aed6041ab0cc484224d9ca54b6f41        refs/tags/v0.99.1
+       7ceca275d047c90c0c7d5afb13ab97efdf51bd6e        refs/tags/v0.99.3
+       c5db5456ae3b0873fc659c19fafdde22313cc441        refs/tags/v0.99.2
+       0918385dbd9656cab0d1d81ba7453d49bbc16250        refs/tags/junio-gpg-pub
+       $ git ls-remote http://www.kernel.org/pub/scm/git/git.git master pu rc
+       5fe978a5381f1fbad26a80e682ddd2a401966740        refs/heads/master
+       c781a84b5204fb294c9ccc79f8b3baceeb32c061        refs/heads/pu
+       b1d096f2926c4e37c9c0b6a7bf2119bedaa277cb        refs/heads/rc
+       $ echo http://www.kernel.org/pub/scm/git/git.git >.git/branches/public
+       $ git ls-remote --tags public v\*
+       d6602ec5194c87b0fc87103ca4d67251c76f233a        refs/tags/v0.99
+       f25a265a342aed6041ab0cc484224d9ca54b6f41        refs/tags/v0.99.1
+       c5db5456ae3b0873fc659c19fafdde22313cc441        refs/tags/v0.99.2
+       7ceca275d047c90c0c7d5afb13ab97efdf51bd6e        refs/tags/v0.99.3
+
+Author
+------
+Written by Junio C Hamano <junkio@cox.net>
+
+GIT
+---
+Part of the link:git.html[git] suite
+
diff --git a/Documentation/git-ls-tree.txt b/Documentation/git-ls-tree.txt
new file mode 100644 (file)
index 0000000..fdb215e
--- /dev/null
@@ -0,0 +1,55 @@
+git-ls-tree(1)
+==============
+v0.1, May 2005
+
+NAME
+----
+git-ls-tree - Lists the contents of a tree object.
+
+
+SYNOPSIS
+--------
+'git-ls-tree' [-d] [-r] [-z] <tree-ish> [paths...]
+
+DESCRIPTION
+-----------
+Lists the contents of a tree object, like what "/bin/ls -a" does
+in the current working directory.
+
+OPTIONS
+-------
+<tree-ish>::
+       Id of a tree-ish.
+
+-d::
+       show only the named tree entry itself, not its children
+
+-r::
+       recurse into sub-trees
+
+-z::
+       \0 line termination on output
+
+paths::
+       When paths are given, show them.  Otherwise implicitly
+       uses the root level of the tree as the sole path argument.
+
+
+Output Format
+-------------
+        <mode> SP <type> SP <object> TAB <file>
+
+
+Author
+------
+Written by Linus Torvalds <torvalds@osdl.org>
+Completely rewritten from scratch 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
+
diff --git a/Documentation/git-merge-base.txt b/Documentation/git-merge-base.txt
new file mode 100644 (file)
index 0000000..1e27bf2
--- /dev/null
@@ -0,0 +1,34 @@
+git-merge-base(1)
+=================
+v0.1, May 2005
+
+NAME
+----
+git-merge-base - Finds as good a common ancestor as possible for a merge
+
+
+SYNOPSIS
+--------
+'git-merge-base' <commit> <commit>
+
+DESCRIPTION
+-----------
+"git-merge-base" finds as good a common ancestor as possible. Given a
+selection of equally good common ancestors it should not be relied on
+to decide in any particular way.
+
+The "git-merge-base" algorithm is still in flux - use the source...
+
+
+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
+
diff --git a/Documentation/git-merge-cache.txt b/Documentation/git-merge-cache.txt
new file mode 100644 (file)
index 0000000..bd3c9ca
--- /dev/null
@@ -0,0 +1,89 @@
+git-merge-cache(1)
+==================
+v0.1, May 2005
+
+NAME
+----
+git-merge-cache - Runs a merge for files needing merging
+
+
+SYNOPSIS
+--------
+'git-merge-cache' [-o] [-q] <merge-program> (-a | -- | <file>\*) 
+
+DESCRIPTION
+-----------
+This looks up the <file>(s) in the cache and, if there are any merge
+entries, passes the SHA1 hash for those files as arguments 1, 2, 3 (empty
+argument if no file), and <file> as argument 4.  File modes for the three
+files are passed as arguments 5, 6 and 7.
+
+OPTIONS
+-------
+--::
+       Interpret all following arguments as filenames.
+
+-a::
+       Run merge against all files in the cache that need merging.
+
+-o::
+       Instead of stopping at the first failed merge, do all of them
+       in one shot - continue with merging even when previous merges
+       returned errors, and only return the error code after all the
+       merges are over.
+
+-q::
+       Do not complain about failed merge program (the merge program
+       failure usually indicates conflicts during merge). This is for
+       porcelains which might want to emit custom messages.
+
+If "git-merge-cache" is called with multiple <file>s (or -a) then it
+processes them in turn only stopping if merge returns a non-zero exit
+code.
+
+Typically this is run with the a script calling the merge command from
+the RCS package.
+
+A sample script called "git-merge-one-file-script" is included in the
+distribution.
+
+ALERT ALERT ALERT! The git "merge object order" is different from the
+RCS "merge" program merge object order. In the above ordering, the
+original is first. But the argument order to the 3-way merge program
+"merge" is to have the original in the middle. Don't ask me why.
+
+Examples:
+
+  torvalds@ppc970:~/merge-test> git-merge-cache cat MM
+  This is MM from the original tree.                   # original
+  This is modified MM in the branch A.                 # merge1
+  This is modified MM in the branch B.                 # merge2
+  This is modified MM in the branch B.                 # current contents
+
+or 
+
+  torvalds@ppc970:~/merge-test> git-merge-cache cat AA MM
+  cat: : No such file or directory
+  This is added AA in the branch A.
+  This is added AA in the branch B.
+  This is added AA in the branch B.
+  fatal: merge program failed
+
+where the latter example shows how "git-merge-cache" will stop trying to
+merge once anything has returned an error (ie "cat" returned an error
+for the AA file, because it didn't exist in the original, and thus
+"git-merge-cache" didn't even try to merge the MM thing).
+
+Author
+------
+Written by Linus Torvalds <torvalds@osdl.org>
+One-shot merge by Petr Baudis <pasky@ucw.cz>
+
+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
+
diff --git a/Documentation/git-merge-one-file-script.txt b/Documentation/git-merge-one-file-script.txt
new file mode 100644 (file)
index 0000000..387601d
--- /dev/null
@@ -0,0 +1,30 @@
+git-merge-one-file-script(1)
+============================
+v0.1, May 2005
+
+NAME
+----
+git-merge-one-file-script - The standard helper program to use with "git-merge-cache"
+
+
+SYNOPSIS
+--------
+'git-merge-one-file-script'
+
+DESCRIPTION
+-----------
+This is the standard helper program to use with "git-merge-cache"
+to resolve a merge after the trivial merge done with "git-read-tree -m".
+
+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
+
diff --git a/Documentation/git-mktag.txt b/Documentation/git-mktag.txt
new file mode 100644 (file)
index 0000000..708f4ef
--- /dev/null
@@ -0,0 +1,48 @@
+git-mktag(1)
+============
+v0.1, May 2005
+
+NAME
+----
+git-mktag - Creates a tag object
+
+
+SYNOPSIS
+--------
+'git-mktag' < signature_file
+
+DESCRIPTION
+-----------
+Reads a tag contents on standard input and creates a tag object
+that can also be used to sign other objects.
+
+The output is the new tag's <object> identifier.
+
+Tag Format
+----------
+A tag signature file has a very simple fixed format: three lines of
+
+  object <sha1>
+  type <typename>
+  tag <tagname>
+
+followed by some 'optional' free-form signature that git itself
+doesn't care about, but that can be verified with gpg or similar.
+
+The size of the full object is artificially limited to 8kB.  (Just
+because I'm a lazy bastard, and if you can't fit a signature in that
+size, you're doing something wrong)
+
+
+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
+
diff --git a/Documentation/git-pack-objects.txt b/Documentation/git-pack-objects.txt
new file mode 100644 (file)
index 0000000..9628183
--- /dev/null
@@ -0,0 +1,80 @@
+git-pack-objects(1)
+===================
+v0.1, July 2005
+
+NAME
+----
+git-pack-objects - Create a packed archive of objects.
+
+
+SYNOPSIS
+--------
+'git-pack-objects' [--incremental] [--window=N] [--depth=N] {--stdout | base-name} < object-list
+
+
+DESCRIPTION
+-----------
+Reads list of objects from the standard input, and writes a packed
+archive with specified base-name, or to the standard output.
+
+A packed archive is an efficient way to transfer set of objects
+between two repositories, and also is an archival format which
+is efficient to access.  The packed archive format (.pack) is
+designed to be unpackable without having anything else, but for
+random access, accompanied with the pack index file (.idx).
+
+'git-unpack-objects' command can read the packed archive and
+expand the objects contained in the pack into "one-file
+one-object" format; this is typically done by the smart-pull
+commands when a pack is created on-the-fly for efficient network
+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.
+
+
+OPTIONS
+-------
+base-name::
+       Write into a pair of files (.pack and .idx), using
+       <base-name> to determine the name of the created file.
+       When this option is used, the two files are written in
+       <base-name>-<SHA1>.{pack,idx} files.  <SHA1> is a hash
+       of object names (currently in random order so it does
+       not have any useful meaning) to make the resulting
+       filename reasonably unique, and written to the standard
+       output of the command.
+
+--stdout::
+       Write the pack contents (what would have been writtin to
+       .pack file) out to the standard output.
+
+--window and --depth::
+       These two options affects how the objects contained in
+       the pack are stored using delta compression.  The
+       objects are first internally sorted by type, size and
+       optionally names and compared against the other objects
+       within --window to see if using delta compression saves
+       space.  --depth limits the maximum delta depth; making
+       it too deep affects the performance on the unpacker
+       side, because delta data needs to be applied that many
+       times to get to the necessary object.
+
+--incremental::
+       This flag causes an object already in a pack ignored
+       even if it appears in the standard input.
+
+
+Author
+------
+Written by Linus Torvalds <torvalds@osdl.org>
+
+Documentation
+-------------
+Documentation by Junio C Hamano
+
+GIT
+---
+Part of the link:git.html[git] suite
+
diff --git a/Documentation/git-peek-remote.txt b/Documentation/git-peek-remote.txt
new file mode 100644 (file)
index 0000000..9007540
--- /dev/null
@@ -0,0 +1,53 @@
+git-peek-remote(1)
+==================
+v0.1, July 2005
+
+NAME
+----
+git-peek-remote - Lists the references in a remote repository.
+
+
+SYNOPSIS
+--------
+'git-peek-remote' [--exec=<git-upload-pack>] [<host>:]<directory>
+
+DESCRIPTION
+-----------
+Lists the references the remote repository has, and optionally
+stores them in the local repository under the same name.
+
+OPTIONS
+-------
+--exec=<git-upload-pack>::
+       Use this to specify the path to 'git-upload-pack' on the
+       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
+       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
+       shells, but prefer having a lean .bashrc file (they set most of
+       the things up in .bash_profile).
+
+<host>::
+       A remote host that houses the repository.  When this
+       part is specified, 'git-upload-pack' is invoked via
+       ssh.
+
+<directory>::
+       The repository to sync from.
+
+
+Author
+------
+Written by Junio C Hamano <junkio@cox.net>
+
+Documentation
+--------------
+Documentation by Junio C Hamano.
+
+GIT
+---
+Part of the link:git.html[git] suite
+
diff --git a/Documentation/git-prune-script.txt b/Documentation/git-prune-script.txt
new file mode 100644 (file)
index 0000000..e8305a6
--- /dev/null
@@ -0,0 +1,33 @@
+git-prune-script(1)
+===================
+v0.1, May 2005
+
+NAME
+----
+git-prune-script - Prunes all unreachable objects from the object database
+
+
+SYNOPSIS
+--------
+'git-prune-script'
+
+DESCRIPTION
+-----------
+This runs "git-fsck-cache --unreachable" program using the heads specified
+on the command line (or `$GIT_DIR/refs/heads/\*` and `$GIT_DIR/refs/tags/\*`
+if none is specified), and prunes all unreachable objects from the object
+database.
+
+
+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
+
diff --git a/Documentation/git-pull-script.txt b/Documentation/git-pull-script.txt
new file mode 100644 (file)
index 0000000..ec1e7a2
--- /dev/null
@@ -0,0 +1,37 @@
+git-pull-script(1)
+==================
+v0.1, May 2005
+
+NAME
+----
+git-pull-script - Pull and merge from another repository.
+
+
+SYNOPSIS
+--------
+'git-pull-script' <repository> [ <head> | tag <tag> ]
+
+
+DESCRIPTION
+-----------
+Runs 'git-fetch-script' with the given parameters, then
+'git-resolve-script' to merge the local HEAD and FETCH_HEAD.
+
+
+OPTIONS
+-------
+include::pull-fetch-param.txt[]
+
+
+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
+
diff --git a/Documentation/git-read-tree.txt b/Documentation/git-read-tree.txt
new file mode 100644 (file)
index 0000000..e1be6cc
--- /dev/null
@@ -0,0 +1,268 @@
+git-read-tree(1)
+================
+v0.1, May 2005
+
+NAME
+----
+git-read-tree - Reads tree information into the directory cache
+
+
+SYNOPSIS
+--------
+'git-read-tree' (<tree-ish> | [-m [-u]] <tree-ish1> [<tree-ish2> [<tree-ish3>]])
+
+
+DESCRIPTION
+-----------
+Reads the tree information given by <tree-ish> into the directory cache,
+but does not actually *update* any of the files it "caches". (see:
+git-checkout-cache)
+
+Optionally, it can merge a tree into the cache, perform a
+fast-forward (i.e. 2-way) merge, or a 3-way merge, with the -m
+flag.  When used with -m, the -u flag causes it to also update
+the files in the work tree with the result of the merge.
+
+Trivial merges are done by "git-read-tree" itself.  Only conflicting paths
+will be in unmerged state when "git-read-tree" returns.
+
+OPTIONS
+-------
+-m::
+       Perform a merge, not just a read.
+
+-u::
+       After a successful merge, update the files in the work
+       tree with the result of the merge.
+
+<tree-ish#>::
+       The id of the tree object(s) to be read/merged.
+
+
+Merging
+-------
+If '-m' is specified, "git-read-tree" can perform 3 kinds of
+merge, a single tree merge if only 1 tree is given, a
+fast-forward merge with 2 trees, or a 3-way merge if 3 trees are
+provided.
+
+
+Single Tree Merge
+~~~~~~~~~~~~~~~~~
+If only 1 tree is specified, git-read-tree operates as if the user did not
+specify '-m', except that if the original cache has an entry for a
+given pathname, and the contents of the path matches with the tree
+being read, the stat info from the cache is used. (In other words, the
+cache's stat()s take precedence over the merged tree's).
+
+That means that if you do a "git-read-tree -m <newtree>" followed by a
+"git-checkout-cache -f -u -a", the "git-checkout-cache" only checks out
+the stuff that really changed.
+
+This is used to avoid unnecessary false hits when "git-diff-files" is
+run after git-read-tree.
+
+
+Two Tree Merge
+~~~~~~~~~~~~~~
+
+Typically, this is invoked as "git-read-tree -m $H $M", where $H
+is the head commit of the current repository, and $M is the head
+of a foreign tree, which is simply ahead of $H (i.e. we are in a
+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
+        the user may have local changes in them since $H;
+
+    (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".
+Here are the "carry forward" rules:
+
+        I (index)           H        M        Result
+       -------------------------------------------------------
+      0 nothing             nothing  nothing  (does not happen)
+      1 nothing             nothing  exists   use M
+      2 nothing             exists   nothing  remove path from cache
+      3 nothing             exists   exists   use M
+
+        clean I==H  I==M
+       ------------------
+      4 yes   N/A   N/A     nothing  nothing  keep index
+      5 no    N/A   N/A     nothing  nothing  keep index
+
+      6 yes   N/A   yes     nothing  exists   keep index
+      7 no    N/A   yes     nothing  exists   keep index
+      8 yes   N/A   no      nothing  exists   fail
+      9 no    N/A   no      nothing  exists   fail
+
+     10 yes   yes   N/A     exists   nothing  remove path from cache
+     11 no    yes   N/A     exists   nothing  fail
+     12 yes   no    N/A     exists   nothing  fail
+     13 no    no    N/A     exists   nothing  fail
+
+        clean (H=M)
+       ------
+     14 yes                 exists   exists   keep index
+     15 no                  exists   exists   keep index
+
+        clean I==H  I==M (H!=M)
+       ------------------
+     16 yes   no    no      exists   exists   fail
+     17 no    no    no      exists   exists   fail
+     18 yes   no    yes     exists   exists   keep index
+     19 no    no    yes     exists   exists   keep index
+     20 yes   yes   no      exists   exists   use M
+     21 no    yes   no      exists   exists   fail
+
+In all "keep index" cases, the cache entry stays as in the
+original index file.  If the entry were not up to date,
+git-read-tree keeps the copy in the work tree intact when
+operating under the -u flag.
+
+When this form of git-read-tree returns successfully, you can
+see what "local changes" you made are carried forward by running
+"git-diff-cache --cached $M".  Note that this does not
+necessarily match "git-diff-cache --cached $H" would have
+produced before such a two tree merge.  This is because of cases
+18 and 19 --- if you already had the changes in $M (e.g. maybe
+you picked it up via e-mail in a patch form), "git-diff-cache
+--cached $H" would have told you about the change before this
+merge, but it would not show in "git-diff-cache --cached $M"
+output after two-tree merge.
+
+
+3-Way Merge
+~~~~~~~~~~~
+Each "index" entry has two bits worth of "stage" state. stage 0 is the
+normal one, and is the only one you'd see in any kind of normal use.
+
+However, when you do "git-read-tree" with three trees, the "stage"
+starts out at 1.
+
+This means that you can do
+
+       git-read-tree -m <tree1> <tree2> <tree3>
+
+and you will end up with an index with all of the <tree1> entries in
+"stage1", all of the <tree2> entries in "stage2" and all of the
+<tree3> entries in "stage3".
+
+Furthermore, "git-read-tree" has special-case logic that says: if you see
+a file that matches in all respects in the following states, it
+"collapses" back to "stage0":
+
+   - stage 2 and 3 are the same; take one or the other (it makes no
+     difference - the same work has been done on stage 2 and 3)
+
+   - stage 1 and stage 2 are the same and stage 3 is different; take
+     stage 3 (some work has been done on stage 3)
+
+   - stage 1 and stage 3 are the same and stage 2 is different take
+     stage 2 (some work has been done on stage 2)
+
+The "git-write-tree" command refuses to write a nonsensical tree, and it
+will complain about unmerged entries if it sees a single entry that is not
+stage 0.
+
+Ok, this all sounds like a collection of totally nonsensical rules,
+but it's actually exactly what you want in order to do a fast
+merge. The different stages represent the "result tree" (stage 0, aka
+"merged"), the original tree (stage 1, aka "orig"), and the two trees
+you are trying to merge (stage 2 and 3 respectively).
+
+The order of stages 1, 2 and 3 (hence the order of three
+<tree-ish> command line arguments) are significant when you
+start a 3-way merge with an index file that is already
+populated.  Here is an outline of how the algorithm works:
+
+- if a file exists in identical format in all three trees, it will
+  automatically collapse to "merged" state by git-read-tree.
+
+- a file that has _any_ difference what-so-ever in the three trees
+  will stay as separate entries in the index. It's up to "porcelain
+  policy" to determine how to remove the non-0 stages, and insert a
+  merged version.
+
+- the index file saves and restores with all this information, so you
+  can merge things incrementally, but as long as it has entries in
+  stages 1/2/3 (ie "unmerged entries") you can't write the result. So
+  now the merge algorithm ends up being really simple:
+
+  * you walk the index in order, and ignore all entries of stage 0,
+    since they've already been done.
+
+  * if you find a "stage1", but no matching "stage2" or "stage3", you
+    know it's been removed from both trees (it only existed in the
+    original tree), and you remove that entry.
+
+  * if you find a matching "stage2" and "stage3" tree, you remove one
+    of them, and turn the other into a "stage0" entry. Remove any
+    matching "stage1" entry if it exists too.  .. all the normal
+    trivial rules ..
+
+You would normally use "git-merge-cache" with supplied
+"git-merge-one-file-script" to do this last step.  The script
+does not touch the files in the work tree, and the entire merge
+happens in the index file.  In other words, there is no need to
+worry about what is in the working directory, since it is never
+shown and never used.
+
+When you start a 3-way merge with an index file that is already
+populated, it is assumed that it represents the state of the
+files in your work tree, and you can even have files with
+changes unrecorded in the index file.  It is further assumed
+that this state is "derived" from the stage 2 tree.  The 3-way
+merge refuses to run if it finds an entry in the original index
+file that does not match stage 2.
+
+This is done to prevent you from losing your work-in-progress
+changes.  To illustrate, suppose you start from what has been
+commited last to your repository:
+
+    $ JC=`cat .git/HEAD`
+    $ git-checkout-cache -f -u -a $JC
+
+You do random edits, without running git-update-cache.  And then
+you notice that the tip of your "upstream" tree has advanced
+since you pulled from him:
+
+    $ git-fetch-script rsync://.... linus
+    $ LT=`cat .git/MERGE_HEAD`
+
+Your work tree is still based on your HEAD ($JC), but you have
+some edits since.  Three-way merge makes sure that you have not
+added or modified cache entries since $JC, and if you haven't,
+then does the right thing.  So with the following sequence:
+
+    $ git-read-tree -m -u `git-merge-base $JC $LT` $JC $LT
+    $ git-merge-cache git-merge-one-file-script -a
+    $ echo "Merge with Linus" | \
+      git-commit-tree `git-write-tree` -p $JC -p $LT
+
+what you would commit is a pure merge between $JC and LT without
+your work-in-progress changes, and your work tree would be
+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]
+
+
+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
+
diff --git a/Documentation/git-receive-pack.txt b/Documentation/git-receive-pack.txt
new file mode 100644 (file)
index 0000000..2f1a1f8
--- /dev/null
@@ -0,0 +1,90 @@
+git-receive-pack(1)
+===================
+v0.1, July 2005
+
+NAME
+----
+git-receive-pack - Receive what is pushed into it
+
+
+SYNOPSIS
+--------
+'git-receive-pack' <directory>
+
+DESCRIPTION
+-----------
+Invoked by 'git-send-pack' and updates the repository with the
+information fed from the remote end.
+
+This command is usually not invoked directly by the end user.
+The UI for the protocol is on the 'git-send-pack' side, and the
+program pair is meant to be used to push updates to remote
+repository.  For pull operations, see 'git-fetch-pack' and
+'git-clone-pack'.
+
+The command allows for creation and fast forwarding of sha1 refs
+(heads/tags) on the remote end (strictly speaking, it is the
+local end receive-pack runs, but to the user who is sitting at
+the send-pack end, it is updating the remote.  Confused?)
+
+Before each ref is updated, if $GIT_DIR/hooks/update file exists
+and executable, it is called with three parameters:
+
+       $GIT_DIR/hooks/update refname sha1-old sha1-new
+
+The refname parameter is relative to $GIT_DIR; e.g. for the
+master head this is "refs/heads/master".  Two sha1 are the
+object names for the refname before and after the update.  Note
+that the hook is called before the refname is updated, so either
+sha1-old is 0{40} (meaning there is no such ref yet), or it
+should match what is recorded in refname.
+
+The hook should exit with non-zero status if it wants to
+disallow updating the named ref.  Otherwise it should exit with
+zero.
+
+Using this hook, it is easy to generate mails on updates to
+the local repository. This example script sends a mail with
+the commits pushed to the repository:
+
+       #!/bin/sh
+       # mail out commit update information.
+       if expr "$2" : '0*$' >/dev/null
+       then
+               echo "Created a new ref, with the following commits:"
+               git-rev-list --pretty "$2"
+       else
+               echo "New commits:"
+               git-rev-list --pretty "$3" "^$2"
+       fi |
+       mail -s "Changes to ref $1" commit-list@mydomain
+       exit 0
+
+Another hook $GIT_DIR/hooks/post-update, if exists and
+executable, is called with the list of refs that have been
+updated.  This can be used to implement repository wide cleanup
+task if needed.  The exit code from this hook invocation is
+ignored; the only thing left for git-receive-pack to do at that
+point is to exit itself anyway.  This hook can be used, for
+example, to run "git-update-server-info" if the repository is
+packed and is served via a dumb transport.
+
+       #!/bin/sh
+       exec git-update-server-info
+
+OPTIONS
+-------
+<directory>::
+       The repository to sync into.
+
+Author
+------
+Written by Linus Torvalds <torvalds@osdl.org>
+
+Documentation
+--------------
+Documentation by Junio C Hamano.
+
+GIT
+---
+Part of the link:git.html[git] suite
diff --git a/Documentation/git-rename-script.txt b/Documentation/git-rename-script.txt
new file mode 100644 (file)
index 0000000..1abc68b
--- /dev/null
@@ -0,0 +1,34 @@
+
+git-rename-script(1)
+=====================
+v0.1, May 2005
+
+NAME
+----
+git-rename-script - Script used to rename a file, directory or symlink.
+
+
+SYNOPSIS
+--------
+'git-rename-script' <source> <destination>
+
+DESCRIPTION
+-----------
+This script is used to rename a file, directory or symlink.
+
+The index is updated after successful completion, but the change must still be
+committed.
+
+Author
+------
+Written by Linus Torvalds <torvalds@osdl.org>
+Rewritten by Ryan Anderson <ryan@michonline.com>
+
+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
+
diff --git a/Documentation/git-resolve-script.txt b/Documentation/git-resolve-script.txt
new file mode 100644 (file)
index 0000000..8dd84a3
--- /dev/null
@@ -0,0 +1,30 @@
+git-resolve-script(1)
+=====================
+v0.1, May 2005
+
+NAME
+----
+git-resolve-script - Script used to merge two trees
+
+
+SYNOPSIS
+--------
+'git-resolve-script'
+
+DESCRIPTION
+-----------
+This script is used by Linus to merge two trees.
+
+
+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
+
diff --git a/Documentation/git-rev-list.txt b/Documentation/git-rev-list.txt
new file mode 100644 (file)
index 0000000..35fecf3
--- /dev/null
@@ -0,0 +1,96 @@
+git-rev-list(1)
+===============
+v0.1, May 2005
+
+NAME
+----
+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> ...]
+
+DESCRIPTION
+-----------
+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
+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.
+
+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.
+
+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.
+
+Author
+------
+Written by Linus Torvalds <torvalds@osdl.org>
+
+Original *--merge-order* logic by Jon Seymour <jon.seymour@gmail.com>
+
+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
+
diff --git a/Documentation/git-rev-tree.txt b/Documentation/git-rev-tree.txt
new file mode 100644 (file)
index 0000000..2ec7ed0
--- /dev/null
@@ -0,0 +1,88 @@
+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
+
diff --git a/Documentation/git-send-email-script.txt b/Documentation/git-send-email-script.txt
new file mode 100644 (file)
index 0000000..89f98bb
--- /dev/null
@@ -0,0 +1,74 @@
+git-send-email-script(1)
+=======================
+v0.1, July 2005
+
+NAME
+----
+git-send-email-script - Send a collection of patches as emails
+
+
+SYNOPSIS
+--------
+'git-send-email-script' [options] <file|directory> [... file|directory]
+
+
+
+DESCRIPTION
+-----------
+Takes the patches given on the command line and emails them out.
+
+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.
+
+The options available are:
+
+  --to
+       Specify the primary recipient of the emails generated.
+       Generally, this will be the upstream maintainer of the
+       project involved.
+
+   --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.
+
+   --subject
+       Specify the initial subject of the email thread.
+
+   --in-reply-to
+       Specify the contents of the first In-Reply-To header.
+       Subsequent emails will refer to the previous email 
+       instead of this.
+       When overriding on the command line, it may be necessary
+       to set this to a space.  For example
+               --in-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
+       this, it is recommended that the first file given be an overview of the
+       entire patch series.
+       Default is --chain-reply-to
+
+   --smtp-server
+       If set, specifies the outgoing SMTP server to use.  Defaults to
+       localhost.
+
+
+Author
+------
+Written by Ryan Anderson <ryan@michonline.com>
+
+git-send-email-script is originally based upon
+send_lots_of_email.pl by Greg Kroah-Hartman.
+
+Documentation
+--------------
+Documentation by Ryan Anderson
+
+GIT
+---
+Part of the link:git.html[git] suite
+
diff --git a/Documentation/git-send-pack.txt b/Documentation/git-send-pack.txt
new file mode 100644 (file)
index 0000000..6192c5d
--- /dev/null
@@ -0,0 +1,92 @@
+git-send-pack(1)
+================
+v0.1, July 2005
+
+NAME
+----
+git-send-pack - Push missing objects packed.
+
+
+SYNOPSIS
+--------
+'git-send-pack' [--all] [--exec=<git-receive-pack>] [<host>:]<directory> [<head>...]
+
+DESCRIPTION
+-----------
+Invokes 'git-receive-pack' on a possibly remote repository, and
+updates it from the current repository, sending named heads.
+
+
+OPTIONS
+-------
+--exec=<git-receive-pack>::
+       Path to the 'git-receive-pack' program on the remote
+       end.  Sometimes useful when pushing to a remote
+       repository over ssh, and you do not have the program in
+       a directory on the default $PATH.
+
+--all::
+       Instead of explicitly specifying which refs to update,
+       update all refs that locally exist.
+
+<host>::
+       A remote host to house the repository.  When this
+       part is specified, 'git-receive-pack' is invoked via
+       ssh.
+
+<directory>::
+       The repository to update.
+
+<head>...:
+       The remote refs to update.
+
+
+Specifying the Refs
+-------------------
+
+There are three ways to specify which refs to update on the
+remote end.
+
+With '--all' flag, all refs that exist locally are transfered to
+the remote side.  You cannot specify any '<head>' if you use
+this flag.
+
+Without '--all' and without any '<head>', the refs that exist
+both on the local side and on the remote side are updated.
+
+When '<head>'s 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
+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 that is
+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 local
+   refs.
+
+ - It is an error if <dst> matches more than one remote 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.
+
+
+Author
+------
+Written by Linus Torvalds <torvalds@osdl.org>
+
+Documentation
+--------------
+Documentation by Junio C Hamano.
+
+GIT
+---
+Part of the link:git.html[git] suite
diff --git a/Documentation/git-show-index.txt b/Documentation/git-show-index.txt
new file mode 100644 (file)
index 0000000..beefe94
--- /dev/null
@@ -0,0 +1,36 @@
+git-show-index(1)
+=================
+v0.1, July 2005
+
+NAME
+----
+git-show-index - Show packed archive index
+
+
+SYNOPSIS
+--------
+'git-show-index' < idx-file
+
+
+DESCRIPTION
+-----------
+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
+'git-verify-pack -v'; this command only shows the packfile
+offset and SHA1 of each object.
+
+
+Author
+------
+Written by Linus Torvalds <torvalds@osdl.org>
+
+Documentation
+--------------
+Documentation by Junio C Hamano
+
+GIT
+---
+Part of the link:git.html[git] suite
+
diff --git a/Documentation/git-ssh-pull.txt b/Documentation/git-ssh-pull.txt
new file mode 100644 (file)
index 0000000..ac3fb34
--- /dev/null
@@ -0,0 +1,52 @@
+git-ssh-pull(1)
+===============
+v0.1, May 2005
+
+NAME
+----
+git-ssh-pull - Pulls from a remote repository over ssh connection
+
+
+
+SYNOPSIS
+--------
+'git-ssh-pull' [-c] [-t] [-a] [-d] [-v] [-w filename] [--recover] commit-id url
+
+DESCRIPTION
+-----------
+Pulls from a remote repository over ssh connection, invoking
+git-ssh-push on the other end. It functions identically to
+git-ssh-push, aside from which end you run it on.
+
+
+OPTIONS
+-------
+commit-id::
+        Either the hash or the filename under [URL]/refs/ to
+        pull.
+
+-c::
+       Get the commit objects.
+-t::
+       Get trees associated with the commit objects.
+-a::
+       Get all the objects.
+-v::
+       Report what is downloaded.
+-w::
+        Writes the commit-id into the filename under $GIT_DIR/refs/ on
+        the local end after the transfer is complete.
+
+
+Author
+------
+Written by Daniel Barkalow <barkalow@iabervon.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
+
diff --git a/Documentation/git-ssh-push.txt b/Documentation/git-ssh-push.txt
new file mode 100644 (file)
index 0000000..e38679d
--- /dev/null
@@ -0,0 +1,48 @@
+git-ssh-push(1)
+===============
+v0.1, Jun 2005
+
+NAME
+----
+git-ssh-push - Pushes to a remote repository over ssh connection
+
+
+SYNOPSIS
+--------
+'git-ssh-push' [-c] [-t] [-a] [-d] [-v] [-w filename] [--recover] commit-id url
+
+DESCRIPTION
+-----------
+Pushes from a remote repository over ssh connection, invoking
+git-ssh-pull on the other end. It functions identically to
+git-ssh-pull, aside from which end you run it on.
+
+OPTIONS
+-------
+commit-id::
+        Id of commit to push.
+
+-c::
+        Get the commit objects.
+-t::
+        Get tree associated with the requested commit object.
+-a::
+        Get all the objects.
+-v::
+        Report what is uploaded.
+-w::
+        Writes the commit-id into the filename under [URL]/refs/ on
+        the remote end after the transfer is complete.
+
+Author
+------
+Written by Daniel Barkalow <barkalow@iabervon.org>
+
+Documentation
+--------------
+Documentation by Daniel Barkalow
+
+GIT
+---
+Part of the link:git.html[git] suite
+
diff --git a/Documentation/git-tag-script.txt b/Documentation/git-tag-script.txt
new file mode 100644 (file)
index 0000000..2df396e
--- /dev/null
@@ -0,0 +1,38 @@
+git-tag-script(1)
+=================
+v0.1, May 2005
+
+NAME
+----
+git-tag-script -  Create a tag object signed with GPG
+
+
+
+SYNOPSIS
+--------
+'git-tag-script' [-s | -a] [-f] <name>
+
+DESCRIPTION
+-----------
+Adds a "tag" reference in .git/refs/tags/
+
+Unless "-f" is given, the tag must not yet exist in ".git/refs/tags"
+
+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.
+
+A GnuPG signed tag object will be created when "-s" is used.
+
+
+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
diff --git a/Documentation/git-tar-tree.txt b/Documentation/git-tar-tree.txt
new file mode 100644 (file)
index 0000000..9323ddd
--- /dev/null
@@ -0,0 +1,39 @@
+git-tar-tree(1)
+===============
+v0.1, May 2005
+
+NAME
+----
+git-tar-tree - Creates a tar archive of the files in the named tree
+
+
+SYNOPSIS
+--------
+'git-tar-tree' <tree-ish> [ <base> ]
+
+DESCRIPTION
+-----------
+Creates a tar archive containing the tree structure for the named tree.
+When <base> is specified it is added as a leading path to the files in the
+generated tar archive.
+
+git-tar-tree behaves differently when given a tree ID versus when given
+a commit ID or tag ID.  In the first case the current time is used as
+modification time of each file in the archive.  In the latter case the
+commit time as recorded in the referenced commit object is used instead.
+Additionally the commit ID is stored in a global extended pax header.
+It can be extracted using git-get-tar-commit-id.
+
+
+Author
+------
+Written by Rene Scharfe.
+
+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
+
diff --git a/Documentation/git-unpack-file.txt b/Documentation/git-unpack-file.txt
new file mode 100644 (file)
index 0000000..2f2130d
--- /dev/null
@@ -0,0 +1,37 @@
+git-unpack-file(1)
+==================
+v0.1, May 2005
+
+NAME
+----
+git-unpack-file - Creates a temporary file with a blob's contents
+
+
+
+SYNOPSIS
+--------
+'git-unpack-file' <blob>
+
+DESCRIPTION
+-----------
+Creates a file holding the contents of the blob specified by sha1. It
+returns the name of the temporary file in the following format:
+       .merge_file_XXXXX
+
+OPTIONS
+-------
+<blob>::
+       Must be a blob id
+
+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
+
diff --git a/Documentation/git-unpack-objects.txt b/Documentation/git-unpack-objects.txt
new file mode 100644 (file)
index 0000000..a12b843
--- /dev/null
@@ -0,0 +1,33 @@
+git-unpack-objects(1)
+=====================
+v0.1, July 2005
+
+NAME
+----
+git-unpack-objects - Unpack objects from a packed archive.
+
+
+SYNOPSIS
+--------
+'git-unpack-objects' < pack-file
+
+
+DESCRIPTION
+-----------
+Reads a packed archive (.pack) from the standard input, and
+expands the objects contained in the pack into "one-file
+one-object" format in $GIT_OBJECT_DIRECTORY.
+
+
+Author
+------
+Written by Linus Torvalds <torvalds@osdl.org>
+
+Documentation
+-------------
+Documentation by Junio C Hamano
+
+GIT
+---
+Part of the link:git.html[git] suite
+
diff --git a/Documentation/git-update-cache.txt b/Documentation/git-update-cache.txt
new file mode 100644 (file)
index 0000000..7340e89
--- /dev/null
@@ -0,0 +1,126 @@
+git-update-cache(1)
+===================
+v0.1, May 2005
+
+NAME
+----
+git-update-cache - Modifies the index or directory cache
+
+
+SYNOPSIS
+--------
+'git-update-cache'
+            [--add] [--remove] [--refresh] [--replace]
+            [--ignore-missing]
+            [--force-remove]
+            [--cacheinfo <mode> <object> <file>]\*
+            [--info-only]
+            [--] [<file>]\*
+
+DESCRIPTION
+-----------
+Modifies the index or directory cache. Each file mentioned is updated
+into the cache and any 'unmerged' or 'needs updating' state is
+cleared.
+
+The way "git-update-cache" handles files it is told about can be modified
+using the various options:
+
+OPTIONS
+-------
+--add::
+       If a specified file isn't in the cache already then it's
+       added.
+       Default behaviour is to ignore new files.
+
+--remove::
+       If a specified file is in the cache but is missing then it's
+       removed.
+       Default behaviour is to ignore removed file.
+
+--refresh::
+       Looks at the current cache and checks to see if merges or
+       updates are needed by checking stat() information.
+
+--ignore-missing::
+       Ignores missing files during a --refresh
+
+--cacheinfo <mode> <object> <path>::
+       Directly insert the specified info into the cache.
+       
+--info-only::
+       Do not create objects in the object database for all
+       <file> arguments that follow this flag; just insert
+       their object IDs into the cache.
+
+--force-remove::
+       Remove the file from the index even when the working directory
+       still has such a file. (Implies --remove.)
+
+--replace::
+       By default, when a file `path` exists in the index,
+       git-update-cache refuses an attempt to add `path/file`.
+       Similarly if a file `path/file` exists, a file `path`
+       cannot be added.  With --replace flag, existing entries
+       that conflicts with the entry being added are
+       automatically removed with warning messages.
+
+--::
+       Do not interpret any more arguments as options.
+
+<file>::
+       Files to act on.
+       Note that files begining 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 '//'
+
+Using --refresh
+---------------
+'--refresh' does not calculate a new sha1 file or bring the cache
+up-to-date for mode/content changes. But what it *does* do is to
+"re-match" the stat information of a file with the cache, so that you
+can refresh the cache for a file that hasn't been changed but where
+the stat entry is out of date.
+
+For example, you'd want to do this after doing a "git-read-tree", to link
+up the stat cache details with the proper files.
+
+Using --cacheinfo or --info-only
+--------------------------------
+'--cacheinfo' is used to register a file that is not in the
+current working directory.  This is useful for minimum-checkout
+merging.
+
+  To pretend you have a file with mode and sha1 at path, say:
+
+   $ git-update-cache --cacheinfo mode sha1 path
+
+'--info-only' is used to register files without placing them in the object
+database.  This is useful for status-only repositories.
+
+Both '--cacheinfo' and '--info-only' behave similarly: the index is updated
+but the object database isn't.  '--cacheinfo' is useful when the object is
+in the database but the file isn't available locally.  '--info-only' is
+useful when the file is available, but you do not wish to update the
+object database.
+
+Examples
+--------
+To update and refresh only the files already checked out:
+
+   git-checkout-cache -n -f -a && git-update-cache --ignore-missing --refresh
+
+
+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
+
diff --git a/Documentation/git-update-server-info.txt b/Documentation/git-update-server-info.txt
new file mode 100644 (file)
index 0000000..830f398
--- /dev/null
@@ -0,0 +1,42 @@
+git-update-server-info(1)
+=========================
+v0.1, July 2005
+
+NAME
+----
+git-update-server-info - Update auxiliary info file to help dumb servers
+
+
+SYNOPSIS
+--------
+'git-update-server-info' [--force]
+
+DESCRIPTION
+-----------
+A dumb server that does not do on-the-fly pack generations can
+have some auxiliary information files in $GIT_DIR/info and
+$GIT_OBJECT_DIRECTORY/info directories to help clients discover
+what references and packs the server has and make optimized
+pull decisions.  This command generates such auxiliary files.
+
+
+OPTIONS
+-------
+
+--force::
+       Update the info files even when they do not appear
+       stale.
+
+
+Author
+------
+Written by Junio C Hamano <junkio@cox.net>
+
+Documentation
+--------------
+Documentation by Junio C Hamano.
+
+GIT
+---
+Part of the link:git.html[git] suite
+
diff --git a/Documentation/git-upload-pack.txt b/Documentation/git-upload-pack.txt
new file mode 100644 (file)
index 0000000..be597a1
--- /dev/null
@@ -0,0 +1,40 @@
+git-upload-pack(1)
+==================
+v0.1, July 2005
+
+NAME
+----
+git-upload-pack - Send missing objects packed.
+
+
+SYNOPSIS
+--------
+'git-upload-pack' <directory>
+
+DESCRIPTION
+-----------
+Invoked by 'git-clone-pack' and/or 'git-fetch-pack', learns what
+objects the other side is missing, and sends them after packing.
+
+This command is usually not invoked directly by the end user.
+The UI for the protocol is on the 'git-fetch-pack' side, and the
+program pair is meant to be used to pull updates from a remote
+repository.  For push operations, see 'git-send-pack'.
+
+
+OPTIONS
+-------
+<directory>::
+       The repository to sync from.
+
+Author
+------
+Written by Linus Torvalds <torvalds@osdl.org>
+
+Documentation
+--------------
+Documentation by Junio C Hamano.
+
+GIT
+---
+Part of the link:git.html[git] suite
diff --git a/Documentation/git-var.txt b/Documentation/git-var.txt
new file mode 100644 (file)
index 0000000..45b1006
--- /dev/null
@@ -0,0 +1,60 @@
+git-var(1)
+==========
+v0.1, July 2005
+
+NAME
+----
+git-var - Print the git users identity
+
+
+SYNOPSIS
+--------
+git-var [ -l | <variable> ]
+
+DESCRIPTION
+-----------
+Prints a git logical variable.
+
+-l causes the logical variables to be listed.
+
+EXAMPLE
+--------
+$git-var GIT_AUTHOR_IDENT
+
+Eric W. Biederman <ebiederm@lnxi.com> 1121223278 -0600
+
+
+VARIABLES
+----------
+GIT_AUTHOR_IDENT
+    The author of a piece of code.
+
+GIT_COMMITTER_IDENT
+    The person who put a piece of code into git.
+
+Diagnostics
+-----------
+You don't exist. Go away!::
+    The passwd(5) gecos field couldn't be read
+Your parents must have hated you!::
+    The password(5) gecos field is longer than a giant static buffer.
+Your sysadmin must hate you!::
+    The password(5) name field is longer than a giant static buffer.
+
+See Also
+--------
+link:git-commit-tree.html[git-commit-tree]
+link:git-tag-script.html[git-tag-script]
+
+Author
+------
+Written by Eric Biederman <ebiederm@xmission.com>
+
+Documentation
+--------------
+Documentation by Eric Biederman and the git-list <git@vger.kernel.org>.
+
+GIT
+---
+Part of the link:git.html[git] suite
+
diff --git a/Documentation/git-verify-pack.txt b/Documentation/git-verify-pack.txt
new file mode 100644 (file)
index 0000000..d1043eb
--- /dev/null
@@ -0,0 +1,49 @@
+git-verify-pack(1)
+==================
+v0.1, June 2005
+
+NAME
+----
+git-verify-pack - Validate packed GIT archive files.
+
+
+SYNOPSIS
+--------
+'git-verify-pack' [-v] <pack>.idx ...
+
+
+DESCRIPTION
+-----------
+Reads given idx file for packed GIT archive created with
+git-pack-objects command and verifies idx file and the
+corresponding pack file.
+
+OPTIONS
+-------
+<pack>.idx ...::
+       The idx files to verify.
+
+-v::
+       After verifying the pack, show list of objects contained
+       in the pack.  The format used is:
+
+               SHA1 type size offset-in-packfile
+
+       for objects that are not deltified in the pack, and
+
+               SHA1 type size offset-in-packfile depth base-SHA1
+
+       for objects that are deltified.
+
+Author
+------
+Written by Junio C Hamano <junkio@cox.net>
+
+Documentation
+--------------
+Documentation by Junio C Hamano
+
+GIT
+---
+Part of the link:git.html[git] suite
+
diff --git a/Documentation/git-write-tree.txt b/Documentation/git-write-tree.txt
new file mode 100644 (file)
index 0000000..3cf8c93
--- /dev/null
@@ -0,0 +1,43 @@
+git-write-tree(1)
+=================
+v0.1, May 2005
+
+NAME
+----
+git-write-tree - Creates a tree object from the current cache
+
+
+SYNOPSIS
+--------
+'git-write-tree' [--missing-ok]
+
+DESCRIPTION
+-----------
+Creates a tree object using the current cache.
+
+The cache must be merged.
+
+Conceptually, "git-write-tree" sync()s the current directory cache contents
+into a set of tree files.
+In order to have that match what is actually in your directory right
+now, you need to have done a "git-update-cache" phase before you did the
+"git-write-tree".
+
+OPTIONS
+-------
+--missing-ok::
+       Normally "git-write-tree" ensures that the objects referenced by the
+       directory exist in the object database.  This option disables this check.
+
+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
+
diff --git a/Documentation/git.txt b/Documentation/git.txt
new file mode 100644 (file)
index 0000000..1308eb6
--- /dev/null
@@ -0,0 +1,356 @@
+git(7)
+======
+May 2005
+
+NAME
+----
+git - the stupid content tracker
+
+
+SYNOPSIS
+--------
+'git-<command>' <args>
+
+DESCRIPTION
+-----------
+
+This is reference information for the core git commands.
+
+The Discussion section below contains much useful definition and
+clarification info - read that first.  And of the commands, I suggest
+reading link:git-update-cache.html[git-update-cache] and
+link:git-read-tree.html[git-read-tree] first - I wish I had!
+
+David Greaves <david@dgreaves.com>
+08/05/05
+
+Updated by Junio C Hamano <junkio@cox.net> on 2005-05-05 to
+reflect recent changes.
+
+Commands Overview
+-----------------
+The git commands can helpfully be split into those that manipulate
+the repository, the cache and the working fileset, those that
+interrogate and compare them, and those that moves objects and
+references between repositories.
+
+In addition, git itself comes with a spartan set of porcelain
+commands.  They are usable but are not meant to compete with real
+Porcelains.
+
+There are also some ancilliary programs that can be viewed as useful
+aids for using the core commands but which are unlikely to be used by
+SCMs layered over git.
+
+Manipulation commands
+~~~~~~~~~~~~~~~~~~~~~
+link:git-checkout-cache.html[git-checkout-cache]::
+       Copy files from the cache to the working directory
+
+link:git-commit-tree.html[git-commit-tree]::
+       Creates a new commit object
+
+link:git-init-db.html[git-init-db]::
+       Creates an empty git object database
+
+link:git-merge-base.html[git-merge-base]::
+       Finds as good a common ancestor as possible for a merge
+
+link:git-mktag.html[git-mktag]::
+       Creates a tag object
+
+link:git-read-tree.html[git-read-tree]::
+       Reads tree information into the directory cache
+
+link:git-update-cache.html[git-update-cache]::
+       Modifies the index or directory cache
+
+link:git-hash-object.html[git-hash-object]::
+       Computes the object ID from a file.
+
+link:git-write-tree.html[git-write-tree]::
+       Creates a tree from the current cache
+
+Interrogation commands
+~~~~~~~~~~~~~~~~~~~~~~
+link:git-cat-file.html[git-cat-file]::
+       Provide content or type information for repository objects
+
+link:git-diff-cache.html[git-diff-cache]::
+       Compares content and mode of blobs between the cache and repository
+
+link:git-diff-files.html[git-diff-files]::
+       Compares files in the working tree and the cache
+
+link:git-diff-tree.html[git-diff-tree]::
+       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-cache.html[git-fsck-cache]::
+       Verifies the connectivity and validity of the objects in the database
+
+link:git-ls-files.html[git-ls-files]::
+       Information about files in the cache/working directory
+
+link:git-ls-tree.html[git-ls-tree]::
+       Displays a tree object in human readable form
+
+link:git-merge-cache.html[git-merge-cache]::
+       Runs a merge for files needing merging
+
+link:git-rev-list.html[git-rev-list]::
+       Lists commit objects in reverse chronological order
+
+link:git-rev-tree.html[git-rev-tree]::
+       Provides the revision tree for one or more commits
+
+link:git-tar-tree.html[git-tar-tree]::
+       Creates a tar archive of the files in the named tree
+
+link:git-unpack-file.html[git-unpack-file]::
+       Creates a temporary file with a blob's contents
+
+link:git-var.html[git-var]::
+       Displays a git logical variable
+
+link:git-verify-pack.html[git-verify-pack]::
+       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
+
+
+Synching repositories
+~~~~~~~~~~~~~~~~~~~~~
+
+link:git-clone-script.html[git-clone-script]::
+       Clones a repository into the current repository (user interface)
+
+link:git-clone-pack.html[git-clone-pack]::
+       Clones a repository into the current repository (engine
+       for ssh and local transport)
+
+link:git-http-pull.html[git-http-pull]::
+       Downloads a remote GIT repository via HTTP
+
+link:git-local-pull.html[git-local-pull]::
+       Duplicates another GIT repository on a local system
+
+link:git-ssh-pull.html[git-ssh-pull]::
+       Pulls from a remote repository over ssh connection
+
+link:git-send-pack.html[git-send-pack]::
+       Pushes to a remote repository, intelligently.
+
+link:git-receive-pack.html[git-receive-pack]::
+       Invoked by 'git-send-pack' to receive what is pushed to it.
+
+link:git-clone-pack.html[git-clone-pack]::
+       Clones from a remote repository.
+
+link:git-fetch-pack.html[git-fetch-pack]::
+       Updates from a remote repository.
+
+link:git-peek-remote.html[git-peek-remote]::
+       Lists references on a remote repository using upload-pack protocol.
+
+link:git-upload-pack.html[git-upload-pack]::
+       Invoked by 'git-clone-pack' and 'git-fetch-pack' to push
+       what are asked for.
+
+link:git-update-server-info.html[git-update-server-info]::
+       Updates auxiliary information on a dumb server to help
+       clients discover references and packs on it.
+
+
+Porcelainish Commands
+---------------------
+link:git-fetch-script.html[git-fetch-script]::
+       Download from a remote repository via various protocols.
+
+link:git-pull-script.html[git-pull-script]::
+       Fetch from and merge with a remote repository.
+
+link:git-commit-script.html[git-commit-script]::
+       Record changes to the repository.
+
+
+Ancilliary Commands
+-------------------
+Manipulators:
+
+link:git-apply-patch-script.html[git-apply-patch-script]::
+       Sample script to apply the diffs from git-diff-*
+
+link:git-convert-cache.html[git-convert-cache]::
+       Converts old-style GIT repository
+
+link:git-merge-one-file-script.html[git-merge-one-file-script]::
+       The standard helper program to use with "git-merge-cache"
+
+link:git-prune-script.html[git-prune-script]::
+       Prunes all unreachable objects from the object database
+
+link:git-resolve-script.html[git-resolve-script]::
+       Script used to merge two trees
+
+link:git-tag-script.html[git-tag-script]::
+       An example script to create a tag object signed with GPG
+
+
+Interogators:
+
+link:git-diff-helper.html[git-diff-helper]::
+       Generates patch format output for git-diff-*
+
+link:git-ssh-push.html[git-ssh-push]::
+       Helper "server-side" program used by git-ssh-pull
+
+
+
+Identifier Terminology
+----------------------
+<object>::
+       Indicates the sha1 identifier for any type of object
+
+<blob>::
+       Indicates a blob object sha1 identifier
+
+<tree>::
+       Indicates a tree object sha1 identifier
+
+<commit>::
+       Indicates a commit object sha1 identifier
+
+<tree-ish>::
+       Indicates a tree, commit or tag object sha1 identifier.  A
+       command that takes a <tree-ish> argument ultimately wants to
+       operate on a <tree> object but automatically dereferences
+       <commit> and <tag> objects that point at a <tree>.
+
+<type>::
+       Indicates that an object type is required.
+       Currently one of: blob/tree/commit/tag
+
+<file>::
+       Indicates a filename - always relative to the root of
+       the tree structure GIT_INDEX_FILE describes.
+
+Symbolic Identifiers
+--------------------
+Any git comand accepting any <object> can also use the following
+symbolic notation:
+
+HEAD::
+       indicates the head of the repository (ie the contents of
+       `$GIT_DIR/HEAD`)
+<tag>::
+       a valid tag 'name'+
+       (ie the contents of `$GIT_DIR/refs/tags/<tag>`)
+<head>::
+       a valid head 'name'+
+       (ie the contents of `$GIT_DIR/refs/heads/<head>`)
+<snap>::
+       a valid snapshot 'name'+
+       (ie the contents of `$GIT_DIR/refs/snap/<snap>`)
+
+
+File/Directory Structure
+------------------------
+The git-core manipulates the following areas in the directory:
+
+ .git/        The base (overridden with $GIT_DIR)
+   objects/    The object base (overridden with $GIT_OBJECT_DIRECTORY)
+     ??/       'First 2 chars of object' directories.
+     pack/     Packed archives.
+
+   refs/       Directories containing symbolic names for objects
+              (each file contains the hex SHA1 + newline)
+     heads/    Commits which are heads of various sorts
+     tags/     Tags, by the tag name (or some local renaming of it)
+     */        Any other subdirectory of refs/ can be used to store
+              files similar to what are under refs/heads/.
+   HEAD        Symlink to refs/heads/<current-branch-name>
+
+Higher level SCMs may provide and manage additional information in the
+GIT_DIR.
+
+Terminology
+-----------
+Each line contains terms which you may see used interchangeably
+
+ object database, .git directory
+ directory cache, index
+ id, sha1, sha1-id, sha1 hash
+ type, tag
+
+
+Environment Variables
+---------------------
+Various git commands use the following environment variables:
+
+The git Repository
+~~~~~~~~~~~~~~~~~~
+These environment variables apply to 'all' core git commands. Nb: it
+is worth noting that they may be used/overridden by SCMS sitting above
+git so take care if using Cogito etc
+
+'GIT_INDEX_FILE'::
+       This environment allows the specification of an alternate
+       cache/index file. If not specified, the default of
+       `$GIT_DIR/index` is used.
+
+'GIT_OBJECT_DIRECTORY'::
+       If the object storage directory is specified via this
+       environment variable then the sha1 directories are created
+       underneath - otherwise the default `$GIT_DIR/objects`
+       directory is used.
+
+'GIT_ALTERNATE_OBJECT_DIRECTORIES'::
+       Due to the immutable nature of git objects, old objects can be
+       archived into shared, read-only directories. This variable
+       specifies a ":" seperated list of git object directories which
+       can be used to search for git objects. New objects will not be
+       written to these directories.
+
+'GIT_DIR'::
+       If the 'GIT_DIR' environment variable is set then it specifies
+       a path to use instead of `./.git` for the base of the
+       repository.
+
+git Commits
+~~~~~~~~~~~
+'GIT_AUTHOR_NAME'::
+'GIT_AUTHOR_EMAIL'::
+'GIT_AUTHOR_DATE'::
+'GIT_COMMITTER_NAME'::
+'GIT_COMMITTER_EMAIL'::
+       see link:git-commit-tree.html[git-commit-tree]
+
+git Diffs
+~~~~~~~~~
+'GIT_DIFF_OPTS'::
+'GIT_EXTERNAL_DIFF'::
+       see the "generating patches" section in :
+       link:git-diff-cache.html[git-diff-cache];
+       link:git-diff-files.html[git-diff-files];
+       link:git-diff-tree.html[git-diff-tree]
+
+Discussion
+----------
+include::../README[]
+
+Author
+------
+Written by Linus Torvalds <torvalds@osdl.org> and the git-list <git@vger.kernel.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
+
diff --git a/Documentation/pack-protocol.txt b/Documentation/pack-protocol.txt
new file mode 100644 (file)
index 0000000..7d6aec4
--- /dev/null
@@ -0,0 +1,38 @@
+There are two Pack push-pull protocols.
+
+upload-pack (S) | fetch/clone-pack (C) protocol:
+
+       # Tell the puller what commits we have and what their names are
+       S: SHA1 name
+       S: ...
+       S: SHA1 name
+       S: # flush -- it's your turn
+       # Tell the pusher what commits we want, and what we have
+       C: want name
+       C: ..
+       C: want name
+       C: have SHA1
+       C: have SHA1
+       C: ...
+       C: # flush -- occasionally ask "had enough?"
+       S: NAK
+       C: have SHA1
+       C: ...
+       C: have SHA1
+       S: ACK
+       C: done
+       S: XXXXXXX -- packfile contents.
+
+send-pack | receive-pack protocol.
+
+       # Tell the pusher what commits we have and what their names are
+       C: SHA1 name
+       C: ...
+       C: SHA1 name
+       C: # flush -- it's your turn
+       # Tell the puller what the pusher has
+       S: old-SHA1 new-SHA1 name
+       S: old-SHA1 new-SHA1 name
+       S: ...
+       S: # flush -- done with the list
+       S: XXXXXXX --- packfile contents.
diff --git a/Documentation/pull-fetch-param.txt b/Documentation/pull-fetch-param.txt
new file mode 100644 (file)
index 0000000..7ae4ba0
--- /dev/null
@@ -0,0 +1,36 @@
+<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 that, as a short-hand, the name of a file
+       in $GIT_DIR/branches directory can be specified; 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.
+
+<head>::
+       The remote head name to fetch from.  That is, make the
+       objects reachable from the commit recorded in
+       $GIT_DIR/refs/heads/<head> in the remote repository
+       available locally.
+
+tag <tag>::
+       The remote head tag to fetch from.  That is, make the
+       objects reachable from the commit recorded in
+       $GIT_DIR/refs/tags/<tag> in the remote repository
+       available locally.
+
diff --git a/Documentation/tutorial.txt b/Documentation/tutorial.txt
new file mode 100644 (file)
index 0000000..66b6e30
--- /dev/null
@@ -0,0 +1,1109 @@
+A short git tutorial
+====================
+May 2005
+
+
+Introduction
+------------
+
+This is trying to be a short tutorial on setting up and using a git
+archive, mainly because being hands-on and using explicit examples is
+often the best way of explaining what is going on.
+
+In normal life, most people wouldn't use the "core" git programs
+directly, but rather script around them to make them more palatable. 
+Understanding the core git stuff may help some people get those scripts
+done, though, and it may also be instructive in helping people
+understand what it is that the higher-level helper scripts are actually
+doing. 
+
+The core git is often called "plumbing", with the prettier user
+interfaces on top of it called "porcelain".  You may not want to use the
+plumbing directly very often, but it can be good to know what the
+plumbing does for when the porcelain isn't flushing... 
+
+
+Creating a git archive
+----------------------
+
+Creating a new git archive couldn't be easier: all git archives start
+out empty, and the only thing you need to do is find yourself a
+subdirectory that you want to use as a working tree - either an empty
+one for a totally new project, or an existing working tree that you want
+to import into git. 
+
+For our first example, we're going to start a totally new archive from
+scratch, with no pre-existing files, and we'll call it "git-tutorial".
+To start up, create a subdirectory for it, change into that
+subdirectory, and initialize the git infrastructure with "git-init-db":
+
+       mkdir git-tutorial
+       cd git-tutorial
+       git-init-db 
+
+to which git will reply
+
+       defaulting to local storage area
+
+which is just git's way of saying that you haven't been doing anything
+strange, and that it will have created a local .git directory setup for
+your new project. You will now have a ".git" directory, and you can
+inspect that with "ls". For your new empty project, ls should show you
+three entries:
+
+ - a symlink called HEAD, pointing to "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
+   start your HEAD development branch yet.
+
+ - a subdirectory called "objects", which will contain all the git SHA1
+   objects of your project. You should never have any real reason to
+   look at the objects directly, but you might want to know that these
+   objects are what contains all the real _data_ in your repository.
+
+ - a subdirectory called "refs", which contains references to objects.
+
+   In particular, the "refs" subdirectory will contain two other
+   subdirectories, named "heads" and "tags" respectively.  They do
+   exactly what their names imply: they contain references to any number
+   of different "heads" of development (aka "branches"), and to any
+   "tags" that you have created to name specific versions of your
+   repository. 
+
+   One note: the special "master" head is the default branch, which is
+   why the .git/HEAD file was created as a symlink to it even if it
+   doesn't yet exist. Basically, the HEAD link is supposed to always
+   point to the branch you are working on right now, and you always
+   start out expecting to work on the "master" branch.
+
+   However, this is only a convention, and you can name your branches
+   anything you want, and don't have to ever even _have_ a "master"
+   branch.  A number of the git tools will assume that .git/HEAD is
+   valid, though.
+
+   [ Implementation note: an "object" is identified by its 160-bit SHA1
+   hash, aka "name", and a reference to an object is always the 40-byte
+   hex representation of that SHA1 name. The files in the "refs"
+   subdirectory are expected to contain these hex references (usually
+   with a final '\n' at the end), and you should thus expect to see a
+   number of 41-byte files containing these references in this refs
+   subdirectories when you actually start populating your tree ]
+
+You have now created your first git archive. Of course, since it's
+empty, that's not very useful, so let's start populating it with data.
+
+
+       Populating a git archive
+       ------------------------
+
+We'll keep this simple and stupid, so we'll start off with populating a
+few trivial files just to get a feel for it.
+
+Start off with just creating any random files that you want to maintain
+in your git archive. We'll start off with a few bad examples, just to
+get a feel for how this works:
+
+       echo "Hello World" >hello
+       echo "Silly example" >example
+
+you have now created two files in your working directory, but to
+actually check in your hard work, you will have to go through two steps:
+
+ - fill in the "cache" aka "index" file with the information about your
+   working directory state
+
+ - commit that index file as an object.
+
+The first step is trivial: when you want to tell git about any changes
+to your working directory, you use the "git-update-cache" program.  That
+program normally just takes a list of filenames you want to update, but
+to avoid trivial mistakes, it refuses to add new entries to the cache
+(or remove existing ones) unless you explicitly tell it that you're
+adding a new entry with the "--add" flag (or removing an entry with the
+"--remove") flag. 
+
+So to populate the index with the two files you just created, you can do
+
+       git-update-cache --add hello example
+
+and you have now told git to track those two files.
+
+In fact, as you did that, if you now look into your object directory,
+you'll notice that git will have added two new objects to the object
+store.  If you did exactly the steps above, you should now be able to do
+
+       ls .git/objects/??/*
+
+and see two files:
+
+       .git/objects/55/7db03de997c86a4a028e1ebd3a1ceb225be238 
+       .git/objects/f2/4c74a2e500f5ee1332c86b94199f52b1d1d962
+
+which correspond with the object with SHA1 names of 557db... and f24c7..
+respectively.
+
+If you want to, you can use "git-cat-file" to look at those objects, but
+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
+regular file), and you can see the contents with
+
+       git-cat-file "blob" 557db03de997c86a4a028e1ebd3a1ceb225be238
+
+which will print out "Hello World".  The object 557db...  is nothing
+more than the contents of your file "hello". 
+
+[ Digression: don't confuse that object with the file "hello" itself.  The
+  object is literally just those specific _contents_ of the file, and
+  however much you later change the contents in file "hello", the object we
+  just looked at will never change.  Objects are immutable.  ]
+
+Anyway, as we mentioned previously, you normally never actually take a
+look at the objects themselves, and typing long 40-character hex SHA1
+names is not something you'd normally want to do.  The above digression
+was just to show that "git-update-cache" did something magical, and
+actually saved away the contents of your files into the git content
+store. 
+
+Updating the cache did something else too: it created a ".git/index"
+file.  This is the index that describes your current working tree, and
+something you should be very aware of.  Again, you normally never worry
+about the index file itself, but you should be aware of the fact that
+you have not actually really "checked in" your files into git so far,
+you've only _told_ git about them.
+
+However, since git knows about them, you can now start using some of the
+most basic git commands to manipulate the files or look at their status. 
+
+In particular, let's not even check in the two files into git yet, we'll
+start off by adding another line to "hello" first:
+
+       echo "It's a new day for git" >>hello
+
+and you can now, since you told git about the previous state of "hello", ask
+git what has changed in the tree compared to your old index, using the
+"git-diff-files" command:
+
+       git-diff-files 
+
+oops.  That wasn't very readable.  It just spit out its own internal
+version of a "diff", but that internal version really just tells you
+that it has noticed that "hello" has been modified, and that the old object
+contents it had have been replaced with something else.
+
+To make it readable, we can tell git-diff-files to output the
+differences as a patch, using the "-p" flag:
+
+       git-diff-files -p
+
+which will spit out
+
+       diff --git a/hello b/hello
+       --- a/hello
+       +++ b/hello
+       @@ -1 +1,2 @@
+        Hello World
+       +It's a new day for git
+
+ie the diff of the change we caused by adding another line to "hello".
+
+In other words, git-diff-files always shows us the difference between
+what is recorded in the index, and what is currently in the working
+tree. That's very useful.
+
+A common shorthand for "git-diff-files -p" is to just write
+
+       git diff
+
+which will do the same thing. 
+
+
+       Committing git state
+       --------------------
+
+Now, we want to go to the next stage in git, which is to take the files
+that git knows about in the index, and commit them as a real tree. We do
+that in two phases: creating a "tree" object, and committing that "tree"
+object as a "commit" object together with an explanation of what the
+tree was all about, along with information of how we came to that state.
+
+Creating a tree object is trivial, and is done with "git-write-tree". 
+There are no options or other input: git-write-tree will take the
+current index state, and write an object that describes that whole
+index.  In other words, we're now tying together all the different
+filenames with their contents (and their permissions), and we're
+creating the equivalent of a git "directory" object:
+
+       git-write-tree
+
+and this will just output the name of the resulting tree, in this case
+(if you have done exactly as I've described) it should be
+
+       8988da15d077d4829fc51d8544c097def6644dbb
+
+which is another incomprehensible object name. Again, if you want to,
+you can use "git-cat-file -t 8988d.." to see that this time the object
+is not a "blob" object, but a "tree" object (you can also use
+git-cat-file to actually output the raw object contents, but you'll see
+mainly a binary mess, so that's less interesting).
+
+However - normally you'd never use "git-write-tree" on its own, because
+normally you always commit a tree into a commit object using the
+"git-commit-tree" command. In fact, it's easier to not actually use
+git-write-tree on its own at all, but to just pass its result in as an
+argument to "git-commit-tree".
+
+"git-commit-tree" normally takes several arguments - it wants to know
+what the _parent_ of a commit was, but since this is the first commit
+ever in this new archive, and it has no parents, we only need to pass in
+the tree ID. However, git-commit-tree also wants to get a commit message
+on its standard input, and it will write out the resulting ID 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:
+
+       echo "Initial commit" | git-commit-tree $(git-write-tree) > .git/HEAD
+
+which will say:
+
+       Committing initial tree 8988da15d077d4829fc51d8544c097def6644dbb
+
+just to warn you about the fact that it created a totally new commit
+that is not related to anything else. Normally you do this only _once_
+for a project ever, and all later commits will be parented on top of an
+earlier commit, and you'll never see this "Committing initial tree"
+message ever again.
+
+Again, normally you'd never actually do this by hand.  There is a
+helpful script called "git commit" that will do all of this for you. So
+you could have just written
+
+       git commit
+
+instead, and it would have done the above magic scripting for you.
+
+
+       Making a change
+       ---------------
+
+Remember how we did the "git-update-cache" on file "hello" and then we
+changed "hello" afterward, and could compare the new state of "hello" with the
+state we saved in the index file? 
+
+Further, remember how I said that "git-write-tree" writes the contents
+of the _index_ file to the tree, and thus what we just committed was in
+fact the _original_ contents of the file "hello", not the new ones. We did
+that on purpose, to show the difference between the index state, and the
+state in the working directory, and how they don't have to match, even
+when we commit things.
+
+As before, if we do "git-diff-files -p" in our git-tutorial project,
+we'll still see the same difference we saw last time: the index file
+hasn't changed by the act of committing anything.  However, now that we
+have committed something, we can also learn to use a new command:
+"git-diff-cache".
+
+Unlike "git-diff-files", which showed the difference between the index
+file and the working directory, "git-diff-cache" shows the differences
+between a committed _tree_ and either the index file or the working
+directory.  In other words, git-diff-cache wants a tree to be diffed
+against, and before we did the commit, we couldn't do that, because we
+didn't have anything to diff against. 
+
+But now we can do 
+
+       git-diff-cache -p HEAD
+
+(where "-p" has the same meaning as it did in git-diff-files), and it
+will show us the same difference, but for a totally different reason. 
+Now we're comparing the working directory not against the index file,
+but against the tree we just wrote.  It just so happens that those two
+are obviously the same, so we get the same result.
+
+Again, because this is a common operation, you can also just shorthand
+it with
+
+       git diff HEAD
+
+which ends up doing the above for you.
+
+In other words, "git-diff-cache" normally compares a tree against the
+working directory, but when given the "--cached" flag, it is told to
+instead compare against just the index cache contents, and ignore the
+current working directory state entirely.  Since we just wrote the index
+file to HEAD, doing "git-diff-cache --cached -p HEAD" should thus return
+an empty set of differences, and that's exactly what it does. 
+
+[ Digression: "git-diff-cache" really always uses the index for its
+  comparisons, and saying that it compares a tree against the working
+  directory is thus not strictly accurate. In particular, the list of
+  files to compare (the "meta-data") _always_ comes from the index file,
+  regardless of whether the --cached flag is used or not. The --cached
+  flag really only determines whether the file _contents_ to be compared
+  come from the working directory 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
+  expects you to tell it what the files are, and that's what the index
+  is there for.  ]
+
+However, our next step is to commit the _change_ we did, and again, to
+understand what's going on, keep in mind the difference between "working
+directory contents", "index file" and "committed tree".  We have changes
+in the working directory that we want to commit, and we always have to
+work through the index file, so the first thing we need to do is to
+update the index cache:
+
+       git-update-cache hello
+
+(note how we didn't need the "--add" flag this time, since git knew
+about the file already).
+
+Note what happens to the different git-diff-xxx versions here.  After
+we've updated "hello" in the index, "git-diff-files -p" now shows no
+differences, but "git-diff-cache -p HEAD" still _does_ show that the
+current state is different from the state we committed.  In fact, now
+"git-diff-cache" shows the same difference whether we use the "--cached"
+flag or not, since now the index is coherent with the working directory. 
+
+Now, since we've updated "hello" in the index, we can commit the new
+version.  We could do it by writing the tree by hand again, and
+committing the tree (this time we'd have to use the "-p HEAD" flag to
+tell commit that the HEAD was the _parent_ of the new commit, and that
+this wasn't an initial commit any more), but you've done that once
+already, so let's just use the helpful script this time:
+
+       git commit
+
+which starts an editor for you to write the commit message and tells you
+a bit about what you're doing. 
+
+Write whatever message you want, and all the lines that start with '#'
+will be pruned out, and the rest will be used as the commit message for
+the change. If you decide you don't want to commit anything after all at
+this point (you can continue to edit things and update the cache), you
+can just leave an empty message. Otherwise git-commit-script will commit
+the change for you.
+
+You've now made your first real git commit. And if you're interested in
+looking at what git-commit-script really does, feel free to investigate:
+it's a few very simple shell scripts to generate the helpful (?) commit
+message headers, and a few one-liners that actually do the commit itself.
+
+
+       Checking it out
+       ---------------
+
+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
+"diff" family, namely "git-diff-tree". 
+
+git-diff-tree can be given two arbitrary trees, and it will tell you the
+differences between them. Perhaps even more commonly, though, you can
+give it just a single commit object, and it will figure out the parent
+of that commit itself, and show the difference directly. Thus, to get
+the same diff that we've already seen several times, we can now do
+
+       git-diff-tree -p HEAD
+
+(again, "-p" means to show the difference as a human-readable patch),
+and it will show what the last commit (in HEAD) actually changed.
+
+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.
+Alternatively, you can tell it to be "silent", and not show the diffs at
+all, but just show the actual commit message.
+
+In fact, together with the "git-rev-list" program (which generates a
+list of revisions), git-diff-tree ends up being a veritable fount of
+changes. A trivial (but very useful) script called "git-whatchanged" is
+included with git which does exactly this, and shows a log of recent
+activity.
+
+To see the whole history of our pitiful little git-tutorial project, you
+can do
+
+       git log
+
+which shows just the log messages, or if we want to see the log together
+with the associated patches use the more complex (and much more
+powerful)
+
+       git-whatchanged -p --root
+
+and you will see exactly what has changed in the repository over its
+short history. 
+
+[ Side note: the "--root" flag is a flag to git-diff-tree to tell it to
+  show the initial aka "root" commit too.  Normally you'd probably not
+  want to see the initial import diff, but since the tutorial project
+  was started from scratch and is so small, we use it to make the result
+  a bit more interesting ]
+
+With that, you should now be having some inkling of what git does, and
+can explore on your own.
+
+
+[ Side note: most likely, you are not directly using the core
+  git Plumbing commands, but using Porcelain like Cogito on top
+  of it.  Cogito works a bit differently and you usually do not
+  have to run "git-update-cache" yourself for changed files (you
+  do tell underlying git about additions and removals via
+  "cg-add" and "cg-rm" commands).  Just before you make a commit
+  with "cg-commit", Cogito figures out which files you modified,
+  and runs "git-update-cache" on them for you.  ]
+
+
+       Tagging a version
+       -----------------
+
+In git, there's two kinds of tags, a "light" one, and a "signed tag".
+
+A "light" tag is technically nothing more than a branch, except we put
+it in the ".git/refs/tags/" subdirectory instead of calling it a "head".
+So the simplest form of tag involves nothing more than
+
+       git tag my-first-tag
+
+which just writes the current HEAD into the .git/refs/tags/my-first-tag
+file, after which point you can then use this symbolic name for that
+particular state.  You can, for example, do
+
+       git diff my-first-tag
+
+to diff your current state against that tag (which at this point will
+obviously be an empty diff, but if you continue to develop and commit
+stuff, you can use your tag as an "anchor-point" to see what has changed
+since you tagged it.
+
+A "signed tag" is actually a real git object, and contains not only a
+pointer to the state you want to tag, but also a small tag name and
+message, along with a PGP signature that says that yes, you really did
+that tag. You create these signed tags with the "-s" flag to "git tag":
+
+       git tag -s <tagname>
+
+which will sign the current HEAD (but you can also give it another
+argument that specifies the thing to tag, ie you could have tagged the
+current "mybranch" point by using "git tag <tagname> mybranch").
+
+You normally only do signed tags for major releases or things
+like that, while the light-weight tags are useful for any marking you
+want to do - any time you decide that you want to remember a certain
+point, just create a private tag for it, and you have a nice symbolic
+name for the state at that point.
+
+
+       Copying archives
+       -----------------
+
+Git archives 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"
+subdirectory.  There is nothing else.  What you see is what you got. 
+
+[ Side note: you can tell git to split the git internal information from
+  the directory that it tracks, but we'll ignore that for now: it's not
+  how normal projects work, and it's really only meant for special uses.
+  So the mental model of "the git information is always tied directly to
+  the working directory that it describes" may not be technically 100%
+  accurate, but it's a good model for all normal use ]
+
+This has two implications: 
+
+ - if you grow bored with the tutorial archive you created (or you've
+   made a mistake and want to start all over), you can just do simple
+
+       rm -rf git-tutorial
+
+   and it will be gone. There's no external repository, and there's no
+   history outside of the project you created.
+
+ - if you want to move or duplicate a git archive, you can do so. There
+   is "git clone" command, but if all you want to do is just to
+   create a copy of your archive (with all the full history that
+   went along with it), you can do so with a regular
+   "cp -a git-tutorial new-git-tutorial".
+
+   Note that when you've moved or copied a git archive, your git index
+   file (which caches various information, notably some of the "stat"
+   information for the files involved) will likely need to be refreshed.
+   So after you do a "cp -a" to create a new copy, you'll want to do
+
+       git-update-cache --refresh
+
+   to make sure that the index file is up-to-date in the new one. 
+
+Note that the second point is true even across machines.  You can
+duplicate a remote git archive with _any_ regular copy mechanism, be it
+"scp", "rsync" or "wget". 
+
+When copying a remote repository, you'll want to at a minimum update the
+index cache when you do this, and especially with other peoples
+repositories you often want to make sure that the index cache is in some
+known state (you don't know _what_ they've done and not yet checked in),
+so usually you'll precede the "git-update-cache" with a
+
+       git-read-tree --reset HEAD
+       git-update-cache --refresh
+
+which will force a total index re-build from the tree pointed to by HEAD
+(it resets the index contents to HEAD, and then the git-update-cache
+makes sure to match up all index entries with the checked-out files). 
+
+The above can also be written as simply
+
+       git reset
+
+and in fact a lot of the common git command combinations can be scripted
+with the "git xyz" interfaces, and you can learn things by just looking
+at what the git-*-script scripts do ("git reset" is the above two lines
+implemented in "git-reset-script", but some things like "git status" and
+"git commit" are slightly more complex scripts around the basic git
+commands). 
+
+NOTE! Many (most?) public remote repositories will not contain any of
+the checked out files or even an index file, and will _only_ contain the
+actual core git files.  Such a repository usually doesn't even have the
+".git" subdirectory, but has all the git files directly in the
+repository. 
+
+To create your own local live copy of such a "raw" git repository, you'd
+first create your own subdirectory for the project, and then copy the
+raw repository contents into the ".git" directory. For example, to
+create your own copy of the git repository, you'd do the following
+
+       mkdir my-git
+       cd my-git
+       rsync -rL rsync://rsync.kernel.org/pub/scm/git/git.git/ .git
+
+followed by 
+
+       git-read-tree HEAD
+
+to populate the index. However, now you have populated the index, and
+you have all the git internal files, but you will notice that you don't
+actually have any of the _working_directory_ files to work on. To get
+those, you'd check them out with
+
+       git-checkout-cache -u -a
+
+where the "-u" flag means that you want the checkout to keep the index
+up-to-date (so that you don't have to refresh it afterward), and the
+"-a" flag means "check out all files" (if you have a stale copy or an
+older version of a checked out tree you may also need to add the "-f"
+flag first, to tell git-checkout-cache to _force_ overwriting of any old
+files). 
+
+Again, this can all be simplified with
+
+       git clone rsync://rsync.kernel.org/pub/scm/git/git.git/ my-git
+       cd my-git
+       git checkout
+
+which will end up doing all of the above for you.
+
+You have now successfully copied somebody else's (mine) remote
+repository, and checked it out. 
+
+
+       Creating a new branch
+       ---------------------
+
+Branches in git are really nothing more than pointers into the git
+object space from within the ".git/refs/" subdirectory, and as we
+already discussed, the HEAD branch is nothing but a symlink to one of
+these object pointers. 
+
+You can at any time create a new branch by just picking an arbitrary
+point in the project history, and just writing the SHA1 name of that
+object into a file under .git/refs/heads/.  You can use any filename you
+want (and indeed, subdirectories), but the convention is that the
+"normal" branch is called "master".  That's just a convention, though,
+and nothing enforces it. 
+
+To show that as an example, let's go back to the git-tutorial archive we
+used earlier, and create a branch in it. You do that by simply just
+saying that you want to check out a new branch:
+
+       git checkout -b mybranch
+
+will create a new branch based at the current HEAD position, and switch
+to it. 
+
+[ Side note: if you make the decision to start your new branch at some
+  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-branch
+
+  and it would create the new branch "mybranch" at the earlier point,
+  and check out the state at that time. ]
+
+You can always just jump back to your original "master" branch by doing
+
+       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
+
+will tell you where it's pointing.
+
+NOTE! 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]
+
+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
+on that branch - switch to that branch with a regular "git checkout"
+with the branchname as the argument.
+
+
+       Merging two branches
+       --------------------
+
+One of the ideas of having a branch is that you do some (possibly
+experimental) work in it, and eventually merge it back to the main
+branch.  So assuming you created the above "mybranch" that started out
+being the same as the original "master" branch, let's make sure we're in
+that branch, and do some work there.
+
+       git checkout mybranch
+       echo "Work, work, work" >>hello
+       git commit hello
+
+Here, we just added another line to "hello", and we used a shorthand for
+both going a "git-update-cache hello" and "git commit" by just giving the
+filename directly to "git commit".
+
+Now, to make it a bit more interesting, let's assume that somebody else
+does some work in the original branch, and simulate that by going back
+to the master branch, and editing the same file differently there:
+
+       git checkout master
+
+Here, take a moment to look at the contents of "hello", and notice how they
+don't contain the work we just did in "mybranch" - because that work
+hasn't happened in the "master" branch at all. Then do
+
+       echo "Play, play, play" >>hello
+       echo "Lots of fun" >>example
+       git commit hello example
+
+since the master branch is obviously in a much better mood.
+
+Now, you've got two branches, and you decide that you want to merge the
+work done. Before we do that, let's introduce a cool graphical tool that
+helps you view what's going on:
+
+       gitk --all
+
+will show you graphically both of your branches (that's what the "--all"
+means: normally it will just show you your current HEAD) and their
+histories.  You can also see exactly how they came to be from a common
+source. 
+
+Anyway, let's exit gitk (^Q or the File menu), and decide that we want
+to merge the work we did on the "mybranch" branch into the "master"
+branch (which is currently our HEAD too).  To do that, there's a nice
+script called "git resolve", which wants to know which branches you want
+to resolve and what the merge is all about:
+
+       git resolve HEAD mybranch "Merge work in mybranch"
+
+where the third argument is going to be used as the commit message if
+the merge can be resolved automatically.
+
+Now, in this case we've intentionally created a situation where the
+merge will need to be fixed up by hand, though, so git will do as much
+of it as it can automatically (which in this case is just merge the "b"
+file, which had no differences in the "mybranch" branch), and say:
+
+       Simple merge failed, trying Automatic merge
+       Auto-merging hello.
+       merge: warning: conflicts during merge
+       ERROR: Merge conflict in hello.
+       fatal: merge program failed
+       Automatic merge failed, fix up by hand
+
+which is way too verbose, but it basically tells you that it failed the
+really trivial merge ("Simple merge") and did an "Automatic merge"
+instead, but that too failed due to conflicts in "hello".
+
+Not to worry. It left the (trivial) conflict in "hello" in the same form you
+should already be well used to if you've ever used CVS, so let's just
+open "hello" in our editor (whatever that may be), and fix it up somehow.
+I'd suggest just making it so that "hello" contains all four lines:
+
+       Hello World
+       It's a new day for git
+       Play, play, play
+       Work, work, work
+
+and once you're happy with your manual merge, just do a
+
+       git commit hello
+
+which will very loudly warn you that you're now committing a merge
+(which is correct, so never mind), and you can write a small merge
+message about your adventures in git-merge-land. 
+
+After you're done, start up "gitk --all" to see graphically what the
+history looks like.  Notice that "mybranch" still exists, and you can
+switch to it, and continue to work with it if you want to.  The
+"mybranch" branch will not contain the merge, but next time you merge it
+from the "master" branch, git will know how you merged it, so you'll not
+have to do _that_ merge again.
+
+
+       Merging external work
+       ---------------------
+
+It's usually much more common that you merge with somebody else than
+merging with your own branches, so it's worth pointing out that git
+makes that very easy too, and in fact, it's not that different from
+doing a "git resolve".  In fact, a remote merge ends up being nothing
+more than "fetch the work from a remote repository into a temporary tag"
+followed by a "git resolve". 
+
+It's such a common thing to do that it's called "git pull", and you can
+simply do
+
+       git pull <remote-repository>
+
+and optionally give a branch-name for the remote end as a second
+argument.
+
+The "remote" repository can even be on the same machine.  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/
+
+       SSH URL
+               remote.machine:/path/to/repo.git/
+
+       Local directory
+               /path/to/repo.git/
+
+[ Digression: you could do without using any branches at all, by
+  keeping as many local repositories as you would like to have
+  branches, and merging between them with "git pull", just like
+  you merge between branches.  The advantage of this approach is
+  that it lets you keep set of files for each "branch" checked
+  out and you may find it easier to switch back and forth if you
+  juggle multiple lines of development simultaneously.  Of
+  course, you will pay the price of more disk usage to hold
+  multiple working trees, but disk space is cheap these days.  ]
+
+It is likely that you will be pulling from the same remote
+repository from time to time.  As a short hand, you can store
+the remote repository URL in a file under .git/branches/
+directory, like this:
+
+       mkdir -p .git/branches
+       echo rsync://kernel.org/pub/scm/git/git.git/ \
+           >.git/branches/linus
+
+and use the filename to "git pull" instead of the full URL.
+The contents of a file under .git/branches can even be a prefix
+of a full URL, like this:
+
+       echo rsync://kernel.org/pub/.../jgarzik/
+               >.git/branches/jgarzik
+
+Examples.
+
+       (1) git pull linus
+       (2) git pull linus tag v0.99.1
+       (3) git pull jgarzik/netdev-2.6.git/ e100
+
+the above are equivalent to:
+
+       (1) git pull rsync://kernel.org/pub/scm/git/git.git/ HEAD
+       (2) git pull rsync://kernel.org/pub/scm/git/git.git/ tag v0.99.1
+       (3) git pull rsync://kernel.org/pub/.../jgarzik/netdev-2.6.git e100
+
+
+       Publishing your work
+       --------------------
+
+So we can use somebody else's work from a remote repository; but
+how can _you_ prepare a repository to let other people pull from
+it?
+
+Your do your real work in your working directory that has your
+primary repository hanging under it as its ".git" subdirectory.
+You _could_ make that repository accessible remotely and ask
+people to pull from it, but in practice that is not the way
+things are usually done.  A recommended way is to have a public
+repository, make it reachable by other people, and when the
+changes you made in your primary working directory are in good
+shape, update the public repository from it.  This is often
+called "pushing".
+
+[ Side note: this public repository could further be mirrored,
+  and that is how kernel.org git repositories are done.  ]
+
+Publishing the changes from your local (private) repository to
+your remote (public) repository requires a write privilege on
+the remote machine.  You need to have an SSH account there to
+run a single command, "git-receive-pack".
+
+First, you need to create an empty repository on the remote
+machine that will house your public repository.  This empty
+repository will be populated and be kept up-to-date by pushing
+into it later.  Obviously, this repository creation needs to be
+done only once.
+
+[ Digression: "git push" uses a pair of programs,
+  "git-send-pack" on your local machine, and "git-receive-pack"
+  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 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
+
+Then, make that directory into a GIT repository by running
+git-init-db, but this time, since it's name is not the usual
+".git", we do things slightly differently:
+
+       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
+you need to make sure that you have the "git-receive-pack"
+program on the $PATH.
+
+[ Side note: many installations of sshd do not invoke your shell
+  as the login shell when you directly run programs; what this
+  means is that if your login shell is bash, only .bashrc is
+  read and not .bash_profile.  As a workaround, make sure
+  .bashrc sets up $PATH so that you can run 'git-receive-pack'
+  program.  ]
+
+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
+
+This synchronizes your public repository to match the named
+branch head (i.e. "master" in this case) and objects reachable
+from them in your current repository.
+
+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/ 
+
+
+[ Digression: your GIT "public" repository people can pull from
+  is different from a public CVS repository that lets read-write
+  access to multiple developers.  It is a copy of _your_ primary
+  repository published for others to use, and you should not
+  push into it from more than one repository (this means, not
+  just disallowing other developers to push into it, but also
+  you should push into it from a single repository of yours).
+  Sharing the result of work done by multiple people are always
+  done by pulling (i.e. fetching and merging) from public
+  repositories of those people.  Typically this is done by the
+  "project lead" person, and the resulting repository is
+  published as the public repository of the "project lead" for
+  everybody to base further changes on.  ]
+
+
+       Packing your repository
+       -----------------------
+
+Earlier, we saw that one file under .git/objects/??/ directory
+is stored for each git object you create.  This representation
+is convenient and efficient to create atomically and safely, but
+not so 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
+
+will do it for you.  If you followed the tutorial examples, you
+would have accumulated about 17 objects in .git/objects/??/
+directories by now.  "git repack" tells you how many objects it
+packed, and stores the packed file in .git/objects/pack
+directory.
+
+[ Side Note: you will see two files, pack-*.pack and pack-*.idx,
+  in .git/objects/pack directory.  They are closely related to
+  each other, and if you ever copy them by hand to a different
+  repository for whatever reason, you should make sure you copy
+  them together.  The former holds all the data from the objects
+  in the pack, and the latter holds the index for random
+  access.  ]
+
+If you are paranoid, running "git-verify-pack" command would
+detect if you have a corrupt pack, but do not worry too much.
+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
+
+would remove them for you.
+
+You can try running "find .git/objects -type f" before and after
+you run "git prune-packed" if you are curious.
+
+[ Side Note: "git pull" is slightly cumbersome for HTTP transport,
+  as a packed repository may contain relatively few objects in a
+  relatively large pack. If you expect many HTTP pulls from your
+  public repository you might want to repack & prune often, or
+  never. ]
+
+If you run "git repack" again at this point, it will say
+"Nothing to pack".  Once you continue your development and
+accumulate the changes, running "git repack" again will create a
+new pack, that contains objects created since you packed your
+archive the last time.  We recommend that you pack your project
+soon after the initial import (unless you are starting your
+project from scratch), and then run "git repack" every once in a
+while, depending on how active your project is.
+
+When a repository is synchronized via "git push" and "git pull",
+objects packed in the source repository are usually stored
+unpacked in the destination, unless rsync transport is used.
+
+
+       Working with Others
+       -------------------
+
+Although git is a truly distributed system, it is often
+convenient to organize your project with an informal hierarchy
+of developers.  Linux kernel development is run this way.  There
+is a nice illustration (page 17, "Merges to Mainline") in Randy
+Dunlap's presentation (http://tinyurl.com/a2jdg).
+
+It should be stressed that this hierarchy is purely "informal".
+There is nothing fundamental in git that enforces the "chain of
+patch flow" this hierarchy implies.  You do not have to pull
+from only one remote repository.
+
+
+A recommended workflow for a "project lead" goes like this:
+
+ (1) Prepare your primary repository on your local machine. Your
+     work is done there.
+
+ (2) Prepare a public repository accessible to others.
+
+ (3) Push into the public repository from your primary
+     repository.
+
+ (4) "git repack" the public repository.  This establishes a big
+     pack that contains the initial set of objects as the
+     baseline, and possibly "git prune-packed" if the transport
+     used for pulling from your repository supports packed
+     repositories.
+
+ (5) Keep working in your primary repository.  Your changes
+     include modifications of your own, patches you receive via
+     e-mails, and merges resulting from pulling the "public"
+     repositories of your "subsystem maintainers".
+
+     You can repack this private repository whenever you feel
+     like.
+
+ (6) Push your changes to the public repository, and announce it
+     to the public.
+
+ (7) Every once in a while, "git repack" the public repository.
+     Go back to step (5) and continue working.
+
+
+A recommended work cycle for a "subsystem maintainer" who works
+on that project and has an own "public repository" goes like this:
+
+ (1) Prepare your work repository, by "git clone" the public
+     repository of the "project lead".  The URL used for the
+     initial cloning is stored in .git/branches/origin.
+
+ (2) Prepare a public repository accessible to others.
+
+ (3) Copy over the packed files from "project lead" public
+     repository to your public repository by hand; preferrably
+     use rsync for that task.
+
+ (4) Push into the public repository from your primary
+     repository.  Run "git repack", and possibly "git
+     prune-packed" if the transport used for pulling from your
+     repository supports packed repositories.
+
+ (5) Keep working in your primary repository.  Your changes
+     include modifications of your own, patches you receive via
+     e-mails, and merges resulting from pulling the "public"
+     repositories of your "project lead" and possibly your
+     "sub-subsystem maintainers".
+
+     You can repack this private repository whenever you feel
+     like.
+
+ (6) Push your changes to your public repository, and ask your
+     "project lead" and possibly your "sub-subsystem
+     maintainers" to pull from it.
+
+ (7) Every once in a while, "git repack" the public repository.
+     Go back to step (5) and continue working.
+
+
+A recommended work cycle for an "individual developer" who does
+not have a "public" repository is somewhat different.  It goes
+like this:
+
+ (1) Prepare your work repository, by "git clone" the public
+     repository of the "project lead" (or a "subsystem
+     maintainer", if you work on a subsystem).  The URL used for
+     the initial cloning is stored in .git/branches/origin.
+
+ (2) Do your work there.  Make commits.
+
+ (3) Run "git fetch origin" from the public repository of your
+     upstream every once in a while.  This does only the first
+     half of "git pull" but does not merge.  The head of the
+     public repository is stored in .git/refs/heads/origin.
+
+ (4) Use "git cherry origin" to see which ones of your patches
+     were accepted, and/or use "git rebase origin" to port your
+     unmerged changes forward to the updated upstream.
+
+ (5) Use "git format-patch origin" to prepare patches for e-mail
+     submission to your upstream and send it out.  Go back to
+     step (2) and continue.
+
+
+[ to be continued.. cvsimports ]
diff --git a/INSTALL b/INSTALL
new file mode 100644 (file)
index 0000000..43f2bb9
--- /dev/null
+++ b/INSTALL
@@ -0,0 +1,66 @@
+
+               Git installation
+
+Normally you can just do "make" followed by "make install", and that
+will install the git programs in your own ~/bin/ directory.  If you want
+to do a global install, you can do
+
+       make prefix=/usr install
+
+(or prefix=/usr/local, of course).  Some day somebody may send me a RPM
+spec file or something, and you can do "make rpm" or whatever.
+
+Issues of note:
+
+ - git normally installs a helper script wrapper called "git", which
+   conflicts with a similarly named "GNU interactive tools" program.
+
+   Tough.  Either don't use the wrapper script, or delete the old GNU
+   interactive tools.  None of the core git stuff needs the wrapper,
+   it's just a convenient shorthand and while it is documented in some
+   places, you can always replace "git commit" with "git-commit-script"
+   instead. 
+
+   But let's face it, most of us don't have GNU interactive tools, and
+   even if we had it, we wouldn't know what it does.  I don't think it
+   has been actively developed since 1997, and people have moved over to
+   graphical file managers.
+
+ - Git is reasonably self-sufficient, but does depend on a few external
+   programs and libraries:
+
+       - "zlib", the compression library. Git won't build without it.
+
+       - "openssl".  The git-rev-list program uses bignum support from
+         openssl, and unless you specify otherwise, you'll also get the
+         SHA1 library from here.
+
+         If you don't have openssl, you can use one of the SHA1 libraries
+         that come with git (git includes the one from Mozilla, and has
+         its own PowerPC-optimized one too - see the Makefile), and you
+         can avoid the bignum support by excising git-rev-list support
+         for "--merge-order" (by hand).
+
+       - "libcurl" and "curl" executable.  git-http-pull and
+         git-fetch-script use them.  If you do not use http
+         transfer, you are probabaly OK if you do not have
+         them.
+
+       - "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?
+
+         Non-GNU versions of the diff/patch programs don't generally support
+         the unified patch format (which is the one git uses), so you
+         really do want to get the GNU one.  Trust me, you will want to
+         do that even if it wasn't for git.  There's no point in living
+         in the dark ages any more. 
+
+       - "merge", the standard UNIX three-way merge program.  It usually
+         comes with the "rcs" package on most Linux distributions, so if
+         you have a developer install you probably have it already, but a
+         "graphical user desktop" install might have left it out.
+
+         You'll only need the merge program if you do development using
+         git, and if you only use git to track other peoples work you'll
+         never notice the lack of it. 
diff --git a/Makefile b/Makefile
new file mode 100644 (file)
index 0000000..e0ea88f
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,252 @@
+# Define MOZILLA_SHA1 environment variable when running make to make use of
+# a bundled SHA1 routine coming from Mozilla. It is GPL'd and should be fast
+# on non-x86 architectures (e.g. PowerPC), while the OpenSSL version (default
+# choice) has very fast version optimized for i586.
+#
+# 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 PPC_SHA1 environment variable when running make to make use of
+# a bundled SHA1 routine optimized for PowerPC.
+
+
+# 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.4
+
+COPTS?=-g -O2
+CFLAGS+=$(COPTS) -Wall $(DEFINES)
+
+prefix=$(HOME)
+bindir=$(prefix)/bin
+template_dir=$(prefix)/share/git-core/templates/
+# dest=
+
+CC?=gcc
+AR?=ar
+INSTALL?=install
+RPMBUILD?=rpmbuild
+
+# sparse is architecture-neutral, which means that we need to tell it
+# explicitly what architecture to check for. Fix this up for yours..
+SPARSE_FLAGS?=-D__BIG_ENDIAN__ -D__powerpc__
+
+
+
+### --- END CONFIGURATION SECTION ---
+
+
+
+SCRIPTS=git git-apply-patch-script git-merge-one-file-script git-prune-script \
+       git-pull-script git-tag-script git-resolve-script git-whatchanged \
+       git-fetch-script git-status-script git-commit-script \
+       git-log-script git-shortlog git-cvsimport-script git-diff-script \
+       git-reset-script git-add-script git-checkout-script git-clone-script \
+       gitk git-cherry git-rebase-script git-relink-script git-repack-script \
+       git-format-patch-script git-sh-setup-script git-push-script \
+       git-branch-script git-parse-remote git-verify-tag-script \
+       git-ls-remote-script git-clone-dumb-http git-rename-script \
+       git-request-pull-script git-bisect-script
+
+SCRIPTS += git-count-objects-script
+# SCRIPTS += git-send-email-script
+SCRIPTS += git-revert-script
+
+PROG=   git-update-cache git-diff-files git-init-db git-write-tree \
+       git-read-tree git-commit-tree git-cat-file git-fsck-cache \
+       git-checkout-cache git-diff-tree git-rev-tree git-ls-files \
+       git-ls-tree git-merge-base git-merge-cache \
+       git-unpack-file git-export git-diff-cache git-convert-cache \
+       git-ssh-push git-ssh-pull git-rev-list git-mktag \
+       git-diff-helper git-tar-tree git-local-pull git-hash-object \
+       git-get-tar-commit-id git-apply git-stripspace \
+       git-diff-stages git-rev-parse git-patch-id git-pack-objects \
+       git-unpack-objects git-verify-pack git-receive-pack git-send-pack \
+       git-prune-packed git-fetch-pack git-upload-pack git-clone-pack \
+       git-show-index git-daemon git-var git-peek-remote \
+       git-update-server-info git-show-rev-cache git-build-rev-cache
+
+ifndef NO_CURL
+PROG+= git-http-pull
+endif
+
+LIB_FILE=libgit.a
+LIB_H=cache.h object.h blob.h tree.h commit.h tag.h delta.h epoch.h csum-file.h \
+       pack.h pkt-line.h refs.h
+LIB_OBJS=read-cache.o sha1_file.o usage.o object.o commit.o tree.o blob.o \
+        tag.o date.o index.o diff-delta.o patch-delta.o entry.o path.o \
+        refs.o csum-file.o pack-check.o pkt-line.o connect.o ident.o \
+        sha1_name.o
+
+LIB_H += rev-cache.h
+LIB_OBJS += rev-cache.o
+
+LIB_H += run-command.h
+LIB_OBJS += run-command.o
+
+LIB_H += strbuf.h
+LIB_OBJS += strbuf.o
+
+LIB_H += quote.h
+LIB_OBJS += quote.o 
+
+LIB_H += diff.h count-delta.h
+DIFF_OBJS = diff.o diffcore-rename.o diffcore-pickaxe.o diffcore-pathspec.o \
+       diffcore-break.o diffcore-order.o
+LIB_OBJS += $(DIFF_OBJS) count-delta.o
+
+LIB_OBJS += gitenv.o
+LIB_OBJS += server-info.o
+
+LIBS = $(LIB_FILE)
+LIBS += -lz
+
+ifndef NO_OPENSSL
+       LIB_OBJS += epoch.o
+       OPENSSL_LIBSSL=-lssl
+else
+       CFLAGS += '-DNO_OPENSSL'
+       MOZILLA_SHA1=1
+       OPENSSL_LIBSSL=
+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
+else
+  SHA1_HEADER=<openssl/sha.h>
+ifeq ($(shell uname -s),Darwin)
+  LIBS += -lcrypto -lssl
+else
+  LIBS += -lcrypto
+endif
+endif
+endif
+
+CFLAGS += '-DSHA1_HEADER=$(SHA1_HEADER)'
+
+
+
+### Build rules
+
+all: $(PROG)
+
+all:
+       $(MAKE) -C templates
+
+.SECONDARY: %.o
+.c.o:
+       $(CC) $(CFLAGS) -o $*.o -c $*.c
+git-%: %.o $(LIB_FILE)
+       $(CC) $(CFLAGS) -o $@ $(filter %.o,$^) $(LIBS)
+
+git-http-pull: pull.o
+git-local-pull: pull.o
+git-ssh-pull: rsh.o pull.o
+git-ssh-push: rsh.o
+
+git-http-pull: LIBS += -lcurl
+git-rev-list: LIBS += $(OPENSSL_LIBSSL)
+
+init-db.o: init-db.c
+       $(CC) -c $(CFLAGS) -DDEFAULT_GIT_TEMPLATE_DIR='"$(template_dir)"' $*.c
+
+$(LIB_OBJS): $(LIB_H)
+$(patsubst git-%,%.o,$(PROG)): $(LIB_H)
+$(DIFF_OBJS): diffcore.h
+
+$(LIB_FILE): $(LIB_OBJS)
+       $(AR) rcs $@ $(LIB_OBJS)
+
+doc:
+       $(MAKE) -C Documentation all
+
+
+
+### Testing rules
+
+test: all
+       $(MAKE) -C t/ all
+
+test-date: test-date.c date.o
+       $(CC) $(CFLAGS) -o $@ test-date.c date.o
+
+test-delta: test-delta.c diff-delta.o patch-delta.o
+       $(CC) $(CFLAGS) -o $@ $^
+
+check:
+       for i in *.c; do sparse $(CFLAGS) $(SPARSE_FLAGS) $$i; done
+
+
+
+### Installation rules
+
+install: $(PROG) $(SCRIPTS)
+       $(INSTALL) -m755 -d $(dest)$(bindir)
+       $(INSTALL) $(PROG) $(SCRIPTS) $(dest)$(bindir)
+       $(MAKE) -C templates install
+
+install-tools:
+       $(MAKE) -C tools install
+
+install-doc:
+       $(MAKE) -C Documentation install
+
+
+
+
+### Maintainer's dist rules
+
+git-core.spec: git-core.spec.in Makefile
+       sed -e 's/@@VERSION@@/$(GIT_VERSION)/g' < $< > $@
+
+GIT_TARNAME=git-core-$(GIT_VERSION)
+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
+       @rm -rf $(GIT_TARNAME)
+       gzip -f -9 $(GIT_TARNAME).tar
+
+rpm: dist
+       $(RPMBUILD) -ta git-core-$(GIT_VERSION).tar.gz
+
+deb: dist
+       rm -rf $(GIT_TARNAME)
+       tar zxf $(GIT_TARNAME).tar.gz
+       cd $(GIT_TARNAME) && fakeroot debian/rules binary
+
+### Cleaning rules
+
+clean:
+       rm -f *.o mozilla-sha1/*.o ppc/*.o $(PROG) $(LIB_FILE)
+       rm -f $(GIT_TARNAME).tar.gz git-core.spec git-core_$(GIT_VERSION)-*.deb
+       rm -rf $(GIT_TARNAME)
+       $(MAKE) -C tools/ clean
+       $(MAKE) -C Documentation/ clean
+       $(MAKE) -C templates/ clean
+       $(MAKE) -C t/ clean
diff --git a/README b/README
new file mode 100644 (file)
index 0000000..80cc279
--- /dev/null
+++ b/README
@@ -0,0 +1,482 @@
+////////////////////////////////////////////////////////////////
+
+       GIT - the stupid content tracker
+
+////////////////////////////////////////////////////////////////
+"git" can mean anything, depending on your mood.
+
+ - random three-letter combination that is pronounceable, and not
+   actually used by any common UNIX command.  The fact that it is a
+   mispronunciation of "get" may or may not be relevant.
+ - stupid. contemptible and despicable. simple. Take your pick from the
+   dictionary of slang.
+ - "global information tracker": you're in a good mood, and it actually
+   works for you. Angels sing, and a light suddenly fills the room. 
+ - "goddamn idiotic truckload of sh*t": when it breaks
+
+This is a stupid (but extremely fast) directory content manager.  It
+doesn't do a whole lot, but what it _does_ do is track directory
+contents efficiently. 
+
+There are two object abstractions: the "object database", and the
+"current directory cache" aka "index".
+
+The Object Database
+~~~~~~~~~~~~~~~~~~~
+The object database is literally just a content-addressable collection
+of objects.  All objects are named by their content, which is
+approximated by the SHA1 hash of the object itself.  Objects may refer
+to other objects (by referencing their SHA1 hash), and so you can
+build up a hierarchy of objects.
+
+All objects have a statically determined "type" aka "tag", which is
+determined at object creation time, and which identifies the format of
+the object (i.e. how it is used, and how it can refer to other
+objects).  There are currently four different object types: "blob",
+"tree", "commit" and "tag".
+
+A "blob" object cannot refer to any other object, and is, like the tag
+implies, a pure storage object containing some user data.  It is used to
+actually store the file data, i.e. a blob object is associated with some
+particular version of some file. 
+
+A "tree" object is an object that ties one or more "blob" objects into a
+directory structure. In addition, a tree object can refer to other tree
+objects, thus creating a directory hierarchy. 
+
+A "commit" object ties such directory hierarchies together into
+a DAG of revisions - each "commit" is associated with exactly one tree
+(the directory hierarchy at the time of the commit). In addition, a
+"commit" refers to one or more "parent" commit objects that describe the
+history of how we arrived at that directory hierarchy.
+
+As a special case, a commit object with no parents is called the "root"
+object, and is the point of an initial project commit.  Each project
+must have at least one root, and while you can tie several different
+root objects together into one project by creating a commit object which
+has two or more separate roots as its ultimate parents, that's probably
+just going to confuse people.  So aim for the notion of "one root object
+per project", even if git itself does not enforce that. 
+
+A "tag" object symbolically identifies and can be used to sign other
+objects. It contains the identifier and type of another object, a
+symbolic name (of course!) and, optionally, a signature.
+
+Regardless of object type, all objects share the following
+characteristics: they are all deflated with zlib, and have a header
+that not only specifies their tag, but also provides size information
+about the data in the object.  It's worth noting that the SHA1 hash
+that is used to name the object is the hash of the original data.
+(Historical note: in the dawn of the age of git the hash
+was the sha1 of the _compressed_ object)
+
+As a result, the general consistency of an object can always be tested
+independently of the contents or the type of the object: all objects can
+be validated by verifying that (a) their hashes match the content of the
+file and (b) the object successfully inflates to a stream of bytes that
+forms a sequence of <ascii tag without space> + <space> + <ascii decimal
+size> + <byte\0> + <binary object data>. 
+
+The structured objects can further have their structure and
+connectivity to other objects verified. This is generally done with
+the "git-fsck-cache" program, which generates a full dependency graph
+of all objects, and verifies their internal consistency (in addition
+to just verifying their superficial consistency through the hash).
+
+The object types in some more detail:
+
+Blob Object
+~~~~~~~~~~~
+A "blob" object is nothing but a binary blob of data, and doesn't
+refer to anything else.  There is no signature or any other
+verification of the data, so while the object is consistent (it _is_
+indexed by its sha1 hash, so the data itself is certainly correct), it
+has absolutely no other attributes.  No name associations, no
+permissions.  It is purely a blob of data (i.e. normally "file
+contents").
+
+In particular, since the blob is entirely defined by its data, if two
+files in a directory tree (or in multiple different versions of the
+repository) have the same contents, they will share the same blob
+object. The object is totally independent of it's 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-cache.html[git-update-cache]
+is run, and it's data can be accessed by link:git-cat-file.html[git-cat-file].
+
+Tree Object
+~~~~~~~~~~~
+The next hierarchical object type is the "tree" object.  A tree object
+is a list of mode/name/blob data, sorted by name.  Alternatively, the
+mode data may specify a directory mode, in which case instead of
+naming a blob, that name is associated with another TREE object.
+
+Like the "blob" object, a tree object is uniquely determined by the
+set contents, and so two separate but identical trees will always
+share the exact same object. This is true at all levels, i.e. it's
+true for a "leaf" tree (which does not refer to any other trees, only
+blobs) as well as for a whole subdirectory.
+
+For that reason a "tree" object is just a pure data abstraction: it
+has no history, no signatures, no verification of validity, except
+that since the contents are again protected by the hash itself, we can
+trust that the tree is immutable and its contents never change.
+
+So you can trust the contents of a tree to be valid, the same way you
+can trust the contents of a blob, but you don't know where those
+contents _came_ from.
+
+Side note on trees: since a "tree" object is a sorted list of
+"filename+content", you can create a diff between two trees without
+actually having to unpack two trees.  Just ignore all common parts,
+and your diff will look right.  In other words, you can effectively
+(and efficiently) tell the difference between any two random trees by
+O(n) where "n" is the size of the difference, rather than the size of
+the tree.
+
+Side note 2 on trees: since the name of a "blob" depends entirely and
+exclusively on its contents (i.e. there are no names or permissions
+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
+it's data can be accessed by link:git-ls-tree.html[git-ls-tree]
+
+Commit Object
+~~~~~~~~~~~~~
+The "commit" object is an object that introduces the notion of
+history into the picture.  In contrast to the other objects, it
+doesn't just describe the physical state of a tree, it describes how
+we got there, and why.
+
+A "commit" is defined by the tree-object that it results in, the
+parent commits (zero, one or more) that led up to that point, and a
+comment on what happened.  Again, a commit is not trusted per se:
+the contents are well-defined and "safe" due to the cryptographically
+strong signatures at all levels, but there is no reason to believe
+that the tree is "good" or that the merge information makes sense.
+The parents do not have to actually have any relationship with the
+result, for example.
+
+Note on commits: unlike real SCM's, commits do not contain
+rename information or file mode chane information.  All of that is
+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
+it's data can be accessed by link:git-cat-file.html[git-cat-file]
+
+Trust
+~~~~~
+An aside on the notion of "trust". Trust is really outside the scope
+of "git", but it's worth noting a few things.  First off, since
+everything is hashed with SHA1, you _can_ trust that an object is
+intact and has not been messed with by external sources.  So the name
+of an object uniquely identifies a known state - just not a state that
+you may want to trust.
+
+Furthermore, since the SHA1 signature of a commit refers to the
+SHA1 signatures of the tree it is associated with and the signatures
+of the parent, a single named commit specifies uniquely a whole set
+of history, with full contents.  You can't later fake any step of the
+way once you have the name of a commit.
+
+So to introduce some real trust in the system, the only thing you need
+to do is to digitally sign just _one_ special note, which includes the
+name of a top-level commit.  Your digital signature shows others
+that you trust that commit, and the immutability of the history of
+commits tells others that they can trust the whole history.
+
+In other words, you can easily validate a whole archive by just
+sending out a single email that tells the people the name (SHA1 hash)
+of the top commit, and digitally sign that email using something
+like GPG/PGP.
+
+To assist in this, git also provides the tag object...
+
+Tag Object
+~~~~~~~~~~
+Git provides the "tag" object to simplify creating, managing and
+exchanging symbolic and signed tokens.  The "tag" object at its
+simplest simply symbolically identifies another object by containing
+the sha1, type and symbolic name.
+
+However it can optionally contain additional signature information
+(which git doesn't care about as long as there's less than 8k of
+it). This can then be verified externally to git.
+
+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] and
+it's data can be accessed by link:git-cat-file.html[git-cat-file]
+
+
+The "index" aka "Current Directory Cache"
+-----------------------------------------
+The index is a simple binary file, which contains an efficient
+representation of a virtual directory content at some random time.  It
+does so by a simple array that associates a set of names, dates,
+permissions and content (aka "blob") objects together.  The cache is
+always kept ordered by name, and names are unique (with a few very
+specific rules) at any point in time, but the cache has no long-term
+meaning, and can be partially updated at any time.
+
+In particular, the index certainly does not need to be consistent with
+the current directory contents (in fact, most operations will depend on
+different ways to make the index _not_ be consistent with the directory
+hierarchy), but it has three very important attributes:
+
+'(a) it can re-generate the full state it caches (not just the
+directory structure: it contains pointers to the "blob" objects so
+that it can regenerate the data too)'
+
+As a special case, there is a clear and unambiguous one-way mapping
+from a current directory cache to a "tree object", which can be
+efficiently created from just the current directory cache without
+actually looking at any other data.  So a directory cache at any one
+time uniquely specifies one and only one "tree" object (but has
+additional data to make it easy to match up that tree object with what
+has happened in the directory)
+
+'(b) it has efficient methods for finding inconsistencies between that
+cached state ("tree object waiting to be instantiated") and the
+current state.'
+
+'(c) it can additionally efficiently represent information about merge
+conflicts between different tree objects, allowing each pathname to be
+associated with sufficient information about the trees involved that
+you can create a three-way merge between them.'
+
+Those are the three ONLY things that the directory cache does.  It's a
+cache, and the normal operation is to re-generate it completely from a
+known tree object, or update/compare it with a live tree that is being
+developed.  If you blow the directory cache away entirely, you generally
+haven't lost any information as long as you have the name of the tree
+that it described. 
+
+At the same time, the directory index is at the same time also the
+staging area for creating new trees, and creating a new tree always
+involves a controlled modification of the index file.  In particular,
+the index file can have the representation of an intermediate tree that
+has not yet been instantiated.  So the index can be thought of as a
+write-back cache, which can contain dirty information that has not yet
+been written back to the backing store.
+
+
+
+The Workflow
+------------
+Generally, all "git" operations work on the index file. Some operations
+work *purely* on the index file (showing the current state of the
+index), but most operations move data to and from the index file. Either
+from the database or from the working directory. Thus there are four
+main combinations: 
+
+1) working directory -> index
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+You update the index with information from the working directory with
+the link:git-update-cache.html[git-update-cache] command.  You
+generally update the index information by just specifying the filename
+you want to update, like so:
+
+       git-update-cache filename
+
+but to avoid common mistakes with filename globbing etc, the command
+will not normally add totally new entries or remove old entries,
+i.e. it will normally just update existing cache entries.
+
+To tell git that yes, you really do realize that certain files no
+longer exist in the archive, or that new files should be added, you
+should use the "--remove" and "--add" flags respectively.
+
+NOTE! A "--remove" flag does _not_ mean that subsequent filenames will
+necessarily be removed: if the files still exist in your directory
+structure, the index will be updated with their new status, not
+removed. The only thing "--remove" means is that update-cache will be
+considering a removed file to be a valid thing, and if the file really
+does not exist any more, it will update the index accordingly.
+
+As a special case, you can also do "git-update-cache --refresh", which
+will refresh the "stat" information of each index to match the current
+stat information. It will _not_ update the object status itself, and
+it will only update the fields that are used to quickly test whether
+an object still matches its old backing store object.
+
+2) index -> object database
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+You write your current index file to a "tree" object with the program
+
+       git-write-tree
+
+that doesn't come with any options - it will just write out the
+current index into the set of tree objects that describe that state,
+and it will return the name of the resulting top-level tree. You can
+use that tree to re-generate the index at any time by going in the
+other direction:
+
+3) object database -> index
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+You read a "tree" file from the object database, and use that to
+populate (and overwrite - don't do this if your index contains any
+unsaved state that you might want to restore later!) your current
+index.  Normal operation is just
+
+               git-read-tree <sha1 of tree>
+
+and your index file will now be equivalent to the tree that you saved
+earlier. However, that is only your _index_ file: your working
+directory contents have not been modified.
+
+4) index -> working directory
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+You update your working directory from the index by "checking out"
+files. This is not a very common operation, since normally you'd just
+keep your files updated, and rather than write to your working
+directory, you'd tell the index files about the changes in your
+working directory (i.e. "git-update-cache").
+
+However, if you decide to jump to a new version, or check out somebody
+else's version, or just restore a previous tree, you'd populate your
+index file with read-tree, and then you need to check out the result
+with
+               git-checkout-cache filename
+
+or, if you want to check out all of the index, use "-a".
+
+NOTE! git-checkout-cache normally refuses to overwrite old files, so
+if you have an old version of the tree already checked out, you will
+need to use the "-f" flag (_before_ the "-a" flag or the filename) to
+_force_ the checkout.
+
+
+Finally, there are a few odds and ends which are not purely moving
+from one representation to the other:
+
+5) Tying it all together
+~~~~~~~~~~~~~~~~~~~~~~~~
+To commit a tree you have instantiated with "git-write-tree", you'd
+create a "commit" object that refers to that tree and the history
+behind it - most notably the "parent" commits that preceded it in
+history.
+
+Normally a "commit" has one parent: the previous state of the tree
+before a certain change was made. However, sometimes it can have two
+or more parent commits, in which case we call it a "merge", due to the
+fact that such a commit brings together ("merges") two or more
+previous states represented by other commits.
+
+In other words, while a "tree" represents a particular directory state
+of a working directory, a "commit" represents that state in "time",
+and explains how we got there.
+
+You create a commit object by giving it the tree that describes the
+state at the time of the commit, and a list of parents:
+
+       git-commit-tree <tree> -p <parent> [-p <parent2> ..]
+
+and then giving the reason for the commit on stdin (either through
+redirection from a pipe or file, or by just typing it at the tty).
+
+git-commit-tree will return the name of the object that represents
+that commit, and you should save it away for later use. Normally,
+you'd commit a new "HEAD" state, and while git doesn't care where you
+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.
+
+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
+object:
+
+               git-cat-file -t <objectname>
+
+shows the type of the object, and once you have the type (which is
+usually implicit in where you find the object), you can use
+
+               git-cat-file blob|tree|commit <objectname>
+
+to show its contents. NOTE! Trees have binary content, and as a result
+there is a special helper for showing that content, called
+"git-ls-tree", which turns the binary content into a more easily
+readable form.
+
+It's especially instructive to look at "commit" objects, since those
+tend to be small and fairly self-explanatory. In particular, if you
+follow the convention of having the top commit name in ".git/HEAD",
+you can do
+
+               git-cat-file commit $(cat .git/HEAD)
+
+to see what the top commit was.
+
+7) Merging multiple trees
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Git helps you do a three-way merge, which you can expand to n-way by
+repeating the merge procedure arbitrary times until you finally
+"commit" the state.  The normal situation is that you'd only do one
+three-way merge (two parents), and commit it, but if you like to, you
+can do multiple parents in one go.
+
+To do a three-way merge, you need the two sets of "commit" objects
+that you want to merge, use those to find the closest common parent (a
+third "commit" object), and then use those commit objects to find the
+state of the directory ("tree" object) at these points.
+
+To get the "base" for the merge, you first look up the common parent
+of two commits with
+
+               git-merge-base <commit1> <commit2>
+
+which will return you the commit they are both based on.  You should
+now look up the "tree" objects of those commits, which you can easily
+do with (for example)
+
+               git-cat-file commit <commitname> | head -1
+
+since the tree object information is always the first line in a commit
+object.
+
+Once you know the three trees you are going to merge (the one
+"original" tree, aka the common case, and the two "result" trees, aka
+the branches you want to merge), you do a "merge" read into the
+index. This will throw away your old index contents, so you should
+make sure that you've committed those - in fact you would normally
+always do a merge against your last commit (which should thus match
+what you have in your current index anyway).
+
+To do the merge, do
+
+               git-read-tree -m <origtree> <target1tree> <target2tree>
+
+which will do all trivial merge operations for you directly in the
+index file, and you can just write the result out with
+"git-write-tree".
+
+NOTE! Because the merge is done in the index file, and not in your
+working directory, your working directory will no longer match your
+index. You can use "git-checkout-cache -f -a" to make the effect of
+the merge be seen in your working directory.
+
+NOTE2! Sadly, many merges aren't trivial. If there are files that have
+been added.moved or removed, or if both branches have modified the
+same file, you will be left with an index tree that contains "merge
+entries" in it. Such an index tree can _NOT_ be written out to a tree
+object, and you will have to resolve any such merge clashes using
+other tools before you can write out the result.
+
+
+[ fixme: talk about resolving merges here ]
diff --git a/apply.c b/apply.c
new file mode 100644 (file)
index 0000000..81607c0
--- /dev/null
+++ b/apply.c
@@ -0,0 +1,1516 @@
+/*
+ * apply.c
+ *
+ * Copyright (C) Linus Torvalds, 2005
+ *
+ * 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"
+
+// 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
+//
+static int merge_patch = 1;
+static int check_index = 0;
+static int write_index = 0;
+static int diffstat = 0;
+static int summary = 0;
+static int check = 0;
+static int apply = 1;
+static int show_files = 0;
+static const char apply_usage[] =
+"git-apply [--no-merge] [--stat] [--summary] [--check] [--index] [--apply] [--show-files] <patch>...";
+
+/*
+ * For "diff-stat" like behaviour, we keep track of the biggest change
+ * we've seen, and the longest filename. That allows us to do simple
+ * scaling.
+ */
+static int max_change, max_len;
+
+/*
+ * Various "current state", notably line numbers and what
+ * file (and how) we're patching right now.. The "is_xxxx"
+ * things are flags, where -1 means "don't know yet".
+ */
+static int linenr = 1;
+
+struct fragment {
+       unsigned long oldpos, oldlines;
+       unsigned long newpos, newlines;
+       const char *patch;
+       int size;
+       struct fragment *next;
+};
+
+struct patch {
+       char *new_name, *old_name, *def_name;
+       unsigned int old_mode, new_mode;
+       int is_rename, is_copy, is_new, is_delete;
+       int lines_added, lines_deleted;
+       int score;
+       struct fragment *fragments;
+       char *result;
+       unsigned long resultsize;
+       struct patch *next;
+};
+
+#define CHUNKSIZE (8192)
+#define SLOP (16)
+
+static void *read_patch_file(int fd, unsigned long *sizep)
+{
+       unsigned long size = 0, alloc = CHUNKSIZE;
+       void *buffer = xmalloc(alloc);
+
+       for (;;) {
+               int nr = alloc - size;
+               if (nr < 1024) {
+                       alloc += CHUNKSIZE;
+                       buffer = xrealloc(buffer, alloc);
+                       nr = alloc - size;
+               }
+               nr = read(fd, buffer + size, nr);
+               if (!nr)
+                       break;
+               if (nr < 0) {
+                       if (errno == EAGAIN)
+                               continue;
+                       die("git-apply: read returned %s", strerror(errno));
+               }
+               size += nr;
+       }
+       *sizep = size;
+
+       /*
+        * Make sure that we have some slop in the buffer
+        * so that we can do speculative "memcmp" etc, and
+        * see to it that it is NUL-filled.
+        */
+       if (alloc < size + SLOP)
+               buffer = xrealloc(buffer, size + SLOP);
+       memset(buffer + size, 0, SLOP);
+       return buffer;
+}
+
+static unsigned long linelen(const char *buffer, unsigned long size)
+{
+       unsigned long len = 0;
+       while (size--) {
+               len++;
+               if (*buffer++ == '\n')
+                       break;
+       }
+       return len;
+}
+
+static int is_dev_null(const char *str)
+{
+       return !memcmp("/dev/null", str, 9) && isspace(str[9]);
+}
+
+#define TERM_SPACE     1
+#define TERM_TAB       2
+
+static int name_terminate(const char *name, int namelen, int c, int terminate)
+{
+       if (c == ' ' && !(terminate & TERM_SPACE))
+               return 0;
+       if (c == '\t' && !(terminate & TERM_TAB))
+               return 0;
+
+       return 1;
+}
+
+static char * find_name(const char *line, char *def, int p_value, int terminate)
+{
+       int len;
+       const char *start = line;
+       char *name;
+
+       for (;;) {
+               char c = *line;
+
+               if (isspace(c)) {
+                       if (c == '\n')
+                               break;
+                       if (name_terminate(start, line-start, c, terminate))
+                               break;
+               }
+               line++;
+               if (c == '/' && !--p_value)
+                       start = line;
+       }
+       if (!start)
+               return def;
+       len = line - start;
+       if (!len)
+               return def;
+
+       /*
+        * Generally we prefer the shorter name, especially
+        * if the other one is just a variation of that with
+        * something else tacked on to the end (ie "file.orig"
+        * or "file~").
+        */
+       if (def) {
+               int deflen = strlen(def);
+               if (deflen < len && !strncmp(start, def, deflen))
+                       return def;
+       }
+
+       name = xmalloc(len + 1);
+       memcpy(name, start, len);
+       name[len] = 0;
+       free(def);
+       return name;
+}
+
+/*
+ * Get the name etc info from the --/+++ lines of a traditional patch header
+ *
+ * NOTE! This hardcodes "-p1" behaviour in filename detection.
+ *
+ * FIXME! The end-of-filename heuristics are kind of screwy. For existing
+ * files, we can happily check the index for a match, but for creating a
+ * new file we should try to match whatever "patch" does. I have no idea.
+ */
+static void parse_traditional_patch(const char *first, const char *second, struct patch *patch)
+{
+       int p_value = 1;
+       char *name;
+
+       first += 4;     // skip "--- "
+       second += 4;    // skip "+++ "
+       if (is_dev_null(first)) {
+               patch->is_new = 1;
+               patch->is_delete = 0;
+               name = find_name(second, NULL, p_value, TERM_SPACE | TERM_TAB);
+               patch->new_name = name;
+       } else if (is_dev_null(second)) {
+               patch->is_new = 0;
+               patch->is_delete = 1;
+               name = find_name(first, NULL, p_value, TERM_SPACE | TERM_TAB);
+               patch->old_name = name;
+       } else {
+               name = find_name(first, NULL, p_value, TERM_SPACE | TERM_TAB);
+               name = find_name(second, name, p_value, TERM_SPACE | TERM_TAB);
+               patch->old_name = patch->new_name = name;
+       }
+       if (!name)
+               die("unable to find filename in patch at line %d", linenr);
+}
+
+static int gitdiff_hdrend(const char *line, struct patch *patch)
+{
+       return -1;
+}
+
+/*
+ * We're anal about diff header consistency, to make
+ * sure that we don't end up having strange ambiguous
+ * patches floating around.
+ *
+ * As a result, gitdiff_{old|new}name() will check
+ * their names against any previous information, just
+ * to make sure..
+ */
+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) {
+               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;
+               return orig_name;
+       }
+       die("git-apply: bad git-diff - inconsistent %s filename on line %d", oldnew, linenr);
+       return NULL;
+}
+
+static int gitdiff_oldname(const char *line, struct patch *patch)
+{
+       patch->old_name = gitdiff_verify_name(line, patch->is_new, patch->old_name, "old");
+       return 0;
+}
+
+static int gitdiff_newname(const char *line, struct patch *patch)
+{
+       patch->new_name = gitdiff_verify_name(line, patch->is_delete, patch->new_name, "new");
+       return 0;
+}
+
+static int gitdiff_oldmode(const char *line, struct patch *patch)
+{
+       patch->old_mode = strtoul(line, NULL, 8);
+       return 0;
+}
+
+static int gitdiff_newmode(const char *line, struct patch *patch)
+{
+       patch->new_mode = strtoul(line, NULL, 8);
+       return 0;
+}
+
+static int gitdiff_delete(const char *line, struct patch *patch)
+{
+       patch->is_delete = 1;
+       patch->old_name = patch->def_name;
+       return gitdiff_oldmode(line, patch);
+}
+
+static int gitdiff_newfile(const char *line, struct patch *patch)
+{
+       patch->is_new = 1;
+       patch->new_name = patch->def_name;
+       return gitdiff_newmode(line, patch);
+}
+
+static int gitdiff_copysrc(const char *line, struct patch *patch)
+{
+       patch->is_copy = 1;
+       patch->old_name = find_name(line, NULL, 0, 0);
+       return 0;
+}
+
+static int gitdiff_copydst(const char *line, struct patch *patch)
+{
+       patch->is_copy = 1;
+       patch->new_name = find_name(line, NULL, 0, 0);
+       return 0;
+}
+
+static int gitdiff_renamesrc(const char *line, struct patch *patch)
+{
+       patch->is_rename = 1;
+       patch->old_name = find_name(line, NULL, 0, 0);
+       return 0;
+}
+
+static int gitdiff_renamedst(const char *line, struct patch *patch)
+{
+       patch->is_rename = 1;
+       patch->new_name = find_name(line, NULL, 0, 0);
+       return 0;
+}
+
+static int gitdiff_similarity(const char *line, struct patch *patch)
+{
+       if ((patch->score = strtoul(line, NULL, 10)) == ULONG_MAX)
+               patch->score = 0;
+       return 0;
+}
+
+static int gitdiff_dissimilarity(const char *line, struct patch *patch)
+{
+       if ((patch->score = strtoul(line, NULL, 10)) == ULONG_MAX)
+               patch->score = 0;
+       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.
+ */
+static int gitdiff_unrecognized(const char *line, struct patch *patch)
+{
+       return -1;
+}
+
+static char *git_header_name(char *line)
+{
+       int len;
+       char *name, *second;
+
+       /*
+        * Find the first '/'
+        */
+       name = line;
+       for (;;) {
+               char c = *name++;
+               if (c == '\n')
+                       return NULL;
+               if (c == '/')
+                       break;
+       }
+
+       /*
+        * We don't accept absolute paths (/dev/null) as possibly valid
+        */
+       if (name == line+1)
+               return NULL;
+
+       /*
+        * Accept a name only if it shows up twice, exactly the same
+        * form.
+        */
+       for (len = 0 ; ; len++) {
+               char c = name[len];
+
+               switch (c) {
+               default:
+                       continue;
+               case '\n':
+                       break;
+               case '\t': case ' ':
+                       second = name+len;
+                       for (;;) {
+                               char c = *second++;
+                               if (c == '\n')
+                                       return NULL;
+                               if (c == '/')
+                                       break;
+                       }
+                       if (second[len] == '\n' && !memcmp(name, second, len)) {
+                               char *ret = xmalloc(len + 1);
+                               memcpy(ret, name, len);
+                               ret[len] = 0;
+                               return ret;
+                       }
+               }
+       }
+       return NULL;
+}
+
+/* Verify that we recognize the lines following a git header */
+static int parse_git_header(char *line, int len, unsigned int size, struct patch *patch)
+{
+       unsigned long offset;
+
+       /* A git diff has explicit new/delete information, so we don't guess */
+       patch->is_new = 0;
+       patch->is_delete = 0;
+
+       /*
+        * Some things may not have the old name in the
+        * rest of the headers anywhere (pure mode changes,
+        * 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 "));
+
+       line += len;
+       size -= len;
+       linenr++;
+       for (offset = len ; size > 0 ; offset += len, size -= len, line += len, linenr++) {
+               static const struct opentry {
+                       const char *str;
+                       int (*fn)(const char *, struct patch *);
+               } optable[] = {
+                       { "@@ -", gitdiff_hdrend },
+                       { "--- ", gitdiff_oldname },
+                       { "+++ ", gitdiff_newname },
+                       { "old mode ", gitdiff_oldmode },
+                       { "new mode ", gitdiff_newmode },
+                       { "deleted file mode ", gitdiff_delete },
+                       { "new file mode ", gitdiff_newfile },
+                       { "copy from ", gitdiff_copysrc },
+                       { "copy to ", gitdiff_copydst },
+                       { "rename old ", gitdiff_renamesrc },
+                       { "rename new ", gitdiff_renamedst },
+                       { "rename from ", gitdiff_renamesrc },
+                       { "rename to ", gitdiff_renamedst },
+                       { "similarity index ", gitdiff_similarity },
+                       { "dissimilarity index ", gitdiff_dissimilarity },
+                       { "", gitdiff_unrecognized },
+               };
+               int i;
+
+               len = linelen(line, size);
+               if (!len || line[len-1] != '\n')
+                       break;
+               for (i = 0; i < sizeof(optable) / sizeof(optable[0]); i++) {
+                       const struct opentry *p = optable + i;
+                       int oplen = strlen(p->str);
+                       if (len < oplen || memcmp(p->str, line, oplen))
+                               continue;
+                       if (p->fn(line + oplen, patch) < 0)
+                               return offset;
+                       break;
+               }
+       }
+
+       return offset;
+}
+
+static int parse_num(const char *line, unsigned long *p)
+{
+       char *ptr;
+
+       if (!isdigit(*line))
+               return 0;
+       *p = strtoul(line, &ptr, 10);
+       return ptr - line;
+}
+
+static int parse_range(const char *line, int len, int offset, const char *expect,
+                       unsigned long *p1, unsigned long *p2)
+{
+       int digits, ex;
+
+       if (offset < 0 || offset >= len)
+               return -1;
+       line += offset;
+       len -= offset;
+
+       digits = parse_num(line, p1);
+       if (!digits)
+               return -1;
+
+       offset += digits;
+       line += digits;
+       len -= digits;
+
+       *p2 = *p1;
+       if (*line == ',') {
+               digits = parse_num(line+1, p2);
+               if (!digits)
+                       return -1;
+
+               offset += digits+1;
+               line += digits+1;
+               len -= digits+1;
+       }
+
+       ex = strlen(expect);
+       if (ex > len)
+               return -1;
+       if (memcmp(line, expect, ex))
+               return -1;
+
+       return offset + ex;
+}
+
+/*
+ * Parse a unified diff fragment header of the
+ * form "@@ -a,b +c,d @@"
+ */
+static int parse_fragment_header(char *line, int len, struct fragment *fragment)
+{
+       int offset;
+
+       if (!len || line[len-1] != '\n')
+               return -1;
+
+       /* Figure out the number of lines in a fragment */
+       offset = parse_range(line, len, 4, " +", &fragment->oldpos, &fragment->oldlines);
+       offset = parse_range(line, len, offset, " @@", &fragment->newpos, &fragment->newlines);
+
+       return offset;
+}
+
+static int find_header(char *line, unsigned long size, int *hdrsize, struct patch *patch)
+{
+       unsigned long offset, len;
+
+       patch->is_rename = patch->is_copy = 0;
+       patch->is_new = patch->is_delete = -1;
+       patch->old_mode = patch->new_mode = 0;
+       patch->old_name = patch->new_name = NULL;
+       for (offset = 0; size > 0; offset += len, size -= len, line += len, linenr++) {
+               unsigned long nextlen;
+
+               len = linelen(line, size);
+               if (!len)
+                       break;
+
+               /* Testing this early allows us to take a few shortcuts.. */
+               if (len < 6)
+                       continue;
+
+               /*
+                * Make sure we don't find any unconnected patch fragmants.
+                * That's a sign that we didn't find a header, and that a
+                * patch has become corrupted/broken up.
+                */
+               if (!memcmp("@@ -", line, 4)) {
+                       struct fragment dummy;
+                       if (parse_fragment_header(line, len, &dummy) < 0)
+                               continue;
+                       error("patch fragment without header at line %d: %.*s", linenr, (int)len-1, line);
+               }
+
+               if (size < len + 6)
+                       break;
+
+               /*
+                * Git patch? It might not have a real patch, just a rename
+                * or mode change, so we handle that specially
+                */
+               if (!memcmp("diff --git ", line, 11)) {
+                       int git_hdr_len = parse_git_header(line, len, size, patch);
+                       if (git_hdr_len <= len)
+                               continue;
+                       if (!patch->old_name && !patch->new_name) {
+                               if (!patch->def_name)
+                                       die("git diff header lacks filename information (line %d)", linenr);
+                               patch->old_name = patch->new_name = patch->def_name;
+                       }
+                       *hdrsize = git_hdr_len;
+                       return offset;
+               }
+
+               /** --- followed by +++ ? */
+               if (memcmp("--- ", line,  4) || memcmp("+++ ", line + len, 4))
+                       continue;
+
+               /*
+                * We only accept unified patches, so we want it to
+                * at least have "@@ -a,b +c,d @@\n", which is 14 chars
+                * minimum
+                */
+               nextlen = linelen(line + len, size - len);
+               if (size < nextlen + 14 || memcmp("@@ -", line + len + nextlen, 4))
+                       continue;
+
+               /* Ok, we'll consider it a patch */
+               parse_traditional_patch(line, line+len, patch);
+               *hdrsize = len + nextlen;
+               linenr += 2;
+               return offset;
+       }
+       return -1;
+}
+
+/*
+ * Parse a unified diff. Note that this really needs
+ * to parse each fragment separately, since the only
+ * way to know the difference between a "---" that is
+ * part of a patch, and a "---" that starts the next
+ * patch is to look at the line counts..
+ */
+static int parse_fragment(char *line, unsigned long size, struct patch *patch, struct fragment *fragment)
+{
+       int added, deleted;
+       int len = linelen(line, size), offset;
+       unsigned long oldlines, newlines;
+
+       offset = parse_fragment_header(line, len, fragment);
+       if (offset < 0)
+               return -1;
+       oldlines = fragment->oldlines;
+       newlines = fragment->newlines;
+
+       if (patch->is_new < 0) {
+               patch->is_new =  !oldlines;
+               if (!oldlines)
+                       patch->old_name = NULL;
+       }
+       if (patch->is_delete < 0) {
+               patch->is_delete = !newlines;
+               if (!newlines)
+                       patch->new_name = NULL;
+       }
+
+       if (patch->is_new != !oldlines)
+               return error("new file depends on old contents");
+       if (patch->is_delete != !newlines) {
+               if (newlines)
+                       return error("deleted file still has contents");
+               fprintf(stderr, "** warning: file %s becomes empty but is not deleted\n", patch->new_name);
+       }
+
+       /* Parse the thing.. */
+       line += len;
+       size -= len;
+       linenr++;
+       added = deleted = 0;
+       for (offset = len; size > 0; offset += len, size -= len, line += len, linenr++) {
+               if (!oldlines && !newlines)
+                       break;
+               len = linelen(line, size);
+               if (!len || line[len-1] != '\n')
+                       return -1;
+               switch (*line) {
+               default:
+                       return -1;
+               case ' ':
+                       oldlines--;
+                       newlines--;
+                       break;
+               case '-':
+                       deleted++;
+                       oldlines--;
+                       break;
+               case '+':
+                       added++;
+                       newlines--;
+                       break;
+               /* We allow "\ No newline at end of file" */
+               case '\\':
+                       if (len < 12 || memcmp(line, "\\ No newline", 12))
+                               return -1;
+                       break;
+               }
+       }
+       /* If a fragment ends with an incomplete line, we failed to include
+        * it in the above loop because we hit oldlines == newlines == 0
+        * before seeing it.
+        */
+       if (12 < size && !memcmp(line, "\\ No newline", 12))
+               offset += linelen(line, size);
+
+       patch->lines_added += added;
+       patch->lines_deleted += deleted;
+       return offset;
+}
+
+static int parse_single_patch(char *line, unsigned long size, struct patch *patch)
+{
+       unsigned long offset = 0;
+       struct fragment **fragp = &patch->fragments;
+
+       while (size > 4 && !memcmp(line, "@@ -", 4)) {
+               struct fragment *fragment;
+               int len;
+
+               fragment = xmalloc(sizeof(*fragment));
+               memset(fragment, 0, sizeof(*fragment));
+               len = parse_fragment(line, size, patch, fragment);
+               if (len <= 0)
+                       die("corrupt patch at line %d", linenr);
+
+               fragment->patch = line;
+               fragment->size = len;
+
+               *fragp = fragment;
+               fragp = &fragment->next;
+
+               offset += len;
+               line += len;
+               size -= len;
+       }
+       return offset;
+}
+
+static int parse_chunk(char *buffer, unsigned long size, struct patch *patch)
+{
+       int hdrsize, patchsize;
+       int offset = find_header(buffer, size, &hdrsize, patch);
+
+       if (offset < 0)
+               return offset;
+
+       patchsize = parse_single_patch(buffer + offset + hdrsize, size - offset - hdrsize, patch);
+
+       return offset + hdrsize + patchsize;
+}
+
+static const char pluses[] = "++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++";
+static const char minuses[]= "----------------------------------------------------------------------";
+
+static void show_stats(struct patch *patch)
+{
+       const char *prefix = "";
+       char *name = patch->new_name;
+       int len, max, add, del, total;
+
+       if (!name)
+               name = patch->old_name;
+
+       /*
+        * "scale" the filename
+        */
+       len = strlen(name);
+       max = max_len;
+       if (max > 50)
+               max = 50;
+       if (len > max) {
+               char *slash;
+               prefix = "...";
+               max -= 3;
+               name += len - max;
+               slash = strchr(name, '/');
+               if (slash)
+                       name = slash;
+       }
+       len = max;
+
+       /*
+        * scale the add/delete
+        */
+       max = max_change;
+       if (max + len > 70)
+               max = 70 - len;
+
+       add = patch->lines_added;
+       del = patch->lines_deleted;
+       total = add + del;
+
+       if (max_change > 0) {
+               total = (total * max + max_change / 2) / max_change;
+               add = (add * max + max_change / 2) / max_change;
+               del = total - add;
+       }
+       printf(" %s%-*s |%5d %.*s%.*s\n", prefix,
+               len, name, patch->lines_added + patch->lines_deleted,
+               add, pluses, del, minuses);
+}
+
+static int read_old_data(struct stat *st, const char *path, void *buf, unsigned long size)
+{
+       int fd;
+       unsigned long got;
+
+       switch (st->st_mode & S_IFMT) {
+       case S_IFLNK:
+               return readlink(path, buf, size);
+       case S_IFREG:
+               fd = open(path, O_RDONLY);
+               if (fd < 0)
+                       return error("unable to open %s", path);
+               got = 0;
+               for (;;) {
+                       int ret = read(fd, buf + got, size - got);
+                       if (ret < 0) {
+                               if (errno == EAGAIN)
+                                       continue;
+                               break;
+                       }
+                       if (!ret)
+                               break;
+                       got += ret;
+               }
+               close(fd);
+               return got;
+
+       default:
+               return -1;
+       }
+}
+
+static int find_offset(const char *buf, unsigned long size, const char *fragment, unsigned long fragsize, int line)
+{
+       int i;
+       unsigned long start, backwards, forwards;
+
+       if (fragsize > size)
+               return -1;
+
+       start = 0;
+       if (line > 1) {
+               unsigned long offset = 0;
+               i = line-1;
+               while (offset + fragsize <= size) {
+                       if (buf[offset++] == '\n') {
+                               start = offset;
+                               if (!--i)
+                                       break;
+                       }
+               }
+       }
+
+       /* Exact line number? */
+       if (!memcmp(buf + start, fragment, fragsize))
+               return start;
+
+       /*
+        * There's probably some smart way to do this, but I'll leave
+        * that to the smart and beautiful people. I'm simple and stupid.
+        */
+       backwards = start;
+       forwards = start;
+       for (i = 0; ; i++) {
+               unsigned long try;
+               int n;
+
+               /* "backward" */
+               if (i & 1) {
+                       if (!backwards) {
+                               if (forwards + fragsize > size)
+                                       break;
+                               continue;
+                       }
+                       do {
+                               --backwards;
+                       } while (backwards && buf[backwards-1] != '\n');
+                       try = backwards;
+               } else {
+                       while (forwards + fragsize <= size) {
+                               if (buf[forwards++] == '\n')
+                                       break;
+                       }
+                       try = forwards;
+               }
+
+               if (try + fragsize > size)
+                       continue;
+               if (memcmp(buf + try, fragment, fragsize))
+                       continue;
+               n = (i >> 1)+1;
+               if (i & 1)
+                       n = -n;
+               return try;
+       }
+
+       /*
+        * We should start searching forward and backward.
+        */
+       return -1;
+}
+
+struct buffer_desc {
+       char *buffer;
+       unsigned long size;
+       unsigned long alloc;
+};
+
+static int apply_one_fragment(struct buffer_desc *desc, struct fragment *frag)
+{
+       char *buf = desc->buffer;
+       const char *patch = frag->patch;
+       int offset, size = frag->size;
+       char *old = xmalloc(size);
+       char *new = xmalloc(size);
+       int oldsize = 0, newsize = 0;
+
+       while (size > 0) {
+               int len = linelen(patch, size);
+               int plen;
+
+               if (!len)
+                       break;
+
+               /*
+                * "plen" is how much of the line we should use for
+                * the actual patch data. Normally we just remove the
+                * first character on the line, but if the line is
+                * followed by "\ No newline", then we also remove the
+                * last one (which is the newline, of course).
+                */
+               plen = len-1;
+               if (len < size && patch[len] == '\\')
+                       plen--;
+               switch (*patch) {
+               case ' ':
+               case '-':
+                       memcpy(old + oldsize, patch + 1, plen);
+                       oldsize += plen;
+                       if (*patch == '-')
+                               break;
+               /* Fall-through for ' ' */
+               case '+':
+                       memcpy(new + newsize, patch + 1, plen);
+                       newsize += plen;
+                       break;
+               case '@': case '\\':
+                       /* Ignore it, we already handled it */
+                       break;
+               default:
+                       return -1;
+               }
+               patch += len;
+               size -= len;
+       }
+
+       offset = find_offset(buf, desc->size, old, oldsize, frag->newpos);
+       if (offset >= 0) {
+               int diff = newsize - oldsize;
+               unsigned long size = desc->size + diff;
+               unsigned long alloc = desc->alloc;
+
+               if (size > alloc) {
+                       alloc = size + 8192;
+                       desc->alloc = alloc;
+                       buf = xrealloc(buf, alloc);
+                       desc->buffer = buf;
+               }
+               desc->size = size;
+               memmove(buf + offset + newsize, buf + offset + oldsize, size - offset - newsize);
+               memcpy(buf + offset, new, newsize);
+               offset = 0;
+       }
+
+       free(old);
+       free(new);
+       return offset;
+}
+
+static int apply_fragments(struct buffer_desc *desc, struct patch *patch)
+{
+       struct fragment *frag = patch->fragments;
+
+       while (frag) {
+               if (apply_one_fragment(desc, frag) < 0)
+                       return error("patch failed: %s:%ld", patch->old_name, frag->oldpos);
+               frag = frag->next;
+       }
+       return 0;
+}
+
+static int apply_data(struct patch *patch, struct stat *st)
+{
+       char *buf;
+       unsigned long size, alloc;
+       struct buffer_desc desc;
+
+       size = 0;
+       alloc = 0;
+       buf = NULL;
+       if (patch->old_name) {
+               size = st->st_size;
+               alloc = size + 8192;
+               buf = xmalloc(alloc);
+               if (read_old_data(st, patch->old_name, buf, alloc) != size)
+                       return error("read of %s failed", patch->old_name);
+       }
+
+       desc.size = size;
+       desc.alloc = alloc;
+       desc.buffer = buf;
+       if (apply_fragments(&desc, patch) < 0)
+               return -1;
+       patch->result = desc.buffer;
+       patch->resultsize = desc.size;
+
+       if (patch->is_delete && patch->resultsize)
+               return error("removal patch leaves file contents");
+
+       return 0;
+}
+
+static int check_patch(struct patch *patch)
+{
+       struct stat st;
+       const char *old_name = patch->old_name;
+       const char *new_name = patch->new_name;
+
+       if (old_name) {
+               int changed;
+
+               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);
+                       changed = ce_match_stat(active_cache[pos], &st);
+                       if (changed)
+                               return error("%s: does not match index", old_name);
+               }
+               if (patch->is_new < 0)
+                       patch->is_new = 0;
+               st.st_mode = ntohl(create_ce_mode(st.st_mode));
+               if (!patch->old_mode)
+                       patch->old_mode = st.st_mode;
+               if ((st.st_mode ^ patch->old_mode) & S_IFMT)
+                       return error("%s: wrong type", old_name);
+               if (st.st_mode != patch->old_mode)
+                       fprintf(stderr, "warning: %s has type %o, expected %o\n",
+                               old_name, st.st_mode, patch->old_mode);
+       }
+
+       if (new_name && (patch->is_new | patch->is_rename | patch->is_copy)) {
+               if (check_index && cache_name_pos(new_name, strlen(new_name)) >= 0)
+                       return error("%s: already exists in index", new_name);
+               if (!lstat(new_name, &st))
+                       return error("%s: already exists in working directory", new_name);
+               if (errno != ENOENT)
+                       return error("%s: %s", new_name, strerror(errno));
+               if (!patch->new_mode)
+                       patch->new_mode = S_IFREG | 0644;
+       }
+
+       if (new_name && old_name) {
+               int same = !strcmp(old_name, new_name);
+               if (!patch->new_mode)
+                       patch->new_mode = patch->old_mode;
+               if ((patch->old_mode ^ patch->new_mode) & S_IFMT)
+                       return error("new mode (%o) of %s does not match old mode (%o)%s%s",
+                               patch->new_mode, new_name, patch->old_mode,
+                               same ? "" : " of ", same ? "" : old_name);
+       }       
+
+       if (apply_data(patch, &st) < 0)
+               return error("%s: patch does not apply", old_name);
+       return 0;
+}
+
+static int check_patch_list(struct patch *patch)
+{
+       int error = 0;
+
+       for (;patch ; patch = patch->next)
+               error |= check_patch(patch);
+       return error;
+}
+
+static void show_file(int c, unsigned int mode, const char *name)
+{
+       printf("%c %o %s\n", c, mode, name);
+}
+
+static void show_file_list(struct patch *patch)
+{
+       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);
+       }
+}
+
+static void stat_patch_list(struct patch *patch)
+{
+       int files, adds, dels;
+
+       for (files = adds = dels = 0 ; patch ; patch = patch->next) {
+               files++;
+               adds += patch->lines_added;
+               dels += patch->lines_deleted;
+               show_stats(patch);
+       }
+
+       printf(" %d files changed, %d insertions(+), %d deletions(-)\n", files, adds, dels);
+}
+
+static void show_file_mode_name(const char *newdelete, unsigned int mode, const char *name)
+{
+       if (mode)
+               printf(" %s mode %06o %s\n", newdelete, mode, name);
+       else
+               printf(" %s %s\n", newdelete, name);
+}
+
+static void show_mode_change(struct patch *p, int show_name)
+{
+       if (p->old_mode && p->new_mode && p->old_mode != p->new_mode) {
+               if (show_name)
+                       printf(" mode change %06o => %06o %s\n",
+                              p->old_mode, p->new_mode, p->new_name);
+               else
+                       printf(" mode change %06o => %06o\n",
+                              p->old_mode, p->new_mode);
+       }
+}
+
+static void show_rename_copy(struct patch *p)
+{
+       const char *renamecopy = p->is_rename ? "rename" : "copy";
+       const char *old, *new;
+
+       /* Find common prefix */
+       old = p->old_name;
+       new = p->new_name;
+       while (1) {
+               const char *slash_old, *slash_new;
+               slash_old = strchr(old, '/');
+               slash_new = strchr(new, '/');
+               if (!slash_old ||
+                   !slash_new ||
+                   slash_old - old != slash_new - new ||
+                   memcmp(old, new, slash_new - new))
+                       break;
+               old = slash_old + 1;
+               new = slash_new + 1;
+       }
+       /* p->old_name thru old is the common prefix, and old and new
+        * through the end of names are renames
+        */
+       if (old != p->old_name)
+               printf(" %s %.*s{%s => %s} (%d%%)\n", renamecopy,
+                      (int)(old - p->old_name), p->old_name,
+                      old, new, p->score);
+       else
+               printf(" %s %s => %s (%d%%)\n", renamecopy,
+                      p->old_name, p->new_name, p->score);
+       show_mode_change(p, 0);
+}
+
+static void summary_patch_list(struct patch *patch)
+{
+       struct patch *p;
+
+       for (p = patch; p; p = p->next) {
+               if (p->is_new)
+                       show_file_mode_name("create", p->new_mode, p->new_name);
+               else if (p->is_delete)
+                       show_file_mode_name("delete", p->old_mode, p->old_name);
+               else {
+                       if (p->is_rename || p->is_copy)
+                               show_rename_copy(p);
+                       else {
+                               if (p->score) {
+                                       printf(" rewrite %s (%d%%)\n",
+                                              p->new_name, p->score);
+                                       show_mode_change(p, 0);
+                               }
+                               else
+                                       show_mode_change(p, 1);
+                       }
+               }
+       }
+}
+
+static void patch_stats(struct patch *patch)
+{
+       int lines = patch->lines_added + patch->lines_deleted;
+
+       if (lines > max_change)
+               max_change = lines;
+       if (patch->old_name) {
+               int len = strlen(patch->old_name);
+               if (len > max_len)
+                       max_len = len;
+       }
+       if (patch->new_name) {
+               int len = strlen(patch->new_name);
+               if (len > max_len)
+                       max_len = len;
+       }
+}
+
+static void remove_file(struct patch *patch)
+{
+       if (write_index) {
+               if (remove_file_from_cache(patch->old_name) < 0)
+                       die("unable to remove %s from index", patch->old_name);
+       }
+       unlink(patch->old_name);
+}
+
+static void add_index_file(const char *path, unsigned mode, void *buf, unsigned long size)
+{
+       struct stat st;
+       struct cache_entry *ce;
+       int namelen = strlen(path);
+       unsigned ce_size = cache_entry_size(namelen);
+
+       if (!write_index)
+               return;
+
+       ce = xmalloc(ce_size);
+       memset(ce, 0, ce_size);
+       memcpy(ce->name, path, namelen);
+       ce->ce_mode = create_ce_mode(mode);
+       ce->ce_flags = htons(namelen);
+       if (lstat(path, &st) < 0)
+               die("unable to stat newly created file %s", path);
+       fill_stat_cache_info(ce, &st);
+       if (write_sha1_file(buf, size, "blob", ce->sha1) < 0)
+               die("unable to create backing store for newly created file %s", path);
+       if (add_cache_entry(ce, ADD_CACHE_OK_TO_ADD) < 0)
+               die("unable to add cache entry for %s", path);
+}
+
+static void create_subdirectories(const char *path)
+{
+       int len = strlen(path);
+       char *buf = xmalloc(len + 1);
+       const char *slash = path;
+
+       while ((slash = strchr(slash+1, '/')) != NULL) {
+               len = slash - path;
+               memcpy(buf, path, len);
+               buf[len] = 0;
+               if (mkdir(buf, 0777) < 0) {
+                       if (errno != EEXIST)
+                               break;
+               }
+       }
+       free(buf);
+}
+
+static int try_create_file(const char *path, unsigned int mode, const char *buf, unsigned long size)
+{
+       int fd;
+
+       if (S_ISLNK(mode))
+               return symlink(buf, path);
+       fd = open(path, O_CREAT | O_EXCL | O_WRONLY | O_TRUNC, (mode & 0100) ? 0777 : 0666);
+       if (fd < 0)
+               return -1;
+       while (size) {
+               int written = write(fd, buf, size);
+               if (written < 0) {
+                       if (errno == EINTR || errno == EAGAIN)
+                               continue;
+                       die("writing file %s: %s", path, strerror(errno));
+               }
+               if (!written)
+                       die("out of space writing file %s", path);
+               buf += written;
+               size -= written;
+       }
+       if (close(fd) < 0)
+               die("closing file %s: %s", path, strerror(errno));
+       return 0;
+}
+
+/*
+ * We optimistically assume that the directories exist,
+ * which is true 99% of the time anyway. If they don't,
+ * we create them and try again.
+ */
+static void create_one_file(const char *path, unsigned mode, const char *buf, unsigned long size)
+{
+       if (!try_create_file(path, mode, buf, size))
+               return;
+
+       if (errno == ENOENT) {
+               create_subdirectories(path);
+               if (!try_create_file(path, mode, buf, size))
+                       return;
+       }
+
+       if (errno == EEXIST) {
+               unsigned int nr = getpid();
+
+               for (;;) {
+                       const char *newpath;
+                       newpath = mkpath("%s~%u", path, nr);
+                       if (!try_create_file(newpath, mode, buf, size)) {
+                               if (!rename(newpath, path))
+                                       return;
+                               unlink(newpath);
+                               break;
+                       }
+                       if (errno != EEXIST)
+                               break;
+               }                       
+       }
+       die("unable to write file %s mode %o", path, mode);
+}
+
+static void create_file(struct patch *patch)
+{
+       const char *path = patch->new_name;
+       unsigned mode = patch->new_mode;
+       unsigned long size = patch->resultsize;
+       char *buf = patch->result;
+
+       if (!mode)
+               mode = S_IFREG | 0644;
+       create_one_file(path, mode, buf, size); 
+       add_index_file(path, mode, buf, size);
+}
+
+static void write_out_one_result(struct patch *patch)
+{
+       if (patch->is_delete > 0) {
+               remove_file(patch);
+               return;
+       }
+       if (patch->is_new > 0 || patch->is_copy) {
+               create_file(patch);
+               return;
+       }
+       /*
+        * Rename or modification boils down to the same
+        * thing: remove the old, write the new
+        */
+       remove_file(patch);
+       create_file(patch);
+}
+
+static void write_out_results(struct patch *list, int skipped_patch)
+{
+       if (!list && !skipped_patch)
+               die("No changes");
+
+       while (list) {
+               write_out_one_result(list);
+               list = list->next;
+       }
+}
+
+static struct cache_file cache_file;
+
+static struct excludes {
+       struct excludes *next;
+       const char *path;
+} *excludes;
+
+static int use_patch(struct patch *p)
+{
+       const char *pathname = p->new_name ? : p->old_name;
+       struct excludes *x = excludes;
+       while (x) {
+               if (fnmatch(x->path, pathname, 0) == 0)
+                       return 0;
+               x = x->next;
+       }
+       return 1;
+}
+
+static int apply_patch(int fd)
+{
+       int newfd;
+       unsigned long offset, size;
+       char *buffer = read_patch_file(fd, &size);
+       struct patch *list = NULL, **listp = &list;
+       int skipped_patch = 0;
+
+       if (!buffer)
+               return -1;
+       offset = 0;
+       while (size > 0) {
+               struct patch *patch;
+               int nr;
+
+               patch = xmalloc(sizeof(*patch));
+               memset(patch, 0, sizeof(*patch));
+               nr = parse_chunk(buffer + offset, size, patch);
+               if (nr < 0)
+                       break;
+               if (use_patch(patch)) {
+                       patch_stats(patch);
+                       *listp = patch;
+                       listp = &patch->next;
+               } else {
+                       /* perhaps free it a bit better? */
+                       free(patch);
+                       skipped_patch++;
+               }
+               offset += nr;
+               size -= nr;
+       }
+
+       newfd = -1;
+       write_index = check_index && apply;
+       if (write_index)
+               newfd = hold_index_file_for_update(&cache_file, get_index_file());
+       if (check_index) {
+               if (read_cache() < 0)
+                       die("unable to read index file");
+       }
+
+       if ((check || apply) && check_patch_list(list) < 0)
+               exit(1);
+
+       if (apply)
+               write_out_results(list, skipped_patch);
+
+       if (write_index) {
+               if (write_cache(newfd, active_cache, active_nr) ||
+                   commit_index_file(&cache_file))
+                       die("Unable to write new cachefile");
+       }
+
+       if (show_files)
+               show_file_list(list);
+
+       if (diffstat)
+               stat_patch_list(list);
+
+       if (summary)
+               summary_patch_list(list);
+
+       free(buffer);
+       return 0;
+}
+
+int main(int argc, char **argv)
+{
+       int i;
+       int read_stdin = 1;
+
+       for (i = 1; i < argc; i++) {
+               const char *arg = argv[i];
+               int fd;
+
+               if (!strcmp(arg, "-")) {
+                       apply_patch(0);
+                       read_stdin = 0;
+                       continue;
+               }
+               if (!strncmp(arg, "--exclude=", 10)) {
+                       struct excludes *x = xmalloc(sizeof(*x));
+                       x->path = arg + 10;
+                       x->next = excludes;
+                       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, "--summary")) {
+                       apply = 0;
+                       summary = 1;
+                       continue;
+               }
+               if (!strcmp(arg, "--check")) {
+                       apply = 0;
+                       check = 1;
+                       continue;
+               }
+               if (!strcmp(arg, "--index")) {
+                       check_index = 1;
+                       continue;
+               }
+               if (!strcmp(arg, "--apply")) {
+                       apply = 1;
+                       continue;
+               }
+               if (!strcmp(arg, "--show-files")) {
+                       show_files = 1;
+                       continue;
+               }
+               fd = open(arg, O_RDONLY);
+               if (fd < 0)
+                       usage(apply_usage);
+               read_stdin = 0;
+               apply_patch(fd);
+               close(fd);
+       }
+       if (read_stdin)
+               apply_patch(0);
+       return 0;
+}
diff --git a/blob.c b/blob.c
new file mode 100644 (file)
index 0000000..ea52ad5
--- /dev/null
+++ b/blob.c
@@ -0,0 +1,52 @@
+#include "blob.h"
+#include "cache.h"
+#include <stdlib.h>
+
+const char *blob_type = "blob";
+
+struct blob *lookup_blob(const unsigned char *sha1)
+{
+       struct object *obj = lookup_object(sha1);
+       if (!obj) {
+               struct blob *ret = xmalloc(sizeof(struct blob));
+               memset(ret, 0, sizeof(struct blob));
+               created_object(sha1, &ret->object);
+               ret->object.type = blob_type;
+               return ret;
+       }
+       if (!obj->type)
+               obj->type = blob_type;
+       if (obj->type != blob_type) {
+               error("Object %s is a %s, not a blob", 
+                     sha1_to_hex(sha1), obj->type);
+               return NULL;
+       }
+       return (struct blob *) obj;
+}
+
+int parse_blob_buffer(struct blob *item, void *buffer, unsigned long size)
+{
+       item->object.parsed = 1;
+       return 0;
+}
+
+int parse_blob(struct blob *item)
+{
+        char type[20];
+        void *buffer;
+        unsigned long size;
+       int ret;
+
+        if (item->object.parsed)
+                return 0;
+        buffer = read_sha1_file(item->object.sha1, type, &size);
+        if (!buffer)
+                return error("Could not read %s",
+                             sha1_to_hex(item->object.sha1));
+        if (strcmp(type, blob_type))
+                return error("Object %s not a blob",
+                             sha1_to_hex(item->object.sha1));
+       ret = parse_blob_buffer(item, buffer, size);
+       free(buffer);
+       return ret;
+}
diff --git a/blob.h b/blob.h
new file mode 100644 (file)
index 0000000..ea5d9e9
--- /dev/null
+++ b/blob.h
@@ -0,0 +1,18 @@
+#ifndef BLOB_H
+#define BLOB_H
+
+#include "object.h"
+
+extern const char *blob_type;
+
+struct blob {
+       struct object object;
+};
+
+struct blob *lookup_blob(const unsigned char *sha1);
+
+int parse_blob_buffer(struct blob *item, void *buffer, unsigned long size);
+
+int parse_blob(struct blob *item);
+
+#endif /* BLOB_H */
diff --git a/build-rev-cache.c b/build-rev-cache.c
new file mode 100644 (file)
index 0000000..948898b
--- /dev/null
@@ -0,0 +1,56 @@
+#include "refs.h"
+#include "cache.h"
+#include "commit.h"
+#include "rev-cache.h"
+
+static void process_head_list(int verbose)
+{
+       char buf[512];
+
+       while (fgets(buf, sizeof(buf), stdin)) {
+               unsigned char sha1[20];
+               struct commit *commit;
+
+               if (get_sha1_hex(buf, sha1)) {
+                       error("ignoring: %s", buf);
+                       continue;
+               }
+               if (!(commit = lookup_commit_reference(sha1))) {
+                       error("not a commit: %s", sha1_to_hex(sha1));
+                       continue;
+               }
+               record_rev_cache(commit->object.sha1, verbose ? stderr : NULL);
+       }
+}
+
+
+static const char *build_rev_cache_usage =
+"git-build-rev-cache <rev-cache-file> < list-of-heads";
+
+int main(int ac, char **av)
+{
+       int verbose = 0;
+       const char *path;
+
+       while (1 < ac && av[1][0] == '-') {
+               if (!strcmp(av[1], "-v"))
+                       verbose = 1;
+               else
+                       usage(build_rev_cache_usage);
+               ac--; av++;
+       }
+
+       if (ac != 2)
+               usage(build_rev_cache_usage);
+
+       path = av[1];
+
+       /* read existing rev-cache */
+       read_rev_cache(path, NULL, 0);
+
+       process_head_list(verbose);
+
+       /* update the rev-cache database by appending newly found one to it */
+       write_rev_cache(path, path);
+       return 0;
+}
diff --git a/cache.h b/cache.h
new file mode 100644 (file)
index 0000000..efd2a2c
--- /dev/null
+++ b/cache.h
@@ -0,0 +1,340 @@
+#ifndef CACHE_H
+#define CACHE_H
+
+#include <unistd.h>
+#include <stdio.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <errno.h>
+#include <limits.h>
+#include <sys/mman.h>
+#include <sys/param.h>
+#include <netinet/in.h>
+#include <sys/types.h>
+#include <dirent.h>
+
+#include SHA1_HEADER
+#include <zlib.h>
+
+#if ZLIB_VERNUM < 0x1200
+#define deflateBound(c,s)  ((s) + (((s) + 7) >> 3) + (((s) + 63) >> 6) + 11)
+#endif
+
+#ifdef DT_UNKNOWN
+#define DTYPE(de)      ((de)->d_type)
+#else
+#define DT_UNKNOWN     0
+#define DT_DIR         1
+#define DT_REG         2
+#define DT_LNK         3
+#define DTYPE(de)      DT_UNKNOWN
+#endif
+
+#ifdef __GNUC__
+#define NORETURN __attribute__((__noreturn__))
+#else
+#define NORETURN
+#endif
+
+#ifndef __attribute__
+#define __attribute(x)
+#endif
+
+/*
+ * Intensive research over the course of many years has shown that
+ * port 9418 is totally unused by anything else. Or
+ *
+ *     Your search - "port 9418" - did not match any documents.
+ *
+ * as www.google.com puts it.
+ */
+#define DEFAULT_GIT_PORT 9418
+
+/*
+ * Environment variables transition.
+ * We accept older names for now but warn.
+ */
+extern char *gitenv_bc(const char *);
+#define gitenv(e) (getenv(e) ? : gitenv_bc(e))
+
+/*
+ * Basic data structures for the directory cache
+ */
+
+#define CACHE_SIGNATURE 0x44495243     /* "DIRC" */
+struct cache_header {
+       unsigned int hdr_signature;
+       unsigned int hdr_version;
+       unsigned int hdr_entries;
+};
+
+/*
+ * The "cache_time" is just the low 32 bits of the
+ * time. It doesn't matter if it overflows - we only
+ * check it for equality in the 32 bits we save.
+ */
+struct cache_time {
+       unsigned int sec;
+       unsigned int nsec;
+};
+
+/*
+ * dev/ino/uid/gid/size are also just tracked to the low 32 bits
+ * Again - this is just a (very strong in practice) heuristic that
+ * the inode hasn't changed.
+ *
+ * We save the fields in big-endian order to allow using the
+ * index file over NFS transparently.
+ */
+struct cache_entry {
+       struct cache_time ce_ctime;
+       struct cache_time ce_mtime;
+       unsigned int ce_dev;
+       unsigned int ce_ino;
+       unsigned int ce_mode;
+       unsigned int ce_uid;
+       unsigned int ce_gid;
+       unsigned int ce_size;
+       unsigned char sha1[20];
+       unsigned short ce_flags;
+       char name[0];
+};
+
+#define CE_NAMEMASK  (0x0fff)
+#define CE_STAGEMASK (0x3000)
+#define CE_UPDATE    (0x4000)
+#define CE_STAGESHIFT 12
+
+#define create_ce_flags(len, stage) htons((len) | ((stage) << CE_STAGESHIFT))
+#define ce_namelen(ce) (CE_NAMEMASK & ntohs((ce)->ce_flags))
+#define ce_size(ce) cache_entry_size(ce_namelen(ce))
+#define ce_stage(ce) ((CE_STAGEMASK & ntohs((ce)->ce_flags)) >> CE_STAGESHIFT)
+
+#define ce_permissions(mode) (((mode) & 0100) ? 0755 : 0644)
+static inline unsigned int create_ce_mode(unsigned int mode)
+{
+       if (S_ISLNK(mode))
+               return htonl(S_IFLNK);
+       return htonl(S_IFREG | ce_permissions(mode));
+}
+
+#define cache_entry_size(len) ((offsetof(struct cache_entry,name) + (len) + 8) & ~7)
+
+extern struct cache_entry **active_cache;
+extern unsigned int active_nr, active_alloc, active_cache_changed;
+
+#define GIT_DIR_ENVIRONMENT "GIT_DIR"
+#define DEFAULT_GIT_DIR_ENVIRONMENT ".git"
+#define DB_ENVIRONMENT "GIT_OBJECT_DIRECTORY"
+#define INDEX_ENVIRONMENT "GIT_INDEX_FILE"
+#define GRAFT_ENVIRONMENT "GIT_GRAFT_FILE"
+
+extern char *get_object_directory(void);
+extern char *get_refs_directory(void);
+extern char *get_index_file(void);
+extern char *get_graft_file(void);
+
+#define ALTERNATE_DB_ENVIRONMENT "GIT_ALTERNATE_OBJECT_DIRECTORIES"
+
+#define alloc_nr(x) (((x)+16)*3/2)
+
+/* Initialize and use the cache information */
+extern int read_cache(void);
+extern int write_cache(int newfd, struct cache_entry **cache, int entries);
+extern int cache_name_pos(const char *name, int namelen);
+#define ADD_CACHE_OK_TO_ADD 1          /* Ok to add */
+#define ADD_CACHE_OK_TO_REPLACE 2      /* Ok to replace file/directory */
+#define ADD_CACHE_SKIP_DFCHECK 4       /* Ok to skip DF conflict checks */
+extern int add_cache_entry(struct cache_entry *ce, int option);
+extern int remove_cache_entry_at(int pos);
+extern int remove_file_from_cache(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_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 void fill_stat_cache_info(struct cache_entry *ce, struct stat *st);
+
+struct cache_file {
+       struct cache_file *next;
+       char lockfile[PATH_MAX];
+};
+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 *);
+
+#define MTIME_CHANGED  0x0001
+#define CTIME_CHANGED  0x0002
+#define OWNER_CHANGED  0x0004
+#define MODE_CHANGED    0x0008
+#define INODE_CHANGED   0x0010
+#define DATA_CHANGED    0x0020
+#define TYPE_CHANGED    0x0040
+
+/* Return a statically allocated filename matching the sha1 signature */
+extern char *mkpath(const char *fmt, ...) __attribute__((format (printf, 1, 2)));
+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);
+
+int git_mkstemp(char *path, size_t n, const char *template);
+
+int safe_create_leading_directories(char *path);
+char *safe_strncpy(char *, const char *, size_t);
+
+/* Read and unpack a sha1 file into memory, write memory to a sha1 file */
+extern int unpack_sha1_header(z_stream *stream, void *map, unsigned long mapsize, void *buffer, unsigned long size);
+extern int parse_sha1_header(char *hdr, char *type, unsigned long *sizep);
+extern int sha1_object_info(const unsigned char *, char *, unsigned long *);
+extern void * unpack_sha1_file(void *map, unsigned long mapsize, char *type, unsigned long *size);
+extern void * read_sha1_file(const unsigned char *sha1, char *type, unsigned long *size);
+extern int write_sha1_file(void *buf, unsigned long len, const char *type, unsigned char *return_sha1);
+extern char *write_sha1_file_prepare(void *buf,
+                                    unsigned long len,
+                                    const char *type,
+                                    unsigned char *sha1,
+                                    unsigned char *hdr,
+                                    int *hdrlen);
+
+extern int check_sha1_signature(const unsigned char *sha1, void *buf, unsigned long size, const char *type);
+
+/* Read a tree into the cache */
+extern int read_tree(void *buffer, unsigned long size, int stage, const char **paths);
+
+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 has_sha1_pack(const unsigned char *sha1);
+extern int has_sha1_file(const unsigned char *sha1);
+
+extern int has_pack_file(const unsigned char *sha1);
+extern int has_pack_index(const unsigned char *sha1);
+
+/* Convert to/from hex/sha1 representation */
+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! */
+
+/* General helper functions */
+extern void usage(const char *err) NORETURN;
+extern void die(const char *err, ...) NORETURN __attribute__((format (printf, 1, 2)));
+extern int error(const char *err, ...) __attribute__((format (printf, 1, 2)));
+
+extern int base_name_compare(const char *name1, int len1, int mode1, const char *name2, int len2, int mode2);
+extern int cache_name_compare(const char *name1, int len1, const char *name2, int len2);
+
+extern void *read_object_with_reference(const unsigned char *sha1,
+                                       const char *required_type,
+                                       unsigned long *size,
+                                       unsigned char *sha1_ret);
+
+const char *show_date(unsigned long time, int timezone);
+void parse_date(const char *date, char *buf, int bufsize);
+void datestamp(char *buf, int bufsize);
+
+extern int setup_ident(void);
+extern char *get_ident(const char *name, const char *email, const char *date_str);
+extern char *git_author_info(void);
+extern char *git_committer_info(void);
+
+static inline void *xmalloc(size_t size)
+{
+       void *ret = malloc(size);
+       if (!ret)
+               die("Out of memory, malloc failed");
+       return ret;
+}
+
+static inline void *xrealloc(void *ptr, size_t size)
+{
+       void *ret = realloc(ptr, size);
+       if (!ret)
+               die("Out of memory, realloc failed");
+       return ret;
+}
+
+static inline void *xcalloc(size_t nmemb, size_t size)
+{
+       void *ret = calloc(nmemb, size);
+       if (!ret)
+               die("Out of memory, calloc failed");
+       return ret;
+}
+
+struct checkout {
+       const char *base_dir;
+       int base_dir_len;
+       unsigned force:1,
+                quiet:1,
+                not_new:1,
+                refresh_cache:1;
+};
+
+extern int checkout_entry(struct cache_entry *ce, struct checkout *state);
+
+extern struct alternate_object_database {
+       char *base;
+       char *name;
+} *alt_odb;
+extern void prepare_alt_odb(void);
+
+extern struct packed_git {
+       struct packed_git *next;
+       unsigned long index_size;
+       unsigned long pack_size;
+       unsigned int *index_base;
+       void *pack_base;
+       unsigned int pack_last_used;
+       unsigned int pack_use_cnt;
+       unsigned char sha1[20];
+       char pack_name[0]; /* something like ".git/objects/pack/xxxxx.pack" */
+} *packed_git;
+
+struct pack_entry {
+       unsigned int offset;
+       unsigned char sha1[20];
+       struct packed_git *p;
+};
+
+struct ref {
+       struct ref *next;
+       unsigned char old_sha1[20];
+       unsigned char new_sha1[20];
+       struct ref *peer_ref; /* when renaming */
+       char name[0];
+};
+
+extern int git_connect(int fd[2], char *url, const char *prog);
+extern int finish_connect(pid_t pid);
+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 packed_git *parse_pack_index(unsigned char *sha1);
+
+extern void prepare_packed_git(void);
+extern void install_packed_git(struct packed_git *pack);
+
+extern struct packed_git *find_sha1_pack(const unsigned char *sha1, 
+                                        struct packed_git *packs);
+
+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 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 *);
+extern void *unpack_entry_gently(struct pack_entry *, char *, unsigned long *);
+extern void packed_object_info_detail(struct pack_entry *, char *, unsigned long *, unsigned long *, int *, unsigned char *);
+
+/* Dumb servers support */
+extern int update_server_info(int);
+
+#endif /* CACHE_H */
diff --git a/cat-file.c b/cat-file.c
new file mode 100644 (file)
index 0000000..ef58970
--- /dev/null
@@ -0,0 +1,55 @@
+/*
+ * GIT - The information manager from hell
+ *
+ * Copyright (C) Linus Torvalds, 2005
+ */
+#include "cache.h"
+
+int main(int argc, char **argv)
+{
+       unsigned char sha1[20];
+       char type[20];
+       void *buf;
+       unsigned long size;
+
+       if (argc != 3 || get_sha1(argv[2], sha1))
+               usage("git-cat-file [-t | -s | <type>] <sha1>");
+
+       if (!strcmp("-t", argv[1]) || !strcmp("-s", argv[1])) {
+               if (!sha1_object_info(sha1, type,
+                                     argv[1][1] == 's' ? &size : NULL)) {
+                       switch (argv[1][1]) {
+                       case 't':
+                               printf("%s\n", type);
+                               break;
+                       case 's':
+                               printf("%lu\n", size);
+                               break;
+                       }
+                       return 0;
+               }
+               buf = NULL;
+       } else {
+               buf = read_object_with_reference(sha1, argv[1], &size, NULL);
+       }
+
+       if (!buf)
+               die("git-cat-file %s: bad file", argv[2]);
+
+       while (size > 0) {
+               long ret = write(1, buf, size);
+               if (ret < 0) {
+                       if (errno == EAGAIN)
+                               continue;
+                       /* Ignore epipe */
+                       if (errno == EPIPE)
+                               break;
+                       die("git-cat-file: %s", strerror(errno));
+               } else if (!ret) {
+                       die("git-cat-file: disk full?");
+               }
+               size -= ret;
+               buf += ret;
+       }
+       return 0;
+}
diff --git a/checkout-cache.c b/checkout-cache.c
new file mode 100644 (file)
index 0000000..8d67403
--- /dev/null
@@ -0,0 +1,150 @@
+/*
+ * Check-out files from the "current cache directory"
+ *
+ * Copyright (C) 2005 Linus Torvalds
+ *
+ * Careful: order of argument flags does matter. For example,
+ *
+ *     git-checkout-cache -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-cache" does nothing. You probably
+ * meant "git-checkout-cache -a". And if you want to force it, you
+ * want "git-checkout-cache -f -a".
+ *
+ * Intuitiveness is not the goal here. Repeatability is. The
+ * reason for the "no arguments means no work" thing is that
+ * from scripts you are supposed to be able to do things like
+ *
+ *     find . -name '*.h' -print0 | xargs -0 git-checkout-cache -f --
+ *
+ * which will force all existing *.h files to be replaced with
+ * their cached copies. If an empty command line implied "all",
+ * then this would force-refresh everything in the cache, which
+ * was not the point.
+ *
+ * Oh, and the "--" is just a good idea when you know the rest
+ * will be filenames. Just so that you wouldn't have a filename
+ * of "-a" causing problems (not possible in the above example,
+ * but get used to it in scripting!).
+ */
+#include "cache.h"
+
+static struct checkout state = {
+       .base_dir = "",
+       .base_dir_len = 0,
+       .force = 0,
+       .quiet = 0,
+       .not_new = 0,
+       .refresh_cache = 0,
+};
+
+static int checkout_file(const char *name)
+{
+       int pos = cache_name_pos(name, strlen(name));
+       if (pos < 0) {
+               if (!state.quiet) {
+                       pos = -pos - 1;
+                       fprintf(stderr,
+                               "git-checkout-cache: %s is %s.\n",
+                               name,
+                               (pos < active_nr &&
+                                !strcmp(active_cache[pos]->name, name)) ?
+                               "unmerged" : "not in the cache");
+               }
+               return -1;
+       }
+       return checkout_entry(active_cache[pos], &state);
+}
+
+static int checkout_all(void)
+{
+       int i;
+
+       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;
+       }
+       return 0;
+}
+
+static const char checkout_cache_usage[] =
+"git-checkout-cache [-u] [-q] [-a] [-f] [-n] [--prefix=<string>] [--] <file>...";
+
+int main(int argc, char **argv)
+{
+       int i, force_filename = 0;
+       struct cache_file cache_file;
+       int newfd = -1;
+
+       if (read_cache() < 0) {
+               die("invalid cache");
+       }
+
+       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 (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;
+               }
+               checkout_file(arg);
+       }
+
+       if (0 <= newfd &&
+           (write_cache(newfd, active_cache, active_nr) ||
+            commit_index_file(&cache_file)))
+               die("Unable to write new cachefile");
+       return 0;
+}
diff --git a/clone-pack.c b/clone-pack.c
new file mode 100644 (file)
index 0000000..49820c6
--- /dev/null
@@ -0,0 +1,182 @@
+#include "cache.h"
+#include "refs.h"
+#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 *exec = "git-upload-pack";
+
+static void clone_handshake(int fd[2], struct ref *ref)
+{
+       unsigned char sha1[20];
+
+       while (ref) {
+               packet_write(fd[1], "want %s\n", sha1_to_hex(ref->old_sha1));
+               ref = ref->next;
+       }
+       packet_flush(fd[1]);
+
+       /* We don't have nuttin' */
+       packet_write(fd[1], "done\n");
+       if (get_ack(fd[0], sha1))
+               error("Huh! git-clone-pack got positive ack for %s", sha1_to_hex(sha1));
+}
+
+static int is_master(struct ref *ref)
+{
+       return !strcmp(ref->name, "refs/heads/master");
+}
+
+static void write_one_ref(struct ref *ref)
+{
+       char *path = git_path("%s", ref->name);
+       int fd;
+       char *hex;
+
+       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);
+       if (fd < 0)
+               die("unable to create ref %s", ref->name);
+       hex = sha1_to_hex(ref->old_sha1);
+       hex[40] = '\n';
+       if (write(fd, hex, 41) != 41)
+               die("unable to write ref %s", ref->name);
+       close(fd);
+}
+
+static void write_refs(struct ref *ref)
+{
+       struct ref *head = NULL, *head_ptr, *master_ref;
+       char *head_path;
+
+       if (!strcmp(ref->name, "HEAD")) {
+               head = ref;
+               ref = ref->next;
+       }
+       head_ptr = NULL;
+       master_ref = NULL;
+       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;
+               }
+               write_one_ref(ref);
+               ref = ref->next;
+       }
+       if (!head)
+               return;
+
+       head_path = git_path("HEAD");
+       if (!head_ptr) {
+               /*
+                * If we had a master ref, and it wasn't HEAD, we need to undo the
+                * symlink, and write a standalone HEAD. Give a warning, because that's
+                * really really wrong.
+                */
+               if (master_ref) {
+                       error("HEAD doesn't point to any refs! Making standalone HEAD");
+                       unlink(head_path);
+               }
+               write_one_ref(head);
+               return;
+       }
+
+       /* We reset to the master branch if it's available */
+       if (master_ref)
+               return;
+
+       /*
+        * 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)
+               die("unable to link HEAD to %s", head_ptr->name);
+}
+
+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);
+       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);
+               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);
+}
+
+int main(int argc, char **argv)
+{
+       int i, ret, nr_heads;
+       char *dest = NULL, **heads;
+       int fd[2];
+       pid_t pid;
+
+       nr_heads = 0;
+       heads = NULL;
+       for (i = 1; i < argc; i++) {
+               char *arg = argv[i];
+
+               if (*arg == '-') {
+                       if (!strcmp("-q", arg)) {
+                               quiet = 1;
+                               continue;
+                       }
+                       if (!strncmp("--exec=", arg, 7)) {
+                               exec = arg + 7;
+                               continue;
+                       }
+                       usage(clone_pack_usage);
+               }
+               dest = arg;
+               heads = argv + i + 1;
+               nr_heads = argc - i - 1;
+               break;
+       }
+       if (!dest)
+               usage(clone_pack_usage);
+       pid = git_connect(fd, dest, exec);
+       if (pid < 0)
+               return 1;
+       ret = clone_pack(fd, nr_heads, heads);
+       close(fd[0]);
+       close(fd[1]);
+       finish_connect(pid);
+       return ret;
+}
diff --git a/commit-tree.c b/commit-tree.c
new file mode 100644 (file)
index 0000000..b1ef0b5
--- /dev/null
@@ -0,0 +1,131 @@
+/*
+ * GIT - The information manager from hell
+ *
+ * Copyright (C) Linus Torvalds, 2005
+ */
+#include "cache.h"
+
+#include <pwd.h>
+#include <time.h>
+#include <ctype.h>
+
+#define BLOCKING (1ul << 14)
+
+/*
+ * FIXME! Share the code with "write-tree.c"
+ */
+static void init_buffer(char **bufp, unsigned int *sizep)
+{
+       char *buf = xmalloc(BLOCKING);
+       *sizep = 0;
+       *bufp = buf;
+}
+
+static void add_buffer(char **bufp, unsigned int *sizep, const char *fmt, ...)
+{
+       char one_line[2048];
+       va_list args;
+       int len;
+       unsigned long alloc, size, newsize;
+       char *buf;
+
+       va_start(args, fmt);
+       len = vsnprintf(one_line, sizeof(one_line), fmt, args);
+       va_end(args);
+       size = *sizep;
+       newsize = size + len;
+       alloc = (size + 32767) & ~32767;
+       buf = *bufp;
+       if (newsize > alloc) {
+               alloc = (newsize + 32767) & ~32767;
+               buf = xrealloc(buf, alloc);
+               *bufp = buf;
+       }
+       *sizep = newsize;
+       memcpy(buf + size, one_line, len);
+}
+
+static void check_valid(unsigned char *sha1, const char *expect)
+{
+       void *buf;
+       char type[20];
+       unsigned long size;
+
+       buf = read_sha1_file(sha1, type, &size);
+       if (!buf || strcmp(type, expect))
+               die("%s is not a valid '%s' object", sha1_to_hex(sha1), expect);
+       free(buf);
+}
+
+/*
+ * Having more than two parents is not strange at all, and this is
+ * how multi-way merges are represented.
+ */
+#define MAXPARENT (16)
+static unsigned char parent_sha1[MAXPARENT][20];
+
+static const char commit_tree_usage[] = "git-commit-tree <sha1> [-p <sha1>]* < changelog";
+
+static int new_parent(int idx)
+{
+       int i;
+       unsigned char *sha1 = parent_sha1[idx];
+       for (i = 0; i < idx; i++) {
+               if (!memcmp(parent_sha1[i], sha1, 20)) {
+                       error("duplicate parent %s ignored", sha1_to_hex(sha1));
+                       return 0;
+               }
+       }
+       return 1;
+}
+
+int main(int argc, char **argv)
+{
+       int i;
+       int parents = 0;
+       unsigned char tree_sha1[20];
+       unsigned char commit_sha1[20];
+       char comment[1000];
+       char *buffer;
+       unsigned int size;
+
+       if (argc < 2 || get_sha1_hex(argv[1], tree_sha1) < 0)
+               usage(commit_tree_usage);
+
+       check_valid(tree_sha1, "tree");
+       for (i = 2; i < argc; i += 2) {
+               char *a, *b;
+               a = argv[i]; b = argv[i+1];
+               if (!b || strcmp(a, "-p") || get_sha1(b, parent_sha1[parents]))
+                       usage(commit_tree_usage);
+               check_valid(parent_sha1[parents], "commit");
+               if (new_parent(parents))
+                       parents++;
+       }
+       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));
+
+       /*
+        * NOTE! This ordering means that the same exact tree merged with a
+        * different order of parents will be a _different_ changeset even
+        * if everything else stays the same.
+        */
+       for (i = 0; i < parents; i++)
+               add_buffer(&buffer, &size, "parent %s\n", sha1_to_hex(parent_sha1[i]));
+
+       /* Person/date information */
+       add_buffer(&buffer, &size, "author %s\n", git_author_info());
+       add_buffer(&buffer, &size, "committer %s\n\n", git_committer_info());
+
+       /* And add the comment */
+       while (fgets(comment, sizeof(comment), stdin) != NULL)
+               add_buffer(&buffer, &size, "%s", comment);
+
+       write_sha1_file(buffer, size, "commit", commit_sha1);
+       printf("%s\n", sha1_to_hex(commit_sha1));
+       return 0;
+}
diff --git a/commit.c b/commit.c
new file mode 100644 (file)
index 0000000..59e93d4
--- /dev/null
+++ b/commit.c
@@ -0,0 +1,590 @@
+#include <ctype.h>
+#include "tag.h"
+#include "commit.h"
+#include "cache.h"
+
+struct sort_node
+{
+       /*
+         * the number of children of the associated commit
+         * that also occur in the list being sorted.
+         */
+       unsigned int indegree;
+
+       /*
+         * reference to original list item that we will re-use
+         * on output.
+         */
+       struct commit_list * list_item;
+
+};
+
+const char *commit_type = "commit";
+
+enum cmit_fmt get_commit_format(const char *arg)
+{
+       if (!*arg)
+               return CMIT_FMT_DEFAULT;
+       if (!strcmp(arg, "=raw"))
+               return CMIT_FMT_RAW;
+       if (!strcmp(arg, "=medium"))
+               return CMIT_FMT_MEDIUM;
+       if (!strcmp(arg, "=short"))
+               return CMIT_FMT_SHORT;
+       if (!strcmp(arg, "=full"))
+               return CMIT_FMT_FULL;
+       if (!strcmp(arg, "=oneline"))
+               return CMIT_FMT_ONELINE;
+       die("invalid --pretty format");
+}
+
+static struct commit *check_commit(struct object *obj, const unsigned char *sha1)
+{
+       if (obj->type != commit_type) {
+               error("Object %s is a %s, not a commit", 
+                     sha1_to_hex(sha1), obj->type);
+               return NULL;
+       }
+       return (struct commit *) obj;
+}
+
+struct commit *lookup_commit_reference(const unsigned char *sha1)
+{
+       struct object *obj = deref_tag(parse_object(sha1));
+
+       if (!obj)
+               return NULL;
+       return check_commit(obj, sha1);
+}
+
+struct commit *lookup_commit(const unsigned char *sha1)
+{
+       struct object *obj = lookup_object(sha1);
+       if (!obj) {
+               struct commit *ret = xmalloc(sizeof(struct commit));
+               memset(ret, 0, sizeof(struct commit));
+               created_object(sha1, &ret->object);
+               ret->object.type = commit_type;
+               return ret;
+       }
+       if (!obj->type)
+               obj->type = commit_type;
+       return check_commit(obj, sha1);
+}
+
+static unsigned long parse_commit_date(const char *buf)
+{
+       unsigned long date;
+
+       if (memcmp(buf, "author", 6))
+               return 0;
+       while (*buf++ != '\n')
+               /* nada */;
+       if (memcmp(buf, "committer", 9))
+               return 0;
+       while (*buf++ != '>')
+               /* nada */;
+       date = strtoul(buf, NULL, 10);
+       if (date == ULONG_MAX)
+               date = 0;
+       return date;
+}
+
+static struct commit_graft {
+       unsigned char sha1[20];
+       int nr_parent;
+       unsigned char parent[0][20]; /* more */
+} **commit_graft;
+static int commit_graft_alloc, commit_graft_nr;
+
+static int commit_graft_pos(const unsigned char *sha1)
+{
+       int lo, hi;
+       lo = 0;
+       hi = commit_graft_nr;
+       while (lo < hi) {
+               int mi = (lo + hi) / 2;
+               struct commit_graft *graft = commit_graft[mi];
+               int cmp = memcmp(sha1, graft->sha1, 20);
+               if (!cmp)
+                       return mi;
+               if (cmp < 0)
+                       hi = mi;
+               else
+                       lo = mi + 1;
+       }
+       return -lo - 1;
+}
+
+static void prepare_commit_graft(void)
+{
+       char *graft_file = get_graft_file();
+       FILE *fp = fopen(graft_file, "r");
+       char buf[1024];
+       if (!fp) {
+               commit_graft = (struct commit_graft **) "hack";
+               return;
+       }
+       while (fgets(buf, sizeof(buf), fp)) {
+               /* The format is just "Commit Parent1 Parent2 ...\n" */
+               int len = strlen(buf);
+               int i;
+               struct commit_graft *graft = NULL;
+
+               if (buf[len-1] == '\n')
+                       buf[--len] = 0;
+               if (buf[0] == '#')
+                       continue;
+               if ((len + 1) % 41) {
+               bad_graft_data:
+                       error("bad graft data: %s", buf);
+                       free(graft);
+                       continue;
+               }
+               i = (len + 1) / 41 - 1;
+               graft = xmalloc(sizeof(*graft) + 20 * i);
+               graft->nr_parent = i;
+               if (get_sha1_hex(buf, graft->sha1))
+                       goto bad_graft_data;
+               for (i = 40; i < len; i += 41) {
+                       if (buf[i] != ' ')
+                               goto bad_graft_data;
+                       if (get_sha1_hex(buf + i + 1, graft->parent[i/41]))
+                               goto bad_graft_data;
+               }
+               i = commit_graft_pos(graft->sha1);
+               if (0 <= i) {
+                       error("duplicate graft data: %s", buf);
+                       free(graft);
+                       continue;
+               }
+               i = -i - 1;
+               if (commit_graft_alloc <= ++commit_graft_nr) {
+                       commit_graft_alloc = alloc_nr(commit_graft_alloc);
+                       commit_graft = xrealloc(commit_graft,
+                                               sizeof(*commit_graft) *
+                                               commit_graft_alloc);
+               }
+               if (i < commit_graft_nr)
+                       memmove(commit_graft + i + 1,
+                               commit_graft + i,
+                               (commit_graft_nr - i - 1) *
+                               sizeof(*commit_graft));
+               commit_graft[i] = graft;
+       }
+       fclose(fp);
+}
+
+static struct commit_graft *lookup_commit_graft(const unsigned char *sha1)
+{
+       int pos;
+       if (!commit_graft)
+               prepare_commit_graft();
+       pos = commit_graft_pos(sha1);
+       if (pos < 0)
+               return NULL;
+       return commit_graft[pos];
+}
+
+int parse_commit_buffer(struct commit *item, void *buffer, unsigned long size)
+{
+       char *bufptr = buffer;
+       unsigned char parent[20];
+       struct commit_list **pptr;
+       struct commit_graft *graft;
+
+       if (item->object.parsed)
+               return 0;
+       item->object.parsed = 1;
+       if (memcmp(bufptr, "tree ", 5))
+               return error("bogus commit object %s", sha1_to_hex(item->object.sha1));
+       if (get_sha1_hex(bufptr + 5, parent) < 0)
+               return error("bad tree pointer in commit %s\n", sha1_to_hex(item->object.sha1));
+       item->tree = lookup_tree(parent);
+       if (item->tree)
+               add_ref(&item->object, &item->tree->object);
+       bufptr += 46; /* "tree " + "hex sha1" + "\n" */
+       pptr = &item->parents;
+
+       graft = lookup_commit_graft(item->object.sha1);
+       while (!memcmp(bufptr, "parent ", 7)) {
+               struct commit *new_parent;
+
+               if (get_sha1_hex(bufptr + 7, parent) || bufptr[47] != '\n')
+                       return error("bad parents in commit %s", sha1_to_hex(item->object.sha1));
+               bufptr += 48;
+               if (graft)
+                       continue;
+               new_parent = lookup_commit(parent);
+               if (new_parent) {
+                       pptr = &commit_list_insert(new_parent, pptr)->next;
+                       add_ref(&item->object, &new_parent->object);
+               }
+       }
+       if (graft) {
+               int i;
+               struct commit *new_parent;
+               for (i = 0; i < graft->nr_parent; i++) {
+                       new_parent = lookup_commit(graft->parent[i]);
+                       if (!new_parent)
+                               continue;
+                       pptr = &commit_list_insert(new_parent, pptr)->next;
+                       add_ref(&item->object, &new_parent->object);
+               }
+       }
+       item->date = parse_commit_date(bufptr);
+       return 0;
+}
+
+int parse_commit(struct commit *item)
+{
+       char type[20];
+       void *buffer;
+       unsigned long size;
+       int ret;
+
+       if (item->object.parsed)
+               return 0;
+       buffer = read_sha1_file(item->object.sha1, type, &size);
+       if (!buffer)
+               return error("Could not read %s",
+                            sha1_to_hex(item->object.sha1));
+       if (strcmp(type, commit_type)) {
+               free(buffer);
+               return error("Object %s not a commit",
+                            sha1_to_hex(item->object.sha1));
+       }
+       ret = parse_commit_buffer(item, buffer, size);
+       if (!ret) {
+               item->buffer = buffer;
+               return 0;
+       }
+       free(buffer);
+       return ret;
+}
+
+struct commit_list *commit_list_insert(struct commit *item, struct commit_list **list_p)
+{
+       struct commit_list *new_list = xmalloc(sizeof(struct commit_list));
+       new_list->item = item;
+       new_list->next = *list_p;
+       *list_p = new_list;
+       return new_list;
+}
+
+void free_commit_list(struct commit_list *list)
+{
+       while (list) {
+               struct commit_list *temp = list;
+               list = temp->next;
+               free(temp);
+       }
+}
+
+struct commit_list * insert_by_date(struct commit *item, struct commit_list **list)
+{
+       struct commit_list **pp = list;
+       struct commit_list *p;
+       while ((p = *pp) != NULL) {
+               if (p->item->date < item->date) {
+                       break;
+               }
+               pp = &p->next;
+       }
+       return commit_list_insert(item, pp);
+}
+
+       
+void sort_by_date(struct commit_list **list)
+{
+       struct commit_list *ret = NULL;
+       while (*list) {
+               insert_by_date((*list)->item, &ret);
+               *list = (*list)->next;
+       }
+       *list = ret;
+}
+
+struct commit *pop_most_recent_commit(struct commit_list **list,
+                                     unsigned int mark)
+{
+       struct commit *ret = (*list)->item;
+       struct commit_list *parents = ret->parents;
+       struct commit_list *old = *list;
+
+       *list = (*list)->next;
+       free(old);
+
+       while (parents) {
+               struct commit *commit = parents->item;
+               parse_commit(commit);
+               if (!(commit->object.flags & mark)) {
+                       commit->object.flags |= mark;
+                       insert_by_date(commit, list);
+               }
+               parents = parents->next;
+       }
+       return ret;
+}
+
+/*
+ * Generic support for pretty-printing the header
+ */
+static int get_one_line(const char *msg, unsigned long len)
+{
+       int ret = 0;
+
+       while (len--) {
+               char c = *msg++;
+               ret++;
+               if (c == '\n')
+                       break;
+               if (!c)
+                       return 0;
+       }
+       return ret;
+}
+
+static int add_user_info(const char *what, enum cmit_fmt fmt, char *buf, const char *line)
+{
+       char *date;
+       unsigned int namelen;
+       unsigned long time;
+       int tz, ret;
+
+       if (fmt == CMIT_FMT_ONELINE)
+               return 0;
+       date = strchr(line, '>');
+       if (!date)
+               return 0;
+       namelen = ++date - line;
+       time = strtoul(date, &date, 10);
+       tz = strtol(date, NULL, 10);
+
+       ret = sprintf(buf, "%s: %.*s\n", what, namelen, line);
+       if (fmt == CMIT_FMT_MEDIUM)
+               ret += sprintf(buf + ret, "Date:   %s\n", show_date(time, tz));
+       return ret;
+}
+
+static int is_empty_line(const char *line, int len)
+{
+       while (len && isspace(line[len-1]))
+               len--;
+       return !len;
+}
+
+static int add_parent_info(enum cmit_fmt fmt, char *buf, const char *line, int parents)
+{
+       int offset = 0;
+
+       if (fmt == CMIT_FMT_ONELINE)
+               return offset;
+       switch (parents) {
+       case 1:
+               break;
+       case 2:
+               /* Go back to the previous line: 40 characters of previous parent, and one '\n' */
+               offset = sprintf(buf, "Merge: %.40s\n", line-41);
+               /* Fallthrough */
+       default:
+               /* Replace the previous '\n' with a space */
+               buf[offset-1] = ' ';
+               offset += sprintf(buf + offset, "%.40s\n", line+7);
+       }
+       return offset;
+}
+
+unsigned long pretty_print_commit(enum cmit_fmt fmt, const char *msg, unsigned long len, char *buf, unsigned long space)
+{
+       int hdr = 1, body = 0;
+       unsigned long offset = 0;
+       int parents = 0;
+       int indent = (fmt == CMIT_FMT_ONELINE) ? 0 : 4;
+
+       for (;;) {
+               const char *line = msg;
+               int linelen = get_one_line(msg, len);
+
+               if (!linelen)
+                       break;
+
+               /*
+                * We want some slop for indentation and a possible
+                * final "...". Thus the "+ 20".
+                */
+               if (offset + linelen + 20 > space) {
+                       memcpy(buf + offset, "    ...\n", 8);
+                       offset += 8;
+                       break;
+               }
+
+               msg += linelen;
+               len -= linelen;
+               if (hdr) {
+                       if (linelen == 1) {
+                               hdr = 0;
+                               if (fmt != CMIT_FMT_ONELINE)
+                                       buf[offset++] = '\n';
+                               continue;
+                       }
+                       if (fmt == CMIT_FMT_RAW) {
+                               memcpy(buf + offset, line, linelen);
+                               offset += linelen;
+                               continue;
+                       }
+                       if (!memcmp(line, "parent ", 7)) {
+                               if (linelen != 48)
+                                       die("bad parent line in commit");
+                               offset += add_parent_info(fmt, buf + offset, line, ++parents);
+                       }
+                       if (!memcmp(line, "author ", 7))
+                               offset += add_user_info("Author", fmt, buf + offset, line + 7);
+                       if (fmt == CMIT_FMT_FULL) {
+                               if (!memcmp(line, "committer ", 10))
+                                       offset += add_user_info("Commit", fmt, buf + offset, line + 10);
+                       }
+                       continue;
+               }
+
+               if (is_empty_line(line, linelen)) {
+                       if (!body)
+                               continue;
+                       if (fmt == CMIT_FMT_SHORT)
+                               break;
+               } else {
+                       body = 1;
+               }
+
+               memset(buf + offset, ' ', indent);
+               memcpy(buf + offset + indent, line, linelen);
+               offset += linelen + indent;
+               if (fmt == CMIT_FMT_ONELINE)
+                       break;
+       }
+       if (fmt == CMIT_FMT_ONELINE) {
+               /* We do not want the terminating newline */
+               if (buf[offset - 1] == '\n')
+                       offset--;
+       }
+       else {
+               /* Make sure there is an EOLN */
+               if (buf[offset - 1] != '\n')
+                       buf[offset++] = '\n';
+       }
+       buf[offset] = '\0';
+       return offset;
+}
+
+struct commit *pop_commit(struct commit_list **stack)
+{
+       struct commit_list *top = *stack;
+       struct commit *item = top ? top->item : NULL;
+
+       if (top) {
+               *stack = top->next;
+               free(top);
+       }
+       return item;
+}
+
+int count_parents(struct commit * commit)
+{
+        int count = 0;
+        struct commit_list * parents = commit->parents;
+        for (count=0;parents; parents=parents->next,count++)
+          ;
+        return count;
+}
+
+/*
+ * Performs an in-place topological sort on the list supplied.
+ */
+void sort_in_topological_order(struct commit_list ** list)
+{
+       struct commit_list * next = *list;
+       struct commit_list * work = NULL;
+       struct commit_list ** pptr = list;
+       struct sort_node * nodes;
+       struct sort_node * next_nodes;
+       int count = 0;
+
+       /* determine the size of the list */
+       while (next) {
+               next = next->next;
+               count++;
+       }
+       /* allocate an array to help sort the list */
+       nodes = xcalloc(count, sizeof(*nodes));
+       /* link the list to the array */
+       next_nodes = nodes;
+       next=*list;
+       while (next) {
+               next_nodes->list_item = next;
+               next->item->object.util = next_nodes;
+               next_nodes++;
+               next = next->next;
+       }
+       /* update the indegree */
+       next=*list;
+       while (next) {
+               struct commit_list * parents = next->item->parents;
+               while (parents) {
+                       struct commit * parent=parents->item;
+                       struct sort_node * pn = (struct sort_node *)parent->object.util;
+                       
+                       if (pn)
+                               pn->indegree++;
+                       parents=parents->next;
+               }
+               next=next->next;
+       }
+       /* 
+         * find the tips
+         *
+         * tips are nodes not reachable from any other node in the list 
+         * 
+         * the tips serve as a starting set for the work queue.
+         */
+       next=*list;
+       while (next) {
+               struct sort_node * node = (struct sort_node *)next->item->object.util;
+
+               if (node->indegree == 0) {
+                       commit_list_insert(next->item, &work);
+               }
+               next=next->next;
+       }
+       /* process the list in topological order */
+       while (work) {
+               struct commit * work_item = pop_commit(&work);
+               struct sort_node * work_node = (struct sort_node *)work_item->object.util;
+               struct commit_list * parents = work_item->parents;
+
+               while (parents) {
+                       struct commit * parent=parents->item;
+                       struct sort_node * pn = (struct sort_node *)parent->object.util;
+                       
+                       if (pn) {
+                               /* 
+                                * parents are only enqueued for emission 
+                                 * when all their children have been emitted thereby
+                                 * guaranteeing topological order.
+                                 */
+                               pn->indegree--;
+                               if (!pn->indegree) 
+                                       commit_list_insert(parent, &work);
+                       }
+                       parents=parents->next;
+               }
+               /*
+                 * work_item is a commit all of whose children
+                 * have already been emitted. we can emit it now.
+                 */
+               *pptr = work_node->list_item;
+               pptr = &(*pptr)->next;
+               *pptr = NULL;
+               work_item->object.util = NULL;
+       }
+       free(nodes);
+}
diff --git a/commit.h b/commit.h
new file mode 100644 (file)
index 0000000..5d179d4
--- /dev/null
+++ b/commit.h
@@ -0,0 +1,71 @@
+#ifndef COMMIT_H
+#define COMMIT_H
+
+#include "object.h"
+#include "tree.h"
+
+struct commit_list {
+       struct commit *item;
+       struct commit_list *next;
+};
+
+struct commit {
+       struct object object;
+       unsigned long date;
+       struct commit_list *parents;
+       struct tree *tree;
+       char *buffer;
+};
+
+extern const char *commit_type;
+
+struct commit *lookup_commit(const unsigned char *sha1);
+struct commit *lookup_commit_reference(const unsigned char *sha1);
+
+int parse_commit_buffer(struct commit *item, void *buffer, unsigned long size);
+
+int parse_commit(struct commit *item);
+
+struct commit_list * commit_list_insert(struct commit *item, struct commit_list **list_p);
+struct commit_list * insert_by_date(struct commit *item, struct commit_list **list);
+
+void free_commit_list(struct commit_list *list);
+
+void sort_by_date(struct commit_list **list);
+
+/* Commit formats */
+enum cmit_fmt {
+       CMIT_FMT_RAW,
+       CMIT_FMT_MEDIUM,
+       CMIT_FMT_DEFAULT = CMIT_FMT_MEDIUM,
+       CMIT_FMT_SHORT,
+       CMIT_FMT_FULL,
+       CMIT_FMT_ONELINE,
+};
+
+extern enum cmit_fmt get_commit_format(const char *arg);
+extern unsigned long pretty_print_commit(enum cmit_fmt fmt, const char *msg, unsigned long len, char *buf, unsigned long space);
+
+/** Removes the first commit from a list sorted by date, and adds all
+ * of its parents.
+ **/
+struct commit *pop_most_recent_commit(struct commit_list **list, 
+                                     unsigned int mark);
+
+struct commit *pop_commit(struct commit_list **stack);
+
+int count_parents(struct commit * commit);
+
+/*
+ * Performs an in-place topological sort of list supplied.
+ *
+ * Pre-conditions:
+ *   all commits in input list and all parents of those
+ *   commits must have object.util == NULL
+ *        
+ * Post-conditions: 
+ *   invariant of resulting list is:
+ *      a reachable from b => ord(b) < ord(a)
+ */
+void sort_in_topological_order(struct commit_list ** list);
+#endif /* COMMIT_H */
diff --git a/connect.c b/connect.c
new file mode 100644 (file)
index 0000000..a6657b1
--- /dev/null
+++ b/connect.c
@@ -0,0 +1,417 @@
+#include "cache.h"
+#include "pkt-line.h"
+#include "quote.h"
+#include <sys/wait.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+
+/*
+ * Read all the refs from the other end
+ */
+struct ref **get_remote_heads(int in, struct ref **list, int nr_match, char **match)
+{
+       *list = NULL;
+       for (;;) {
+               struct ref *ref;
+               unsigned char old_sha1[20];
+               static char buffer[1000];
+               char *name;
+               int len;
+
+               len = packet_read_line(in, buffer, sizeof(buffer));
+               if (!len)
+                       break;
+               if (buffer[len-1] == '\n')
+                       buffer[--len] = 0;
+
+               if (len < 42 || get_sha1_hex(buffer, old_sha1) || buffer[40] != ' ')
+                       die("protocol error: expected sha/ref, got '%s'", buffer);
+               name = buffer + 41;
+               if (nr_match && !path_match(name, nr_match, match))
+                       continue;
+               ref = xcalloc(1, sizeof(*ref) + len - 40);
+               memcpy(ref->old_sha1, old_sha1, 20);
+               memcpy(ref->name, buffer + 41, len - 40);
+               *list = ref;
+               list = &ref->next;
+       }
+       return list;
+}
+
+int get_ack(int fd, unsigned char *result_sha1)
+{
+       static char line[1000];
+       int len = packet_read_line(fd, line, sizeof(line));
+
+       if (!len)
+               die("git-fetch-pack: expected ACK/NAK, got EOF");
+       if (line[len-1] == '\n')
+               line[--len] = 0;
+       if (!strcmp(line, "NAK"))
+               return 0;
+       if (!strncmp(line, "ACK ", 3)) {
+               if (!get_sha1_hex(line+4, result_sha1))
+                       return 1;
+       }
+       die("git-fetch_pack: expected ACK/NAK, got '%s'", line);
+}
+
+int path_match(const char *path, int nr, char **match)
+{
+       int i;
+       int pathlen = strlen(path);
+
+       for (i = 0; i < nr; i++) {
+               char *s = match[i];
+               int len = strlen(s);
+
+               if (!len || len > pathlen)
+                       continue;
+               if (memcmp(path + pathlen - len, s, len))
+                       continue;
+               if (pathlen > len && path[pathlen - len - 1] != '/')
+                       continue;
+               *s = 0;
+               return 1;
+       }
+       return 0;
+}
+
+struct refspec {
+       char *src;
+       char *dst;
+};
+
+static struct refspec *parse_ref_spec(int nr_refspec, char **refspec)
+{
+       int i;
+       struct refspec *rs = xmalloc(sizeof(*rs) * (nr_refspec + 1));
+       for (i = 0; i < nr_refspec; i++) {
+               char *sp, *dp, *ep;
+               sp = refspec[i];
+               ep = strchr(sp, ':');
+               if (ep) {
+                       dp = ep + 1;
+                       *ep = 0;
+               }
+               else
+                       dp = sp;
+               rs[i].src = sp;
+               rs[i].dst = dp;
+       }
+       rs[nr_refspec].src = rs[nr_refspec].dst = NULL;
+       return rs;
+}
+
+static int count_refspec_match(const char *pattern,
+                              struct ref *refs,
+                              struct ref **matched_ref)
+{
+       int match;
+       int patlen = strlen(pattern);
+
+       for (match = 0; refs; refs = refs->next) {
+               char *name = refs->name;
+               int namelen = strlen(name);
+               if (namelen < patlen ||
+                   memcmp(name + namelen - patlen, pattern, patlen))
+                       continue;
+               if (namelen != patlen && name[namelen - patlen - 1] != '/')
+                       continue;
+               match++;
+               *matched_ref = refs;
+       }
+       return match;
+}
+
+static void link_dst_tail(struct ref *ref, struct ref ***tail)
+{
+       **tail = ref;
+       *tail = &ref->next;
+       **tail = NULL;
+}
+
+static struct ref *try_explicit_object_name(const char *name)
+{
+       unsigned char sha1[20];
+       struct ref *ref;
+       int len;
+       if (get_sha1(name, sha1))
+               return NULL;
+       len = strlen(name) + 1;
+       ref = xcalloc(1, sizeof(*ref) + len);
+       memcpy(ref->name, name, len);
+       memcpy(ref->new_sha1, sha1, 20);
+       return ref;
+}
+
+static int match_explicit_refs(struct ref *src, struct ref *dst,
+                              struct ref ***dst_tail, struct refspec *rs)
+{
+       int i, errs;
+       for (i = errs = 0; rs[i].src; i++) {
+               struct ref *matched_src, *matched_dst;
+
+               matched_src = matched_dst = NULL;
+               switch (count_refspec_match(rs[i].src, src, &matched_src)) {
+               case 1:
+                       break;
+               case 0:
+                       /* The source could be in the get_sha1() format
+                        * not a reference name.
+                        */
+                       matched_src = try_explicit_object_name(rs[i].src);
+                       if (matched_src)
+                               break;
+                       errs = 1;
+                       error("src refspec %s does not match any.",
+                             rs[i].src);
+                       break;
+               default:
+                       errs = 1;
+                       error("src refspec %s matches more than one.",
+                             rs[i].src);
+                       break;
+               }
+               switch (count_refspec_match(rs[i].dst, dst, &matched_dst)) {
+               case 1:
+                       break;
+               case 0:
+                       if (!memcmp(rs[i].dst, "refs/", 5)) {
+                               int len = strlen(rs[i].dst) + 1;
+                               matched_dst = xcalloc(1, sizeof(*dst) + len);
+                               memcpy(matched_dst->name, rs[i].dst, len);
+                               link_dst_tail(matched_dst, dst_tail);
+                       }
+                       else if (!strcmp(rs[i].src, rs[i].dst) &&
+                                matched_src) {
+                               /* pushing "master:master" when
+                                * remote does not have master yet.
+                                */
+                               int len = strlen(matched_src->name) + 1;
+                               matched_dst = xcalloc(1, sizeof(*dst) + len);
+                               memcpy(matched_dst->name, matched_src->name,
+                                      len);
+                               link_dst_tail(matched_dst, dst_tail);
+                       }
+                       else {
+                               errs = 1;
+                               error("dst refspec %s does not match any "
+                                     "existing ref on the remote and does "
+                                     "not start with refs/.", rs[i].dst);
+                       }
+                       break;
+               default:
+                       errs = 1;
+                       error("dst refspec %s matches more than one.",
+                             rs[i].dst);
+                       break;
+               }
+               if (errs)
+                       continue;
+               if (matched_dst->peer_ref) {
+                       errs = 1;
+                       error("dst ref %s receives from more than one src.",
+                             matched_dst->name);
+               }
+               else
+                       matched_dst->peer_ref = matched_src;
+       }
+       return -errs;
+}
+
+static struct ref *find_ref_by_name(struct ref *list, const char *name)
+{
+       for ( ; list; list = list->next)
+               if (!strcmp(list->name, name))
+                       return list;
+       return NULL;
+}
+
+int match_refs(struct ref *src, struct ref *dst, struct ref ***dst_tail,
+              int nr_refspec, char **refspec, int all)
+{
+       struct refspec *rs = parse_ref_spec(nr_refspec, refspec);
+
+       if (nr_refspec)
+               return match_explicit_refs(src, dst, dst_tail, rs);
+
+       /* pick the remainder */
+       for ( ; src; src = src->next) {
+               struct ref *dst_peer;
+               if (src->peer_ref)
+                       continue;
+               dst_peer = find_ref_by_name(dst, src->name);
+               if ((dst_peer && dst_peer->peer_ref) || (!dst_peer && !all))
+                       continue;
+               if (!dst_peer) {
+                       /* Create a new one and link it */
+                       int len = strlen(src->name) + 1;
+                       dst_peer = xcalloc(1, sizeof(*dst_peer) + len);
+                       memcpy(dst_peer->name, src->name, len);
+                       memcpy(dst_peer->new_sha1, src->new_sha1, 20);
+                       link_dst_tail(dst_peer, dst_tail);
+               }
+               dst_peer->peer_ref = src;
+       }
+       return 0;
+}
+
+enum protocol {
+       PROTO_LOCAL = 1,
+       PROTO_SSH,
+       PROTO_GIT,
+};
+
+static enum protocol get_protocol(const char *name)
+{
+       if (!strcmp(name, "ssh"))
+               return PROTO_SSH;
+       if (!strcmp(name, "git"))
+               return PROTO_GIT;
+       die("I don't handle protocol '%s'", name);
+}
+
+#define STR_(s)        # s
+#define STR(s) STR_(s)
+
+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);
+       struct addrinfo hints, *ai0, *ai;
+       int gai;
+
+       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;
+       }
+
+       memset(&hints, 0, sizeof(hints));
+       hints.ai_socktype = SOCK_STREAM;
+       hints.ai_protocol = IPPROTO_TCP;
+
+       gai = getaddrinfo(host, port, &hints, &ai);
+       if (gai)
+               die("Unable to look up %s (%s)", host, gai_strerror(gai));
+
+       for (ai0 = ai; ai; ai = ai->ai_next) {
+               sockfd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
+               if (sockfd < 0)
+                       continue;
+               if (connect(sockfd, ai->ai_addr, ai->ai_addrlen) < 0) {
+                       close(sockfd);
+                       sockfd = -1;
+                       continue;
+               }
+               break;
+       }
+
+       freeaddrinfo(ai0);
+
+       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;
+}
+
+/*
+ * Yeah, yeah, fixme. Need to pass in the heads etc.
+ */
+int git_connect(int fd[2], char *url, const char *prog)
+{
+       char command[1024];
+       char *host, *path;
+       char *colon;
+       int pipefd[2][2];
+       pid_t pid;
+       enum protocol protocol;
+
+       host = NULL;
+       path = url;
+       colon = strchr(url, ':');
+       protocol = PROTO_LOCAL;
+       if (colon) {
+               *colon = 0;
+               host = url;
+               path = colon+1;
+               protocol = PROTO_SSH;
+               if (!memcmp(path, "//", 2)) {
+                       char *slash = strchr(path + 2, '/');
+                       if (slash) {
+                               int nr = slash - path - 2;
+                               memmove(path, path+2, nr);
+                               path[nr] = 0;
+                               protocol = get_protocol(url);
+                               host = path;
+                               path = slash;
+                       }
+               }
+       }
+
+       if (protocol == PROTO_GIT)
+               return git_tcp_connect(fd, prog, host, path);
+
+       if (pipe(pipefd[0]) < 0 || pipe(pipefd[1]) < 0)
+               die("unable to create pipe pair for communication");
+       pid = fork();
+       if (!pid) {
+               snprintf(command, sizeof(command), "%s %s", prog,
+                        sq_quote(path));
+               dup2(pipefd[1][0], 0);
+               dup2(pipefd[0][1], 1);
+               close(pipefd[0][0]);
+               close(pipefd[0][1]);
+               close(pipefd[1][0]);
+               close(pipefd[1][1]);
+               if (protocol == PROTO_SSH) {
+                       const char *ssh = getenv("GIT_SSH") ? : "ssh";
+                       const char *ssh_basename = strrchr(ssh, '/');
+                       if (!ssh_basename)
+                               ssh_basename = ssh;
+                       else
+                               ssh_basename++;
+                       execlp(ssh, ssh_basename, host, command, NULL);
+               }
+               else
+                       execlp("sh", "sh", "-c", command, NULL);
+               die("exec failed");
+       }               
+       fd[0] = pipefd[0][0];
+       fd[1] = pipefd[1][1];
+       close(pipefd[0][1]);
+       close(pipefd[1][0]);
+       return pid;
+}
+
+int finish_connect(pid_t pid)
+{
+       int ret;
+
+       for (;;) {
+               ret = waitpid(pid, NULL, 0);
+               if (!ret)
+                       break;
+               if (errno != EINTR)
+                       break;
+       }
+       return ret;
+}
diff --git a/convert-cache.c b/convert-cache.c
new file mode 100644 (file)
index 0000000..95f0302
--- /dev/null
@@ -0,0 +1,326 @@
+#define _XOPEN_SOURCE /* glibc2 needs this */
+#include <time.h>
+#include <ctype.h>
+#include "cache.h"
+
+struct entry {
+       unsigned char old_sha1[20];
+       unsigned char new_sha1[20];
+       int converted;
+};
+
+#define MAXOBJECTS (1000000)
+
+static struct entry *convert[MAXOBJECTS];
+static int nr_convert;
+
+static struct entry * convert_entry(unsigned char *sha1);
+
+static struct entry *insert_new(unsigned char *sha1, int pos)
+{
+       struct entry *new = xmalloc(sizeof(struct entry));
+       memset(new, 0, sizeof(*new));
+       memcpy(new->old_sha1, sha1, 20);
+       memmove(convert + pos + 1, convert + pos, (nr_convert - pos) * sizeof(struct entry *));
+       convert[pos] = new;
+       nr_convert++;
+       if (nr_convert == MAXOBJECTS)
+               die("you're kidding me - hit maximum object limit");
+       return new;
+}
+
+static struct entry *lookup_entry(unsigned char *sha1)
+{
+       int low = 0, high = nr_convert;
+
+       while (low < high) {
+               int next = (low + high) / 2;
+               struct entry *n = convert[next];
+               int cmp = memcmp(sha1, n->old_sha1, 20);
+               if (!cmp)
+                       return n;
+               if (cmp < 0) {
+                       high = next;
+                       continue;
+               }
+               low = next+1;
+       }
+       return insert_new(sha1, low);
+}
+
+static void convert_binary_sha1(void *buffer)
+{
+       struct entry *entry = convert_entry(buffer);
+       memcpy(buffer, entry->new_sha1, 20);
+}
+
+static void convert_ascii_sha1(void *buffer)
+{
+       unsigned char sha1[20];
+       struct entry *entry;
+
+       if (get_sha1_hex(buffer, sha1))
+               die("expected sha1, got '%s'", (char*) buffer);
+       entry = convert_entry(sha1);
+       memcpy(buffer, sha1_to_hex(entry->new_sha1), 40);
+}
+
+static unsigned int convert_mode(unsigned int mode)
+{
+       unsigned int newmode;
+
+       newmode = mode & S_IFMT;
+       if (S_ISREG(mode))
+               newmode |= (mode & 0100) ? 0755 : 0644;
+       return newmode;
+}
+
+static int write_subdirectory(void *buffer, unsigned long size, const char *base, int baselen, unsigned char *result_sha1)
+{
+       char *new = xmalloc(size);
+       unsigned long newlen = 0;
+       unsigned long used;
+
+       used = 0;
+       while (size) {
+               int len = 21 + strlen(buffer);
+               char *path = strchr(buffer, ' ');
+               unsigned char *sha1;
+               unsigned int mode;
+               char *slash, *origpath;
+
+               if (!path || sscanf(buffer, "%o", &mode) != 1)
+                       die("bad tree conversion");
+               mode = convert_mode(mode);
+               path++;
+               if (memcmp(path, base, baselen))
+                       break;
+               origpath = path;
+               path += baselen;
+               slash = strchr(path, '/');
+               if (!slash) {
+                       newlen += sprintf(new + newlen, "%o %s", mode, path);
+                       new[newlen++] = '\0';
+                       memcpy(new + newlen, buffer + len - 20, 20);
+                       newlen += 20;
+
+                       used += len;
+                       size -= len;
+                       buffer += len;
+                       continue;
+               }
+
+               newlen += sprintf(new + newlen, "%o %.*s", S_IFDIR, (int)(slash - path), path);
+               new[newlen++] = 0;
+               sha1 = (unsigned char *)(new + newlen);
+               newlen += 20;
+
+               len = write_subdirectory(buffer, size, origpath, slash-origpath+1, sha1);
+
+               used += len;
+               size -= len;
+               buffer += len;
+       }
+
+       write_sha1_file(new, newlen, "tree", result_sha1);
+       free(new);
+       return used;
+}
+
+static void convert_tree(void *buffer, unsigned long size, unsigned char *result_sha1)
+{
+       void *orig_buffer = buffer;
+       unsigned long orig_size = size;
+
+       while (size) {
+               int len = 1+strlen(buffer);
+
+               convert_binary_sha1(buffer + len);
+
+               len += 20;
+               if (len > size)
+                       die("corrupt tree object");
+               size -= len;
+               buffer += len;
+       }
+
+       write_subdirectory(orig_buffer, orig_size, "", 0, result_sha1);
+}
+
+static unsigned long parse_oldstyle_date(const char *buf)
+{
+       char c, *p;
+       char buffer[100];
+       struct tm tm;
+       const char *formats[] = {
+               "%c",
+               "%a %b %d %T",
+               "%Z",
+               "%Y",
+               " %Y",
+               NULL
+       };
+       /* We only ever did two timezones in the bad old format .. */
+       const char *timezones[] = {
+               "PDT", "PST", "CEST", NULL
+       };
+       const char **fmt = formats;
+
+       p = buffer;
+       while (isspace(c = *buf))
+               buf++;
+       while ((c = *buf++) != '\n')
+               *p++ = c;
+       *p++ = 0;
+       buf = buffer;
+       memset(&tm, 0, sizeof(tm));
+       do {
+               const char *next = strptime(buf, *fmt, &tm);
+               if (next) {
+                       if (!*next)
+                               return mktime(&tm);
+                       buf = next;
+               } else {
+                       const char **p = timezones;
+                       while (isspace(*buf))
+                               buf++;
+                       while (*p) {
+                               if (!memcmp(buf, *p, strlen(*p))) {
+                                       buf += strlen(*p);
+                                       break;
+                               }
+                               p++;
+                       }
+               }
+               fmt++;
+       } while (*buf && *fmt);
+       printf("left: %s\n", buf);
+       return mktime(&tm);                             
+}
+
+static int convert_date_line(char *dst, void **buf, unsigned long *sp)
+{
+       unsigned long size = *sp;
+       char *line = *buf;
+       char *next = strchr(line, '\n');
+       char *date = strchr(line, '>');
+       int len;
+
+       if (!next || !date)
+               die("missing or bad author/committer line %s", line);
+       next++; date += 2;
+
+       *buf = next;
+       *sp = size - (next - line);
+
+       len = date - line;
+       memcpy(dst, line, len);
+       dst += len;
+
+       /* Is it already in new format? */
+       if (isdigit(*date)) {
+               int datelen = next - date;
+               memcpy(dst, date, datelen);
+               return len + datelen;
+       }
+
+       /*
+        * Hacky hacky: one of the sparse old-style commits does not have
+        * any date at all, but we can fake it by using the committer date.
+        */
+       if (*date == '\n' && strchr(next, '>'))
+               date = strchr(next, '>')+2;
+
+       return len + sprintf(dst, "%lu -0700\n", parse_oldstyle_date(date));
+}
+
+static void convert_date(void *buffer, unsigned long size, unsigned char *result_sha1)
+{
+       char *new = xmalloc(size + 100);
+       unsigned long newlen = 0;
+       
+       // "tree <sha1>\n"
+       memcpy(new + newlen, buffer, 46);
+       newlen += 46;
+       buffer += 46;
+       size -= 46;
+
+       // "parent <sha1>\n"
+       while (!memcmp(buffer, "parent ", 7)) {
+               memcpy(new + newlen, buffer, 48);
+               newlen += 48;
+               buffer += 48;
+               size -= 48;
+       }
+
+       // "author xyz <xyz> date"
+       newlen += convert_date_line(new + newlen, &buffer, &size);
+       // "committer xyz <xyz> date"
+       newlen += convert_date_line(new + newlen, &buffer, &size);
+
+       // Rest
+       memcpy(new + newlen, buffer, size);
+       newlen += size;
+
+       write_sha1_file(new, newlen, "commit", result_sha1);
+       free(new);      
+}
+
+static void convert_commit(void *buffer, unsigned long size, unsigned char *result_sha1)
+{
+       void *orig_buffer = buffer;
+       unsigned long orig_size = size;
+
+       if (memcmp(buffer, "tree ", 5))
+               die("Bad commit '%s'", (char*) buffer);
+       convert_ascii_sha1(buffer+5);
+       buffer += 46;    /* "tree " + "hex sha1" + "\n" */
+       while (!memcmp(buffer, "parent ", 7)) {
+               convert_ascii_sha1(buffer+7);
+               buffer += 48;
+       }
+       convert_date(orig_buffer, orig_size, result_sha1);
+}
+
+static struct entry * convert_entry(unsigned char *sha1)
+{
+       struct entry *entry = lookup_entry(sha1);
+       char type[20];
+       void *buffer, *data;
+       unsigned long size;
+
+       if (entry->converted)
+               return entry;
+       data = read_sha1_file(sha1, type, &size);
+       if (!data)
+               die("unable to read object %s", sha1_to_hex(sha1));
+
+       buffer = xmalloc(size);
+       memcpy(buffer, data, size);
+       
+       if (!strcmp(type, "blob")) {
+               write_sha1_file(buffer, size, "blob", entry->new_sha1);
+       } else if (!strcmp(type, "tree"))
+               convert_tree(buffer, size, entry->new_sha1);
+       else if (!strcmp(type, "commit"))
+               convert_commit(buffer, size, entry->new_sha1);
+       else
+               die("unknown object type '%s' in %s", type, sha1_to_hex(sha1));
+       entry->converted = 1;
+       free(buffer);
+       free(data);
+       return entry;
+}
+
+int main(int argc, char **argv)
+{
+       unsigned char sha1[20];
+       struct entry *entry;
+
+       if (argc != 2 || get_sha1(argv[1], sha1))
+               usage("git-convert-cache <sha1>");
+
+       entry = convert_entry(sha1);
+       printf("new sha1: %s\n", sha1_to_hex(entry->new_sha1));
+       return 0;
+}
diff --git a/count-delta.c b/count-delta.c
new file mode 100644 (file)
index 0000000..7559ff6
--- /dev/null
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2005 Junio C Hamano
+ * The delta-parsing part is almost straight copy of patch-delta.c
+ * which is (C) 2005 Nicolas Pitre <nico@cam.org>.
+ */
+#include <stdlib.h>
+#include <string.h>
+#include <limits.h>
+#include "delta.h"
+#include "count-delta.h"
+
+/*
+ * NOTE.  We do not _interpret_ delta fully.  As an approximation, we
+ * just count the number of bytes that are copied from the source, and
+ * the number of literal data bytes that are inserted.
+ *
+ * Number of bytes that are _not_ copied from the source is deletion,
+ * and number of inserted literal bytes are addition, so sum of them
+ * is the extent of damage.  xdelta can express an edit that copies
+ * data inside of the destination which originally came from the
+ * source.  We do not count that in the following routine, so we are
+ * undercounting the source material that remains in the final output
+ * that way.
+ */
+int count_delta(void *delta_buf, unsigned long delta_size,
+               unsigned long *src_copied, unsigned long *literal_added)
+{
+       unsigned long copied_from_source, added_literal;
+       const unsigned char *data, *top;
+       unsigned char cmd;
+       unsigned long src_size, dst_size, out;
+
+       if (delta_size < DELTA_SIZE_MIN)
+               return -1;
+
+       data = delta_buf;
+       top = delta_buf + delta_size;
+
+       src_size = get_delta_hdr_size(&data);
+       dst_size = get_delta_hdr_size(&data);
+
+       added_literal = copied_from_source = out = 0;
+       while (data < top) {
+               cmd = *data++;
+               if (cmd & 0x80) {
+                       unsigned long cp_off = 0, cp_size = 0;
+                       if (cmd & 0x01) cp_off = *data++;
+                       if (cmd & 0x02) cp_off |= (*data++ << 8);
+                       if (cmd & 0x04) cp_off |= (*data++ << 16);
+                       if (cmd & 0x08) cp_off |= (*data++ << 24);
+                       if (cmd & 0x10) cp_size = *data++;
+                       if (cmd & 0x20) cp_size |= (*data++ << 8);
+                       if (cp_size == 0) cp_size = 0x10000;
+
+                       if (cmd & 0x40)
+                               /* copy from dst */
+                               ;
+                       else
+                               copied_from_source += cp_size;
+                       out += cp_size;
+               } else {
+                       /* write literal into dst */
+                       added_literal += cmd;
+                       out += cmd;
+                       data += cmd;
+               }
+       }
+
+       /* sanity check */
+       if (data != top || out != dst_size)
+               return -1;
+
+       /* delete size is what was _not_ copied from source.
+        * edit size is that and literal additions.
+        */
+       *src_copied = copied_from_source;
+       *literal_added = added_literal;
+       return 0;
+}
diff --git a/count-delta.h b/count-delta.h
new file mode 100644 (file)
index 0000000..7359629
--- /dev/null
@@ -0,0 +1,10 @@
+/*
+ * Copyright (C) 2005 Junio C Hamano
+ */
+#ifndef COUNT_DELTA_H
+#define COUNT_DELTA_H
+
+int count_delta(void *, unsigned long,
+               unsigned long *src_copied, unsigned long *literal_added);
+
+#endif
diff --git a/csum-file.c b/csum-file.c
new file mode 100644 (file)
index 0000000..c66b9eb
--- /dev/null
@@ -0,0 +1,148 @@
+/*
+ * csum-file.c
+ *
+ * Copyright (C) 2005 Linus Torvalds
+ *
+ * Simple file write infrastructure for writing SHA1-summed
+ * files. Useful when you write a file that you want to be
+ * able to verify hasn't been messed with afterwards.
+ */
+#include "cache.h"
+#include "csum-file.h"
+
+static int sha1flush(struct sha1file *f, unsigned int count)
+{
+       void *buf = f->buffer;
+
+       for (;;) {
+               int ret = write(f->fd, buf, count);
+               if (ret > 0) {
+                       buf += ret;
+                       count -= ret;
+                       if (count)
+                               continue;
+                       return 0;
+               }
+               if (!ret)
+                       die("sha1 file '%s' write error. Out of diskspace", f->name);
+               if (errno == EAGAIN || errno == EINTR)
+                       continue;
+               die("sha1 file '%s' write error (%s)", f->name, strerror(errno));
+       }
+}
+
+int sha1close(struct sha1file *f, unsigned char *result, int update)
+{
+       unsigned offset = f->offset;
+       if (offset) {
+               SHA1_Update(&f->ctx, f->buffer, offset);
+               sha1flush(f, offset);
+       }
+       SHA1_Final(f->buffer, &f->ctx);
+       if (result)
+               memcpy(result, f->buffer, 20);
+       if (update)
+               sha1flush(f, 20);
+       if (close(f->fd))
+               die("%s: sha1 file error on close (%s)", f->name, strerror(errno));
+       free(f);
+       return 0;
+}
+
+int sha1write(struct sha1file *f, void *buf, unsigned int count)
+{
+       while (count) {
+               unsigned offset = f->offset;
+               unsigned left = sizeof(f->buffer) - offset;
+               unsigned nr = count > left ? left : count;
+
+               memcpy(f->buffer + offset, buf, nr);
+               count -= nr;
+               offset += nr;
+               buf += nr;
+               left -= nr;
+               if (!left) {
+                       SHA1_Update(&f->ctx, f->buffer, offset);
+                       sha1flush(f, offset);
+                       offset = 0;
+               }
+               f->offset = offset;
+       }
+       return 0;
+}
+
+struct sha1file *sha1create(const char *fmt, ...)
+{
+       struct sha1file *f;
+       unsigned len;
+       va_list arg;
+       int fd;
+
+       f = xmalloc(sizeof(*f));
+
+       va_start(arg, fmt);
+       len = vsnprintf(f->name, sizeof(f->name), fmt, arg);
+       va_end(arg);
+       if (len >= PATH_MAX)
+               die("you wascally wabbit, you");
+       f->namelen = len;
+
+       fd = open(f->name, O_CREAT | O_EXCL | O_WRONLY, 0666);
+       if (fd < 0)
+               die("unable to open %s (%s)", f->name, strerror(errno));
+       f->fd = fd;
+       f->error = 0;
+       f->offset = 0;
+       SHA1_Init(&f->ctx);
+       return f;
+}
+
+struct sha1file *sha1fd(int fd, const char *name)
+{
+       struct sha1file *f;
+       unsigned len;
+
+       f = xmalloc(sizeof(*f));
+
+       len = strlen(name);
+       if (len >= PATH_MAX)
+               die("you wascally wabbit, you");
+       f->namelen = len;
+       memcpy(f->name, name, len+1);
+
+       f->fd = fd;
+       f->error = 0;
+       f->offset = 0;
+       SHA1_Init(&f->ctx);
+       return f;
+}
+
+int sha1write_compressed(struct sha1file *f, void *in, unsigned int size)
+{
+       z_stream stream;
+       unsigned long maxsize;
+       void *out;
+
+       memset(&stream, 0, sizeof(stream));
+       deflateInit(&stream, Z_DEFAULT_COMPRESSION);
+       maxsize = deflateBound(&stream, size);
+       out = xmalloc(maxsize);
+
+       /* Compress it */
+       stream.next_in = in;
+       stream.avail_in = size;
+
+       stream.next_out = out;
+       stream.avail_out = maxsize;
+
+       while (deflate(&stream, Z_FINISH) == Z_OK)
+               /* nothing */;
+       deflateEnd(&stream);
+
+       size = stream.total_out;
+       sha1write(f, out, size);
+       free(out);
+       return size;
+}
+
+
diff --git a/csum-file.h b/csum-file.h
new file mode 100644 (file)
index 0000000..3ad1a99
--- /dev/null
@@ -0,0 +1,19 @@
+#ifndef CSUM_FILE_H
+#define CSUM_FILE_H
+
+/* A SHA1-protected file */
+struct sha1file {
+       int fd, error;
+       unsigned int offset, namelen;
+       SHA_CTX ctx;
+       char name[PATH_MAX];
+       unsigned char buffer[8192];
+};
+
+extern struct sha1file *sha1fd(int fd, const char *name);
+extern struct sha1file *sha1create(const char *fmt, ...) __attribute__((format (printf, 1, 2)));
+extern int sha1close(struct sha1file *, unsigned char *, int);
+extern int sha1write(struct sha1file *, void *, unsigned int);
+extern int sha1write_compressed(struct sha1file *, void *, unsigned int);
+
+#endif
diff --git a/daemon.c b/daemon.c
new file mode 100644 (file)
index 0000000..932d908
--- /dev/null
+++ b/daemon.c
@@ -0,0 +1,359 @@
+#include "cache.h"
+#include "pkt-line.h"
+#include <signal.h>
+#include <sys/wait.h>
+#include <sys/socket.h>
+#include <netdb.h>
+#include <netinet/in.h>
+
+static const char daemon_usage[] = "git-daemon [--inetd | --port=n]";
+
+static int upload(char *dir, int dirlen)
+{
+       if (chdir(dir) < 0)
+               return -1;
+       chdir(".git");
+
+       /*
+        * Security on the cheap.
+        *
+        * 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))
+               return -1;
+
+       /*
+        * We'll ignore SIGTERM from now on, we have a
+        * good client.
+        */
+       signal(SIGTERM, SIG_IGN);
+
+       /* git-upload-pack only ever reads stuff, so this is safe */
+       execlp("git-upload-pack", "git-upload-pack", ".", NULL);
+       return -1;
+}
+
+static int execute(void)
+{
+       static char line[1000];
+       int len;
+
+       len = packet_read_line(0, line, sizeof(line));
+
+       if (len && line[len-1] == '\n')
+               line[--len] = 0;
+
+       if (!strncmp("git-upload-pack /", line, 17))
+               return upload(line + 16, len - 16);
+
+       fprintf(stderr, "got bad connection '%s'\n", line);
+       return -1;
+}
+
+
+/*
+ * We count spawned/reaped separately, just to avoid any
+ * races when updating them from signals. The SIGCHLD handler
+ * will only update children_reaped, and the fork logic will
+ * only update children_spawned.
+ *
+ * MAX_CHILDREN should be a power-of-two to make the modulus
+ * operation cheap. It should also be at least twice
+ * the maximum number of connections we will ever allow.
+ */
+#define MAX_CHILDREN 128
+
+static int max_connections = 25;
+
+/* These are updated by the signal handler */
+static volatile unsigned int children_reaped = 0;
+static pid_t dead_child[MAX_CHILDREN];
+
+/* These are updated by the main loop */
+static unsigned int children_spawned = 0;
+static unsigned int children_deleted = 0;
+
+static struct child {
+       pid_t pid;
+       socklen_t addrlen;
+       struct sockaddr_storage address;
+} live_child[MAX_CHILDREN];
+
+static void add_child(int idx, pid_t pid, struct sockaddr *addr, socklen_t addrlen)
+{
+       live_child[idx].pid = pid;
+       live_child[idx].addrlen = addrlen;
+       memcpy(&live_child[idx].address, addr, addrlen);
+}
+
+/*
+ * Walk from "deleted" to "spawned", and remove child "pid".
+ *
+ * We move everything up by one, since the new "deleted" will
+ * be one higher.
+ */
+static void remove_child(pid_t pid, unsigned deleted, unsigned spawned)
+{
+       struct child n;
+
+       deleted %= MAX_CHILDREN;
+       spawned %= MAX_CHILDREN;
+       if (live_child[deleted].pid == pid) {
+               live_child[deleted].pid = -1;
+               return;
+       }
+       n = live_child[deleted];
+       for (;;) {
+               struct child m;
+               deleted = (deleted + 1) % MAX_CHILDREN;
+               if (deleted == spawned)
+                       die("could not find dead child %d\n", pid);
+               m = live_child[deleted];
+               live_child[deleted] = n;
+               if (m.pid == pid)
+                       return;
+               n = m;
+       }
+}
+
+/*
+ * This gets called if the number of connections grows
+ * past "max_connections".
+ *
+ * We _should_ start off by searching for connections
+ * from the same IP, and if there is some address wth
+ * multiple connections, we should kill that first.
+ *
+ * As it is, we just "randomly" kill 25% of the connections,
+ * and our pseudo-random generator sucks too. I have no
+ * shame.
+ *
+ * Really, this is just a place-holder for a _real_ algorithm.
+ */
+static void kill_some_children(int signo, unsigned start, unsigned stop)
+{
+       start %= MAX_CHILDREN;
+       stop %= MAX_CHILDREN;
+       while (start != stop) {
+               if (!(start & 3))
+                       kill(live_child[start].pid, signo);
+               start = (start + 1) % MAX_CHILDREN;
+       }
+}
+
+static void check_max_connections(void)
+{
+       for (;;) {
+               int active;
+               unsigned spawned, reaped, deleted;
+
+               spawned = children_spawned;
+               reaped = children_reaped;
+               deleted = children_deleted;
+
+               while (deleted < reaped) {
+                       pid_t pid = dead_child[deleted % MAX_CHILDREN];
+                       remove_child(pid, deleted, spawned);
+                       deleted++;
+               }
+               children_deleted = deleted;
+
+               active = spawned - deleted;
+               if (active <= max_connections)
+                       break;
+
+               /* Kill some unstarted connections with SIGTERM */
+               kill_some_children(SIGTERM, deleted, spawned);
+               if (active <= max_connections << 1)
+                       break;
+
+               /* If the SIGTERM thing isn't helping use SIGKILL */
+               kill_some_children(SIGKILL, deleted, spawned);
+               sleep(1);
+       }
+}
+
+static void handle(int incoming, struct sockaddr *addr, socklen_t addrlen)
+{
+       pid_t pid = fork();
+
+       if (pid) {
+               unsigned idx;
+
+               close(incoming);
+               if (pid < 0)
+                       return;
+
+               idx = children_spawned % MAX_CHILDREN;
+               children_spawned++;
+               add_child(idx, pid, addr, addrlen);
+
+               check_max_connections();
+               return;
+       }
+
+       dup2(incoming, 0);
+       dup2(incoming, 1);
+       close(incoming);
+       exit(execute());
+}
+
+static void child_handler(int signo)
+{
+       for (;;) {
+               pid_t pid = waitpid(-1, NULL, WNOHANG);
+
+               if (pid > 0) {
+                       unsigned reaped = children_reaped;
+                       dead_child[reaped % MAX_CHILDREN] = pid;
+                       children_reaped = reaped + 1;
+                       continue;
+               }
+               break;
+       }
+}
+
+static int serve(int port)
+{
+       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);
+
+       sprintf(pbuf, "%d", port);
+       memset(&hints, 0, sizeof(hints));
+       hints.ai_family = AF_UNSPEC;
+       hints.ai_socktype = SOCK_STREAM;
+       hints.ai_protocol = IPPROTO_TCP;
+       hints.ai_flags = AI_PASSIVE;
+
+       gai = getaddrinfo(NULL, pbuf, &hints, &ai0);
+       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;
+
+               sockfd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
+               if (sockfd < 0)
+                       continue;
+               if (sockfd >= FD_SETSIZE) {
+                       error("too large socket descriptor.");
+                       close(sockfd);
+                       continue;
+               }
+
+#ifdef IPV6_V6ONLY
+               if (ai->ai_family == AF_INET6) {
+                       int on = 1;
+                       setsockopt(sockfd, IPPROTO_IPV6, IPV6_V6ONLY,
+                                  &on, sizeof(on));
+                       /* Note: error is not fatal */
+               }
+#endif
+
+               if (bind(sockfd, ai->ai_addr, ai->ai_addrlen) < 0) {
+                       close(sockfd);
+                       continue;       /* not fatal */
+               }
+               if (listen(sockfd, 5) < 0) {
+                       close(sockfd);
+                       continue;       /* not fatal */
+               }
+
+               newlist = realloc(socklist, sizeof(int) * (socknum + 1));
+               if (!newlist)
+                       die("memory allocation failed: %s", strerror(errno));
+
+               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);
+
+       for (;;) {
+               int i;
+               fds = fds_init;
+               
+               if (select(maxfd + 1, &fds, NULL, NULL, NULL) < 0) {
+                       if (errno != EINTR) {
+                               error("select failed, resuming: %s",
+                                     strerror(errno));
+                               sleep(1);
+                       }
+                       continue;
+               }
+
+               for (i = 0; i < socknum; i++) {
+                       int sockfd = socklist[i];
+
+                       if (FD_ISSET(sockfd, &fds)) {
+                               struct sockaddr_storage ss;
+                               socklen_t sslen = sizeof(ss);
+                               int incoming = accept(sockfd, (struct sockaddr *)&ss, &sslen);
+                               if (incoming < 0) {
+                                       switch (errno) {
+                                       case EAGAIN:
+                                       case EINTR:
+                                       case ECONNABORTED:
+                                               continue;
+                                       default:
+                                               die("accept returned %s", strerror(errno));
+                                       }
+                               }
+                               handle(incoming, (struct sockaddr *)&ss, sslen);
+                       }
+               }
+       }
+}
+
+int main(int argc, char **argv)
+{
+       int port = DEFAULT_GIT_PORT;
+       int inetd_mode = 0;
+       int i;
+
+       for (i = 1; i < argc; i++) {
+               char *arg = argv[i];
+
+               if (!strncmp(arg, "--port=", 7)) {
+                       char *end;
+                       unsigned long n;
+                       n = strtoul(arg+7, &end, 0);
+                       if (arg[7] && !*end) {
+                               port = n;
+                               continue;
+                       }
+               }
+
+               if (!strcmp(arg, "--inetd")) {
+                       inetd_mode = 1;
+                       continue;
+               }
+
+               usage(daemon_usage);
+       }
+
+       if (inetd_mode)
+               return execute();
+
+       return serve(port);
+}
diff --git a/date.c b/date.c
new file mode 100644 (file)
index 0000000..b46f2ce
--- /dev/null
+++ b/date.c
@@ -0,0 +1,459 @@
+/*
+ * GIT - The information manager from hell
+ *
+ * Copyright (C) Linus Torvalds, 2005
+ */
+
+#include <ctype.h>
+#include <time.h>
+
+#include "cache.h"
+
+static time_t my_mktime(struct tm *tm)
+{
+       static const int mdays[] = {
+           0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334
+       };
+       int year = tm->tm_year - 70;
+       int month = tm->tm_mon;
+       int day = tm->tm_mday;
+
+       if (year < 0 || year > 129) /* algo only works for 1970-2099 */
+               return -1;
+       if (month < 0 || month > 11) /* array bounds */
+               return -1;
+       if (month < 2 || (year + 2) % 4)
+               day--;
+       return (year * 365 + (year + 1) / 4 + mdays[month] + day) * 24*60*60UL +
+               tm->tm_hour * 60*60 + tm->tm_min * 60 + tm->tm_sec;
+}
+
+static const char *month_names[] = {
+       "January", "February", "March", "April", "May", "June",
+       "July", "August", "September", "October", "November", "December"
+};
+
+static const char *weekday_names[] = {
+       "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"
+};
+
+/*
+ * The "tz" thing is passed in as this strange "decimal parse of tz"
+ * thing, which means that tz -0100 is passed in as the integer -100,
+ * even though it means "sixty minutes off"
+ */
+const char *show_date(unsigned long time, int tz)
+{
+       struct tm *tm;
+       time_t t;
+       static char timebuf[200];
+       int minutes;
+
+       minutes = tz < 0 ? -tz : tz;
+       minutes = (minutes / 100)*60 + (minutes % 100);
+       minutes = tz < 0 ? -minutes : minutes;
+       t = time + minutes * 60;
+       tm = gmtime(&t);
+       if (!tm)
+               return NULL;
+       sprintf(timebuf, "%.3s %.3s %d %02d:%02d:%02d %d %+05d",
+               weekday_names[tm->tm_wday],
+               month_names[tm->tm_mon],
+               tm->tm_mday,
+               tm->tm_hour, tm->tm_min, tm->tm_sec,
+               tm->tm_year + 1900, tz);
+       return timebuf;
+}
+
+/*
+ * Check these. And note how it doesn't do the summer-time conversion.
+ *
+ * In my world, it's always summer, and things are probably a bit off
+ * in other ways too.
+ */
+static const struct {
+       const char *name;
+       int offset;
+       int dst;
+} timezone_names[] = {
+       { "IDLW", -12, 0, },    /* International Date Line West */
+       { "NT",   -11, 0, },    /* Nome */
+       { "CAT",  -10, 0, },    /* Central Alaska */
+       { "HST",  -10, 0, },    /* Hawaii Standard */
+       { "HDT",  -10, 1, },    /* Hawaii Daylight */
+       { "YST",   -9, 0, },    /* Yukon Standard */
+       { "YDT",   -9, 1, },    /* Yukon Daylight */
+       { "PST",   -8, 0, },    /* Pacific Standard */
+       { "PDT",   -8, 1, },    /* Pacific Daylight */
+       { "MST",   -7, 0, },    /* Mountain Standard */
+       { "MDT",   -7, 1, },    /* Mountain Daylight */
+       { "CST",   -6, 0, },    /* Central Standard */
+       { "CDT",   -6, 1, },    /* Central Daylight */
+       { "EST",   -5, 0, },    /* Eastern Standard */
+       { "EDT",   -5, 1, },    /* Eastern Daylight */
+       { "AST",   -3, 0, },    /* Atlantic Standard */
+       { "ADT",   -3, 1, },    /* Atlantic Daylight */
+       { "WAT",   -1, 0, },    /* West Africa */
+
+       { "GMT",    0, 0, },    /* Greenwich Mean */
+       { "UTC",    0, 0, },    /* Universal (Coordinated) */
+
+       { "WET",    0, 0, },    /* Western European */
+       { "BST",    0, 1, },    /* British Summer */
+       { "CET",   +1, 0, },    /* Central European */
+       { "MET",   +1, 0, },    /* Middle European */
+       { "MEWT",  +1, 0, },    /* Middle European Winter */
+       { "MEST",  +1, 1, },    /* Middle European Summer */
+       { "CEST",  +1, 1, },    /* Central European Summer */
+       { "MESZ",  +1, 1, },    /* Middle European Summer */
+       { "FWT",   +1, 0, },    /* French Winter */
+       { "FST",   +1, 1, },    /* French Summer */
+       { "EET",   +2, 0, },    /* Eastern Europe, USSR Zone 1 */
+       { "EEST",  +2, 1, },    /* Eastern European Daylight */
+       { "WAST",  +7, 0, },    /* West Australian Standard */
+       { "WADT",  +7, 1, },    /* West Australian Daylight */
+       { "CCT",   +8, 0, },    /* China Coast, USSR Zone 7 */
+       { "JST",   +9, 0, },    /* Japan Standard, USSR Zone 8 */
+       { "EAST", +10, 0, },    /* Eastern Australian Standard */
+       { "EADT", +10, 1, },    /* Eastern Australian Daylight */
+       { "GST",  +10, 0, },    /* Guam Standard, USSR Zone 9 */
+       { "NZT",  +11, 0, },    /* New Zealand */
+       { "NZST", +11, 0, },    /* New Zealand Standard */
+       { "NZDT", +11, 1, },    /* New Zealand Daylight */
+       { "IDLE", +12, 0, },    /* International Date Line East */
+};
+
+#define NR_TZ (sizeof(timezone_names) / sizeof(timezone_names[0]))
+       
+static int match_string(const char *date, const char *str)
+{
+       int i = 0;
+
+       for (i = 0; *date; date++, str++, i++) {
+               if (*date == *str)
+                       continue;
+               if (toupper(*date) == toupper(*str))
+                       continue;
+               if (!isalnum(*date))
+                       break;
+               return 0;
+       }
+       return i;
+}
+
+static int skip_alpha(const char *date)
+{
+       int i = 0;
+       do {
+               i++;
+       } while (isalpha(date[i]));
+       return i;
+}
+
+/*
+* Parse month, weekday, or timezone name
+*/
+static int match_alpha(const char *date, struct tm *tm, int *offset)
+{
+       int i;
+
+       for (i = 0; i < 12; i++) {
+               int match = match_string(date, month_names[i]);
+               if (match >= 3) {
+                       tm->tm_mon = i;
+                       return match;
+               }
+       }
+
+       for (i = 0; i < 7; i++) {
+               int match = match_string(date, weekday_names[i]);
+               if (match >= 3) {
+                       tm->tm_wday = i;
+                       return match;
+               }
+       }
+
+       for (i = 0; i < NR_TZ; i++) {
+               int match = match_string(date, timezone_names[i].name);
+               if (match >= 3) {
+                       int off = timezone_names[i].offset;
+
+                       /* This is bogus, but we like summer */
+                       off += timezone_names[i].dst;
+
+                       /* Only use the tz name offset if we don't have anything better */
+                       if (*offset == -1)
+                               *offset = 60*off;
+
+                       return match;
+               }
+       }
+
+       if (match_string(date, "PM") == 2) {
+               if (tm->tm_hour > 0 && tm->tm_hour < 12)
+                       tm->tm_hour += 12;
+               return 2;
+       }
+
+       /* BAD CRAP */
+       return skip_alpha(date);
+}
+
+static int is_date(int year, int month, int day, struct tm *tm)
+{
+       if (month > 0 && month < 13 && day > 0 && day < 32) {
+               if (year == -1) {
+                       tm->tm_mon = month-1;
+                       tm->tm_mday = day;
+                       return 1;
+               }
+               if (year >= 1970 && year < 2100) {
+                       year -= 1900;
+               } else if (year > 70 && year < 100) {
+                       /* ok */
+               } else if (year < 38) {
+                       year += 100;
+               } else
+                       return 0;
+
+               tm->tm_mon = month-1;
+               tm->tm_mday = day;
+               tm->tm_year = year;
+               return 1;
+       }
+       return 0;
+}
+
+static int match_multi_number(unsigned long num, char c, const char *date, char *end, struct tm *tm)
+{
+       long num2, num3;
+
+       num2 = strtol(end+1, &end, 10);
+       num3 = -1;
+       if (*end == c && isdigit(end[1]))
+               num3 = strtol(end+1, &end, 10);
+
+       /* Time? Date? */
+       switch (c) {
+       case ':':
+               if (num3 < 0)
+                       num3 = 0;
+               if (num < 25 && num2 >= 0 && num2 < 60 && num3 >= 0 && num3 <= 60) {
+                       tm->tm_hour = num;
+                       tm->tm_min = num2;
+                       tm->tm_sec = num3;
+                       break;
+               }
+               return 0;
+
+       case '-':
+       case '/':
+               if (num > 70) {
+                       /* yyyy-mm-dd? */
+                       if (is_date(num, num2, num3, tm))
+                               break;
+                       /* yyyy-dd-mm? */
+                       if (is_date(num, num3, num2, tm))
+                               break;
+               }
+               /* mm/dd/yy ? */
+               if (is_date(num3, num2, num, tm))
+                       break;
+               /* dd/mm/yy ? */
+               if (is_date(num3, num, num2, tm))
+                       break;
+               return 0;
+       }
+       return end - date;
+}
+
+/*
+ * We've seen a digit. Time? Year? Date? 
+ */
+static int match_digit(const char *date, struct tm *tm, int *offset, int *tm_gmt)
+{
+       int n;
+       char *end;
+       unsigned long num;
+
+       num = strtoul(date, &end, 10);
+
+       /*
+        * Seconds since 1970? We trigger on that for anything after Jan 1, 2000
+        */
+       if (num > 946684800) {
+               time_t time = num;
+               if (gmtime_r(&time, tm)) {
+                       *tm_gmt = 1;
+                       return end - date;
+               }
+       }
+
+       /*
+        * Check for special formats: num[:-/]num[same]num
+        */
+       switch (*end) {
+       case ':':
+       case '/':
+       case '-':
+               if (isdigit(end[1])) {
+                       int match = match_multi_number(num, *end, date, end, tm);
+                       if (match)
+                               return match;
+               }
+       }
+
+       /*
+        * None of the special formats? Try to guess what
+        * the number meant. We use the number of digits
+        * to make a more educated guess..
+        */
+       n = 0;
+       do {
+               n++;
+       } while (isdigit(date[n]));
+
+       /* Four-digit year or a timezone? */
+       if (n == 4) {
+               if (num <= 1200 && *offset == -1) {
+                       unsigned int minutes = num % 100;
+                       unsigned int hours = num / 100;
+                       *offset = hours*60 + minutes;
+               } else if (num > 1900 && num < 2100)
+                       tm->tm_year = num - 1900;
+               return n;
+       }
+
+       /*
+        * NOTE! We will give precedence to day-of-month over month or
+        * year numebers in the 1-12 range. So 05 is always "mday 5",
+        * unless we already have a mday..
+        *
+        * IOW, 01 Apr 05 parses as "April 1st, 2005".
+        */
+       if (num > 0 && num < 32 && tm->tm_mday < 0) {
+               tm->tm_mday = num;
+               return n;
+       }
+
+       /* Two-digit year? */
+       if (n == 2 && tm->tm_year < 0) {
+               if (num < 10 && tm->tm_mday >= 0) {
+                       tm->tm_year = num + 100;
+                       return n;
+               }
+               if (num >= 70) {
+                       tm->tm_year = num;
+                       return n;
+               }
+       }
+
+       if (num > 0 && num < 32) {
+               tm->tm_mday = num;
+       } else if (num > 1900) {
+               tm->tm_year = num - 1900;
+       } else if (num > 70) {
+               tm->tm_year = num;
+       } else if (num > 0 && num < 13) {
+               tm->tm_mon = num-1;
+       }
+               
+       return n;
+}
+
+static int match_tz(const char *date, int *offp)
+{
+       char *end;
+       int offset = strtoul(date+1, &end, 10);
+       int min, hour;
+       int n = end - date - 1;
+
+       min = offset % 100;
+       hour = offset / 100;
+
+       /*
+        * Don't accept any random crap.. At least 3 digits, and
+        * a valid minute. We might want to check that the minutes
+        * are divisible by 30 or something too.
+        */
+       if (min < 60 && n > 2) {
+               offset = hour*60+min;
+               if (*date == '-')
+                       offset = -offset;
+
+               *offp = offset;
+       }
+       return end - date;
+}
+
+/* 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)
+{
+       struct tm tm;
+       int offset, sign, tm_gmt;
+       time_t then;
+
+       memset(&tm, 0, sizeof(tm));
+       tm.tm_year = -1;
+       tm.tm_mon = -1;
+       tm.tm_mday = -1;
+       tm.tm_isdst = -1;
+       offset = -1;
+       tm_gmt = 0;
+
+       for (;;) {
+               int match = 0;
+               unsigned char c = *date;
+
+               /* Stop at end of string or newline */
+               if (!c || c == '\n')
+                       break;
+
+               if (isalpha(c))
+                       match = match_alpha(date, &tm, &offset);
+               else if (isdigit(c))
+                       match = match_digit(date, &tm, &offset, &tm_gmt);
+               else if ((c == '-' || c == '+') && isdigit(date[1]))
+                       match = match_tz(date, &offset);
+
+               if (!match) {
+                       /* BAD CRAP */
+                       match = 1;
+               }       
+
+               date += match;
+       }
+
+       /* mktime uses local timezone */
+       then = my_mktime(&tm); 
+       if (offset == -1)
+               offset = (then - mktime(&tm)) / 60;
+
+       if (then == -1)
+               return;
+
+       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);
+}
+
+void datestamp(char *buf, int bufsize)
+{
+       time_t now;
+       int offset;
+
+       time(&now);
+
+       offset = my_mktime(localtime(&now)) - now;
+       offset /= 60;
+
+       snprintf(buf, bufsize, "%lu %+05d", now, offset/60*100 + offset%60);
+}
diff --git a/debian/changelog b/debian/changelog
new file mode 100644 (file)
index 0000000..dbc6120
--- /dev/null
@@ -0,0 +1,33 @@
+git-core (0.99.4-2) unstable; urgency=low
+
+  * Git 0.99.4 official release.
+
+ -- Junio C Hamano <junkio@cox.net>  Wed, 10 Aug 2005 15:00:00 -0700
+
+git-core (0.99.4-1) unstable; urgency=low
+
+  * Pass prefix down to the submake when building.
+
+ -- Junio C Hamano <junkio@cox.net>  Sat, 6 Aug 2005 13:00:00 -0700
+
+git-core (0.99-2) unstable; urgency=low
+
+  * Conflict with the GNU Interactive Tools package, which also installs
+    /usr/bin/git.
+  * Use the Mozilla SHA1 code and/or the PPC assembly in preference to
+    OpenSSL.  This is only a partial fix for the license issues with OpenSSL.
+  * Minor tweaks to the Depends.
+
+ -- Ryan Anderson <ryan@michonline.com>  Sat, 23 Jul 2005 14:15:00 -0400
+
+git-core (0.99-1) unstable; urgency=low
+
+  * Update deb package support to build correctly. 
+
+ -- Ryan Anderson <ryan@michonline.com>  Thu, 21 Jul 2005 02:03:32 -0400
+
+git-core (0.99-0) unstable; urgency=low
+
+  * Initial deb package support
+
+ -- Eric Biederman <ebiederm@xmission.com>  Tue, 12 Jul 2005 10:57:51 -0600
diff --git a/debian/compat b/debian/compat
new file mode 100644 (file)
index 0000000..b8626c4
--- /dev/null
@@ -0,0 +1 @@
+4
diff --git a/debian/control b/debian/control
new file mode 100644 (file)
index 0000000..8bdbd73
--- /dev/null
@@ -0,0 +1,19 @@
+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)
+Standards-Version: 3.6.1
+
+Package: git-core
+Architecture: any
+Depends: ${shlibs:Depends}, ${misc:Depends}, patch, diff, rcs
+Recommends: rsync, curl, ssh, libmail-sendmail-perl, libemail-valid-perl
+Conflicts: git
+Description: The git content addressable filesystem
+ GIT comes in two layers. The bottom layer is merely an extremely fast
+ and flexible filesystem-based database designed to store directory trees
+ with regard to their history. The top layer is a SCM-like tool which
+ enables human beings to work with the database in a manner to a degree
+ similar to other SCM tools (like CVS, BitKeeper or Monotone).
+
diff --git a/debian/copyright b/debian/copyright
new file mode 100644 (file)
index 0000000..32b7e9c
--- /dev/null
@@ -0,0 +1,3 @@
+License: 
+
+GPL v2 (see COPYING for details)
diff --git a/debian/docs b/debian/docs
new file mode 100644 (file)
index 0000000..a252d0f
--- /dev/null
@@ -0,0 +1,3 @@
+README
+COPYING
+
diff --git a/debian/git-core.doc-base b/debian/git-core.doc-base
new file mode 100644 (file)
index 0000000..f1bbea8
--- /dev/null
@@ -0,0 +1,12 @@
+Document: git-core
+Title: git-core
+Author: 
+Abstract: This manual describes git
+Section: Devel
+
+Format: HTML
+Index: /usr/share/doc/git-core/git.html
+Files: /usr/share/doc/git-core/*.html
+
+Format: text
+Files: /usr/share/doc/git-core/git-core.txt
diff --git a/debian/git-core.install b/debian/git-core.install
new file mode 100644 (file)
index 0000000..72e8ffc
--- /dev/null
@@ -0,0 +1 @@
+*
diff --git a/debian/rules b/debian/rules
new file mode 100755 (executable)
index 0000000..2c575ff
--- /dev/null
@@ -0,0 +1,95 @@
+#!/usr/bin/make -f
+# -*- makefile -*-
+
+# Uncomment this to turn on verbose mode.
+#export DH_VERBOSE=1
+
+CFLAGS = -g -Wall
+ifneq (,$(findstring noopt,$(DEB_BUILD_OPTIONS)))
+       CFLAGS += -O0
+else
+       CFLAGS += -O2
+endif
+export CFLAGS
+
+#
+# On PowerPC we compile against the hand-crafted assembly, on all
+# other architectures we compile against GPL'ed sha1 code lifted
+# from Mozilla.  OpenSSL is strangely licensed and best avoided
+# in Debian.
+#
+HOST_ARCH=$(shell dpkg-architecture -qDEB_HOST_ARCH)
+ifeq (${HOST_ARCH},powerpc)
+       export PPC_SHA1=YesPlease
+else
+       export MOZILLA_SHA1=YesPlease
+endif
+
+
+PREFIX := /usr
+MANDIR := /usr/share/man/
+
+SRC    := ./
+DOC    := Documentation/
+DESTDIR  := $(CURDIR)/debian/tmp
+DOC_DESTDIR := $(DESTDIR)/usr/share/doc/git-core/
+MAN_DESTDIR := $(DESTDIR)/$(MANDIR)
+
+build: debian/build-stamp
+debian/build-stamp:
+       dh_testdir
+       $(MAKE) prefix=$(PREFIX) all doc
+       touch debian/build-stamp
+
+debian-clean:
+       dh_testdir
+       dh_testroot
+       rm -f debian/build-stamp
+       dh_clean
+
+clean: debian-clean
+       $(MAKE) clean
+
+install: build
+       dh_testdir
+       dh_testroot
+       dh_clean -k 
+       dh_installdirs 
+
+       make dest=$(DESTDIR) prefix=$(PREFIX) mandir=$(MANDIR) install install-tools install-doc
+
+       mkdir -p $(DOC_DESTDIR)
+       find $(DOC) '(' -name '*.txt' -o -name '*.html' ')' -exec install {} $(DOC_DESTDIR) ';'
+
+       dh_install --list-missing --sourcedir=$(DESTDIR)
+
+binary: build install
+       dh_testdir
+       dh_testroot
+       dh_installchangelogs
+       dh_installdocs
+       dh_installexamples
+#      dh_installmenu
+#      dh_installdebconf       
+#      dh_installlogrotate     
+#      dh_installemacsen
+#      dh_installpam
+#      dh_installmime
+#      dh_installinit
+#      dh_installcron
+#      dh_installinfo
+       dh_installman
+       dh_link
+       dh_strip
+       dh_compress 
+       dh_fixperms
+#      dh_perl
+#      dh_python
+       dh_makeshlibs
+       dh_installdeb
+       dh_shlibdeps
+       dh_gencontrol
+       dh_md5sums
+       dh_builddeb
+
+.PHONY: build clean binary install clean debian-clean
diff --git a/delta.h b/delta.h
new file mode 100644 (file)
index 0000000..31d1820
--- /dev/null
+++ b/delta.h
@@ -0,0 +1,34 @@
+#ifndef DELTA_H
+#define DELTA_H
+
+/* handling of delta buffers */
+extern void *diff_delta(void *from_buf, unsigned long from_size,
+                       void *to_buf, unsigned long to_size,
+                       unsigned long *delta_size, unsigned long max_size);
+extern void *patch_delta(void *src_buf, unsigned long src_size,
+                        void *delta_buf, unsigned long delta_size,
+                        unsigned long *dst_size);
+
+/* the smallest possible delta size is 4 bytes */
+#define DELTA_SIZE_MIN 4
+
+/*
+ * This must be called twice on the delta data buffer, first to get the
+ * expected reference buffer size, and again to get the result buffer size.
+ */
+static inline unsigned long get_delta_hdr_size(const unsigned char **datap)
+{
+       const unsigned char *data = *datap;
+       unsigned char cmd = *data++;
+       unsigned long size = cmd & ~0x80;
+       int i = 7;
+       while (cmd & 0x80) {
+               cmd = *data++;
+               size |= (cmd & ~0x80) << i;
+               i += 7;
+       }
+       *datap = data;
+       return size;
+}
+
+#endif
diff --git a/diff-cache.c b/diff-cache.c
new file mode 100644 (file)
index 0000000..47a4e09
--- /dev/null
@@ -0,0 +1,294 @@
+#include "cache.h"
+#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;
+
+/* A file entry went away or appeared */
+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);
+}
+
+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;
+
+       if (!cached_only) {
+               static unsigned char no_sha1[20];
+               int changed;
+               struct stat st;
+               if (lstat(ce->name, &st) < 0) {
+                       if (errno == ENOENT && match_nonexisting) {
+                               *sha1p = sha1;
+                               *modep = mode;
+                               return 0;
+                       }
+                       return -1;
+               }
+               changed = ce_match_stat(ce, &st);
+               if (changed) {
+                       mode = create_ce_mode(st.st_mode);
+                       sha1 = no_sha1;
+               }
+       }
+
+       *sha1p = sha1;
+       *modep = mode;
+       return 0;
+}
+
+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 */
+       if (get_stat_data(new, &sha1, &mode) < 0)
+               return;
+
+       show_file("+", new, sha1, mode);
+}
+
+static int show_modified(struct cache_entry *old,
+                        struct cache_entry *new,
+                        int report_missing)
+{
+       unsigned int mode, oldmode;
+       unsigned char *sha1;
+
+       if (get_stat_data(new, &sha1, &mode) < 0) {
+               if (report_missing)
+                       show_file("-", old, old->sha1, old->ce_mode);
+               return -1;
+       }
+
+       oldmode = old->ce_mode;
+       if (mode == oldmode && !memcmp(sha1, old->sha1, 20) &&
+           !find_copies_harder)
+               return 0;
+
+       mode = ntohl(mode);
+       oldmode = ntohl(oldmode);
+
+       diff_change(oldmode, mode,
+                   old->sha1, sha1, old->name, NULL);
+       return 0;
+}
+
+static int diff_cache(struct cache_entry **ac, int entries, const char **pathspec)
+{
+       while (entries) {
+               struct cache_entry *ce = *ac;
+               int same = (entries > 1) && ce_same_name(ce, ac[1]);
+
+               if (!ce_path_match(ce, pathspec))
+                       goto skip_entry;
+
+               switch (ce_stage(ce)) {
+               case 0:
+                       /* No stage 1 entry? That means it's a new file */
+                       if (!same) {
+                               show_new_file(ce);
+                               break;
+                       }
+                       /* Show difference between old and new */
+                       show_modified(ac[1], ce, 1);
+                       break;
+               case 1:
+                       /* No stage 3 (merge) entry? That means it's been deleted */
+                       if (!same) {
+                               show_file("-", ce, ce->sha1, ce->ce_mode);
+                               break;
+                       }
+                       /* We come here with ce pointing at stage 1
+                        * (original tree) and ac[1] pointing at stage
+                        * 3 (unmerged).  show-modified with
+                        * report-mising set to false does not say the
+                        * file is deleted but reports true if work
+                        * tree does not have it, in which case we
+                        * fall through to report the unmerged state.
+                        * Otherwise, we show the differences between
+                        * the original tree and the work tree.
+                        */
+                       if (!cached_only && !show_modified(ce, ac[1], 0))
+                               break;
+                       /* fallthru */
+               case 3:
+                       diff_unmerge(ce->name);
+                       break;
+
+               default:
+                       die("impossible cache entry stage");
+               }
+
+skip_entry:
+               /*
+                * Ignore all the different stages for this file,
+                * we've handled the relevant cases now.
+                */
+               do {
+                       ac++;
+                       entries--;
+               } while (entries && ce_same_name(ce, ac[0]));
+       }
+       return 0;
+}
+
+/*
+ * This turns all merge entries into "stage 3". That guarantees that
+ * when we read in the new tree (into "stage 1"), we won't lose sight
+ * of the fact that we had unmerged entries.
+ */
+static void mark_merge_entries(void)
+{
+       int i;
+       for (i = 0; i < active_nr; i++) {
+               struct cache_entry *ce = active_cache[i];
+               if (!ce_stage(ce))
+                       continue;
+               ce->ce_flags |= htons(CE_STAGEMASK);
+       }
+}
+
+static const char diff_cache_usage[] =
+"git-diff-cache [-m] [--cached] "
+"[<common diff options>] <tree-ish> [<path>...]"
+COMMON_DIFF_OPTIONS_HELP;
+
+int main(int argc, const char **argv)
+{
+       const char *tree_name = NULL;
+       unsigned char sha1[20];
+       const char **pathspec = NULL;
+       void *tree;
+       unsigned long size;
+       int ret;
+       int allow_options = 1;
+       int i;
+
+       read_cache();
+       for (i = 1; i < argc; i++) {
+               const char *arg = argv[i];
+
+               if (!allow_options || *arg != '-') {
+                       if (tree_name) {
+                               pathspec = argv + i;
+                               break;
+                       }
+                       tree_name = arg;
+                       continue;
+               }
+                       
+               if (!strcmp(arg, "--")) {
+                       allow_options = 0;
+                       continue;
+               }
+               if (!strcmp(arg, "-r")) {
+                       /* 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;
+                       continue;
+               }
+               if (!strcmp(arg, "-m")) {
+                       match_nonexisting = 1;
+                       continue;
+               }
+               if (!strcmp(arg, "--cached")) {
+                       cached_only = 1;
+                       continue;
+               }
+               usage(diff_cache_usage);
+       }
+
+       if (find_copies_harder && detect_rename != DIFF_DETECT_COPY)
+               usage(diff_cache_usage);
+
+       if (!tree_name || get_sha1(tree_name, sha1))
+               usage(diff_cache_usage);
+
+       /* The rest is for paths restriction. */
+       diff_setup(diff_setup_opt);
+
+       mark_merge_entries();
+
+       tree = read_object_with_reference(sha1, "tree", &size, NULL);
+       if (!tree)
+               die("bad tree object %s", tree_name);
+       if (read_tree(tree, size, 1, pathspec))
+               die("unable to read tree object %s", tree_name);
+
+       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);
+       return ret;
+}
diff --git a/diff-delta.c b/diff-delta.c
new file mode 100644 (file)
index 0000000..b2ae7b5
--- /dev/null
@@ -0,0 +1,334 @@
+/*
+ * diff-delta.c: generate a delta between two buffers
+ *
+ *  Many parts of this file have been lifted from LibXDiff version 0.10.
+ *  http://www.xmailserver.org/xdiff-lib.html
+ *
+ *  LibXDiff was written by Davide Libenzi <davidel@xmailserver.org>
+ *  Copyright (C) 2003 Davide Libenzi
+ *
+ *  Many mods for GIT usage by Nicolas Pitre <nico@cam.org>, (C) 2005.
+ *
+ *  This file is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2.1 of the License, or (at your option) any later version.
+ *
+ *  Use of this within git automatically means that the LGPL
+ *  licensing gets turned into GPLv2 within this project.
+ */
+
+#include <stdlib.h>
+#include "delta.h"
+
+
+/* block size: min = 16, max = 64k, power of 2 */
+#define BLK_SIZE 16
+
+#define MIN(a, b) ((a) < (b) ? (a) : (b))
+
+#define GR_PRIME 0x9e370001
+#define HASH(v, b) (((unsigned int)(v) * GR_PRIME) >> (32 - (b)))
+       
+/* largest prime smaller than 65536 */
+#define BASE 65521
+
+/* NMAX is the largest n such that 255n(n+1)/2 + (n+1)(BASE-1) <= 2^32-1 */
+#define NMAX 5552
+
+#define DO1(buf, i)  { s1 += buf[i]; s2 += s1; }
+#define DO2(buf, i)  DO1(buf, i); DO1(buf, i + 1);
+#define DO4(buf, i)  DO2(buf, i); DO2(buf, i + 2);
+#define DO8(buf, i)  DO4(buf, i); DO4(buf, i + 4);
+#define DO16(buf)    DO8(buf, 0); DO8(buf, 8);
+
+static unsigned int adler32(unsigned int adler, const unsigned char *buf, int len)
+{
+       int k;
+       unsigned int s1 = adler & 0xffff;
+       unsigned int s2 = adler >> 16;
+
+       while (len > 0) {
+               k = MIN(len, NMAX);
+               len -= k;
+               while (k >= 16) {
+                       DO16(buf);
+                       buf += 16;
+                       k -= 16;
+               }
+               if (k != 0)
+                       do {
+                               s1 += *buf++;
+                               s2 += s1;
+                       } while (--k);
+               s1 %= BASE;
+               s2 %= BASE;
+       }
+
+       return (s2 << 16) | s1;
+}
+
+static unsigned int hashbits(unsigned int size)
+{
+       unsigned int val = 1, bits = 0;
+       while (val < size && bits < 32) {
+               val <<= 1;
+               bits++;
+       }
+       return bits ? bits: 1;
+}
+
+typedef struct s_chanode {
+       struct s_chanode *next;
+       int icurr;
+} chanode_t;
+
+typedef struct s_chastore {
+       chanode_t *head, *tail;
+       int isize, nsize;
+       chanode_t *ancur;
+       chanode_t *sncur;
+       int scurr;
+} chastore_t;
+
+static void cha_init(chastore_t *cha, int isize, int icount)
+{
+       cha->head = cha->tail = NULL;
+       cha->isize = isize;
+       cha->nsize = icount * isize;
+       cha->ancur = cha->sncur = NULL;
+       cha->scurr = 0;
+}
+
+static void *cha_alloc(chastore_t *cha)
+{
+       chanode_t *ancur;
+       void *data;
+
+       ancur = cha->ancur;
+       if (!ancur || ancur->icurr == cha->nsize) {
+               ancur = malloc(sizeof(chanode_t) + cha->nsize);
+               if (!ancur)
+                       return NULL;
+               ancur->icurr = 0;
+               ancur->next = NULL;
+               if (cha->tail)
+                       cha->tail->next = ancur;
+               if (!cha->head)
+                       cha->head = ancur;
+               cha->tail = ancur;
+               cha->ancur = ancur;
+       }
+
+       data = (void *)ancur + sizeof(chanode_t) + ancur->icurr;
+       ancur->icurr += cha->isize;
+       return data;
+}
+
+static void cha_free(chastore_t *cha)
+{
+       chanode_t *cur = cha->head;
+       while (cur) {
+               chanode_t *tmp = cur;
+               cur = cur->next;
+               free(tmp);
+       }
+}
+
+typedef struct s_bdrecord {
+       struct s_bdrecord *next;
+       unsigned int fp;
+       const unsigned char *ptr;
+} bdrecord_t;
+
+typedef struct s_bdfile {
+       const unsigned char *data, *top;
+       chastore_t cha;
+       unsigned int fphbits;
+       bdrecord_t **fphash;
+} bdfile_t;
+
+static int delta_prepare(const unsigned char *buf, int bufsize, bdfile_t *bdf)
+{
+       unsigned int fphbits;
+       int i, hsize;
+       const unsigned char *base, *data, *top;
+       bdrecord_t *brec;
+       bdrecord_t **fphash;
+
+       fphbits = hashbits(bufsize / BLK_SIZE + 1);
+       hsize = 1 << fphbits;
+       fphash = malloc(hsize * sizeof(bdrecord_t *));
+       if (!fphash)
+               return -1;
+       for (i = 0; i < hsize; i++)
+               fphash[i] = NULL;
+       cha_init(&bdf->cha, sizeof(bdrecord_t), hsize / 4 + 1);
+
+       bdf->data = data = base = buf;
+       bdf->top = top = buf + bufsize;
+       data += (bufsize / BLK_SIZE) * BLK_SIZE;
+       if (data == top)
+               data -= BLK_SIZE;
+
+       for ( ; data >= base; data -= BLK_SIZE) {
+               brec = cha_alloc(&bdf->cha);
+               if (!brec) {
+                       cha_free(&bdf->cha);
+                       free(fphash);
+                       return -1;
+               }
+               brec->fp = adler32(0, data, MIN(BLK_SIZE, top - data));
+               brec->ptr = data;
+               i = HASH(brec->fp, fphbits);
+               brec->next = fphash[i];
+               fphash[i] = brec;
+       }
+
+       bdf->fphbits = fphbits;
+       bdf->fphash = fphash;
+
+       return 0;
+}
+
+static void delta_cleanup(bdfile_t *bdf)
+{
+       free(bdf->fphash);
+       cha_free(&bdf->cha);
+}
+
+#define COPYOP_SIZE(o, s) \
+    (!!(o & 0xff) + !!(o & 0xff00) + !!(o & 0xff0000) + !!(o & 0xff000000) + \
+     !!(s & 0xff) + !!(s & 0xff00) + 1)
+
+void *diff_delta(void *from_buf, unsigned long from_size,
+                void *to_buf, unsigned long to_size,
+                unsigned long *delta_size,
+                unsigned long max_size)
+{
+       int i, outpos, outsize, inscnt, csize, msize, moff;
+       unsigned int fp;
+       const unsigned char *data, *top, *ptr1, *ptr2;
+       unsigned char *out, *orig;
+       bdrecord_t *brec;
+       bdfile_t bdf;
+
+       if (!from_size || !to_size || delta_prepare(from_buf, from_size, &bdf))
+               return NULL;
+       
+       outpos = 0;
+       outsize = 8192;
+       out = malloc(outsize);
+       if (!out) {
+               delta_cleanup(&bdf);
+               return NULL;
+       }
+
+       data = to_buf;
+       top = to_buf + to_size;
+
+       /* store reference buffer size */
+       out[outpos++] = from_size;
+       from_size >>= 7;
+       while (from_size) {
+               out[outpos - 1] |= 0x80;
+               out[outpos++] = from_size;
+               from_size >>= 7;
+       }
+
+       /* store target buffer size */
+       out[outpos++] = to_size;
+       to_size >>= 7;
+       while (to_size) {
+               out[outpos - 1] |= 0x80;
+               out[outpos++] = to_size;
+               to_size >>= 7;
+       }
+
+       inscnt = 0;
+       moff = 0;
+       while (data < top) {
+               msize = 0;
+               fp = adler32(0, data, MIN(top - data, BLK_SIZE));
+               i = HASH(fp, bdf.fphbits);
+               for (brec = bdf.fphash[i]; brec; brec = brec->next) {
+                       if (brec->fp == fp) {
+                               csize = bdf.top - brec->ptr;
+                               if (csize > top - data)
+                                       csize = top - data;
+                               for (ptr1 = brec->ptr, ptr2 = data; 
+                                    csize && *ptr1 == *ptr2;
+                                    csize--, ptr1++, ptr2++);
+
+                               csize = ptr1 - brec->ptr;
+                               if (csize > msize) {
+                                       moff = brec->ptr - bdf.data;
+                                       msize = csize;
+                                       if (msize >= 0x10000) {
+                                               msize = 0x10000;
+                                               break;
+                                       }
+                               }
+                       }
+               }
+
+               if (!msize || msize < COPYOP_SIZE(moff, msize)) {
+                       if (!inscnt)
+                               outpos++;
+                       out[outpos++] = *data++;
+                       inscnt++;
+                       if (inscnt == 0x7f) {
+                               out[outpos - inscnt - 1] = inscnt;
+                               inscnt = 0;
+                       }
+               } else {
+                       if (inscnt) {
+                               out[outpos - inscnt - 1] = inscnt;
+                               inscnt = 0;
+                       }
+
+                       data += msize;
+                       orig = out + outpos++;
+                       i = 0x80;
+
+                       if (moff & 0xff) { out[outpos++] = moff; i |= 0x01; }
+                       moff >>= 8;
+                       if (moff & 0xff) { out[outpos++] = moff; i |= 0x02; }
+                       moff >>= 8;
+                       if (moff & 0xff) { out[outpos++] = moff; i |= 0x04; }
+                       moff >>= 8;
+                       if (moff & 0xff) { out[outpos++] = moff; i |= 0x08; }
+
+                       if (msize & 0xff) { out[outpos++] = msize; i |= 0x10; }
+                       msize >>= 8;
+                       if (msize & 0xff) { out[outpos++] = msize; i |= 0x20; }
+
+                       *orig = i;
+               }
+
+               if (max_size && outpos > max_size) {
+                       free(out);
+                       delta_cleanup(&bdf);
+                       return NULL;
+               }
+
+               /* next time around the largest possible output is 1 + 4 + 3 */
+               if (outpos > outsize - 8) {
+                       void *tmp = out;
+                       outsize = outsize * 3 / 2;
+                       out = realloc(out, outsize);
+                       if (!out) {
+                               free(tmp);
+                               delta_cleanup(&bdf);
+                               return NULL;
+                       }
+               }
+       }
+
+       if (inscnt)
+               out[outpos - inscnt - 1] = inscnt;
+
+       delta_cleanup(&bdf);
+       *delta_size = outpos;
+       return out;
+}
diff --git a/diff-files.c b/diff-files.c
new file mode 100644 (file)
index 0000000..2e6416e
--- /dev/null
@@ -0,0 +1,157 @@
+/*
+ * GIT - The information manager from hell
+ *
+ * Copyright (C) Linus Torvalds, 2005
+ */
+#include "cache.h"
+#include "diff.h"
+
+static const char diff_files_usage[] =
+"git-diff-files [-q] "
+"[<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 int silent = 0;
+
+static void show_unmerge(const char *path)
+{
+       diff_unmerge(path);
+}
+
+static void show_file(int pfx, struct cache_entry *ce)
+{
+       diff_addremove(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);
+}
+
+int main(int argc, const char **argv)
+{
+       static const unsigned char null_sha1[20] = { 0, };
+       const char **pathspec;
+       int entries = read_cache();
+       int i;
+
+       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"))
+                       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)
+                               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)
+                               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--;
+       }
+
+       /* Do we have a pathspec? */
+       pathspec = (argc > 1) ? argv + 1 : NULL;
+
+       if (find_copies_harder && detect_rename != DIFF_DETECT_COPY)
+               usage(diff_files_usage);
+
+       /* At this point, if argc == 1, then we are doing everything.
+        * Otherwise argv[1] .. argv[argc-1] have the explicit paths.
+        */
+       if (entries < 0) {
+               perror("read_cache");
+               exit(1);
+       }
+
+       diff_setup(diff_setup_opt);
+
+       for (i = 0; i < entries; i++) {
+               struct stat st;
+               unsigned int oldmode;
+               struct cache_entry *ce = active_cache[i];
+               int changed;
+
+               if (!ce_path_match(ce, pathspec))
+                       continue;
+
+               if (ce_stage(ce)) {
+                       show_unmerge(ce->name);
+                       while (i < entries &&
+                              !strcmp(ce->name, active_cache[i]->name))
+                               i++;
+                       i--; /* compensate for loop control increments */
+                       continue;
+               }
+
+               if (lstat(ce->name, &st) < 0) {
+                       if (errno != ENOENT && errno != ENOTDIR) {
+                               perror(ce->name);
+                               continue;
+                       }
+                       if (silent)
+                               continue;
+                       show_file('-', ce);
+                       continue;
+               }
+               changed = ce_match_stat(ce, &st);
+               if (!changed && !find_copies_harder)
+                       continue;
+               oldmode = ntohl(ce->ce_mode);
+               show_modified(oldmode, DIFF_FILE_CANON_MODE(st.st_mode),
+                             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);
+       return 0;
+}
diff --git a/diff-helper.c b/diff-helper.c
new file mode 100644 (file)
index 0000000..3fa50ee
--- /dev/null
@@ -0,0 +1,154 @@
+/*
+ * 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("MCRNDU", 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;
+}
diff --git a/diff-stages.c b/diff-stages.c
new file mode 100644 (file)
index 0000000..2e9c0bc
--- /dev/null
@@ -0,0 +1,130 @@
+/*
+ * Copyright (c) 2005 Junio C Hamano
+ */
+
+#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 const char diff_stages_usage[] =
+"git-diff-stages [<common diff options>] <stage1> <stage2> [<path>...]"
+COMMON_DIFF_OPTIONS_HELP;
+
+static void diff_stages(int stage1, int stage2)
+{
+       int i = 0;
+       while (i < active_nr) {
+               struct cache_entry *ce, *stages[4] = { NULL, };
+               struct cache_entry *one, *two;
+               const char *name;
+               int len;
+               ce = active_cache[i];
+               len = ce_namelen(ce);
+               name = ce->name;
+               for (;;) {
+                       int stage = ce_stage(ce);
+                       stages[stage] = ce;
+                       if (active_nr <= ++i)
+                               break;
+                       ce = active_cache[i];
+                       if (ce_namelen(ce) != len ||
+                           memcmp(name, ce->name, len))
+                               break;
+               }
+               one = stages[stage1];
+               two = stages[stage2];
+               if (!one && !two)
+                       continue;
+               if (!one)
+                       diff_addremove('+', ntohl(two->ce_mode),
+                                      two->sha1, name, NULL);
+               else if (!two)
+                       diff_addremove('-', 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),
+                                   one->sha1, two->sha1, name, NULL);
+       }
+}
+
+int main(int ac, const char **av)
+{
+       int stage1, stage2;
+
+       read_cache();
+       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)
+                               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)
+                               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++;
+       }
+
+       if (ac < 3 ||
+           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))
+               usage(diff_stages_usage);
+
+       av += 3; /* The rest from av[0] are for paths restriction. */
+       diff_setup(diff_setup_opt);
+
+       diff_stages(stage1, stage2);
+
+       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);
+       return 0;
+}
diff --git a/diff-tree.c b/diff-tree.c
new file mode 100644 (file)
index 0000000..0dd3cda
--- /dev/null
@@ -0,0 +1,559 @@
+#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;
+
+       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)
+{
+       diff_setup(diff_setup_opt);
+}
+
+static int call_diff_flush(void)
+{
+       diffcore_std(NULL,
+                    detect_rename, diff_score_opt,
+                    pickaxe, pickaxe_opts,
+                    diff_break_opt,
+                    orderfile,
+                    diff_filter);
+       if (diff_queue_is_empty()) {
+               diff_flush(DIFF_FORMAT_NO_OUTPUT, diff_line_termination);
+               return 0;
+       }
+       if (header) {
+               printf("%s%c", header, diff_line_termination);
+               header = NULL;
+       }
+       diff_flush(diff_output_format, diff_line_termination);
+       return 1;
+}
+
+static int diff_tree_sha1_top(const unsigned char *old,
+                             const unsigned char *new, const char *base)
+{
+       int ret;
+
+       call_diff_setup();
+       ret = diff_tree_sha1(old, new, base);
+       call_diff_flush();
+       return ret;
+}
+
+static int diff_root_tree(const unsigned char *new, const char *base)
+{
+       int retval;
+       void *tree;
+       unsigned long size;
+
+       call_diff_setup();
+       tree = read_object_with_reference(new, "tree", &size, NULL);
+       if (!tree)
+               die("unable to read root tree (%s)", sha1_to_hex(new));
+       retval = diff_tree("", 0, tree, size, base);
+       free(tree);
+       call_diff_flush();
+       return retval;
+}
+
+static const char *generate_header(const char *commit, const char *parent, const char *msg, unsigned long len)
+{
+       static char this_header[16384];
+       int offset;
+
+       if (!verbose_header)
+               return commit;
+
+       offset = sprintf(this_header, "%s%s (from %s)\n", header_prefix, commit, parent);
+       offset += pretty_print_commit(commit_format, msg, len, this_header + offset, sizeof(this_header) - offset);
+       return this_header;
+}
+
+static int diff_tree_commit(const unsigned char *commit, const char *name)
+{
+       unsigned long size, offset;
+       char *buf = read_object_with_reference(commit, "commit", &size, NULL);
+
+       if (!buf)
+               return -1;
+
+       if (!name) {
+               static char commit_name[60];
+               strcpy(commit_name, sha1_to_hex(commit));
+               name = commit_name;
+       }
+
+       /* Root commit? */
+       if (show_root_diff && memcmp(buf + 46, "parent ", 7)) {
+               header = generate_header(name, "root", buf, size);
+               diff_root_tree(commit, "");
+       }
+
+       /* More than one parent? */
+       if (ignore_merges) {
+               if (!memcmp(buf + 46 + 48, "parent ", 7))
+                       return 0;
+       }
+
+       offset = 46;
+       while (offset + 48 < size && !memcmp(buf + offset, "parent ", 7)) {
+               unsigned char parent[20];
+               if (get_sha1_hex(buf + offset + 7, parent))
+                       return -1;
+               header = generate_header(name, sha1_to_hex(parent), buf, size);
+               diff_tree_sha1_top(parent, commit, "");
+               if (!header && verbose_header) {
+                       header_prefix = "\ndiff-tree ";
+                       /*
+                        * Don't print multiple merge entries if we
+                        * don't print the diffs.
+                        */
+               }
+               offset += 48;
+       }
+       return 0;
+}
+
+static int diff_tree_stdin(char *line)
+{
+       int len = strlen(line);
+       unsigned char commit[20], parent[20];
+       static char this_header[1000];
+
+       if (!len || line[len-1] != '\n')
+               return -1;
+       line[len-1] = 0;
+       if (get_sha1_hex(line, commit))
+               return -1;
+       if (isspace(line[40]) && !get_sha1_hex(line+41, parent)) {
+               line[40] = 0;
+               line[81] = 0;
+               sprintf(this_header, "%s (from %s)\n", line, line+41);
+               header = this_header;
+               return diff_tree_sha1_top(parent, commit, "");
+       }
+       line[40] = 0;
+       return diff_tree_commit(commit, line);
+}
+
+static const char diff_tree_usage[] =
+"git-diff-tree [--stdin] [-m] [-s] [-v] [--pretty] [-t] "
+"[<common diff options>] <tree-ish> <tree-ish>"
+COMMON_DIFF_OPTIONS_HELP;
+
+int main(int argc, const char **argv)
+{
+       int nr_sha1;
+       char line[1000];
+       unsigned char sha1[2][20];
+
+       nr_sha1 = 0;
+       for (;;) {
+               const char *arg;
+
+               argv++;
+               argc--;
+               arg = *argv;
+               if (!arg)
+                       break;
+
+               if (*arg != '-') {
+                       if (nr_sha1 < 2 && !get_sha1(arg, sha1[nr_sha1])) {
+                               nr_sha1++;
+                               continue;
+                       }
+                       break;
+               }
+
+               if (!strcmp(arg, "--")) {
+                       argv++;
+                       argc--;
+                       break;
+               }
+               if (!strcmp(arg, "-r")) {
+                       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;
+                       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 ";
+                       continue;
+               }
+               if (!strncmp(arg, "--pretty", 8)) {
+                       verbose_header = 1;
+                       header_prefix = "diff-tree ";
+                       commit_format = get_commit_format(arg+8);
+                       continue;
+               }
+               if (!strcmp(arg, "--stdin")) {
+                       read_stdin = 1;
+                       continue;
+               }
+               if (!strcmp(arg, "--root")) {
+                       show_root_diff = 1;
+                       continue;
+               }
+               usage(diff_tree_usage);
+       }
+       if (find_copies_harder && detect_rename != DIFF_DETECT_COPY)
+               usage(diff_tree_usage);
+
+       if (argc > 0) {
+               int i;
+
+               paths = argv;
+               nr_paths = argc;
+               pathlens = xmalloc(nr_paths * sizeof(int));
+               for (i=0; i<nr_paths; i++)
+                       pathlens[i] = strlen(paths[i]);
+       }
+
+       switch (nr_sha1) {
+       case 0:
+               if (!read_stdin)
+                       usage(diff_tree_usage);
+               break;
+       case 1:
+               diff_tree_commit(sha1[0], NULL);
+               break;
+       case 2:
+               diff_tree_sha1_top(sha1[0], sha1[1], "");
+               break;
+       }
+
+       if (!read_stdin)
+               return 0;
+
+       if (detect_rename)
+               diff_setup_opt |= (DIFF_SETUP_USE_SIZE_CACHE |
+                                  DIFF_SETUP_USE_CACHE);
+       while (fgets(line, sizeof(line), stdin))
+               diff_tree_stdin(line);
+
+       return 0;
+}
diff --git a/diff.c b/diff.c
new file mode 100644 (file)
index 0000000..3e52fec
--- /dev/null
+++ b/diff.c
@@ -0,0 +1,1205 @@
+/*
+ * Copyright (C) 2005 Junio C Hamano
+ */
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <signal.h>
+#include "cache.h"
+#include "quote.h"
+#include "diff.h"
+#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 const char *external_diff(void)
+{
+       static const char *external_diff_cmd = NULL;
+       static int done_preparing = 0;
+
+       if (done_preparing)
+               return external_diff_cmd;
+
+       /*
+        * Default values above are meant to match the
+        * Linux kernel development style.  Examples of
+        * alternative styles you can specify via environment
+        * variables are:
+        *
+        * GIT_DIFF_OPTS="-c";
+        */
+       if (gitenv("GIT_EXTERNAL_DIFF"))
+               external_diff_cmd = gitenv("GIT_EXTERNAL_DIFF");
+
+       /* In case external diff fails... */
+       diff_opts = gitenv("GIT_DIFF_OPTS") ? : diff_opts;
+
+       done_preparing = 1;
+       return external_diff_cmd;
+}
+
+#define TEMPFILE_PATH_LEN              50
+
+static struct diff_tempfile {
+       const char *name; /* filename external diff should read from */
+       char hex[41];
+       char mode[10];
+       char tmp_path[TEMPFILE_PATH_LEN];
+} diff_temp[2];
+
+static int count_lines(const char *filename)
+{
+       FILE *in;
+       int count, ch, completely_empty = 1, nl_just_seen = 0;
+       in = fopen(filename, "r");
+       count = 0;
+       while ((ch = fgetc(in)) != EOF)
+               if (ch == '\n') {
+                       count++;
+                       nl_just_seen = 1;
+                       completely_empty = 0;
+               }
+               else {
+                       nl_just_seen = 0;
+                       completely_empty = 0;
+               }
+       fclose(in);
+       if (completely_empty)
+               return 0;
+       if (!nl_just_seen)
+               count++; /* no trailing newline */
+       return count;
+}
+
+static void print_line_count(int count)
+{
+       switch (count) {
+       case 0:
+               printf("0,0");
+               break;
+       case 1:
+               printf("1");
+               break;
+       default:
+               printf("1,%d", count);
+               break;
+       }
+}
+
+static void copy_file(int prefix, const char *filename)
+{
+       FILE *in;
+       int ch, nl_just_seen = 1;
+       in = fopen(filename, "r");
+       while ((ch = fgetc(in)) != EOF) {
+               if (nl_just_seen)
+                       putchar(prefix);
+               putchar(ch);
+               if (ch == '\n')
+                       nl_just_seen = 1;
+               else
+                       nl_just_seen = 0;
+       }
+       fclose(in);
+       if (!nl_just_seen)
+               printf("\n\\ No newline at end of file\n");
+}
+
+static void emit_rewrite_diff(const char *name_a,
+                             const char *name_b,
+                             struct diff_tempfile *temp)
+{
+       /* Use temp[i].name as input, name_a and name_b as labels */
+       int lc_a, lc_b;
+       lc_a = count_lines(temp[0].name);
+       lc_b = count_lines(temp[1].name);
+       printf("--- %s\n+++ %s\n@@ -", name_a, name_b);
+       print_line_count(lc_a);
+       printf(" +");
+       print_line_count(lc_b);
+       printf(" @@\n");
+       if (lc_a)
+               copy_file('-', temp[0].name);
+       if (lc_b)
+               copy_file('+', temp[1].name);
+}
+
+static void builtin_diff(const char *name_a,
+                        const char *name_b,
+                        struct diff_tempfile *temp,
+                        const char *xfrm_msg,
+                        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 *input_name_sq[2];
+       const char *path0[2];
+       const char *path1[2];
+       const char *name_sq[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.
+        * we use 2 spaces around diff-opts, and we need to count
+        * terminating NUL, so we subtract 9 here.
+        */
+       cmd_size = (strlen(diff_cmd) + strlen(diff_opts) +
+                       strlen(diff_arg) - 9);
+       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]));
+       }
+
+       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]);
+       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("new file mode %s\n", temp[1].mode);
+               if (xfrm_msg && xfrm_msg[0])
+                       puts(xfrm_msg);
+       }
+       else if (!path1[1][0]) {
+               printf("deleted file mode %s\n", temp[0].mode);
+               if (xfrm_msg && xfrm_msg[0])
+                       puts(xfrm_msg);
+       }
+       else {
+               if (strcmp(temp[0].mode, temp[1].mode)) {
+                       printf("old mode %s\n", temp[0].mode);
+                       printf("new mode %s\n", temp[1].mode);
+               }
+               if (xfrm_msg && xfrm_msg[0])
+                       puts(xfrm_msg);
+               if (strncmp(temp[0].mode, temp[1].mode, 3))
+                       /* we do not run diff between different kind
+                        * of objects.
+                        */
+                       exit(0);
+               if (complete_rewrite) {
+                       fflush(NULL);
+                       emit_rewrite_diff(name_a, name_b, temp);
+                       exit(0);
+               }
+       }
+       fflush(NULL);
+       execlp("/bin/sh","sh", "-c", cmd, NULL);
+}
+
+struct diff_filespec *alloc_filespec(const char *path)
+{
+       int namelen = strlen(path);
+       struct diff_filespec *spec = xmalloc(sizeof(*spec) + namelen + 1);
+       spec->path = (char *)(spec + 1);
+       strcpy(spec->path, path);
+       spec->should_free = spec->should_munmap = 0;
+       spec->xfrm_flags = 0;
+       spec->size = 0;
+       spec->data = NULL;
+       spec->mode = 0;
+       memset(spec->sha1, 0, 20);
+       return spec;
+}
+
+void fill_filespec(struct diff_filespec *spec, const unsigned char *sha1,
+                  unsigned short mode)
+{
+       if (mode) {
+               spec->mode = DIFF_FILE_CANON_MODE(mode);
+               memcpy(spec->sha1, sha1, 20);
+               spec->sha1_valid = !!memcmp(sha1, null_sha1, 20);
+       }
+}
+
+/*
+ * Given a name and sha1 pair, if the dircache tells us the file in
+ * the work tree has that object contents, return true, so that
+ * prepare_temp_file() does not have to inflate and extract.
+ */
+static int work_tree_matches(const char *name, const unsigned char *sha1)
+{
+       struct cache_entry *ce;
+       struct stat st;
+       int pos, len;
+
+       /* We do not read the cache ourselves here, because the
+        * benchmark with my previous version that always reads cache
+        * shows that it makes things worse for diff-tree comparing
+        * two linux-2.6 kernel trees in an already checked out work
+        * tree.  This is because most diff-tree comparisons deal with
+        * only a small number of files, while reading the cache is
+        * expensive for a large project, and its cost outweighs the
+        * savings we get by not inflating the object to a temporary
+        * file.  Practically, this code only helps when we are used
+        * by diff-cache --cached, which does read the cache before
+        * calling us.
+        */
+       if (!active_cache)
+               return 0;
+
+       len = strlen(name);
+       pos = cache_name_pos(name, len);
+       if (pos < 0)
+               return 0;
+       ce = active_cache[pos];
+       if ((lstat(name, &st) < 0) ||
+           !S_ISREG(st.st_mode) || /* careful! */
+           ce_match_stat(ce, &st) ||
+           memcmp(sha1, ce->sha1, 20))
+               return 0;
+       /* we return 1 only when we can stat, it is a regular file,
+        * stat information matches, and sha1 recorded in the cache
+        * matches.  I.e. we know the file in the work tree really is
+        * the same as the <name, sha1> pair.
+        */
+       return 1;
+}
+
+static struct sha1_size_cache {
+       unsigned char sha1[20];
+       unsigned long size;
+} **sha1_size_cache;
+static int sha1_size_cache_nr, sha1_size_cache_alloc;
+
+static struct sha1_size_cache *locate_size_cache(unsigned char *sha1,
+                                                int find_only,
+                                                unsigned long size)
+{
+       int first, last;
+       struct sha1_size_cache *e;
+
+       first = 0;
+       last = sha1_size_cache_nr;
+       while (last > first) {
+               int cmp, next = (last + first) >> 1;
+               e = sha1_size_cache[next];
+               cmp = memcmp(e->sha1, sha1, 20);
+               if (!cmp)
+                       return e;
+               if (cmp < 0) {
+                       last = next;
+                       continue;
+               }
+               first = next+1;
+       }
+       /* not found */
+       if (find_only)
+               return NULL;
+       /* insert to make it at "first" */
+       if (sha1_size_cache_alloc <= sha1_size_cache_nr) {
+               sha1_size_cache_alloc = alloc_nr(sha1_size_cache_alloc);
+               sha1_size_cache = xrealloc(sha1_size_cache,
+                                          sha1_size_cache_alloc *
+                                          sizeof(*sha1_size_cache));
+       }
+       sha1_size_cache_nr++;
+       if (first < sha1_size_cache_nr)
+               memmove(sha1_size_cache + first + 1, sha1_size_cache + first,
+                       (sha1_size_cache_nr - first - 1) *
+                       sizeof(*sha1_size_cache));
+       e = xmalloc(sizeof(struct sha1_size_cache));
+       sha1_size_cache[first] = e;
+       memcpy(e->sha1, sha1, 20);
+       e->size = size;
+       return e;
+}
+
+/*
+ * While doing rename detection and pickaxe operation, we may need to
+ * grab the data for the blob (or file) for our own in-core comparison.
+ * diff_filespec has data and size fields for this purpose.
+ */
+int diff_populate_filespec(struct diff_filespec *s, int size_only)
+{
+       int err = 0;
+       if (!DIFF_FILE_VALID(s))
+               die("internal error: asking to populate invalid file.");
+       if (S_ISDIR(s->mode))
+               return -1;
+
+       if (!use_size_cache)
+               size_only = 0;
+
+       if (s->data)
+               return err;
+       if (!s->sha1_valid ||
+           work_tree_matches(s->path, s->sha1)) {
+               struct stat st;
+               int fd;
+               if (lstat(s->path, &st) < 0) {
+                       if (errno == ENOENT) {
+                       err_empty:
+                               err = -1;
+                       empty:
+                               s->data = "";
+                               s->size = 0;
+                               return err;
+                       }
+               }
+               s->size = st.st_size;
+               if (!s->size)
+                       goto empty;
+               if (size_only)
+                       return 0;
+               if (S_ISLNK(st.st_mode)) {
+                       int ret;
+                       s->data = xmalloc(s->size);
+                       s->should_free = 1;
+                       ret = readlink(s->path, s->data, s->size);
+                       if (ret < 0) {
+                               free(s->data);
+                               goto err_empty;
+                       }
+                       return 0;
+               }
+               fd = open(s->path, O_RDONLY);
+               if (fd < 0)
+                       goto err_empty;
+               s->data = mmap(NULL, s->size, PROT_READ, MAP_PRIVATE, fd, 0);
+               close(fd);
+               if (s->data == MAP_FAILED)
+                       goto err_empty;
+               s->should_munmap = 1;
+       }
+       else {
+               char type[20];
+               struct sha1_size_cache *e;
+
+               if (size_only) {
+                       e = locate_size_cache(s->sha1, 1, 0);
+                       if (e) {
+                               s->size = e->size;
+                               return 0;
+                       }
+                       if (!sha1_object_info(s->sha1, type, &s->size))
+                               locate_size_cache(s->sha1, 0, s->size);
+               }
+               else {
+                       s->data = read_sha1_file(s->sha1, type, &s->size);
+                       s->should_free = 1;
+               }
+       }
+       return 0;
+}
+
+void diff_free_filespec_data(struct diff_filespec *s)
+{
+       if (s->should_free)
+               free(s->data);
+       else if (s->should_munmap)
+               munmap(s->data, s->size);
+       s->should_free = s->should_munmap = 0;
+       s->data = NULL;
+}
+
+static void prep_temp_blob(struct diff_tempfile *temp,
+                          void *blob,
+                          unsigned long size,
+                          unsigned char *sha1,
+                          int mode)
+{
+       int fd;
+
+       fd = git_mkstemp(temp->tmp_path, TEMPFILE_PATH_LEN, ".diff_XXXXXX");
+       if (fd < 0)
+               die("unable to create temp-file");
+       if (write(fd, blob, size) != size)
+               die("unable to write temp-file");
+       close(fd);
+       temp->name = temp->tmp_path;
+       strcpy(temp->hex, sha1_to_hex(sha1));
+       temp->hex[40] = 0;
+       sprintf(temp->mode, "%06o", mode);
+}
+
+static void prepare_temp_file(const char *name,
+                             struct diff_tempfile *temp,
+                             struct diff_filespec *one)
+{
+       if (!DIFF_FILE_VALID(one)) {
+       not_a_valid_file:
+               /* A '-' entry produces this for file-2, and
+                * a '+' entry produces this for file-1.
+                */
+               temp->name = "/dev/null";
+               strcpy(temp->hex, ".");
+               strcpy(temp->mode, ".");
+               return;
+       }
+
+       if (!one->sha1_valid ||
+           work_tree_matches(name, one->sha1)) {
+               struct stat st;
+               if (lstat(name, &st) < 0) {
+                       if (errno == ENOENT)
+                               goto not_a_valid_file;
+                       die("stat(%s): %s", name, strerror(errno));
+               }
+               if (S_ISLNK(st.st_mode)) {
+                       int ret;
+                       char *buf, buf_[1024];
+                       buf = ((sizeof(buf_) < st.st_size) ?
+                              xmalloc(st.st_size) : buf_);
+                       ret = readlink(name, buf, st.st_size);
+                       if (ret < 0)
+                               die("readlink(%s)", name);
+                       prep_temp_blob(temp, buf, st.st_size,
+                                      (one->sha1_valid ?
+                                       one->sha1 : null_sha1),
+                                      (one->sha1_valid ?
+                                       one->mode : S_IFLNK));
+               }
+               else {
+                       /* we can borrow from the file in the work tree */
+                       temp->name = name;
+                       if (!one->sha1_valid)
+                               strcpy(temp->hex, sha1_to_hex(null_sha1));
+                       else
+                               strcpy(temp->hex, sha1_to_hex(one->sha1));
+                       /* Even though we may sometimes borrow the
+                        * contents from the work tree, we always want
+                        * one->mode.  mode is trustworthy even when
+                        * !(one->sha1_valid), as long as
+                        * DIFF_FILE_VALID(one).
+                        */
+                       sprintf(temp->mode, "%06o", one->mode);
+               }
+               return;
+       }
+       else {
+               if (diff_populate_filespec(one, 0))
+                       die("cannot read data blob for %s", one->path);
+               prep_temp_blob(temp, one->data, one->size,
+                              one->sha1, one->mode);
+       }
+}
+
+static void remove_tempfile(void)
+{
+       int i;
+
+       for (i = 0; i < 2; i++)
+               if (diff_temp[i].name == diff_temp[i].tmp_path) {
+                       unlink(diff_temp[i].name);
+                       diff_temp[i].name = NULL;
+               }
+}
+
+static void remove_tempfile_on_signal(int signo)
+{
+       remove_tempfile();
+}
+
+/* An external diff command takes:
+ *
+ * diff-cmd name infile1 infile1-sha1 infile1-mode \
+ *               infile2 infile2-sha1 infile2-mode [ rename-to ]
+ *
+ */
+static void run_external_diff(const char *pgm,
+                             const char *name,
+                             const char *other,
+                             struct diff_filespec *one,
+                             struct diff_filespec *two,
+                             const char *xfrm_msg,
+                             int complete_rewrite)
+{
+       struct diff_tempfile *temp = diff_temp;
+       pid_t pid;
+       int status;
+       static int atexit_asked = 0;
+
+       if (one && two) {
+               prepare_temp_file(name, &temp[0], one);
+               prepare_temp_file(other ? : name, &temp[1], two);
+               if (! atexit_asked &&
+                   (temp[0].name == temp[0].tmp_path ||
+                    temp[1].name == temp[1].tmp_path)) {
+                       atexit_asked = 1;
+                       atexit(remove_tempfile);
+               }
+               signal(SIGINT, remove_tempfile_on_signal);
+       }
+
+       fflush(NULL);
+       pid = fork();
+       if (pid < 0)
+               die("unable to fork");
+       if (!pid) {
+               if (pgm) {
+                       if (one && two) {
+                               const char *exec_arg[10];
+                               const char **arg = &exec_arg[0];
+                               *arg++ = pgm;
+                               *arg++ = name;
+                               *arg++ = temp[0].name;
+                               *arg++ = temp[0].hex;
+                               *arg++ = temp[0].mode;
+                               *arg++ = temp[1].name;
+                               *arg++ = temp[1].hex;
+                               *arg++ = temp[1].mode;
+                               if (other) {
+                                       *arg++ = other;
+                                       *arg++ = xfrm_msg;
+                               }
+                               *arg = NULL;
+                               execvp(pgm, (char *const*) exec_arg);
+                       }
+                       else
+                               execlp(pgm, pgm, name, NULL);
+               }
+               /*
+                * otherwise we use the built-in one.
+                */
+               if (one && two)
+                       builtin_diff(name, other ? : name, temp, xfrm_msg,
+                                    complete_rewrite);
+               else
+                       printf("* Unmerged path %s\n", name);
+               exit(0);
+       }
+       if (waitpid(pid, &status, 0) < 0 ||
+           !WIFEXITED(status) || WEXITSTATUS(status)) {
+               /* Earlier we did not check the exit status because
+                * diff exits non-zero if files are different, and
+                * we are not interested in knowing that.  It was a
+                * mistake which made it harder to quit a diff-*
+                * session that uses the git-apply-patch-script as
+                * the GIT_EXTERNAL_DIFF.  A custom GIT_EXTERNAL_DIFF
+                * should also exit non-zero only when it wants to
+                * abort the entire diff-* session.
+                */
+               remove_tempfile();
+               fprintf(stderr, "external diff died, stopping at %s.\n", name);
+               exit(1);
+       }
+       remove_tempfile();
+}
+
+static void run_diff(struct diff_filepair *p)
+{
+       const char *pgm = external_diff();
+       char msg_[PATH_MAX*2+200], *xfrm_msg;
+       struct diff_filespec *one;
+       struct diff_filespec *two;
+       const char *name;
+       const char *other;
+       int complete_rewrite = 0;
+
+       if (DIFF_PAIR_UNMERGED(p)) {
+               /* unmerged */
+               run_external_diff(pgm, p->one->path, NULL, NULL, NULL, NULL,
+                                 0);
+               return;
+       }
+
+       name = p->one->path;
+       other = (strcmp(name, p->two->path) ? p->two->path : NULL);
+       one = p->one; two = p->two;
+       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_;
+               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_;
+               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_;
+                       complete_rewrite = 1;
+                       break;
+               }
+               /* fallthru */
+       default:
+               xfrm_msg = NULL;
+       }
+
+       if (!pgm &&
+           DIFF_FILE_VALID(one) && DIFF_FILE_VALID(two) &&
+           (S_IFMT & one->mode) != (S_IFMT & two->mode)) {
+               /* a filepair that changes between file and symlink
+                * needs to be split into deletion and creation.
+                */
+               struct diff_filespec *null = alloc_filespec(two->path);
+               run_external_diff(NULL, name, other, one, null, xfrm_msg, 0);
+               free(null);
+               null = alloc_filespec(one->path);
+               run_external_diff(NULL, name, other, null, two, xfrm_msg, 0);
+               free(null);
+       }
+       else
+               run_external_diff(pgm, name, other, one, two, xfrm_msg,
+                                 complete_rewrite);
+}
+
+void diff_setup(int flags)
+{
+       if (flags & DIFF_SETUP_REVERSE)
+               reverse_diff = 1;
+       if (flags & 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
+                        * it does not smudge active_cache or active_nr
+                        * when it fails, so we do not have to worry about
+                        * cleaning it up oufselves either.
+                        */
+                       read_cache();
+       }
+       if (flags & DIFF_SETUP_USE_SIZE_CACHE)
+               use_size_cache = 1;
+       
+}
+
+static int parse_num(const char **cp_p)
+{
+       int num, scale, ch, cnt;
+       const char *cp = *cp_p;
+
+       cnt = num = 0;
+       scale = 1;
+       while ('0' <= (ch = *cp) && ch <= '9') {
+               if (cnt++ < 5) {
+                       /* We simply ignore more than 5 digits precision. */
+                       scale *= 10;
+                       num = num * 10 + ch - '0';
+               }
+               *cp++;
+       }
+       *cp_p = cp;
+
+       /* user says num divided by scale and we say internally that
+        * is MAX_SCORE * num / scale.
+        */
+       return (MAX_SCORE * num / scale);
+}
+
+int diff_scoreopt_parse(const char *opt)
+{
+       int opt1, opt2, cmd;
+
+       if (*opt++ != '-')
+               return -1;
+       cmd = *opt++;
+       if (cmd != 'M' && cmd != 'C' && cmd != 'B')
+               return -1; /* that is not a -M, -C nor -B option */
+
+       opt1 = parse_num(&opt);
+       if (cmd != 'B')
+               opt2 = 0;
+       else {
+               if (*opt == 0)
+                       opt2 = 0;
+               else if (*opt != '/')
+                       return -1; /* we expect -B80/99 or -B80 */
+               else {
+                       opt++;
+                       opt2 = parse_num(&opt);
+               }
+       }
+       if (*opt != 0)
+               return -1;
+       return opt1 | (opt2 << 16);
+}
+
+struct diff_queue_struct diff_queued_diff;
+
+void diff_q(struct diff_queue_struct *queue, struct diff_filepair *dp)
+{
+       if (queue->alloc <= queue->nr) {
+               queue->alloc = alloc_nr(queue->alloc);
+               queue->queue = xrealloc(queue->queue,
+                                       sizeof(dp) * queue->alloc);
+       }
+       queue->queue[queue->nr++] = dp;
+}
+
+struct diff_filepair *diff_queue(struct diff_queue_struct *queue,
+                                struct diff_filespec *one,
+                                struct diff_filespec *two)
+{
+       struct diff_filepair *dp = xmalloc(sizeof(*dp));
+       dp->one = one;
+       dp->two = two;
+       dp->score = 0;
+       dp->status = 0;
+       dp->source_stays = 0;
+       dp->broken_pair = 0;
+       diff_q(queue, dp);
+       return dp;
+}
+
+void diff_free_filepair(struct diff_filepair *p)
+{
+       diff_free_filespec_data(p->one);
+       diff_free_filespec_data(p->two);
+       free(p);
+}
+
+static void diff_flush_raw(struct diff_filepair *p,
+                          int line_termination,
+                          int inter_name_termination)
+{
+       int two_paths;
+       char status[10];
+
+       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);
+       }
+
+       if (p->score)
+               sprintf(status, "%c%03d", p->status,
+                       (int)(0.5 + p->score * 100.0/MAX_SCORE));
+       else {
+               status[0] = p->status;
+               status[1] = 0;
+       }
+       switch (p->status) {
+       case DIFF_STATUS_COPIED:
+       case DIFF_STATUS_RENAMED:
+               two_paths = 1;
+               break;
+       case DIFF_STATUS_ADDED:
+       case DIFF_STATUS_DELETED:
+               two_paths = 0;
+               break;
+       default:
+               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 (two_paths)
+               printf("%c%s", inter_name_termination, p->two->path);
+       putchar(line_termination);
+}
+
+static void diff_flush_name(struct diff_filepair *p,
+                           int line_termination)
+{
+       printf("%s%c", p->two->path, line_termination);
+}
+
+int diff_unmodified_pair(struct diff_filepair *p)
+{
+       /* This function is written stricter than necessary to support
+        * the currently implemented transformers, but the idea is to
+        * let transformers to produce diff_filepairs any way they want,
+        * and filter and clean them up here before producing the output.
+        */
+       struct diff_filespec *one, *two;
+
+       if (DIFF_PAIR_UNMERGED(p))
+               return 0; /* unmerged is interesting */
+
+       one = p->one;
+       two = p->two;
+
+       /* deletion, addition, mode or type change
+        * and rename are all interesting.
+        */
+       if (DIFF_FILE_VALID(one) != DIFF_FILE_VALID(two) ||
+           DIFF_PAIR_MODE_CHANGED(p) ||
+           strcmp(one->path, two->path))
+               return 0;
+
+       /* both are valid and point at the same path.  that is, we are
+        * dealing with a change.
+        */
+       if (one->sha1_valid && two->sha1_valid &&
+           !memcmp(one->sha1, two->sha1, sizeof(one->sha1)))
+               return 1; /* no change */
+       if (!one->sha1_valid && !two->sha1_valid)
+               return 1; /* both look at the same file on the filesystem. */
+       return 0;
+}
+
+static void diff_flush_patch(struct diff_filepair *p)
+{
+       if (diff_unmodified_pair(p))
+               return;
+
+       if ((DIFF_FILE_VALID(p->one) && S_ISDIR(p->one->mode)) ||
+           (DIFF_FILE_VALID(p->two) && S_ISDIR(p->two->mode)))
+               return; /* no tree diffs in patch format */ 
+
+       run_diff(p);
+}
+
+int diff_queue_is_empty(void)
+{
+       struct diff_queue_struct *q = &diff_queued_diff;
+       int i;
+       for (i = 0; i < q->nr; i++)
+               if (!diff_unmodified_pair(q->queue[i]))
+                       return 0;
+       return 1;
+}
+
+#if DIFF_DEBUG
+void diff_debug_filespec(struct diff_filespec *s, int x, const char *one)
+{
+       fprintf(stderr, "queue[%d] %s (%s) %s %06o %s\n",
+               x, one ? : "",
+               s->path,
+               DIFF_FILE_VALID(s) ? "valid" : "invalid",
+               s->mode,
+               s->sha1_valid ? sha1_to_hex(s->sha1) : "");
+       fprintf(stderr, "queue[%d] %s size %lu flags %d\n",
+               x, one ? : "",
+               s->size, s->xfrm_flags);
+}
+
+void diff_debug_filepair(const struct diff_filepair *p, int i)
+{
+       diff_debug_filespec(p->one, i, "one");
+       diff_debug_filespec(p->two, i, "two");
+       fprintf(stderr, "score %d, status %c stays %d broken %d\n",
+               p->score, p->status ? : '?',
+               p->source_stays, p->broken_pair);
+}
+
+void diff_debug_queue(const char *msg, struct diff_queue_struct *q)
+{
+       int i;
+       if (msg)
+               fprintf(stderr, "%s\n", msg);
+       fprintf(stderr, "q->nr = %d\n", q->nr);
+       for (i = 0; i < q->nr; i++) {
+               struct diff_filepair *p = q->queue[i];
+               diff_debug_filepair(p, i);
+       }
+}
+#endif
+
+static void diff_resolve_rename_copy(void)
+{
+       int i, j;
+       struct diff_filepair *p, *pp;
+       struct diff_queue_struct *q = &diff_queued_diff;
+
+       diff_debug_queue("resolve-rename-copy", q);
+
+       for (i = 0; i < q->nr; i++) {
+               p = q->queue[i];
+               p->status = 0; /* undecided */
+               if (DIFF_PAIR_UNMERGED(p))
+                       p->status = DIFF_STATUS_UNMERGED;
+               else if (!DIFF_FILE_VALID(p->one))
+                       p->status = DIFF_STATUS_ADDED;
+               else if (!DIFF_FILE_VALID(p->two))
+                       p->status = DIFF_STATUS_DELETED;
+               else if (DIFF_PAIR_TYPE_CHANGED(p))
+                       p->status = DIFF_STATUS_TYPE_CHANGED;
+
+               /* from this point on, we are dealing with a pair
+                * whose both sides are valid and of the same type, i.e.
+                * either in-place edit or rename/copy edit.
+                */
+               else if (DIFF_PAIR_RENAME(p)) {
+                       if (p->source_stays) {
+                               p->status = DIFF_STATUS_COPIED;
+                               continue;
+                       }
+                       /* See if there is some other filepair that
+                        * copies from the same source as us.  If so
+                        * we are a copy.  Otherwise we are a rename.
+                        */
+                       for (j = i + 1; j < q->nr; j++) {
+                               pp = q->queue[j];
+                               if (strcmp(pp->one->path, p->one->path))
+                                       continue; /* not us */
+                               if (!DIFF_PAIR_RENAME(pp))
+                                       continue; /* not a rename/copy */
+                               /* pp is a rename/copy from the same source */
+                               p->status = DIFF_STATUS_COPIED;
+                               break;
+                       }
+                       if (!p->status)
+                               p->status = DIFF_STATUS_RENAMED;
+               }
+               else if (memcmp(p->one->sha1, p->two->sha1, 20) ||
+                        p->one->mode != p->two->mode)
+                       p->status = DIFF_STATUS_MODIFIED;
+               else {
+                       /* This is a "no-change" entry and should not
+                        * happen anymore, but prepare for broken callers.
+                        */
+                       error("feeding unmodified %s to diffcore",
+                             p->one->path);
+                       p->status = DIFF_STATUS_UNKNOWN;
+               }
+       }
+       diff_debug_queue("resolve-rename-copy done", q);
+}
+
+void diff_flush(int diff_output_style, int line_termination)
+{
+       struct diff_queue_struct *q = &diff_queued_diff;
+       int i;
+       int inter_name_termination = '\t';
+
+       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) ||
+                   (p->status == DIFF_STATUS_UNKNOWN))
+                       continue;
+               if (p->status == 0)
+                       die("internal error in diff-resolve-rename-copy");
+               switch (diff_output_style) {
+               case DIFF_FORMAT_PATCH:
+                       diff_flush_patch(p);
+                       break;
+               case DIFF_FORMAT_RAW:
+                       diff_flush_raw(p, line_termination,
+                                      inter_name_termination);
+                       break;
+               case DIFF_FORMAT_NAME:
+                       diff_flush_name(p, line_termination);
+                       break;
+               }
+       }
+       for (i = 0; i < q->nr; i++)
+               diff_free_filepair(q->queue[i]);
+       free(q->queue);
+       q->queue = NULL;
+       q->nr = q->alloc = 0;
+}
+
+static void diffcore_apply_filter(const char *filter)
+{
+       int i;
+       struct diff_queue_struct *q = &diff_queued_diff;
+       struct diff_queue_struct outq;
+       outq.queue = NULL;
+       outq.nr = outq.alloc = 0;
+
+       if (!filter)
+               return;
+
+       if (strchr(filter, DIFF_STATUS_FILTER_AON)) {
+               int found;
+               for (i = found = 0; !found && i < q->nr; i++) {
+                       struct diff_filepair *p = q->queue[i];
+                       if (((p->status == DIFF_STATUS_MODIFIED) &&
+                            ((p->score &&
+                              strchr(filter, DIFF_STATUS_FILTER_BROKEN)) ||
+                             (!p->score &&
+                              strchr(filter, DIFF_STATUS_MODIFIED)))) ||
+                           ((p->status != DIFF_STATUS_MODIFIED) &&
+                            strchr(filter, p->status)))
+                               found++;
+               }
+               if (found)
+                       return;
+
+               /* otherwise we will clear the whole queue
+                * by copying the empty outq at the end of this
+                * function, but first clear the current entries
+                * in the queue.
+                */
+               for (i = 0; i < q->nr; i++)
+                       diff_free_filepair(q->queue[i]);
+       }
+       else {
+               /* Only the matching ones */
+               for (i = 0; i < q->nr; i++) {
+                       struct diff_filepair *p = q->queue[i];
+
+                       if (((p->status == DIFF_STATUS_MODIFIED) &&
+                            ((p->score &&
+                              strchr(filter, DIFF_STATUS_FILTER_BROKEN)) ||
+                             (!p->score &&
+                              strchr(filter, DIFF_STATUS_MODIFIED)))) ||
+                           ((p->status != DIFF_STATUS_MODIFIED) &&
+                            strchr(filter, p->status)))
+                               diff_q(&outq, p);
+                       else
+                               diff_free_filepair(p);
+               }
+       }
+       free(q->queue);
+       *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)
+{
+       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)
+               diffcore_merge_broken();
+       if (pickaxe)
+               diffcore_pickaxe(pickaxe, pickaxe_opts);
+       if (orderfile)
+               diffcore_order(orderfile);
+       diff_resolve_rename_copy();
+       diffcore_apply_filter(filter);
+}
+
+
+void diffcore_std_no_resolve(const char **paths,
+                            const char *pickaxe, int pickaxe_opts,
+                            const char *orderfile,
+                            const char *filter)
+{
+       if (paths && paths[0])
+               diffcore_pathspec(paths);
+       if (pickaxe)
+               diffcore_pickaxe(pickaxe, pickaxe_opts);
+       if (orderfile)
+               diffcore_order(orderfile);
+       diffcore_apply_filter(filter);
+}
+
+void diff_addremove(int addremove, unsigned mode,
+                   const unsigned char *sha1,
+                   const char *base, const char *path)
+{
+       char concatpath[PATH_MAX];
+       struct diff_filespec *one, *two;
+
+       /* This may look odd, but it is a preparation for
+        * feeding "there are unchanged files which should
+        * not produce diffs, but when you are doing copy
+        * detection you would need them, so here they are"
+        * entries to the diff-core.  They will be prefixed
+        * with something like '=' or '*' (I haven't decided
+        * which but should not make any difference).
+        * Feeding the same new and old to diff_change() 
+        * also has the same effect.
+        * Before the final output happens, they are pruned after
+        * merged into rename/copy pairs as appropriate.
+        */
+       if (reverse_diff)
+               addremove = (addremove == '+' ? '-' :
+                            addremove == '-' ? '+' : addremove);
+
+       if (!path) path = "";
+       sprintf(concatpath, "%s%s", base, path);
+       one = alloc_filespec(concatpath);
+       two = alloc_filespec(concatpath);
+
+       if (addremove != '+')
+               fill_filespec(one, sha1, mode);
+       if (addremove != '-')
+               fill_filespec(two, sha1, 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,
+                const unsigned char *old_sha1,
+                const unsigned char *new_sha1,
+                const char *base, const char *path) 
+{
+       char concatpath[PATH_MAX];
+       struct diff_filespec *one, *two;
+
+       if (reverse_diff) {
+               unsigned tmp;
+               const unsigned char *tmp_c;
+               tmp = old_mode; old_mode = new_mode; new_mode = tmp;
+               tmp_c = old_sha1; old_sha1 = new_sha1; new_sha1 = tmp_c;
+       }
+       if (!path) path = "";
+       sprintf(concatpath, "%s%s", base, path);
+       one = alloc_filespec(concatpath);
+       two = alloc_filespec(concatpath);
+       fill_filespec(one, old_sha1, old_mode);
+       fill_filespec(two, new_sha1, new_mode);
+
+       diff_queue(&diff_queued_diff, one, two);
+}
+
+void diff_unmerge(const char *path)
+{
+       struct diff_filespec *one, *two;
+       one = alloc_filespec(path);
+       two = alloc_filespec(path);
+       diff_queue(&diff_queued_diff, one, two);
+}
diff --git a/diff.h b/diff.h
new file mode 100644 (file)
index 0000000..3deb7fa
--- /dev/null
+++ b/diff.h
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2005 Junio C Hamano
+ */
+#ifndef DIFF_H
+#define DIFF_H
+
+#define DIFF_FILE_CANON_MODE(mode) \
+       (S_ISREG(mode) ? (S_IFREG | ce_permissions(mode)) : \
+       S_ISLNK(mode) ? S_IFLNK : S_IFDIR)
+
+extern void diff_addremove(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_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 int diff_scoreopt_parse(const char *opt);
+
+#define DIFF_SETUP_REVERSE             1
+#define DIFF_SETUP_USE_CACHE           2
+#define DIFF_SETUP_USE_SIZE_CACHE      4
+
+extern void diff_setup(int flags);
+
+#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_no_resolve(const char **paths,
+                                   const char *pickaxe, int pickaxe_opts,
+                                   const char *orderfile, const char *filter);
+
+#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" \
+"  --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" \
+"  --pickaxe-all\n" \
+"              show all files diff when -S is used and hit is found.\n"
+
+extern int diff_queue_is_empty(void);
+
+#define DIFF_FORMAT_RAW                1
+#define DIFF_FORMAT_PATCH      2
+#define DIFF_FORMAT_NO_OUTPUT  3
+#define DIFF_FORMAT_NAME       4
+
+extern void diff_flush(int output_style, int line_terminator);
+
+/* diff-raw status letters */
+#define DIFF_STATUS_ADDED              'A'
+#define DIFF_STATUS_COPIED             'C'
+#define DIFF_STATUS_DELETED            'D'
+#define DIFF_STATUS_MODIFIED           'M'
+#define DIFF_STATUS_RENAMED            'R'
+#define DIFF_STATUS_TYPE_CHANGED       'T'
+#define DIFF_STATUS_UNKNOWN            'X'
+#define DIFF_STATUS_UNMERGED           'U'
+
+/* 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_BROKEN      'B'
+
+#endif /* DIFF_H */
diff --git a/diffcore-break.c b/diffcore-break.c
new file mode 100644 (file)
index 0000000..06f9a7f
--- /dev/null
@@ -0,0 +1,283 @@
+/*
+ * Copyright (C) 2005 Junio C Hamano
+ */
+#include "cache.h"
+#include "diff.h"
+#include "diffcore.h"
+#include "delta.h"
+#include "count-delta.h"
+
+static int should_break(struct diff_filespec *src,
+                       struct diff_filespec *dst,
+                       int break_score,
+                       int *merge_score_p)
+{
+       /* dst is recorded as a modification of src.  Are they so
+        * different that we are better off recording this as a pair
+        * of delete and create?
+        *
+        * There are two criteria used in this algorithm.  For the
+        * purposes of helping later rename/copy, we take both delete
+        * and insert into account and estimate the amount of "edit".
+        * If the edit is very large, we break this pair so that
+        * rename/copy can pick the pieces up to match with other
+        * files.
+        *
+        * On the other hand, we would want to ignore inserts for the
+        * pure "complete rewrite" detection.  As long as most of the
+        * existing contents were removed from the file, it is a
+        * complete rewrite, and if sizable chunk from the original
+        * still remains in the result, it is not a rewrite.  It does
+        * not matter how much or how little new material is added to
+        * the file.
+        *
+        * The score we leave for such a broken filepair uses the
+        * latter definition so that later clean-up stage can find the
+        * pieces that should not have been broken according to the
+        * latter definition after rename/copy runs, and merge the
+        * broken pair that have a score lower than given criteria
+        * back together.  The break operation itself happens
+        * according to the former definition.
+        *
+        * The minimum_edit parameter tells us when to break (the
+        * amount of "edit" required for us to consider breaking the
+        * pair).  We leave the amount of deletion in *merge_score_p
+        * when we return.
+        *
+        * The value we return is 1 if we want the pair to be broken,
+        * or 0 if we do not.
+        */
+       void *delta;
+       unsigned long delta_size, base_size, src_copied, literal_added;
+       int to_break = 0;
+
+       *merge_score_p = 0; /* assume no deletion --- "do not break"
+                            * is the default.
+                            */
+
+       if (!S_ISREG(src->mode) || !S_ISREG(dst->mode))
+               return 0; /* leave symlink rename alone */
+
+       if (diff_populate_filespec(src, 0) || diff_populate_filespec(dst, 0))
+               return 0; /* error but caught downstream */
+
+       base_size = ((src->size < dst->size) ? src->size : dst->size);
+
+       delta = diff_delta(src->data, src->size,
+                          dst->data, dst->size,
+                          &delta_size, 0);
+
+       /* Estimate the edit size by interpreting delta. */
+       if (count_delta(delta, delta_size,
+                       &src_copied, &literal_added)) {
+               free(delta);
+               return 0; /* we cannot tell */
+       }
+       free(delta);
+
+       /* Compute merge-score, which is "how much is removed
+        * from the source material".  The clean-up stage will
+        * merge the surviving pair together if the score is
+        * less than the minimum, after rename/copy runs.
+        */
+       if (src->size <= src_copied)
+               ; /* all copied, nothing removed */
+       else {
+               delta_size = src->size - src_copied;
+               *merge_score_p = delta_size * MAX_SCORE / src->size;
+       }
+       
+       /* Extent of damage, which counts both inserts and
+        * deletes.
+        */
+       if (src->size + literal_added <= src_copied)
+               delta_size = 0; /* avoid wrapping around */
+       else
+               delta_size = (src->size - src_copied) + literal_added;
+       
+       /* We break if the edit exceeds the minimum.
+        * i.e. (break_score / MAX_SCORE < delta_size / base_size)
+        */
+       if (break_score * base_size < delta_size * MAX_SCORE)
+               to_break = 1;
+
+       return to_break;
+}
+
+void diffcore_break(int break_score)
+{
+       struct diff_queue_struct *q = &diff_queued_diff;
+       struct diff_queue_struct outq;
+
+       /* When the filepair has this much edit (insert and delete),
+        * it is first considered to be a rewrite and broken into a
+        * create and delete filepair.  This is to help breaking a
+        * file that had too much new stuff added, possibly from
+        * moving contents from another file, so that rename/copy can
+        * match it with the other file.
+        *
+        * int break_score; we reuse incoming parameter for this.
+        */
+
+       /* After a pair is broken according to break_score and
+        * subjected to rename/copy, both of them may survive intact,
+        * due to lack of suitable rename/copy peer.  Or, the caller
+        * may be calling us without using rename/copy.  When that
+        * happens, we merge the broken pieces back into one
+        * modification together if the pair did not have more than
+        * this much delete.  For this computation, we do not take
+        * insert into account at all.  If you start from a 100-line
+        * file and delete 97 lines of it, it does not matter if you
+        * add 27 lines to it to make a new 30-line file or if you add
+        * 997 lines to it to make a 1000-line file.  Either way what
+        * you did was a rewrite of 97%.  On the other hand, if you
+        * delete 3 lines, keeping 97 lines intact, it does not matter
+        * if you add 3 lines to it to make a new 100-line file or if
+        * you add 903 lines to it to make a new 1000-line file.
+        * Either way you did a lot of additions and not a rewrite.
+        * This merge happens to catch the latter case.  A merge_score
+        * of 80% would be a good default value (a broken pair that
+        * has score lower than merge_score will be merged back
+        * together).
+        */
+       int merge_score;
+       int i;
+
+       /* See comment on DEFAULT_BREAK_SCORE and
+        * DEFAULT_MERGE_SCORE in diffcore.h
+        */
+       merge_score = (break_score >> 16) & 0xFFFF;
+       break_score = (break_score & 0xFFFF);
+
+       if (!break_score)
+               break_score = DEFAULT_BREAK_SCORE;
+       if (!merge_score)
+               merge_score = DEFAULT_MERGE_SCORE;
+
+       outq.nr = outq.alloc = 0;
+       outq.queue = NULL;
+
+       for (i = 0; i < q->nr; i++) {
+               struct diff_filepair *p = q->queue[i];
+               int score;
+
+               /* We deal only with in-place edit of non directory.
+                * We do not break anything else.
+                */
+               if (DIFF_FILE_VALID(p->one) && DIFF_FILE_VALID(p->two) &&
+                   !S_ISDIR(p->one->mode) && !S_ISDIR(p->two->mode) &&
+                   !strcmp(p->one->path, p->two->path)) {
+                       if (should_break(p->one, p->two,
+                                        break_score, &score) &&
+                           MINIMUM_BREAK_SIZE <= p->one->size) {
+                               /* Split this into delete and create */
+                               struct diff_filespec *null_one, *null_two;
+                               struct diff_filepair *dp;
+
+                               /* Set score to 0 for the pair that
+                                * needs to be merged back together
+                                * should they survive rename/copy.
+                                * Also we do not want to break very
+                                * small files.
+                                */
+                               if (score < merge_score)
+                                       score = 0;
+
+                               /* deletion of one */
+                               null_one = alloc_filespec(p->one->path);
+                               dp = diff_queue(&outq, p->one, null_one);
+                               dp->score = score;
+                               dp->broken_pair = 1;
+
+                               /* creation of two */
+                               null_two = alloc_filespec(p->two->path);
+                               dp = diff_queue(&outq, null_two, p->two);
+                               dp->score = score;
+                               dp->broken_pair = 1;
+
+                               free(p); /* not diff_free_filepair(), we are
+                                         * reusing one and two here.
+                                         */
+                               continue;
+                       }
+               }
+               diff_q(&outq, p);
+       }
+       free(q->queue);
+       *q = outq;
+
+       return;
+}
+
+static void merge_broken(struct diff_filepair *p,
+                        struct diff_filepair *pp,
+                        struct diff_queue_struct *outq)
+{
+       /* p and pp are broken pairs we want to merge */
+       struct diff_filepair *c = p, *d = pp, *dp;
+       if (DIFF_FILE_VALID(p->one)) {
+               /* this must be a delete half */
+               d = p; c = pp;
+       }
+       /* Sanity check */
+       if (!DIFF_FILE_VALID(d->one))
+               die("internal error in merge #1");
+       if (DIFF_FILE_VALID(d->two))
+               die("internal error in merge #2");
+       if (DIFF_FILE_VALID(c->one))
+               die("internal error in merge #3");
+       if (!DIFF_FILE_VALID(c->two))
+               die("internal error in merge #4");
+
+       dp = diff_queue(outq, d->one, c->two);
+       dp->score = p->score;
+       diff_free_filespec_data(d->two);
+       diff_free_filespec_data(c->one);
+       free(d);
+       free(c);
+}
+
+void diffcore_merge_broken(void)
+{
+       struct diff_queue_struct *q = &diff_queued_diff;
+       struct diff_queue_struct outq;
+       int i, j;
+
+       outq.nr = outq.alloc = 0;
+       outq.queue = NULL;
+
+       for (i = 0; i < q->nr; i++) {
+               struct diff_filepair *p = q->queue[i];
+               if (!p)
+                       /* we already merged this with its peer */
+                       continue;
+               else if (p->broken_pair &&
+                        !strcmp(p->one->path, p->two->path)) {
+                       /* If the peer also survived rename/copy, then
+                        * we merge them back together.
+                        */
+                       for (j = i + 1; j < q->nr; j++) {
+                               struct diff_filepair *pp = q->queue[j];
+                               if (pp->broken_pair &&
+                                   !strcmp(pp->one->path, pp->two->path) &&
+                                   !strcmp(p->one->path, pp->two->path)) {
+                                       /* Peer survived.  Merge them */
+                                       merge_broken(p, pp, &outq);
+                                       q->queue[j] = NULL;
+                                       break;
+                               }
+                       }
+                       if (q->nr <= j)
+                               /* The peer did not survive, so we keep
+                                * it in the output.
+                                */
+                               diff_q(&outq, p);
+               }
+               else
+                       diff_q(&outq, p);
+       }
+       free(q->queue);
+       *q = outq;
+
+       return;
+}
diff --git a/diffcore-order.c b/diffcore-order.c
new file mode 100644 (file)
index 0000000..b381223
--- /dev/null
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2005 Junio C Hamano
+ */
+#include "cache.h"
+#include "diff.h"
+#include "diffcore.h"
+#include <fnmatch.h>
+
+static char **order;
+static int order_cnt;
+
+static void prepare_order(const char *orderfile)
+{
+       int fd, cnt, pass;
+       void *map;
+       char *cp, *endp;
+       struct stat st;
+
+       if (order)
+               return;
+
+       fd = open(orderfile, O_RDONLY);
+       if (fd < 0)
+               return;
+       if (fstat(fd, &st)) {
+               close(fd);
+               return;
+       }
+       map = mmap(NULL, st.st_size, PROT_READ|PROT_WRITE, MAP_PRIVATE, fd, 0);
+       close(fd);
+       if (map == MAP_FAILED)
+               return;
+       endp = map + st.st_size;
+       for (pass = 0; pass < 2; pass++) {
+               cnt = 0;
+               cp = map;
+               while (cp < endp) {
+                       char *ep;
+                       for (ep = cp; ep < endp && *ep != '\n'; ep++)
+                               ;
+                       /* cp to ep has one line */
+                       if (*cp == '\n' || *cp == '#')
+                               ; /* comment */
+                       else if (pass == 0)
+                               cnt++;
+                       else {
+                               if (*ep == '\n') {
+                                       *ep = 0;
+                                       order[cnt] = cp;
+                               }
+                               else {
+                                       order[cnt] = xmalloc(ep-cp+1);
+                                       memcpy(order[cnt], cp, ep-cp);
+                                       order[cnt][ep-cp] = 0;
+                               }
+                               cnt++;
+                       }
+                       if (ep < endp)
+                               ep++;
+                       cp = ep;
+               }
+               if (pass == 0) {
+                       order_cnt = cnt;
+                       order = xmalloc(sizeof(*order) * cnt);
+               }
+       }
+}
+
+struct pair_order {
+       struct diff_filepair *pair;
+       int orig_order;
+       int order;
+};
+
+static int match_order(const char *path)
+{
+       int i;
+       char p[PATH_MAX];
+
+       for (i = 0; i < order_cnt; i++) {
+               strcpy(p, path);
+               while (p[0]) {
+                       char *cp;
+                       if (!fnmatch(order[i], p, 0))
+                               return i;
+                       cp = strrchr(p, '/');
+                       if (!cp)
+                               break;
+                       *cp = 0;
+               }
+       }
+       return order_cnt;
+}
+
+static int compare_pair_order(const void *a_, const void *b_)
+{
+       struct pair_order const *a, *b;
+       a = (struct pair_order const *)a_;
+       b = (struct pair_order const *)b_;
+       if (a->order != b->order)
+               return a->order - b->order;
+       return a->orig_order - b->orig_order;
+}
+
+void diffcore_order(const char *orderfile)
+{
+       struct diff_queue_struct *q = &diff_queued_diff;
+       struct pair_order *o = xmalloc(sizeof(*o) * q->nr);
+       int i;
+
+       prepare_order(orderfile);
+       for (i = 0; i < q->nr; i++) {
+               o[i].pair = q->queue[i];
+               o[i].orig_order = i;
+               o[i].order = match_order(o[i].pair->two->path);
+       }
+       qsort(o, q->nr, sizeof(*o), compare_pair_order);
+       for (i = 0; i < q->nr; i++)
+               q->queue[i] = o[i].pair;
+       free(o);
+       return;
+}
diff --git a/diffcore-pathspec.c b/diffcore-pathspec.c
new file mode 100644 (file)
index 0000000..a48acbc
--- /dev/null
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2005 Junio C Hamano
+ */
+#include "cache.h"
+#include "diff.h"
+#include "diffcore.h"
+
+struct path_spec {
+       const char *spec;
+       int len;
+};
+
+static int matches_pathspec(const char *name, struct path_spec *s, int cnt)
+{
+       int i;
+       int namelen;
+
+       if (cnt == 0)
+               return 1;
+
+       namelen = strlen(name);
+       for (i = 0; i < cnt; i++) {
+               int len = s[i].len;
+               if (namelen < len)
+                       continue;
+               if (memcmp(s[i].spec, name, len))
+                       continue;
+               if (s[i].spec[len-1] == '/' ||
+                   name[len] == 0 ||
+                   name[len] == '/')
+                       return 1;
+       }
+       return 0;
+}
+
+void diffcore_pathspec(const char **pathspec)
+{
+       struct diff_queue_struct *q = &diff_queued_diff;
+       int i, speccnt;
+       struct diff_queue_struct outq;
+       struct path_spec *spec;
+
+       outq.queue = NULL;
+       outq.nr = outq.alloc = 0;
+
+       for (i = 0; pathspec[i]; i++)
+               ;
+       speccnt = i;
+       spec = xmalloc(sizeof(*spec) * speccnt);
+       for (i = 0; pathspec[i]; i++) {
+               spec[i].spec = pathspec[i];
+               spec[i].len = strlen(pathspec[i]);
+       }
+
+       for (i = 0; i < q->nr; i++) {
+               struct diff_filepair *p = q->queue[i];
+               if (matches_pathspec(p->two->path, spec, speccnt))
+                       diff_q(&outq, p);
+               else
+                       diff_free_filepair(p);
+       }
+       free(q->queue);
+       *q = outq;
+       return;
+}
diff --git a/diffcore-pickaxe.c b/diffcore-pickaxe.c
new file mode 100644 (file)
index 0000000..50e46ab
--- /dev/null
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2005 Junio C Hamano
+ */
+#include "cache.h"
+#include "diff.h"
+#include "diffcore.h"
+
+static unsigned int contains(struct diff_filespec *one,
+                            const char *needle, unsigned long len)
+{
+       unsigned int cnt;
+       unsigned long offset, sz;
+       const char *data;
+       if (diff_populate_filespec(one, 0))
+               return 0;
+
+       sz = one->size;
+       data = one->data;
+       cnt = 0;
+
+       /* Yes, I've heard of strstr(), but the thing is *data may
+        * not be NUL terminated.  Sue me.
+        */
+       for (offset = 0; offset + len <= sz; offset++) {
+               /* we count non-overlapping occurrences of needle */
+               if (!memcmp(needle, data + offset, len)) {
+                       offset += len - 1;
+                       cnt++;
+               }
+       }
+       return cnt;
+}
+
+void diffcore_pickaxe(const char *needle, int opts)
+{
+       struct diff_queue_struct *q = &diff_queued_diff;
+       unsigned long len = strlen(needle);
+       int i, has_changes;
+       struct diff_queue_struct outq;
+       outq.queue = NULL;
+       outq.nr = outq.alloc = 0;
+
+       if (opts & DIFF_PICKAXE_ALL) {
+               /* Showing the whole changeset if needle exists */
+               for (i = has_changes = 0; !has_changes && i < q->nr; i++) {
+                       struct diff_filepair *p = q->queue[i];
+                       if (!DIFF_FILE_VALID(p->one)) {
+                               if (!DIFF_FILE_VALID(p->two))
+                                       continue; /* ignore unmerged */
+                               /* created */
+                               if (contains(p->two, needle, len))
+                                       has_changes++;
+                       }
+                       else if (!DIFF_FILE_VALID(p->two)) {
+                               if (contains(p->one, needle, len))
+                                       has_changes++;
+                       }
+                       else if (!diff_unmodified_pair(p) &&
+                                contains(p->one, needle, len) !=
+                                contains(p->two, needle, len))
+                               has_changes++;
+               }
+               if (has_changes)
+                       return; /* not munge the queue */
+
+               /* otherwise we will clear the whole queue
+                * by copying the empty outq at the end of this
+                * function, but first clear the current entries
+                * in the queue.
+                */
+               for (i = 0; i < q->nr; i++)
+                       diff_free_filepair(q->queue[i]);
+       }
+       else 
+               /* Showing only the filepairs that has the needle */
+               for (i = 0; i < q->nr; i++) {
+                       struct diff_filepair *p = q->queue[i];
+                       has_changes = 0;
+                       if (!DIFF_FILE_VALID(p->one)) {
+                               if (!DIFF_FILE_VALID(p->two))
+                                       ; /* ignore unmerged */
+                               /* created */
+                               else if (contains(p->two, needle, len))
+                                       has_changes = 1;
+                       }
+                       else if (!DIFF_FILE_VALID(p->two)) {
+                               if (contains(p->one, needle, len))
+                                       has_changes = 1;
+                       }
+                       else if (!diff_unmodified_pair(p) &&
+                                contains(p->one, needle, len) !=
+                                contains(p->two, needle, len))
+                               has_changes = 1;
+
+                       if (has_changes)
+                               diff_q(&outq, p);
+                       else
+                               diff_free_filepair(p);
+               }
+
+       free(q->queue);
+       *q = outq;
+       return;
+}
diff --git a/diffcore-rename.c b/diffcore-rename.c
new file mode 100644 (file)
index 0000000..6a52699
--- /dev/null
@@ -0,0 +1,416 @@
+/*
+ * Copyright (C) 2005 Junio C Hamano
+ */
+#include "cache.h"
+#include "diff.h"
+#include "diffcore.h"
+#include "delta.h"
+#include "count-delta.h"
+
+/* Table of rename/copy destinations */
+
+static struct diff_rename_dst {
+       struct diff_filespec *two;
+       struct diff_filepair *pair;
+} *rename_dst;
+static int rename_dst_nr, rename_dst_alloc;
+
+static struct diff_rename_dst *locate_rename_dst(struct diff_filespec *two,
+                                                int insert_ok)
+{
+       int first, last;
+
+       first = 0;
+       last = rename_dst_nr;
+       while (last > first) {
+               int next = (last + first) >> 1;
+               struct diff_rename_dst *dst = &(rename_dst[next]);
+               int cmp = strcmp(two->path, dst->two->path);
+               if (!cmp)
+                       return dst;
+               if (cmp < 0) {
+                       last = next;
+                       continue;
+               }
+               first = next+1;
+       }
+       /* not found */
+       if (!insert_ok)
+               return NULL;
+       /* insert to make it at "first" */
+       if (rename_dst_alloc <= rename_dst_nr) {
+               rename_dst_alloc = alloc_nr(rename_dst_alloc);
+               rename_dst = xrealloc(rename_dst,
+                                     rename_dst_alloc * sizeof(*rename_dst));
+       }
+       rename_dst_nr++;
+       if (first < rename_dst_nr)
+               memmove(rename_dst + first + 1, rename_dst + first,
+                       (rename_dst_nr - first - 1) * sizeof(*rename_dst));
+       rename_dst[first].two = two;
+       rename_dst[first].pair = NULL;
+       return &(rename_dst[first]);
+}
+
+/* Table of rename/copy src files */
+static struct diff_rename_src {
+       struct diff_filespec *one;
+       unsigned src_stays : 1;
+} *rename_src;
+static int rename_src_nr, rename_src_alloc;
+
+static struct diff_rename_src *register_rename_src(struct diff_filespec *one,
+                                                  int src_stays)
+{
+       int first, last;
+
+       first = 0;
+       last = rename_src_nr;
+       while (last > first) {
+               int next = (last + first) >> 1;
+               struct diff_rename_src *src = &(rename_src[next]);
+               int cmp = strcmp(one->path, src->one->path);
+               if (!cmp)
+                       return src;
+               if (cmp < 0) {
+                       last = next;
+                       continue;
+               }
+               first = next+1;
+       }
+
+       /* insert to make it at "first" */
+       if (rename_src_alloc <= rename_src_nr) {
+               rename_src_alloc = alloc_nr(rename_src_alloc);
+               rename_src = xrealloc(rename_src,
+                                     rename_src_alloc * sizeof(*rename_src));
+       }
+       rename_src_nr++;
+       if (first < rename_src_nr)
+               memmove(rename_src + first + 1, rename_src + first,
+                       (rename_src_nr - first - 1) * sizeof(*rename_src));
+       rename_src[first].one = one;
+       rename_src[first].src_stays = src_stays;
+       return &(rename_src[first]);
+}
+
+static int is_exact_match(struct diff_filespec *src, struct diff_filespec *dst)
+{
+       if (src->sha1_valid && dst->sha1_valid &&
+           !memcmp(src->sha1, dst->sha1, 20))
+               return 1;
+       if (diff_populate_filespec(src, 1) || diff_populate_filespec(dst, 1))
+               return 0;
+       if (src->size != dst->size)
+               return 0;
+       if (diff_populate_filespec(src, 0) || diff_populate_filespec(dst, 0))
+               return 0;
+       if (src->size == dst->size &&
+           !memcmp(src->data, dst->data, src->size))
+               return 1;
+       return 0;
+}
+
+struct diff_score {
+       int src; /* index in rename_src */
+       int dst; /* index in rename_dst */
+       int score;
+};
+
+static int estimate_similarity(struct diff_filespec *src,
+                              struct diff_filespec *dst,
+                              int minimum_score)
+{
+       /* src points at a file that existed in the original tree (or
+        * optionally a file in the destination tree) and dst points
+        * at a newly created file.  They may be quite similar, in which
+        * case we want to say src is renamed to dst or src is copied into
+        * dst, and then some edit has been applied to dst.
+        *
+        * Compare them and return how similar they are, representing
+        * the score as an integer between 0 and MAX_SCORE.
+        *
+        * When there is an exact match, it is considered a better
+        * match than anything else; the destination does not even
+        * call into this function in that case.
+        */
+       void *delta;
+       unsigned long delta_size, base_size, src_copied, literal_added;
+       unsigned long delta_limit;
+       int score;
+
+       /* We deal only with regular files.  Symlink renames are handled
+        * only when they are exact matches --- in other words, no edits
+        * after renaming.
+        */
+       if (!S_ISREG(src->mode) || !S_ISREG(dst->mode))
+               return 0;
+
+       delta_size = ((src->size < dst->size) ?
+                     (dst->size - src->size) : (src->size - dst->size));
+       base_size = ((src->size < dst->size) ? src->size : dst->size);
+
+       /* We would not consider edits that change the file size so
+        * drastically.  delta_size must be smaller than
+        * (MAX_SCORE-minimum_score)/MAX_SCORE * min(src->size, dst->size).
+        *
+        * Note that base_size == 0 case is handled here already
+        * and the final score computation below would not have a
+        * divide-by-zero issue.
+        */
+       if (base_size * (MAX_SCORE-minimum_score) < delta_size * MAX_SCORE)
+               return 0;
+
+       if (diff_populate_filespec(src, 0) || diff_populate_filespec(dst, 0))
+               return 0; /* error but caught downstream */
+
+       delta_limit = base_size * (MAX_SCORE-minimum_score) / MAX_SCORE;
+       delta = diff_delta(src->data, src->size,
+                          dst->data, dst->size,
+                          &delta_size, delta_limit);
+       if (!delta)
+               /* If delta_limit is exceeded, we have too much differences */
+               return 0;
+
+       /* A delta that has a lot of literal additions would have
+        * big delta_size no matter what else it does.
+        */
+       if (base_size * (MAX_SCORE-minimum_score) < delta_size * MAX_SCORE)
+               return 0;
+
+       /* Estimate the edit size by interpreting delta. */
+       if (count_delta(delta, delta_size, &src_copied, &literal_added)) {
+               free(delta);
+               return 0;
+       }
+       free(delta);
+
+       /* Extent of damage */
+       if (src->size + literal_added < src_copied)
+               delta_size = 0;
+       else
+               delta_size = (src->size - src_copied) + literal_added;
+
+       /*
+        * Now we will give some score to it.  100% edit gets 0 points
+        * and 0% edit gets MAX_SCORE points.
+        */
+       score = MAX_SCORE - (MAX_SCORE * delta_size / base_size); 
+       if (score < 0) return 0;
+       if (MAX_SCORE < score) return MAX_SCORE;
+       return score;
+}
+
+static void record_rename_pair(struct diff_queue_struct *renq,
+                              int dst_index, int src_index, int score)
+{
+       struct diff_filespec *one, *two, *src, *dst;
+       struct diff_filepair *dp;
+
+       if (rename_dst[dst_index].pair)
+               die("internal error: dst already matched.");
+
+       src = rename_src[src_index].one;
+       one = alloc_filespec(src->path);
+       fill_filespec(one, src->sha1, src->mode);
+
+       dst = rename_dst[dst_index].two;
+       two = alloc_filespec(dst->path);
+       fill_filespec(two, dst->sha1, dst->mode);
+
+       dp = diff_queue(renq, one, two);
+       dp->score = score;
+       dp->source_stays = rename_src[src_index].src_stays;
+       rename_dst[dst_index].pair = dp;
+}
+
+/*
+ * We sort the rename similarity matrix with the score, in descending
+ * order (the most similar first).
+ */
+static int score_compare(const void *a_, const void *b_)
+{
+       const struct diff_score *a = a_, *b = b_;
+       return b->score - a->score;
+}
+
+void diffcore_rename(int detect_rename, int minimum_score)
+{
+       struct diff_queue_struct *q = &diff_queued_diff;
+       struct diff_queue_struct renq, outq;
+       struct diff_score *mx;
+       int i, j;
+       int num_create, num_src, dst_cnt;
+
+       if (!minimum_score)
+               minimum_score = DEFAULT_RENAME_SCORE;
+       renq.queue = NULL;
+       renq.nr = renq.alloc = 0;
+
+       for (i = 0; i < q->nr; i++) {
+               struct diff_filepair *p = q->queue[i];
+               if (!DIFF_FILE_VALID(p->one))
+                       if (!DIFF_FILE_VALID(p->two))
+                               continue; /* unmerged */
+                       else
+                               locate_rename_dst(p->two, 1);
+               else if (!DIFF_FILE_VALID(p->two)) {
+                       /* If the source is a broken "delete", and
+                        * they did not really want to get broken,
+                        * that means the source actually stays.
+                        */
+                       int stays = (p->broken_pair && !p->score);
+                       register_rename_src(p->one, stays);
+               }
+               else if (detect_rename == DIFF_DETECT_COPY)
+                       register_rename_src(p->one, 1);
+       }
+       if (rename_dst_nr == 0)
+               goto cleanup; /* nothing to do */
+
+       /* We really want to cull the candidates list early
+        * with cheap tests in order to avoid doing deltas.
+        */
+       for (i = 0; i < rename_dst_nr; i++) {
+               struct diff_filespec *two = rename_dst[i].two;
+               for (j = 0; j < rename_src_nr; j++) {
+                       struct diff_filespec *one = rename_src[j].one;
+                       if (!is_exact_match(one, two))
+                               continue;
+                       record_rename_pair(&renq, i, j, MAX_SCORE);
+                       break; /* we are done with this entry */
+               }
+       }
+       diff_debug_queue("done detecting exact", &renq);
+
+       /* Have we run out the created file pool?  If so we can avoid
+        * doing the delta matrix altogether.
+        */
+       if (renq.nr == rename_dst_nr)
+               goto cleanup;
+
+       num_create = (rename_dst_nr - renq.nr);
+       num_src = rename_src_nr;
+       mx = xmalloc(sizeof(*mx) * num_create * num_src);
+       for (dst_cnt = i = 0; i < rename_dst_nr; i++) {
+               int base = dst_cnt * num_src;
+               struct diff_filespec *two = rename_dst[i].two;
+               if (rename_dst[i].pair)
+                       continue; /* dealt with exact match already. */
+               for (j = 0; j < rename_src_nr; j++) {
+                       struct diff_filespec *one = rename_src[j].one;
+                       struct diff_score *m = &mx[base+j];
+                       m->src = j;
+                       m->dst = i;
+                       m->score = estimate_similarity(one, two,
+                                                      minimum_score);
+               }
+               dst_cnt++;
+       }
+       /* cost matrix sorted by most to least similar pair */
+       qsort(mx, num_create * num_src, sizeof(*mx), score_compare);
+       for (i = 0; i < num_create * num_src; i++) {
+               struct diff_rename_dst *dst = &rename_dst[mx[i].dst];
+               if (dst->pair)
+                       continue; /* already done, either exact or fuzzy. */
+               if (mx[i].score < minimum_score)
+                       break; /* there is no more usable pair. */
+               record_rename_pair(&renq, mx[i].dst, mx[i].src, mx[i].score);
+       }
+       free(mx);
+       diff_debug_queue("done detecting fuzzy", &renq);
+
+ cleanup:
+       /* At this point, we have found some renames and copies and they
+        * are kept in renq.  The original list is still in *q.
+        */
+       outq.queue = NULL;
+       outq.nr = outq.alloc = 0;
+       for (i = 0; i < q->nr; i++) {
+               struct diff_filepair *p = q->queue[i];
+               struct diff_filepair *pair_to_free = NULL;
+
+               if (!DIFF_FILE_VALID(p->one) && DIFF_FILE_VALID(p->two)) {
+                       /*
+                        * Creation
+                        *
+                        * We would output this create record if it has
+                        * not been turned into a rename/copy already.
+                        */
+                       struct diff_rename_dst *dst =
+                               locate_rename_dst(p->two, 0);
+                       if (dst && dst->pair) {
+                               diff_q(&outq, dst->pair);
+                               pair_to_free = p;
+                       }
+                       else
+                               /* no matching rename/copy source, so
+                                * record this as a creation.
+                                */
+                               diff_q(&outq, p);
+               }
+               else if (DIFF_FILE_VALID(p->one) && !DIFF_FILE_VALID(p->two)) {
+                       /*
+                        * Deletion
+                        *
+                        * We would output this delete record if:
+                        *
+                        * (1) this is a broken delete and the counterpart
+                        *     broken create remains in the output; or
+                        * (2) this is not a broken delete, and renq does
+                        *     not have a rename/copy to move p->one->path
+                        *     out.
+                        *
+                        * Otherwise, the counterpart broken create
+                        * has been turned into a rename-edit; or
+                        * delete did not have a matching create to
+                        * begin with.
+                        */
+                       if (DIFF_PAIR_BROKEN(p)) {
+                               /* broken delete */
+                               struct diff_rename_dst *dst =
+                                       locate_rename_dst(p->one, 0);
+                               if (dst && dst->pair)
+                                       /* counterpart is now rename/copy */
+                                       pair_to_free = p;
+                       }
+                       else {
+                               for (j = 0; j < renq.nr; j++)
+                                       if (!strcmp(renq.queue[j]->one->path,
+                                                   p->one->path))
+                                               break;
+                               if (j < renq.nr)
+                                       /* this path remains */
+                                       pair_to_free = p;
+                       }
+
+                       if (pair_to_free)
+                               ;
+                       else
+                               diff_q(&outq, p);
+               }
+               else if (!diff_unmodified_pair(p))
+                       /* all the usual ones need to be kept */
+                       diff_q(&outq, p);
+               else
+                       /* no need to keep unmodified pairs */
+                       pair_to_free = p;
+
+               if (pair_to_free)
+                       diff_free_filepair(pair_to_free);
+       }
+       diff_debug_queue("done copying original", &outq);
+
+       free(renq.queue);
+       free(q->queue);
+       *q = outq;
+       diff_debug_queue("done collapsing", q);
+
+       free(rename_dst);
+       rename_dst = NULL;
+       rename_dst_nr = rename_dst_alloc = 0;
+       free(rename_src);
+       rename_src = NULL;
+       rename_src_nr = rename_src_alloc = 0;
+       return;
+}
diff --git a/diffcore.h b/diffcore.h
new file mode 100644 (file)
index 0000000..f1b5ca7
--- /dev/null
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2005 Junio C Hamano
+ */
+#ifndef _DIFFCORE_H_
+#define _DIFFCORE_H_
+
+/* This header file is internal between diff.c and its diff transformers
+ * (e.g. diffcore-rename, diffcore-pickaxe).  Never include this header
+ * in anything else.
+ */
+
+/* We internally use unsigned short as the score value,
+ * and rely on an int capable to hold 32-bits.  -B can take
+ * -Bmerge_score/break_score format and the two scores are
+ * passed around in one int (high 16-bit for merge and low 16-bit
+ * for break).
+ */
+#define MAX_SCORE 60000
+#define DEFAULT_RENAME_SCORE 30000 /* rename/copy similarity minimum (50%) */
+#define DEFAULT_BREAK_SCORE  30000 /* minimum for break to happen (50%)*/
+#define DEFAULT_MERGE_SCORE  48000 /* maximum for break-merge to happen (80%)*/
+
+#define MINIMUM_BREAK_SIZE     400 /* do not break a file smaller than this */
+
+struct diff_filespec {
+       unsigned char sha1[20];
+       char *path;
+       void *data;
+       unsigned long size;
+       int xfrm_flags;          /* for use by the xfrm */
+       unsigned short mode;     /* file mode */
+       unsigned sha1_valid : 1; /* if true, use sha1 and trust mode;
+                                 * if false, use the name and read from
+                                 * the filesystem.
+                                 */
+#define DIFF_FILE_VALID(spec) (((spec)->mode) != 0)
+       unsigned should_free : 1; /* data should be free()'ed */
+       unsigned should_munmap : 1; /* data should be munmap()'ed */
+};
+
+extern struct diff_filespec *alloc_filespec(const char *);
+extern void fill_filespec(struct diff_filespec *, const unsigned char *,
+                         unsigned short);
+
+extern int diff_populate_filespec(struct diff_filespec *, int);
+extern void diff_free_filespec_data(struct diff_filespec *);
+
+struct diff_filepair {
+       struct diff_filespec *one;
+       struct diff_filespec *two;
+       unsigned short int score;
+       char status; /* M C R N D U (see Documentation/diff-format.txt) */
+       unsigned source_stays : 1; /* all of R/C are copies */
+       unsigned broken_pair : 1;
+};
+#define DIFF_PAIR_UNMERGED(p) \
+       (!DIFF_FILE_VALID((p)->one) && !DIFF_FILE_VALID((p)->two))
+
+#define DIFF_PAIR_RENAME(p) (strcmp((p)->one->path, (p)->two->path))
+
+#define DIFF_PAIR_BROKEN(p) \
+       ( (!DIFF_FILE_VALID((p)->one) != !DIFF_FILE_VALID((p)->two)) && \
+         ((p)->broken_pair != 0) )
+
+#define DIFF_PAIR_TYPE_CHANGED(p) \
+       ((S_IFMT & (p)->one->mode) != (S_IFMT & (p)->two->mode))
+
+#define DIFF_PAIR_MODE_CHANGED(p) ((p)->one->mode != (p)->two->mode)
+
+extern void diff_free_filepair(struct diff_filepair *);
+
+extern int diff_unmodified_pair(struct diff_filepair *);
+
+struct diff_queue_struct {
+       struct diff_filepair **queue;
+       int alloc;
+       int nr;
+};
+
+extern struct diff_queue_struct diff_queued_diff;
+extern struct diff_filepair *diff_queue(struct diff_queue_struct *,
+                                       struct diff_filespec *,
+                                       struct diff_filespec *);
+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_merge_broken(void);
+extern void diffcore_pickaxe(const char *needle, int opts);
+extern void diffcore_order(const char *orderfile);
+
+#define DIFF_DEBUG 0
+#if DIFF_DEBUG
+void diff_debug_filespec(struct diff_filespec *, int, const char *);
+void diff_debug_filepair(const struct diff_filepair *, int);
+void diff_debug_queue(const char *, struct diff_queue_struct *);
+#else
+#define diff_debug_filespec(a,b,c) do {} while(0)
+#define diff_debug_filepair(a,b) do {} while(0)
+#define diff_debug_queue(a,b) do {} while(0)
+#endif
+
+#endif
diff --git a/entry.c b/entry.c
new file mode 100644 (file)
index 0000000..15b8fda
--- /dev/null
+++ b/entry.c
@@ -0,0 +1,156 @@
+#include <sys/types.h>
+#include <dirent.h>
+#include "cache.h"
+
+static void create_directories(const char *path, struct checkout *state)
+{
+       int len = strlen(path);
+       char *buf = xmalloc(len + 1);
+       const char *slash = path;
+
+       while ((slash = strchr(slash+1, '/')) != NULL) {
+               len = slash - path;
+               memcpy(buf, path, len);
+               buf[len] = 0;
+               if (mkdir(buf, 0777)) {
+                       if (errno == EEXIST) {
+                               struct stat st;
+                               if (len > state->base_dir_len && state->force && !unlink(buf) && !mkdir(buf, 0777))
+                                       continue;
+                               if (!stat(buf, &st) && S_ISDIR(st.st_mode))
+                                       continue; /* ok */
+                       }
+                       die("cannot create directory at %s", buf);
+               }
+       }
+       free(buf);
+}
+
+static void remove_subtree(const char *path)
+{
+       DIR *dir = opendir(path);
+       struct dirent *de;
+       char pathbuf[PATH_MAX];
+       char *name;
+       
+       if (!dir)
+               die("cannot opendir %s", path);
+       strcpy(pathbuf, path);
+       name = pathbuf + strlen(path);
+       *name++ = '/';
+       while ((de = readdir(dir)) != NULL) {
+               struct stat st;
+               if ((de->d_name[0] == '.') &&
+                   ((de->d_name[1] == 0) ||
+                    ((de->d_name[1] == '.') && de->d_name[2] == 0)))
+                       continue;
+               strcpy(name, de->d_name);
+               if (lstat(pathbuf, &st))
+                       die("cannot lstat %s", pathbuf);
+               if (S_ISDIR(st.st_mode))
+                       remove_subtree(pathbuf);
+               else if (unlink(pathbuf))
+                       die("cannot unlink %s", pathbuf);
+       }
+       closedir(dir);
+       if (rmdir(path))
+               die("cannot rmdir %s", path);
+}
+
+static int create_file(const char *path, unsigned int mode)
+{
+       mode = (mode & 0100) ? 0777 : 0666;
+       return open(path, O_WRONLY | O_TRUNC | O_CREAT | O_EXCL, mode);
+}
+
+static int write_entry(struct cache_entry *ce, const char *path, struct checkout *state)
+{
+       int fd;
+       void *new;
+       unsigned long size;
+       long wrote;
+       char type[20];
+       char target[1024];
+
+       new = read_sha1_file(ce->sha1, type, &size);
+       if (!new || strcmp(type, "blob")) {
+               if (new)
+                       free(new);
+               return error("git-checkout-cache: unable to read sha1 file of %s (%s)",
+                       path, sha1_to_hex(ce->sha1));
+       }
+       switch (ntohl(ce->ce_mode) & S_IFMT) {
+       case S_IFREG:
+               fd = create_file(path, ntohl(ce->ce_mode));
+               if (fd < 0) {
+                       free(new);
+                       return error("git-checkout-cache: unable to create file %s (%s)",
+                               path, strerror(errno));
+               }
+               wrote = write(fd, new, size);
+               close(fd);
+               free(new);
+               if (wrote != size)
+                       return error("git-checkout-cache: unable to write file %s", path);
+               break;
+       case S_IFLNK:
+               memcpy(target, new, size);
+               target[size] = '\0';
+               if (symlink(target, path)) {
+                       free(new);
+                       return error("git-checkout-cache: unable to create symlink %s (%s)",
+                               path, strerror(errno));
+               }
+               free(new);
+               break;
+       default:
+               free(new);
+               return error("git-checkout-cache: unknown file mode for %s", path);
+       }
+
+       if (state->refresh_cache) {
+               struct stat st;
+               lstat(ce->name, &st);
+               fill_stat_cache_info(ce, &st);
+       }
+       return 0;
+}
+
+int checkout_entry(struct cache_entry *ce, struct checkout *state)
+{
+       struct stat st;
+       static char path[MAXPATHLEN+1];
+       int len = state->base_dir_len;
+
+       memcpy(path, state->base_dir, len);
+       strcpy(path + len, ce->name);
+
+       if (!lstat(path, &st)) {
+               unsigned changed = ce_match_stat(ce, &st);
+               if (!changed)
+                       return 0;
+               if (!state->force) {
+                       if (!state->quiet)
+                               fprintf(stderr, "git-checkout-cache: %s already exists\n", path);
+                       return 0;
+               }
+
+               /*
+                * We unlink the old file, to get the new one with the
+                * right permissions (including umask, which is nasty
+                * to emulate by hand - much easier to let the system
+                * just do the right thing)
+                */
+               unlink(path);
+               if (S_ISDIR(st.st_mode)) {
+                       if (!state->force)
+                               return error("%s is a directory", path);
+                       remove_subtree(path);
+               }
+       } else if (state->not_new) 
+               return 0;
+       create_directories(path, state);
+       return write_entry(ce, path, state);
+}
+
+
diff --git a/epoch.c b/epoch.c
new file mode 100644 (file)
index 0000000..db44f5c
--- /dev/null
+++ b/epoch.c
@@ -0,0 +1,639 @@
+/*
+ * Copyright (c) 2005, Jon Seymour
+ *
+ * For more information about epoch theory on which this module is based,
+ * refer to http://blackcubes.dyndns.org/epoch/. That web page defines
+ * terms such as "epoch" and "minimal, non-linear epoch" and provides rationales
+ * for some of the algorithms used here.
+ *
+ */
+#include <stdlib.h>
+
+/* Provides arbitrary precision integers required to accurately represent
+ * fractional mass: */
+#include <openssl/bn.h>
+
+#include "cache.h"
+#include "commit.h"
+#include "epoch.h"
+
+struct fraction {
+       BIGNUM numerator;
+       BIGNUM denominator;
+};
+
+#define HAS_EXACTLY_ONE_PARENT(n) ((n)->parents && !(n)->parents->next)
+
+static BN_CTX *context = NULL;
+static struct fraction *one = NULL;
+static struct fraction *zero = NULL;
+
+static BN_CTX *get_BN_CTX(void)
+{
+       if (!context) {
+               context = BN_CTX_new();
+       }
+       return context;
+}
+
+static struct fraction *new_zero(void)
+{
+       struct fraction *result = xmalloc(sizeof(*result));
+       BN_init(&result->numerator);
+       BN_init(&result->denominator);
+       BN_zero(&result->numerator);
+       BN_one(&result->denominator);
+       return result;
+}
+
+static void clear_fraction(struct fraction *fraction)
+{
+       BN_clear(&fraction->numerator);
+       BN_clear(&fraction->denominator);
+}
+
+static struct fraction *divide(struct fraction *result, struct fraction *fraction, int divisor)
+{
+       BIGNUM bn_divisor;
+
+       BN_init(&bn_divisor);
+       BN_set_word(&bn_divisor, divisor);
+
+       BN_copy(&result->numerator, &fraction->numerator);
+       BN_mul(&result->denominator, &fraction->denominator, &bn_divisor, get_BN_CTX());
+
+       BN_clear(&bn_divisor);
+       return result;
+}
+
+static struct fraction *init_fraction(struct fraction *fraction)
+{
+       BN_init(&fraction->numerator);
+       BN_init(&fraction->denominator);
+       BN_zero(&fraction->numerator);
+       BN_one(&fraction->denominator);
+       return fraction;
+}
+
+static struct fraction *get_one(void)
+{
+       if (!one) {
+               one = new_zero();
+               BN_one(&one->numerator);
+       }
+       return one;
+}
+
+static struct fraction *get_zero(void)
+{
+       if (!zero) {
+               zero = new_zero();
+       }
+       return zero;
+}
+
+static struct fraction *copy(struct fraction *to, struct fraction *from)
+{
+       BN_copy(&to->numerator, &from->numerator);
+       BN_copy(&to->denominator, &from->denominator);
+       return to;
+}
+
+static struct fraction *add(struct fraction *result, struct fraction *left, struct fraction *right)
+{
+       BIGNUM a, b, gcd;
+
+       BN_init(&a);
+       BN_init(&b);
+       BN_init(&gcd);
+
+       BN_mul(&a, &left->numerator, &right->denominator, get_BN_CTX());
+       BN_mul(&b, &left->denominator, &right->numerator, get_BN_CTX());
+       BN_mul(&result->denominator, &left->denominator, &right->denominator, get_BN_CTX());
+       BN_add(&result->numerator, &a, &b);
+
+       BN_gcd(&gcd, &result->denominator, &result->numerator, get_BN_CTX());
+       BN_div(&result->denominator, NULL, &result->denominator, &gcd, get_BN_CTX());
+       BN_div(&result->numerator, NULL, &result->numerator, &gcd, get_BN_CTX());
+
+       BN_clear(&a);
+       BN_clear(&b);
+       BN_clear(&gcd);
+
+       return result;
+}
+
+static int compare(struct fraction *left, struct fraction *right)
+{
+       BIGNUM a, b;
+       int result;
+
+       BN_init(&a);
+       BN_init(&b);
+
+       BN_mul(&a, &left->numerator, &right->denominator, get_BN_CTX());
+       BN_mul(&b, &left->denominator, &right->numerator, get_BN_CTX());
+
+       result = BN_cmp(&a, &b);
+
+       BN_clear(&a);
+       BN_clear(&b);
+
+       return result;
+}
+
+struct mass_counter {
+       struct fraction seen;
+       struct fraction pending;
+};
+
+static struct mass_counter *new_mass_counter(struct commit *commit, struct fraction *pending)
+{
+       struct mass_counter *mass_counter = xmalloc(sizeof(*mass_counter));
+       memset(mass_counter, 0, sizeof(*mass_counter));
+
+       init_fraction(&mass_counter->seen);
+       init_fraction(&mass_counter->pending);
+
+       copy(&mass_counter->pending, pending);
+       copy(&mass_counter->seen, get_zero());
+
+       if (commit->object.util) {
+               die("multiple attempts to initialize mass counter for %s",
+                   sha1_to_hex(commit->object.sha1));
+       }
+
+       commit->object.util = mass_counter;
+
+       return mass_counter;
+}
+
+static void free_mass_counter(struct mass_counter *counter)
+{
+       clear_fraction(&counter->seen);
+       clear_fraction(&counter->pending);
+       free(counter);
+}
+
+/*
+ * Finds the base commit of a list of commits.
+ *
+ * One property of the commit being searched for is that every commit reachable
+ * from the base commit is reachable from the commits in the starting list only
+ * via paths that include the base commit.
+ *
+ * This algorithm uses a conservation of mass approach to find the base commit.
+ *
+ * We start by injecting one unit of mass into the graph at each
+ * of the commits in the starting list. Injecting mass into a commit
+ * is achieved by adding to its pending mass counter and, if it is not already
+ * enqueued, enqueuing the commit in a list of pending commits, in latest
+ * commit date first order.
+ *
+ * The algorithm then preceeds to visit each commit in the pending queue.
+ * Upon each visit, the pending mass is added to the mass already seen for that
+ * commit and then divided into N equal portions, where N is the number of
+ * parents of the commit being visited. The divided portions are then injected
+ * into each of the parents.
+ *
+ * The algorithm continues until we discover a commit which has seen all the
+ * mass originally injected or until we run out of things to do.
+ *
+ * If we find a commit that has seen all the original mass, we have found
+ * the common base of all the commits in the starting list.
+ *
+ * The algorithm does _not_ depend on accurate timestamps for correct operation.
+ * However, reasonably sane (e.g. non-random) timestamps are required in order
+ * to prevent an exponential performance characteristic. The occasional
+ * timestamp inaccuracy will not dramatically affect performance but may
+ * result in more nodes being processed than strictly necessary.
+ *
+ * This procedure sets *boundary to the address of the base commit. It returns
+ * non-zero if, and only if, there was a problem parsing one of the
+ * commits discovered during the traversal.
+ */
+static int find_base_for_list(struct commit_list *list, struct commit **boundary)
+{
+       int ret = 0;
+       struct commit_list *cleaner = NULL;
+       struct commit_list *pending = NULL;
+       struct fraction injected;
+       init_fraction(&injected);
+       *boundary = NULL;
+
+       for (; list; list = list->next) {
+               struct commit *item = list->item;
+
+               if (!item->object.util) {
+                       new_mass_counter(list->item, get_one());
+                       add(&injected, &injected, get_one());
+
+                       commit_list_insert(list->item, &cleaner);
+                       commit_list_insert(list->item, &pending);
+               }
+       }
+
+       while (!*boundary && pending && !ret) {
+               struct commit *latest = pop_commit(&pending);
+               struct mass_counter *latest_node = (struct mass_counter *) latest->object.util;
+               int num_parents;
+
+               if ((ret = parse_commit(latest)))
+                       continue;
+               add(&latest_node->seen, &latest_node->seen, &latest_node->pending);
+
+               num_parents = count_parents(latest);
+               if (num_parents) {
+                       struct fraction distribution;
+                       struct commit_list *parents;
+
+                       divide(init_fraction(&distribution), &latest_node->pending, num_parents);
+
+                       for (parents = latest->parents; parents; parents = parents->next) {
+                               struct commit *parent = parents->item;
+                               struct mass_counter *parent_node = (struct mass_counter *) parent->object.util;
+
+                               if (!parent_node) {
+                                       parent_node = new_mass_counter(parent, &distribution);
+                                       insert_by_date(parent, &pending);
+                                       commit_list_insert(parent, &cleaner);
+                               } else {
+                                       if (!compare(&parent_node->pending, get_zero()))
+                                               insert_by_date(parent, &pending);
+                                       add(&parent_node->pending, &parent_node->pending, &distribution);
+                               }
+                       }
+
+                       clear_fraction(&distribution);
+               }
+
+               if (!compare(&latest_node->seen, &injected))
+                       *boundary = latest;
+               copy(&latest_node->pending, get_zero());
+       }
+
+       while (cleaner) {
+               struct commit *next = pop_commit(&cleaner);
+               free_mass_counter((struct mass_counter *) next->object.util);
+               next->object.util = NULL;
+       }
+
+       if (pending)
+               free_commit_list(pending);
+
+       clear_fraction(&injected);
+       return ret;
+}
+
+
+/*
+ * Finds the base of an minimal, non-linear epoch, headed at head, by
+ * applying the find_base_for_list to a list consisting of the parents
+ */
+static int find_base(struct commit *head, struct commit **boundary)
+{
+       int ret = 0;
+       struct commit_list *pending = NULL;
+       struct commit_list *next;
+
+       for (next = head->parents; next; next = next->next) {
+               commit_list_insert(next->item, &pending);
+       }
+       ret = find_base_for_list(pending, boundary);
+       free_commit_list(pending);
+
+       return ret;
+}
+
+/*
+ * This procedure traverses to the boundary of the first epoch in the epoch
+ * sequence of the epoch headed at head_of_epoch. This is either the end of
+ * the maximal linear epoch or the base of a minimal non-linear epoch.
+ *
+ * The queue of pending nodes is sorted in reverse date order and each node
+ * is currently in the queue at most once.
+ */
+static int find_next_epoch_boundary(struct commit *head_of_epoch, struct commit **boundary)
+{
+       int ret;
+       struct commit *item = head_of_epoch;
+
+       ret = parse_commit(item);
+       if (ret)
+               return ret;
+
+       if (HAS_EXACTLY_ONE_PARENT(item)) {
+               /*
+                * We are at the start of a maximimal linear epoch.
+                * Traverse to the end.
+                */
+               while (HAS_EXACTLY_ONE_PARENT(item) && !ret) {
+                       item = item->parents->item;
+                       ret = parse_commit(item);
+               }
+               *boundary = item;
+
+       } else {
+               /*
+                * Otherwise, we are at the start of a minimal, non-linear
+                * epoch - find the common base of all parents.
+                */
+               ret = find_base(item, boundary);
+       }
+
+       return ret;
+}
+
+/*
+ * Returns non-zero if parent is known to be a parent of child.
+ */
+static int is_parent_of(struct commit *parent, struct commit *child)
+{
+       struct commit_list *parents;
+       for (parents = child->parents; parents; parents = parents->next) {
+               if (!memcmp(parent->object.sha1, parents->item->object.sha1,
+                           sizeof(parents->item->object.sha1)))
+                       return 1;
+       }
+       return 0;
+}
+
+/*
+ * Pushes an item onto the merge order stack. If the top of the stack is
+ * marked as being a possible "break", we check to see whether it actually
+ * is a break.
+ */
+static void push_onto_merge_order_stack(struct commit_list **stack, struct commit *item)
+{
+       struct commit_list *top = *stack;
+       if (top && (top->item->object.flags & DISCONTINUITY)) {
+               if (is_parent_of(top->item, item)) {
+                       top->item->object.flags &= ~DISCONTINUITY;
+               }
+       }
+       commit_list_insert(item, stack);
+}
+
+/*
+ * Marks all interesting, visited commits reachable from this commit
+ * as uninteresting. We stop recursing when we reach the epoch boundary,
+ * an unvisited node or a node that has already been marking uninteresting.
+ *
+ * This doesn't actually mark all ancestors between the start node and the
+ * epoch boundary uninteresting, but does ensure that they will eventually
+ * be marked uninteresting when the main sort_first_epoch() traversal
+ * eventually reaches them.
+ */
+static void mark_ancestors_uninteresting(struct commit *commit)
+{
+       unsigned int flags = commit->object.flags;
+       int visited = flags & VISITED;
+       int boundary = flags & BOUNDARY;
+       int uninteresting = flags & UNINTERESTING;
+       struct commit_list *next;
+
+       commit->object.flags |= UNINTERESTING;
+
+       /*
+        * We only need to recurse if
+        *      we are not on the boundary and
+        *      we have not already been marked uninteresting and
+        *      we have already been visited.
+        *
+        * The main sort_first_epoch traverse will mark unreachable
+        * all uninteresting, unvisited parents as they are visited
+        * so there is no need to duplicate that traversal here.
+        *
+        * Similarly, if we are already marked uninteresting
+        * then either all ancestors have already been marked
+        * uninteresting or will be once the sort_first_epoch
+        * traverse reaches them.
+        */
+
+       if (uninteresting || boundary || !visited)
+               return;
+
+       for (next = commit->parents; next; next = next->next)
+               mark_ancestors_uninteresting(next->item);
+}
+
+/*
+ * Sorts the nodes of the first epoch of the epoch sequence of the epoch headed at head
+ * into merge order.
+ */
+static void sort_first_epoch(struct commit *head, struct commit_list **stack)
+{
+       struct commit_list *parents;
+
+       head->object.flags |= VISITED;
+
+       /*
+        * TODO: By sorting the parents in a different order, we can alter the
+        * merge order to show contemporaneous changes in parallel branches
+        * occurring after "local" changes. This is useful for a developer
+        * when a developer wants to see all changes that were incorporated
+        * into the same merge as her own changes occur after her own
+        * changes.
+        */
+
+       for (parents = head->parents; parents; parents = parents->next) {
+               struct commit *parent = parents->item;
+
+               if (head->object.flags & UNINTERESTING) {
+                       /*
+                        * Propagates the uninteresting bit to all parents.
+                        * if we have already visited this parent, then
+                        * the uninteresting bit will be propagated to each
+                        * reachable commit that is still not marked
+                        * uninteresting and won't otherwise be reached.
+                        */
+                       mark_ancestors_uninteresting(parent);
+               }
+
+               if (!(parent->object.flags & VISITED)) {
+                       if (parent->object.flags & BOUNDARY) {
+                               if (*stack) {
+                                       die("something else is on the stack - %s",
+                                           sha1_to_hex((*stack)->item->object.sha1));
+                               }
+                               push_onto_merge_order_stack(stack, parent);
+                               parent->object.flags |= VISITED;
+
+                       } else {
+                               sort_first_epoch(parent, stack);
+                               if (parents) {
+                                       /*
+                                        * This indicates a possible
+                                        * discontinuity it may not be be
+                                        * actual discontinuity if the head
+                                        * of parent N happens to be the tail
+                                        * of parent N+1.
+                                        *
+                                        * The next push onto the stack will
+                                        * resolve the question.
+                                        */
+                                       (*stack)->item->object.flags |= DISCONTINUITY;
+                               }
+                       }
+               }
+       }
+
+       push_onto_merge_order_stack(stack, head);
+}
+
+/*
+ * Emit the contents of the stack.
+ *
+ * The stack is freed and replaced by NULL.
+ *
+ * Sets the return value to STOP if no further output should be generated.
+ */
+static int emit_stack(struct commit_list **stack, emitter_func emitter, int include_last)
+{
+       unsigned int seen = 0;
+       int action = CONTINUE;
+
+       while (*stack && (action != STOP)) {
+               struct commit *next = pop_commit(stack);
+               seen |= next->object.flags;
+               if (*stack || include_last) {
+                       if (!*stack) 
+                               next->object.flags |= BOUNDARY;
+                       action = emitter(next);
+               }
+       }
+
+       if (*stack) {
+               free_commit_list(*stack);
+               *stack = NULL;
+       }
+
+       return (action == STOP || (seen & UNINTERESTING)) ? STOP : CONTINUE;
+}
+
+/*
+ * Sorts an arbitrary epoch into merge order by sorting each epoch
+ * of its epoch sequence into order.
+ *
+ * Note: this algorithm currently leaves traces of its execution in the
+ * object flags of nodes it discovers. This should probably be fixed.
+ */
+static int sort_in_merge_order(struct commit *head_of_epoch, emitter_func emitter)
+{
+       struct commit *next = head_of_epoch;
+       int ret = 0;
+       int action = CONTINUE;
+
+       ret = parse_commit(head_of_epoch);
+
+       next->object.flags |= BOUNDARY;
+
+       while (next && next->parents && !ret && (action != STOP)) {
+               struct commit *base = NULL;
+
+               ret = find_next_epoch_boundary(next, &base);
+               if (ret)
+                       return ret;
+               next->object.flags |= BOUNDARY;
+               if (base)
+                       base->object.flags |= BOUNDARY;
+
+               if (HAS_EXACTLY_ONE_PARENT(next)) {
+                       while (HAS_EXACTLY_ONE_PARENT(next)
+                              && (action != STOP)
+                              && !ret) {
+                               if (next->object.flags & UNINTERESTING) {
+                                       action = STOP;
+                               } else {
+                                       action = emitter(next);
+                               }
+                               if (action != STOP) {
+                                       next = next->parents->item;
+                                       ret = parse_commit(next);
+                               }
+                       }
+
+               } else {
+                       struct commit_list *stack = NULL;
+                       sort_first_epoch(next, &stack);
+                       action = emit_stack(&stack, emitter, (base == NULL));
+                       next = base;
+               }
+       }
+
+       if (next && (action != STOP) && !ret) {
+               emitter(next);
+       }
+
+       return ret;
+}
+
+/*
+ * Sorts the nodes reachable from a starting list in merge order, we
+ * first find the base for the starting list and then sort all nodes
+ * in this subgraph using the sort_first_epoch algorithm. Once we have
+ * reached the base we can continue sorting using sort_in_merge_order.
+ */
+int sort_list_in_merge_order(struct commit_list *list, emitter_func emitter)
+{
+       struct commit_list *stack = NULL;
+       struct commit *base;
+       int ret = 0;
+       int action = CONTINUE;
+       struct commit_list *reversed = NULL;
+
+       for (; list; list = list->next)
+               commit_list_insert(list->item, &reversed);
+
+       if (!reversed)
+               return ret;
+       else if (!reversed->next) {
+               /*
+                * If there is only one element in the list, we can sort it
+                * using sort_in_merge_order.
+                */
+               base = reversed->item;
+       } else {
+               /*
+                * Otherwise, we search for the base of the list.
+                */
+               ret = find_base_for_list(reversed, &base);
+               if (ret)
+                       return ret;
+               if (base)
+                       base->object.flags |= BOUNDARY;
+
+               while (reversed) {
+                       struct commit * next = pop_commit(&reversed);
+
+                       if (!(next->object.flags & VISITED) && next!=base) {
+                               sort_first_epoch(next, &stack);
+                               if (reversed) {
+                                       /*
+                                        * If we have more commits 
+                                        * to push, then the first
+                                        * push for the next parent may 
+                                        * (or may * not) represent a 
+                                        * discontinuity with respect
+                                        * to the parent currently on 
+                                        * the top of the stack.
+                                        *
+                                        * Mark it for checking here, 
+                                        * and check it with the next 
+                                        * push. See sort_first_epoch()
+                                        * for more details.
+                                        */
+                                       stack->item->object.flags |= DISCONTINUITY;
+                               }
+                       }
+               }
+
+               action = emit_stack(&stack, emitter, (base==NULL));
+       }
+
+       if (base && (action != STOP)) {
+               ret = sort_in_merge_order(base, emitter);
+       }
+
+       return ret;
+}
diff --git a/epoch.h b/epoch.h
new file mode 100644 (file)
index 0000000..7493d5a
--- /dev/null
+++ b/epoch.h
@@ -0,0 +1,21 @@
+#ifndef EPOCH_H
+#define EPOCH_H
+
+
+// return codes for emitter_func
+#define STOP     0
+#define CONTINUE 1
+#define DO       2
+typedef int (*emitter_func) (struct commit *); 
+
+int sort_list_in_merge_order(struct commit_list *list, emitter_func emitter);
+
+/* Low bits are used by rev-list */
+#define UNINTERESTING   (1u<<10)
+#define BOUNDARY        (1u<<11)
+#define VISITED         (1u<<12)
+#define DISCONTINUITY   (1u<<13)
+#define LAST_EPOCH_FLAG (1u<<14)
+
+
+#endif /* EPOCH_H */
diff --git a/export.c b/export.c
new file mode 100644 (file)
index 0000000..ce10b5a
--- /dev/null
+++ b/export.c
@@ -0,0 +1,81 @@
+#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;
+}
diff --git a/fetch-pack.c b/fetch-pack.c
new file mode 100644 (file)
index 0000000..65e0076
--- /dev/null
@@ -0,0 +1,144 @@
+#include "cache.h"
+#include "refs.h"
+#include "pkt-line.h"
+#include <sys/wait.h>
+
+static int quiet;
+static const char fetch_pack_usage[] = "git-fetch-pack [-q] [--exec=upload-pack] [host:]directory [heads]* < mycommitlist";
+static const char *exec = "git-upload-pack";
+
+static int find_common(int fd[2], unsigned char *result_sha1, unsigned char *remote)
+{
+       static char line[1000];
+       int count = 0, flushes = 0, retval;
+       FILE *revs;
+
+       revs = popen("git-rev-list $(git-rev-parse --all)", "r");
+       if (!revs)
+               die("unable to run 'git-rev-list'");
+       packet_write(fd[1], "want %s\n", sha1_to_hex(remote));
+       packet_flush(fd[1]);
+       flushes = 1;
+       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");
+               packet_write(fd[1], "have %s\n", sha1_to_hex(sha1));
+               if (!(31 & ++count)) {
+                       packet_flush(fd[1]);
+                       flushes++;
+
+                       /*
+                        * We keep one window "ahead" of the other side, and
+                        * will wait for an ACK only on the next one
+                        */
+                       if (count == 32)
+                               continue;
+                       if (get_ack(fd[0], result_sha1)) {
+                               flushes = 0;
+                               retval = 0;
+                               break;
+                       }
+                       flushes--;
+               }
+       }
+       pclose(revs);
+       packet_write(fd[1], "done\n");
+       while (flushes) {
+               flushes--;
+               if (get_ack(fd[0], result_sha1))
+                       return 0;
+       }
+       return retval;
+}
+
+/*
+ * Eventually we'll want to be able to fetch multiple heads.
+ *
+ * Right now we'll just require a single match.
+ */
+static int fetch_pack(int fd[2], int nr_match, char **match)
+{
+       struct ref *ref;
+       unsigned char sha1[20];
+       int status;
+       pid_t pid;
+
+       get_remote_heads(fd[0], &ref, nr_match, match);
+       if (!ref) {
+               packet_flush(fd[1]);
+               die("no matching remote head");
+       }
+       if (ref->next) {
+               packet_flush(fd[1]);
+               die("multiple remote heads");
+       }
+       if (find_common(fd, sha1, ref->old_sha1) < 0)
+               die("git-fetch-pack: no common commits");
+       pid = fork();
+       if (pid < 0)
+               die("git-fetch-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);
+               puts(sha1_to_hex(ref->old_sha1));
+               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);
+}
+
+int main(int argc, char **argv)
+{
+       int i, ret, nr_heads;
+       char *dest = NULL, **heads;
+       int fd[2];
+       pid_t pid;
+
+       nr_heads = 0;
+       heads = NULL;
+       for (i = 1; i < argc; i++) {
+               char *arg = argv[i];
+
+               if (*arg == '-') {
+                       if (!strncmp("--exec=", arg, 7)) {
+                               exec = arg + 7;
+                               continue;
+                       }
+                       usage(fetch_pack_usage);
+               }
+               dest = arg;
+               heads = argv + i + 1;
+               nr_heads = argc - i - 1;
+               break;
+       }
+       if (!dest)
+               usage(fetch_pack_usage);
+       pid = git_connect(fd, dest, exec);
+       if (pid < 0)
+               return 1;
+       ret = fetch_pack(fd, nr_heads, heads);
+       close(fd[0]);
+       close(fd[1]);
+       finish_connect(pid);
+       return ret;
+}
diff --git a/fsck-cache.c b/fsck-cache.c
new file mode 100644 (file)
index 0000000..e40c643
--- /dev/null
@@ -0,0 +1,534 @@
+#include <sys/types.h>
+#include <dirent.h>
+
+#include "cache.h"
+#include "commit.h"
+#include "tree.h"
+#include "blob.h"
+#include "tag.h"
+#include "refs.h"
+#include "pack.h"
+
+#define REACHABLE 0x0001
+
+static int show_root = 0;
+static int show_tags = 0;
+static int show_unreachable = 0;
+static int standalone = 0;
+static int check_full = 0;
+static int check_strict = 0;
+static int keep_cache_objects = 0; 
+static unsigned char head_sha1[20];
+
+static void check_connectivity(void)
+{
+       int i;
+
+       /* Look up all the requirements, warn about missing objects.. */
+       for (i = 0; i < nr_objs; i++) {
+               struct object *obj = objs[i];
+               struct object_list *refs;
+
+               if (!obj->parsed) {
+                       if (!standalone && has_sha1_file(obj->sha1))
+                               ; /* it is in pack */
+                       else
+                               printf("missing %s %s\n",
+                                      obj->type, sha1_to_hex(obj->sha1));
+                       continue;
+               }
+
+               for (refs = obj->refs; refs; refs = refs->next) {
+                       if (refs->item->parsed ||
+                           (!standalone && has_sha1_file(refs->item->sha1)))
+                               continue;
+                       printf("broken link from %7s %s\n",
+                              obj->type, sha1_to_hex(obj->sha1));
+                       printf("              to %7s %s\n",
+                              refs->item->type, sha1_to_hex(refs->item->sha1));
+               }
+
+               if (show_unreachable && !(obj->flags & REACHABLE)) {
+                       printf("unreachable %s %s\n",
+                              obj->type, sha1_to_hex(obj->sha1));
+                       continue;
+               }
+
+               if (!obj->used) {
+                       printf("dangling %s %s\n", obj->type, 
+                              sha1_to_hex(obj->sha1));
+               }
+       }
+}
+
+/*
+ * The entries in a tree are ordered in the _path_ order,
+ * which means that a directory entry is ordered by adding
+ * a slash to the end of it.
+ *
+ * So a directory called "a" is ordered _after_ a file
+ * called "a.c", because "a/" sorts after "a.c".
+ */
+#define TREE_UNORDERED (-1)
+#define TREE_HAS_DUPS  (-2)
+
+static int verify_ordered(struct tree_entry_list *a, struct tree_entry_list *b)
+{
+       int len1 = strlen(a->name);
+       int len2 = strlen(b->name);
+       int len = len1 < len2 ? len1 : len2;
+       unsigned char c1, c2;
+       int cmp;
+
+       cmp = memcmp(a->name, b->name, len);
+       if (cmp < 0)
+               return 0;
+       if (cmp > 0)
+               return TREE_UNORDERED;
+
+       /*
+        * Ok, the first <len> characters are the same.
+        * Now we need to order the next one, but turn
+        * a '\0' into a '/' for a directory entry.
+        */
+       c1 = a->name[len];
+       c2 = b->name[len];
+       if (!c1 && !c2)
+               /*
+                * git-write-tree used to write out a nonsense tree that has
+                * entries with the same name, one blob and one tree.  Make
+                * sure we do not have duplicate entries.
+                */
+               return TREE_HAS_DUPS;
+       if (!c1 && a->directory)
+               c1 = '/';
+       if (!c2 && b->directory)
+               c2 = '/';
+       return c1 < c2 ? 0 : TREE_UNORDERED;
+}
+
+static int fsck_tree(struct tree *item)
+{
+       int retval;
+       int has_full_path = 0;
+       int has_zero_pad = 0;
+       int has_bad_modes = 0;
+       int has_dup_entries = 0;
+       int not_properly_sorted = 0;
+       struct tree_entry_list *entry, *last;
+
+       last = NULL;
+       for (entry = item->entries; entry; entry = entry->next) {
+               if (strchr(entry->name, '/'))
+                       has_full_path = 1;
+               has_zero_pad |= entry->zeropad;
+
+               switch (entry->mode) {
+               /*
+                * Standard modes.. 
+                */
+               case S_IFREG | 0755:
+               case S_IFREG | 0644:
+               case S_IFLNK:
+               case S_IFDIR:
+                       break;
+               /*
+                * This is nonstandard, but we had a few of these
+                * early on when we honored the full set of mode
+                * bits..
+                */
+               case S_IFREG | 0664:
+                       if (!check_strict)
+                               break;
+               default:
+                       has_bad_modes = 1;
+               }
+
+               if (last) {
+                       switch (verify_ordered(last, entry)) {
+                       case TREE_UNORDERED:
+                               not_properly_sorted = 1;
+                               break;
+                       case TREE_HAS_DUPS:
+                               has_dup_entries = 1;
+                               break;
+                       default:
+                               break;
+                       }
+               }
+
+               last = entry;
+       }
+
+       retval = 0;
+       if (has_full_path) {
+               fprintf(stderr, "warning: git-fsck-cache: tree %s "
+                       "has full pathnames in it\n", 
+                       sha1_to_hex(item->object.sha1));
+       }
+       if (has_zero_pad) {
+               fprintf(stderr, "warning: git-fsck-cache: tree %s "
+                       "has zero-padded file modes in it\n",
+                       sha1_to_hex(item->object.sha1));
+       }
+       if (has_bad_modes) {
+               fprintf(stderr, "warning: git-fsck-cache: tree %s "
+                       "has bad file modes in it\n",
+                       sha1_to_hex(item->object.sha1));
+       }
+       if (has_dup_entries) {
+               fprintf(stderr, "error: git-fsck-cache: tree %s "
+                       "has duplicate file entries\n",
+                       sha1_to_hex(item->object.sha1));
+               retval = -1;
+       }
+       if (not_properly_sorted) {
+               fprintf(stderr, "error: git-fsck-cache: tree %s "
+                       "is not properly sorted\n",
+                       sha1_to_hex(item->object.sha1));
+               retval = -1;
+       }
+       return retval;
+}
+
+static int fsck_commit(struct commit *commit)
+{
+       char *buffer = commit->buffer;
+       unsigned char sha1[20];
+
+       if (memcmp(buffer, "tree ", 5))
+               return -1;
+       if (get_sha1_hex(buffer+5, sha1) || buffer[45] != '\n')
+               return -1;
+       buffer += 46;
+       while (!memcmp(buffer, "parent ", 7)) {
+               if (get_sha1_hex(buffer+7, sha1) || buffer[47] != '\n')
+                       return -1;
+               buffer += 48;
+       }
+       if (memcmp(buffer, "author ", 7))
+               return -1;
+       free(commit->buffer);
+       commit->buffer = NULL;
+       if (!commit->tree)
+               return -1;
+       if (!commit->parents && show_root)
+               printf("root %s\n", sha1_to_hex(commit->object.sha1));
+       if (!commit->date)
+               printf("bad commit date in %s\n", 
+                      sha1_to_hex(commit->object.sha1));
+       return 0;
+}
+
+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;
+       }
+       if (!show_tags)
+               return 0;
+
+       printf("tagged %s %s", tagged->type, sha1_to_hex(tagged->sha1));
+       printf(" (%s) in %s\n", tag->tag, sha1_to_hex(tag->object.sha1));
+       return 0;
+}
+
+static int fsck_sha1(unsigned char *sha1)
+{
+       struct object *obj = parse_object(sha1);
+       if (!obj)
+               return -1;
+       if (obj->type == blob_type)
+               return 0;
+       if (obj->type == tree_type)
+               return fsck_tree((struct tree *) obj);
+       if (obj->type == commit_type)
+               return fsck_commit((struct commit *) obj);
+       if (obj->type == tag_type)
+               return fsck_tag((struct tag *) obj);
+       return -1;
+}
+
+/*
+ * This is the sorting chunk size: make it reasonably
+ * big so that we can sort well..
+ */
+#define MAX_SHA1_ENTRIES (1024)
+
+struct sha1_entry {
+       unsigned long ino;
+       unsigned char sha1[20];
+};
+
+static struct {
+       unsigned long nr;
+       struct sha1_entry *entry[MAX_SHA1_ENTRIES];
+} sha1_list;
+
+static int ino_compare(const void *_a, const void *_b)
+{
+       const struct sha1_entry *a = _a, *b = _b;
+       unsigned long ino1 = a->ino, ino2 = b->ino;
+       return ino1 < ino2 ? -1 : ino1 > ino2 ? 1 : 0;
+}
+
+static void fsck_sha1_list(void)
+{
+       int i, nr = sha1_list.nr;
+
+       qsort(sha1_list.entry, nr, sizeof(struct sha1_entry *), ino_compare);
+       for (i = 0; i < nr; i++) {
+               struct sha1_entry *entry = sha1_list.entry[i];
+               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));
+               free(entry);
+       }
+       sha1_list.nr = 0;
+}
+
+static void add_sha1_list(unsigned char *sha1, unsigned long ino)
+{
+       struct sha1_entry *entry = xmalloc(sizeof(*entry));
+       int nr;
+
+       entry->ino = ino;
+       memcpy(entry->sha1, sha1, 20);
+       nr = sha1_list.nr;
+       if (nr == MAX_SHA1_ENTRIES) {
+               fsck_sha1_list();
+               nr = 0;
+       }
+       sha1_list.entry[nr] = entry;
+       sha1_list.nr = ++nr;
+}
+
+static int fsck_dir(int i, char *path)
+{
+       DIR *dir = opendir(path);
+       struct dirent *de;
+
+       if (!dir) {
+               return error("missing sha1 directory '%s'", path);
+       }
+
+       while ((de = readdir(dir)) != NULL) {
+               char name[100];
+               unsigned char sha1[20];
+               int len = strlen(de->d_name);
+
+               switch (len) {
+               case 2:
+                       if (de->d_name[1] != '.')
+                               break;
+               case 1:
+                       if (de->d_name[0] != '.')
+                               break;
+                       continue;
+               case 38:
+                       sprintf(name, "%02x", i);
+                       memcpy(name+2, de->d_name, len+1);
+                       if (get_sha1_hex(name, sha1) < 0)
+                               break;
+                       add_sha1_list(sha1, de->d_ino);
+                       continue;
+               }
+               fprintf(stderr, "bad sha1 file: %s/%s\n", path, de->d_name);
+       }
+       closedir(dir);
+       return 0;
+}
+
+static int default_refs = 0;
+
+static int fsck_handle_ref(const char *refname, const unsigned char *sha1)
+{
+       struct object *obj;
+
+       obj = lookup_object(sha1);
+       if (!obj) {
+               if (!standalone && has_sha1_file(sha1)) {
+                       default_refs++;
+                       return 0; /* it is in a pack */
+               }
+               error("%s: invalid sha1 pointer %s", refname, sha1_to_hex(sha1));
+               /* We'll continue with the rest despite the error.. */
+               return 0;
+       }
+       default_refs++;
+       obj->used = 1;
+       mark_reachable(obj, REACHABLE);
+       return 0;
+}
+
+static void get_default_heads(void)
+{
+       for_each_ref(fsck_handle_ref);
+       if (!default_refs)
+               die("No default references");
+}
+
+static void fsck_object_dir(const char *path)
+{
+       int i;
+       for (i = 0; i < 256; i++) {
+               static char dir[4096];
+               sprintf(dir, "%s/%02x", path, i);
+               fsck_dir(i, dir);
+       }
+       fsck_sha1_list();
+}
+
+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 = gitenv(GIT_DIR_ENVIRONMENT) ? : 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))
+               return error("HEAD: not a valid git pointer");
+       return 0;
+}
+
+int main(int argc, char **argv)
+{
+       int i, heads;
+
+       for (i = 1; i < argc; i++) {
+               const char *arg = argv[i];
+
+               if (!strcmp(arg, "--unreachable")) {
+                       show_unreachable = 1;
+                       continue;
+               }
+               if (!strcmp(arg, "--tags")) {
+                       show_tags = 1;
+                       continue;
+               }
+               if (!strcmp(arg, "--root")) {
+                       show_root = 1;
+                       continue;
+               }
+               if (!strcmp(arg, "--cache")) {
+                       keep_cache_objects = 1;
+                       continue;
+               }
+               if (!strcmp(arg, "--standalone")) {
+                       standalone = 1;
+                       continue;
+               }
+               if (!strcmp(arg, "--full")) {
+                       check_full = 1;
+                       continue;
+               }
+               if (!strcmp(arg, "--strict")) {
+                       check_strict = 1;
+                       continue;
+               }
+               if (*arg == '-')
+                       usage("git-fsck-cache [--tags] [--root] [[--unreachable] [--cache] [--standalone | --full] [--strict] <head-sha1>*]");
+       }
+
+       if (standalone && check_full)
+               die("Only one of --standalone or --full can be used.");
+       if (standalone)
+               unsetenv("GIT_ALTERNATE_OBJECT_DIRECTORIES");
+
+       fsck_head_link();
+       fsck_object_dir(get_object_directory());
+       if (check_full) {
+               int j;
+               struct packed_git *p;
+               prepare_alt_odb();
+               for (j = 0; alt_odb[j].base; j++) {
+                       char namebuf[PATH_MAX];
+                       int namelen = alt_odb[j].name - alt_odb[j].base;
+                       memcpy(namebuf, alt_odb[j].base, namelen);
+                       namebuf[namelen - 1] = 0;
+                       fsck_object_dir(namebuf);
+               }
+               prepare_packed_git();
+               for (p = packed_git; p; p = p->next)
+                       /* verify gives error messages itself */
+                       verify_pack(p, 0);
+
+               for (p = packed_git; p; p = p->next) {
+                       int num = num_packed_objects(p);
+                       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));
+
+                       }
+               }
+       }
+
+       heads = 0;
+       for (i = 1; i < argc; i++) {
+               const char *arg = argv[i]; 
+
+               if (*arg == '-')
+                       continue;
+
+               if (!get_sha1(arg, head_sha1)) {
+                       struct object *obj = lookup_object(head_sha1);
+
+                       /* Error is printed by lookup_object(). */
+                       if (!obj)
+                               continue;
+
+                       obj->used = 1;
+                       mark_reachable(obj, REACHABLE);
+                       heads++;
+                       continue;
+               }
+               error("expected sha1, got %s", arg);
+       }
+
+       /*
+        * If we've not been given any explicit head information, do the
+        * default ones from .git/refs. We also consider the index file
+        * in this case (ie this implies --cache).
+        */
+       if (!heads) {
+               get_default_heads();
+               keep_cache_objects = 1;
+       }
+
+       if (keep_cache_objects) {
+               int i;
+               read_cache();
+               for (i = 0; i < active_nr; i++) {
+                       struct blob *blob = lookup_blob(active_cache[i]->sha1);
+                       struct object *obj;
+                       if (!blob)
+                               continue;
+                       obj = &blob->object;
+                       obj->used = 1;
+                       mark_reachable(obj, REACHABLE);
+               }
+       }
+
+       check_connectivity();
+       return 0;
+}
diff --git a/get-tar-commit-id.c b/get-tar-commit-id.c
new file mode 100644 (file)
index 0000000..a1a17e5
--- /dev/null
@@ -0,0 +1,27 @@
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#define HEADERSIZE     1024
+
+int main(int argc, char **argv)
+{
+       char buffer[HEADERSIZE];
+       ssize_t n;
+
+       n = read(0, buffer, HEADERSIZE);
+       if (n < HEADERSIZE) {
+               fprintf(stderr, "read error\n");
+               return 3;
+       }
+       if (buffer[156] != 'g')
+               return 1;
+       if (memcmp(&buffer[512], "52 comment=", 11))
+               return 1;
+       n = write(1, &buffer[523], 41);
+       if (n < 41) {
+               fprintf(stderr, "write error\n");
+               return 2;
+       }
+       return 0;
+}
diff --git a/git b/git
new file mode 100755 (executable)
index 0000000..9f51195
--- /dev/null
+++ b/git
@@ -0,0 +1,19 @@
+#!/bin/sh
+cmd="$1"
+shift
+if which git-$cmd-script >& /dev/null
+then
+       exec git-$cmd-script "$@"
+fi
+
+if which git-$cmd >& /dev/null
+then
+       exec git-$cmd "$@"
+fi
+
+alternatives=($(echo $PATH | tr ':' '\n' | while read i; do ls $i/git-*-script 2> /dev/null; done))
+
+echo Git command "'$cmd'" not found. Try one of
+for i in "${alternatives[@]}"; do
+       echo $i | sed 's:^.*/git-:   :' | sed 's:-script$::'
+done | sort | uniq
diff --git a/git-add-script b/git-add-script
new file mode 100755 (executable)
index 0000000..61ec040
--- /dev/null
@@ -0,0 +1,2 @@
+#!/bin/sh
+git-update-cache --add -- "$@"
diff --git a/git-apply-patch-script b/git-apply-patch-script
new file mode 100755 (executable)
index 0000000..6261fd8
--- /dev/null
@@ -0,0 +1,144 @@
+#!/bin/sh
+# Copyright (C) 2005 Junio C Hamano
+#
+# Applying diff between two trees to the work tree can be
+# done with the following single command:
+#
+# GIT_EXTERNAL_DIFF=git-apply-patch-script git-diff-tree -p $tree1 $tree2
+#
+
+case "$#" in
+1)
+    echo >&2 "cannot handle unmerged diff on path $1."
+    exit 1 ;;
+8 | 9)
+    echo >&2 "cannot handle rename diff between $1 and $8 yet."
+    exit 1 ;;
+esac
+name="$1" tmp1="$2" hex1="$3" mode1="$4" tmp2="$5" hex2="$6" mode2="$7"
+
+type1=f
+case "$mode1" in
+*120???) type1=l  ;;
+*1007??) mode1=+x ;;
+*1006??) mode1=-x ;;
+.)       type1=-  ;; 
+esac
+
+type2=f
+case "$mode2" in
+*120???) type2=l  ;;
+*1007??) mode2=+x ;;
+*1006??) mode2=-x ;;
+.)       type2=-  ;; 
+esac
+
+case "$type1,$type2" in
+
+-,?)
+    dir=$(dirname "$name")
+    case "$dir" in '' | .) ;; *) mkdir -p "$dir" ;; esac || {
+       echo >&2 "cannot create leading path for $name."
+       exit 1
+    }
+    if test -e "$name"
+    then
+       echo >&2 "path $name to be created already exists."
+       exit 1
+    fi
+    case "$type2" in
+    f)
+        # creating a regular file
+       cat "$tmp2" >"$name" || {
+           echo >&2 "cannot create a regular file $name."
+           exit 1
+       } 
+       case "$mode2" in
+       +x)
+           echo >&2 "created a regular file $name with mode +x."
+           chmod "$mode2" "$name"
+           ;;
+       -x)
+           echo >&2 "created a regular file $name."
+            ;;
+        esac
+       ;;
+    l)
+        # creating a symlink
+        ln -s "$(cat "$tmp2")" "$name" || {
+           echo >&2 "cannot create a symbolic link $name."
+           exit 1
+       }
+       echo >&2 "created a symbolic link $name."
+        ;;
+    *)
+        echo >&2 "do not know how to create $name of type $type2."
+       exit 1
+    esac
+    git-update-cache --add -- "$name" ;;
+
+?,-)
+    rm -f "$name" || {
+       echo >&2 "cannot remove $name"
+       exit 1
+    }
+    echo >&2 "deleted $name."
+    git-update-cache --remove -- "$name" ;;
+
+l,f|f,l)
+    echo >&2 "cannot change a regular file $name and a symbolic link $name."
+    exit 1 ;;
+
+l,l)
+    # symlink to symlink
+    current=$(readlink "$name") || {
+       echo >&2 "cannot read the target of the symbolic link $name."
+       exit 1
+    }
+    original=$(cat "$tmp1")
+    next=$(cat "$tmp2")
+    test "$original" != "$current" || {
+       echo >&2 "cannot apply symbolic link target change ($original->$next) to $name which points to $current."
+       exit 1
+    }
+    if test "$next" != "$current"
+    then
+       rm -f "$name" && ln -s "$next" "$name" || {
+           echo >&2 "cannot create symbolic link $name."
+           exit 1
+       }
+       echo >&2 "changed symbolic target of $name."
+        git-update-cache -- "$name"
+    fi ;;
+
+f,f)
+    # changed
+    test -e "$name" || {
+       echo >&2 "regular file $name to be patched does not exist."
+       exit 1
+    }
+    dir=$(dirname "$name")
+    case "$dir" in '' | .) ;; *) mkdir -p "$dir";; esac || {
+       echo >&2 "cannot create leading path for $name."
+       exit 1
+    }
+    tmp=.git-apply-patch-$$
+    trap "rm -f $tmp-*" 0 1 2 3 15
+
+    # Be careful, in case "$tmp2" is borrowed path from the work tree
+    # we are looking at...
+    diff -u -L "a/$name" -L "b/$name" "$tmp1" "$tmp2" >$tmp-patch
+
+    # This will say "patching ..." so we do not say anything outselves.
+    patch -p1 <$tmp-patch || exit
+    rm -f $tmp-patch
+    case "$mode1,$mode2" in
+    "$mode2,$mode1") ;;
+    *)
+       chmod "$mode2" "$name"
+       echo >&2 "changed mode from $mode1 to $mode2."
+       ;;
+    esac
+    git-update-cache -- "$name"
+
+esac
diff --git a/git-bisect-script b/git-bisect-script
new file mode 100755 (executable)
index 0000000..29b25f4
--- /dev/null
@@ -0,0 +1,158 @@
+#!/bin/sh
+. git-sh-setup-script || dir "Not a git archive"
+
+usage() {
+    echo >&2 'usage: git bisect [start | bad | good | next | reset]
+git bisect start               reset bisect state and start bisection.
+git bisect bad [<rev>]         mark <rev> a known-bad revision.
+git bisect good [<rev>...]     mark <rev>... known-good revisions.
+git bisect next                        find next bisection to test and check it out.
+git bisect reset [<branch>]    finish bisection search and go back to branch.'
+    exit 1
+}
+
+bisect_autostart() {
+       test -d "$GIT_DIR/refs/bisect" || {
+               echo >&2 'You need to start by "git bisect start"'
+               if test -t 0
+               then
+                       echo >&2 -n 'Do you want me to do it for you [Y/n]? '
+                       read yesno
+                       case "$yesno" in
+                       [Nn]*)
+                               exit ;;
+                       esac
+                       bisect_start
+               else
+                       exit 1
+               fi
+       }
+}
+
+bisect_start() {
+        case "$#" in 0) ;; *) usage ;; esac
+       #
+       # 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"
+       case "$head" in
+       refs/heads/bisect*)
+               git checkout master || exit
+               ;;
+       refs/heads/*)
+               ;;
+       *)
+               die "Bad HEAD - strange symlink"
+               ;;
+       esac
+
+       #
+       # Get rid of any old bisect state
+       #
+       rm -f "$GIT_DIR/refs/heads/bisect"
+       rm -rf "$GIT_DIR/refs/bisect/"
+       mkdir "$GIT_DIR/refs/bisect"
+}
+
+bisect_bad() {
+       bisect_autostart
+        case "$#" in 0 | 1) ;; *) usage ;; esac
+       rev=$(git-rev-parse --revs-only --verify --default HEAD "$@") || exit
+       echo "$rev" > "$GIT_DIR/refs/bisect/bad"
+       bisect_auto_next
+}
+
+bisect_good() {
+       bisect_autostart
+        case "$#" in
+       0)    revs=$(git-rev-parse --verify HEAD) || exit ;;
+       *)    revs=$(git-rev-parse --revs-only "$@") || exit ;;
+       esac
+       for rev in $revs
+       do
+           echo "$rev" >"$GIT_DIR/refs/bisect/good-$rev"
+       done
+       bisect_auto_next
+}
+
+bisect_next_check() {
+       next_ok=no
+        test -f "$GIT_DIR/refs/bisect/bad" &&
+       case "$(cd "$GIT_DIR" && echo refs/bisect/good-*)" in
+       refs/bisect/good-\*) ;;
+       *) next_ok=yes ;;
+       esac
+       case "$next_ok,$1" in
+       no,) false ;;
+       no,fail)
+           echo >&2 'You need to give me at least one good and one bad revisions.'
+           exit 1 ;;
+       *)
+           true ;;
+       esac
+}
+
+bisect_auto_next() {
+       bisect_next_check && bisect_next
+}
+
+bisect_next() {
+        case "$#" in 0) ;; *) usage ;; esac
+       bisect_autostart
+       bisect_next_check fail
+       bad=$(git-rev-parse --verify refs/bisect/bad) &&
+       good=$(git-rev-parse --sq --revs-only --not \
+               $(cd "$GIT_DIR" && ls refs/bisect/good-*)) &&
+       rev=$(eval "git-rev-list --bisect $good $bad") || exit
+       nr=$(eval "git-rev-list $rev $good" | wc -l) || exit
+       if [ "$nr" -le "1" ]; then
+           echo "$rev is first bad commit"
+           git-diff-tree --pretty $rev
+           exit 0
+       fi
+       echo "Bisecting: $nr revisions left to test after this"
+       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"
+}
+
+bisect_reset() {
+       case "$#" in
+       0) branch=master ;;
+       1) test -f "$GIT_DIR/refs/heads/$1" || {
+              echo >&2 "$1 does not seem to be a valid branch"
+              exit 1
+          }
+          branch="$1" ;;
+        *)
+           usage ;;
+       esac
+       git checkout "$branch" &&
+       rm -fr "$GIT_DIR/refs/bisect"
+       rm -f "$GIT_DIR/refs/reads/bisect"
+}
+
+case "$#" in
+0)
+    usage ;;
+*)
+    cmd="$1"
+    shift
+    case "$cmd" in
+    start)
+        bisect_start "$@" ;;
+    bad)
+        bisect_bad "$@" ;;
+    good)
+        bisect_good "$@" ;;
+    next)
+        # Not sure we want "next" at the UI level anymore.
+        bisect_next "$@" ;;
+    reset)
+        bisect_reset "$@" ;;
+    *)
+        usage ;;
+    esac
+esac
diff --git a/git-branch-script b/git-branch-script
new file mode 100755 (executable)
index 0000000..041ca51
--- /dev/null
@@ -0,0 +1,17 @@
+#!/bin/sh
+
+. git-sh-setup-script || die "Not a git archive"
+
+branchname="$1"
+case "$2" in
+'')
+       head=HEAD ;;
+*)
+       head="$2^0" ;;
+esac
+rev=$(git-rev-parse --revs-only --verify "$head") || exit
+
+[ -z "$branchname" ] && die "git branch: I want a branch name"
+[ -e "$GIT_DIR/refs/heads/$branchname" ] && die "$branchname already exists"
+
+echo $rev > "$GIT_DIR/refs/heads/$branchname"
diff --git a/git-checkout-script b/git-checkout-script
new file mode 100755 (executable)
index 0000000..a377407
--- /dev/null
@@ -0,0 +1,75 @@
+#!/bin/sh
+. git-sh-setup-script || die "Not a git archive"
+
+old=$(git-rev-parse HEAD)
+new=
+force=
+branch=
+newbranch=
+while [ "$#" != "0" ]; do
+    arg="$1"
+    shift
+    case "$arg" in
+       "-b")
+               newbranch="$1"
+               shift
+               [ -z "$newbranch" ] &&
+                       die "git checkout: -b needs a branch name"
+               [ -e "$GIT_DIR/refs/heads/$newbranch" ] &&
+                       die "git checkout: branch $newbranch already exists"
+               ;;
+       "-f")
+               force=1
+               ;;
+       *)
+               rev=$(git-rev-parse --verify "$arg^0") || exit
+               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"
+               fi
+               ;;
+    esac
+    i=$(($i+1))
+done
+[ -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"
+
+if [ "$force" ]
+then
+    git-read-tree --reset $new &&
+       git-checkout-cache -q -f -u -a
+else
+    git-read-tree -m -u $old $new
+fi
+
+# 
+# Switch the HEAD pointer to the new branch if it we
+# checked out a branch head, and remove any potential
+# old MERGE_HEAD's (subsequent commits will clearly not
+# be based on them, since we re-set the index)
+#
+if [ "$?" -eq 0 ]; then
+       if [ "$newbranch" ]; then
+               echo $new > "$GIT_DIR/refs/heads/$newbranch"
+               branch="$newbranch"
+       fi
+       [ "$branch" ] && ln -sf "refs/heads/$branch" "$GIT_DIR/HEAD"
+       rm -f "$GIT_DIR/MERGE_HEAD"
+fi
diff --git a/git-cherry b/git-cherry
new file mode 100755 (executable)
index 0000000..e186363
--- /dev/null
@@ -0,0 +1,101 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano.
+#
+
+. git-sh-setup-script || die "Not a git archive."
+
+usage="usage: $0 "'[-v] <upstream> [<head>]
+
+             __*__*__*__*__> <upstream>
+            /
+  fork-point
+            \__+__+__+__+__+__+__+__> <head>
+
+Each commit between the fork-point and <head> is examined, and
+compared against the change each commit between the fork-point and
+<upstream> introduces.  If the change does not seem to be in the
+upstream, it is shown on the standard output.
+
+The output is intended to be used as:
+
+    OLD_HEAD=$(git-rev-parse HEAD)
+    git-rev-parse upstream >${GIT_DIR-.}/HEAD
+    git-cherry upstream $OLD_HEAD |
+    while read commit
+    do
+        GIT_EXTERNAL_DIFF=git-apply-patch-script git-diff-tree -p "$commit" &&
+       git-commit-script -C "$commit"
+    done
+'
+
+case "$1" in -v) verbose=t; shift ;; esac 
+
+case "$#,$1" in
+1,*..*)
+    upstream=$(expr "$1" : '\(.*\)\.\.') ours=$(expr "$1" : '.*\.\.\(.*\)$')
+    set x "$upstream" "$ours"
+    shift ;;
+esac
+
+case "$#" in
+1) upstream=`git-rev-parse --verify "$1"` &&
+   ours=`git-rev-parse --verify HEAD` || exit
+   ;;
+2) upstream=`git-rev-parse --verify "$1"` &&
+   ours=`git-rev-parse --verify "$2"` || exit
+   ;;
+*) echo >&2 "$usage"; exit 1 ;;
+esac
+
+# Note that these list commits in reverse order;
+# not that the order in inup matters...
+inup=`git-rev-list ^$ours $upstream` &&
+ours=`git-rev-list $ours ^$upstream` || exit
+
+tmp=.cherry-tmp$$
+patch=$tmp-patch
+mkdir $patch
+trap "rm -rf $tmp-*" 0 1 2 3 15
+
+_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"
+
+for c in $inup
+do
+       git-diff-tree -p $c
+done | git-patch-id |
+while read id name
+do
+       echo $name >>$patch/$id
+done
+
+LF='
+'
+
+O=
+for c in $ours
+do
+       set x `git-diff-tree -p $c | git-patch-id`
+       if test "$2" != ""
+       then
+               if test -f "$patch/$2"
+               then
+                       sign=-
+               else
+                       sign=+
+               fi
+               case "$verbose" in
+               t)
+                       c=$(git-rev-list --pretty=oneline --max-count=1 $c)
+               esac
+               case "$O" in
+               '')     O="$sign $c" ;;
+               *)      O="$sign $c$LF$O" ;;
+               esac
+       fi
+done
+case "$O" in
+'') ;;
+*)  echo "$O" ;;
+esac
diff --git a/git-clone-dumb-http b/git-clone-dumb-http
new file mode 100755 (executable)
index 0000000..5052708
--- /dev/null
@@ -0,0 +1,51 @@
+#!/bin/sh
+#
+# Copyright (c) 2005, Junio C Hamano
+#
+# Called by git-clone-script
+# Exits 2 when the remote site does not support dumb server protocol.
+
+# Usage: git-clone-dumb-http <remote-repo> <local-dir>
+
+R=${1?"remote repository"} D=${2?"local directory"}
+
+if [ -n "$GIT_SSL_NO_VERIFY" ]; then
+    curl_extra_args="-k"
+fi
+http_fetch () {
+       # $1 = Remote, $2 = Local
+       curl -nsf $curl_extra_args "$1" >"$2"
+}
+
+cd "$D" && 
+clone_tmp=".git/clone-tmp" &&
+mkdir -p "$clone_tmp" || exit 1
+trap "rm -rf .git/clone-tmp" 0 1 2 3 15
+
+http_fetch "$R/info/refs" "$clone_tmp/refs" &&
+http_fetch "$R/objects/info/packs" "$clone_tmp/packs" || exit 2
+
+# We do not have to worry about rev-cache when cloning.
+# http_fetch "$R/info/rev-cache" "$clone_tmp/rev-cache" 
+
+# Clone packs
+while read type name
+do
+       case "$type" in
+       P) ;;
+       *) continue ;;
+       esac &&
+
+       idx=`expr "$name" : '\(.*\)\.pack'`.idx
+       http_fetch "$R/objects/pack/$name" ".git/objects/pack/$name" &&
+       http_fetch "$R/objects/pack/$idx" ".git/objects/pack/$idx" &&
+       git-verify-pack ".git/objects/pack/$idx" || exit 1
+
+done <"$clone_tmp/packs"
+
+# Then clone refs.
+while read sha1 refname
+do
+       name=`expr "$refname" : 'refs/\(.*\)'` &&
+       git-http-pull -v -a -w "$name" "$name" "$R/" || exit 1
+done <"$clone_tmp/refs"
diff --git a/git-clone-script b/git-clone-script
new file mode 100755 (executable)
index 0000000..60dc2a9
--- /dev/null
@@ -0,0 +1,117 @@
+#!/bin/sh
+#
+# Copyright (c) 2005, Linus Torvalds
+# Copyright (c) 2005, Junio C Hamano
+# 
+# Clone a repository into a different directory that does not yet exist.
+
+usage() {
+       echo >&2 "* git clone [-l] [-q] [-u <upload-pack>] <repo> <dir>"
+       exit 1
+}
+
+get_repo_base() {
+       (cd "$1" && (cd .git ; pwd)) 2> /dev/null
+}
+
+quiet=
+use_local=no
+upload_pack=
+while
+       case "$#,$1" in
+       0,*) break ;;
+       *,-l|*,--l|*,--lo|*,--loc|*,--loca|*,--local) use_local=yes ;;
+       *,-q|*,--quiet) quiet=-q ;;
+       1,-u|1,--upload-pack) usage ;;
+       *,-u|*,--upload-pack)
+               shift
+               upload_pack="--exec=$1" ;;
+       *,-*) usage ;;
+       *) break ;;
+       esac
+do
+       shift
+done
+
+# Turn the source into an absolute path if
+# it is local
+repo="$1"
+local=no
+if base=$(get_repo_base "$repo"); then
+       repo="$base"
+       local=yes
+fi
+
+dir="$2"
+mkdir "$dir" &&
+D=$(
+       (cd "$dir" && git-init-db && pwd)
+) &&
+test -d "$D" || usage
+
+# We do local magic only when the user tells us to.
+case "$local,$use_local" in
+yes,yes)
+       ( cd "$repo/objects" ) || {
+               echo >&2 "-l flag seen but $repo is not local."
+               exit 1
+       }
+
+       # See if we can hardlink and drop "l" if not.
+       sample_file=$(cd "$repo" && \
+                     find objects -type f -print | sed -e 1q)
+
+       # objects directory should not be empty since we are cloning!
+       test -f "$repo/$sample_file" || exit
+
+       l=
+       if ln "$repo/$sample_file" "$D/.git/objects/sample" 2>/dev/null
+       then
+               l=l
+       fi &&
+       rm -f "$D/.git/objects/sample" &&
+       cd "$repo" &&
+       find objects -type f -print |
+       cpio -puamd$l "$D/.git/" || exit 1
+
+       # Make a duplicate of refs and HEAD pointer
+       HEAD=
+       if test -f "$repo/HEAD"
+       then
+               HEAD=HEAD
+       fi
+       tar Ccf "$repo" - refs $HEAD | tar Cxf "$D/.git" - || exit 1
+       ;;
+*)
+       case "$repo" in
+       rsync://*)
+               rsync $quiet -avz --ignore-existing "$repo/objects/" "$D/.git/objects/" &&
+               rsync $quiet -avz --ignore-existing "$repo/refs/" "$D/.git/refs/"
+               ;;
+       http://*)
+               git-clone-dumb-http "$repo" "$D"
+               case "$?" in
+               2)
+                       echo "Somebody should define smarter http server protocol" >&2
+                       exit 1
+                       ;;
+               0)
+                       ;;
+               *)
+                       exit
+               esac
+               ;;
+       *)
+               cd "$D" && case "$upload_pack" in
+               '') git-clone-pack $quiet "$repo" ;;
+               *) git-clone-pack $quiet "$upload_pack" "$repo" ;;
+               esac
+               ;;
+       esac
+       ;;
+esac
+
+# Update origin.
+mkdir -p "$D/.git/branches/" &&
+rm -f "$D/.git/branches/origin" &&
+echo "$repo" >"$D/.git/branches/origin"
diff --git a/git-commit-script b/git-commit-script
new file mode 100755 (executable)
index 0000000..24ec446
--- /dev/null
@@ -0,0 +1,191 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Linus Torvalds
+#
+
+. git-sh-setup-script || die "Not a git archive"
+
+usage () {
+       die 'git commit [-a]  [-m <message>] [-F <logfile>] [(-C|-c) <commit>] [<path>...]'
+}
+
+all= logfile= use_commit= no_edit= log_given= log_message=
+while case "$#" in 0) break;; esac
+do
+  case "$1" in
+  -a|--a|--al|--all)
+    all=t
+    shift ;;
+  -F=*|--f=*|--fi=*|--fil=*|--file=*)
+    log_given=t$log_given
+    logfile=`expr "$1" : '-[^=]*=\(.*\)'`
+    no_edit=t
+    shift ;;
+  -F|--f|--fi|--fil|--file)
+    case "$#" in 1) usage ;; esac; shift
+    log_given=t$log_given
+    logfile="$1"
+    no_edit=t
+    shift ;;
+  -m=*|--m=*|--me=*|--mes=*|--mess=*|--messa=*|--messag=*|--message=*)
+    log_given=t$log_given
+    log_message=`expr "$1" : '-[^=]*=\(.*\)'`
+    no_edit=t
+    shift ;;
+  -m|--m|--me|--mes|--mess|--messa|--messag|--message)
+    case "$#" in 1) usage ;; esac; shift
+    log_given=t$log_given
+    log_message="$1"
+    no_edit=t
+    shift ;;
+  -c=*|--ree=*|--reed=*|--reedi=*|--reedit=*|--reedit-=*|--reedit-m=*|\
+  --reedit-me=*|--reedit-mes=*|--reedit-mess=*|--reedit-messa=*|\
+  --reedit-messag=*|--reedit-message=*)
+    log_given=t$log_given
+    use_commit=`expr "$1" : '-[^=]*=\(.*\)'`
+    shift ;;
+  -c|--ree|--reed|--reedi|--reedit|--reedit-|--reedit-m|--reedit-me|\
+  --reedit-mes|--reedit-mess|--reedit-messa|--reedit-messag|--reedit-message)
+    case "$#" in 1) usage ;; esac; shift
+    log_given=t$log_given
+    use_commit="$1"
+    shift ;;
+  -C=*|--reu=*|--reus=*|--reuse=*|--reuse-=*|--reuse-m=*|--reuse-me=*|\
+  --reuse-mes=*|--reuse-mess=*|--reuse-messa=*|--reuse-messag=*|\
+  --reuse-message=*)
+    log_given=t$log_given
+    use_commit=`expr "$1" : '-[^=]*=\(.*\)'`
+    no_edit=t
+    shift ;;
+  -C|--reu|--reus|--reuse|--reuse-|--reuse-m|--reuse-me|--reuse-mes|\
+  --reuse-mess|--reuse-messa|--reuse-messag|--reuse-message)
+    case "$#" in 1) usage ;; esac; shift
+    log_given=t$log_given
+    use_commit="$1"
+    no_edit=t
+    shift ;;
+  --)
+    shift
+    break ;;
+  -*)
+     usage ;;
+  *)
+    break ;;
+  esac
+done
+
+case "$log_given" in
+tt*)
+  die "Only one of -c/-C/-F/-m can be used." ;;
+esac
+
+case "$all" in
+t)
+       git-diff-files --name-only -z |
+       xargs -0 git-update-cache -q -- || exit 1 ;;
+esac
+git-update-cache -q --refresh -- "$@" || exit 1
+
+PARENTS="-p HEAD"
+if [ ! -r "$GIT_DIR/HEAD" ]; then
+       if [ -z "$(git-ls-files)" ]; then
+               echo Nothing to commit 1>&2
+               exit 1
+       fi
+       {
+               echo "#"
+               echo "# Initial commit"
+               case "$no_edit" in
+               t) echo "# (ignoring your commit message for initial commit)"
+                  no_edit= 
+               esac
+               echo "#"
+               git-ls-files | sed 's/^/# New file: /'
+               echo "#"
+       } >.editmsg
+       PARENTS=""
+       no_edit=
+else
+       if [ -f "$GIT_DIR/MERGE_HEAD" ]; then
+               echo "#"
+               echo "# It looks like your may be committing a MERGE."
+               echo "# If this is not correct, please remove the file"
+               echo "# $GIT_DIR/MERGE_HEAD"
+               echo "# and try again"
+               case "$no_edit" in
+               t) echo "# (ignoring your commit message for merge commit)"
+                  no_edit= 
+               esac
+               echo "#"
+               PARENTS="-p HEAD -p MERGE_HEAD"
+       elif test "$log_message" != ''
+       then
+               echo "$log_message"
+       elif test "$logfile" != ""
+       then
+               if test "$logfile" = -
+               then
+                       test -t 0 &&
+                       echo >&2 "(reading log message from standard input)"
+                       cat
+               else
+                       cat <"$logfile"
+               fi
+       elif test "$use_commit" != ""
+       then
+               pick_author_script='
+               /^author /{
+                       h
+                       s/^author \([^<]*\) <[^>]*> .*$/\1/
+                       s/'\''/'\''\'\'\''/g
+                       s/.*/GIT_AUTHOR_NAME='\''&'\''/p
+
+                       g
+                       s/^author [^<]* <\([^>]*\)> .*$/\1/
+                       s/'\''/'\''\'\'\''/g
+                       s/.*/GIT_AUTHOR_EMAIL='\''&'\''/p
+
+                       g
+                       s/^author [^<]* <[^>]*> \(.*\)$/\1/
+                       s/'\''/'\''\'\'\''/g
+                       s/.*/GIT_AUTHOR_DATE='\''&'\''/p
+
+                       q
+               }
+               '
+               set_author_env=`git-cat-file commit "$use_commit" |
+               sed -ne "$pick_author_script"`
+               eval "$set_author_env"
+               export GIT_AUTHOR_NAME
+               export GIT_AUTHOR_EMAIL
+               export GIT_AUTHOR_DATE
+               git-cat-file commit "$use_commit" |
+               sed -e '1,/^$/d'
+       fi >.editmsg
+       git-status-script >>.editmsg
+fi
+if [ "$?" != "0" -a ! -f $GIT_DIR/MERGE_HEAD ]
+then
+       cat .editmsg
+       rm .editmsg
+       exit 1
+fi
+case "$no_edit" in
+'')
+       ${VISUAL:-${EDITOR:-vi}} .editmsg
+       ;;
+esac
+grep -v '^#' < .editmsg | git-stripspace > .cmitmsg
+if test -s .cmitmsg
+then
+       tree=$(git-write-tree) &&
+       commit=$(cat .cmitmsg | git-commit-tree $tree $PARENTS) &&
+       echo $commit > "$GIT_DIR/HEAD" &&
+       rm -f -- "$GIT_DIR/MERGE_HEAD"
+else
+       echo >&2 "* no commit message?  aborting commit."
+       false
+fi
+ret="$?"
+rm -f .cmitmsg .editmsg
+exit "$ret"
diff --git a/git-core.spec.in b/git-core.spec.in
new file mode 100644 (file)
index 0000000..8f079db
--- /dev/null
@@ -0,0 +1,56 @@
+# Pass --without docs to rpmbuild if you don't want the documetnation
+Name:          git-core
+Version:       @@VERSION@@
+Release:       1
+Vendor:        Junio C Hamano <junkio@cox.net>
+Summary:       Git core and tools
+License:       GPL
+Group:                 Development/Tools
+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}-root
+Requires:      sh-utils, diffutils, rsync, rcs, mktemp >= 1.5
+
+%description
+This is a stupid (but extremely fast) directory content manager.  It
+doesn't do a whole lot, but what it _does_ do is track directory
+contents efficiently. It is intended to be the base of an efficient,
+distributed source code management system. This package includes
+rudimentary tools that can be used as a SCM, but you should look
+elsewhere for tools for ordinary humans layered on top of this.
+
+%prep
+%setup -q
+
+%build
+make prefix=%{_prefix} all %{!?_without_docs: doc}
+
+%install
+rm -rf $RPM_BUILD_ROOT
+make dest=$RPM_BUILD_ROOT prefix=%{_prefix} mandir=%{_mandir} \
+     install install-tools %{!?_without_docs: install-doc}
+
+%clean
+rm -rf $RPM_BUILD_ROOT
+
+%files
+%defattr(-,root,root)
+%{_bindir}/*
+%{_datadir}/git-core/templates/*
+%doc README COPYING Documentation/*.txt
+%{!?_without_docs: %doc Documentation/*.html }
+%{!?_without_docs: %{_mandir}/man1/*.1.gz}
+%{!?_without_docs: %{_mandir}/man7/*.7.gz}
+
+%changelog
+* Sun Aug 07 2005 Horst H. von Brand <vonbrand@inf.utfsm.cl>
+- Redid the description
+- Cut overlong make line, loosened changelog a bit
+- I think Junio (or perhaps OSDL?) should be vendor...
+
+* Thu Jul 14 2005 Eric Biederman <ebiederm@xmission.com>
+- Add the man pages, and the --without docs build option
+
+* Wed Jul 7 2005 Chris Wright <chris@osdl.org>
+- initial git spec file
diff --git a/git-count-objects-script b/git-count-objects-script
new file mode 100755 (executable)
index 0000000..86432db
--- /dev/null
@@ -0,0 +1,13 @@
+#!/bin/sh
+
+. git-sh-setup-script
+
+echo $(find "$GIT_DIR/objects"/?? -type f -print | wc -l) objects, \
+$({
+    echo 0
+    # "no-such" is to help Darwin folks by not using xargs -r.
+    find "$GIT_DIR/objects"/?? -type f -print 2>/dev/null |
+    xargs du -k "$GIT_DIR/objects/no-such" 2>/dev/null |
+    sed -e 's/[        ].*/ +/'
+    echo p
+} | dc) kilobytes
diff --git a/git-cvsimport-script b/git-cvsimport-script
new file mode 100755 (executable)
index 0000000..8f57396
--- /dev/null
@@ -0,0 +1,742 @@
+#!/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 aggregate CVS check-ins into related changes.
+# Fortunately, "cvsps" does that for us; all we have to do is to parse
+# its output.
+#
+# Checking out the files is done by a single long-running CVS connection
+# / server process.
+#
+# The head revision is on branch "origin" by default.
+# You can change that with the '-o' option.
+
+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::Socket;
+use IO::Pipe;
+use POSIX qw(strftime dup2);
+
+$SIG{'PIPE'}="IGNORE";
+$ENV{'TZ'}="UTC";
+
+our($opt_h,$opt_o,$opt_v,$opt_d,$opt_p,$opt_C,$opt_z,$opt_i);
+
+sub usage() {
+       print STDERR <<END;
+Usage: ${\basename $0}     # fetch/update GIT from CVS
+       [ -o branch-for-HEAD ] [ -h ] [ -v ] [ -d CVSROOT ]
+       [ -p opts-for-cvsps ] [ -C GIT_repository ] [ -z fuzz ]
+       [ -i ] [ CVS_module ]
+END
+       exit(1);
+}
+
+getopts("hivo:d:p:C:z:") or usage();
+usage if $opt_h;
+
+@ARGV <= 1 or usage();
+
+if($opt_d) {
+       $ENV{"CVSROOT"} = $opt_d;
+} elsif(-f 'CVS/Root') {
+       open my $f, '<', 'CVS/Root' or die 'Failed to open CVS/Root';
+       $opt_d = <$f>;
+       chomp $opt_d;
+       close $f;
+       $ENV{"CVSROOT"} = $opt_d;
+} elsif($ENV{"CVSROOT"}) {
+       $opt_d = $ENV{"CVSROOT"};
+} else {
+       die "CVSROOT needs to be set";
+}
+$opt_o ||= "origin";
+my $git_tree = $opt_C;
+$git_tree ||= ".";
+
+my $cvs_tree;
+if ($#ARGV == 0) {
+       $cvs_tree = $ARGV[0];
+} elsif (-f 'CVS/Repository') {
+       open my $f, '<', 'CVS/Repository' or 
+           die 'Failed to open CVS/Repository';
+       $cvs_tree = <$f>;
+       chomp $cvs_tree;
+       close $f
+} else {
+       usage();
+}
+
+select(STDERR); $|=1; select(STDOUT);
+
+
+package CVSconn;
+# Basic CVS dialog.
+# 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,$subdir) = @_;
+       $what=ref($what) if ref($what);
+
+       my $self = {};
+       $self->{'buffer'} = "";
+       bless($self,$what);
+
+       $repo =~ s#/+$##;
+       $self->{'fullrep'} = $repo;
+       $self->conn();
+
+       $self->{'subdir'} = $subdir;
+       $self->{'lines'} = undef;
+
+       return $self;
+}
+
+sub conn {
+       my $self = shift;
+       my $repo = $self->{'fullrep'};
+       if($repo =~ s/^:pserver:(?:(.*?)(?::(.*?))?@)?([^:\/]*)(?::(\d*))?//) {
+               my($user,$pass,$serv,$port) = ($1,$2,$3,$4);
+               $user="anonymous" unless defined $user;
+               my $rr2 = "-";
+               unless($port) {
+                       $rr2 = ":pserver:$user\@$serv:$repo";
+                       $port=2401;
+               }
+               my $rr = ":pserver:$user\@$serv:$port$repo";
+
+               unless($pass) {
+                       open(H,$ENV{'HOME'}."/.cvspass") and do {
+                               # :pserver:cvs@mea.tmt.tele.fi:/cvsroot/zmailer Ah<Z
+                               while(<H>) {
+                                       chomp;
+                                       s/^\/\d+\s+//;
+                                       my ($w,$p) = split(/\s/,$_,2);
+                                       if($w eq $rr or $w eq $rr2) {
+                                               $pass = $p;
+                                               last;
+                                       }
+                               }
+                       };
+               }
+               $pass="A" unless $pass;
+
+               my $s = IO::Socket::INET->new(PeerHost => $serv, PeerPort => $port);
+               die "Socket to $serv: $!\n" unless defined $s;
+               $s->write("BEGIN AUTH REQUEST\n$repo\n$user\n$pass\nEND AUTH REQUEST\n")
+                       or die "Write to $serv: $!\n";
+               $s->flush();
+
+               my $rep = <$s>;
+
+               if($rep ne "I LOVE YOU\n") {
+                       $rep="<unknown>" unless $rep;
+                       die "AuthReply: $rep\n";
+               }
+               $self->{'socketo'} = $s;
+               $self->{'socketi'} = $s;
+       } else { # local or ext: Fork off our own cvs server.
+               my $pr = IO::Pipe->new();
+               my $pw = IO::Pipe->new();
+               my $pid = fork();
+               die "Fork: $!\n" unless defined $pid;
+               my $cvs = 'cvs';
+               $cvs = $ENV{CVS_SERVER} if exists $ENV{CVS_SERVER};
+               my $rsh = 'rsh';
+               $rsh = $ENV{CVS_RSH} if exists $ENV{CVS_RSH};
+
+               my @cvs = ($cvs, 'server');
+               my ($local, $user, $host);
+               $local = $repo =~ s/:local://;
+               if (!$local) {
+                   $repo =~ s/:ext://;
+                   $local = !($repo =~ s/^(?:([^\@:]+)\@)?([^:]+)://);
+                   ($user, $host) = ($1, $2);
+               }
+               if (!$local) {
+                   if ($user) {
+                       unshift @cvs, $rsh, '-l', $user, $host;
+                   } else {
+                       unshift @cvs, $rsh, $host;
+                   }
+               }
+
+               unless($pid) {
+                       $pr->writer();
+                       $pw->reader();
+                       dup2($pw->fileno(),0);
+                       dup2($pr->fileno(),1);
+                       $pr->close();
+                       $pw->close();
+                       exec(@cvs);
+               }
+               $pw->writer();
+               $pr->reader();
+               $self->{'socketo'} = $pw;
+               $self->{'socketi'} = $pr;
+       }
+       $self->{'socketo'}->write("Root $repo\n");
+
+       # Trial and error says that this probably is the minimum set
+       $self->{'socketo'}->write("Valid-responses ok error Valid-requests Mode M Mbinary E F Checked-in Created Updated Merged Removed\n");
+
+       $self->{'socketo'}->write("valid-requests\n");
+       $self->{'socketo'}->flush();
+
+       chomp(my $rep=$self->readline());
+       if($rep !~ s/^Valid-requests\s*//) {
+               $rep="<unknown>" unless $rep;
+               die "Expected Valid-requests from server, but got: $rep\n";
+       }
+       chomp(my $res=$self->readline());
+       die "validReply: $res\n" if $res ne "ok";
+
+       $self->{'socketo'}->write("UseUnchanged\n") if $rep =~ /\bUseUnchanged\b/;
+       $self->{'repo'} = $repo;
+}
+
+sub readline {
+       my($self) = @_;
+       return $self->{'socketi'}->getline();
+}
+
+sub _file {
+       # Request a file with a given revision.
+       # Trial and error says this is a good way to do it. :-/
+       my($self,$fn,$rev) = @_;
+       $self->{'socketo'}->write("Argument -N\n") or return undef;
+       $self->{'socketo'}->write("Argument -P\n") or return undef;
+       # $self->{'socketo'}->write("Argument -ko\n") or return undef;
+       # -ko: Linus' version doesn't use it
+       $self->{'socketo'}->write("Argument -r\n") or return undef;
+       $self->{'socketo'}->write("Argument $rev\n") or return undef;
+       $self->{'socketo'}->write("Argument --\n") or return undef;
+       $self->{'socketo'}->write("Argument $self->{'subdir'}/$fn\n") or return undef;
+       $self->{'socketo'}->write("Directory .\n") or return undef;
+       $self->{'socketo'}->write("$self->{'repo'}\n") or return undef;
+       # $self->{'socketo'}->write("Sticky T1.0\n") or return undef;
+       $self->{'socketo'}->write("co\n") or return undef;
+       $self->{'socketo'}->flush() or return undef;
+       $self->{'lines'} = 0;
+       return 1;
+}
+sub _line {
+       # Read a line from the server.
+       # ... except that 'line' may be an entire file. ;-)
+       my($self, $fh) = @_;
+       die "Not in lines" unless defined $self->{'lines'};
+
+       my $line;
+       my $res=0;
+       while(defined($line = $self->readline())) {
+               # M U gnupg-cvs-rep/AUTHORS
+               # Updated gnupg-cvs-rep/
+               # /daten/src/rsync/gnupg-cvs-rep/AUTHORS
+               # /AUTHORS/1.1///T1.1
+               # u=rw,g=rw,o=rw
+               # 0
+               # ok
+
+               if($line =~ s/^(?:Created|Updated) //) {
+                       $line = $self->readline(); # path
+                       $line = $self->readline(); # Entries line
+                       my $mode = $self->readline(); chomp $mode;
+                       $self->{'mode'} = $mode;
+                       defined (my $cnt = $self->readline())
+                               or die "EOF from server after 'Changed'\n";
+                       chomp $cnt;
+                       die "Duh: Filesize $cnt" if $cnt !~ /^\d+$/;
+                       $line="";
+                       $res=0;
+                       while($cnt) {
+                               my $buf;
+                               my $num = $self->{'socketi'}->read($buf,$cnt);
+                               die "Server: Filesize $cnt: $num: $!\n" if not defined $num or $num<=0;
+                               print $fh $buf;
+                               $res += $num;
+                               $cnt -= $num;
+                       }
+               } elsif($line =~ s/^ //) {
+                       print $fh $line;
+                       $res += length($line);
+               } elsif($line =~ /^M\b/) {
+                       # output, do nothing
+               } elsif($line =~ /^Mbinary\b/) {
+                       my $cnt;
+                       die "EOF from server after 'Mbinary'" unless defined ($cnt = $self->readline());
+                       chomp $cnt;
+                       die "Duh: Mbinary $cnt" if $cnt !~ /^\d+$/ or $cnt<1;
+                       $line="";
+                       while($cnt) {
+                               my $buf;
+                               my $num = $self->{'socketi'}->read($buf,$cnt);
+                               die "S: Mbinary $cnt: $num: $!\n" if not defined $num or $num<=0;
+                               print $fh $buf;
+                               $res += $num;
+                               $cnt -= $num;
+                       }
+               } else {
+                       chomp $line;
+                       if($line eq "ok") {
+                               # print STDERR "S: ok (".length($res).")\n";
+                               return $res;
+                       } elsif($line =~ s/^E //) {
+                               # print STDERR "S: $line\n";
+                       } else {
+                               die "Unknown: $line\n";
+                       }
+               }
+       }
+}
+sub file {
+       my($self,$fn,$rev) = @_;
+       my $res;
+
+       my ($fh, $name) = tempfile('gitcvs.XXXXXX', 
+                   DIR => File::Spec->tmpdir(), UNLINK => 1);
+
+       $self->_file($fn,$rev) and $res = $self->_line($fh);
+
+       if (!defined $res) {
+           # retry
+           $self->conn();
+           $self->_file($fn,$rev)
+                   or die "No file command send\n";
+           $res = $self->_line($fh);
+           die "No input: $fn $rev\n" unless defined $res;
+       }
+       close ($fh);
+
+       return ($name, $res);
+}
+
+
+package main;
+
+my $cvs = CVSconn->new($opt_d, $cvs_tree);
+
+
+sub pdate($) {
+       my($d) = @_;
+       m#(\d{2,4})/(\d\d)/(\d\d)\s(\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 pmode($) {
+       my($mode) = @_;
+       my $m = 0;
+       my $mm = 0;
+       my $um = 0;
+       for my $x(split(//,$mode)) {
+               if($x eq ",") {
+                       $m |= $mm&$um;
+                       $mm = 0;
+                       $um = 0;
+               } elsif($x eq "u") { $um |= 0700;
+               } elsif($x eq "g") { $um |= 0070;
+               } elsif($x eq "o") { $um |= 0007;
+               } elsif($x eq "r") { $mm |= 0444;
+               } elsif($x eq "w") { $mm |= 0222;
+               } elsif($x eq "x") { $mm |= 0111;
+               } elsif($x eq "=") { # do nothing
+               } else { die "Unknown mode: $mode\n";
+               }
+       }
+       $m |= $mm&$um;
+       return $m;
+}
+
+sub getwd() {
+       my $pwd = `pwd`;
+       chomp $pwd;
+       return $pwd;
+}
+
+-d $git_tree
+       or mkdir($git_tree,0777)
+       or die "Could not create $git_tree: $!";
+chdir($git_tree);
+
+my $last_branch = "";
+my $orig_branch = "";
+my $forward_master = 0;
+my %branch_date;
+
+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;
+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";
+
+       $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;
+       if (-f "$git_dir/CVS2GIT_HEAD") {
+               die <<EOM;
+CVS2GIT_HEAD exists.
+Make sure your working directory corresponds to HEAD and remove CVS2GIT_HEAD.
+You may need to run
+
+    git-read-tree -m -u CVS2GIT_HEAD HEAD
+EOM
+       }
+       system('cp', "$git_dir/HEAD", "$git_dir/CVS2GIT_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_branch);
+       die "read-tree failed: $?\n" if $?;
+
+       # Get the last import timestamps
+       opendir(D,"$git_dir/refs/heads");
+       while(defined(my $head = readdir(D))) {
+               next if $head =~ /^\./;
+               open(F,"$git_dir/refs/heads/$head")
+                       or die "Bad head branch: $head: $!\n";
+               chomp(my $ftag = <F>);
+               close(F);
+               open(F,"git-cat-file commit $ftag |");
+               while(<F>) {
+                       next unless /^author\s.*\s(\d+)\s[-+]\d{4}$/;
+                       $branch_date{$head} = $1;
+                       last;
+               }
+               close(F);
+       }
+       closedir(D);
+}
+
+-d $git_dir
+       or die "Could not create git subdir ($git_dir).\n";
+
+my $pid = open(CVS,"-|");
+die "Cannot fork: $!\n" unless defined $pid;
+unless($pid) {
+       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";
+}
+
+
+## cvsps output:
+#---------------------
+#PatchSet 314
+#Date: 1999/09/18 13:03:59
+#Author: wkoch
+#Branch: STABLE-BRANCH-1-0
+#Ancestor branch: HEAD
+#Tag: (none)
+#Log:
+#    See ChangeLog: Sat Sep 18 13:03:28 CEST 1999  Werner Koch
+#Members:
+#      README:1.57->1.57.2.1
+#      VERSION:1.96->1.96.2.1
+#
+#---------------------
+
+my $state = 0;
+
+my($patchset,$date,$author,$branch,$ancestor,$tag,$logmsg);
+my(@old,@new);
+my $commit = sub {
+       my $pid;
+       while(@old) {
+               my @o2;
+               if(@old > 55) {
+                       @o2 = splice(@old,0,50);
+               } else {
+                       @o2 = @old;
+                       @old = ();
+               }
+               system("git-update-cache","--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-cache","--add",
+                       (map { ('--cacheinfo', @$_) } @n2));
+               die "Cannot add files: $?\n" if $?;
+       }
+
+       $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 $parent = "";
+       if(open(C,"$git_dir/refs/heads/$last_branch")) {
+               chomp($parent = <C>);
+               close(C);
+               length($parent) == 40
+                       or die "Cannot get parent id ($parent): $!\n";
+               print "Parent ID $parent\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();
+               dup2($pw->fileno(),0);
+               dup2($pr->fileno(),1);
+               $pr->close();
+               $pw->close();
+
+               my @par = ();
+               @par = ("-p",$parent) if $parent;
+               exec("env",
+                       "GIT_AUTHOR_NAME=$author",
+                       "GIT_AUTHOR_EMAIL=$author",
+                       "GIT_AUTHOR_DATE=".strftime("+0000 %Y-%m-%d %H:%M:%S",gmtime($date)),
+                       "GIT_COMMITTER_NAME=$author",
+                       "GIT_COMMITTER_EMAIL=$author",
+                       "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();
+
+       # compatibility with git2cvs
+       substr($logmsg,32767) = "" if length($logmsg) > 32767;
+       $logmsg =~ s/[\s\n]+\z//;
+
+       print $pw "$logmsg\n"
+               or die "Error writing to git-commit-tree: $!\n";
+       $pw->close();
+
+       print "Committed patch $patchset ($branch)\n" if $opt_v;
+       chomp(my $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 $?;
+
+       open(C,">$git_dir/refs/heads/$branch")
+               or die "Cannot open branch $branch for update: $!\n";
+       print C "$cid\n"
+               or die "Cannot write branch $branch for update: $!\n";
+       close(C)
+               or die "Cannot write branch $branch for update: $!\n";
+
+       if($tag) {
+               open(C,">$git_dir/refs/tags/$tag")
+                       or die "Cannot create tag $tag: $!\n";
+               print C "$cid\n"
+                       or die "Cannot write tag $branch: $!\n";
+               close(C)
+                       or die "Cannot write tag $branch: $!\n";
+               print "Created tag '$tag' on '$branch'\n" if $opt_v;
+       }
+};
+
+while(<CVS>) {
+       chomp;
+       if($state == 0 and /^-+$/) {
+               $state = 1;
+       } elsif($state == 0) {
+               $state = 1;
+               redo;
+       } elsif(($state==0 or $state==1) and s/^PatchSet\s+//) {
+               $patchset = 0+$_;
+               $state=2;
+       } elsif($state == 2 and s/^Date:\s+//) {
+               $date = pdate($_);
+               unless($date) {
+                       print STDERR "Could not parse date: $_\n";
+                       $state=0;
+                       next;
+               }
+               $state=3;
+       } elsif($state == 3 and s/^Author:\s+//) {
+               s/\s+$//;
+               $author = $_;
+               $state = 4;
+       } elsif($state == 4 and s/^Branch:\s+//) {
+               s/\s+$//;
+               $branch = $_;
+               $state = 5;
+       } elsif($state == 5 and s/^Ancestor branch:\s+//) {
+               s/\s+$//;
+               $ancestor = $_;
+               $ancestor = $opt_o if $ancestor eq "HEAD";
+               $state = 6;
+       } elsif($state == 5) {
+               $ancestor = undef;
+               $state = 6;
+               redo;
+       } elsif($state == 6 and s/^Tag:\s+//) {
+               s/\s+$//;
+               if($_ eq "(none)") {
+                       $tag = undef;
+               } else {
+                       $tag = $_;
+               }
+               $state = 7;
+       } elsif($state == 7 and /^Log:/) {
+               $logmsg = "";
+               $state = 8;
+       } elsif($state == 8 and /^Members:/) {
+               $branch = $opt_o if $branch eq "HEAD";
+               if(defined $branch_date{$branch} and $branch_date{$branch} >= $date) {
+                       # skip
+                       print "skip patchset $patchset: $date before $branch_date{$branch}\n" if $opt_v;
+                       $state = 11;
+                       next;
+               }
+               if($ancestor) {
+                       if(-f "$git_dir/refs/heads/$branch") {
+                               print STDERR "Branch $branch already exists!\n";
+                               $state=11;
+                               next;
+                       }
+                       unless(open(H,"$git_dir/refs/heads/$ancestor")) {
+                               print STDERR "Branch $ancestor does not exist!\n";
+                               $state=11;
+                               next;
+                       }
+                       chomp(my $id = <H>);
+                       close(H);
+                       unless(open(H,"> $git_dir/refs/heads/$branch")) {
+                               print STDERR "Could not create branch $branch: $!\n";
+                               $state=11;
+                               next;
+                       }
+                       print H "$id\n"
+                               or die "Could not write branch $branch: $!";
+                       close(H)
+                               or die "Could not write branch $branch: $!";
+               }
+               if(($ancestor || $branch) ne $last_branch) {
+                       print "Switching from $last_branch to $branch\n" if $opt_v;
+                       system("git-read-tree", $branch);
+                       die "read-tree failed: $?\n" if $?;
+               }
+               $last_branch = $branch if $branch ne $last_branch;
+               $state = 9;
+       } elsif($state == 8) {
+               $logmsg .= "$_\n";
+       } elsif($state == 9 and /^\s+(\S+):(INITIAL|\d+(?:\.\d+)+)->(\d+(?:\.\d+)+)\s*$/) {
+#      VERSION:1.96->1.96.2.1
+               my $init = ($2 eq "INITIAL");
+               my $fn = $1;
+               my $rev = $3;
+               $fn =~ s#^/+##;
+               my ($tmpname, $size) = $cvs->file($fn,$rev);
+               print "".($init ? "New" : "Update")." $fn: $size bytes.\n" if $opt_v;
+               open my $F, '-|', "git-hash-object -w $tmpname"
+                       or die "Cannot create object: $!\n";
+               my $sha = <$F>;
+               chomp $sha;
+               close $F;
+               unlink($tmpname);
+               my $mode = pmode($cvs->{'mode'});
+               push(@new,[$mode, $sha, $fn]); # may be resurrected!
+       } elsif($state == 9 and /^\s+(\S+):\d(?:\.\d+)+->(\d(?:\.\d+)+)\(DEAD\)\s*$/) {
+               my $fn = $1;
+               $fn =~ s#^/+##;
+               push(@old,$fn);
+       } elsif($state == 9 and /^\s*$/) {
+               $state = 10;
+       } elsif(($state == 9 or $state == 10) and /^-+$/) {
+               &$commit();
+               $state = 1;
+       } elsif($state == 11 and /^-+$/) {
+               $state = 1;
+       } elsif(/^-+$/) { # end of unknown-line processing
+               $state = 1;
+       } elsif($state != 11) { # ignore stuff when skipping
+               print "* UNKNOWN LINE * $_\n";
+       }
+}
+&$commit() if $branch and $state != 11;
+
+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;
+       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', 'CVS2GIT_HEAD', 'HEAD');
+               die "read-tree failed: $?\n" if $?;
+       }
+} else {
+       $orig_branch = "master";
+       print "DONE; creating $orig_branch branch\n" if $opt_v;
+       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/CVS2GIT_HEAD");
diff --git a/git-diff-script b/git-diff-script
new file mode 100755 (executable)
index 0000000..03ed555
--- /dev/null
@@ -0,0 +1,19 @@
+#!/bin/sh
+. git-sh-setup-script || die "Not a git archive"
+
+rev=($(git-rev-parse --revs-only "$@"))
+flags=($(git-rev-parse --no-revs --flags "$@"))
+files=($(git-rev-parse --no-revs --no-flags "$@"))
+case "${#rev[*]}" in
+0)
+       git-diff-files -M -p "$@";;
+1)
+       git-diff-cache -M -p "$@";;
+2)
+       begin=$(echo "${rev[1]}" | tr -d '^')
+       end="${rev[0]}"
+       git-diff-tree -M -p $flags $begin $end $files;;
+*)
+       echo "I don't understand"
+       exit 1;;
+esac
diff --git a/git-external-diff-script b/git-external-diff-script
new file mode 100755 (executable)
index 0000000..137280a
--- /dev/null
@@ -0,0 +1,67 @@
+#!/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
+
diff --git a/git-fetch-script b/git-fetch-script
new file mode 100755 (executable)
index 0000000..24f0a5e
--- /dev/null
@@ -0,0 +1,50 @@
+#!/bin/sh
+#
+. git-sh-setup-script || die "Not a git archive"
+. git-parse-remote "$@"
+merge_repo="$_remote_repo"
+merge_head="$_remote_head"
+merge_store="$_remote_store"
+
+TMP_HEAD="$GIT_DIR/TMP_HEAD"
+
+case "$merge_repo" in
+http://* | https://*)
+        if [ -n "$GIT_SSL_NO_VERIFY" ]; then
+            curl_extra_args="-k"
+        fi
+       _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" &&
+       head=$(curl -nsf $curl_extra_args "$merge_repo/$merge_head") &&
+       expr "$head" : "$_x40\$" >/dev/null || {
+               echo >&2 "Failed to fetch $merge_head from $merge_repo"
+               exit 1
+       }
+       echo Fetching "$merge_head" using http
+       git-http-pull -v -a "$head" "$merge_repo/" || exit
+       ;;
+rsync://*)
+       rsync -L "$merge_repo/$merge_head" "$TMP_HEAD" || exit 1
+       head=$(git-rev-parse TMP_HEAD)
+       rm -f "$TMP_HEAD"
+       rsync -avz --ignore-existing "$merge_repo/objects/" "$GIT_OBJECT_DIRECTORY/"
+       ;;
+*)
+       head=$(git-fetch-pack "$merge_repo" "$merge_head")
+       ;;
+esac || exit 1
+
+git-rev-parse --verify "$head" > /dev/null || exit 1
+
+case "$merge_store" in
+'')
+       ;;
+*)
+       echo "$head" > "$GIT_DIR/$merge_store"
+esac &&
+
+# FETCH_HEAD is fed to git-resolve-script which will eventually be
+# passed to git-commit-tree as one of the parents.  Make sure we do
+# not give a tag object ID.
+
+git-rev-parse "$head^0" >"$GIT_DIR/FETCH_HEAD"
diff --git a/git-format-patch-script b/git-format-patch-script
new file mode 100755 (executable)
index 0000000..c008169
--- /dev/null
@@ -0,0 +1,198 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+
+. git-sh-setup-script || die "Not a git archive."
+
+usage () {
+    echo >&2 "usage: $0"' [-n] [-o dir] [--mbox] [--check] [-<diff options>...] 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
+numbered sequentially from 1, and uses the first line of the commit
+message (massaged for pathname safety) as the filename.
+
+When -o is specified, output files are created in that directory; otherwise in
+the current working directory.
+
+When -n is specified, instead of "[PATCH] Subject", the first line is formatted
+as "[PATCH N/M] Subject", unless you have only one patch.
+
+When --mbox is specified, the output is formatted to resemble
+UNIX mailbox format, and can be concatenated together for processing
+with applymbox.
+'
+    exit 1
+}
+
+diff_opts=
+IFS='
+'
+LF='
+'
+
+outdir=./
+while case "$#" in 0) break;; esac
+do
+    case "$1" in
+    -a|--a|--au|--aut|--auth|--autho|--author)
+    author=t ;;
+    -c|--c|--ch|--che|--chec|--check)
+    check=t ;;
+    -d|--d|--da|--dat|--date)
+    date=t ;;
+    -m|--m|--mb|--mbo|--mbox)
+    date=t author=t mbox=t ;;
+    -n|--n|--nu|--num|--numb|--numbe|--number|--numbere|--numbered)
+    numbered=t ;;
+    -o=*|--o=*|--ou=*|--out=*|--outp=*|--outpu=*|--output=*|--output-=*|\
+    --output-d=*|--output-di=*|--output-dir=*|--output-dire=*|\
+    --output-direc=*|--output-direct=*|--output-directo=*|\
+    --output-director=*|--output-directory=*)
+    outdir=`expr "$1" : '-[^=]*=\(.*\)'` ;;
+    -o|--o|--ou|--out|--outp|--outpu|--output|--output-|--output-d|\
+    --output-di|--output-dir|--output-dire|--output-direc|--output-direct|\
+    --output-directo|--output-director|--output-directory)
+    case "$#" in 1) usage ;; esac; shift
+    outdir="$1" ;;
+    -*)        diff_opts="$diff_opts$LF$1" ;;
+    *) break ;;
+    esac
+    shift
+done
+
+revpair=
+case "$#" in
+2)
+    revpair="$1..$2" ;;
+1)
+    case "$1" in
+    *..*)
+       revpair="$1";;
+    *)
+       revpair="$1..HEAD";;
+    esac ;;
+*)
+    usage ;;
+esac
+
+me=`git-var GIT_AUTHOR_IDENT | sed -e 's/>.*/>/'`
+
+case "$outdir" in
+*/) ;;
+*) outdir="$outdir/" ;;
+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
+       s/^\[PATCH[^]]*\] *//
+       s/[^-a-z.A-Z_0-9]/-/g
+        s/\.\.\.*/\./g
+       s/\.*$//
+       s/--*/-/g
+       s/^-//
+       s/-$//
+       s/$/./
+       p
+       q
+'
+
+whosepatchScript='
+/^author /{
+       s/author \(.*>\) \(.*\)$/au='\''\1'\'' ad='\''\2'\''/p
+       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-rev-list --no-merges --merge-order \
+       $(git-rev-parse --revs-only "$revpair") >$series
+total=`wc -l <$series | tr -dc "[0-9]"`
+i=$total
+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 >&2 "* $file"
+    {
+       mailScript='
+       /./d
+       /^$/n
+       s|^\[PATCH[^]]*\] *||'
+
+       case "$mbox" in
+       t)
+           echo 'From nobody Mon Sep 17 00:00:00 2001' ;# UNIX "From" line
+           mailScript="$mailScript"'
+           s|^|Subject: [PATCH'"$num"'] |'
+           ;;
+       *)
+           mailScript="$mailScript"'
+           s|^|[PATCH'"$num"'] |'
+           ;;
+       esac
+
+       eval "$(sed -ne "$whosepatchScript" $commsg)"
+       test "$author,$au" = ",$me" || {
+               mailScript="$mailScript"'
+       a\
+From: '"$au"
+       }
+       test "$date,$au" = ",$me" || {
+               mailScript="$mailScript"'
+       a\
+Date: '"$ad"
+       }
+
+       mailScript="$mailScript"'
+       : body
+       p
+       n
+       b body'
+
+       sed -ne "$mailScript" <$commsg
+       echo '---'
+       echo
+       git-diff-tree -p $diff_opts "$commit" | git-apply --stat --summary
+       echo
+       git-diff-tree -p $diff_opts "$commit" | sed -e "$stripCommitHead"
+
+       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.
+    esac
+done <$series
diff --git a/git-log-script b/git-log-script
new file mode 100755 (executable)
index 0000000..24d1e83
--- /dev/null
@@ -0,0 +1,5 @@
+#!/bin/sh
+. git-sh-setup-script || die "Not a git archive"
+revs=$(git-rev-parse --revs-only --default HEAD "$@")
+[ "$revs" ] || die "No HEAD ref"
+git-rev-list --pretty $(git-rev-parse --default HEAD "$@") | LESS=-S ${PAGER:-less}
diff --git a/git-ls-remote-script b/git-ls-remote-script
new file mode 100755 (executable)
index 0000000..75f6027
--- /dev/null
@@ -0,0 +1,100 @@
+#!/bin/sh
+#
+. git-sh-setup-script || die "Not a git archive"
+
+usage () {
+    echo >&2 "usage: $0 [--heads] [--tags] <repository> <refs>..."
+    exit 1;
+}
+
+while case "$#" in 0) break;; esac
+do
+  case "$1" in
+  -h|--h|--he|--hea|--head|--heads)
+  heads=heads; shift ;;
+  -t|--t|--ta|--tag|--tags)
+  tags=tags; shift ;;
+  --)
+  shift; break ;;
+  -*)
+  usage ;;
+  *)
+  break ;;
+  esac
+done
+
+case "$#" in 0) usage ;; esac
+
+case ",$heads,$tags," in
+,,,) heads=heads tags=tags other=other ;;
+esac
+
+. git-parse-remote "$1"
+peek_repo="$_remote_repo"
+shift
+
+tmp=.ls-remote-$$
+trap "rm -fr $tmp-*" 0 1 2 3 15
+tmpdir=$tmp-d
+
+case "$peek_repo" in
+http://* | https://* )
+        if [ -n "$GIT_SSL_NO_VERIFY" ]; then
+            curl_extra_args="-k"
+        fi
+       curl -nsf $curl_extra_args "$peek_repo/info/refs" || exit 1
+       ;;
+
+rsync://* )
+       mkdir $tmpdir
+       rsync -rq "$peek_repo/refs" $tmpdir || exit 1
+       (cd $tmpdir && find refs -type f) |
+       while read path
+       do
+               cat "$tmpdir/$path" | tr -d '\012'
+               echo "  $path"
+       done &&
+       rm -fr $tmpdir
+       ;;
+
+* )
+       git-peek-remote "$peek_repo"
+       ;;
+esac |
+sort -t '      ' -k 2 |
+while read sha1 path
+do
+       case "$path" in
+       refs/heads/*)
+               group=heads ;;
+       refs/tags/*)
+               group=tags ;;
+       *)
+               group=other ;;
+       esac
+       case ",$heads,$tags,$other," in
+       *,$group,*)
+               ;;
+       *)
+               continue;;
+       esac
+       case "$#" in
+       0)
+               match=yes ;;
+       *)
+               match=no
+               for pat
+               do
+                       case "/$path" in
+                       */$pat )
+                               match=yes
+                               break ;;
+                       esac
+               done
+       esac
+       case "$match" in
+       no)
+               continue ;;
+       esac
+       echo "$sha1     $path"
+done
diff --git a/git-merge-one-file-script b/git-merge-one-file-script
new file mode 100755 (executable)
index 0000000..be64c07
--- /dev/null
@@ -0,0 +1,86 @@
+#!/bin/sh
+#
+# Copyright (c) Linus Torvalds, 2005
+#
+# This is the git per-file merge script, called with
+#
+#   $1 - original file SHA1 (or empty)
+#   $2 - file in branch1 SHA1 (or empty)
+#   $3 - file in branch2 SHA1 (or empty)
+#   $4 - pathname in repository
+#   $5 - orignal file mode (or empty)
+#   $6 - file in branch1 mode (or empty)
+#   $7 - file in branch2 mode (or empty)
+#
+# Handle some trivial cases.. The _really_ trivial cases have
+# been handled already by git-read-tree, but that one doesn't
+# do any merges that might change the tree layout.
+
+case "${1:-.}${2:-.}${3:-.}" in
+#
+# Deleted in both or deleted in one and unchanged in the other
+#
+"$1.." | "$1.$1" | "$1$1.")
+       echo "Removing $4"
+       if test -f "$4"; then
+               rm -f -- "$4"
+       fi &&
+               exec git-update-cache --remove -- "$4"
+       ;;
+
+#
+# Added in one.
+#
+".$2." | "..$3" )
+       echo "Adding $4"
+       git-update-cache --add --cacheinfo "$6$7" "$2$3" "$4" &&
+               exec git-checkout-cache -u -f -- "$4"
+       ;;
+
+#
+# Added in both (check for same permissions).
+#
+".$3$2")
+       if [ "$6" != "$7" ]; then
+               echo "ERROR: File $4 added identically in both branches,"
+               echo "ERROR: but permissions conflict $6->$7."
+               exit 1
+       fi
+       echo "Adding $4"
+       git-update-cache --add --cacheinfo "$6" "$2" "$4" &&
+               exec git-checkout-cache -u -f -- "$4"
+       ;;
+
+#
+# Modified in both, but differently.
+#
+"$1$2$3")
+       echo "Auto-merging $4."
+       orig=`git-unpack-file $1`
+       src2=`git-unpack-file $3`
+
+       # We reset the index to the first branch, making
+       # git-diff-file useful
+       git-update-cache --add --cacheinfo "$6" "$2" "$4"
+               git-checkout-cache -u -f -- "$4" &&
+               merge "$4" "$orig" "$src2"
+       ret=$?
+       rm -f -- "$orig" "$src2"
+
+       if [ "$6" != "$7" ]; then
+               echo "ERROR: Permissions conflict: $5->$6,$7."
+               ret=1
+       fi
+
+       if [ $ret -ne 0 ]; then
+               echo "ERROR: Merge conflict in $4."
+               exit 1
+       fi
+       exec git-update-cache -- "$4"
+       ;;
+
+*)
+       echo "ERROR: $4: Not handling case $1 -> $2 -> $3"
+       ;;
+esac
+exit 1
diff --git a/git-parse-remote b/git-parse-remote
new file mode 100755 (executable)
index 0000000..bfe7a90
--- /dev/null
@@ -0,0 +1,79 @@
+: To be included in git-pull and git-fetch scripts.
+
+# A remote repository can be specified on the command line
+# in one of the following formats:
+#
+#      <repo>
+#      <repo> <head>
+#      <repo> tag <tag>
+#
+# where <repo> could be one of:
+#
+#      a URL (including absolute or local pathname)
+#      a short-hand
+#      a short-hand followed by a trailing path
+#
+# A short-hand <name> has a corresponding file $GIT_DIR/branches/<name>,
+# whose contents is a URL, possibly followed by a URL fragment #<head>
+# to name the default branch on the remote side to fetch from.
+
+_remote_repo= _remote_store= _remote_head= _remote_name=
+
+case "$1" in
+*:* | /* | ../* | ./* )
+       _remote_repo="$1"
+       ;;
+* )
+       # otherwise, it is a short hand.
+       case "$1" in
+       */*)
+               # a short-hand followed by a trailing path
+               _token=$(expr "$1" : '\([^/]*\)/')
+               _rest=$(expr "$1" : '[^/]*\(/.*\)$')
+               ;;
+       *)
+               _token="$1"
+               _rest=
+               _remote_store="refs/heads/$_token"
+               ;;
+       esac
+       test -f "$GIT_DIR/branches/$_token" ||
+       die "No such remote branch: $_token"
+
+       _remote_repo=$(cat "$GIT_DIR/branches/$_token")"$_rest"
+       ;;
+esac
+
+case "$_remote_repo" in
+*"#"*)
+       _remote_head=`expr "$_remote_repo" : '.*#\(.*\)$'`
+       _remote_repo=`expr "$_remote_repo" : '\(.*\)#'`
+       ;;
+esac
+
+_remote_name=$(echo "$_remote_repo" | sed 's|\.git/*$||')
+
+case "$2" in
+tag)
+       _remote_name="tag '$3' of $_remote_name"
+       _remote_head="refs/tags/$3"
+       _remote_store="$_remote_head"
+       ;;
+?*)
+       # command line specified a head explicitly; do not
+       # store the fetched head as a branch head.
+       _remote_name="head '$2' of $_remote_name"
+       _remote_head="refs/heads/$2"
+       _remote_store=''
+       ;;
+'')
+       case "$_remote_head" in
+       '')
+               _remote_head=HEAD ;;
+       *)
+               _remote_head="refs/heads/$_remote_head"
+               _remote_name="head '$_remote_head' of $_remote_name"
+               ;;
+       esac
+       ;;
+esac
diff --git a/git-prune-script b/git-prune-script
new file mode 100755 (executable)
index 0000000..a8e338c
--- /dev/null
@@ -0,0 +1,25 @@
+#!/bin/sh
+
+. git-sh-setup-script || die "Not a git archive"
+
+dryrun=
+while case "$#" in 0) break ;; esac
+do
+    case "$1" in
+    -n) dryrun=echo ;;
+    --) break ;;
+    -*) echo >&2 "usage: git-prune-script [ -n ] [ heads... ]"; exit 1 ;;
+    *)  break ;;
+    esac
+    shift;
+done
+
+git-fsck-cache --full --cache --unreachable "$@" |
+sed -ne '/unreachable /{
+    s/unreachable [^ ][^ ]* //
+    s|\(..\)|\1/|p
+}' | {
+       cd "$GIT_OBJECT_DIRECTORY" || exit
+       xargs $dryrun rm -f
+}
+
diff --git a/git-pull-script b/git-pull-script
new file mode 100755 (executable)
index 0000000..fc46326
--- /dev/null
@@ -0,0 +1,12 @@
+#!/bin/sh
+#
+. git-sh-setup-script || die "Not a git archive"
+. git-parse-remote "$@"
+merge_name="$_remote_name"
+
+git-fetch-script "$@" || exit 1
+
+git-resolve-script \
+       "$(cat "$GIT_DIR"/HEAD)" \
+       "$(cat "$GIT_DIR"/FETCH_HEAD)" \
+       "Merge $merge_name"
diff --git a/git-push-script b/git-push-script
new file mode 100755 (executable)
index 0000000..70fa968
--- /dev/null
@@ -0,0 +1,64 @@
+#!/bin/sh
+. git-sh-setup-script || die "Not a git archive"
+
+# Parse out parameters and then stop at remote, so that we can
+# translate it using .git/branches information
+has_all=
+has_force=
+has_exec=
+remote=
+
+while case "$#" in 0) break ;; esac
+do
+       case "$1" in
+       --all)
+               has_all=--all ;;
+       --force)
+               has_force=--force ;;
+       --exec=*)
+               has_exec="$1" ;;
+       -*)
+               die "Unknown parameter $1" ;;
+        *)
+               remote="$1"
+               shift
+               set x "$@"
+               shift
+               break ;;
+       esac
+       shift
+done
+
+case "$remote" in
+*:* | /* | ../* | ./* )
+       # An URL, host:/path/to/git, absolute and relative paths.
+       ;;
+* )
+       # Shorthand
+       if expr "$remote" : '..*/..*' >/dev/null
+       then
+               # a short-hand followed by a trailing path
+               shorthand=$(expr "$remote" : '\([^/]*\)')
+               remainder=$(expr "$remote" : '[^/]*\(/.*\)$')
+       else
+               shorthand="$remote"
+               remainder=
+       fi
+       remote=$(sed -e 's/#.*//' "$GIT_DIR/branches/$remote") &&
+       expr "$remote" : '..*:' >/dev/null &&
+       remote="$remote$remainder" ||
+       die "Cannot parse remote $remote"
+       ;;
+esac
+
+case "$remote" in
+http://* | https://* | git://* | rsync://* )
+       die "Cannot push to $remote" ;;
+esac
+
+set x "$remote" "$@"; shift
+test "$has_all" && set x "$has_all" "$@" && shift
+test "$has_force" && set x "$has_force" "$@" && shift
+test "$has_exec" && set x "$has_exec" "$@" && shift
+
+exec git-send-pack "$@"
diff --git a/git-rebase-script b/git-rebase-script
new file mode 100755 (executable)
index 0000000..7779813
--- /dev/null
@@ -0,0 +1,56 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano.
+#
+
+. git-sh-setup-script || die "Not a git archive."
+
+usage="usage: $0 "'<upstream> [<head>]
+
+Uses output from git-cherry to rebase local commits to the new head of
+upstream tree.'
+
+case "$#,$1" in
+1,*..*)
+    upstream=$(expr "$1" : '\(.*\)\.\.') ours=$(expr "$1" : '.*\.\.\(.*\)$')
+    set x "$upstream" "$ours"
+    shift ;;
+esac
+
+case "$#" in
+1) upstream=`git-rev-parse --verify "$1"` &&
+   ours=`git-rev-parse --verify HEAD` || exit
+   ;;
+2) upstream=`git-rev-parse --verify "$1"` &&
+   ours=`git-rev-parse --verify "$2"` || exit
+   ;;
+*) echo >&2 "$usage"; exit 1 ;;
+esac
+
+git-read-tree -m -u $ours $upstream &&
+echo "$upstream" >"$GIT_DIR/HEAD" || exit
+
+tmp=.rebase-tmp$$
+fail=$tmp-fail
+trap "rm -rf $tmp-*" 0 1 2 3 15
+
+>$fail
+
+git-cherry $upstream $ours |
+while read sign commit
+do
+       case "$sign" in
+       -) continue ;;
+       esac
+       S=`cat "$GIT_DIR/HEAD"` &&
+        GIT_EXTERNAL_DIFF=git-apply-patch-script git-diff-tree -p $commit &&
+       git-commit-script -C "$commit" || {
+               echo $commit >>$fail
+               git-read-tree --reset -u $S
+       }
+done
+if test -s $fail
+then
+       echo Some commits could not be rebased, check by hand:
+       cat $fail
+fi
diff --git a/git-relink-script b/git-relink-script
new file mode 100755 (executable)
index 0000000..f6b4f6a
--- /dev/null
@@ -0,0 +1,173 @@
+#!/usr/bin/env perl
+# Copyright 2005, Ryan Anderson <ryan@michonline.com>
+# Distribution permitted under the GPL v2, as distributed
+# by the Free Software Foundation.
+# Later versions of the GPL at the discretion of Linus Torvalds
+#
+# Scan two git object-trees, and hardlink any common objects between them.
+
+use 5.006;
+use strict;
+use warnings;
+use Getopt::Long;
+
+sub get_canonical_form($);
+sub do_scan_directory($$$);
+sub compare_two_files($$);
+sub usage();
+sub link_two_files($$);
+
+# stats
+my $total_linked = 0;
+my $total_already = 0;
+my ($linked,$already);
+
+my $fail_on_different_sizes = 0;
+my $help = 0;
+GetOptions("safe" => \$fail_on_different_sizes,
+          "help" => \$help);
+
+usage() if $help;
+
+my (@dirs) = @ARGV;
+
+usage() if (!defined $dirs[0] || !defined $dirs[1]);
+
+$_ = get_canonical_form($_) foreach (@dirs);
+
+my $master_dir = pop @dirs;
+
+opendir(D,$master_dir . "objects/")
+       or die "Failed to open $master_dir/objects/ : $!";
+
+my @hashdirs = grep !/^\.{1,2}$/, readdir(D);
+
+foreach my $repo (@dirs) {
+       $linked = 0;
+       $already = 0;
+       printf("Searching '%s' and '%s' for common objects and hardlinking them...\n",
+               $master_dir,$repo);
+
+       foreach my $hashdir (@hashdirs) {
+               do_scan_directory($master_dir, $hashdir, $repo);
+       }
+
+       printf("Linked %d files, %d were already linked.\n",$linked, $already);
+
+       $total_linked += $linked;
+       $total_already += $already;
+}
+
+printf("Totals: Linked %d files, %d were already linked.\n",
+       $total_linked, $total_already);
+
+
+sub do_scan_directory($$$) {
+       my ($srcdir, $subdir, $dstdir) = @_;
+
+       my $sfulldir = sprintf("%sobjects/%s/",$srcdir,$subdir);
+       my $dfulldir = sprintf("%sobjects/%s/",$dstdir,$subdir);
+
+       opendir(S,$sfulldir)
+               or die "Failed to opendir $sfulldir: $!";
+
+       foreach my $file (grep(!/\.{1,2}$/, readdir(S))) {
+               my $sfilename = $sfulldir . $file;
+               my $dfilename = $dfulldir . $file;
+
+               compare_two_files($sfilename,$dfilename);
+
+       }
+       closedir(S);
+}
+
+sub compare_two_files($$) {
+       my ($sfilename, $dfilename) = @_;
+
+       # Perl's stat returns relevant information as follows:
+       # 0 = dev number
+       # 1 = inode number
+       # 7 = size
+       my @sstatinfo = stat($sfilename);
+       my @dstatinfo = stat($dfilename);
+
+       if (@sstatinfo == 0 && @dstatinfo == 0) {
+               die sprintf("Stat of both %s and %s failed: %s\n",$sfilename, $dfilename, $!);
+
+       } elsif (@dstatinfo == 0) {
+               return;
+       }
+
+       if ( ($sstatinfo[0] == $dstatinfo[0]) &&
+            ($sstatinfo[1] != $dstatinfo[1])) {
+               if ($sstatinfo[7] == $dstatinfo[7]) {
+                       link_two_files($sfilename, $dfilename);
+
+               } else {
+                       my $err = sprintf("ERROR: File sizes are not the same, cannot relink %s to %s.\n",
+                               $sfilename, $dfilename);
+                       if ($fail_on_different_sizes) {
+                               die $err;
+                       } else {
+                               warn $err;
+                       }
+               }
+
+       } elsif ( ($sstatinfo[0] == $dstatinfo[0]) &&
+            ($sstatinfo[1] == $dstatinfo[1])) {
+               $already++;
+       }
+}
+
+sub get_canonical_form($) {
+       my $dir = shift;
+       my $original = $dir;
+
+       die "$dir is not a directory." unless -d $dir;
+
+       $dir .= "/" unless $dir =~ m#/$#;
+       $dir .= ".git/" unless $dir =~ m#\.git/$#;
+
+       die "$original does not have a .git/ subdirectory.\n" unless -d $dir;
+
+       return $dir;
+}
+
+sub link_two_files($$) {
+       my ($sfilename, $dfilename) = @_;
+       my $tmpdname = sprintf("%s.old",$dfilename);
+       rename($dfilename,$tmpdname)
+               or die sprintf("Failure renaming %s to %s: %s",
+                       $dfilename, $tmpdname, $!);
+
+       if (! link($sfilename,$dfilename)) {
+               my $failtxt = "";
+               unless (rename($tmpdname,$dfilename)) {
+                       $failtxt = sprintf(
+                               "Git Repository containing %s is probably corrupted, " .
+                               "please copy '%s' to '%s' to fix.\n",
+                               $tmpdname, $dfilename);
+               }
+
+               die sprintf("Failed to link %s to %s: %s\n%s" .
+                       $sfilename, $dfilename,
+                       $!, $dfilename, $failtxt);
+       }
+
+       unlink($tmpdname)
+               or die sprintf("Unlink of %s failed: %s\n",
+                       $dfilename, $!);
+
+       $linked++;
+}
+
+
+sub usage() {
+       print("Usage: $0 [--safe] <dir> [<dir> ...] <master_dir> \n");
+       print("All directories should contain a .git/objects/ subdirectory.\n");
+       print("Options\n");
+       print("\t--safe\t" .
+               "Stops if two objects with the same hash exist but " .
+               "have different sizes.  Default is to warn and continue.\n");
+       exit(1);
+}
diff --git a/git-rename-script b/git-rename-script
new file mode 100755 (executable)
index 0000000..b4d3148
--- /dev/null
@@ -0,0 +1,70 @@
+#!/usr/bin/perl
+#
+# Copyright 2005, Ryan Anderson <ryan@michonline.com>
+#
+# This file is licensed under the GPL v2, or a later version
+# at the discretion of Linus Torvalds.
+
+
+use warnings;
+use strict;
+
+sub usage($);
+
+# Sanity checks:
+my $GIT_DIR = $ENV{'GIT_DIR'} || ".git";
+
+unless ( -d $GIT_DIR && -d $GIT_DIR . "/objects" && 
+       -d $GIT_DIR . "/objects/00" && -d $GIT_DIR . "/refs") {
+       usage("Git repository not found.");
+}
+
+usage("") if scalar @ARGV != 2;
+
+my ($src,$dst) = @ARGV;
+
+unless (-f $src || -l $src || -d $src) {
+       usage("git rename: bad source '$src'");
+}
+
+if (-e $dst) {
+       usage("git rename: destinations '$dst' already exists");
+}
+
+my (@allfiles,@srcfiles,@dstfiles);
+
+$/ = "\0";
+open(F,"-|","git-ls-files","-z")
+       or die "Failed to open pipe from git-ls-files: " . $!;
+
+@allfiles = map { chomp; $_; } <F>;
+close(F);
+
+my $safesrc = quotemeta($src);
+@srcfiles = grep /^$safesrc/, @allfiles;
+@dstfiles = @srcfiles;
+s#^$safesrc(/|$)#$dst$1# for @dstfiles;
+
+rename($src,$dst)
+       or die "rename failed: $!";
+
+my $rc = system("git-update-cache","--add","--",@dstfiles);
+die "git-update-cache failed to add new name with code $?\n" if $rc;
+
+$rc = system("git-update-cache","--remove","--",@srcfiles);
+die "git-update-cache failed to remove old name with code $?\n" if $rc;
+
+
+sub usage($) {
+       my $s = shift;
+       print $s, "\n" if (length $s != 0);
+       print <<EOT;
+$0 <source> <dest>
+source must exist and be either a file, symlink or directory.
+dest must NOT exist.
+
+Renames source to dest, and updates the git cache to reflect the change.
+Use "git commit" to make record the change permanently.
+EOT
+       exit(1);
+}
diff --git a/git-repack-script b/git-repack-script
new file mode 100755 (executable)
index 0000000..15b7fd6
--- /dev/null
@@ -0,0 +1,14 @@
+#!/bin/sh
+. git-sh-setup-script || die "Not a git archive"
+       
+rm -f .tmp-pack-*
+packname=$(git-rev-list --unpacked --objects $(git-rev-parse --all) |
+       git-pack-objects --non-empty --incremental .tmp-pack) ||
+       exit 1
+if [ -z "$packname" ]; then
+       echo Nothing new to pack
+       exit 0
+fi
+mkdir -p "$GIT_OBJECT_DIRECTORY/pack" &&
+mv .tmp-pack-$packname.pack "$GIT_OBJECT_DIRECTORY/pack/pack-$packname.pack" &&
+mv .tmp-pack-$packname.idx  "$GIT_OBJECT_DIRECTORY/pack/pack-$packname.idx"
diff --git a/git-request-pull-script b/git-request-pull-script
new file mode 100755 (executable)
index 0000000..9190815
--- /dev/null
@@ -0,0 +1,35 @@
+#!/bin/sh -e
+# Copyright 2005, Ryan Anderson <ryan@michonline.com>
+#
+# This file is licensed under the GPL v2, or a later version
+# at the discretion of Linus Torvalds.
+
+usage()
+{
+       echo "$0 <commit> <url> [ <head> ]"
+       echo "  Summarizes the changes since <commit> to the standard output,"
+       echo "  and includes <url> in the message generated."
+       exit 1
+}
+
+revision=$1
+url=$2
+head=${3-HEAD}
+
+[ "$revision" ] || usage
+[ "$url" ] || usage
+
+baserev=`git-rev-parse --verify $revision^0` &&
+headrev=`git-rev-parse --verify $head^0` || exit
+
+echo "The following changes since commit $baserev:"
+git log --max-count=1 --pretty=short "$baserev" |
+git-shortlog | sed -e 's/^\(.\)/  \1/'
+
+echo "are found in the git repository at:" 
+echo
+echo "  $url"
+echo
+
+git log  $baserev..$headrev | git-shortlog ;
+git diff $baserev..$headrev | git-apply --stat --summary
diff --git a/git-reset-script b/git-reset-script
new file mode 100755 (executable)
index 0000000..49994df
--- /dev/null
@@ -0,0 +1,13 @@
+#!/bin/sh
+. git-sh-setup-script || die "Not a git archive"
+rev=$(git-rev-parse --revs-only --verify --default HEAD "$@") || exit
+rev=$(git-rev-parse --revs-only --verify $rev^0) || exit
+git-read-tree --reset "$rev" && {
+       if orig=$(git-rev-parse --verify HEAD 2>/dev/null)
+       then
+               echo "$orig" >"$GIT_DIR/ORIG_HEAD"
+       fi
+       echo "$rev" > "$GIT_DIR/HEAD"
+}
+git-update-cache --refresh
+rm -f "$GIT_DIR/MERGE_HEAD"
diff --git a/git-resolve-script b/git-resolve-script
new file mode 100755 (executable)
index 0000000..52dd83b
--- /dev/null
@@ -0,0 +1,64 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Linus Torvalds
+#
+# Resolve two trees.
+#
+. git-sh-setup-script || die "Not a git archive"
+
+head=$(git-rev-parse --verify "$1")
+merge=$(git-rev-parse --verify "$2")
+merge_msg="$3"
+
+dropheads() {
+       rm -f -- "$GIT_DIR/MERGE_HEAD" \
+               "$GIT_DIR/LAST_MERGE" || exit 1
+}
+
+#
+# The remote name is just used for the message,
+# but we do want it.
+#
+if [ -z "$head" -o -z "$merge" -o -z "$merge_msg" ]; then
+       die "git-resolve-script <head> <remote> <merge-message>"
+fi
+
+dropheads
+echo $head > "$GIT_DIR"/ORIG_HEAD
+echo $merge > "$GIT_DIR"/LAST_MERGE
+
+common=$(git-merge-base $head $merge)
+if [ -z "$common" ]; then
+       die "Unable to find common commit between" $merge $head
+fi
+
+if [ "$common" == "$merge" ]; then
+       echo "Already up-to-date. Yeeah!"
+       dropheads
+       exit 0
+fi
+if [ "$common" == "$head" ]; then
+       echo "Updating from $head to $merge."
+       git-read-tree -u -m $head $merge || exit 1
+       echo $merge > "$GIT_DIR"/HEAD
+       git-diff-tree -p $head $merge | git-apply --stat
+       dropheads
+       exit 0
+fi
+echo "Trying to merge $merge into $head"
+git-read-tree -u -m $common $head $merge || exit 1
+result_tree=$(git-write-tree  2> /dev/null)
+if [ $? -ne 0 ]; then
+       echo "Simple merge failed, trying Automatic merge"
+       git-merge-cache -o git-merge-one-file-script -a
+       if [ $? -ne 0 ]; then
+               echo $merge > "$GIT_DIR"/MERGE_HEAD
+               die "Automatic merge failed, fix up by hand"
+       fi
+       result_tree=$(git-write-tree) || exit 1
+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-diff-tree -p $head $result_commit | git-apply --stat
+dropheads
diff --git a/git-revert-script b/git-revert-script
new file mode 100755 (executable)
index 0000000..dc2dea4
--- /dev/null
@@ -0,0 +1,37 @@
+#!/bin/sh
+. git-sh-setup-script || die "Not a git archive"
+
+# We want a clean tree and clean index to be able to revert.
+status=$(git status)
+case "$status" in
+'nothing to commit') ;;
+*)
+       echo "$status"
+       die "Your working tree is dirty; cannot revert a previous patch." ;;
+esac
+
+rev=$(git-rev-parse --no-flags --verify --revs-only "$@") &&
+commit=$(git-rev-parse --verify "$rev^0") || exit
+if git-diff-tree -R -M -p $commit | git-apply --index &&
+   msg=$(git-rev-list --pretty=oneline --max-count=1 $commit)
+then
+        {
+                echo "$msg" | sed -e '
+                       s/^[^ ]* /Revert "/
+                       s/$/"/'
+                echo
+                echo "This reverts $commit commit."
+                test "$rev" = "$commit" ||
+                echo "(original 'git revert' arguments: $@)"
+        } | git commit -F -
+else
+        # Now why did it fail?
+        parents=`git-cat-file commit "$commit" 2>/dev/null |
+                sed -ne '/^$/q;/^parent /p' |
+                wc -l`
+        case $parents in
+        0) die "Cannot revert the root commit nor non commit-ish." ;;
+        1) die "The patch does not apply." ;;
+        *) die "Cannot revert a merge commit." ;;
+        esac
+fi
diff --git a/git-send-email-script b/git-send-email-script
new file mode 100755 (executable)
index 0000000..1dbdbaf
--- /dev/null
@@ -0,0 +1,292 @@
+#!/usr/bin/perl -w
+#
+# Copyright 2002,2005 Greg Kroah-Hartman <greg@kroah.com>
+# Copyright 2005 Ryan Anderson <ryan@michonline.com>
+#
+# GPL v2 (See COPYING)
+#
+# Ported to support git "mbox" format files by Ryan Anderson <ryan@michonline.com>
+#
+# Sends a collection of emails to the given email addresses, disturbingly fast.
+#
+# Supports two formats:
+# 1. mbox format files (ignoring most headers and MIME formatting - this is designed for sending patches)
+# 2. The original format support by Greg's script:
+#    first line of the message is who to CC,
+#    and second line is the subject of the message.
+#
+
+use strict;
+use warnings;
+use Term::ReadLine;
+use Mail::Sendmail qw(sendmail %mailcfg);
+use Getopt::Long;
+use Data::Dumper;
+use Email::Valid;
+
+sub unique_email_list(@);
+
+# Variables we fill in automatically, or via prompting:
+my (@to,@cc,$initial_reply_to,$initial_subject,@files,$from);
+
+# Behavior modification variables
+my ($chain_reply_to, $smtp_server) = (1, "localhost");
+
+# Example reply to:
+#$initial_reply_to = ''; #<20050203173208.GA23964@foobar.com>';
+
+my $term = new Term::ReadLine 'git-send-email';
+
+# Begin by accumulating all the variables (defined above), that we will end up
+# needing, first, from the command line:
+
+my $rc = GetOptions("from=s" => \$from,
+                    "in-reply-to=s" => \$initial_reply_to,
+                   "subject=s" => \$initial_subject,
+                   "to=s" => \@to,
+                   "chain-reply-to!" => \$chain_reply_to,
+                   "smtp-server=s" => \$smtp_server,
+        );
+
+# Now, let's fill any that aren't set in with defaults:
+
+open(GITVAR,"-|","git-var","-l")
+       or die "Failed to open pipe from git-var: $!";
+
+my ($author,$committer);
+while(<GITVAR>) {
+       chomp;
+       my ($var,$data) = split /=/,$_,2;
+       my @fields = split /\s+/, $data;
+
+       my $ident = join(" ", @fields[0...(@fields-3)]);
+
+       if ($var eq 'GIT_AUTHOR_IDENT') {
+               $author = $ident;
+       } elsif ($var eq 'GIT_COMMITTER_IDENT') {
+               $committer = $ident;
+       }
+}
+close(GITVAR);
+
+
+if (!defined $from) {
+       $from = $author || $committer;
+       do {
+               $_ = $term->readline("Who should the emails appear to be from? ",
+                       $from);
+       } while (!defined $_);
+
+       $from = $_;
+       print "Emails will be sent from: ", $from, "\n";
+}
+
+if (!@to) {
+       do {
+               $_ = $term->readline("Who should the emails be sent to? ",
+                               "");
+       } while (!defined $_);
+       my $to = $_;
+       push @to, split /,/, $to;
+}
+
+if (!defined $initial_subject) {
+       do {
+               $_ = $term->readline("What subject should the emails start with? ",
+                       $initial_subject);
+       } while (!defined $_);
+       $initial_subject = $_;
+}
+
+if (!defined $initial_reply_to) {
+       do {
+               $_= $term->readline("Message-ID to be used as In-Reply-To? ",
+                       $initial_reply_to);
+       } while (!defined $_);
+
+       $initial_reply_to = $_;
+       $initial_reply_to =~ s/(^\s+|\s+$)//g;
+}
+
+if (!defined $smtp_server) {
+       $smtp_server = "localhost";
+}
+
+# Now that all the defaults are set, process the rest of the command line
+# arguments and collect up the files that need to be processed.
+for my $f (@ARGV) {
+       if (-d $f) {
+               opendir(DH,$f)
+                       or die "Failed to opendir $f: $!";
+
+               push @files, grep { -f $_ } map { +$f . "/" . $_ }
+                               sort readdir(DH);
+
+       } elsif (-f $f) {
+               push @files, $f;
+
+       } else {
+               print STDERR "Skipping $f - not found.\n";
+       }
+}
+
+if (@files) {
+       print $_,"\n" for @files;
+} else {
+       print <<EOT;
+git-send-email-script [options] <file | directory> [... file | directory ]
+Options:
+   --from         Specify the "From:" line of the email to be sent.
+   --to           Specify the primary "To:" line of the email.
+   --subject      Specify the initial "Subject:" line.
+   --in-reply-to  Specify the first "In-Reply-To:" header line.
+   --chain-reply-to If set, the replies will all be to the previous
+                  email sent, rather than to the first email sent.
+                  Defaults to on.
+   --smtp-server  If set, specifies the outgoing SMTP server to use.
+                  Defaults to localhost.
+
+Error: Please specify a file or a directory on the command line.
+EOT
+       exit(1);
+}
+
+# Variables we set as part of the loop over files
+our ($message_id, $cc, %mail, $subject, $reply_to, $message);
+
+
+# Usually don't need to change anything below here.
+
+# we make a "fake" message id by taking the current number
+# of seconds since the beginning of Unix time and tacking on
+# a random number to the end, in case we are called quicker than
+# 1 second since the last time we were called.
+
+# We'll setup a template for the message id, using the "from" address:
+my $message_id_from = Email::Valid->address($from);
+my $message_id_template = "<%s-git-send-email-$message_id_from>";
+
+sub make_message_id
+{
+       my $date = `date "+\%s"`;
+       chomp($date);
+       my $pseudo_rand = int (rand(4200));
+       $message_id = sprintf $message_id_template, "$date$pseudo_rand";
+       #print "new message id = $message_id\n"; # Was useful for debugging
+}
+
+
+
+$cc = "";
+
+sub send_message
+{
+       my $to = join (", ", unique_email_list(@to));
+
+       %mail = (       To      =>      $to,
+                       From    =>      $from,
+                       CC      =>      $cc,
+                       Subject =>      $subject,
+                       Message =>      $message,
+                       'Reply-to'      =>      $from,
+                       'In-Reply-To'   =>      $reply_to,
+                       'Message-ID'    =>      $message_id,
+                       'X-Mailer'      =>      "git-send-email-script",
+               );
+
+       $mail{smtp} = $smtp_server;
+       $mailcfg{mime} = 0;
+
+       #print Data::Dumper->Dump([\%mail],[qw(*mail)]);
+
+       sendmail(%mail) or die $Mail::Sendmail::error;
+
+       print "OK. Log says:\n", $Mail::Sendmail::log;
+       print "\n\n"
+}
+
+
+$reply_to = $initial_reply_to;
+make_message_id();
+$subject = $initial_subject;
+
+foreach my $t (@files) {
+       my $F = $t;
+       open(F,"<",$t) or die "can't open file $t";
+
+       @cc = ();
+       my $found_mbox = 0;
+       my $header_done = 0;
+       $message = "";
+       while(<F>) {
+               if (!$header_done) {
+                       $found_mbox = 1, next if (/^From /);
+                       chomp;
+
+                       if ($found_mbox) {
+                               if (/^Subject:\s+(.*)$/) {
+                                       $subject = $1;
+
+                               } elsif (/^(Cc|From):\s+(.*)$/) {
+                                       printf("(mbox) Adding cc: %s from line '%s'\n",
+                                               $2, $_);
+                                       push @cc, $2;
+                               }
+
+                       } else {
+                               # In the traditional
+                               # "send lots of email" format,
+                               # line 1 = cc
+                               # line 2 = subject
+                               # So let's support that, too.
+                               if (@cc == 0) {
+                                       printf("(non-mbox) Adding cc: %s from line '%s'\n",
+                                               $_, $_);
+
+                                       push @cc, $_;
+
+                               } elsif (!defined $subject) {
+                                       $subject = $_;
+                               }
+                       }
+
+                       # A whitespace line will terminate the headers
+                       if (m/^\s*$/) {
+                               $header_done = 1;
+                       }
+               } else {
+                       $message .=  $_;
+                       if (/^Signed-off-by: (.*)$/i) {
+                               my $c = $1;
+                               chomp $c;
+                               push @cc, $c;
+                               printf("(sob) Adding cc: %s from line '%s'\n",
+                                       $c, $_);
+                       }
+               }
+       }
+       close F;
+
+       $cc = join(", ", unique_email_list(@cc));
+
+       send_message();
+
+       # set up for the next message
+       if ($chain_reply_to || length($reply_to) == 0) {
+               $reply_to = $message_id;
+       }
+       make_message_id();
+}
+
+
+sub unique_email_list(@) {
+       my %seen;
+       my @emails;
+
+       foreach my $entry (@_) {
+               my $clean = Email::Valid->address($entry);
+               next if $seen{$clean}++;
+               push @emails, $entry;
+       }
+       return @emails;
+}
diff --git a/git-sh-setup-script b/git-sh-setup-script
new file mode 100755 (executable)
index 0000000..5bf471f
--- /dev/null
@@ -0,0 +1,17 @@
+#!/bin/sh
+#
+# Set up GIT_DIR and GIT_OBJECT_DIRECTORY
+# and return true if everything looks ok
+#
+: ${GIT_DIR=.git}
+: ${GIT_OBJECT_DIRECTORY="$GIT_DIR/objects"}
+
+die() {
+       echo "$@" >&2
+       exit 1
+}
+
+[ -d "$GIT_DIR" ] &&
+[ -d "$GIT_DIR/refs" ] &&
+[ -d "$GIT_OBJECT_DIRECTORY" ] &&
+[ -d "$GIT_OBJECT_DIRECTORY/00" ]
diff --git a/git-shortlog b/git-shortlog
new file mode 100755 (executable)
index 0000000..107c895
--- /dev/null
@@ -0,0 +1,177 @@
+#!/usr/bin/perl -w
+
+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 (%map);
+my $pstate = 1;
+my $n_records = 0;
+my $n_output = 0;
+
+
+sub shortlog_entry($$) {
+       my ($name, $desc) = @_;
+       my $key = $name;
+
+       $desc =~ s#/pub/scm/linux/kernel/git/#/.../#g;
+       $desc =~ s#\[PATCH\] ##g;
+
+       # store description in array, in email->{desc list} map
+       if (exists $map{$key}) {
+               # grab ref
+               my $obj = $map{$key};
+
+               # add desc to array
+               push(@$obj, $desc);
+       } else {
+               # create new array, containing 1 item
+               my @arr = ($desc);
+
+               # store ref to array
+               $map{$key} = \@arr;
+       }
+}
+
+# sort comparison function
+sub by_name($$) {
+       my ($a, $b) = @_;
+
+       uc($a) cmp uc($b);
+}
+
+sub shortlog_output {
+       my ($obj, $key, $desc);
+
+       foreach $key (sort by_name keys %map) {
+               # output author
+               printf "%s:\n", $key;
+
+               # output author's 1-line summaries
+               $obj = $map{$key};
+               foreach $desc (reverse @$obj) {
+                       print "  $desc\n";
+                       $n_output++;
+               }
+
+               # blank line separating author from next author
+               print "\n";
+       }
+}
+
+sub changelog_input {
+       my ($author, $desc);
+
+       while (<>) {
+               # get author and email
+               if ($pstate == 1) {
+                       my ($email);
+
+                       next unless /^Author: (.*)<(.*)>.*$/;
+       
+                       $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 "")) {
+                               $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.*)$/;
+
+                       # skip lines that are obviously not
+                       # a 1-line cset description
+                       next if /^\s*From: /;
+
+                       chomp;
+                       $desc = $1;
+       
+                       &shortlog_entry($author, $desc);
+       
+                       $pstate = 1;
+               }
+       
+               else {
+                       die "invalid parse state $pstate";
+               }
+       }
+}
+
+sub finalize {
+       #print "\n$n_records records parsed.\n";
+
+       if ($n_records != $n_output) {
+               die "parse error: input records != output records\n";
+       }
+}
+
+&changelog_input;
+&shortlog_output;
+&finalize;
+exit(0);
+
diff --git a/git-status-script b/git-status-script
new file mode 100755 (executable)
index 0000000..e9a0383
--- /dev/null
@@ -0,0 +1,38 @@
+#!/bin/sh
+report () {
+  header="#
+# $1:
+#   ($2)
+#
+"
+  trailer=""
+  while read oldmode mode oldsha sha status name newname
+  do
+    echo -n "$header"
+    header=""
+    trailer="#
+"
+    case "$status" in
+    M ) echo "#        modified: $name";;
+    D*) echo "#        deleted:  $name";;
+    T ) echo "#        typechange: $name";;
+    C*) echo "#        copied: $name -> $newname";;
+    R*) echo "#        renamed: $name -> $newname";;
+    A*) echo "#        new file: $name";;
+    U ) echo "#        unmerged: $name";;
+    esac
+  done
+  echo -n "$trailer"
+  [ "$header" ]
+}
+
+git-update-cache --refresh >& /dev/null
+git-diff-cache -M --cached HEAD | sed 's/^://' | report "Updated but not checked in" "will commit"
+committable="$?"
+git-diff-files | sed 's/^://' | report "Changed but not updated" "use git-update-cache to mark for commit"
+if [ "$committable" == "0" ]
+then
+       echo "nothing to commit"
+       exit 1
+fi
+exit 0
diff --git a/git-tag-script b/git-tag-script
new file mode 100755 (executable)
index 0000000..d3074a8
--- /dev/null
@@ -0,0 +1,82 @@
+#!/bin/sh
+# Copyright (c) 2005 Linus Torvalds
+
+. git-sh-setup-script || die "Not a git archive"
+
+usage () {
+    echo >&2 "Usage: git-tag-script [-a | -s] [-f] [-m "tag message"] tagname"
+    exit 1
+}
+
+annotate=
+signed=
+force=
+message=
+while case "$#" in 0) break ;; esac
+do
+    case "$1" in
+    -a)
+       annotate=1
+       ;;
+    -s)
+       annotate=1
+       signed=1
+       ;;
+    -f)
+       force=1
+       ;;
+    -m)
+       annotate=1
+       shift
+       message="$1"
+       ;;
+    -*)
+        usage
+       ;;
+    *)
+       break
+       ;;
+    esac
+    shift
+done
+
+name="$1"
+[ "$name" ] || usage
+if [ -e "$GIT_DIR/refs/tags/$name" -a -z "$force" ]; then
+    die "tag '$name' already exists"
+fi
+shift
+
+object=$(git-rev-parse --verify --revs-only --default HEAD "$@") || exit 1
+type=$(git-cat-file -t $object) || exit 1
+tagger=$(git-var GIT_COMMITTER_IDENT) || exit 1
+
+trap 'rm -f .tmp-tag* .tagmsg .editmsg' 0
+
+if [ "$annotate" ]; then
+    if [ -z "$message" ]; then
+        ( echo "#"
+          echo "# Write a tag message"
+          echo "#" ) > .editmsg
+        ${VISUAL:-${EDITOR:-vi}} .editmsg || exit
+    else
+        echo "$message" > .editmsg
+    fi
+
+    grep -v '^#' < .editmsg | git-stripspace > .tagmsg
+
+    [ -s .tagmsg ] || exit
+
+    ( echo -e "object $object\ntype $type\ntag $name\ntagger $tagger\n"; cat .tagmsg ) > .tmp-tag
+    rm -f .tmp-tag.asc .tagmsg
+    if [ "$signed" ]; then
+       me=$(expr "$tagger" : '\(.*>\)') &&
+       gpg -bsa -u "$me" .tmp-tag &&
+       cat .tmp-tag.asc >>.tmp-tag ||
+       die "failed to sign the tag with GPG."
+    fi
+    object=$(git-mktag < .tmp-tag)
+fi
+
+mkdir -p "$GIT_DIR/refs/tags"
+echo $object > "$GIT_DIR/refs/tags/$name"
diff --git a/git-verify-tag-script b/git-verify-tag-script
new file mode 100755 (executable)
index 0000000..6eb650b
--- /dev/null
@@ -0,0 +1,8 @@
+#!/bin/sh
+. git-sh-setup-script || die "Not a git archive"
+
+tag=$(git-rev-parse $1) || exit 1
+
+git-cat-file tag $tag > .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-whatchanged b/git-whatchanged
new file mode 100755 (executable)
index 0000000..85a49fc
--- /dev/null
@@ -0,0 +1,7 @@
+#!/bin/sh
+rev_list_args=$(git-rev-parse --sq --default HEAD --revs-only "$@") &&
+diff_tree_args=$(git-rev-parse --sq --no-revs "$@") &&
+
+eval "git-rev-list $rev_list_args" |
+eval "git-diff-tree --stdin --pretty -r $diff_tree_args" |
+LESS="$LESS -S" ${PAGER:-less}
diff --git a/gitenv.c b/gitenv.c
new file mode 100644 (file)
index 0000000..ab9396f
--- /dev/null
+++ b/gitenv.c
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2005 Junio C Hamano
+ */
+#include "cache.h"
+
+/*
+ * This array must be sorted by its canonical name, because
+ * we do look-up by binary search.
+ */
+static struct backward_compatible_env {
+       const char *canonical;
+       const char *old;
+} bc_name[] = {
+       { "GIT_ALTERNATE_OBJECT_DIRECTORIES", "SHA1_FILE_DIRECTORIES" },
+       { "GIT_AUTHOR_DATE", "AUTHOR_DATE" },
+       { "GIT_AUTHOR_EMAIL", "AUTHOR_EMAIL" },
+       { "GIT_AUTHOR_NAME", "AUTHOR_NAME" }, 
+       { "GIT_COMMITTER_EMAIL", "COMMIT_AUTHOR_EMAIL" },
+       { "GIT_COMMITTER_NAME", "COMMIT_AUTHOR_NAME" },
+       { "GIT_OBJECT_DIRECTORY", "SHA1_FILE_DIRECTORY" },
+};
+
+static void warn_old_environment(int pos)
+{
+       int i;
+       static int warned = 0;
+       if (warned)
+               return;
+
+       warned = 1;
+       fprintf(stderr,
+               "warning: Attempting to use %s\n",
+               bc_name[pos].old);
+       fprintf(stderr,
+               "warning: GIT environment variables have been renamed.\n"
+               "warning: Please adjust your scripts and environment.\n");
+       for (i = 0; i < sizeof(bc_name) / sizeof(bc_name[0]); i++) {
+               /* warning is needed only when old name is there and
+                * new name is not.
+                */
+               if (!getenv(bc_name[i].canonical) && getenv(bc_name[i].old))
+                       fprintf(stderr, "warning: old %s => new %s\n",
+                               bc_name[i].old, bc_name[i].canonical);
+       }
+}
+
+char *gitenv_bc(const char *e)
+{
+       int first, last;
+       char *val = getenv(e);
+       if (val)
+               die("gitenv_bc called on existing %s; fix the caller.", e);
+
+       first = 0;
+       last = sizeof(bc_name) / sizeof(bc_name[0]);
+       while (last > first) {
+               int next = (last + first) >> 1;
+               int cmp = strcmp(e, bc_name[next].canonical);
+               if (!cmp) {
+                       val = getenv(bc_name[next].old);
+                       /* If the user has only old name, warn.
+                        * otherwise stay silent.
+                        */
+                       if (val)
+                               warn_old_environment(next);
+                       return val;
+               }
+               if (cmp < 0) {
+                       last = next;
+                       continue;
+               }
+               first = next+1;
+       }
+       return NULL;
+}
diff --git a/hash-object.c b/hash-object.c
new file mode 100644 (file)
index 0000000..c8c9adb
--- /dev/null
@@ -0,0 +1,45 @@
+/*
+ * GIT - The information manager from hell
+ *
+ * Copyright (C) Linus Torvalds, 2005
+ * Copyright (C) Junio C Hamano, 2005 
+ */
+#include "cache.h"
+
+static void hash_object(const char *path, const char *type, int write_object)
+{
+       int fd;
+       struct stat st;
+       unsigned char sha1[20];
+       fd = open(path, O_RDONLY);
+       if (fd < 0 ||
+           fstat(fd, &st) < 0 ||
+           index_fd(sha1, fd, &st, write_object, type))
+               die(write_object
+                   ? "Unable to add %s to database"
+                   : "Unable to hash %s", path);
+       printf("%s\n", sha1_to_hex(sha1));
+}
+
+static const char hash_object_usage[] =
+"git-hash-object [-t <type>] [-w] <file>...";
+
+int main(int argc, char **argv)
+{
+       int i;
+       const char *type = "blob";
+       int write_object = 0;
+
+       for (i = 1 ; i < argc; i++) {
+               if (!strcmp(argv[i], "-t")) {
+                       if (argc <= ++i)
+                               die(hash_object_usage);
+                       type = argv[i];
+               }
+               else if (!strcmp(argv[i], "-w"))
+                       write_object = 1;
+               else
+                       hash_object(argv[i], type, write_object);
+       }
+       return 0;
+}
diff --git a/http-pull.c b/http-pull.c
new file mode 100644 (file)
index 0000000..e70ab39
--- /dev/null
@@ -0,0 +1,371 @@
+#include "cache.h"
+#include "commit.h"
+
+#include "pull.h"
+
+#include <curl/curl.h>
+#include <curl/easy.h>
+
+#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
+
+static CURL *curl;
+
+static char *base;
+
+static SHA_CTX c;
+static z_stream stream;
+
+static int local;
+static int zret;
+
+static int curl_ssl_verify;
+
+struct buffer
+{
+        size_t posn;
+        size_t size;
+        void *buffer;
+};
+
+static size_t fwrite_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(buffer->buffer + buffer->posn, ptr, size);
+        buffer->posn += size;
+        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;
+       do {
+               ssize_t retval = write(local, ptr + posn, size - posn);
+               if (retval < 0)
+                       return posn;
+               posn += retval;
+       } while (posn < size);
+
+       stream.avail_in = size;
+       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);
+       return size;
+}
+
+void prefetch(unsigned char *sha1)
+{
+}
+
+static int got_indices = 0;
+
+static struct packed_git *packs = NULL;
+
+static int fetch_index(unsigned char *sha1)
+{
+       char *filename;
+       char *url;
+
+       FILE *indexfile;
+
+       if (has_pack_index(sha1))
+               return 0;
+
+       if (get_verbosely)
+               fprintf(stderr, "Getting index for pack %s\n",
+                       sha1_to_hex(sha1));
+       
+       url = xmalloc(strlen(base) + 64);
+       sprintf(url, "%s/objects/pack/pack-%s.idx",
+               base, sha1_to_hex(sha1));
+       
+       filename = sha1_pack_index_name(sha1);
+       indexfile = fopen(filename, "w");
+       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);
+       
+       if (curl_easy_perform(curl)) {
+               fclose(indexfile);
+               return error("Unable to get pack index %s", url);
+       }
+
+       fclose(indexfile);
+       return 0;
+}
+
+static int setup_index(unsigned char *sha1)
+{
+       struct packed_git *new_pack;
+       if (has_pack_file(sha1))
+               return 0; // don't list this as something we can get
+
+       if (fetch_index(sha1))
+               return -1;
+
+       new_pack = parse_pack_index(sha1);
+       new_pack->next = packs;
+       packs = new_pack;
+       return 0;
+}
+
+static int fetch_indices(void)
+{
+       unsigned char sha1[20];
+       char *url;
+       struct buffer buffer;
+       char *data;
+       int i = 0;
+
+       if (got_indices)
+               return 0;
+
+       data = xmalloc(4096);
+       buffer.size = 4096;
+       buffer.posn = 0;
+       buffer.buffer = data;
+
+       if (get_verbosely)
+               fprintf(stderr, "Getting pack list\n");
+       
+       url = xmalloc(strlen(base) + 21);
+       sprintf(url, "%s/objects/info/packs", 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 error("Unable to get pack index %s", url);
+       }
+
+       do {
+               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++;
+       } while (i < buffer.posn);
+
+       got_indices = 1;
+       return 0;
+}
+
+static int fetch_pack(unsigned char *sha1)
+{
+       char *url;
+       struct packed_git *target;
+       struct packed_git **lst;
+       FILE *packfile;
+       char *filename;
+
+       if (fetch_indices())
+               return -1;
+       target = find_sha1_pack(sha1, packs);
+       if (!target)
+               return error("Couldn't get %s: not separate or in any pack",
+                            sha1_to_hex(sha1));
+
+       if (get_verbosely) {
+               fprintf(stderr, "Getting pack %s\n",
+                       sha1_to_hex(target->sha1));
+               fprintf(stderr, " which contains %s\n",
+                       sha1_to_hex(sha1));
+       }
+
+       url = xmalloc(strlen(base) + 65);
+       sprintf(url, "%s/objects/pack/pack-%s.pack",
+               base, sha1_to_hex(target->sha1));
+
+       filename = sha1_pack_name(target->sha1);
+       packfile = fopen(filename, "w");
+       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);
+       
+       if (curl_easy_perform(curl)) {
+               fclose(packfile);
+               return error("Unable to get pack file %s", url);
+       }
+
+       fclose(packfile);
+
+       lst = &packs;
+       while (*lst != target)
+               lst = &((*lst)->next);
+       *lst = (*lst)->next;
+
+       install_packed_git(target);
+
+       return 0;
+}
+
+int fetch(unsigned char *sha1)
+{
+       char *hex = sha1_to_hex(sha1);
+       char *filename = sha1_file_name(sha1);
+       unsigned char real_sha1[20];
+       char *url;
+       char *posn;
+
+       local = open(filename, O_WRONLY | O_CREAT | O_EXCL, 0666);
+
+       if (local < 0)
+               return error("Couldn't open local object %s\n", filename);
+
+       memset(&stream, 0, sizeof(stream));
+
+       inflateInit(&stream);
+
+       SHA1_Init(&c);
+
+       curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1);
+       curl_easy_setopt(curl, CURLOPT_FILE, NULL);
+       curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, fwrite_sha1_file);
+
+       url = xmalloc(strlen(base) + 50);
+       strcpy(url, base);
+       posn = url + strlen(base);
+       strcpy(posn, "objects/");
+       posn += 8;
+       memcpy(posn, hex, 2);
+       posn += 2;
+       *(posn++) = '/';
+       strcpy(posn, hex + 2);
+
+       curl_easy_setopt(curl, CURLOPT_URL, url);
+
+       if (curl_easy_perform(curl)) {
+               unlink(filename);
+               if (fetch_pack(sha1))
+                       return error("Tried %s", url);
+               return 0;
+       }
+
+       close(local);
+       inflateEnd(&stream);
+       SHA1_Final(real_sha1, &c);
+       if (zret != Z_STREAM_END) {
+               unlink(filename);
+               return error("File %s (%s) corrupt\n", hex, url);
+       }
+       if (memcmp(sha1, real_sha1, 20)) {
+               unlink(filename);
+               return error("File %s has bad hash\n", hex);
+       }
+       
+       pull_say("got %s\n", hex);
+       return 0;
+}
+
+int fetch_ref(char *ref, unsigned char *sha1)
+{
+        char *url, *posn;
+        char hex[42];
+        struct buffer buffer;
+        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);
+
+        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);
+
+        hex[40] = '\0';
+        get_sha1_hex(hex, sha1);
+        return 0;
+}
+
+int main(int argc, char **argv)
+{
+       char *commit_id;
+       char *url;
+       int arg = 1;
+
+       while (arg < argc && argv[arg][0] == '-') {
+               if (argv[arg][1] == 't') {
+                       get_tree = 1;
+               } else if (argv[arg][1] == 'c') {
+                       get_history = 1;
+               } else if (argv[arg][1] == 'a') {
+                       get_all = 1;
+                       get_tree = 1;
+                       get_history = 1;
+               } else if (argv[arg][1] == 'v') {
+                       get_verbosely = 1;
+               } else if (argv[arg][1] == 'w') {
+                       write_ref = argv[arg + 1];
+                       arg++;
+               }
+               arg++;
+       }
+       if (argc < arg + 2) {
+               usage("git-http-pull [-c] [-t] [-a] [-d] [-v] [--recover] [-w ref] commit-id url");
+               return 1;
+       }
+       commit_id = argv[arg];
+       url = argv[arg + 1];
+
+       curl_global_init(CURL_GLOBAL_ALL);
+
+       curl = curl_easy_init();
+
+       curl_ssl_verify = gitenv("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);
+#endif
+
+       base = url;
+
+       if (pull(commit_id))
+               return 1;
+
+       curl_global_cleanup();
+       return 0;
+}
diff --git a/ident.c b/ident.c
new file mode 100644 (file)
index 0000000..9ef636e
--- /dev/null
+++ b/ident.c
@@ -0,0 +1,148 @@
+/*
+ * ident.c
+ *
+ * create git identifier lines of the form "name <email> date"
+ *
+ * Copyright (C) 2005 Linus Torvalds
+ */
+#include "cache.h"
+
+#include <pwd.h>
+#include <time.h>
+#include <ctype.h>
+
+static char real_email[1000];
+static char real_name[1000];
+static char real_date[50];
+
+int setup_ident(void)
+{
+       int len;
+       struct passwd *pw = getpwuid(getuid());
+
+       if (!pw)
+               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);
+
+       /* Make up a fake email address (name + '@' + hostname [+ '.' + domainname]) */
+       len = strlen(pw->pw_name);
+       if (len > sizeof(real_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);
+       }
+
+       /* And set the default date */
+       datestamp(real_date, sizeof(real_date));
+       return 0;
+}
+
+static int add_raw(char *buf, int size, int offset, const char *str)
+{
+       int len = strlen(str);
+       if (offset + len > size)
+               return size;
+       memcpy(buf + offset, str, len);
+       return offset + len;
+}
+
+static int crud(unsigned char c)
+{
+       static const char crud_array[256] = {
+               [0 ... 31] = 1,
+               [' '] = 1,
+               ['.'] = 1, [','] = 1,
+               [':'] = 1, [';'] = 1,
+               ['<'] = 1, ['>'] = 1,
+               ['"'] = 1, ['\''] = 1,
+       };
+       return crud_array[c];
+}
+
+/*
+ * Copy over a string to the destination, but avoid special
+ * characters ('\n', '<' and '>') and remove crud at the end
+ */
+static int copy(char *buf, int size, int offset, const char *src)
+{
+       int i, len;
+       unsigned char c;
+
+       /* Remove crud from the beginning.. */
+       while ((c = *src) != 0) {
+               if (!crud(c))
+                       break;
+               src++;
+       }
+
+       /* Remove crud from the end.. */
+       len = strlen(src);
+       while (len > 0) {
+               c = src[len-1];
+               if (!crud(c))
+                       break;
+               --len;
+       }
+
+       /*
+        * Copy the rest to the buffer, but avoid the special
+        * characters '\n' '<' and '>' that act as delimeters on
+        * a identification line
+        */
+       for (i = 0; i < len; i++) {
+               c = *src++;
+               switch (c) {
+               case '\n': case '<': case '>':
+                       continue;
+               }
+               if (offset >= size)
+                       return size;
+               buf[offset++] = c;
+       }
+       return offset;
+}
+
+char *get_ident(const char *name, const char *email, const char *date_str)
+{
+       static char buffer[1000];
+       char date[50];
+       int i;
+
+       if (!name)
+               name = real_name;
+       if (!email)
+               email = real_email;
+       strcpy(date, real_date);
+       if (date_str)
+               parse_date(date_str, date, sizeof(date));
+
+       i = copy(buffer, sizeof(buffer), 0, name);
+       i = add_raw(buffer, sizeof(buffer), i, " <");
+       i = copy(buffer, sizeof(buffer), i, email);
+       i = add_raw(buffer, sizeof(buffer), i, "> ");
+       i = copy(buffer, sizeof(buffer), i, date);
+       if (i >= sizeof(buffer))
+               die("Impossibly long personal identifier");
+       buffer[i] = 0;
+       return buffer;
+}
+
+char *git_author_info(void)
+{
+       return get_ident(gitenv("GIT_AUTHOR_NAME"), gitenv("GIT_AUTHOR_EMAIL"), gitenv("GIT_AUTHOR_DATE"));
+}
+
+char *git_committer_info(void)
+{
+       return get_ident(gitenv("GIT_COMMITTER_NAME"), gitenv("GIT_COMMITTER_EMAIL"), gitenv("GIT_COMMITTER_DATE"));
+}
diff --git a/index.c b/index.c
new file mode 100644 (file)
index 0000000..87fc7b0
--- /dev/null
+++ b/index.c
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2005, Junio C Hamano
+ */
+#include <signal.h>
+#include "cache.h"
+
+static struct cache_file *cache_file_list;
+
+static void remove_lock_file(void)
+{
+       while (cache_file_list) {
+               if (cache_file_list->lockfile[0])
+                       unlink(cache_file_list->lockfile);
+               cache_file_list = cache_file_list->next;
+       }
+}
+
+static void remove_lock_file_on_signal(int signo)
+{
+       remove_lock_file();
+}
+
+int hold_index_file_for_update(struct cache_file *cf, const char *path)
+{
+       sprintf(cf->lockfile, "%s.lock", path);
+       cf->next = cache_file_list;
+       cache_file_list = cf;
+       if (!cf->next) {
+               signal(SIGINT, remove_lock_file_on_signal);
+               atexit(remove_lock_file);
+       }
+       return open(cf->lockfile, O_RDWR | O_CREAT | O_EXCL, 0600);
+}
+
+int commit_index_file(struct cache_file *cf)
+{
+       char indexfile[PATH_MAX];
+       int i;
+       strcpy(indexfile, cf->lockfile);
+       i = strlen(indexfile) - 5; /* .lock */
+       indexfile[i] = 0;
+       i = rename(cf->lockfile, indexfile);
+       cf->lockfile[0] = 0;
+       return i;
+}
+
+void rollback_index_file(struct cache_file *cf)
+{
+       if (cf->lockfile[0])
+               unlink(cf->lockfile);
+       cf->lockfile[0] = 0;
+}
+
diff --git a/init-db.c b/init-db.c
new file mode 100644 (file)
index 0000000..1fb3f7f
--- /dev/null
+++ b/init-db.c
@@ -0,0 +1,253 @@
+/*
+ * GIT - The information manager from hell
+ *
+ * Copyright (C) Linus Torvalds, 2005
+ */
+#include "cache.h"
+
+#ifndef DEFAULT_GIT_TEMPLATE_DIR
+#define DEFAULT_GIT_TEMPLATE_DIR "/usr/share/git-core/templates/"
+#endif
+
+static void safe_create_dir(const char *dir)
+{
+       if (mkdir(dir, 0777) < 0) {
+               if (errno != EEXIST) {
+                       perror(dir);
+                       exit(1);
+               }
+       }
+}
+
+static int copy_file(const char *dst, const char *src, int mode)
+{
+       int fdi, fdo;
+
+       mode = (mode & 0111) ? 0777 : 0666;
+       if ((fdi = open(src, O_RDONLY)) < 0)
+               return fdi;
+       if ((fdo = open(dst, O_WRONLY | O_CREAT | O_EXCL, mode)) < 0) {
+               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);
+       }
+       close(fdo);
+       close(fdi);
+       return 0;
+}
+
+static void copy_templates_1(char *path, int baselen,
+                            char *template, int template_baselen,
+                            DIR *dir)
+{
+       struct dirent *de;
+
+       /* Note: if ".git/hooks" file exists in the repository being
+        * re-initialized, /etc/core-git/templates/hooks/update would
+        * cause git-init-db to fail here.  I think this is sane but
+        * it means that the set of templates we ship by default, along
+        * with the way the namespace under .git/ is organized, should
+        * be really carefully chosen.
+        */
+       safe_create_dir(path);
+       while ((de = readdir(dir)) != NULL) {
+               struct stat st_git, st_template;
+               int namelen;
+               int exists = 0;
+
+               if (de->d_name[0] == '.')
+                       continue;
+               namelen = strlen(de->d_name);
+               if ((PATH_MAX <= baselen + namelen) ||
+                   (PATH_MAX <= template_baselen + namelen))
+                       die("insanely long template name %s", de->d_name);
+               memcpy(path + baselen, de->d_name, namelen+1);
+               memcpy(template + template_baselen, de->d_name, namelen+1);
+               if (lstat(path, &st_git)) {
+                       if (errno != ENOENT)
+                               die("cannot stat %s", path);
+               }
+               else
+                       exists = 1;
+
+               if (lstat(template, &st_template))
+                       die("cannot stat template %s", template);
+
+               if (S_ISDIR(st_template.st_mode)) {
+                       DIR *subdir = opendir(template);
+                       int baselen_sub = baselen + namelen;
+                       int template_baselen_sub = template_baselen + namelen;
+                       if (!subdir)
+                               die("cannot opendir %s", template);
+                       path[baselen_sub++] =
+                               template[template_baselen_sub++] = '/';
+                       path[baselen_sub] =
+                               template[template_baselen_sub] = 0;
+                       copy_templates_1(path, baselen_sub,
+                                        template, template_baselen_sub,
+                                        subdir);
+                       closedir(subdir);
+               }
+               else if (exists)
+                       continue;
+               else if (S_ISLNK(st_template.st_mode)) {
+                       char lnk[256];
+                       int len;
+                       len = readlink(template, lnk, sizeof(lnk));
+                       if (len < 0)
+                               die("cannot readlink %s", template);
+                       if (sizeof(lnk) <= len)
+                               die("insanely long symlink %s", template);
+                       lnk[len] = 0;
+                       if (symlink(lnk, path))
+                               die("cannot symlink %s %s", lnk, path);
+               }
+               else if (S_ISREG(st_template.st_mode)) {
+                       if (copy_file(path, template, st_template.st_mode))
+                               die("cannot copy %s to %s", template, path);
+               }
+               else
+                       error("ignoring template %s", template);
+       }
+}
+
+static void copy_templates(const char *git_dir, int len, char *template_dir)
+{
+       char path[PATH_MAX];
+       char template_path[PATH_MAX];
+       int template_len;
+       DIR *dir;
+
+       if (!template_dir)
+               template_dir = DEFAULT_GIT_TEMPLATE_DIR;
+       strcpy(template_path, template_dir);
+       template_len = strlen(template_path);
+       if (template_path[template_len-1] != '/') {
+               template_path[template_len++] = '/';
+               template_path[template_len] = 0;
+       }
+       dir = opendir(template_path);
+       if (!dir) {
+               fprintf(stderr, "warning: templates not found %s\n",
+                       template_dir);
+               return;
+       }
+
+       memcpy(path, git_dir, len);
+       copy_templates_1(path, len,
+                        template_path, template_len,
+                        dir);
+       closedir(dir);
+}
+
+static void create_default_files(const char *git_dir,
+                                char *template_path)
+{
+       unsigned len = strlen(git_dir);
+       static char path[PATH_MAX];
+
+       if (len > sizeof(path)-50)
+               die("insane git directory %s", git_dir);
+       memcpy(path, git_dir, len);
+
+       if (len && path[len-1] != '/')
+               path[len++] = '/';
+
+       /*
+        * Create .git/refs/{heads,tags}
+        */
+       strcpy(path + len, "refs");
+       safe_create_dir(path);
+       strcpy(path + len, "refs/heads");
+       safe_create_dir(path);
+       strcpy(path + len, "refs/tags");
+       safe_create_dir(path);
+
+       /*
+        * Create the default symlink from ".git/HEAD" to the "master"
+        * branch
+        */
+       strcpy(path + len, "HEAD");
+       if (symlink("refs/heads/master", path) < 0) {
+               if (errno != EEXIST) {
+                       perror(path);
+                       exit(1);
+               }
+       }
+       copy_templates(path, len, template_path);
+}
+
+static const char init_db_usage[] =
+"git-init-db [--template=<template-directory>]";
+
+/*
+ * If you want to, you can share the DB area with any number of branches.
+ * That has advantages: you can save space by sharing all the SHA1 objects.
+ * On the other hand, it might just make lookup slower and messier. You
+ * be the judge.  The default case is to have one DB per managed directory.
+ */
+int main(int argc, char **argv)
+{
+       const char *git_dir;
+       const char *sha1_dir;
+       char *path, *template_dir = NULL;
+       int len, i;
+
+       for (i = 1; i < argc; i++, argv++) {
+               char *arg = argv[1];
+               if (arg[0] != '-')
+                       break;
+               else if (!strncmp(arg, "--template=", 11))
+                       template_dir = arg+11;
+               else
+                       die(init_db_usage);
+       }
+
+       /*
+        * Set up the default .git directory contents
+        */
+       git_dir = gitenv(GIT_DIR_ENVIRONMENT);
+       if (!git_dir) {
+               git_dir = DEFAULT_GIT_DIR_ENVIRONMENT;
+               fprintf(stderr, "defaulting to local storage area\n");
+       }
+       safe_create_dir(git_dir);
+       create_default_files(git_dir, template_dir);
+
+       /*
+        * And set up the object store.
+        */
+       sha1_dir = get_object_directory();
+       len = strlen(sha1_dir);
+       path = xmalloc(len + 40);
+       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);
+       return 0;
+}
diff --git a/local-pull.c b/local-pull.c
new file mode 100644 (file)
index 0000000..b715203
--- /dev/null
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2005 Junio C Hamano
+ */
+#include "cache.h"
+#include "commit.h"
+#include "pull.h"
+
+static int use_link = 0;
+static int use_symlink = 0;
+static int use_filecopy = 1;
+
+static char *path; /* "Remote" git repository */
+
+void prefetch(unsigned char *sha1)
+{
+}
+
+int fetch(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);
+
+       if (object_name_start < 0) {
+               strcpy(filename, path); /* e.g. git.git */
+               strcat(filename, "/objects/");
+               object_name_start = strlen(filename);
+       }
+       filename[object_name_start+0] = hex[0];
+       filename[object_name_start+1] = hex[1];
+       filename[object_name_start+2] = '/';
+       strcpy(filename + object_name_start + 3, hex + 2);
+       if (use_link) {
+               if (!link(filename, dest_filename)) {
+                       pull_say("link %s\n", hex);
+                       return 0;
+               }
+               /* If we got ENOENT there is no point continuing. */
+               if (errno == ENOENT) {
+                       fprintf(stderr, "does not exist %s\n", filename);
+                       return -1;
+               }
+       }
+       if (use_symlink && !symlink(filename, dest_filename)) {
+               pull_say("symlink %s\n", hex);
+               return 0;
+       }
+       if (use_filecopy) {
+               int ifd, ofd, status;
+               struct stat st;
+               void *map;
+               ifd = open(filename, O_RDONLY);
+               if (ifd < 0 || fstat(ifd, &st) < 0) {
+                       close(ifd);
+                       fprintf(stderr, "cannot open %s\n", filename);
+                       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", filename);
+                       return -1;
+               }
+               ofd = open(dest_filename, O_WRONLY | O_CREAT | O_EXCL, 0666);
+               status = ((ofd < 0) ||
+                         (write(ofd, map, st.st_size) != st.st_size));
+               munmap(map, st.st_size);
+               close(ofd);
+               if (status)
+                       fprintf(stderr, "cannot write %s\n", dest_filename);
+               else
+                       pull_say("copy %s\n", hex);
+               return status;
+       }
+       fprintf(stderr, "failed to copy %s with given copy methods.\n", hex);
+       return -1;
+}
+
+int fetch_ref(char *ref, unsigned char *sha1)
+{
+       static int ref_name_start = -1;
+       static char filename[PATH_MAX];
+       static char hex[41];
+       int ifd;
+
+       if (ref_name_start < 0) {
+               sprintf(filename, "%s/refs/", path);
+               ref_name_start = strlen(filename);
+       }
+       strcpy(filename + ref_name_start, ref);
+       ifd = open(filename, O_RDONLY);
+       if (ifd < 0) {
+               close(ifd);
+               fprintf(stderr, "cannot open %s\n", filename);
+               return -1;
+       }
+       if (read(ifd, hex, 40) != 40 || get_sha1_hex(hex, sha1)) {
+               close(ifd);
+               fprintf(stderr, "cannot read from %s\n", filename);
+               return -1;
+       }
+       close(ifd);
+       pull_say("ref %s\n", sha1_to_hex(sha1));
+       return 0;
+}
+
+static const char local_pull_usage[] =
+"git-local-pull [-c] [-t] [-a] [-d] [-v] [-w filename] [--recover] [-l] [-s] [-n] commit-id path";
+
+/* 
+ * By default we only use file copy.
+ * If -l is specified, a hard link is attempted.
+ * If -s is specified, then a symlink is attempted.
+ * If -n is _not_ specified, then a regular file-to-file copy is done.
+ */
+int main(int argc, char **argv)
+{
+       char *commit_id;
+       int arg = 1;
+
+       while (arg < argc && argv[arg][0] == '-') {
+               if (argv[arg][1] == 't')
+                       get_tree = 1;
+               else if (argv[arg][1] == 'c')
+                       get_history = 1;
+               else if (argv[arg][1] == 'a') {
+                       get_all = 1;
+                       get_tree = 1;
+                       get_history = 1;
+               }
+               else if (argv[arg][1] == 'l')
+                       use_link = 1;
+               else if (argv[arg][1] == 's')
+                       use_symlink = 1;
+               else if (argv[arg][1] == 'n')
+                       use_filecopy = 0;
+               else if (argv[arg][1] == 'v')
+                       get_verbosely = 1;
+               else if (argv[arg][1] == 'w')
+                       write_ref = argv[++arg];
+               else
+                       usage(local_pull_usage);
+               arg++;
+       }
+       if (argc < arg + 2)
+               usage(local_pull_usage);
+       commit_id = argv[arg];
+       path = argv[arg + 1];
+
+       if (pull(commit_id))
+               return 1;
+
+       return 0;
+}
diff --git a/ls-files.c b/ls-files.c
new file mode 100644 (file)
index 0000000..13ca02e
--- /dev/null
@@ -0,0 +1,474 @@
+/*
+ * This merges the file listing in the directory cache index
+ * with the actual working directory list, and shows different
+ * combinations of the two.
+ *
+ * Copyright (C) Linus Torvalds, 2005
+ */
+#include <dirent.h>
+#include <fnmatch.h>
+
+#include "cache.h"
+
+static int show_deleted = 0;
+static int show_cached = 0;
+static int show_others = 0;
+static int show_ignored = 0;
+static int show_stage = 0;
+static int show_unmerged = 0;
+static int show_killed = 0;
+static int line_terminator = '\n';
+
+static const char *tag_cached = "";
+static const char *tag_unmerged = "";
+static const char *tag_removed = "";
+static const char *tag_other = "";
+static const char *tag_killed = "";
+
+static char *exclude_per_dir = NULL;
+
+/* We maintain three exclude pattern lists:
+ * EXC_CMDL lists patterns explicitly given on the command line.
+ * EXC_DIRS lists patterns obtained from per-directory ignore files.
+ * EXC_FILE lists patterns from fallback ignore files.
+ */
+#define EXC_CMDL 0
+#define EXC_DIRS 1
+#define EXC_FILE 2
+static struct exclude_list {
+       int nr;
+       int alloc;
+       struct exclude {
+               const char *pattern;
+               const char *base;
+               int baselen;
+       } **excludes;
+} exclude_list[3];
+
+static void add_exclude(const char *string, const char *base,
+                       int baselen, struct exclude_list *which)
+{
+       struct exclude *x = xmalloc(sizeof (*x));
+
+       x->pattern = string;
+       x->base = base;
+       x->baselen = baselen;
+       if (which->nr == which->alloc) {
+               which->alloc = alloc_nr(which->alloc);
+               which->excludes = realloc(which->excludes,
+                                         which->alloc * sizeof(x));
+       }
+       which->excludes[which->nr++] = x;
+}
+
+static int add_excludes_from_file_1(const char *fname,
+                                   const char *base,
+                                   int baselen,
+                                   struct exclude_list *which)
+{
+       int fd, i;
+       long size;
+       char *buf, *entry;
+
+       fd = open(fname, O_RDONLY);
+       if (fd < 0)
+               goto err;
+       size = lseek(fd, 0, SEEK_END);
+       if (size < 0)
+               goto err;
+       lseek(fd, 0, SEEK_SET);
+       if (size == 0) {
+               close(fd);
+               return 0;
+       }
+       buf = xmalloc(size);
+       if (read(fd, buf, size) != size)
+               goto err;
+       close(fd);
+
+       entry = buf;
+       for (i = 0; i < size; i++) {
+               if (buf[i] == '\n') {
+                       if (entry != buf + i && entry[0] != '#') {
+                               buf[i] = 0;
+                               add_exclude(entry, base, baselen, which);
+                       }
+                       entry = buf + i + 1;
+               }
+       }
+       return 0;
+
+ err:
+       if (0 <= fd)
+               close(fd);
+       return -1;
+}
+
+static void add_excludes_from_file(const char *fname)
+{
+       if (add_excludes_from_file_1(fname, "", 0,
+                                    &exclude_list[EXC_FILE]) < 0)
+               die("cannot use %s as an exclude file", fname);
+}
+
+static int push_exclude_per_directory(const char *base, int baselen)
+{
+       char exclude_file[PATH_MAX];
+       struct exclude_list *el = &exclude_list[EXC_DIRS];
+       int current_nr = el->nr;
+
+       if (exclude_per_dir) {
+               memcpy(exclude_file, base, baselen);
+               strcpy(exclude_file + baselen, exclude_per_dir);
+               add_excludes_from_file_1(exclude_file, base, baselen, el);
+       }
+       return current_nr;
+}
+
+static void pop_exclude_per_directory(int stk)
+{
+       struct exclude_list *el = &exclude_list[EXC_DIRS];
+
+       while (stk < el->nr)
+               free(el->excludes[--el->nr]);
+}
+
+/* Scan the list and let the last match determines the fate.
+ * Return 1 for exclude, 0 for include and -1 for undecided.
+ */
+static int excluded_1(const char *pathname,
+                     int pathlen,
+                     struct exclude_list *el)
+{
+       int i;
+
+       if (el->nr) {
+               for (i = el->nr - 1; 0 <= i; i--) {
+                       struct exclude *x = el->excludes[i];
+                       const char *exclude = x->pattern;
+                       int to_exclude = 1;
+
+                       if (*exclude == '!') {
+                               to_exclude = 0;
+                               exclude++;
+                       }
+
+                       if (!strchr(exclude, '/')) {
+                               /* match basename */
+                               const char *basename = strrchr(pathname, '/');
+                               basename = (basename) ? basename+1 : pathname;
+                               if (fnmatch(exclude, basename, 0) == 0)
+                                       return to_exclude;
+                       }
+                       else {
+                               /* match with FNM_PATHNAME:
+                                * exclude has base (baselen long) inplicitly
+                                * in front of it.
+                                */
+                               int baselen = x->baselen;
+                               if (*exclude == '/')
+                                       exclude++;
+
+                               if (pathlen < baselen ||
+                                   (baselen && pathname[baselen-1] != '/') ||
+                                   strncmp(pathname, x->base, baselen))
+                                   continue;
+
+                               if (fnmatch(exclude, pathname+baselen,
+                                           FNM_PATHNAME) == 0)
+                                       return to_exclude;
+                       }
+               }
+       }
+       return -1; /* undecided */
+}
+
+static int excluded(const char *pathname)
+{
+       int pathlen = strlen(pathname);
+       int st;
+
+       for (st = EXC_CMDL; st <= EXC_FILE; st++) {
+               switch (excluded_1(pathname, pathlen, &exclude_list[st])) {
+               case 0:
+                       return 0;
+               case 1:
+                       return 1;
+               }
+       }
+       return 0;
+}
+
+struct nond_on_fs {
+       int len;
+       char name[0];
+};
+
+static struct nond_on_fs **dir;
+static int nr_dir;
+static int dir_alloc;
+
+static void add_name(const char *pathname, int len)
+{
+       struct nond_on_fs *ent;
+
+       if (cache_name_pos(pathname, len) >= 0)
+               return;
+
+       if (nr_dir == dir_alloc) {
+               dir_alloc = alloc_nr(dir_alloc);
+               dir = xrealloc(dir, dir_alloc*sizeof(ent));
+       }
+       ent = xmalloc(sizeof(*ent) + len + 1);
+       ent->len = len;
+       memcpy(ent->name, pathname, len);
+       dir[nr_dir++] = ent;
+}
+
+/*
+ * Read a directory tree. We currently ignore anything but
+ * directories, regular files and symlinks. That's because git
+ * doesn't handle them at all yet. Maybe that will change some
+ * day.
+ *
+ * Also, we ignore the name ".git" (even if it is not a directory).
+ * That likely will not change.
+ */
+static void read_directory(const char *path, const char *base, int baselen)
+{
+       DIR *dir = opendir(path);
+
+       if (dir) {
+               int exclude_stk;
+               struct dirent *de;
+               char fullname[MAXPATHLEN + 1];
+               memcpy(fullname, base, baselen);
+
+               exclude_stk = push_exclude_per_directory(base, baselen);
+
+               while ((de = readdir(dir)) != NULL) {
+                       int len;
+
+                       if ((de->d_name[0] == '.') &&
+                           (de->d_name[1] == 0 ||
+                            !strcmp(de->d_name + 1, ".") ||
+                            !strcmp(de->d_name + 1, "git")))
+                               continue;
+                       len = strlen(de->d_name);
+                       memcpy(fullname + baselen, de->d_name, len+1);
+                       if (excluded(fullname) != show_ignored)
+                               continue;
+
+                       switch (DTYPE(de)) {
+                       struct stat st;
+                       default:
+                               continue;
+                       case DT_UNKNOWN:
+                               if (lstat(fullname, &st))
+                                       continue;
+                               if (S_ISREG(st.st_mode) || S_ISLNK(st.st_mode))
+                                       break;
+                               if (!S_ISDIR(st.st_mode))
+                                       continue;
+                               /* fallthrough */
+                       case DT_DIR:
+                               memcpy(fullname + baselen + len, "/", 2);
+                               read_directory(fullname, fullname,
+                                              baselen + len + 1);
+                               continue;
+                       case DT_REG:
+                       case DT_LNK:
+                               break;
+                       }
+                       add_name(fullname, baselen + len);
+               }
+               closedir(dir);
+
+               pop_exclude_per_directory(exclude_stk);
+       }
+}
+
+static int cmp_name(const void *p1, const void *p2)
+{
+       const struct nond_on_fs *e1 = *(const struct nond_on_fs **)p1;
+       const struct nond_on_fs *e2 = *(const struct nond_on_fs **)p2;
+
+       return cache_name_compare(e1->name, e1->len,
+                                 e2->name, e2->len);
+}
+
+static void show_killed_files(void)
+{
+       int i;
+       for (i = 0; i < nr_dir; i++) {
+               struct nond_on_fs *ent = dir[i];
+               char *cp, *sp;
+               int pos, len, killed = 0;
+
+               for (cp = ent->name; cp - ent->name < ent->len; cp = sp + 1) {
+                       sp = strchr(cp, '/');
+                       if (!sp) {
+                               /* If ent->name is prefix of an entry in the
+                                * cache, it will be killed.
+                                */
+                               pos = cache_name_pos(ent->name, ent->len);
+                               if (0 <= pos)
+                                       die("bug in show-killed-files");
+                               pos = -pos - 1;
+                               while (pos < active_nr &&
+                                      ce_stage(active_cache[pos]))
+                                       pos++; /* skip unmerged */
+                               if (active_nr <= pos)
+                                       break;
+                               /* pos points at a name immediately after
+                                * ent->name in the cache.  Does it expect
+                                * ent->name to be a directory?
+                                */
+                               len = ce_namelen(active_cache[pos]);
+                               if ((ent->len < len) &&
+                                   !strncmp(active_cache[pos]->name,
+                                            ent->name, ent->len) &&
+                                   active_cache[pos]->name[ent->len] == '/')
+                                       killed = 1;
+                               break;
+                       }
+                       if (0 <= cache_name_pos(ent->name, sp - ent->name)) {
+                               /* If any of the leading directories in
+                                * ent->name is registered in the cache,
+                                * ent->name will be killed.
+                                */
+                               killed = 1;
+                               break;
+                       }
+               }
+               if (killed)
+                       printf("%s%.*s%c", tag_killed,
+                              dir[i]->len, dir[i]->name,
+                              line_terminator);
+       }
+}
+
+static void show_files(void)
+{
+       int i;
+
+       /* For cached/deleted files we don't need to even do the readdir */
+       if (show_others || show_killed) {
+               read_directory(".", "", 0);
+               qsort(dir, nr_dir, sizeof(struct nond_on_fs *), cmp_name);
+               if (show_others)
+                       for (i = 0; i < nr_dir; i++)
+                               printf("%s%.*s%c", tag_other,
+                                      dir[i]->len, dir[i]->name,
+                                      line_terminator);
+               if (show_killed)
+                       show_killed_files();
+       }
+       if (show_cached | show_stage) {
+               for (i = 0; i < active_nr; i++) {
+                       struct cache_entry *ce = active_cache[i];
+                       if (excluded(ce->name) != show_ignored)
+                               continue;
+                       if (show_unmerged && !ce_stage(ce))
+                               continue;
+                       if (!show_stage)
+                               printf("%s%s%c",
+                                      ce_stage(ce) ? tag_unmerged :
+                                      tag_cached,
+                                      ce->name, line_terminator);
+                       else
+                               printf("%s%06o %s %d\t%s%c",
+                                      ce_stage(ce) ? tag_unmerged :
+                                      tag_cached,
+                                      ntohl(ce->ce_mode),
+                                      sha1_to_hex(ce->sha1),
+                                      ce_stage(ce),
+                                      ce->name, line_terminator); 
+               }
+       }
+       if (show_deleted) {
+               for (i = 0; i < active_nr; i++) {
+                       struct cache_entry *ce = active_cache[i];
+                       struct stat st;
+                       if (excluded(ce->name) != show_ignored)
+                               continue;
+                       if (!lstat(ce->name, &st))
+                               continue;
+                       printf("%s%s%c", tag_removed, ce->name,
+                              line_terminator);
+               }
+       }
+}
+
+static const char ls_files_usage[] =
+       "git-ls-files [-z] [-t] (--[cached|deleted|others|stage|unmerged|killed])* "
+       "[ --ignored ] [--exclude=<pattern>] [--exclude-from=<file>] "
+       "[ --exclude-per-directory=<filename> ]";
+
+int main(int argc, char **argv)
+{
+       int i;
+       int exc_given = 0;
+
+       for (i = 1; i < argc; i++) {
+               char *arg = argv[i];
+
+               if (!strcmp(arg, "-z")) {
+                       line_terminator = 0;
+               } else if (!strcmp(arg, "-t")) {
+                       tag_cached = "H ";
+                       tag_unmerged = "M ";
+                       tag_removed = "R ";
+                       tag_other = "? ";
+                       tag_killed = "K ";
+               } else if (!strcmp(arg, "-c") || !strcmp(arg, "--cached")) {
+                       show_cached = 1;
+               } else if (!strcmp(arg, "-d") || !strcmp(arg, "--deleted")) {
+                       show_deleted = 1;
+               } else if (!strcmp(arg, "-o") || !strcmp(arg, "--others")) {
+                       show_others = 1;
+               } else if (!strcmp(arg, "-i") || !strcmp(arg, "--ignored")) {
+                       show_ignored = 1;
+               } else if (!strcmp(arg, "-s") || !strcmp(arg, "--stage")) {
+                       show_stage = 1;
+               } else if (!strcmp(arg, "-k") || !strcmp(arg, "--killed")) {
+                       show_killed = 1;
+               } else if (!strcmp(arg, "-u") || !strcmp(arg, "--unmerged")) {
+                       /* There's no point in showing unmerged unless
+                        * you also show the stage information.
+                        */
+                       show_stage = 1;
+                       show_unmerged = 1;
+               } else if (!strcmp(arg, "-x") && i+1 < argc) {
+                       exc_given = 1;
+                       add_exclude(argv[++i], "", 0, &exclude_list[EXC_CMDL]);
+               } else if (!strncmp(arg, "--exclude=", 10)) {
+                       exc_given = 1;
+                       add_exclude(arg+10, "", 0, &exclude_list[EXC_CMDL]);
+               } else if (!strcmp(arg, "-X") && i+1 < argc) {
+                       exc_given = 1;
+                       add_excludes_from_file(argv[++i]);
+               } else if (!strncmp(arg, "--exclude-from=", 15)) {
+                       exc_given = 1;
+                       add_excludes_from_file(arg+15);
+               } else if (!strncmp(arg, "--exclude-per-directory=", 24)) {
+                       exc_given = 1;
+                       exclude_per_dir = arg + 24;
+               } else
+                       usage(ls_files_usage);
+       }
+
+       if (show_ignored && !exc_given) {
+               fprintf(stderr, "%s: --ignored needs some exclude pattern\n",
+                       argv[0]);
+               exit(1);
+       }
+
+       /* With no flags, we default to showing the cached files */
+       if (!(show_stage | show_deleted | show_others | show_unmerged | show_killed))
+               show_cached = 1;
+
+       read_cache();
+       show_files();
+       return 0;
+}
diff --git a/ls-tree.c b/ls-tree.c
new file mode 100644 (file)
index 0000000..e198a20
--- /dev/null
+++ b/ls-tree.c
@@ -0,0 +1,247 @@
+/*
+ * GIT - The information manager from hell
+ *
+ * Copyright (C) Linus Torvalds, 2005
+ */
+#include "cache.h"
+#include "blob.h"
+#include "tree.h"
+
+static int line_termination = '\n';
+#define LS_RECURSIVE 1
+#define LS_TREE_ONLY 2
+static int ls_options = 0;
+
+static struct tree_entry_list root_entry;
+
+static void prepare_root(unsigned char *sha1)
+{
+       unsigned char rsha[20];
+       unsigned long size;
+       void *buf;
+       struct tree *root_tree;
+
+       buf = read_object_with_reference(sha1, "tree", &size, rsha);
+       free(buf);
+       if (!buf)
+               die("Could not read %s", sha1_to_hex(sha1));
+
+       root_tree = lookup_tree(rsha);
+       if (!root_tree)
+               die("Could not read %s", sha1_to_hex(sha1));
+
+       /* Prepare a fake entry */
+       root_entry.directory = 1;
+       root_entry.executable = root_entry.symlink = 0;
+       root_entry.mode = S_IFDIR;
+       root_entry.name = "";
+       root_entry.item.tree = root_tree;
+       root_entry.parent = NULL;
+}
+
+static int prepare_children(struct tree_entry_list *elem)
+{
+       if (!elem->directory)
+               return -1;
+       if (!elem->item.tree->object.parsed) {
+               struct tree_entry_list *e;
+               if (parse_tree(elem->item.tree))
+                       return -1;
+               /* Set up the parent link */
+               for (e = elem->item.tree->entries; e; e = e->next)
+                       e->parent = elem;
+       }
+       return 0;
+}
+
+static struct tree_entry_list *find_entry(const char *path)
+{
+       const char *next, *slash;
+       int len;
+       struct tree_entry_list *elem = &root_entry;
+
+       /* Find tree element, descending from root, that
+        * corresponds to the named path, lazily expanding
+        * the tree if possible.
+        */
+
+       while (path) {
+               /* The fact we still have path means that the caller
+                * wants us to make sure that elem at this point is a
+                * directory, and possibly descend into it.  Even what
+                * is left is just trailing slashes, we loop back to
+                * here, and this call to prepare_children() will
+                * catch elem not being a tree.  Nice.
+                */
+               if (prepare_children(elem))
+                       return NULL;
+
+               slash = strchr(path, '/');
+               if (!slash) {
+                       len = strlen(path);
+                       next = NULL;
+               }
+               else {
+                       next = slash + 1;
+                       len = slash - path;
+               }
+               if (len) {
+                       /* (len == 0) if the original path was "drivers/char/"
+                        * and we have run already two rounds, having elem
+                        * pointing at the drivers/char directory.
+                        */
+                       elem = elem->item.tree->entries;
+                       while (elem) {
+                               if ((strlen(elem->name) == len) &&
+                                   !strncmp(elem->name, path, len)) {
+                                       /* found */
+                                       break;
+                               }
+                               elem = elem->next;
+                       }
+                       if (!elem)
+                               return NULL;
+               }
+               path = next;
+       }
+
+       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");
+}
+
+static const char *entry_hex(struct tree_entry_list *e)
+{
+       return sha1_to_hex(e->directory
+                          ? e->item.tree->object.sha1
+                          : e->item.blob->object.sha1);
+}
+
+/* forward declaration for mutually recursive routines */
+static int show_entry(struct tree_entry_list *, int);
+
+static int show_children(struct tree_entry_list *e, int level)
+{
+       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);
+               e = e->next;
+       }
+       return 0;
+}
+
+static int show_entry(struct tree_entry_list *e, int level)
+{
+       int err = 0; 
+
+       if (e != &root_entry) {
+               printf("%06o %s %s      ", e->mode, entry_type(e),
+                      entry_hex(e));
+               show_entry_name(e);
+               putchar(line_termination);
+       }
+
+       if (e->directory) {
+               /* If this is a directory, we have the following cases:
+                * (1) This is the top-level request (explicit path from the
+                *     command line, or "root" if there is no command line).
+                *  a. Without any flag.  We show direct children.  We do not 
+                *     recurse into them.
+                *  b. With -r.  We do recurse into children.
+                *  c. With -d.  We do not recurse into children.
+                * (2) We came here because our caller is either (1-a) or
+                *     (1-b).
+                *  a. Without any flag.  We do not show our children (which
+                *     are grandchildren for the original request).
+                *  b. With -r.  We continue to recurse into our children.
+                *  c. With -d.  We should not have come here to begin with.
+                */
+               if (level == 0 && !(ls_options & LS_TREE_ONLY))
+                       /* case (1)-a and (1)-b */
+                       err = err | show_children(e, level+1);
+               else if (level && ls_options & LS_RECURSIVE)
+                       /* case (2)-b */
+                       err = err | show_children(e, level+1);
+       }
+       return err;
+}
+
+static int list_one(const char *path)
+{
+       int err = 0;
+       struct tree_entry_list *e = find_entry(path);
+       if (!e) {
+               /* traditionally ls-tree does not complain about
+                * missing path.  We may change this later to match
+                * what "/bin/ls -a" does, which is to complain.
+                */
+               return err;
+       }
+       err = err | show_entry(e, 0);
+       return err;
+}
+
+static int list(char **path)
+{
+       int i;
+       int err = 0;
+       for (i = 0; path[i]; i++)
+               err = err | list_one(path[i]);
+       return err;
+}
+
+static const char ls_tree_usage[] =
+       "git-ls-tree [-d] [-r] [-z] <tree-ish> [path...]";
+
+int main(int argc, char **argv)
+{
+       static char *path0[] = { "", NULL };
+       char **path;
+       unsigned char sha1[20];
+
+       while (1 < argc && argv[1][0] == '-') {
+               switch (argv[1][1]) {
+               case 'z':
+                       line_termination = 0;
+                       break;
+               case 'r':
+                       ls_options |= LS_RECURSIVE;
+                       break;
+               case 'd':
+                       ls_options |= LS_TREE_ONLY;
+                       break;
+               default:
+                       usage(ls_tree_usage);
+               }
+               argc--; argv++;
+       }
+
+       if (argc < 2)
+               usage(ls_tree_usage);
+       if (get_sha1(argv[1], sha1) < 0)
+               usage(ls_tree_usage);
+
+       path = (argc == 2) ? path0 : (argv + 2);
+       prepare_root(sha1);
+       if (list(path) < 0)
+               die("list failed");
+       return 0;
+}
diff --git a/merge-base.c b/merge-base.c
new file mode 100644 (file)
index 0000000..5919566
--- /dev/null
@@ -0,0 +1,70 @@
+#include <stdlib.h>
+#include "cache.h"
+#include "commit.h"
+
+static struct commit *common_ancestor(struct commit *rev1, struct commit *rev2)
+{
+       struct commit_list *list = NULL;
+       struct commit_list *result = NULL;
+
+       if (rev1 == rev2)
+               return rev1;
+
+       parse_commit(rev1);
+       parse_commit(rev2);
+
+       rev1->object.flags |= 1;
+       rev2->object.flags |= 2;
+       insert_by_date(rev1, &list);
+       insert_by_date(rev2, &list);
+
+       while (list) {
+               struct commit *commit = list->item;
+               struct commit_list *tmp = list, *parents;
+               int flags = commit->object.flags & 3;
+
+               list = list->next;
+               free(tmp);
+               switch (flags) {
+               case 3:
+                       insert_by_date(commit, &result);
+                       continue;
+               case 0:
+                       die("git-merge-base: commit without either parent?");
+               }
+               parents = commit->parents;
+               while (parents) {
+                       struct commit *p = parents->item;
+                       parents = parents->next;
+                       if ((p->object.flags & flags) == flags)
+                               continue;
+                       parse_commit(p);
+                       p->object.flags |= flags;
+                       insert_by_date(p, &list);
+               }
+       }
+       if (!result)
+               return NULL;
+       return result->item;
+}
+
+int main(int argc, char **argv)
+{
+       struct commit *rev1, *rev2, *ret;
+       unsigned char rev1key[20], rev2key[20];
+
+       if (argc != 3 ||
+           get_sha1(argv[1], rev1key) ||
+           get_sha1(argv[2], rev2key)) {
+               usage("git-merge-base <commit-id> <commit-id>");
+       }
+       rev1 = lookup_commit_reference(rev1key);
+       rev2 = lookup_commit_reference(rev2key);
+       if (!rev1 || !rev2)
+               return 1;
+       ret = common_ancestor(rev1, rev2);
+       if (!ret)
+               return 1;
+       printf("%s\n", sha1_to_hex(ret->object.sha1));
+       return 0;
+}
diff --git a/merge-cache.c b/merge-cache.c
new file mode 100644 (file)
index 0000000..744681b
--- /dev/null
@@ -0,0 +1,135 @@
+#include <sys/types.h>
+#include <sys/wait.h>
+
+#include "cache.h"
+
+static const char *pgm = NULL;
+static const char *arguments[8];
+static int one_shot, quiet;
+static int err;
+
+static void run_program(void)
+{
+       int pid = fork(), status;
+
+       if (pid < 0)
+               die("unable to fork");
+       if (!pid) {
+               execlp(pgm, arguments[0],
+                           arguments[1],
+                           arguments[2],
+                           arguments[3],
+                           arguments[4],
+                           arguments[5],
+                           arguments[6],
+                           arguments[7],
+                           NULL);
+               die("unable to execute '%s'", pgm);
+       }
+       if (waitpid(pid, &status, 0) < 0 || !WIFEXITED(status) || WEXITSTATUS(status)) {
+               if (one_shot) {
+                       err++;
+               } else {
+                       if (!quiet)
+                               die("merge program failed");
+                       exit(1);
+               }
+       }
+}
+
+static int merge_entry(int pos, const char *path)
+{
+       int found;
+       
+       if (pos >= active_nr)
+               die("git-merge-cache: %s not in the cache", path);
+       arguments[0] = pgm;
+       arguments[1] = "";
+       arguments[2] = "";
+       arguments[3] = "";
+       arguments[4] = path;
+       arguments[5] = "";
+       arguments[6] = "";
+       arguments[7] = "";
+       found = 0;
+       do {
+               static char hexbuf[4][60];
+               static char ownbuf[4][60];
+               struct cache_entry *ce = active_cache[pos];
+               int stage = ce_stage(ce);
+
+               if (strcmp(ce->name, path))
+                       break;
+               found++;
+               strcpy(hexbuf[stage], sha1_to_hex(ce->sha1));
+               sprintf(ownbuf[stage], "%o", ntohl(ce->ce_mode) & (~S_IFMT));
+               arguments[stage] = hexbuf[stage];
+               arguments[stage + 4] = ownbuf[stage];
+       } while (++pos < active_nr);
+       if (!found)
+               die("git-merge-cache: %s not in the cache", path);
+       run_program();
+       return found;
+}
+
+static void merge_file(const char *path)
+{
+       int pos = cache_name_pos(path, strlen(path));
+
+       /*
+        * If it already exists in the cache as stage0, it's
+        * already merged and there is nothing to do.
+        */
+       if (pos < 0)
+               merge_entry(-pos-1, path);
+}
+
+static void merge_all(void)
+{
+       int i;
+       for (i = 0; i < active_nr; i++) {
+               struct cache_entry *ce = active_cache[i];
+               if (!ce_stage(ce))
+                       continue;
+               i += merge_entry(i, ce->name)-1;
+       }
+}
+
+int main(int argc, char **argv)
+{
+       int i, force_file = 0;
+
+       if (argc < 3)
+               usage("git-merge-cache [-o] [-q] <merge-program> (-a | <filename>*)");
+
+       read_cache();
+
+       i = 1;
+       if (!strcmp(argv[i], "-o")) {
+               one_shot = 1;
+               i++;
+       }
+       if (!strcmp(argv[i], "-q")) {
+               quiet = 1;
+               i++;
+       }
+       pgm = argv[i++];
+       for (; i < argc; i++) {
+               char *arg = argv[i];
+               if (!force_file && *arg == '-') {
+                       if (!strcmp(arg, "--")) {
+                               force_file = 1;
+                               continue;
+                       }
+                       if (!strcmp(arg, "-a")) {
+                               merge_all();
+                               continue;
+                       }
+                       die("git-merge-cache: unknown option %s", arg);
+               }
+               merge_file(arg);
+       }
+       if (err && !quiet)
+               die("merge program failed");
+       return err;
+}
diff --git a/mktag.c b/mktag.c
new file mode 100644 (file)
index 0000000..585677e
--- /dev/null
+++ b/mktag.c
@@ -0,0 +1,136 @@
+#include "cache.h"
+
+/*
+ * A signature file has a very simple fixed format: three lines
+ * of "object <sha1>" + "type <typename>" + "tag <tagname>",
+ * followed by some free-form signature that git itself doesn't
+ * care about, but that can be verified with gpg or similar.
+ *
+ * The first three lines are guaranteed to be at least 63 bytes:
+ * "object <sha1>\n" is 48 bytes, "type tag\n" at 9 bytes is the
+ * shortest possible type-line, and "tag .\n" at 6 bytes is the
+ * shortest single-character-tag line. 
+ *
+ * We also artificially limit the size of the full object to 8kB.
+ * Just because I'm a lazy bastard, and if you can't fit a signature
+ * in that size, you're doing something wrong.
+ */
+
+// Some random size
+#define MAXSIZE (8192)
+
+/*
+ * We refuse to tag something we can't verify. Just because.
+ */
+static int verify_object(unsigned char *sha1, const char *expected_type)
+{
+       int ret = -1;
+       char type[100];
+       unsigned long size;
+       void *buffer = read_sha1_file(sha1, type, &size);
+
+       if (buffer) {
+               if (!strcmp(type, expected_type))
+                       ret = check_sha1_signature(sha1, buffer, size, type);
+               free(buffer);
+       }
+       return ret;
+}
+
+static int verify_tag(char *buffer, unsigned long size)
+{
+       int typelen;
+       char type[20];
+       unsigned char sha1[20];
+       const char *object, *type_line, *tag_line, *tagger_line;
+
+       if (size < 64 || size > MAXSIZE-1)
+               return -1;
+       buffer[size] = 0;
+
+       /* Verify object line */
+       object = buffer;
+       if (memcmp(object, "object ", 7))
+               return -1;
+       if (get_sha1_hex(object + 7, sha1))
+               return -1;
+
+       /* Verify type line */
+       type_line = object + 48;
+       if (memcmp(type_line - 1, "\ntype ", 6))
+               return -1;
+
+       /* Verify tag-line */
+       tag_line = strchr(type_line, '\n');
+       if (!tag_line)
+               return -1;
+       tag_line++;
+       if (memcmp(tag_line, "tag ", 4) || tag_line[4] == '\n')
+               return -1;
+
+       /* Get the actual type */
+       typelen = tag_line - type_line - strlen("type \n");
+       if (typelen >= sizeof(type))
+               return -1;
+       memcpy(type, type_line+5, typelen);
+       type[typelen] = 0;
+
+       /* Verify that the object matches */
+       if (get_sha1_hex(object + 7, sha1))
+               return -1;
+       if (verify_object(sha1, type))
+               return -1;
+
+       /* Verify the tag-name: we don't allow control characters or spaces in it */
+       tag_line += 4;
+       for (;;) {
+               unsigned char c = *tag_line++;
+               if (c == '\n')
+                       break;
+               if (c > ' ')
+                       continue;
+               return -1;
+       }
+
+       /* Verify the tagger line */
+       tagger_line = tag_line;
+
+       if (memcmp(tagger_line, "tagger", 6) || (tagger_line[6] == '\n'))
+               return -1;
+
+       /* The actual stuff afterwards we don't care about.. */
+       return 0;
+}
+
+int main(int argc, char **argv)
+{
+       unsigned long size;
+       char buffer[MAXSIZE];
+       unsigned char result_sha1[20];
+
+       if (argc != 1)
+               usage("cat <signaturefile> | git-mktag");
+
+       // Read the signature
+       size = 0;
+       for (;;) {
+               int ret = read(0, buffer + size, MAXSIZE - size);
+               if (!ret)
+                       break;
+               if (ret < 0) {
+                       if (errno == EAGAIN)
+                               continue;
+                       break;
+               }
+               size += ret;
+       }
+
+       // Verify it for some basic sanity: it needs to start with "object <sha1>\ntype\ntagger "
+       if (verify_tag(buffer, size) < 0)
+               die("invalid tag signature file");
+
+       if (write_sha1_file(buffer, size, "tag", result_sha1) < 0)
+               die("unable to write tag file");
+       printf("%s\n", sha1_to_hex(result_sha1));
+       return 0;
+}
diff --git a/mozilla-sha1/sha1.c b/mozilla-sha1/sha1.c
new file mode 100644 (file)
index 0000000..7f6fc05
--- /dev/null
@@ -0,0 +1,152 @@
+/* 
+ * The contents of this file are subject to the Mozilla Public
+ * License Version 1.1 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.mozilla.org/MPL/
+ * 
+ * Software distributed under the License is distributed on an "AS
+ * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ * implied. See the License for the specific language governing
+ * rights and limitations under the License.
+ * 
+ * The Original Code is SHA 180-1 Reference Implementation (Compact version)
+ * 
+ * The Initial Developer of the Original Code is Paul Kocher of
+ * Cryptography Research.  Portions created by Paul Kocher are 
+ * Copyright (C) 1995-9 by Cryptography Research, Inc.  All
+ * Rights Reserved.
+ * 
+ * Contributor(s):
+ *
+ *     Paul Kocher
+ * 
+ * Alternatively, the contents of this file may be used under the
+ * terms of the GNU General Public License Version 2 or later (the
+ * "GPL"), in which case the provisions of the GPL are applicable 
+ * instead of those above.  If you wish to allow use of your 
+ * version of this file only under the terms of the GPL and not to
+ * allow others to use your version of this file under the MPL,
+ * indicate your decision by deleting the provisions above and
+ * replace them with the notice and other provisions required by
+ * the GPL.  If you do not delete the provisions above, a recipient
+ * may use your version of this file under either the MPL or the
+ * GPL.
+ */
+
+#include "sha1.h"
+
+static void shaHashBlock(SHA_CTX *ctx);
+
+void SHA1_Init(SHA_CTX *ctx) {
+  int i;
+
+  ctx->lenW = 0;
+  ctx->sizeHi = ctx->sizeLo = 0;
+
+  /* Initialize H with the magic constants (see FIPS180 for constants)
+   */
+  ctx->H[0] = 0x67452301;
+  ctx->H[1] = 0xefcdab89;
+  ctx->H[2] = 0x98badcfe;
+  ctx->H[3] = 0x10325476;
+  ctx->H[4] = 0xc3d2e1f0;
+
+  for (i = 0; i < 80; i++)
+    ctx->W[i] = 0;
+}
+
+
+void SHA1_Update(SHA_CTX *ctx, void *_dataIn, int len) {
+  unsigned char *dataIn = _dataIn;
+  int i;
+
+  /* Read the data into W and process blocks as they get full
+   */
+  for (i = 0; i < len; i++) {
+    ctx->W[ctx->lenW / 4] <<= 8;
+    ctx->W[ctx->lenW / 4] |= (unsigned int)dataIn[i];
+    if ((++ctx->lenW) % 64 == 0) {
+      shaHashBlock(ctx);
+      ctx->lenW = 0;
+    }
+    ctx->sizeLo += 8;
+    ctx->sizeHi += (ctx->sizeLo < 8);
+  }
+}
+
+
+void SHA1_Final(unsigned char hashout[20], SHA_CTX *ctx) {
+  unsigned char pad0x80 = 0x80;
+  unsigned char pad0x00 = 0x00;
+  unsigned char padlen[8];
+  int i;
+
+  /* Pad with a binary 1 (e.g. 0x80), then zeroes, then length
+   */
+  padlen[0] = (unsigned char)((ctx->sizeHi >> 24) & 255);
+  padlen[1] = (unsigned char)((ctx->sizeHi >> 16) & 255);
+  padlen[2] = (unsigned char)((ctx->sizeHi >> 8) & 255);
+  padlen[3] = (unsigned char)((ctx->sizeHi >> 0) & 255);
+  padlen[4] = (unsigned char)((ctx->sizeLo >> 24) & 255);
+  padlen[5] = (unsigned char)((ctx->sizeLo >> 16) & 255);
+  padlen[6] = (unsigned char)((ctx->sizeLo >> 8) & 255);
+  padlen[7] = (unsigned char)((ctx->sizeLo >> 0) & 255);
+  SHA1_Update(ctx, &pad0x80, 1);
+  while (ctx->lenW != 56)
+    SHA1_Update(ctx, &pad0x00, 1);
+  SHA1_Update(ctx, padlen, 8);
+
+  /* Output hash
+   */
+  for (i = 0; i < 20; i++) {
+    hashout[i] = (unsigned char)(ctx->H[i / 4] >> 24);
+    ctx->H[i / 4] <<= 8;
+  }
+
+  /*
+   *  Re-initialize the context (also zeroizes contents)
+   */
+  SHA1_Init(ctx);
+}
+
+
+#define SHA_ROT(X,n) (((X) << (n)) | ((X) >> (32-(n))))
+
+static void shaHashBlock(SHA_CTX *ctx) {
+  int t;
+  unsigned int A,B,C,D,E,TEMP;
+
+  for (t = 16; t <= 79; t++)
+    ctx->W[t] =
+      SHA_ROT(ctx->W[t-3] ^ ctx->W[t-8] ^ ctx->W[t-14] ^ ctx->W[t-16], 1);
+
+  A = ctx->H[0];
+  B = ctx->H[1];
+  C = ctx->H[2];
+  D = ctx->H[3];
+  E = ctx->H[4];
+
+  for (t = 0; t <= 19; t++) {
+    TEMP = SHA_ROT(A,5) + (((C^D)&B)^D)     + E + ctx->W[t] + 0x5a827999;
+    E = D; D = C; C = SHA_ROT(B, 30); B = A; A = TEMP;
+  }
+  for (t = 20; t <= 39; t++) {
+    TEMP = SHA_ROT(A,5) + (B^C^D)           + E + ctx->W[t] + 0x6ed9eba1;
+    E = D; D = C; C = SHA_ROT(B, 30); B = A; A = TEMP;
+  }
+  for (t = 40; t <= 59; t++) {
+    TEMP = SHA_ROT(A,5) + ((B&C)|(D&(B|C))) + E + ctx->W[t] + 0x8f1bbcdc;
+    E = D; D = C; C = SHA_ROT(B, 30); B = A; A = TEMP;
+  }
+  for (t = 60; t <= 79; t++) {
+    TEMP = SHA_ROT(A,5) + (B^C^D)           + E + ctx->W[t] + 0xca62c1d6;
+    E = D; D = C; C = SHA_ROT(B, 30); B = A; A = TEMP;
+  }
+
+  ctx->H[0] += A;
+  ctx->H[1] += B;
+  ctx->H[2] += C;
+  ctx->H[3] += D;
+  ctx->H[4] += E;
+}
+
diff --git a/mozilla-sha1/sha1.h b/mozilla-sha1/sha1.h
new file mode 100644 (file)
index 0000000..f5decbf
--- /dev/null
@@ -0,0 +1,45 @@
+/* 
+ * The contents of this file are subject to the Mozilla Public
+ * License Version 1.1 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.mozilla.org/MPL/
+ * 
+ * Software distributed under the License is distributed on an "AS
+ * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ * implied. See the License for the specific language governing
+ * rights and limitations under the License.
+ * 
+ * The Original Code is SHA 180-1 Header File
+ * 
+ * The Initial Developer of the Original Code is Paul Kocher of
+ * Cryptography Research.  Portions created by Paul Kocher are 
+ * Copyright (C) 1995-9 by Cryptography Research, Inc.  All
+ * Rights Reserved.
+ * 
+ * Contributor(s):
+ *
+ *     Paul Kocher
+ * 
+ * Alternatively, the contents of this file may be used under the
+ * terms of the GNU General Public License Version 2 or later (the
+ * "GPL"), in which case the provisions of the GPL are applicable 
+ * instead of those above.  If you wish to allow use of your 
+ * version of this file only under the terms of the GPL and not to
+ * allow others to use your version of this file under the MPL,
+ * indicate your decision by deleting the provisions above and
+ * replace them with the notice and other provisions required by
+ * the GPL.  If you do not delete the provisions above, a recipient
+ * may use your version of this file under either the MPL or the
+ * GPL.
+ */
+
+typedef struct {
+  unsigned int H[5];
+  unsigned int W[80];
+  int lenW;
+  unsigned int sizeHi,sizeLo;
+} SHA_CTX;
+
+void SHA1_Init(SHA_CTX *ctx);
+void SHA1_Update(SHA_CTX *ctx, void *dataIn, int len);
+void SHA1_Final(unsigned char hashout[20], SHA_CTX *ctx);
diff --git a/object.c b/object.c
new file mode 100644 (file)
index 0000000..72f6aa5
--- /dev/null
+++ b/object.c
@@ -0,0 +1,205 @@
+#include "object.h"
+#include "blob.h"
+#include "tree.h"
+#include "commit.h"
+#include "cache.h"
+#include "tag.h"
+
+struct object **objs;
+int nr_objs;
+static int obj_allocs;
+
+static int find_object(const unsigned char *sha1)
+{
+       int first = 0, last = nr_objs;
+
+        while (first < last) {
+                int next = (first + last) / 2;
+                struct object *obj = objs[next];
+                int cmp;
+
+                cmp = memcmp(sha1, obj->sha1, 20);
+                if (!cmp)
+                        return next;
+                if (cmp < 0) {
+                        last = next;
+                        continue;
+                }
+                first = next+1;
+        }
+        return -first-1;
+}
+
+struct object *lookup_object(const unsigned char *sha1)
+{
+       int pos = find_object(sha1);
+       if (pos >= 0)
+               return objs[pos];
+       return NULL;
+}
+
+void created_object(const unsigned char *sha1, struct object *obj)
+{
+       int pos = find_object(sha1);
+
+       obj->parsed = 0;
+       memcpy(obj->sha1, sha1, 20);
+       obj->type = NULL;
+       obj->refs = NULL;
+       obj->used = 0;
+
+       if (pos >= 0)
+               die("Inserting %s twice\n", sha1_to_hex(sha1));
+       pos = -pos-1;
+
+       if (obj_allocs == nr_objs) {
+               obj_allocs = alloc_nr(obj_allocs);
+               objs = xrealloc(objs, obj_allocs * sizeof(struct object *));
+       }
+
+       /* Insert it into the right place */
+       memmove(objs + pos + 1, objs + pos, (nr_objs - pos) * 
+               sizeof(struct object *));
+
+       objs[pos] = obj;
+       nr_objs++;
+}
+
+void add_ref(struct object *refer, struct object *target)
+{
+       struct object_list **pp = &refer->refs;
+       struct object_list *p;
+       
+       while ((p = *pp) != NULL) {
+               if (p->item == target)
+                       return;
+               pp = &p->next;
+       }
+
+       target->used = 1;
+       p = xmalloc(sizeof(*p));
+       p->item = target;
+       p->next = NULL;
+       *pp = p;
+}
+
+void mark_reachable(struct object *obj, unsigned int mask)
+{
+       struct object_list *p = obj->refs;
+
+       /* If we've been here already, don't bother */
+       if (obj->flags & mask)
+               return;
+       obj->flags |= mask;
+       while (p) {
+               mark_reachable(p->item, mask);
+               p = p->next;
+       }
+}
+
+struct object *lookup_object_type(const unsigned char *sha1, const char *type)
+{
+       if (!type) {
+               return lookup_unknown_object(sha1);
+       } else if (!strcmp(type, blob_type)) {
+               return &lookup_blob(sha1)->object;
+       } else if (!strcmp(type, tree_type)) {
+               return &lookup_tree(sha1)->object;
+       } else if (!strcmp(type, commit_type)) {
+               return &lookup_commit(sha1)->object;
+       } else if (!strcmp(type, tag_type)) {
+               return &lookup_tag(sha1)->object;
+       } else {
+               error("Unknown type %s", type);
+               return NULL;
+       }
+}
+
+union any_object {
+       struct object object;
+       struct commit commit;
+       struct tree tree;
+       struct blob blob;
+       struct tag tag;
+};
+
+struct object *lookup_unknown_object(const unsigned char *sha1)
+{
+       struct object *obj = lookup_object(sha1);
+       if (!obj) {
+               union any_object *ret = xmalloc(sizeof(*ret));
+               memset(ret, 0, sizeof(*ret));
+               created_object(sha1, &ret->object);
+               ret->object.type = NULL;
+               return &ret->object;
+       }
+       return obj;
+}
+
+struct object *parse_object(const unsigned char *sha1)
+{
+       unsigned long size;
+       char type[20];
+       void *buffer = read_sha1_file(sha1, type, &size);
+       if (buffer) {
+               struct object *obj;
+               if (check_sha1_signature(sha1, buffer, size, type) < 0)
+                       printf("sha1 mismatch %s\n", sha1_to_hex(sha1));
+               if (!strcmp(type, "blob")) {
+                       struct blob *blob = lookup_blob(sha1);
+                       parse_blob_buffer(blob, buffer, size);
+                       obj = &blob->object;
+               } else if (!strcmp(type, "tree")) {
+                       struct tree *tree = lookup_tree(sha1);
+                       parse_tree_buffer(tree, buffer, size);
+                       obj = &tree->object;
+               } else if (!strcmp(type, "commit")) {
+                       struct commit *commit = lookup_commit(sha1);
+                       parse_commit_buffer(commit, buffer, size);
+                       if (!commit->buffer) {
+                               commit->buffer = buffer;
+                               buffer = NULL;
+                       }
+                       obj = &commit->object;
+               } else if (!strcmp(type, "tag")) {
+                       struct tag *tag = lookup_tag(sha1);
+                       parse_tag_buffer(tag, buffer, size);
+                       obj = &tag->object;
+               } else {
+                       obj = NULL;
+               }
+               free(buffer);
+               return obj;
+       }
+       return NULL;
+}
+
+struct object_list *object_list_insert(struct object *item,
+                                      struct object_list **list_p)
+{
+       struct object_list *new_list = xmalloc(sizeof(struct object_list));
+        new_list->item = item;
+        new_list->next = *list_p;
+        *list_p = new_list;
+        return new_list;
+}
+
+unsigned object_list_length(struct object_list *list)
+{
+       unsigned ret = 0;
+       while (list) {
+               list = list->next;
+               ret++;
+       }
+       return ret;
+}
+
+int object_list_contains(struct object_list *list, struct object *obj)
+{
+       while (list) {
+               if (list->item == obj)
+                       return 1;
+               list = list->next;
+       }
+       return 0;
+}
diff --git a/object.h b/object.h
new file mode 100644 (file)
index 0000000..5af530d
--- /dev/null
+++ b/object.h
@@ -0,0 +1,48 @@
+#ifndef OBJECT_H
+#define OBJECT_H
+
+struct object_list {
+       struct object *item;
+       struct object_list *next;
+       const char *name;
+};
+
+struct object {
+       unsigned parsed : 1;
+       unsigned used : 1;
+       unsigned int flags;
+       unsigned char sha1[20];
+       const char *type;
+       struct object_list *refs;
+       void *util;
+};
+
+extern int nr_objs;
+extern struct object **objs;
+
+/** Internal only **/
+struct object *lookup_object(const unsigned char *sha1);
+
+/** Returns the object, having looked it up as being the given type. **/
+struct object *lookup_object_type(const unsigned char *sha1, const char *type);
+
+void created_object(const unsigned char *sha1, struct object *obj);
+
+/** Returns the object, having parsed it to find out what it is. **/
+struct object *parse_object(const unsigned char *sha1);
+
+/** Returns the object, with potentially excess memory allocated. **/
+struct object *lookup_unknown_object(const unsigned  char *sha1);
+
+void add_ref(struct object *refer, struct object *target);
+
+void mark_reachable(struct object *obj, unsigned int mask);
+
+struct object_list *object_list_insert(struct object *item, 
+                                      struct object_list **list_p);
+
+unsigned object_list_length(struct object_list *list);
+
+int object_list_contains(struct object_list *list, struct object *obj);
+
+#endif /* OBJECT_H */
diff --git a/pack-check.c b/pack-check.c
new file mode 100644 (file)
index 0000000..511f294
--- /dev/null
@@ -0,0 +1,143 @@
+#include "cache.h"
+#include "pack.h"
+
+static int verify_packfile(struct packed_git *p)
+{
+       unsigned long index_size = p->index_size;
+       void *index_base = p->index_base;
+       SHA_CTX ctx;
+       unsigned char sha1[20];
+       unsigned long pack_size = p->pack_size;
+       void *pack_base;
+       struct pack_header *hdr;
+       int nr_objects, err, i;
+
+       /* Header consistency check */
+       hdr = p->pack_base;
+       if (hdr->hdr_signature != htonl(PACK_SIGNATURE))
+               return error("Packfile %s signature mismatch", p->pack_name);
+       if (hdr->hdr_version != htonl(PACK_VERSION))
+               return error("Packfile version %d different from ours %d",
+                            ntohl(hdr->hdr_version), PACK_VERSION);
+       nr_objects = ntohl(hdr->hdr_entries);
+       if (num_packed_objects(p) != nr_objects)
+               return error("Packfile claims to have %d objects, "
+                            "while idx size expects %d", nr_objects,
+                            num_packed_objects(p));
+
+       SHA1_Init(&ctx);
+       pack_base = p->pack_base;
+       SHA1_Update(&ctx, pack_base, pack_size - 20);
+       SHA1_Final(sha1, &ctx);
+       if (memcmp(sha1, index_base + index_size - 40, 20))
+               return error("Packfile %s SHA1 mismatch with idx",
+                            p->pack_name);
+       if (memcmp(sha1, pack_base + pack_size - 20, 20))
+               return error("Packfile %s SHA1 mismatch with itself",
+                            p->pack_name);
+
+       /* Make sure everything reachable from idx is valid.  Since we
+        * have verified that nr_objects matches between idx and pack,
+        * we do not do scan-streaming check on the pack file.
+        */
+       for (i = err = 0; i < nr_objects; i++) {
+               unsigned char sha1[20];
+               struct pack_entry e;
+               void *data;
+               char type[20];
+               unsigned long size;
+
+               if (nth_packed_object_sha1(p, i, sha1))
+                       die("internal error pack-check nth-packed-object");
+               if (!find_pack_entry_one(sha1, &e, p))
+                       die("internal error pack-check find-pack-entry-one");
+               data = unpack_entry_gently(&e, type, &size);
+               if (!data) {
+                       err = error("cannot unpack %s from %s",
+                                   sha1_to_hex(sha1), p->pack_name);
+                       continue;
+               }
+               if (check_sha1_signature(sha1, data, size, type)) {
+                       err = error("packed %s from %s is corrupt",
+                                   sha1_to_hex(sha1), p->pack_name);
+                       free(data);
+                       continue;
+               }
+               free(data);
+       }
+
+       return err;
+}
+
+
+static void show_pack_info(struct packed_git *p)
+{
+       struct pack_header *hdr;
+       int nr_objects, i;
+
+       hdr = p->pack_base;
+       nr_objects = ntohl(hdr->hdr_entries);
+
+       for (i = 0; i < nr_objects; i++) {
+               unsigned char sha1[20], base_sha1[20];
+               struct pack_entry e;
+               char type[20];
+               unsigned long size;
+               unsigned long store_size;
+               int delta_chain_length;
+
+               if (nth_packed_object_sha1(p, i, sha1))
+                       die("internal error pack-check nth-packed-object");
+               if (!find_pack_entry_one(sha1, &e, p))
+                       die("internal error pack-check find-pack-entry-one");
+
+               packed_object_info_detail(&e, type, &size, &store_size,
+                                         &delta_chain_length,
+                                         base_sha1);
+               printf("%s ", sha1_to_hex(sha1));
+               if (!delta_chain_length)
+                       printf("%-6s %lu %u\n", type, size, e.offset);
+               else
+                       printf("%-6s %lu %u %d %s\n", type, size, e.offset,
+                              delta_chain_length, sha1_to_hex(base_sha1));
+       }
+
+}
+
+int verify_pack(struct packed_git *p, int verbose)
+{
+       unsigned long index_size = p->index_size;
+       void *index_base = p->index_base;
+       SHA_CTX ctx;
+       unsigned char sha1[20];
+       int ret;
+
+       ret = 0;
+       /* Verify SHA1 sum of the index file */
+       SHA1_Init(&ctx);
+       SHA1_Update(&ctx, index_base, index_size - 20);
+       SHA1_Final(sha1, &ctx);
+       if (memcmp(sha1, index_base + index_size - 20, 20))
+               ret = error("Packfile index for %s SHA1 mismatch",
+                           p->pack_name);
+
+       if (!ret) {
+               /* Verify pack file */
+               use_packed_git(p);
+               ret = verify_packfile(p);
+               unuse_packed_git(p);
+       }
+
+       if (verbose) {
+               if (ret)
+                       printf("%s: bad\n", p->pack_name);
+               else {
+                       use_packed_git(p);
+                       show_pack_info(p);
+                       unuse_packed_git(p);
+                       printf("%s: ok\n", p->pack_name);
+               }
+       }
+
+       return ret;
+}
diff --git a/pack-objects.c b/pack-objects.c
new file mode 100644 (file)
index 0000000..3d62278
--- /dev/null
@@ -0,0 +1,475 @@
+#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";
+
+struct object_entry {
+       unsigned char sha1[20];
+       unsigned long size;
+       unsigned long offset;
+       unsigned int depth;
+       unsigned int hash;
+       enum object_type type;
+       unsigned long delta_size;
+       struct object_entry *delta;
+};
+
+static unsigned char object_list_sha1[20];
+static int non_empty = 0;
+static int incremental = 0;
+static struct object_entry **sorted_by_sha, **sorted_by_type;
+static struct object_entry *objects = NULL;
+static int nr_objects = 0, nr_alloc = 0;
+static const char *base_name;
+static unsigned char pack_file_sha1[20];
+
+static void *delta_against(void *buf, unsigned long size, struct object_entry *entry)
+{
+       unsigned long othersize, delta_size;
+       char type[10];
+       void *otherbuf = read_sha1_file(entry->delta->sha1, type, &othersize);
+       void *delta_buf;
+
+       if (!otherbuf)
+               die("unable to read %s", sha1_to_hex(entry->delta->sha1));
+        delta_buf = diff_delta(otherbuf, othersize,
+                              buf, size, &delta_size, 0);
+        if (!delta_buf || delta_size != entry->delta_size)
+               die("delta size changed");
+        free(buf);
+        free(otherbuf);
+       return delta_buf;
+}
+
+/*
+ * The per-object header is a pretty dense thing, which is
+ *  - first byte: low four bits are "size", then three bits of "type",
+ *    and the high bit is "size continues".
+ *  - each byte afterwards: low seven bits are size continuation,
+ *    with the high bit being "size continues"
+ */
+static int encode_header(enum object_type type, unsigned long size, unsigned char *hdr)
+{
+       int n = 1;
+       unsigned char c;
+
+       if (type < OBJ_COMMIT || type > OBJ_DELTA)
+               die("bad type %d", type);
+
+       c = (type << 4) | (size & 15);
+       size >>= 4;
+       while (size) {
+               *hdr++ = c | 0x80;
+               c = size & 0x7f;
+               size >>= 7;
+               n++;
+       }
+       *hdr = c;
+       return n;
+}
+
+static unsigned long write_object(struct sha1file *f, struct object_entry *entry)
+{
+       unsigned long size;
+       char type[10];
+       void *buf = read_sha1_file(entry->sha1, type, &size);
+       unsigned char header[10];
+       unsigned hdrlen, datalen;
+       enum object_type obj_type;
+
+       if (!buf)
+               die("unable to read %s", sha1_to_hex(entry->sha1));
+       if (size != entry->size)
+               die("object %s size inconsistency (%lu vs %lu)", sha1_to_hex(entry->sha1), size, entry->size);
+
+       /*
+        * The object header is a byte of 'type' followed by zero or
+        * more bytes of length.  For deltas, the 20 bytes of delta sha1
+        * follows that.
+        */
+       obj_type = entry->type;
+       if (entry->delta) {
+               buf = delta_against(buf, size, entry);
+               size = entry->delta_size;
+               obj_type = OBJ_DELTA;
+       }
+       hdrlen = encode_header(obj_type, size, header);
+       sha1write(f, header, hdrlen);
+       if (entry->delta) {
+               sha1write(f, entry->delta, 20);
+               hdrlen += 20;
+       }
+       datalen = sha1write_compressed(f, buf, size);
+       free(buf);
+       return hdrlen + datalen;
+}
+
+static unsigned long write_one(struct sha1file *f,
+                              struct object_entry *e,
+                              unsigned long offset)
+{
+       if (e->offset)
+               /* offset starts from header size and cannot be zero
+                * if it is written already.
+                */
+               return offset;
+       e->offset = offset;
+       offset += write_object(f, e);
+       /* if we are delitified, write out its base object. */
+       if (e->delta)
+               offset = write_one(f, e->delta, offset);
+       return offset;
+}
+
+static void write_pack_file(void)
+{
+       int i;
+       struct sha1file *f;
+       unsigned long offset;
+       unsigned long mb;
+       struct pack_header hdr;
+
+       if (!base_name)
+               f = sha1fd(1, "<stdout>");
+       else
+               f = sha1create("%s-%s.%s", base_name, sha1_to_hex(object_list_sha1), "pack");
+       hdr.hdr_signature = htonl(PACK_SIGNATURE);
+       hdr.hdr_version = htonl(PACK_VERSION);
+       hdr.hdr_entries = htonl(nr_objects);
+       sha1write(f, &hdr, sizeof(hdr));
+       offset = sizeof(hdr);
+       for (i = 0; i < nr_objects; i++)
+               offset = write_one(f, objects + i, offset);
+
+       sha1close(f, pack_file_sha1, 1);
+       mb = offset >> 20;
+       offset &= 0xfffff;
+}
+
+static void write_index_file(void)
+{
+       int i;
+       struct sha1file *f = sha1create("%s-%s.%s", base_name, sha1_to_hex(object_list_sha1), "idx");
+       struct object_entry **list = sorted_by_sha;
+       struct object_entry **last = list + nr_objects;
+       unsigned int array[256];
+
+       /*
+        * 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 *entry = *next;
+                       if (entry->sha1[0] != i)
+                               break;
+                       next++;
+               }
+               array[i] = htonl(next - sorted_by_sha);
+               list = next;
+       }
+       sha1write(f, array, 256 * sizeof(int));
+
+       /*
+        * Write the actual SHA1 entries..
+        */
+       list = sorted_by_sha;
+       for (i = 0; i < nr_objects; i++) {
+               struct object_entry *entry = *list++;
+               unsigned int offset = htonl(entry->offset);
+               sha1write(f, &offset, 4);
+               sha1write(f, entry->sha1, 20);
+       }
+       sha1write(f, pack_file_sha1, 20);
+       sha1close(f, NULL, 1);
+}
+
+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 (idx >= nr_alloc) {
+               unsigned int needed = (idx + 1024) * 3 / 2;
+               objects = xrealloc(objects, needed * sizeof(*entry));
+               nr_alloc = needed;
+       }
+       entry = objects + idx;
+       memset(entry, 0, sizeof(*entry));
+       memcpy(entry->sha1, sha1, 20);
+       entry->hash = hash;
+       nr_objects = idx+1;
+       return 1;
+}
+
+static void check_object(struct object_entry *entry)
+{
+       char type[20];
+
+       if (!sha1_object_info(entry->sha1, type, &entry->size)) {
+               if (!strcmp(type, "commit")) {
+                       entry->type = OBJ_COMMIT;
+               } else if (!strcmp(type, "tree")) {
+                       entry->type = OBJ_TREE;
+               } else if (!strcmp(type, "blob")) {
+                       entry->type = OBJ_BLOB;
+               } else if (!strcmp(type, "tag")) {
+                       entry->type = OBJ_TAG;
+               } else
+                       die("unable to pack object %s of type %s",
+                           sha1_to_hex(entry->sha1), type);
+       }
+       else
+               die("unable to get type of object %s",
+                   sha1_to_hex(entry->sha1));
+}
+
+static void get_object_details(void)
+{
+       int i;
+       struct object_entry *entry = objects;
+
+       for (i = 0; i < nr_objects; i++)
+               check_object(entry++);
+}
+
+typedef int (*entry_sort_t)(const struct object_entry *, const struct object_entry *);
+
+static entry_sort_t current_sort;
+
+static int sort_comparator(const void *_a, const void *_b)
+{
+       struct object_entry *a = *(struct object_entry **)_a;
+       struct object_entry *b = *(struct object_entry **)_b;
+       return current_sort(a,b);
+}
+
+static struct object_entry **create_sorted_list(entry_sort_t sort)
+{
+       struct object_entry **list = xmalloc(nr_objects * sizeof(struct object_entry *));
+       int i;
+
+       for (i = 0; i < nr_objects; i++)
+               list[i] = objects + i;
+       current_sort = sort;
+       qsort(list, nr_objects, sizeof(struct object_entry *), sort_comparator);
+       return list;
+}
+
+static int sha1_sort(const struct object_entry *a, const struct object_entry *b)
+{
+       return memcmp(a->sha1, b->sha1, 20);
+}
+
+static int type_size_sort(const struct object_entry *a, const struct object_entry *b)
+{
+       if (a->type < b->type)
+               return -1;
+       if (a->type > b->type)
+               return 1;
+       if (a->hash < b->hash)
+               return -1;
+       if (a->hash > b->hash)
+               return 1;
+       if (a->size < b->size)
+               return -1;
+       if (a->size > b->size)
+               return 1;
+       return a < b ? -1 : (a > b);
+}
+
+struct unpacked {
+       struct object_entry *entry;
+       void *data;
+};
+
+/*
+ * We search for deltas _backwards_ in a list sorted by type and
+ * by size, so that we see progressively smaller and smaller files.
+ * That's because we prefer deltas to be from the bigger file
+ * to the smaller - deletes are potentially cheaper, but perhaps
+ * more importantly, the bigger file is likely the more recent
+ * one.
+ */
+static int try_delta(struct unpacked *cur, struct unpacked *old, unsigned max_depth)
+{
+       struct object_entry *cur_entry = cur->entry;
+       struct object_entry *old_entry = old->entry;
+       unsigned long size, oldsize, delta_size, sizediff;
+       long max_size;
+       void *delta_buf;
+
+       /* Don't bother doing diffs between different types */
+       if (cur_entry->type != old_entry->type)
+               return -1;
+
+       size = cur_entry->size;
+       if (size < 50)
+               return -1;
+       oldsize = old_entry->size;
+       sizediff = oldsize > size ? oldsize - size : size - oldsize;
+       if (sizediff > size / 8)
+               return -1;
+       if (old_entry->depth >= max_depth)
+               return 0;
+
+       /*
+        * NOTE!
+        *
+        * We always delta from the bigger to the smaller, since that's
+        * more space-efficient (deletes don't have to say _what_ they
+        * delete).
+        */
+       max_size = size / 2 - 20;
+       if (cur_entry->delta)
+               max_size = cur_entry->delta_size-1;
+       if (sizediff >= max_size)
+               return -1;
+       delta_buf = diff_delta(old->data, oldsize,
+                              cur->data, size, &delta_size, max_size);
+       if (!delta_buf)
+               return 0;
+       cur_entry->delta = old_entry;
+       cur_entry->delta_size = delta_size;
+       cur_entry->depth = old_entry->depth + 1;
+       free(delta_buf);
+       return 0;
+}
+
+static void find_deltas(struct object_entry **list, int window, int depth)
+{
+       int i, idx;
+       unsigned int array_size = window * sizeof(struct unpacked);
+       struct unpacked *array = xmalloc(array_size);
+
+       memset(array, 0, array_size);
+       i = nr_objects;
+       idx = 0;
+       while (--i >= 0) {
+               struct object_entry *entry = list[i];
+               struct unpacked *n = array + idx;
+               unsigned long size;
+               char type[10];
+               int j;
+
+               free(n->data);
+               n->entry = entry;
+               n->data = read_sha1_file(entry->sha1, type, &size);
+               if (size != entry->size)
+                       die("object %s inconsistent object length (%lu vs %lu)", sha1_to_hex(entry->sha1), size, entry->size);
+               j = window;
+               while (--j > 0) {
+                       unsigned int other_idx = idx + j;
+                       struct unpacked *m;
+                       if (other_idx >= window)
+                               other_idx -= window;
+                       m = array + other_idx;
+                       if (!m->entry)
+                               break;
+                       if (try_delta(n, m, depth) < 0)
+                               break;
+               }
+               idx++;
+               if (idx >= window)
+                       idx = 0;
+       }
+
+       for (i = 0; i < window; ++i)
+               free(array[i].data);
+       free(array);
+}
+
+int main(int argc, char **argv)
+{
+       SHA_CTX ctx;
+       char line[PATH_MAX + 20];
+       int window = 10, depth = 10, pack_to_stdout = 0;
+       int i;
+
+       for (i = 1; i < argc; i++) {
+               const char *arg = argv[i];
+
+               if (*arg == '-') {
+                       if (!strcmp("--non-empty", arg)) {
+                               non_empty = 1;
+                               continue;
+                       }
+                       if (!strcmp("--incremental", arg)) {
+                               incremental = 1;
+                               continue;
+                       }
+                       if (!strncmp("--window=", arg, 9)) {
+                               char *end;
+                               window = strtoul(arg+9, &end, 0);
+                               if (!arg[9] || *end)
+                                       usage(pack_usage);
+                               continue;
+                       }
+                       if (!strncmp("--depth=", arg, 8)) {
+                               char *end;
+                               depth = strtoul(arg+8, &end, 0);
+                               if (!arg[8] || *end)
+                                       usage(pack_usage);
+                               continue;
+                       }
+                       if (!strcmp("--stdout", arg)) {
+                               pack_to_stdout = 1;
+                               continue;
+                       }
+                       usage(pack_usage);
+               }
+               if (base_name)
+                       usage(pack_usage);
+               base_name = arg;
+       }
+
+       if (pack_to_stdout != !base_name)
+               usage(pack_usage);
+
+       SHA1_Init(&ctx);
+       while (fgets(line, sizeof(line), stdin) != NULL) {
+               unsigned int hash;
+               char *p;
+               unsigned char sha1[20];
+
+               if (get_sha1_hex(line, sha1))
+                       die("expected sha1, got garbage");
+               hash = 0;
+               p = line+40;
+               while (*p) {
+                       unsigned char c = *p++;
+                       if (isspace(c))
+                               continue;
+                       hash = hash * 11 + c;
+               }
+               if (add_object_entry(sha1, hash))
+                       SHA1_Update(&ctx, sha1, 20);
+       }
+       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);
+
+       write_pack_file();
+       if (!pack_to_stdout) {
+               write_index_file();
+               puts(sha1_to_hex(object_list_sha1));
+       }
+       return 0;
+}
diff --git a/pack.h b/pack.h
new file mode 100644 (file)
index 0000000..657deaa
--- /dev/null
+++ b/pack.h
@@ -0,0 +1,32 @@
+#ifndef PACK_H
+#define PACK_H
+
+/*
+ * The packed object type is stored in 3 bits.
+ * The type value 0 is a reserved prefix if ever there is more than 7
+ * object types, or any future format extensions.
+ */
+enum object_type {
+       OBJ_EXT = 0,
+       OBJ_COMMIT = 1,
+       OBJ_TREE = 2,
+       OBJ_BLOB = 3,
+       OBJ_TAG = 4,
+       /* 5/6 for future expansion */
+       OBJ_DELTA = 7,
+};
+
+/*
+ * Packed object header
+ */
+#define PACK_SIGNATURE 0x5041434b      /* "PACK" */
+#define PACK_VERSION 2
+struct pack_header {
+       unsigned int hdr_signature;
+       unsigned int hdr_version;
+       unsigned int hdr_entries;
+};
+
+extern int verify_pack(struct packed_git *, int);
+
+#endif
diff --git a/patch-delta.c b/patch-delta.c
new file mode 100644 (file)
index 0000000..26281ea
--- /dev/null
@@ -0,0 +1,72 @@
+/*
+ * patch-delta.c:
+ * recreate a buffer from a source and the delta produced by diff-delta.c
+ *
+ * (C) 2005 Nicolas Pitre <nico@cam.org>
+ *
+ * This code 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.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include "delta.h"
+
+void *patch_delta(void *src_buf, unsigned long src_size,
+                 void *delta_buf, unsigned long delta_size,
+                 unsigned long *dst_size)
+{
+       const unsigned char *data, *top;
+       unsigned char *dst_buf, *out, cmd;
+       unsigned long size;
+
+       if (delta_size < DELTA_SIZE_MIN)
+               return NULL;
+
+       data = delta_buf;
+       top = delta_buf + delta_size;
+
+       /* make sure the orig file size matches what we expect */
+       size = get_delta_hdr_size(&data);
+       if (size != src_size)
+               return NULL;
+
+       /* now the result size */
+       size = get_delta_hdr_size(&data);
+       dst_buf = malloc(size);
+       if (!dst_buf)
+               return NULL;
+
+       out = dst_buf;
+       while (data < top) {
+               cmd = *data++;
+               if (cmd & 0x80) {
+                       unsigned long cp_off = 0, cp_size = 0;
+                       const unsigned char *buf;
+                       if (cmd & 0x01) cp_off = *data++;
+                       if (cmd & 0x02) cp_off |= (*data++ << 8);
+                       if (cmd & 0x04) cp_off |= (*data++ << 16);
+                       if (cmd & 0x08) cp_off |= (*data++ << 24);
+                       if (cmd & 0x10) cp_size = *data++;
+                       if (cmd & 0x20) cp_size |= (*data++ << 8);
+                       if (cp_size == 0) cp_size = 0x10000;
+                       buf = (cmd & 0x40) ? dst_buf : src_buf;
+                       memcpy(out, buf + cp_off, cp_size);
+                       out += cp_size;
+               } else {
+                       memcpy(out, data, cmd);
+                       out += cmd;
+                       data += cmd;
+               }
+       }
+
+       /* sanity check */
+       if (data != top || out - dst_buf != size) {
+               free(dst_buf);
+               return NULL;
+       }
+
+       *dst_size = size;
+       return dst_buf;
+}
diff --git a/patch-id.c b/patch-id.c
new file mode 100644 (file)
index 0000000..5a8dc75
--- /dev/null
@@ -0,0 +1,79 @@
+#include <ctype.h>
+#include "cache.h"
+
+static void flush_current_id(int patchlen, unsigned char *id, SHA_CTX *c)
+{
+       unsigned char result[20];
+       char name[50];
+
+       if (!patchlen)
+               return;
+
+       SHA1_Final(result, c);
+       memcpy(name, sha1_to_hex(id), 41);
+       printf("%s %s\n", sha1_to_hex(result), name);
+       SHA1_Init(c);
+}
+
+static int remove_space(char *line)
+{
+       char *src = line;
+       char *dst = line;
+       unsigned char c;
+
+       while ((c = *src++) != '\0') {
+               if (!isspace(c))
+                       *dst++ = c;
+       }
+       return dst - line;
+}
+
+static void generate_id_list(void)
+{
+       static unsigned char sha1[20];
+       static char line[1000];
+       SHA_CTX ctx;
+       int patchlen = 0;
+
+       SHA1_Init(&ctx);
+       while (fgets(line, sizeof(line), stdin) != NULL) {
+               unsigned char n[20];
+               char *p = line;
+               int len;
+
+               if (!memcmp(line, "diff-tree ", 10))
+                       p += 10;
+
+               if (!get_sha1_hex(p, n)) {
+                       flush_current_id(patchlen, sha1, &ctx);
+                       memcpy(sha1, n, 20);
+                       patchlen = 0;
+                       continue;
+               }
+
+               /* Ignore commit comments */
+               if (!patchlen && memcmp(line, "diff ", 5))
+                       continue;
+
+               /* Ignore line numbers when computing the SHA1 of the patch */
+               if (!memcmp(line, "@@ -", 4))
+                       continue;
+
+               /* Compute the sha without whitespace */
+               len = remove_space(line);
+               patchlen += len;
+               SHA1_Update(&ctx, line, len);
+       }
+       flush_current_id(patchlen, sha1, &ctx);
+}
+
+static const char patch_id_usage[] = "git-patch-id < patch";
+
+int main(int argc, char **argv)
+{
+       if (argc != 1)
+               usage(patch_id_usage);
+
+       generate_id_list();
+       return 0;
+}      
diff --git a/path.c b/path.c
new file mode 100644 (file)
index 0000000..b85f087
--- /dev/null
+++ b/path.c
@@ -0,0 +1,91 @@
+/*
+ * I'm tired of doing "vsnprintf()" etc just to open a
+ * file, so here's a "return static buffer with printf"
+ * interface for paths.
+ *
+ * It's obviously not thread-safe. Sue me. But it's quite
+ * useful for doing things like
+ *
+ *   f = open(mkpath("%s/%s.git", base, name), O_RDONLY);
+ *
+ * which is what it's designed for.
+ */
+#include "cache.h"
+
+static char pathname[PATH_MAX];
+static char bad_path[] = "/bad-path/";
+
+static char *cleanup_path(char *path)
+{
+       /* Clean it up */
+       if (!memcmp(path, "./", 2)) {
+               path += 2;
+               while (*path == '/')
+                       path++;
+       }
+       return path;
+}
+
+char *mkpath(const char *fmt, ...)
+{
+       va_list args;
+       unsigned len;
+
+       va_start(args, fmt);
+       len = vsnprintf(pathname, PATH_MAX, fmt, args);
+       va_end(args);
+       if (len >= PATH_MAX)
+               return bad_path;
+       return cleanup_path(pathname);
+}
+
+char *git_path(const char *fmt, ...)
+{
+       const char *git_dir = gitenv(GIT_DIR_ENVIRONMENT) ? : DEFAULT_GIT_DIR_ENVIRONMENT;
+       va_list args;
+       unsigned len;
+
+       len = strlen(git_dir);
+       if (len > PATH_MAX-100)
+               return bad_path;
+       memcpy(pathname, git_dir, len);
+       if (len && git_dir[len-1] != '/')
+               pathname[len++] = '/';
+       va_start(args, fmt);
+       len += vsnprintf(pathname + len, PATH_MAX - len, fmt, args);
+       va_end(args);
+       if (len >= PATH_MAX)
+               return bad_path;
+       return cleanup_path(pathname);
+}
+
+
+/* git_mkstemp() - create tmp file honoring TMPDIR variable */
+int git_mkstemp(char *path, size_t len, const char *template)
+{
+       char *env, *pch = path;
+
+       if ((env = getenv("TMPDIR")) == NULL) {
+               strcpy(pch, "/tmp/");
+               len -= 5;
+               pch += 5;
+       } else {
+               size_t n = snprintf(pch, len, "%s/", env);
+
+               len -= n;
+               pch += n;
+       }
+
+       safe_strncpy(pch, template, len);
+
+       return mkstemp(path);
+}
+
+
+char *safe_strncpy(char *dest, const char *src, size_t n)
+{
+       strncpy(dest, src, n);
+       dest[n - 1] = '\0';
+
+       return dest;
+}
diff --git a/peek-remote.c b/peek-remote.c
new file mode 100644 (file)
index 0000000..4b1d0d5
--- /dev/null
@@ -0,0 +1,55 @@
+#include "cache.h"
+#include "refs.h"
+#include "pkt-line.h"
+#include <sys/wait.h>
+
+static const char peek_remote_usage[] =
+"git-peek-remote [--exec=upload-pack] [host:]directory";
+static const char *exec = "git-upload-pack";
+
+static int peek_remote(int fd[2])
+{
+       struct ref *ref;
+
+       get_remote_heads(fd[0], &ref, 0, NULL);
+       packet_flush(fd[1]);
+
+       while (ref) {
+               printf("%s      %s\n", sha1_to_hex(ref->old_sha1), ref->name);
+               ref = ref->next;
+       }
+       return 0;
+}
+
+int main(int argc, char **argv)
+{
+       int i, ret;
+       char *dest = NULL;
+       int fd[2];
+       pid_t pid;
+
+       for (i = 1; i < argc; i++) {
+               char *arg = argv[i];
+
+               if (*arg == '-') {
+                       if (!strncmp("--exec=", arg, 7))
+                               exec = arg + 7;
+                       else
+                               usage(peek_remote_usage);
+                       continue;
+               }
+               dest = arg;
+               break;
+       }
+       if (!dest || i != argc - 1)
+               usage(peek_remote_usage);
+
+       pid = git_connect(fd, dest, exec);
+       if (pid < 0)
+               return 1;
+       ret = peek_remote(fd);
+       close(fd[0]);
+       close(fd[1]);
+       finish_connect(pid);
+       return ret;
+}
diff --git a/pkt-line.c b/pkt-line.c
new file mode 100644 (file)
index 0000000..6947304
--- /dev/null
@@ -0,0 +1,117 @@
+#include "cache.h"
+#include "pkt-line.h"
+
+/*
+ * Write a packetized stream, where each line is preceded by
+ * its length (including the header) as a 4-byte hex number.
+ * A length of 'zero' means end of stream (and a length of 1-3
+ * would be an error). 
+ *
+ * This is all pretty stupid, but we use this packetized line
+ * format to make a streaming format possible without ever
+ * over-running the read buffers. That way we'll never read
+ * into what might be the pack data (which should go to another
+ * process entirely).
+ *
+ * The writing side could use stdio, but since the reading
+ * side can't, we stay with pure read/write interfaces.
+ */
+static void safe_write(int fd, const void *buf, unsigned n)
+{
+       while (n) {
+               int ret = write(fd, buf, n);
+               if (ret > 0) {
+                       buf += ret;
+                       n -= ret;
+                       continue;
+               }
+               if (!ret)
+                       die("write error (disk full?)");
+               if (errno == EAGAIN || errno == EINTR)
+                       continue;
+               die("write error (%s)", strerror(errno));
+       }
+}
+
+/*
+ * If we buffered things up above (we don't, but we should),
+ * we'd flush it here
+ */
+void packet_flush(int fd)
+{
+       safe_write(fd, "0000", 4);
+}
+
+#define hex(a) (hexchar[(a) & 15])
+void packet_write(int fd, const char *fmt, ...)
+{
+       static char buffer[1000];
+       static char hexchar[] = "0123456789abcdef";
+       va_list args;
+       unsigned n;
+
+       va_start(args, fmt);
+       n = vsnprintf(buffer + 4, sizeof(buffer) - 4, fmt, args);
+       va_end(args);
+       if (n >= sizeof(buffer)-4)
+               die("protocol error: impossibly long line");
+       n += 4;
+       buffer[0] = hex(n >> 12);
+       buffer[1] = hex(n >> 8);
+       buffer[2] = hex(n >> 4);
+       buffer[3] = hex(n);
+       safe_write(fd, buffer, n);
+}
+
+static void safe_read(int fd, void *buffer, unsigned size)
+{
+       int n = 0;
+
+       while (n < size) {
+               int ret = read(fd, buffer + n, size - n);
+               if (ret < 0) {
+                       if (errno == EINTR || errno == EAGAIN)
+                               continue;
+                       die("read error (%s)", strerror(errno));
+               }
+               if (!ret)
+                       die("unexpected EOF");
+               n += ret;
+       }
+}
+
+int packet_read_line(int fd, char *buffer, unsigned size)
+{
+       int n;
+       unsigned len;
+       char linelen[4];
+
+       safe_read(fd, linelen, 4);
+
+       len = 0;
+       for (n = 0; n < 4; n++) {
+               unsigned char c = linelen[n];
+               len <<= 4;
+               if (c >= '0' && c <= '9') {
+                       len += c - '0';
+                       continue;
+               }
+               if (c >= 'a' && c <= 'f') {
+                       len += c - 'a' + 10;
+                       continue;
+               }
+               if (c >= 'A' && c <= 'F') {
+                       len += c - 'A' + 10;
+                       continue;
+               }
+               die("protocol error: bad line length character");
+       }
+       if (!len)
+               return 0;
+       len -= 4;
+       if (len >= size)
+               die("protocol error: bad line length %d", len);
+       safe_read(fd, buffer, len);
+       buffer[len] = 0;
+       return len;
+}
diff --git a/pkt-line.h b/pkt-line.h
new file mode 100644 (file)
index 0000000..51d0cbe
--- /dev/null
@@ -0,0 +1,12 @@
+#ifndef PKTLINE_H
+#define PKTLINE_H
+
+/*
+ * Silly packetized line writing interface
+ */
+void packet_flush(int fd);
+void packet_write(int fd, const char *fmt, ...) __attribute__((format (printf, 2, 3)));
+
+int packet_read_line(int fd, char *buffer, unsigned size);
+
+#endif
diff --git a/ppc/sha1.c b/ppc/sha1.c
new file mode 100644 (file)
index 0000000..5ba4fc5
--- /dev/null
@@ -0,0 +1,72 @@
+/*
+ * SHA-1 implementation.
+ *
+ * Copyright (C) 2005 Paul Mackerras <paulus@samba.org>
+ *
+ * This version assumes we are running on a big-endian machine.
+ * It calls an external sha1_core() to process blocks of 64 bytes.
+ */
+#include <stdio.h>
+#include <string.h>
+#include "sha1.h"
+
+extern void sha1_core(uint32_t *hash, const unsigned char *p,
+                     unsigned int nblocks);
+
+int SHA1_Init(SHA_CTX *c)
+{
+       c->hash[0] = 0x67452301;
+       c->hash[1] = 0xEFCDAB89;
+       c->hash[2] = 0x98BADCFE;
+       c->hash[3] = 0x10325476;
+       c->hash[4] = 0xC3D2E1F0;
+       c->len = 0;
+       c->cnt = 0;
+       return 0;
+}
+
+int SHA1_Update(SHA_CTX *c, const void *ptr, unsigned long n)
+{
+       unsigned long nb;
+       const unsigned char *p = ptr;
+
+       c->len += n << 3;
+       while (n != 0) {
+               if (c->cnt || n < 64) {
+                       nb = 64 - c->cnt;
+                       if (nb > n)
+                               nb = n;
+                       memcpy(&c->buf.b[c->cnt], p, nb);
+                       if ((c->cnt += nb) == 64) {
+                               sha1_core(c->hash, c->buf.b, 1);
+                               c->cnt = 0;
+                       }
+               } else {
+                       nb = n >> 6;
+                       sha1_core(c->hash, p, nb);
+                       nb <<= 6;
+               }
+               n -= nb;
+               p += nb;
+       }
+       return 0;
+}      
+
+int SHA1_Final(unsigned char *hash, SHA_CTX *c)
+{
+       unsigned int cnt = c->cnt;
+
+       c->buf.b[cnt++] = 0x80;
+       if (cnt > 56) {
+               if (cnt < 64)
+                       memset(&c->buf.b[cnt], 0, 64 - cnt);
+               sha1_core(c->hash, c->buf.b, 1);
+               cnt = 0;
+       }
+       if (cnt < 56)
+               memset(&c->buf.b[cnt], 0, 56 - cnt);
+       c->buf.l[7] = c->len;
+       sha1_core(c->hash, c->buf.b, 1);
+       memcpy(hash, c->hash, 20);
+       return 0;
+}
diff --git a/ppc/sha1.h b/ppc/sha1.h
new file mode 100644 (file)
index 0000000..c3c51aa
--- /dev/null
@@ -0,0 +1,20 @@
+/*
+ * SHA-1 implementation.
+ *
+ * Copyright (C) 2005 Paul Mackerras <paulus@samba.org>
+ */
+#include <stdint.h>
+
+typedef struct sha_context {
+       uint32_t hash[5];
+       uint32_t cnt;
+       uint64_t len;
+       union {
+               unsigned char b[64];
+               uint64_t l[8];
+       } buf;
+} SHA_CTX;
+
+int SHA1_Init(SHA_CTX *c);
+int SHA1_Update(SHA_CTX *c, const void *p, unsigned long n);
+int SHA1_Final(unsigned char *hash, SHA_CTX *c);
diff --git a/ppc/sha1ppc.S b/ppc/sha1ppc.S
new file mode 100644 (file)
index 0000000..e85611a
--- /dev/null
@@ -0,0 +1,185 @@
+/*
+ * SHA-1 implementation for PowerPC.
+ *
+ * Copyright (C) 2005 Paul Mackerras <paulus@samba.org>
+ */
+#define FS     80
+
+/*
+ * We roll the registers for T, A, B, C, D, E around on each
+ * iteration; T on iteration t is A on iteration t+1, and so on.
+ * We use registers 7 - 12 for this.
+ */
+#define RT(t)  ((((t)+5)%6)+7)
+#define RA(t)  ((((t)+4)%6)+7)
+#define RB(t)  ((((t)+3)%6)+7)
+#define RC(t)  ((((t)+2)%6)+7)
+#define RD(t)  ((((t)+1)%6)+7)
+#define RE(t)  ((((t)+0)%6)+7)
+
+/* We use registers 16 - 31 for the W values */
+#define W(t)   (((t)%16)+16)
+
+#define STEPD0(t)                              \
+       and     %r6,RB(t),RC(t);                \
+       andc    %r0,RD(t),RB(t);                \
+       rotlwi  RT(t),RA(t),5;                  \
+       rotlwi  RB(t),RB(t),30;                 \
+       or      %r6,%r6,%r0;                    \
+       add     %r0,RE(t),%r15;                 \
+       add     RT(t),RT(t),%r6;                \
+       add     %r0,%r0,W(t);                   \
+       add     RT(t),RT(t),%r0
+
+#define STEPD1(t)                              \
+       xor     %r6,RB(t),RC(t);                \
+       rotlwi  RT(t),RA(t),5;                  \
+       rotlwi  RB(t),RB(t),30;                 \
+       xor     %r6,%r6,RD(t);                  \
+       add     %r0,RE(t),%r15;                 \
+       add     RT(t),RT(t),%r6;                \
+       add     %r0,%r0,W(t);                   \
+       add     RT(t),RT(t),%r0
+
+#define STEPD2(t)                              \
+       and     %r6,RB(t),RC(t);                \
+       and     %r0,RB(t),RD(t);                \
+       rotlwi  RT(t),RA(t),5;                  \
+       rotlwi  RB(t),RB(t),30;                 \
+       or      %r6,%r6,%r0;                    \
+       and     %r0,RC(t),RD(t);                \
+       or      %r6,%r6,%r0;                    \
+       add     %r0,RE(t),%r15;                 \
+       add     RT(t),RT(t),%r6;                \
+       add     %r0,%r0,W(t);                   \
+       add     RT(t),RT(t),%r0
+
+#define LOADW(t)                               \
+       lwz     W(t),(t)*4(%r4)
+
+#define UPDATEW(t)                             \
+       xor     %r0,W((t)-3),W((t)-8);          \
+       xor     W(t),W((t)-16),W((t)-14);       \
+       xor     W(t),W(t),%r0;                  \
+       rotlwi  W(t),W(t),1
+
+#define STEP0LD4(t)                            \
+       STEPD0(t);   LOADW((t)+4);              \
+       STEPD0((t)+1); LOADW((t)+5);            \
+       STEPD0((t)+2); LOADW((t)+6);            \
+       STEPD0((t)+3); LOADW((t)+7)
+
+#define STEPUP4(t, fn)                         \
+       STEP##fn(t);   UPDATEW((t)+4);          \
+       STEP##fn((t)+1); UPDATEW((t)+5);        \
+       STEP##fn((t)+2); UPDATEW((t)+6);        \
+       STEP##fn((t)+3); UPDATEW((t)+7)
+
+#define STEPUP20(t, fn)                                \
+       STEPUP4(t, fn);                         \
+       STEPUP4((t)+4, fn);                     \
+       STEPUP4((t)+8, fn);                     \
+       STEPUP4((t)+12, fn);                    \
+       STEPUP4((t)+16, fn)
+
+       .globl  sha1_core
+sha1_core:
+       stwu    %r1,-FS(%r1)
+       stw     %r15,FS-68(%r1)
+       stw     %r16,FS-64(%r1)
+       stw     %r17,FS-60(%r1)
+       stw     %r18,FS-56(%r1)
+       stw     %r19,FS-52(%r1)
+       stw     %r20,FS-48(%r1)
+       stw     %r21,FS-44(%r1)
+       stw     %r22,FS-40(%r1)
+       stw     %r23,FS-36(%r1)
+       stw     %r24,FS-32(%r1)
+       stw     %r25,FS-28(%r1)
+       stw     %r26,FS-24(%r1)
+       stw     %r27,FS-20(%r1)
+       stw     %r28,FS-16(%r1)
+       stw     %r29,FS-12(%r1)
+       stw     %r30,FS-8(%r1)
+       stw     %r31,FS-4(%r1)
+
+       /* Load up A - E */
+       lwz     RA(0),0(%r3)    /* A */
+       lwz     RB(0),4(%r3)    /* B */
+       lwz     RC(0),8(%r3)    /* C */
+       lwz     RD(0),12(%r3)   /* D */
+       lwz     RE(0),16(%r3)   /* E */
+
+       mtctr   %r5
+
+1:     LOADW(0)
+       LOADW(1)
+       LOADW(2)
+       LOADW(3)
+
+       lis     %r15,0x5a82     /* K0-19 */
+       ori     %r15,%r15,0x7999
+       STEP0LD4(0)
+       STEP0LD4(4)
+       STEP0LD4(8)
+       STEPUP4(12, D0)
+       STEPUP4(16, D0)
+
+       lis     %r15,0x6ed9     /* K20-39 */
+       ori     %r15,%r15,0xeba1
+       STEPUP20(20, D1)
+
+       lis     %r15,0x8f1b     /* K40-59 */
+       ori     %r15,%r15,0xbcdc
+       STEPUP20(40, D2)
+
+       lis     %r15,0xca62     /* K60-79 */
+       ori     %r15,%r15,0xc1d6
+       STEPUP4(60, D1)
+       STEPUP4(64, D1)
+       STEPUP4(68, D1)
+       STEPUP4(72, D1)
+       STEPD1(76)
+       STEPD1(77)
+       STEPD1(78)
+       STEPD1(79)
+
+       lwz     %r20,16(%r3)
+       lwz     %r19,12(%r3)
+       lwz     %r18,8(%r3)
+       lwz     %r17,4(%r3)
+       lwz     %r16,0(%r3)
+       add     %r20,RE(80),%r20
+       add     RD(0),RD(80),%r19
+       add     RC(0),RC(80),%r18
+       add     RB(0),RB(80),%r17
+       add     RA(0),RA(80),%r16
+       mr      RE(0),%r20
+       stw     RA(0),0(%r3)
+       stw     RB(0),4(%r3)
+       stw     RC(0),8(%r3)
+       stw     RD(0),12(%r3)
+       stw     RE(0),16(%r3)
+
+       addi    %r4,%r4,64
+       bdnz    1b
+
+       lwz     %r15,FS-68(%r1)
+       lwz     %r16,FS-64(%r1)
+       lwz     %r17,FS-60(%r1)
+       lwz     %r18,FS-56(%r1)
+       lwz     %r19,FS-52(%r1)
+       lwz     %r20,FS-48(%r1)
+       lwz     %r21,FS-44(%r1)
+       lwz     %r22,FS-40(%r1)
+       lwz     %r23,FS-36(%r1)
+       lwz     %r24,FS-32(%r1)
+       lwz     %r25,FS-28(%r1)
+       lwz     %r26,FS-24(%r1)
+       lwz     %r27,FS-20(%r1)
+       lwz     %r28,FS-16(%r1)
+       lwz     %r29,FS-12(%r1)
+       lwz     %r30,FS-8(%r1)
+       lwz     %r31,FS-4(%r1)
+       addi    %r1,%r1,FS
+       blr
diff --git a/prune-packed.c b/prune-packed.c
new file mode 100644 (file)
index 0000000..a2f4488
--- /dev/null
@@ -0,0 +1,66 @@
+#include "cache.h"
+
+static const char prune_packed_usage[] = "git-prune-packed (no arguments)";
+
+static void prune_dir(int i, DIR *dir, char *pathname, int len)
+{
+       struct dirent *de;
+       char hex[40];
+
+       sprintf(hex, "%02x", i);
+       while ((de = readdir(dir)) != NULL) {
+               unsigned char sha1[20];
+               if (strlen(de->d_name) != 38)
+                       continue;
+               memcpy(hex+2, de->d_name, 38);
+               if (get_sha1_hex(hex, sha1))
+                       continue;
+               if (!has_sha1_pack(sha1))
+                       continue;
+               memcpy(pathname + len, de->d_name, 38);
+               if (unlink(pathname) < 0)
+                       error("unable to unlink %s", pathname);
+       }
+}
+
+static void prune_packed_objects(void)
+{
+       int i;
+       static char pathname[PATH_MAX];
+       const char *dir = get_object_directory();
+       int len = strlen(dir);
+
+       if (len > PATH_MAX - 42)
+               die("impossible object directory");
+       memcpy(pathname, dir, len);
+       if (len && pathname[len-1] != '/')
+               pathname[len++] = '/';
+       for (i = 0; i < 256; i++) {
+               DIR *d;
+
+               sprintf(pathname + len, "%02x/", i);
+               d = opendir(pathname);
+               if (!d)
+                       die("unable to open %s", pathname);
+               prune_dir(i, d, pathname, len + 3);
+               closedir(d);
+       }
+}
+
+int main(int argc, char **argv)
+{
+       int i;
+
+       for (i = 1; i < argc; i++) {
+               const char *arg = argv[i];
+
+               if (*arg == '-') {
+                       /* Handle flags here .. */
+                       usage(prune_packed_usage);
+               }
+               /* Handle arguments here .. */
+               usage(prune_packed_usage);
+       }
+       prune_packed_objects();
+       return 0;
+}
diff --git a/pull.c b/pull.c
new file mode 100644 (file)
index 0000000..950d9f4
--- /dev/null
+++ b/pull.c
@@ -0,0 +1,202 @@
+#include "pull.h"
+
+#include "cache.h"
+#include "commit.h"
+#include "tree.h"
+#include "tag.h"
+#include "blob.h"
+#include "refs.h"
+
+const char *write_ref = NULL;
+
+const unsigned char *current_ref = NULL;
+
+int get_tree = 0;
+int get_history = 0;
+int get_all = 0;
+int get_verbosely = 0;
+static unsigned char current_commit_sha1[20];
+
+void pull_say(const char *fmt, const char *hex) 
+{
+       if (get_verbosely)
+               fprintf(stderr, fmt, hex);
+}
+
+static void report_missing(const char *what, const unsigned char *missing)
+{
+       char missing_hex[41];
+
+       strcpy(missing_hex, sha1_to_hex(missing));;
+       fprintf(stderr,
+               "Cannot obtain needed %s %s\nwhile processing commit %s.\n",
+               what, missing_hex, sha1_to_hex(current_commit_sha1));
+}
+
+static int make_sure_we_have_it(const char *what, unsigned char *sha1)
+{
+       int status = 0;
+
+       if (!has_sha1_file(sha1)) {
+               status = fetch(sha1);
+               if (status && what)
+                       report_missing(what, sha1);
+       }
+       return status;
+}
+
+static int process(unsigned char *sha1, const char *type);
+
+static int process_tree(struct tree *tree)
+{
+       struct tree_entry_list *entries;
+
+       if (parse_tree(tree))
+               return -1;
+
+       for (entries = tree->entries; entries; entries = entries->next) {
+               if (process(entries->item.any->sha1,
+                           entries->directory ? tree_type : blob_type))
+                       return -1;
+       }
+       return 0;
+}
+
+static int process_commit(struct commit *commit)
+{
+       if (parse_commit(commit))
+               return -1;
+
+       memcpy(current_commit_sha1, commit->object.sha1, 20);
+
+       if (get_tree) {
+               if (process(commit->tree->object.sha1, tree_type))
+                       return -1;
+               if (!get_all)
+                       get_tree = 0;
+       }
+       if (get_history) {
+               struct commit_list *parents = commit->parents;
+               for (; parents; parents = parents->next) {
+                       if (has_sha1_file(parents->item->object.sha1))
+                               continue;
+                       if (process(parents->item->object.sha1,
+                                   commit_type))
+                               return -1;
+               }
+       }
+       return 0;
+}
+
+static int process_tag(struct tag *tag)
+{
+       if (parse_tag(tag))
+               return -1;
+       return process(tag->tagged->sha1, NULL);
+}
+
+static struct object_list *process_queue = NULL;
+static struct object_list **process_queue_end = &process_queue;
+
+static int process(unsigned char *sha1, const char *type)
+{
+       struct object *obj;
+       if (has_sha1_file(sha1))
+               return 0;
+       obj = lookup_object_type(sha1, type);
+       if (object_list_contains(process_queue, obj))
+               return 0;
+       object_list_insert(obj, process_queue_end);
+       process_queue_end = &(*process_queue_end)->next;
+
+       //fprintf(stderr, "prefetch %s\n", sha1_to_hex(sha1));
+       prefetch(sha1);
+               
+       return 0;
+}
+
+static int loop(void)
+{
+       while (process_queue) {
+               struct object *obj = process_queue->item;
+               /*
+               fprintf(stderr, "%d objects to pull\n", 
+                       object_list_length(process_queue));
+               */
+               process_queue = process_queue->next;
+               if (!process_queue)
+                       process_queue_end = &process_queue;
+
+               //fprintf(stderr, "fetch %s\n", sha1_to_hex(obj->sha1));
+               
+               if (make_sure_we_have_it(obj->type ?: "object", 
+                                        obj->sha1))
+                       return -1;
+               if (!obj->type)
+                       parse_object(obj->sha1);
+               if (obj->type == commit_type) {
+                       if (process_commit((struct commit *)obj))
+                               return -1;
+                       continue;
+               }
+               if (obj->type == tree_type) {
+                       if (process_tree((struct tree *)obj))
+                               return -1;
+                       continue;
+               }
+               if (obj->type == blob_type) {
+                       continue;
+               }
+               if (obj->type == tag_type) {
+                       if (process_tag((struct tag *)obj))
+                               return -1;
+                       continue;
+               }
+               return error("Unable to determine requirements "
+                            "of type %s for %s",
+                            obj->type, sha1_to_hex(obj->sha1));
+       }
+       return 0;
+}
+
+static int interpret_target(char *target, unsigned char *sha1)
+{
+       if (!get_sha1_hex(target, sha1))
+               return 0;
+       if (!check_ref_format(target)) {
+               if (!fetch_ref(target, sha1)) {
+                       return 0;
+               }
+       }
+       return -1;
+}
+
+
+int pull(char *target)
+{
+       unsigned char sha1[20];
+       int fd = -1;
+
+       if (write_ref && current_ref) {
+               fd = lock_ref_sha1(write_ref, current_ref);
+               if (fd < 0)
+                       return -1;
+       }
+
+       if (interpret_target(target, sha1))
+               return error("Could not interpret %s as something to pull",
+                            target);
+       if (process(sha1, NULL))
+               return -1;
+       if (loop())
+               return -1;
+       
+       if (write_ref) {
+               if (current_ref) {
+                       write_ref_sha1(write_ref, fd, sha1);
+               } else {
+                       write_ref_sha1_unlocked(write_ref, sha1);
+               }
+       }
+       return 0;
+}
diff --git a/pull.h b/pull.h
new file mode 100644 (file)
index 0000000..e7710e8
--- /dev/null
+++ b/pull.h
@@ -0,0 +1,48 @@
+#ifndef PULL_H
+#define PULL_H
+
+/*
+ * Fetch object given SHA1 from the remote, and store it locally under
+ * GIT_OBJECT_DIRECTORY.  Return 0 on success, -1 on failure.  To be
+ * provided by the particular implementation.
+ */
+extern int fetch(unsigned char *sha1);
+
+/*
+ * Fetch the specified object and store it locally; fetch() will be
+ * called later to determine success. To be provided by the particular
+ * implementation.
+ */
+extern void prefetch(unsigned char *sha1);
+
+/*
+ * Fetch ref (relative to $GIT_DIR/refs) from the remote, and store
+ * the 20-byte SHA1 in sha1.  Return 0 on success, -1 on failure.  To
+ * be provided by the particular implementation.
+ */
+extern int fetch_ref(char *ref, unsigned char *sha1);
+
+/* If set, the ref filename to write the target value to. */
+extern const char *write_ref;
+
+/* If set, the hash that the current value of write_ref must be. */
+extern const unsigned char *current_ref;
+
+/* Set to fetch the target tree. */
+extern int get_tree;
+
+/* Set to fetch the commit history. */
+extern int get_history;
+
+/* Set to fetch the trees in the commit history. */
+extern int get_all;
+
+/* Set to be verbose */
+extern int get_verbosely;
+
+/* Report what we got under get_verbosely */
+extern void pull_say(const char *, const char *);
+
+extern int pull(char *target);
+
+#endif /* PULL_H */
diff --git a/quote.c b/quote.c
new file mode 100644 (file)
index 0000000..5e6fda3
--- /dev/null
+++ b/quote.c
@@ -0,0 +1,41 @@
+#include "cache.h"
+#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.
+ *
+ * E.g.
+ *  original     sq_quote     result
+ *  name     ==> name      ==> 'name'
+ *  a b      ==> a b       ==> 'a b'
+ *  a'b      ==> a'\''b    ==> 'a'\''b'
+ */
+char *sq_quote(const char *src)
+{
+       static char *buf = NULL;
+       int cnt, c;
+       const char *cp;
+       char *bp;
+
+       /* count bytes needed to store the quoted string. */
+       for (cnt = 3, cp = src; *cp; cnt++, cp++)
+               if (*cp == '\'')
+                       cnt += 3;
+
+       buf = xmalloc(cnt);
+       bp = buf;
+       *bp++ = '\'';
+       while ((c = *src++)) {
+               if (c != '\'')
+                       *bp++ = c;
+               else {
+                       bp = strcpy(bp, "'\\''");
+                       bp += 4;
+               }
+       }
+       *bp++ = '\'';
+       *bp = 0;
+       return buf;
+}
+
diff --git a/quote.h b/quote.h
new file mode 100644 (file)
index 0000000..c8cfb3a
--- /dev/null
+++ b/quote.h
@@ -0,0 +1,26 @@
+#ifndef QUOTE_H
+#define QUOTE_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.
+ *
+ * For example, if you are passing the result to system() as an
+ * argument:
+ *
+ * sprintf(cmd, "foobar %s %s", sq_quote(arg0), sq_quote(arg1))
+ *
+ * would be appropriate.  If the system() is going to call ssh to
+ * run the command on the other side:
+ *
+ * sprintf(cmd, "git-diff-tree %s %s", sq_quote(arg0), sq_quote(arg1));
+ * sprintf(rcmd, "ssh %s %s", sq_quote(host), sq_quote(cmd));
+ *
+ * Note that the above examples leak memory!  Remember to free result from
+ * sq_quote() in a real application.
+ */
+
+char *sq_quote(const char *src);
+
+#endif
diff --git a/read-cache.c b/read-cache.c
new file mode 100644 (file)
index 0000000..5820f18
--- /dev/null
@@ -0,0 +1,497 @@
+/*
+ * GIT - The information manager from hell
+ *
+ * Copyright (C) Linus Torvalds, 2005
+ */
+#include "cache.h"
+
+struct cache_entry **active_cache = NULL;
+unsigned int active_nr = 0, active_alloc = 0, active_cache_changed = 0;
+
+/*
+ * This only updates the "non-critical" parts of the directory
+ * cache, ie the parts that aren't tracked by GIT, and only used
+ * to validate the cache.
+ */
+void fill_stat_cache_info(struct cache_entry *ce, struct stat *st)
+{
+       ce->ce_ctime.sec = htonl(st->st_ctime);
+       ce->ce_mtime.sec = htonl(st->st_mtime);
+#ifdef USE_NSEC
+       ce->ce_ctime.nsec = htonl(st->st_ctim.tv_nsec);
+       ce->ce_mtime.nsec = htonl(st->st_mtim.tv_nsec);
+#endif
+       ce->ce_dev = htonl(st->st_dev);
+       ce->ce_ino = htonl(st->st_ino);
+       ce->ce_uid = htonl(st->st_uid);
+       ce->ce_gid = htonl(st->st_gid);
+       ce->ce_size = htonl(st->st_size);
+}
+
+int ce_match_stat(struct cache_entry *ce, struct stat *st)
+{
+       unsigned int changed = 0;
+
+       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))
+                       changed |= MODE_CHANGED;
+               break;
+       case S_IFLNK:
+               changed |= !S_ISLNK(st->st_mode) ? TYPE_CHANGED : 0;
+               break;
+       default:
+               die("internal error: ce_mode is %o", ntohl(ce->ce_mode));
+       }
+       if (ce->ce_mtime.sec != htonl(st->st_mtime))
+               changed |= MTIME_CHANGED;
+       if (ce->ce_ctime.sec != htonl(st->st_ctime))
+               changed |= CTIME_CHANGED;
+
+#ifdef USE_NSEC
+       /*
+        * nsec seems unreliable - not all filesystems support it, so
+        * as long as it is in the inode cache you get right nsec
+        * but after it gets flushed, you get zero nsec.
+        */
+       if (ce->ce_mtime.nsec != htonl(st->st_mtim.tv_nsec))
+               changed |= MTIME_CHANGED;
+       if (ce->ce_ctime.nsec != htonl(st->st_ctim.tv_nsec))
+               changed |= CTIME_CHANGED;
+#endif 
+
+       if (ce->ce_uid != htonl(st->st_uid) ||
+           ce->ce_gid != htonl(st->st_gid))
+               changed |= OWNER_CHANGED;
+       if (ce->ce_ino != htonl(st->st_ino))
+               changed |= INODE_CHANGED;
+
+#ifdef USE_STDEV
+       /*
+        * st_dev breaks on network filesystems where different
+        * clients will have different views of what "device"
+        * the filesystem is on
+        */
+       if (ce->ce_dev != htonl(st->st_dev))
+               changed |= INODE_CHANGED;
+#endif
+
+       if (ce->ce_size != htonl(st->st_size))
+               changed |= DATA_CHANGED;
+       return changed;
+}
+
+int base_name_compare(const char *name1, int len1, int mode1,
+                     const char *name2, int len2, int mode2)
+{
+       unsigned char c1, c2;
+       int len = len1 < len2 ? len1 : len2;
+       int cmp;
+
+       cmp = memcmp(name1, name2, len);
+       if (cmp)
+               return cmp;
+       c1 = name1[len];
+       c2 = name2[len];
+       if (!c1 && S_ISDIR(mode1))
+               c1 = '/';
+       if (!c2 && S_ISDIR(mode2))
+               c2 = '/';
+       return (c1 < c2) ? -1 : (c1 > c2) ? 1 : 0;
+}
+
+int cache_name_compare(const char *name1, int flags1, const char *name2, int flags2)
+{
+       int len1 = flags1 & CE_NAMEMASK;
+       int len2 = flags2 & CE_NAMEMASK;
+       int len = len1 < len2 ? len1 : len2;
+       int cmp;
+
+       cmp = memcmp(name1, name2, len);
+       if (cmp)
+               return cmp;
+       if (len1 < len2)
+               return -1;
+       if (len1 > len2)
+               return 1;
+       if (flags1 < flags2)
+               return -1;
+       if (flags1 > flags2)
+               return 1;
+       return 0;
+}
+
+int cache_name_pos(const char *name, int namelen)
+{
+       int first, last;
+
+       first = 0;
+       last = active_nr;
+       while (last > first) {
+               int next = (last + first) >> 1;
+               struct cache_entry *ce = active_cache[next];
+               int cmp = cache_name_compare(name, namelen, ce->name, ntohs(ce->ce_flags));
+               if (!cmp)
+                       return next;
+               if (cmp < 0) {
+                       last = next;
+                       continue;
+               }
+               first = next+1;
+       }
+       return -first-1;
+}
+
+/* Remove entry, return true if there are more entries to go.. */
+int remove_cache_entry_at(int pos)
+{
+       active_cache_changed = 1;
+       active_nr--;
+       if (pos >= active_nr)
+               return 0;
+       memmove(active_cache + pos, active_cache + pos + 1, (active_nr - pos) * sizeof(struct cache_entry *));
+       return 1;
+}
+
+int remove_file_from_cache(char *path)
+{
+       int pos = cache_name_pos(path, strlen(path));
+       if (pos < 0)
+               pos = -pos-1;
+       while (pos < active_nr && !strcmp(active_cache[pos]->name, path))
+               remove_cache_entry_at(pos);
+       return 0;
+}
+
+int ce_same_name(struct cache_entry *a, struct cache_entry *b)
+{
+       int len = ce_namelen(a);
+       return ce_namelen(b) == len && !memcmp(a->name, b->name, len);
+}
+
+int ce_path_match(const struct cache_entry *ce, const char **pathspec)
+{
+       const char *match, *name;
+       int len;
+
+       if (!pathspec)
+               return 1;
+
+       len = ce_namelen(ce);
+       name = ce->name;
+       while ((match = *pathspec++) != NULL) {
+               int matchlen = strlen(match);
+               if (matchlen > len)
+                       continue;
+               if (memcmp(name, match, matchlen))
+                       continue;
+               if (matchlen && name[matchlen-1] == '/')
+                       return 1;
+               if (name[matchlen] == '/' || !name[matchlen])
+                       return 1;
+       }
+       return 0;
+}
+
+/*
+ * Do we have another file that has the beginning components being a
+ * proper superset of the name we're trying to add?
+ */
+static int has_file_name(const struct cache_entry *ce, int pos, int ok_to_replace)
+{
+       int retval = 0;
+       int len = ce_namelen(ce);
+       int stage = ce_stage(ce);
+       const char *name = ce->name;
+
+       while (pos < active_nr) {
+               struct cache_entry *p = active_cache[pos++];
+
+               if (len >= ce_namelen(p))
+                       break;
+               if (memcmp(name, p->name, len))
+                       break;
+               if (ce_stage(p) != stage)
+                       continue;
+               if (p->name[len] != '/')
+                       continue;
+               retval = -1;
+               if (!ok_to_replace)
+                       break;
+               remove_cache_entry_at(--pos);
+       }
+       return retval;
+}
+
+/*
+ * Do we have another file with a pathname that is a proper
+ * subset of the name we're trying to add?
+ */
+static int has_dir_name(const struct cache_entry *ce, int pos, int ok_to_replace)
+{
+       int retval = 0;
+       int stage = ce_stage(ce);
+       const char *name = ce->name;
+       const char *slash = name + ce_namelen(ce);
+
+       for (;;) {
+               int len;
+
+               for (;;) {
+                       if (*--slash == '/')
+                               break;
+                       if (slash <= ce->name)
+                               return retval;
+               }
+               len = slash - name;
+
+               pos = cache_name_pos(name, ntohs(create_ce_flags(len, stage)));
+               if (pos >= 0) {
+                       retval = -1;
+                       if (ok_to_replace)
+                               break;
+                       remove_cache_entry_at(pos);
+                       continue;
+               }
+
+               /*
+                * Trivial optimization: if we find an entry that
+                * already matches the sub-directory, then we know
+                * we're ok, and we can exit.
+                */
+               pos = -pos-1;
+               while (pos < active_nr) {
+                       struct cache_entry *p = active_cache[pos];
+                       if ((ce_namelen(p) <= len) ||
+                           (p->name[len] != '/') ||
+                           memcmp(p->name, name, len))
+                               break; /* not our subdirectory */
+                       if (ce_stage(p) == stage)
+                               /* p is at the same stage as our entry, and
+                                * is a subdirectory of what we are looking
+                                * at, so we cannot have conflicts at our
+                                * level or anything shorter.
+                                */
+                               return retval;
+                       pos++;
+               }
+       }
+       return retval;
+}
+
+/* We may be in a situation where we already have path/file and path
+ * is being added, or we already have path and path/file is being
+ * added.  Either one would result in a nonsense tree that has path
+ * twice when git-write-tree tries to write it out.  Prevent it.
+ * 
+ * If ok-to-replace is specified, we remove the conflicting entries
+ * from the cache so the caller should recompute the insert position.
+ * When this happens, we return non-zero.
+ */
+static int check_file_directory_conflict(const struct cache_entry *ce, int pos, int ok_to_replace)
+{
+       /*
+        * We check if the path is a sub-path of a subsequent pathname
+        * first, since removing those will not change the position
+        * in the array
+        */
+       int retval = has_file_name(ce, pos, ok_to_replace);
+       /*
+        * Then check if the path might have a clashing sub-directory
+        * before it.
+        */
+       return retval + has_dir_name(ce, pos, ok_to_replace);
+}
+
+int add_cache_entry(struct cache_entry *ce, int option)
+{
+       int pos;
+       int ok_to_add = option & ADD_CACHE_OK_TO_ADD;
+       int ok_to_replace = option & ADD_CACHE_OK_TO_REPLACE;
+       int skip_df_check = option & ADD_CACHE_SKIP_DFCHECK;
+       pos = cache_name_pos(ce->name, ntohs(ce->ce_flags));
+
+       /* existing match? Just replace it */
+       if (pos >= 0) {
+               active_cache_changed = 1;
+               active_cache[pos] = ce;
+               return 0;
+       }
+       pos = -pos-1;
+
+       /*
+        * Inserting a merged entry ("stage 0") into the index
+        * will always replace all non-merged entries..
+        */
+       if (pos < active_nr && ce_stage(ce) == 0) {
+               while (ce_same_name(active_cache[pos], ce)) {
+                       ok_to_add = 1;
+                       if (!remove_cache_entry_at(pos))
+                               break;
+               }
+       }
+
+       if (!ok_to_add)
+               return -1;
+
+       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));
+               pos = -pos-1;
+       }
+
+       /* Make sure the array is big enough .. */
+       if (active_nr == active_alloc) {
+               active_alloc = alloc_nr(active_alloc);
+               active_cache = xrealloc(active_cache, active_alloc * sizeof(struct cache_entry *));
+       }
+
+       /* Add it in.. */
+       active_nr++;
+       if (active_nr > pos)
+               memmove(active_cache + pos + 1, active_cache + pos, (active_nr - pos - 1) * sizeof(ce));
+       active_cache[pos] = ce;
+       active_cache_changed = 1;
+       return 0;
+}
+
+static int verify_hdr(struct cache_header *hdr, unsigned long size)
+{
+       SHA_CTX c;
+       unsigned char sha1[20];
+
+       if (hdr->hdr_signature != htonl(CACHE_SIGNATURE))
+               return error("bad signature");
+       if (hdr->hdr_version != htonl(2))
+               return error("bad index version");
+       SHA1_Init(&c);
+       SHA1_Update(&c, hdr, size - 20);
+       SHA1_Final(sha1, &c);
+       if (memcmp(sha1, (void *)hdr + size - 20, 20))
+               return error("bad index file sha1 signature");
+       return 0;
+}
+
+int read_cache(void)
+{
+       int fd, i;
+       struct stat st;
+       unsigned long size, offset;
+       void *map;
+       struct cache_header *hdr;
+
+       errno = EBUSY;
+       if (active_cache)
+               return error("more than one cachefile");
+       errno = ENOENT;
+       fd = open(get_index_file(), O_RDONLY);
+       if (fd < 0)
+               return (errno == ENOENT) ? 0 : error("open failed");
+
+       size = 0; // avoid gcc warning
+       map = MAP_FAILED;
+       if (!fstat(fd, &st)) {
+               size = st.st_size;
+               errno = EINVAL;
+               if (size >= sizeof(struct cache_header) + 20)
+                       map = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);
+       }
+       close(fd);
+       if (map == MAP_FAILED)
+               return error("mmap failed");
+
+       hdr = map;
+       if (verify_hdr(hdr, size) < 0)
+               goto unmap;
+
+       active_nr = ntohl(hdr->hdr_entries);
+       active_alloc = alloc_nr(active_nr);
+       active_cache = calloc(active_alloc, sizeof(struct cache_entry *));
+
+       offset = sizeof(*hdr);
+       for (i = 0; i < active_nr; i++) {
+               struct cache_entry *ce = map + offset;
+               offset = offset + ce_size(ce);
+               active_cache[i] = ce;
+       }
+       return active_nr;
+
+unmap:
+       munmap(map, size);
+       errno = EINVAL;
+       return error("verify header failed");
+}
+
+#define WRITE_BUFFER_SIZE 8192
+static unsigned char write_buffer[WRITE_BUFFER_SIZE];
+static unsigned long write_buffer_len;
+
+static int ce_write(SHA_CTX *context, int fd, void *data, unsigned int len)
+{
+       while (len) {
+               unsigned int buffered = write_buffer_len;
+               unsigned int partial = WRITE_BUFFER_SIZE - buffered;
+               if (partial > len)
+                       partial = len;
+               memcpy(write_buffer + buffered, data, partial);
+               buffered += partial;
+               if (buffered == WRITE_BUFFER_SIZE) {
+                       SHA1_Update(context, write_buffer, WRITE_BUFFER_SIZE);
+                       if (write(fd, write_buffer, WRITE_BUFFER_SIZE) != WRITE_BUFFER_SIZE)
+                               return -1;
+                       buffered = 0;
+               }
+               write_buffer_len = buffered;
+               len -= partial;
+               data += partial;
+       }
+       return 0;
+}
+
+static int ce_flush(SHA_CTX *context, int fd)
+{
+       unsigned int left = write_buffer_len;
+
+       if (left) {
+               write_buffer_len = 0;
+               SHA1_Update(context, write_buffer, left);
+       }
+
+       /* Append the SHA1 signature at the end */
+       SHA1_Final(write_buffer + left, context);
+       left += 20;
+       if (write(fd, write_buffer, left) != left)
+               return -1;
+       return 0;
+}
+
+int write_cache(int newfd, struct cache_entry **cache, int entries)
+{
+       SHA_CTX c;
+       struct cache_header hdr;
+       int i, removed;
+
+       for (i = removed = 0; i < entries; i++)
+               if (!cache[i]->ce_mode)
+                       removed++;
+
+       hdr.hdr_signature = htonl(CACHE_SIGNATURE);
+       hdr.hdr_version = htonl(2);
+       hdr.hdr_entries = htonl(entries - removed);
+
+       SHA1_Init(&c);
+       if (ce_write(&c, newfd, &hdr, sizeof(hdr)) < 0)
+               return -1;
+
+       for (i = 0; i < entries; i++) {
+               struct cache_entry *ce = cache[i];
+               if (!ce->ce_mode)
+                       continue;
+               if (ce_write(&c, newfd, ce, ce_size(ce)) < 0)
+                       return -1;
+       }
+       return ce_flush(&c, newfd);
+}
diff --git a/read-tree.c b/read-tree.c
new file mode 100644 (file)
index 0000000..ce2bb88
--- /dev/null
@@ -0,0 +1,603 @@
+/*
+ * GIT - The information manager from hell
+ *
+ * Copyright (C) Linus Torvalds, 2005
+ */
+#include "cache.h"
+
+static int stage = 0;
+static int update = 0;
+
+static int unpack_tree(unsigned char *sha1)
+{
+       void *buffer;
+       unsigned long size;
+       int ret;
+
+       buffer = read_object_with_reference(sha1, "tree", &size, NULL);
+       if (!buffer)
+               return -1;
+       ret = read_tree(buffer, size, stage, NULL);
+       free(buffer);
+       return ret;
+}
+
+static int path_matches(struct cache_entry *a, struct cache_entry *b)
+{
+       int len = ce_namelen(a);
+       return ce_namelen(b) == len &&
+               !memcmp(a->name, b->name, len);
+}
+
+static int same(struct cache_entry *a, struct cache_entry *b)
+{
+       return a->ce_mode == b->ce_mode && 
+               !memcmp(a->sha1, b->sha1, 20);
+}
+
+
+/*
+ * This removes all trivial merges that don't change the tree
+ * and collapses them to state 0.
+ */
+static struct cache_entry *merge_entries(struct cache_entry *a,
+                                        struct cache_entry *b,
+                                        struct cache_entry *c)
+{
+       /*
+        * Ok, all three entries describe the same
+        * filename, but maybe the contents or file
+        * mode have changed?
+        *
+        * The trivial cases end up being the ones where two
+        * out of three files are the same:
+        *  - both destinations the same, trivially take either
+        *  - one of the destination versions hasn't changed,
+        *    take the other.
+        *
+        * The "all entries exactly the same" case falls out as
+        * a special case of any of the "two same" cases.
+        *
+        * Here "a" is "original", and "b" and "c" are the two
+        * trees we are merging.
+        */
+       if (a && b && c) {
+               if (same(b,c))
+                       return c;
+               if (same(a,b))
+                       return c;
+               if (same(a,c))
+                       return b;
+       }
+       return NULL;
+}
+
+/*
+ * When a CE gets turned into an unmerged entry, we
+ * want it to be up-to-date
+ */
+static void verify_uptodate(struct cache_entry *ce)
+{
+       struct stat st;
+
+       if (!lstat(ce->name, &st)) {
+               unsigned changed = ce_match_stat(ce, &st);
+               if (!changed)
+                       return;
+               errno = 0;
+       }
+       if (errno == ENOENT)
+               return;
+       die("Entry '%s' not uptodate. Cannot merge.", ce->name);
+}
+
+/*
+ * If the old tree contained a CE that isn't even in the
+ * result, that's always a problem, regardless of whether
+ * it's up-to-date or not (ie it can be a file that we
+ * have updated but not committed yet).
+ */
+static void reject_merge(struct cache_entry *ce)
+{
+       die("Entry '%s' would be overwritten by merge. Cannot merge.", ce->name);
+}
+
+static int merged_entry_internal(struct cache_entry *merge, struct cache_entry *old, struct cache_entry **dst, int allow_dirty)
+{
+       merge->ce_flags |= htons(CE_UPDATE);
+       if (old) {
+               /*
+                * See if we can re-use the old CE directly?
+                * That way we get the uptodate stat info.
+                *
+                * This also removes the UPDATE flag on
+                * a match.
+                */
+               if (same(old, merge)) {
+                       *merge = *old;
+               } else if (!allow_dirty) {
+                       verify_uptodate(old);
+               }
+       }
+       merge->ce_flags &= ~htons(CE_STAGEMASK);
+       *dst++ = merge;
+       return 1;
+}
+
+static int merged_entry_allow_dirty(struct cache_entry *merge, struct cache_entry *old, struct cache_entry **dst)
+{
+       return merged_entry_internal(merge, old, dst, 1);
+}
+
+static int merged_entry(struct cache_entry *merge, struct cache_entry *old, struct cache_entry **dst)
+{
+       return merged_entry_internal(merge, old, dst, 0);
+}
+
+static int deleted_entry(struct cache_entry *ce, struct cache_entry *old, struct cache_entry **dst)
+{
+       if (old)
+               verify_uptodate(old);
+       ce->ce_mode = 0;
+       *dst++ = ce;
+       return 1;
+}
+
+static int causes_df_conflict(struct cache_entry *ce, int stage,
+                             struct cache_entry **dst_,
+                             struct cache_entry **next_,
+                             int tail)
+{
+       /* This is called during the merge operation and walking
+        * the active_cache[] array is messy, because it is in the
+        * middle of overlapping copy operation.  The invariants
+        * are:
+        * (1) active_cache points at the first (zeroth) entry.
+        * (2) up to dst pointer are resolved entries.
+        * (3) from the next pointer (head-inclusive) to the tail
+        *     of the active_cache array have the remaining paths
+        *     to be processed.  There can be a gap between dst
+        *     and next.  Note that next is called "src" in the
+        *     merge_cache() function, and tail is the original
+        *     end of active_cache array when merge_cache() started.
+        * (4) the path corresponding to *ce is not found in (2)
+        *     or (3).  It is in the gap.
+        *
+        *  active_cache -----......+++++++++++++.
+        *                    ^dst  ^next        ^tail
+        */
+       int i, next, dst;
+       const char *path = ce->name;
+       int namelen = ce_namelen(ce);
+
+       next = next_ - active_cache;
+       dst = dst_ - active_cache;
+
+       for (i = 0; i < tail; i++) {
+               int entlen, len;
+               const char *one, *two;
+               if (dst <= i && i < next)
+                       continue;
+               ce = active_cache[i];
+               if (ce_stage(ce) != stage)
+                       continue;
+               /* If ce->name is a prefix of path, then path is a file
+                * that hangs underneath ce->name, which is bad.
+                * If path is a prefix of ce->name, then it is the
+                * other way around which also is bad.
+                */
+               entlen = ce_namelen(ce);
+               if (namelen == entlen)
+                       continue;
+               if (namelen < entlen) {
+                       len = namelen;
+                       one = path;
+                       two = ce->name;
+               } else {
+                       len = entlen;
+                       one = ce->name;
+                       two = path;
+               }
+               if (memcmp(one, two, len))
+                       continue;
+               if (two[len] == '/')
+                       return 1;
+       }
+       return 0;
+}
+
+static int threeway_merge(struct cache_entry *stages[4],
+                         struct cache_entry **dst,
+                         struct cache_entry **next, int tail)
+{
+       struct cache_entry *old = stages[0];
+       struct cache_entry *a = stages[1], *b = stages[2], *c = stages[3];
+       struct cache_entry *merge;
+       int count;
+
+       /* #5ALT */
+       if (!a && b && c && same(b, c)) {
+               if (old && !same(b, old))
+                       return -1;
+               return merged_entry_allow_dirty(b, old, dst);
+       }
+       /* #2ALT and #3ALT */
+       if (!a && (!!b != !!c)) {
+               /*
+                * The reason we need to worry about directory/file
+                * conflicts only in #2ALT and #3ALT case is this:
+                *
+                * (1) For all other cases that read-tree internally
+                *     resolves a path, we always have such a path in
+                *     *both* stage2 and stage3 when we begin.
+                *     Traditionally, the behaviour has been even
+                *     stricter and we did not resolve a path without
+                *     initially being in all of stage1, 2, and 3.
+                *
+                * (2) When read-tree finishes, all resolved paths (i.e.
+                *     the paths that are in stage0) must have come from
+                *     either stage2 or stage3.  It is not possible to
+                *     have a stage0 path as a result of a merge if
+                *     neither stage2 nor stage3 had that path.
+                *
+                * (3) It is guaranteed that just after reading the
+                *     stages, each stage cannot have directory/file
+                *     conflicts on its own, because they are populated
+                *     by reading hierarchy of a tree.  Combined with
+                *     (1) and (2) above, this means that no matter what
+                *     combination of paths we take from stage2 and
+                *     stage3 as a result of a merge, they cannot cause
+                *     a directory/file conflict situation (otherwise
+                *     the "guilty" path would have already had such a
+                *     conflict in the original stage, either stage2
+                *     or stage3).  Although its stage2 is synthesized
+                *     by overlaying the current index on top of "our
+                *     head" tree, --emu23 case also has this guarantee,
+                *     by calling add_cache_entry() to create such stage2
+                *     entries.
+                *
+                * (4) Only #2ALT and #3ALT lack the guarantee (1).
+                *     They resolve paths that exist only in stage2
+                *     or stage3.  The stage2 tree may have a file DF
+                *     while stage3 tree may have a file DF/DF.  If
+                *     #2ALT and #3ALT rules happen to apply to both
+                *     of them, we would end up having DF (coming from
+                *     stage2) and DF/DF (from stage3) in the result.
+                *     When we attempt to resolve a path that exists
+                *     only in stage2, we need to make sure there is
+                *     no path that would conflict with it in stage3
+                *     and vice versa.
+                */
+               if (c) { /* #2ALT */
+                       if (!causes_df_conflict(c, 2, dst, next, tail) &&
+                           (!old || same(c, old)))
+                               return merged_entry_allow_dirty(c, old, dst);
+               }
+               else { /* #3ALT */
+                       if (!causes_df_conflict(b, 3, dst, next, tail) &&
+                           (!old || same(b, old)))
+                               return merged_entry_allow_dirty(b, old, dst);
+               }
+               /* otherwise we will apply the original rule */
+       }
+       /* #14ALT */
+       if (a && b && c && same(a, b) && !same(a, c)) {
+               if (old && same(old, c))
+                       return merged_entry_allow_dirty(c, old, dst);
+               /* otherwise the regular rule applies */
+       }
+       /*
+        * If we have an entry in the index cache ("old"), then we want
+        * to make sure that it matches any entries in stage 2 ("first
+        * branch", aka "b").
+        */
+       if (old) {
+               if (!b || !same(old, b))
+                       return -1;
+       }
+       merge = merge_entries(a, b, c);
+       if (merge)
+               return merged_entry(merge, old, dst);
+       if (old)
+               verify_uptodate(old);
+       count = 0;
+       if (a) { *dst++ = a; count++; }
+       if (b) { *dst++ = b; count++; }
+       if (c) { *dst++ = c; count++; }
+       return count;
+}
+
+/*
+ * Two-way merge.
+ *
+ * The rule is to "carry forward" what is in the index without losing
+ * information across a "fast forward", favoring a successful merge
+ * over a merge failure when it makes sense.  For details of the
+ * "carry forward" rule, please see <Documentation/git-read-tree.txt>.
+ *
+ */
+static int twoway_merge(struct cache_entry **src, struct cache_entry **dst,
+                       struct cache_entry **next, int tail)
+{
+       struct cache_entry *current = src[0];
+       struct cache_entry *oldtree = src[1], *newtree = src[2];
+
+       if (src[3])
+               return -1;
+
+       if (current) {
+               if ((!oldtree && !newtree) || /* 4 and 5 */
+                   (!oldtree && newtree &&
+                    same(current, newtree)) || /* 6 and 7 */
+                   (oldtree && newtree &&
+                    same(oldtree, newtree)) || /* 14 and 15 */
+                   (oldtree && newtree &&
+                    !same(oldtree, newtree) && /* 18 and 19*/
+                    same(current, newtree))) {
+                       *dst++ = current;
+                       return 1;
+               }
+               else if (oldtree && !newtree && same(current, oldtree)) {
+                       /* 10 or 11 */
+                       return deleted_entry(oldtree, current, dst);
+               }
+               else if (oldtree && newtree &&
+                        same(current, oldtree) && !same(current, newtree)) {
+                       /* 20 or 21 */
+                       return merged_entry(newtree, current, dst);
+               }
+               else
+                       /* all other failures */
+                       return -1;
+       }
+       else if (newtree)
+               return merged_entry(newtree, current, dst);
+       else
+               return deleted_entry(oldtree, current, dst);
+}
+
+/*
+ * Two-way merge emulated with three-way merge.
+ *
+ * This treats "read-tree -m H M" by transforming it internally
+ * into "read-tree -m H I+H M", where I+H is a tree that would
+ * contain the contents of the current index file, overlayed on
+ * top of H.  Unlike the traditional two-way merge, this leaves
+ * the stages in the resulting index file and lets the user resolve
+ * the merge conflicts using standard tools for three-way merge.
+ *
+ * This function is just to set-up such an arrangement, and the
+ * actual merge uses threeway_merge() function.
+ */
+static void setup_emu23(void)
+{
+       /* stage0 contains I, stage1 H, stage2 M.
+        * move stage2 to stage3, and create stage2 entries
+        * by scanning stage0 and stage1 entries.
+        */
+       int i, namelen, size;
+       struct cache_entry *ce, *stage2;
+
+       for (i = 0; i < active_nr; i++) {
+               ce = active_cache[i];
+               if (ce_stage(ce) != 2)
+                       continue;
+               /* hoist them up to stage 3 */
+               namelen = ce_namelen(ce);
+               ce->ce_flags = create_ce_flags(namelen, 3);
+       }
+
+       for (i = 0; i < active_nr; i++) {
+               ce = active_cache[i];
+               if (ce_stage(ce) > 1)
+                       continue;
+               namelen = ce_namelen(ce);
+               size = cache_entry_size(namelen);
+               stage2 = xmalloc(size);
+               memcpy(stage2, ce, size);
+               stage2->ce_flags = create_ce_flags(namelen, 2);
+               if (add_cache_entry(stage2, ADD_CACHE_OK_TO_ADD) < 0)
+                       die("cannot merge index and our head tree");
+
+               /* We are done with this name, so skip to next name */
+               while (i < active_nr &&
+                      ce_namelen(active_cache[i]) == namelen &&
+                      !memcmp(active_cache[i]->name, ce->name, namelen))
+                       i++;
+               i--; /* compensate for the loop control */
+       }
+}
+
+/*
+ * One-way merge.
+ *
+ * The rule is:
+ * - take the stat information from stage0, take the data from stage1
+ */
+static int oneway_merge(struct cache_entry **src, struct cache_entry **dst,
+                       struct cache_entry **next, int tail)
+{
+       struct cache_entry *old = src[0];
+       struct cache_entry *a = src[1];
+
+       if (src[2] || src[3])
+               return -1;
+
+       if (!a)
+               return 0;
+       if (old && same(old, a)) {
+               *dst++ = old;
+               return 1;
+       }
+       return merged_entry(a, NULL, dst);
+}
+
+static void check_updates(struct cache_entry **src, int nr)
+{
+       static struct checkout state = {
+               .base_dir = "",
+               .force = 1,
+               .quiet = 1,
+               .refresh_cache = 1,
+       };
+       unsigned short mask = htons(CE_UPDATE);
+       while (nr--) {
+               struct cache_entry *ce = *src++;
+               if (!ce->ce_mode) {
+                       if (update)
+                               unlink(ce->name);
+                       continue;
+               }
+               if (ce->ce_flags & mask) {
+                       ce->ce_flags &= ~mask;
+                       if (update)
+                               checkout_entry(ce, &state);
+               }
+       }
+}
+
+typedef int (*merge_fn_t)(struct cache_entry **, struct cache_entry **, struct cache_entry **, int);
+
+static void merge_cache(struct cache_entry **src, int nr, merge_fn_t fn)
+{
+       struct cache_entry **dst = src;
+       int tail = nr;
+
+       while (nr) {
+               int entries;
+               struct cache_entry *name, *ce, *stages[4] = { NULL, };
+
+               name = ce = *src;
+               for (;;) {
+                       int stage = ce_stage(ce);
+                       stages[stage] = ce;
+                       ce = *++src;
+                       active_nr--;
+                       if (!--nr)
+                               break;
+                       if (!path_matches(ce, name))
+                               break;
+               }
+
+               entries = fn(stages, dst, src, tail);
+               if (entries < 0)
+                       reject_merge(name);
+               dst += entries;
+               active_nr += entries;
+       }
+       check_updates(active_cache, active_nr);
+}
+
+static int read_cache_unmerged(void)
+{
+       int i, deleted;
+       struct cache_entry **dst;
+
+       read_cache();
+       dst = active_cache;
+       deleted = 0;
+       for (i = 0; i < active_nr; i++) {
+               struct cache_entry *ce = active_cache[i];
+               if (ce_stage(ce)) {
+                       deleted++;
+                       continue;
+               }
+               if (deleted)
+                       *dst = ce;
+               dst++;
+       }
+       active_nr -= deleted;
+       return deleted;
+}
+
+static const char read_tree_usage[] = "git-read-tree (<sha> | -m [-u] <sha1> [<sha2> [<sha3>]])";
+
+static struct cache_file cache_file;
+
+int main(int argc, char **argv)
+{
+       int i, newfd, merge, reset, emu23;
+       unsigned char sha1[20];
+
+       newfd = hold_index_file_for_update(&cache_file, get_index_file());
+       if (newfd < 0)
+               die("unable to create new cachefile");
+
+       merge = 0;
+       reset = 0;
+       emu23 = 0;
+       for (i = 1; i < argc; i++) {
+               const char *arg = argv[i];
+
+               /* "-u" means "update", meaning that a merge will update the working directory */
+               if (!strcmp(arg, "-u")) {
+                       update = 1;
+                       continue;
+               }
+
+               /* This differs from "-m" in that we'll silently ignore unmerged entries */
+               if (!strcmp(arg, "--reset")) {
+                       if (stage || merge || emu23)
+                               usage(read_tree_usage);
+                       reset = 1;
+                       merge = 1;
+                       stage = 1;
+                       read_cache_unmerged();
+                       continue;
+               }
+
+               /* "-m" stands for "merge", meaning we start in stage 1 */
+               if (!strcmp(arg, "-m")) {
+                       if (stage || merge || emu23)
+                               usage(read_tree_usage);
+                       if (read_cache_unmerged())
+                               die("you need to resolve your current index first");
+                       stage = 1;
+                       merge = 1;
+                       continue;
+               }
+
+               /* "-emu23" uses 3-way merge logic to perform fast-forward */
+               if (!strcmp(arg, "--emu23")) {
+                       if (stage || merge || emu23)
+                               usage(read_tree_usage);
+                       if (read_cache_unmerged())
+                               die("you need to resolve your current index first");
+                       merge = emu23 = stage = 1;
+                       continue;
+               }
+
+               if (get_sha1(arg, sha1) < 0)
+                       usage(read_tree_usage);
+               if (stage > 3)
+                       usage(read_tree_usage);
+               if (unpack_tree(sha1) < 0)
+                       die("failed to unpack tree object %s", arg);
+               stage++;
+       }
+       if (update && !merge)
+               usage(read_tree_usage);
+       if (merge) {
+               static const merge_fn_t merge_function[] = {
+                       [1] = oneway_merge,
+                       [2] = twoway_merge,
+                       [3] = threeway_merge,
+               };
+               merge_fn_t fn;
+
+               if (stage < 2 || stage > 4)
+                       die("just how do you expect me to merge %d trees?", stage-1);
+               if (emu23 && stage != 3)
+                       die("--emu23 takes only two trees");
+               fn = merge_function[stage-1];
+               if (stage == 3 && emu23) { 
+                       setup_emu23();
+                       fn = merge_function[3];
+               }
+               merge_cache(active_cache, active_nr, fn);
+       }
+       if (write_cache(newfd, active_cache, active_nr) ||
+           commit_index_file(&cache_file))
+               die("unable to write new index file");
+       return 0;
+}
diff --git a/receive-pack.c b/receive-pack.c
new file mode 100644 (file)
index 0000000..9026f1b
--- /dev/null
@@ -0,0 +1,287 @@
+#include "cache.h"
+#include "refs.h"
+#include "pkt-line.h"
+#include "run-command.h"
+#include <sys/wait.h>
+
+static const char receive_pack_usage[] = "git-receive-pack <git-dir>";
+
+static const char unpacker[] = "git-unpack-objects";
+
+static int show_ref(const char *path, const unsigned char *sha1)
+{
+       packet_write(1, "%s %s\n", sha1_to_hex(sha1), path);
+       return 0;
+}
+
+static void write_head_info(void)
+{
+       for_each_ref(show_ref);
+}
+
+struct command {
+       struct command *next;
+       unsigned char updated;
+       unsigned char old_sha1[20];
+       unsigned char new_sha1[20];
+       char ref_name[0];
+};
+
+static struct command *commands = NULL;
+
+static int is_all_zeroes(const char *hex)
+{
+       int i;
+       for (i = 0; i < 40; i++)
+               if (*hex++ != '0')
+                       return 0;
+       return 1;
+}
+
+static int verify_old_ref(const char *name, char *hex_contents)
+{
+       int fd, ret;
+       char buffer[60];
+
+       if (is_all_zeroes(hex_contents))
+               return 0;
+       fd = open(name, O_RDONLY);
+       if (fd < 0)
+               return -1;
+       ret = read(fd, buffer, 40);
+       close(fd);
+       if (ret != 40)
+               return -1;
+       if (memcmp(buffer, hex_contents, 40))
+               return -1;
+       return 0;
+}
+
+static char update_hook[] = "hooks/update";
+
+static int run_update_hook(const char *refname,
+                          char *old_hex, char *new_hex)
+{
+       int code;
+
+       if (access(update_hook, X_OK) < 0)
+               return 0;
+       code = run_command(update_hook, refname, old_hex, new_hex, NULL);
+       switch (code) {
+       case 0:
+               return 0;
+       case -ERR_RUN_COMMAND_FORK:
+               die("hook fork failed");
+       case -ERR_RUN_COMMAND_EXEC:
+               die("hook execute failed");
+       case -ERR_RUN_COMMAND_WAITPID:
+               die("waitpid failed");
+       case -ERR_RUN_COMMAND_WAITPID_WRONG_PID:
+               die("waitpid is confused");
+       case -ERR_RUN_COMMAND_WAITPID_SIGNAL:
+               fprintf(stderr, "%s died of signal", update_hook);
+               return -1;
+       case -ERR_RUN_COMMAND_WAITPID_NOEXIT:
+               die("%s died strangely", update_hook);
+       default:
+               error("%s exited with error code %d", update_hook, -code);
+               return -code;
+       }
+}
+
+static int update(const char *name,
+                 unsigned char *old_sha1, unsigned char *new_sha1)
+{
+       char new_hex[60], *old_hex, *lock_name;
+       int newfd, namelen, written;
+
+       namelen = strlen(name);
+       lock_name = xmalloc(namelen + 10);
+       memcpy(lock_name, name, namelen);
+       memcpy(lock_name + namelen, ".lock", 6);
+
+       strcpy(new_hex, sha1_to_hex(new_sha1));
+       old_hex = sha1_to_hex(old_sha1);
+       if (!has_sha1_file(new_sha1))
+               return error("unpack should have generated %s, "
+                            "but I can't find it!", new_hex);
+
+       safe_create_leading_directories(lock_name);
+
+       newfd = open(lock_name, O_CREAT | O_EXCL | O_WRONLY, 0666);
+       if (newfd < 0)
+               return error("unable to create %s (%s)",
+                            lock_name, strerror(errno));
+
+       /* Write the ref with an ending '\n' */
+       new_hex[40] = '\n';
+       new_hex[41] = 0;
+       written = write(newfd, new_hex, 41);
+       /* Remove the '\n' again */
+       new_hex[40] = 0;
+
+       close(newfd);
+       if (written != 41) {
+               unlink(lock_name);
+               return error("unable to write %s", lock_name);
+       }
+       if (verify_old_ref(name, old_hex) < 0) {
+               unlink(lock_name);
+               return error("%s changed during push", name);
+       }
+       if (run_update_hook(name, old_hex, new_hex)) {
+               unlink(lock_name);
+               return error("hook declined to update %s\n", name);
+       }
+       else if (rename(lock_name, name) < 0) {
+               unlink(lock_name);
+               return error("unable to replace %s", name);
+       }
+       else {
+               fprintf(stderr, "%s: %s -> %s\n", name, old_hex, new_hex);
+               return 0;
+       }
+}
+
+static char update_post_hook[] = "hooks/post-update";
+
+static void run_update_post_hook(struct command *cmd)
+{
+       struct command *cmd_p;
+       int argc;
+       char **argv;
+
+       if (access(update_post_hook, X_OK) < 0)
+               return;
+       for (argc = 1, cmd_p = cmd; cmd_p; cmd_p = cmd_p->next) {
+               if (!cmd_p->updated)
+                       continue;
+               argc++;
+       }
+       argv = xmalloc(sizeof(*argv) * (1 + argc));
+       argv[0] = update_post_hook;
+
+       for (argc = 1, cmd_p = cmd; cmd_p; cmd_p = cmd_p->next) {
+               if (!cmd_p->updated)
+                       continue;
+               argv[argc] = xmalloc(strlen(cmd_p->ref_name) + 1);
+               strcpy(argv[argc], cmd_p->ref_name);
+               argc++;
+       }
+       argv[argc] = NULL;
+       run_command_v(argc, argv);
+}
+
+/*
+ * This gets called after(if) we've successfully
+ * unpacked the data payload.
+ */
+static void execute_commands(void)
+{
+       struct command *cmd = commands;
+
+       while (cmd) {
+               cmd->updated = !update(cmd->ref_name,
+                                      cmd->old_sha1, cmd->new_sha1);
+               cmd = cmd->next;
+       }
+       run_update_post_hook(commands);
+}
+
+static void read_head_info(void)
+{
+       struct command **p = &commands;
+       for (;;) {
+               static char line[1000];
+               unsigned char old_sha1[20], new_sha1[20];
+               struct command *cmd;
+               int len;
+
+               len = packet_read_line(0, line, sizeof(line));
+               if (!len)
+                       break;
+               if (line[len-1] == '\n')
+                       line[--len] = 0;
+               if (len < 83 ||
+                   line[40] != ' ' ||
+                   line[81] != ' ' ||
+                   get_sha1_hex(line, old_sha1) ||
+                   get_sha1_hex(line + 41, new_sha1))
+                       die("protocol error: expected old/new/ref, got '%s'", line);
+               cmd = xmalloc(sizeof(struct command) + len - 80);
+               memcpy(cmd->old_sha1, old_sha1, 20);
+               memcpy(cmd->new_sha1, new_sha1, 20);
+               memcpy(cmd->ref_name, line + 82, len - 81);
+               cmd->next = NULL;
+               *p = cmd;
+               p = &cmd->next;
+       }
+}
+
+static void unpack(void)
+{
+       int code = run_command(unpacker, NULL);
+       switch (code) {
+       case 0:
+               return;
+       case -ERR_RUN_COMMAND_FORK:
+               die("unpack fork failed");
+       case -ERR_RUN_COMMAND_EXEC:
+               die("unpack execute failed");
+       case -ERR_RUN_COMMAND_WAITPID:
+               die("waitpid failed");
+       case -ERR_RUN_COMMAND_WAITPID_WRONG_PID:
+               die("waitpid is confused");
+       case -ERR_RUN_COMMAND_WAITPID_SIGNAL:
+               die("%s died of signal", unpacker);
+       case -ERR_RUN_COMMAND_WAITPID_NOEXIT:
+               die("%s died strangely", unpacker);
+       default:
+               die("%s exited with error code %d", unpacker, -code);
+       }
+}
+
+int main(int argc, char **argv)
+{
+       int i;
+       const char *dir = NULL;
+
+       argv++;
+       for (i = 1; i < argc; i++) {
+               const char *arg = *argv++;
+
+               if (*arg == '-') {
+                       /* Do flag handling here */
+                       usage(receive_pack_usage);
+               }
+               if (dir)
+                       usage(receive_pack_usage);
+               dir = arg;
+       }
+       if (!dir)
+               usage(receive_pack_usage);
+
+       /* chdir to the directory. If that fails, try appending ".git" */
+       if (chdir(dir) < 0) {
+               if (chdir(mkpath("%s.git", dir)) < 0)
+                       die("unable to cd to %s", dir);
+       }
+
+       /* If we have a ".git" directory, chdir to it */
+       chdir(".git");
+       setenv("GIT_DIR", ".", 1);
+
+       if (access("objects", X_OK) < 0 || access("refs/heads", X_OK) < 0)
+               die("%s doesn't appear to be a git directory", dir);
+       write_head_info();
+
+       /* EOF */
+       packet_flush(1);
+
+       read_head_info();
+       if (commands) {
+               unpack();
+               execute_commands();
+       }
+       return 0;
+}
diff --git a/refs.c b/refs.c
new file mode 100644 (file)
index 0000000..a5c8946
--- /dev/null
+++ b/refs.c
@@ -0,0 +1,251 @@
+#include "refs.h"
+#include "cache.h"
+
+#include <errno.h>
+
+static int read_ref(const char *refname, unsigned char *sha1)
+{
+       int ret = -1;
+       int fd = open(git_path("%s", refname), O_RDONLY);
+
+       if (fd >= 0) {
+               char buffer[60];
+               if (read(fd, buffer, sizeof(buffer)) >= 40)
+                       ret = get_sha1_hex(buffer, sha1);
+               close(fd);
+       }
+       return ret;
+}
+
+static int do_for_each_ref(const char *base, int (*fn)(const char *path, const unsigned char *sha1))
+{
+       int retval = 0;
+       DIR *dir = opendir(git_path("%s", base));
+
+       if (dir) {
+               struct dirent *de;
+               int baselen = strlen(base);
+               char *path = xmalloc(baselen + 257);
+
+               if (!strncmp(base, "./", 2)) {
+                       base += 2;
+                       baselen -= 2;
+               }
+               memcpy(path, base, baselen);
+               if (baselen && base[baselen-1] != '/')
+                       path[baselen++] = '/';
+
+               while ((de = readdir(dir)) != NULL) {
+                       unsigned char sha1[20];
+                       struct stat st;
+                       int namelen;
+
+                       if (de->d_name[0] == '.')
+                               continue;
+                       namelen = strlen(de->d_name);
+                       if (namelen > 255)
+                               continue;
+                       memcpy(path + baselen, de->d_name, namelen+1);
+                       if (lstat(git_path("%s", path), &st) < 0)
+                               continue;
+                       if (S_ISDIR(st.st_mode)) {
+                               retval = do_for_each_ref(path, fn);
+                               if (retval)
+                                       break;
+                               continue;
+                       }
+                       if (read_ref(path, sha1) < 0)
+                               continue;
+                       if (!has_sha1_file(sha1))
+                               continue;
+                       retval = fn(path, sha1);
+                       if (retval)
+                               break;
+               }
+               free(path);
+               closedir(dir);
+       }
+       return retval;
+}
+
+int head_ref(int (*fn)(const char *path, const unsigned char *sha1))
+{
+       unsigned char sha1[20];
+       if (!read_ref("HEAD", sha1))
+               return fn("HEAD", sha1);
+       return 0;
+}
+
+int for_each_ref(int (*fn)(const char *path, const unsigned char *sha1))
+{
+       return do_for_each_ref("refs", fn);
+}
+
+static char *ref_file_name(const char *ref)
+{
+       char *base = get_refs_directory();
+       int baselen = strlen(base);
+       int reflen = strlen(ref);
+       char *ret = xmalloc(baselen + 2 + reflen);
+       sprintf(ret, "%s/%s", base, ref);
+       return ret;
+}
+
+static char *ref_lock_file_name(const char *ref)
+{
+       char *base = get_refs_directory();
+       int baselen = strlen(base);
+       int reflen = strlen(ref);
+       char *ret = xmalloc(baselen + 7 + reflen);
+       sprintf(ret, "%s/%s.lock", base, 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;
+       if (check_ref_format(ref))
+               return -1;
+       filename = ref_file_name(ref);
+       retval = read_ref_file(filename, sha1);
+       free(filename);
+       return retval;
+}
+
+static int lock_ref_file(const char *filename, const char *lock_filename,
+                        const unsigned char *old_sha1)
+{
+       int fd = open(lock_filename, O_WRONLY | O_CREAT | O_EXCL, 0666);
+       unsigned char current_sha1[20];
+       int retval;
+       if (fd < 0) {
+               return error("Couldn't open lock file for %s: %s",
+                            filename, strerror(errno));
+       }
+       retval = read_ref_file(filename, current_sha1);
+       if (old_sha1) {
+               if (retval) {
+                       close(fd);
+                       unlink(lock_filename);
+                       return error("Could not read the current value of %s",
+                                    filename);
+               }
+               if (memcmp(current_sha1, old_sha1, 20)) {
+                       close(fd);
+                       unlink(lock_filename);
+                       error("The current value of %s is %s",
+                             filename, sha1_to_hex(current_sha1));
+                       return error("Expected %s",
+                                    sha1_to_hex(old_sha1));
+               }
+       } else {
+               if (!retval) {
+                       close(fd);
+                       unlink(lock_filename);
+                       return error("Unexpectedly found a value of %s for %s",
+                                    sha1_to_hex(current_sha1), filename);
+               }
+       }
+       return fd;
+}
+
+int lock_ref_sha1(const char *ref, const unsigned char *old_sha1)
+{
+       char *filename;
+       char *lock_filename;
+       int retval;
+       if (check_ref_format(ref))
+               return -1;
+       filename = ref_file_name(ref);
+       lock_filename = ref_lock_file_name(ref);
+       retval = lock_ref_file(filename, lock_filename, old_sha1);
+       free(filename);
+       free(lock_filename);
+       return retval;
+}
+
+static int write_ref_file(const char *filename,
+                         const char *lock_filename, int fd,
+                         const unsigned char *sha1)
+{
+       char *hex = sha1_to_hex(sha1);
+       char term = '\n';
+       if (write(fd, hex, 40) < 40 ||
+           write(fd, &term, 1) < 1) {
+               error("Couldn't write %s\n", filename);
+               close(fd);
+               return -1;
+       }
+       close(fd);
+       rename(lock_filename, filename);
+       return 0;
+}
+
+int write_ref_sha1(const char *ref, int fd, const unsigned char *sha1)
+{
+       char *filename;
+       char *lock_filename;
+       int retval;
+       if (fd < 0)
+               return -1;
+       if (check_ref_format(ref))
+               return -1;
+       filename = ref_file_name(ref);
+       lock_filename = ref_lock_file_name(ref);
+       retval = write_ref_file(filename, lock_filename, fd, sha1);
+       free(filename);
+       free(lock_filename);
+       return retval;
+}
+
+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 write_ref_sha1_unlocked(const char *ref, const unsigned char *sha1)
+{
+       char *filename;
+       char *lock_filename;
+       int fd;
+       int retval;
+       if (check_ref_format(ref))
+               return -1;
+       filename = ref_file_name(ref);
+       lock_filename = ref_lock_file_name(ref);
+       fd = open(lock_filename, O_WRONLY | O_CREAT | O_EXCL, 0666);
+       if (fd < 0) {
+               error("Writing %s", lock_filename);
+               perror("Open");
+       }
+       retval = write_ref_file(filename, lock_filename, fd, sha1);
+       free(filename);
+       free(lock_filename);
+       return retval;
+}
diff --git a/refs.h b/refs.h
new file mode 100644 (file)
index 0000000..2625596
--- /dev/null
+++ b/refs.h
@@ -0,0 +1,28 @@
+#ifndef REFS_H
+#define REFS_H
+
+/*
+ * Calls the specified function for each ref file until it returns nonzero,
+ * and returns the value
+ */
+extern int head_ref(int (*fn)(const char *path, const unsigned char *sha1));
+extern int for_each_ref(int (*fn)(const char *path, const unsigned char *sha1));
+
+/** Reads the refs file specified into sha1 **/
+extern int get_ref_sha1(const char *ref, unsigned char *sha1);
+
+/** Locks ref and returns the fd to give to write_ref_sha1() if the ref
+ * has the given value currently; otherwise, returns -1.
+ **/
+extern int lock_ref_sha1(const char *ref, const unsigned char *old_sha1);
+
+/** Writes sha1 into the refs file specified, locked with the given fd. **/
+extern int write_ref_sha1(const char *ref, int fd, const unsigned char *sha1);
+
+/** Writes sha1 into the refs file specified. **/
+extern int write_ref_sha1_unlocked(const char *ref, const unsigned char *sha1);
+
+/** Returns 0 if target has the right format for a ref. **/
+extern int check_ref_format(const char *target);
+
+#endif /* REFS_H */
diff --git a/rev-cache.c b/rev-cache.c
new file mode 100644 (file)
index 0000000..6344d62
--- /dev/null
@@ -0,0 +1,318 @@
+#include "refs.h"
+#include "cache.h"
+#include "rev-cache.h"
+
+struct rev_cache **rev_cache;
+int nr_revs, alloc_revs;
+
+static struct rev_list_elem *rle_free;
+
+#define BATCH_SIZE 512
+
+int find_rev_cache(const unsigned char *sha1)
+{
+       int lo = 0, hi = nr_revs;
+       while (lo < hi) {
+               int mi = (lo + hi) / 2;
+               struct rev_cache *ri = rev_cache[mi];
+               int cmp = memcmp(sha1, ri->sha1, 20);
+               if (!cmp)
+                       return mi;
+               if (cmp < 0)
+                       hi = mi;
+               else
+                       lo = mi + 1;
+       }
+       return -lo - 1;
+}
+
+static struct rev_list_elem *alloc_list_elem(void)
+{
+       struct rev_list_elem *rle;
+       if (!rle_free) {
+               int i;
+
+               rle = xmalloc(sizeof(*rle) * BATCH_SIZE);
+               for (i = 0; i < BATCH_SIZE - 1; i++) {
+                       rle[i].ri = NULL;
+                       rle[i].next = &rle[i + 1];
+               }
+               rle[BATCH_SIZE - 1].ri = NULL;
+               rle[BATCH_SIZE - 1].next = NULL;
+               rle_free = rle;
+       }
+       rle = rle_free;
+       rle_free = rle->next;
+       return rle;
+}
+
+static struct rev_cache *create_rev_cache(const unsigned char *sha1)
+{
+       struct rev_cache *ri;
+       int pos = find_rev_cache(sha1);
+
+       if (0 <= pos)
+               return rev_cache[pos];
+       pos = -pos - 1;
+       if (alloc_revs <= ++nr_revs) {
+               alloc_revs = alloc_nr(alloc_revs);
+               rev_cache = xrealloc(rev_cache, sizeof(ri) * alloc_revs);
+       }
+       if (pos < nr_revs)
+               memmove(rev_cache + pos + 1, rev_cache + pos,
+                       (nr_revs - pos - 1) * sizeof(ri));
+       ri = xcalloc(1, sizeof(*ri));
+       memcpy(ri->sha1, sha1, 20);
+       rev_cache[pos] = ri;
+       return ri;
+}
+
+static unsigned char last_sha1[20];
+
+static void write_one_rev_cache(FILE *rev_cache_file, struct rev_cache *ri)
+{
+       unsigned char flag;
+       struct rev_list_elem *rle;
+
+       if (ri->written)
+               return;
+
+       if (ri->parsed) {
+               /* We use last_sha1 compression only for the first parent;
+                * otherwise the resulting rev-cache would lose the parent
+                * order information.
+                */
+               if (ri->parents &&
+                   !memcmp(ri->parents->ri->sha1, last_sha1, 20))
+                       flag = (ri->num_parents - 1) | 0x80;
+               else
+                       flag = ri->num_parents;
+
+               fwrite(ri->sha1, 20, 1, rev_cache_file);
+               fwrite(&flag, 1, 1, rev_cache_file);
+               for (rle = ri->parents; rle; rle = rle->next) {
+                       if (flag & 0x80 && rle == ri->parents)
+                               continue;
+                       fwrite(rle->ri->sha1, 20, 1, rev_cache_file);
+               }
+               memcpy(last_sha1, ri->sha1, 20);
+               ri->written = 1;
+       }
+       /* recursively write children depth first */
+       for (rle = ri->children; rle; rle = rle->next)
+               write_one_rev_cache(rev_cache_file, rle->ri);
+}
+
+void write_rev_cache(const char *newpath, const char *oldpath)
+{
+       /* write the following commit ancestry information in
+        * $GIT_DIR/info/rev-cache.
+        *
+        * The format is:
+        * 20-byte SHA1 (commit ID)
+        * 1-byte flag:
+        * - bit 0-6 records "number of parent commit SHA1s to
+        *   follow" (i.e. up to 127 children can be listed).
+        * - when the bit 7 is on, then "the entry immediately
+        *   before this entry is one of the parents of this
+         *   commit".
+        * N x 20-byte SHA1 (parent commit IDs)
+        */
+       FILE *rev_cache_file;
+       int i;
+       struct rev_cache *ri;
+
+       if (!strcmp(newpath, oldpath)) {
+               /* If we are doing it in place */
+               rev_cache_file = fopen(newpath, "a");
+       }
+       else {
+               char buf[8096];
+               size_t sz;
+               FILE *oldfp = fopen(oldpath, "r");
+               rev_cache_file = fopen(newpath, "w");
+               if (oldfp) {
+                       while (1) {
+                               sz = fread(buf, 1, sizeof(buf), oldfp);
+                               if (sz == 0)
+                                       break;
+                               fwrite(buf, 1, sz, rev_cache_file);
+                       }
+                       fclose(oldfp);
+               }
+       }
+
+       memset(last_sha1, 0, 20);
+
+       /* Go through available rev_cache structures, starting from
+        * parentless ones first, so that we would get most out of
+        * last_sha1 optimization by the depth first behaviour of
+        * write_one_rev_cache().
+        */
+       for (i = 0; i < nr_revs; i++) {
+               ri = rev_cache[i];
+               if (ri->num_parents)
+                       continue;
+               write_one_rev_cache(rev_cache_file, ri);
+       }
+       /* Then the rest */
+       for (i = 0; i < nr_revs; i++) {
+               ri = rev_cache[i];
+               write_one_rev_cache(rev_cache_file, ri);
+       }
+       fclose(rev_cache_file);
+}
+
+static void add_parent(struct rev_cache *child,
+                      const unsigned char *parent_sha1)
+{
+       struct rev_cache *parent = create_rev_cache(parent_sha1);
+       struct rev_list_elem *e = alloc_list_elem();
+
+       /* Keep the parent list ordered in the same way the commit
+        * object records them.
+        */
+       e->ri = parent;
+       e->next = NULL;
+       if (!child->parents_tail)
+               child->parents = e;
+       else
+               child->parents_tail->next = e;
+       child->parents_tail = e;
+       child->num_parents++;
+
+       /* There is no inherent order of the children so we just
+        * LIFO them together.
+        */
+       e = alloc_list_elem();
+       e->next = parent->children;
+       parent->children = e;
+       e->ri = child;
+       parent->num_children++;
+}
+
+int read_rev_cache(const char *path, FILE *dumpfile, int dry_run)
+{
+       unsigned char *map;
+       int fd;
+       struct stat st;
+       unsigned long ofs, len;
+       struct rev_cache *ri = NULL;
+
+       fd = open(path, O_RDONLY);
+       if (fd < 0) {
+               if (dry_run)
+                       return error("cannot open %s", path);
+               if (errno == ENOENT)
+                       return 0;
+               return -1;
+       }
+       if (fstat(fd, &st)) {
+               close(fd);
+               return -1;
+       }
+       map = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
+       close(fd);
+       if (map == MAP_FAILED)
+               return -1;
+
+       memset(last_sha1, 0, 20);
+       ofs = 0;
+       len = st.st_size;
+       while (ofs < len) {
+               unsigned char sha1[20];
+               int flag, cnt, i;
+               if (len < ofs + 21)
+                       die("rev-cache too short");
+               memcpy(sha1, map + ofs, 20);
+               flag = map[ofs + 20];
+               ofs += 21;
+               cnt = (flag & 0x7f) + ((flag & 0x80) != 0);
+               if (len < ofs + (flag & 0x7f) * 20)
+                       die("rev-cache too short to have %d more parents",
+                           (flag & 0x7f));
+               if (dumpfile)
+                       fprintf(dumpfile, "%s", sha1_to_hex(sha1));
+               if (!dry_run) {
+                       ri = create_rev_cache(sha1);
+                       if (!ri)
+                               die("cannot create rev-cache for %s",
+                                   sha1_to_hex(sha1));
+                       ri->written = ri->parsed = 1;
+               }
+               i = 0;
+               if (flag & 0x80) {
+                       if (!dry_run)
+                               add_parent(ri, last_sha1);
+                       if (dumpfile)
+                               fprintf(dumpfile, " %s",
+                                       sha1_to_hex(last_sha1));
+                       i++;
+               }
+               while (i++ < cnt) {
+                       if (!dry_run)
+                               add_parent(ri, map + ofs);
+                       if (dumpfile)
+                               fprintf(dumpfile, " %s",
+                                       sha1_to_hex(last_sha1));
+                       ofs += 20;
+               }
+               if (dumpfile)
+                       fprintf(dumpfile, "\n");
+               memcpy(last_sha1, sha1, 20);
+       }
+       if (ofs != len)
+               die("rev-cache truncated?");
+       munmap(map, len);
+       return 0;
+}
+
+int record_rev_cache(const unsigned char *sha1, FILE *dumpfile)
+{
+       unsigned char parent[20];
+       char type[20];
+       unsigned long size, ofs;
+       unsigned int cnt, i;
+       void *buf;
+       struct rev_cache *ri;
+
+       buf = read_sha1_file(sha1, type, &size);
+       if (!buf)
+               return error("%s: not found", sha1_to_hex(sha1));
+       if (strcmp(type, "commit")) {
+               free(buf);
+               return error("%s: not a commit but a %s",
+                            sha1_to_hex(sha1), type);
+       }
+       ri = create_rev_cache(sha1);
+       if (ri->parsed)
+               return 0;
+       if (dumpfile)
+               fprintf(dumpfile, "commit %s\n", sha1_to_hex(sha1));
+
+       cnt = 0;
+       ofs = 46; /* "tree " + hex-sha1 + "\n" */
+       while (!memcmp(buf + ofs, "parent ", 7) &&
+              !get_sha1_hex(buf + ofs + 7, parent)) {
+               ofs += 48;
+               cnt++;
+       }
+       if (cnt * 48 + 46 != ofs) {
+               free(buf);
+               die("internal error in record_rev_cache");
+       }
+
+       ri = create_rev_cache(sha1);
+       ri->parsed = 1;
+
+       for (i = 0; i < cnt; i++) {
+               unsigned char parent_sha1[20];
+
+               ofs = 46 + i * 48 + 7;
+               get_sha1_hex(buf + ofs, parent_sha1);
+               add_parent(ri, parent_sha1);
+               record_rev_cache(parent_sha1, dumpfile);
+       }
+       free(buf);
+       return 0;
+}
diff --git a/rev-cache.h b/rev-cache.h
new file mode 100644 (file)
index 0000000..b238ac6
--- /dev/null
@@ -0,0 +1,29 @@
+#ifndef REV_CACHE_H
+#define REV_CACHE_H
+
+extern struct rev_cache {
+       struct rev_cache *head_list;
+       struct rev_list_elem *children;
+       struct rev_list_elem *parents;
+       struct rev_list_elem *parents_tail;
+       unsigned short num_parents;
+       unsigned short num_children;
+       unsigned int written : 1;
+       unsigned int parsed : 1;
+       unsigned int work : 30;
+       void *work_ptr;
+       unsigned char sha1[20];
+} **rev_cache;
+extern int nr_revs, alloc_revs;
+
+struct rev_list_elem {
+       struct rev_list_elem *next;
+       struct rev_cache *ri;
+};
+
+extern int find_rev_cache(const unsigned char *);
+extern int read_rev_cache(const char *, FILE *, int);
+extern int record_rev_cache(const unsigned char *, FILE *);
+extern void write_rev_cache(const char *new, const char *old);
+
+#endif
diff --git a/rev-list.c b/rev-list.c
new file mode 100644 (file)
index 0000000..ce5b8b2
--- /dev/null
@@ -0,0 +1,600 @@
+#include "cache.h"
+#include "tag.h"
+#include "commit.h"
+#include "tree.h"
+#include "blob.h"
+#include "epoch.h"
+
+#define SEEN           (1u << 0)
+#define INTERESTING    (1u << 1)
+#define COUNTED                (1u << 2)
+#define SHOWN          (1u << 3)
+
+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";
+
+static int unpacked = 0;
+static int bisect_list = 0;
+static int tag_objects = 0;
+static int tree_objects = 0;
+static int blob_objects = 0;
+static int verbose_header = 0;
+static int show_parents = 0;
+static int hdr_termination = 0;
+static const char *prefix = "";
+static unsigned long max_age = -1;
+static unsigned long min_age = -1;
+static int max_count = -1;
+static enum cmit_fmt commit_format = CMIT_FMT_RAW;
+static int merge_order = 0;
+static int show_breaks = 0;
+static int stop_traversal = 0;
+static int topo_order = 0;
+static int no_merges = 0;
+
+static void show_commit(struct commit *commit)
+{
+       commit->object.flags |= SHOWN;
+       if (show_breaks) {
+               prefix = "| ";
+               if (commit->object.flags & DISCONTINUITY) {
+                       prefix = "^ ";     
+               } else if (commit->object.flags & BOUNDARY) {
+                       prefix = "= ";
+               } 
+        }                      
+       printf("%s%s", prefix, sha1_to_hex(commit->object.sha1));
+       if (show_parents) {
+               struct commit_list *parents = commit->parents;
+               while (parents) {
+                       printf(" %s", sha1_to_hex(parents->item->object.sha1));
+                       parents = parents->next;
+               }
+       }
+       if (commit_format == CMIT_FMT_ONELINE)
+               putchar(' ');
+       else
+               putchar('\n');
+
+       if (verbose_header) {
+               static char pretty_header[16384];
+               pretty_print_commit(commit_format, commit->buffer, ~0, pretty_header, sizeof(pretty_header));
+               printf("%s%c", pretty_header, hdr_termination);
+       }
+       fflush(stdout);
+}
+
+static int filter_commit(struct commit * commit)
+{
+       if (stop_traversal && (commit->object.flags & BOUNDARY))
+               return STOP;
+       if (commit->object.flags & (UNINTERESTING|SHOWN))
+               return CONTINUE;
+       if (min_age != -1 && (commit->date > min_age))
+               return CONTINUE;
+       if (max_age != -1 && (commit->date < max_age)) {
+               stop_traversal=1;
+               return merge_order?CONTINUE:STOP;
+       }
+       if (max_count != -1 && !max_count--)
+               return STOP;
+       if (no_merges && (commit->parents && commit->parents->next))
+               return CONTINUE;
+       return DO;
+}
+
+static int process_commit(struct commit * commit)
+{
+       int action=filter_commit(commit);
+
+       if (action == STOP) {
+               return STOP;
+       }
+
+       if (action == CONTINUE) {
+               return CONTINUE;
+       }
+
+       show_commit(commit);
+
+       return CONTINUE;
+}
+
+static struct object_list **add_object(struct object *obj, struct object_list **p, const char *name)
+{
+       struct object_list *entry = xmalloc(sizeof(*entry));
+       entry->item = obj;
+       entry->next = *p;
+       entry->name = name;
+       *p = entry;
+       return &entry->next;
+}
+
+static struct object_list **process_blob(struct blob *blob, struct object_list **p, const char *name)
+{
+       struct object *obj = &blob->object;
+
+       if (!blob_objects)
+               return p;
+       if (obj->flags & (UNINTERESTING | SEEN))
+               return p;
+       obj->flags |= SEEN;
+       return add_object(obj, p, name);
+}
+
+static struct object_list **process_tree(struct tree *tree, struct object_list **p, const char *name)
+{
+       struct object *obj = &tree->object;
+       struct tree_entry_list *entry;
+
+       if (!tree_objects)
+               return p;
+       if (obj->flags & (UNINTERESTING | SEEN))
+               return p;
+       if (parse_tree(tree) < 0)
+               die("bad tree object %s", sha1_to_hex(obj->sha1));
+       obj->flags |= SEEN;
+       p = add_object(obj, p, name);
+       for (entry = tree->entries ; entry ; entry = entry->next) {
+               if (entry->directory)
+                       p = process_tree(entry->item.tree, p, entry->name);
+               else
+                       p = process_blob(entry->item.blob, p, entry->name);
+       }
+       return p;
+}
+
+static struct object_list *pending_objects = NULL;
+
+static void show_commit_list(struct commit_list *list)
+{
+       struct object_list *objects = NULL, **p = &objects, *pending;
+       while (list) {
+               struct commit *commit = pop_most_recent_commit(&list, SEEN);
+
+               p = process_tree(commit->tree, p, "");
+               if (process_commit(commit) == STOP)
+                       break;
+       }
+       for (pending = pending_objects; pending; pending = pending->next) {
+               struct object *obj = pending->item;
+               const char *name = pending->name;
+               if (obj->flags & (UNINTERESTING | SEEN))
+                       continue;
+               if (obj->type == tag_type) {
+                       obj->flags |= SEEN;
+                       p = add_object(obj, p, name);
+                       continue;
+               }
+               if (obj->type == tree_type) {
+                       p = process_tree((struct tree *)obj, p, name);
+                       continue;
+               }
+               if (obj->type == blob_type) {
+                       p = process_blob((struct blob *)obj, p, name);
+                       continue;
+               }
+               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);
+               objects = objects->next;
+       }
+}
+
+static void mark_blob_uninteresting(struct blob *blob)
+{
+       if (!blob_objects)
+               return;
+       if (blob->object.flags & UNINTERESTING)
+               return;
+       blob->object.flags |= UNINTERESTING;
+}
+
+static void mark_tree_uninteresting(struct tree *tree)
+{
+       struct object *obj = &tree->object;
+       struct tree_entry_list *entry;
+
+       if (!tree_objects)
+               return;
+       if (obj->flags & UNINTERESTING)
+               return;
+       obj->flags |= UNINTERESTING;
+       if (!has_sha1_file(obj->sha1))
+               return;
+       if (parse_tree(tree) < 0)
+               die("bad tree %s", sha1_to_hex(obj->sha1));
+       entry = tree->entries;
+       while (entry) {
+               if (entry->directory)
+                       mark_tree_uninteresting(entry->item.tree);
+               else
+                       mark_blob_uninteresting(entry->item.blob);
+               entry = entry->next;
+       }
+}
+
+static void mark_parents_uninteresting(struct commit *commit)
+{
+       struct commit_list *parents = commit->parents;
+
+       if (tree_objects)
+               mark_tree_uninteresting(commit->tree);
+       while (parents) {
+               struct commit *commit = parents->item;
+               commit->object.flags |= UNINTERESTING;
+
+               /*
+                * 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->parents)
+                       mark_parents_uninteresting(commit);
+
+               /*
+                * A missing commit is ok iff its parent is marked 
+                * uninteresting.
+                *
+                * We just mark such a thing parsed, so that when
+                * it is popped next time around, we won't be trying
+                * to parse it and get an error.
+                */
+               if (!has_sha1_file(commit->object.sha1))
+                       commit->object.parsed = 1;
+               parents = parents->next;
+       }
+}
+
+static int everybody_uninteresting(struct commit_list *orig)
+{
+       struct commit_list *list = orig;
+       while (list) {
+               struct commit *commit = list->item;
+               list = list->next;
+               if (commit->object.flags & UNINTERESTING)
+                       continue;
+               return 0;
+       }
+
+       /*
+        * Ok, go back and mark all the edge trees uninteresting,
+        * since otherwise we can have situations where a parent
+        * that was marked uninteresting (and we never even had
+        * to look at) had lots of objects that we don't want to
+        * include.
+        *
+        * NOTE! This still doesn't mean that the object list is
+        * "correct", since we may end up listing objects that
+        * even older commits (that we don't list) do actually
+        * reference, but it gets us to a minimal list (or very
+        * close) in practice.
+        */
+       if (!tree_objects)
+               return 1;
+
+       while (orig) {
+               struct commit *commit = orig->item;
+               if (!parse_commit(commit) && commit->tree)
+                       mark_tree_uninteresting(commit->tree);
+               orig = orig->next;
+       }
+       return 1;
+}
+
+/*
+ * This is a truly stupid algorithm, but it's only
+ * used for bisection, and we just don't care enough.
+ *
+ * We care just barely enough to avoid recursing for
+ * non-merge entries.
+ */
+static int count_distance(struct commit_list *entry)
+{
+       int nr = 0;
+
+       while (entry) {
+               struct commit *commit = entry->item;
+               struct commit_list *p;
+
+               if (commit->object.flags & (UNINTERESTING | COUNTED))
+                       break;
+               nr++;
+               commit->object.flags |= COUNTED;
+               p = commit->parents;
+               entry = p;
+               if (p) {
+                       p = p->next;
+                       while (p) {
+                               nr += count_distance(p);
+                               p = p->next;
+                       }
+               }
+       }
+       return nr;
+}
+
+static void clear_distance(struct commit_list *list)
+{
+       while (list) {
+               struct commit *commit = list->item;
+               commit->object.flags &= ~COUNTED;
+               list = list->next;
+       }
+}
+
+static struct commit_list *find_bisection(struct commit_list *list)
+{
+       int nr, closest;
+       struct commit_list *p, *best;
+
+       nr = 0;
+       p = list;
+       while (p) {
+               nr++;
+               p = p->next;
+       }
+       closest = 0;
+       best = list;
+
+       p = list;
+       while (p) {
+               int distance = count_distance(p);
+               clear_distance(list);
+               if (nr - distance < distance)
+                       distance = nr - distance;
+               if (distance > closest) {
+                       best = p;
+                       closest = distance;
+               }
+               p = p->next;
+       }
+       if (best)
+               best->next = NULL;
+       return best;
+}
+
+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 object *obj = &commit->object;
+
+               if (unpacked && has_sha1_pack(obj->sha1))
+                       obj->flags |= UNINTERESTING;
+               if (obj->flags & UNINTERESTING) {
+                       mark_parents_uninteresting(commit);
+                       if (everybody_uninteresting(list))
+                               break;
+                       continue;
+               }
+               p = &commit_list_insert(commit, p)->next;
+       }
+       if (bisect_list)
+               newlist = find_bisection(newlist);
+       return newlist;
+}
+
+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)
+{
+       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);
+
+       /*
+        * Tag object? Look what it points to..
+        */
+       while (object->type == tag_type) {
+               struct tag *tag = (struct tag *) object;
+               object->flags |= flags;
+               if (tag_objects && !(object->flags & UNINTERESTING))
+                       add_pending_object(object, tag->tag);
+               object = parse_object(tag->tagged->sha1);
+       }
+
+       /*
+        * Commit object? Just return it, we'll do all the complex
+        * reachability crud.
+        */
+       if (object->type == commit_type) {
+               struct commit *commit = (struct commit *)object;
+               object->flags |= flags;
+               if (parse_commit(commit) < 0)
+                       die("unable to parse commit %s", name);
+               if (flags & UNINTERESTING)
+                       mark_parents_uninteresting(commit);
+               return commit;
+       }
+
+       /*
+        * Tree object? Either mark it uniniteresting, or add it
+        * to the list of objects to look at later..
+        */
+       if (object->type == tree_type) {
+               struct tree *tree = (struct tree *)object;
+               if (!tree_objects)
+                       return NULL;
+               if (flags & UNINTERESTING) {
+                       mark_tree_uninteresting(tree);
+                       return NULL;
+               }
+               add_pending_object(object, "");
+               return NULL;
+       }
+
+       /*
+        * Blob object? You know the drill by now..
+        */
+       if (object->type == blob_type) {
+               struct blob *blob = (struct blob *)object;
+               if (!blob_objects)
+                       return NULL;
+               if (flags & UNINTERESTING) {
+                       mark_blob_uninteresting(blob);
+                       return NULL;
+               }
+               add_pending_object(object, "");
+               return NULL;
+       }
+       die("%s is unknown object", name);
+}
+
+static void handle_one_commit(struct commit *com, struct commit_list **lst)
+{
+       if (!com || com->object.flags & SEEN)
+               return;
+       com->object.flags |= SEEN;
+       commit_list_insert(com, lst);
+}
+
+
+int main(int argc, char **argv)
+{
+       struct commit_list *list = NULL;
+       int i, limited = 0;
+
+       for (i = 1 ; i < argc; i++) {
+               int flags;
+               char *arg = argv[i];
+               char *dotdot;
+               struct commit *commit;
+
+               if (!strncmp(arg, "--max-count=", 12)) {
+                       max_count = atoi(arg + 12);
+                       continue;
+               }
+               if (!strncmp(arg, "--max-age=", 10)) {
+                       max_age = atoi(arg + 10);
+                       continue;
+               }
+               if (!strncmp(arg, "--min-age=", 10)) {
+                       min_age = atoi(arg + 10);
+                       continue;
+               }
+               if (!strcmp(arg, "--header")) {
+                       verbose_header = 1;
+                       continue;
+               }
+               if (!strncmp(arg, "--pretty", 8)) {
+                       commit_format = get_commit_format(arg+8);
+                       verbose_header = 1;
+                       hdr_termination = '\n';
+                       if (commit_format == CMIT_FMT_ONELINE)
+                               prefix = "";
+                       else
+                               prefix = "commit ";
+                       continue;
+               }
+               if (!strncmp(arg, "--no-merges", 11)) {
+                       no_merges = 1;
+                       continue;
+               }
+               if (!strcmp(arg, "--parents")) {
+                       show_parents = 1;
+                       continue;
+               }
+               if (!strcmp(arg, "--bisect")) {
+                       bisect_list = 1;
+                       continue;
+               }
+               if (!strcmp(arg, "--objects")) {
+                       tag_objects = 1;
+                       tree_objects = 1;
+                       blob_objects = 1;
+                       continue;
+               }
+               if (!strcmp(arg, "--unpacked")) {
+                       unpacked = 1;
+                       limited = 1;
+                       continue;
+               }
+               if (!strcmp(arg, "--merge-order")) {
+                       merge_order = 1;
+                       continue;
+               }
+               if (!strcmp(arg, "--show-breaks")) {
+                       show_breaks = 1;
+                       continue;
+               }
+               if (!strcmp(arg, "--topo-order")) {
+                       topo_order = 1;
+                       limited = 1;
+                       continue;
+               }
+
+               if (show_breaks && !merge_order)
+                       usage(rev_list_usage);
+
+               flags = 0;
+               dotdot = strstr(arg, "..");
+               if (dotdot) {
+                       char *next = dotdot + 2;
+                       struct commit *exclude = NULL;
+                       struct commit *include = NULL;
+                       *dotdot = 0;
+                       exclude = get_commit_reference(arg, UNINTERESTING);
+                       include = get_commit_reference(next, 0);
+                       if (exclude && include) {
+                               limited = 1;
+                               handle_one_commit(exclude, &list);
+                               handle_one_commit(include, &list);
+                               continue;
+                       }
+                       *next = '.';
+               }
+               if (*arg == '^') {
+                       flags = UNINTERESTING;
+                       arg++;
+                       limited = 1;
+               }
+               commit = get_commit_reference(arg, flags);
+               handle_one_commit(commit, &list);
+       }
+
+       if (!merge_order) {             
+               sort_by_date(&list);
+               if (limited)
+                       list = limit_list(list);
+               if (topo_order)
+                       sort_in_topological_order(&list);
+               show_commit_list(list);
+       } else {
+#ifndef NO_OPENSSL
+               if (sort_list_in_merge_order(list, &process_commit)) {
+                       die("merge order sort failed\n");
+               }
+#else
+               die("merge order sort unsupported, OpenSSL not linked");
+#endif
+       }
+
+       return 0;
+}
diff --git a/rev-parse.c b/rev-parse.c
new file mode 100644 (file)
index 0000000..1c6ae76
--- /dev/null
@@ -0,0 +1,237 @@
+/*
+ * rev-parse.c
+ *
+ * Copyright (C) Linus Torvalds, 2005
+ */
+#include "cache.h"
+#include "commit.h"
+#include "refs.h"
+
+static char *def = NULL;
+static int no_revs = 0;
+static int single_rev = 0;
+static int revs_only = 0;
+static int do_rev_argument = 1;
+static int output_revs = 0;
+static int flags_only = 0;
+static int no_flags = 0;
+static int output_sq = 0;
+
+#define NORMAL 0
+#define REVERSED 1
+static int show_type = NORMAL;
+
+/*
+ * Some arguments are relevant "revision" arguments,
+ * others are about output format or other details.
+ * This sorts it all out.
+ */
+static int is_rev_argument(const char *arg)
+{
+       static const char *rev_args[] = {
+               "--max-count=",
+               "--max-age=",
+               "--min-age=",
+               "--merge-order",
+               "--topo-order",
+               "--bisect",
+               "--no-merges",
+               NULL
+       };
+       const char **p = rev_args;
+
+       for (;;) {
+               const char *str = *p++;
+               int len;
+               if (!str)
+                       return 0;
+               len = strlen(str);
+               if (!strncmp(arg, str, len))
+                       return 1;
+       }
+}
+
+static void show(const char *arg)
+{
+       if (output_sq) {
+               int sq = '\'', ch;
+
+               putchar(sq);
+               while ((ch = *arg++)) {
+                       if (ch == sq)
+                               fputs("'\\'", stdout);
+                       putchar(ch);
+               }
+               putchar(sq);
+               putchar(' ');
+       }
+       else
+               puts(arg);
+}
+
+static void show_rev(int type, const unsigned char *sha1)
+{
+       if (no_revs)
+               return;
+       output_revs++;
+
+       /* Hexadecimal string plus possibly a carret;
+        * this does not have to be quoted even under output_sq.
+        */
+       printf("%s%s%c", type == show_type ? "" : "^", sha1_to_hex(sha1),
+              output_sq ? ' ' : '\n');
+}
+
+static void show_rev_arg(char *rev)
+{
+       if (no_revs)
+               return;
+       show(rev);
+}
+
+static void show_norev(char *norev)
+{
+       if (flags_only)
+               return;
+       if (revs_only)
+               return;
+       show(norev);
+}
+
+static void show_arg(char *arg)
+{
+       if (no_flags)
+               return;
+       if (do_rev_argument && is_rev_argument(arg))
+               show_rev_arg(arg);
+       else
+               show_norev(arg);
+}
+
+static void show_default(void)
+{
+       char *s = def;
+
+       if (s) {
+               unsigned char sha1[20];
+
+               def = NULL;
+               if (!get_sha1(s, sha1)) {
+                       show_rev(NORMAL, sha1);
+                       return;
+               }
+               show_arg(s);
+       }
+}
+
+static int show_reference(const char *refname, const unsigned char *sha1)
+{
+       show_rev(NORMAL, sha1);
+       return 0;
+}
+
+int main(int argc, char **argv)
+{
+       int i, as_is = 0;
+       unsigned char sha1[20];
+
+       for (i = 1; i < argc; i++) {
+               char *arg = argv[i];
+               char *dotdot;
+       
+               if (as_is) {
+                       show_norev(arg);
+                       continue;
+               }
+               if (*arg == '-') {
+                       if (!strcmp(arg, "--")) {
+                               show_default();
+                               if (revs_only)
+                                       break;
+                               as_is = 1;
+                       }
+                       if (!strcmp(arg, "--default")) {
+                               def = argv[i+1];
+                               i++;
+                               continue;
+                       }
+                       if (!strcmp(arg, "--revs-only")) {
+                               revs_only = 1;
+                               continue;
+                       }
+                       if (!strcmp(arg, "--no-revs")) {
+                               no_revs = 1;
+                               continue;
+                       }
+                       if (!strcmp(arg, "--flags")) {
+                               flags_only = 1;
+                               continue;
+                       }
+                       if (!strcmp(arg, "--no-flags")) {
+                               no_flags = 1;
+                               continue;
+                       }
+                       if (!strcmp(arg, "--verify")) {
+                               revs_only = 1;
+                               do_rev_argument = 0;
+                               single_rev = 1;
+                               continue;
+                       }
+                       if (!strcmp(arg, "--sq")) {
+                               output_sq = 1;
+                               continue;
+                       }
+                       if (!strcmp(arg, "--not")) {
+                               show_type ^= REVERSED;
+                               continue;
+                       }
+                       if (!strcmp(arg, "--all")) {
+                               for_each_ref(show_reference);
+                               continue;
+                       }
+                       show_arg(arg);
+                       continue;
+               }
+               dotdot = strstr(arg, "..");
+               if (dotdot) {
+                       unsigned char end[20];
+                       char *n = dotdot+2;
+                       *dotdot = 0;
+                       if (!get_sha1(arg, sha1)) {
+                               if (!*n)
+                                       n = "HEAD";
+                               if (!get_sha1(n, end)) {
+                                       if (no_revs)
+                                               continue;
+                                       def = NULL;
+                                       show_rev(NORMAL, end);
+                                       show_rev(REVERSED, sha1);
+                                       continue;
+                               }
+                       }
+                       *dotdot = '.';
+               }
+               if (!get_sha1(arg, sha1)) {
+                       if (no_revs)
+                               continue;
+                       def = NULL;
+                       show_rev(NORMAL, sha1);
+                       continue;
+               }
+               if (*arg == '^' && !get_sha1(arg+1, sha1)) {
+                       if (no_revs)
+                               continue;
+                       def = NULL;
+                       show_rev(REVERSED, sha1);
+                       continue;
+               }
+               show_default();
+               show_norev(arg);
+       }
+       show_default();
+       if (single_rev && output_revs != 1) {
+               fprintf(stderr, "Needed a single revision\n");
+               exit(1);
+       }
+       return 0;
+}
diff --git a/rev-tree.c b/rev-tree.c
new file mode 100644 (file)
index 0000000..7f92819
--- /dev/null
@@ -0,0 +1,140 @@
+#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
new file mode 100644 (file)
index 0000000..bcb1c80
--- /dev/null
+++ b/rsh.c
@@ -0,0 +1,74 @@
+#include "rsh.h"
+
+#include <string.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#include "cache.h"
+
+#define COMMAND_SIZE 4096
+
+int setup_connection(int *fd_in, int *fd_out, const char *remote_prog, 
+                    char *url, int rmt_argc, char **rmt_argv)
+{
+       char *host;
+       char *path;
+       int sv[2];
+       char command[COMMAND_SIZE];
+       char *posn;
+       int i;
+
+       if (!strcmp(url, "-")) {
+               *fd_in = 0;
+               *fd_out = 1;
+               return 0;
+       }
+
+       host = strstr(url, "//");
+       if (host) {
+               host += 2;
+               path = strchr(host, '/');
+       } else {
+               host = url;
+               path = strchr(host, ':');
+               if (path)
+                       *(path++) = '\0';
+       }
+       if (!path) {
+               return error("Bad URL: %s", url);
+       }
+       /* ssh <host> 'cd <path>; stdio-pull <arg...> <commit-id>' */
+       snprintf(command, COMMAND_SIZE, 
+                "%s='%s' %s",
+                GIT_DIR_ENVIRONMENT, path, remote_prog);
+       *path = '\0';
+       posn = command + strlen(command);
+       for (i = 0; i < rmt_argc; i++) {
+               *(posn++) = ' ';
+               strncpy(posn, rmt_argv[i], COMMAND_SIZE - (posn - command));
+               posn += strlen(rmt_argv[i]);
+               if (posn - command + 4 >= COMMAND_SIZE) {
+                       return error("Command line too long");
+               }
+       }
+       strcpy(posn, " -");
+       if (socketpair(AF_UNIX, SOCK_STREAM, 0, sv)) {
+               return error("Couldn't create socket");
+       }
+       if (!fork()) {
+               const char *ssh = getenv("GIT_SSH") ? : "ssh";
+               const char *ssh_basename = strrchr(ssh, '/');
+               if (!ssh_basename)
+                       ssh_basename = ssh;
+               else
+                       ssh_basename++;
+               close(sv[1]);
+               dup2(sv[0], 0);
+               dup2(sv[0], 1);
+               execlp(ssh, ssh_basename, host, command, NULL);
+       }
+       close(sv[0]);
+       *fd_in = sv[1];
+       *fd_out = sv[1];
+       return 0;
+}
diff --git a/rsh.h b/rsh.h
new file mode 100644 (file)
index 0000000..3b41942
--- /dev/null
+++ b/rsh.h
@@ -0,0 +1,7 @@
+#ifndef RSH_H
+#define RSH_H
+
+int setup_connection(int *fd_in, int *fd_out, const char *remote_prog, 
+                    char *url, int rmt_argc, char **rmt_argv);
+
+#endif
diff --git a/run-command.c b/run-command.c
new file mode 100644 (file)
index 0000000..5787a50
--- /dev/null
@@ -0,0 +1,58 @@
+#include "cache.h"
+#include "run-command.h"
+#include <sys/wait.h>
+
+int run_command_v(int argc, char **argv)
+{
+       pid_t pid = fork();
+
+       if (pid < 0)
+               return -ERR_RUN_COMMAND_FORK;
+       if (!pid) {
+               execvp(argv[0], (char *const*) argv);
+               die("exec %s failed.", argv[0]);
+       }
+       for (;;) {
+               int status, code;
+               int retval = waitpid(pid, &status, 0);
+
+               if (retval < 0) {
+                       if (errno == EINTR)
+                               continue;
+                       error("waitpid failed (%s)", strerror(retval));
+                       return -ERR_RUN_COMMAND_WAITPID;
+               }
+               if (retval != pid)
+                       return -ERR_RUN_COMMAND_WAITPID_WRONG_PID;
+               if (WIFSIGNALED(status))
+                       return -ERR_RUN_COMMAND_WAITPID_SIGNAL;
+
+               if (!WIFEXITED(status))
+                       return -ERR_RUN_COMMAND_WAITPID_NOEXIT;
+               code = WEXITSTATUS(status);
+               if (code)
+                       return -code;
+               return 0;
+       }
+}
+
+int run_command(const char *cmd, ...)
+{
+       int argc;
+       char *argv[MAX_RUN_COMMAND_ARGS];
+       const char *arg;
+       va_list param;
+
+       va_start(param, cmd);
+       argv[0] = (char*) cmd;
+       argc = 1;
+       while (argc < MAX_RUN_COMMAND_ARGS) {
+               arg = argv[argc++] = va_arg(param, char *);
+               if (!arg)
+                       break;
+       }
+       va_end(param);
+       if (MAX_RUN_COMMAND_ARGS <= argc)
+               return error("too many args to run %s", cmd);
+       return run_command_v(argc, argv);
+}
diff --git a/run-command.h b/run-command.h
new file mode 100644 (file)
index 0000000..5ee0972
--- /dev/null
@@ -0,0 +1,17 @@
+#ifndef RUN_COMMAND_H
+#define RUN_COMMAND_H
+
+#define MAX_RUN_COMMAND_ARGS 256
+enum {
+       ERR_RUN_COMMAND_FORK = 10000,
+       ERR_RUN_COMMAND_EXEC,
+       ERR_RUN_COMMAND_WAITPID,
+       ERR_RUN_COMMAND_WAITPID_WRONG_PID,
+       ERR_RUN_COMMAND_WAITPID_SIGNAL,
+       ERR_RUN_COMMAND_WAITPID_NOEXIT,
+};
+
+int run_command_v(int argc, char **argv);
+int run_command(const char *cmd, ...);
+
+#endif
diff --git a/send-pack.c b/send-pack.c
new file mode 100644 (file)
index 0000000..0ab135c
--- /dev/null
@@ -0,0 +1,311 @@
+#include "cache.h"
+#include "commit.h"
+#include "tag.h"
+#include "refs.h"
+#include "pkt-line.h"
+
+static const char send_pack_usage[] =
+"git-send-pack [--all] [--exec=git-receive-pack] <remote> [<head>...]\n"
+"  --all and explicit <head> specification are mutually exclusive.";
+static const char *exec = "git-receive-pack";
+static int send_all = 0;
+static int force_update = 0;
+
+static int is_zero_sha1(const unsigned char *sha1)
+{
+       int i;
+
+       for (i = 0; i < 20; i++) {
+               if (*sha1++)
+                       return 0;
+       }
+       return 1;
+}
+
+static void exec_pack_objects(void)
+{
+       static char *args[] = {
+               "git-pack-objects",
+               "--stdout",
+               NULL
+       };
+       execvp("git-pack-objects", args);
+       die("git-pack-objects exec failed (%s)", strerror(errno));
+}
+
+static void exec_rev_list(struct ref *refs)
+{
+       static char *args[1000];
+       int i = 0;
+
+       args[i++] = "git-rev-list";     /* 0 */
+       args[i++] = "--objects";        /* 1 */
+       while (refs) {
+               char *buf = malloc(100);
+               if (i > 900)
+                       die("git-rev-list environment overflow");
+               if (!is_zero_sha1(refs->old_sha1) &&
+                   has_sha1_file(refs->old_sha1)) {
+                       args[i++] = buf;
+                       snprintf(buf, 50, "^%s", sha1_to_hex(refs->old_sha1));
+                       buf += 50;
+               }
+               if (!is_zero_sha1(refs->new_sha1)) {
+                       args[i++] = buf;
+                       snprintf(buf, 50, "%s", sha1_to_hex(refs->new_sha1));
+               }
+               refs = refs->next;
+       }
+       args[i] = NULL;
+       execvp("git-rev-list", args);
+       die("git-rev-list exec failed (%s)", strerror(errno));
+}
+
+static void rev_list(int fd, struct ref *refs)
+{
+       int pipe_fd[2];
+       pid_t pack_objects_pid;
+
+       if (pipe(pipe_fd) < 0)
+               die("rev-list setup: pipe failed");
+       pack_objects_pid = fork();
+       if (!pack_objects_pid) {
+               dup2(pipe_fd[0], 0);
+               dup2(fd, 1);
+               close(pipe_fd[0]);
+               close(pipe_fd[1]);
+               close(fd);
+               exec_pack_objects();
+               die("pack-objects setup failed");
+       }
+       if (pack_objects_pid < 0)
+               die("pack-objects fork failed");
+       dup2(pipe_fd[1], 1);
+       close(pipe_fd[0]);
+       close(pipe_fd[1]);
+       close(fd);
+       exec_rev_list(refs);
+}
+
+static int pack_objects(int fd, struct ref *refs)
+{
+       pid_t rev_list_pid;
+
+       rev_list_pid = fork();
+       if (!rev_list_pid) {
+               rev_list(fd, refs);
+               die("rev-list setup failed");
+       }
+       if (rev_list_pid < 0)
+               die("rev-list fork failed");
+       /*
+        * We don't wait for the rev-list pipeline in the parent:
+        * we end up waiting for the other end instead
+        */
+       return 0;
+}
+
+static void unmark_and_free(struct commit_list *list, unsigned int mark)
+{
+       while (list) {
+               struct commit_list *temp = list;
+               temp->item->object.flags &= ~mark;
+               list = temp->next;
+               free(temp);
+       }
+}
+
+static int ref_newer(const unsigned char *new_sha1,
+                    const unsigned char *old_sha1)
+{
+       struct object *o;
+       struct commit *old, *new;
+       struct commit_list *list, *used;
+       int found = 0;
+
+       /* 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));
+       if (!o || o->type != commit_type)
+               return 0;
+       old = (struct commit *) o;
+
+       o = deref_tag(parse_object(new_sha1));
+       if (!o || o->type != commit_type)
+               return 0;
+       new = (struct commit *) o;
+
+       if (parse_commit(new) < 0)
+               return 0;
+
+       used = list = NULL;
+       commit_list_insert(new, &list);
+       while (list) {
+               new = pop_most_recent_commit(&list, 1);
+               commit_list_insert(new, &used);
+               if (new == old) {
+                       found = 1;
+                       break;
+               }
+       }
+       unmark_and_free(list, 1);
+       unmark_and_free(used, 1);
+       return found;
+}
+
+static struct ref *local_refs, **local_tail;
+static struct ref *remote_refs, **remote_tail;
+
+static int one_local_ref(const char *refname, const unsigned char *sha1)
+{
+       struct ref *ref;
+       int len = strlen(refname) + 1;
+       ref = xcalloc(1, sizeof(*ref) + len);
+       memcpy(ref->new_sha1, sha1, 20);
+       memcpy(ref->name, refname, len);
+       *local_tail = ref;
+       local_tail = &ref->next;
+       return 0;
+}
+
+static void get_local_heads(void)
+{
+       local_tail = &local_refs;
+       for_each_ref(one_local_ref);
+}
+
+static int send_pack(int in, int out, int nr_refspec, char **refspec)
+{
+       struct ref *ref;
+       int new_refs;
+
+       /* No funny business with the matcher */
+       remote_tail = get_remote_heads(in, &remote_refs, 0, NULL);
+       get_local_heads();
+
+       /* match them up */
+       if (!remote_tail)
+               remote_tail = &remote_refs;
+       if (match_refs(local_refs, remote_refs, &remote_tail,
+                      nr_refspec, refspec, send_all))
+               return -1;
+       /*
+        * Finally, tell the other end!
+        */
+       new_refs = 0;
+       for (ref = remote_refs; ref; ref = ref->next) {
+               char old_hex[60], *new_hex;
+               if (!ref->peer_ref)
+                       continue;
+               if (!memcmp(ref->old_sha1, ref->peer_ref->new_sha1, 20)) {
+                       fprintf(stderr, "'%s': up-to-date\n", ref->name);
+                       continue;
+               }
+
+               /* This part determines what can overwrite what.
+                * The rules are:
+                *
+                * (0) you can always use --force.
+                *
+                * (1) if the old thing does not exist, it is OK.
+                *
+                * (2) if you do not have the old thing, you are not allowed
+                *     to overwrite it; you would not know what you are losing
+                *     otherwise.
+                *
+                * (3) if both new and old are commit-ish, and new is a
+                *     descendant of old, it is OK.
+                */
+
+               if (!force_update && !is_zero_sha1(ref->old_sha1)) {
+                       if (!has_sha1_file(ref->old_sha1)) {
+                               error("remote '%s' object %s does not "
+                                     "exist on local",
+                                     ref->name, sha1_to_hex(ref->old_sha1));
+                               continue;
+                       }
+                       /* We assume that local is fsck-clean.  Otherwise
+                        * you _could_ have a old tag which points at
+                        * something you do not have which may or may not
+                        * be a commit.
+                        */
+                       if (!ref_newer(ref->peer_ref->new_sha1,
+                                      ref->old_sha1)) {
+                               error("remote ref '%s' is not a strict "
+                                     "subset of local ref '%s'.", ref->name,
+                                     ref->peer_ref->name);
+                               continue;
+                       }
+               }
+               memcpy(ref->new_sha1, ref->peer_ref->new_sha1, 20);
+               if (is_zero_sha1(ref->new_sha1)) {
+                       error("cannot happen anymore");
+                       continue;
+               }
+               new_refs++;
+               strcpy(old_hex, sha1_to_hex(ref->old_sha1));
+               new_hex = sha1_to_hex(ref->new_sha1);
+               packet_write(out, "%s %s %s", old_hex, new_hex, ref->name);
+               fprintf(stderr, "updating '%s'", ref->name);
+               if (strcmp(ref->name, ref->peer_ref->name))
+                       fprintf(stderr, " using '%s'", ref->peer_ref->name);
+               fprintf(stderr, "\n  from %s\n  to   %s\n", old_hex, new_hex);
+       }
+
+       packet_flush(out);
+       if (new_refs)
+               pack_objects(out, remote_refs);
+       close(out);
+       return 0;
+}
+
+
+int main(int argc, char **argv)
+{
+       int i, nr_heads = 0;
+       char *dest = NULL;
+       char **heads = NULL;
+       int fd[2], ret;
+       pid_t pid;
+
+       argv++;
+       for (i = 1; i < argc; i++, argv++) {
+               char *arg = *argv;
+
+               if (*arg == '-') {
+                       if (!strncmp(arg, "--exec=", 7)) {
+                               exec = arg + 7;
+                               continue;
+                       }
+                       if (!strcmp(arg, "--all")) {
+                               send_all = 1;
+                               continue;
+                       }
+                       if (!strcmp(arg, "--force")) {
+                               force_update = 1;
+                               continue;
+                       }
+                       usage(send_pack_usage);
+               }
+               if (!dest) {
+                       dest = arg;
+                       continue;
+               }
+               heads = argv;
+               nr_heads = argc - i;
+               break;
+       }
+       if (!dest)
+               usage(send_pack_usage);
+       if (heads && send_all)
+               usage(send_pack_usage);
+       pid = git_connect(fd, dest, exec);
+       if (pid < 0)
+               return 1;
+       ret = send_pack(fd[0], fd[1], nr_heads, heads);
+       close(fd[0]);
+       close(fd[1]);
+       finish_connect(pid);
+       return ret;
+}
diff --git a/server-info.c b/server-info.c
new file mode 100644 (file)
index 0000000..2b3aeca
--- /dev/null
@@ -0,0 +1,572 @@
+#include "cache.h"
+#include "refs.h"
+#include "object.h"
+#include "commit.h"
+#include "tag.h"
+#include "rev-cache.h"
+
+/* refs */
+static FILE *info_ref_fp;
+static unsigned long info_ref_time;
+static int info_ref_is_stale = 0;
+
+static int stat_ref(const char *path, const unsigned char *sha1)
+{
+       struct stat st;
+       if (!stat(path, &st) && info_ref_time < st.st_mtime)
+               info_ref_is_stale = 1;
+       return 0;
+}
+
+static int add_info_ref(const char *path, const unsigned char *sha1)
+{
+       fprintf(info_ref_fp, "%s        %s\n", sha1_to_hex(sha1), path);
+       return 0;
+}
+
+static int update_info_refs(int force)
+{
+       struct stat st;
+       char *path0 = strdup(git_path("info/refs"));
+       int len = strlen(path0);
+       char *path1 = xmalloc(len + 2);
+
+       strcpy(path1, path0);
+       strcpy(path1 + len, "+");
+
+       if (!force) {
+               if (stat(path0, &st)) {
+                       if (errno == ENOENT)
+                               info_ref_is_stale = 1;
+                       else
+                               return error("cannot stat %s", path0);
+               }
+               else {
+                       info_ref_time = st.st_mtime;
+                       for_each_ref(stat_ref);
+               }
+               if (!info_ref_is_stale)
+                       return 0;
+       }
+
+       safe_create_leading_directories(path0);
+       info_ref_fp = fopen(path1, "w");
+       if (!info_ref_fp)
+               return error("unable to update %s", path0);
+       for_each_ref(add_info_ref);
+       fclose(info_ref_fp);
+       rename(path1, path0);
+       free(path0);
+       free(path1);
+       return 0;
+}
+
+/* packs */
+static struct pack_info {
+       unsigned long latest;
+       struct packed_git *p;
+       int old_num;
+       int new_num;
+       int nr_alloc;
+       int nr_heads;
+       unsigned char (*head)[20];
+       char dep[0]; /* more */
+} **info;
+static int num_pack;
+static const char *objdir;
+static int objdirlen;
+
+static struct object *parse_object_cheap(const unsigned char *sha1)
+{
+       struct object *o;
+
+       if ((o = parse_object(sha1)) == NULL)
+               return NULL;
+       if (o->type == commit_type) {
+               struct commit *commit = (struct commit *)o;
+               free(commit->buffer);
+               commit->buffer = NULL;
+       }
+       return o;
+}
+
+static struct pack_info *find_pack_by_name(const char *name)
+{
+       int i;
+       for (i = 0; i < num_pack; i++) {
+               struct packed_git *p = info[i]->p;
+               /* skip "/pack/" after ".git/objects" */
+               if (!strcmp(p->pack_name + objdirlen + 6, name))
+                       return info[i];
+       }
+       return NULL;
+}
+
+static struct pack_info *find_pack_by_old_num(int old_num)
+{
+       int i;
+       for (i = 0; i < num_pack; i++)
+               if (info[i]->old_num == old_num)
+                       return info[i];
+       return NULL;
+}
+
+static int add_head_def(struct pack_info *this, unsigned char *sha1)
+{
+       if (this->nr_alloc <= this->nr_heads) {
+               this->nr_alloc = alloc_nr(this->nr_alloc);
+               this->head = xrealloc(this->head, this->nr_alloc * 20);
+       }
+       memcpy(this->head[this->nr_heads++], sha1, 20);
+       return 0;
+}
+
+/* Returns non-zero when we detect that the info in the
+ * old file is useless.
+ */
+static int parse_pack_def(const char *line, int old_cnt)
+{
+       struct pack_info *i = find_pack_by_name(line + 2);
+       if (i) {
+               i->old_num = old_cnt;
+               return 0;
+       }
+       else {
+               /* The file describes a pack that is no longer here;
+                * dependencies between packs needs to be recalculated.
+                */
+               return 1;
+       }
+}
+
+/* Returns non-zero when we detect that the info in the
+ * old file is useless.
+ */
+static int parse_depend_def(char *line)
+{
+       unsigned long num;
+       char *cp, *ep;
+       struct pack_info *this, *that;
+
+       cp = line + 2;
+       num = strtoul(cp, &ep, 10);
+       if (ep == cp)
+               return error("invalid input %s", line);
+       this = find_pack_by_old_num(num);
+       if (!this)
+               return 0;
+       while (ep && *(cp = ep)) {
+               num = strtoul(cp, &ep, 10);
+               if (ep == cp)
+                       break;
+               that = find_pack_by_old_num(num);
+               if (!that)
+                       /* The pack this one depends on does not
+                        * exist; this should not happen because
+                        * we write out the list of packs first and
+                        * then dependency information, but it means
+                        * the file is useless anyway.
+                        */
+                       return 1;
+               this->dep[that->new_num] = 1;
+       }
+       return 0;
+}
+
+/* Returns non-zero when we detect that the info in the
+ * old file is useless.
+ */
+static int parse_head_def(char *line)
+{
+       unsigned char sha1[20];
+       unsigned long num;
+       char *cp, *ep;
+       struct pack_info *this;
+       struct object *o;
+
+       cp = line + 2;
+       num = strtoul(cp, &ep, 10);
+       if (ep == cp || *ep++ != ' ')
+               return error("invalid input ix %s", line);
+       this = find_pack_by_old_num(num);
+       if (!this)
+               return 1; /* You know the drill. */
+       if (get_sha1_hex(ep, sha1) || ep[40] != ' ')
+               return error("invalid input sha1 %s (%s)", line, ep);
+       if ((o = parse_object_cheap(sha1)) == NULL)
+               return error("no such object: %s", line);
+       return add_head_def(this, sha1);
+}
+
+/* Returns non-zero when we detect that the info in the
+ * old file is useless.
+ */
+static int read_pack_info_file(const char *infofile)
+{
+       FILE *fp;
+       char line[1000];
+       int old_cnt = 0;
+
+       fp = fopen(infofile, "r");
+       if (!fp)
+               return 1; /* nonexisting is not an error. */
+
+       while (fgets(line, sizeof(line), fp)) {
+               int len = strlen(line);
+               if (line[len-1] == '\n')
+                       line[len-1] = 0;
+
+               switch (line[0]) {
+               case 'P': /* P name */
+                       if (parse_pack_def(line, old_cnt++))
+                               goto out_stale;
+                       break;
+               case 'D': /* D ix dep-ix1 dep-ix2... */
+                       if (parse_depend_def(line))
+                               goto out_stale;
+                       break;
+               case 'T': /* T ix sha1 type */
+                       if (parse_head_def(line))
+                               goto out_stale;
+                       break;
+               default:
+                       error("unrecognized: %s", line);
+                       break;
+               }
+       }
+       fclose(fp);
+       return 0;
+ out_stale:
+       fclose(fp);
+       return 1;
+}
+
+/* We sort the packs according to the date of the latest commit.  That
+ * in turn indicates how young the pack is, and in general we would
+ * want to depend on younger packs.
+ */
+static unsigned long get_latest_commit_date(struct packed_git *p)
+{
+       unsigned char sha1[20];
+       struct object *o;
+       int num = num_packed_objects(p);
+       int i;
+       unsigned long latest = 0;
+
+       for (i = 0; i < num; i++) {
+               if (nth_packed_object_sha1(p, i, sha1))
+                       die("corrupt pack file %s?", p->pack_name);
+               if ((o = parse_object_cheap(sha1)) == NULL)
+                       die("cannot parse %s", sha1_to_hex(sha1));
+               if (o->type == commit_type) {
+                       struct commit *commit = (struct commit *)o;
+                       if (latest < commit->date)
+                               latest = commit->date;
+               }
+       }
+       return latest;
+}
+
+static int compare_info(const void *a_, const void *b_)
+{
+       struct pack_info * const* a = a_;
+       struct pack_info * const* b = b_;
+
+       if (0 <= (*a)->old_num && 0 <= (*b)->old_num)
+               /* Keep the order in the original */
+               return (*a)->old_num - (*b)->old_num;
+       else if (0 <= (*a)->old_num)
+               /* Only A existed in the original so B is obviously newer */
+               return -1;
+       else if (0 <= (*b)->old_num)
+               /* The other way around. */
+               return 1;
+
+       if ((*a)->latest < (*b)->latest)
+               return -1;
+       else if ((*a)->latest == (*b)->latest)
+               return 0;
+       else
+               return 1;
+}
+
+static void init_pack_info(const char *infofile, int force)
+{
+       struct packed_git *p;
+       int stale;
+       int i = 0;
+       char *dep_temp;
+
+       objdir = get_object_directory();
+       objdirlen = strlen(objdir);
+
+       prepare_packed_git();
+       for (p = packed_git; p; p = p->next) {
+               /* we ignore things on alternate path since they are
+                * not available to the pullers in general.
+                */
+               if (strncmp(p->pack_name, objdir, objdirlen) ||
+                   strncmp(p->pack_name + objdirlen, "/pack/", 6))
+                       continue;
+               i++;
+       }
+       num_pack = i;
+       info = xcalloc(num_pack, sizeof(struct pack_info *));
+       for (i = 0, p = packed_git; p; p = p->next) {
+               if (strncmp(p->pack_name, objdir, objdirlen) ||
+                   p->pack_name[objdirlen] != '/')
+                       continue;
+               info[i] = xcalloc(1, sizeof(struct pack_info) + num_pack);
+               info[i]->p = p;
+               info[i]->old_num = -1;
+               i++;
+       }
+
+       if (infofile && !force)
+               stale = read_pack_info_file(infofile);
+       else
+               stale = 1;
+
+       for (i = 0; i < num_pack; i++) {
+               if (stale) {
+                       info[i]->old_num = -1;
+                       memset(info[i]->dep, 0, num_pack);
+                       info[i]->nr_heads = 0;
+               }
+               if (info[i]->old_num < 0)
+                       info[i]->latest = get_latest_commit_date(info[i]->p);
+       }
+
+       qsort(info, num_pack, sizeof(info[0]), compare_info);
+       for (i = 0; i < num_pack; i++)
+               info[i]->new_num = i;
+
+       /* we need to fix up the dependency information
+        * for the old ones.
+        */
+       dep_temp = NULL;
+       for (i = 0; i < num_pack; i++) {
+               int old;
+
+               if (info[i]->old_num < 0)
+                       continue;
+               if (! dep_temp)
+                       dep_temp = xmalloc(num_pack);
+               memset(dep_temp, 0, num_pack);
+               for (old = 0; old < num_pack; old++) {
+                       struct pack_info *base;
+                       if (!info[i]->dep[old])
+                               continue;
+                       base = find_pack_by_old_num(old);
+                       if (!base)
+                               die("internal error renumbering");
+                       dep_temp[base->new_num] = 1;
+               }
+               memcpy(info[i]->dep, dep_temp, num_pack);
+       }
+       free(dep_temp);
+}
+
+static void write_pack_info_file(FILE *fp)
+{
+       int i, j;
+       for (i = 0; i < num_pack; i++)
+               fprintf(fp, "P %s\n", info[i]->p->pack_name + objdirlen + 6);
+
+       for (i = 0; i < num_pack; i++) {
+               fprintf(fp, "D %1d", i);
+               for (j = 0; j < num_pack; j++) {
+                       if ((i == j) || !(info[i]->dep[j]))
+                               continue;
+                       fprintf(fp, " %1d", j);
+               }
+               fputc('\n', fp);
+       }
+
+       for (i = 0; i < num_pack; i++) {
+               struct pack_info *this = info[i];
+               for (j = 0; j < this->nr_heads; j++) {
+                       struct object *o = lookup_object(this->head[j]);
+                       fprintf(fp, "T %1d %s %s\n",
+                               i, sha1_to_hex(this->head[j]), o->type);
+               }
+       }
+
+}
+
+#define REFERENCED 01
+#define INTERNAL  02
+#define EMITTED   04
+
+static void show(struct object *o, int pack_ix)
+{
+       /*
+        * We are interested in objects that are not referenced,
+        * and objects that are referenced but not internal.
+        */
+       if (o->flags & EMITTED)
+               return;
+
+       if (!(o->flags & REFERENCED))
+               add_head_def(info[pack_ix], o->sha1);
+       else if ((o->flags & REFERENCED) && !(o->flags & INTERNAL)) {
+               int i;
+
+               /* Which pack contains this object?  That is what
+                * pack_ix can depend on.  We earlier sorted info
+                * array from youngest to oldest, so try newer packs
+                * first to favor them here.
+                */
+               for (i = num_pack - 1; 0 <= i; i--) {
+                       struct packed_git *p = info[i]->p;
+                       struct pack_entry ent;
+                       if (find_pack_entry_one(o->sha1, &ent, p)) {
+                               info[pack_ix]->dep[i] = 1;
+                               break;
+                       }
+               }
+       }
+       o->flags |= EMITTED;
+}
+
+static void find_pack_info_one(int pack_ix)
+{
+       unsigned char sha1[20];
+       struct object *o;
+       struct object_list *ref;
+       int i;
+       struct packed_git *p = info[pack_ix]->p;
+       int num = num_packed_objects(p);
+
+       /* Scan objects, clear flags from all the edge ones and
+        * internal ones, possibly marked in the previous round.
+        */
+       for (i = 0; i < num; i++) {
+               if (nth_packed_object_sha1(p, i, sha1))
+                       die("corrupt pack file %s?", p->pack_name);
+               if ((o = lookup_object(sha1)) == NULL)
+                       die("cannot parse %s", sha1_to_hex(sha1));
+               for (ref = o->refs; ref; ref = ref->next)
+                       ref->item->flags = 0;
+               o->flags = 0;
+       }
+
+       /* Mark all the internal ones */
+       for (i = 0; i < num; i++) {
+               if (nth_packed_object_sha1(p, i, sha1))
+                       die("corrupt pack file %s?", p->pack_name);
+               if ((o = lookup_object(sha1)) == NULL)
+                       die("cannot find %s", sha1_to_hex(sha1));
+               for (ref = o->refs; ref; ref = ref->next)
+                       ref->item->flags |= REFERENCED;
+               o->flags |= INTERNAL;
+       }
+
+       for (i = 0; i < num; i++) {
+               if (nth_packed_object_sha1(p, i, sha1))
+                       die("corrupt pack file %s?", p->pack_name);
+               if ((o = lookup_object(sha1)) == NULL)
+                       die("cannot find %s", sha1_to_hex(sha1));
+
+               show(o, pack_ix);
+               for (ref = o->refs; ref; ref = ref->next)
+                       show(ref->item, pack_ix);
+       }
+
+}
+
+static void find_pack_info(void)
+{
+       int i;
+       for (i = 0; i < num_pack; i++) {
+               /* The packed objects are cast in stone, and a head
+                * in a pack will stay as head, so is the set of missing
+                * objects.  If the repo has been reorganized and we
+                * are missing some packs available back then, we have
+                * already discarded the info read from the file, so
+                * we will find (old_num < 0) in that case.
+                */
+               if (0 <= info[i]->old_num)
+                       continue;
+               find_pack_info_one(i);
+       }
+}
+
+static int update_info_packs(int force)
+{
+       char infofile[PATH_MAX];
+       char name[PATH_MAX];
+       int namelen;
+       FILE *fp;
+
+       namelen = sprintf(infofile, "%s/info/packs", get_object_directory());
+       strcpy(name, infofile);
+       strcpy(name + namelen, "+");
+
+       init_pack_info(infofile, force);
+       find_pack_info();
+
+       safe_create_leading_directories(name);
+       fp = fopen(name, "w");
+       if (!fp)
+               return error("cannot open %s", name);
+       write_pack_info_file(fp);
+       fclose(fp);
+       rename(name, infofile);
+       return 0;
+}
+
+/* rev-cache */
+static int record_rev_cache_ref(const char *path, const unsigned char *sha1)
+{
+       struct object *obj = parse_object(sha1);
+
+       if (!obj)
+               return error("ref %s has bad sha %s", path, sha1_to_hex(sha1));
+       while (obj && obj->type == tag_type)
+               obj = parse_object(((struct tag *)obj)->tagged->sha1);
+       if (!obj || obj->type != commit_type)
+               /* tag pointing at a non-commit */
+               return 0;
+       return record_rev_cache(obj->sha1, NULL);
+}
+
+static int update_info_revs(int force)
+{
+       char *path0 = strdup(git_path("info/rev-cache"));
+       int len = strlen(path0);
+       char *path1 = xmalloc(len + 2);
+
+       strcpy(path1, path0);
+       strcpy(path1 + len, "+");
+
+       /* read existing rev-cache */
+       if (!force)
+               read_rev_cache(path0, NULL, 0);
+       safe_create_leading_directories(path0);
+
+       for_each_ref(record_rev_cache_ref);
+
+       /* update the rev-cache database */
+       write_rev_cache(path1, force ? "/dev/null" : path0);
+       rename(path1, path0);
+       free(path1);
+       free(path0);
+       return 0;
+}
+
+/* public */
+int update_server_info(int force)
+{
+       /* We would add more dumb-server support files later,
+        * including index of available pack files and their
+        * intended audiences.
+        */
+       int errs = 0;
+
+       errs = errs | update_info_refs(force);
+       errs = errs | update_info_packs(force);
+       errs = errs | update_info_revs(force);
+
+       return errs;
+}
diff --git a/sha1_file.c b/sha1_file.c
new file mode 100644 (file)
index 0000000..8d189d4
--- /dev/null
@@ -0,0 +1,1491 @@
+/*
+ * GIT - The information manager from hell
+ *
+ * Copyright (C) Linus Torvalds, 2005
+ *
+ * This handles basic git sha1 object files - packing, unpacking,
+ * creation etc.
+ */
+#include <sys/types.h>
+#include <dirent.h>
+#include "cache.h"
+#include "delta.h"
+#include "pack.h"
+
+#ifndef O_NOATIME
+#if defined(__linux__) && (defined(__i386__) || defined(__PPC__))
+#define O_NOATIME 01000000
+#else
+#define O_NOATIME 0
+#endif
+#endif
+
+static unsigned int sha1_file_open_flag = O_NOATIME;
+
+static unsigned hexval(char c)
+{
+       if (c >= '0' && c <= '9')
+               return c - '0';
+       if (c >= 'a' && c <= 'f')
+               return c - 'a' + 10;
+       if (c >= 'A' && c <= 'F')
+               return c - 'A' + 10;
+       return ~0;
+}
+
+int get_sha1_hex(const char *hex, unsigned char *sha1)
+{
+       int i;
+       for (i = 0; i < 20; i++) {
+               unsigned int val = (hexval(hex[0]) << 4) | hexval(hex[1]);
+               if (val & ~0xff)
+                       return -1;
+               *sha1++ = val;
+               hex += 2;
+       }
+       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 = gitenv(GIT_DIR_ENVIRONMENT);
+       if (!git_dir)
+               git_dir = DEFAULT_GIT_DIR_ENVIRONMENT;
+       git_object_dir = gitenv(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 = gitenv(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 = gitenv(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;
+
+       while (pos) {
+               pos = strchr(pos, '/');
+               if (!pos)
+                       break;
+               *pos = 0;
+               if (mkdir(path, 0777) < 0)
+                       if (errno != EEXIST) {
+                               *pos = '/';
+                               return -1;
+                       }
+               *pos++ = '/';
+       }
+       return 0;
+}
+
+char * sha1_to_hex(const unsigned char *sha1)
+{
+       static char buffer[50];
+       static const char hex[] = "0123456789abcdef";
+       char *buf = buffer;
+       int i;
+
+       for (i = 0; i < 20; i++) {
+               unsigned int val = *sha1++;
+               *buf++ = hex[val >> 4];
+               *buf++ = hex[val & 0xf];
+       }
+       return buffer;
+}
+
+static void fill_sha1_path(char *pathbuf, const unsigned char *sha1)
+{
+       int i;
+       for (i = 0; i < 20; i++) {
+               static char hex[] = "0123456789abcdef";
+               unsigned int val = sha1[i];
+               char *pos = pathbuf + i*2 + (i > 0);
+               *pos++ = hex[val >> 4];
+               *pos = hex[val & 0xf];
+       }
+}
+
+/*
+ * NOTE! This returns a statically allocated buffer, so you have to be
+ * careful about using it. Do a "strdup()" if you need to save the
+ * filename.
+ *
+ * Also note that this returns the location for creating.  Reading
+ * SHA1 file can happen from any alternate directory listed in the
+ * DB_ENVIRONMENT environment variable if it is not found in
+ * the primary object database.
+ */
+char *sha1_file_name(const unsigned char *sha1)
+{
+       static char *name, *base;
+
+       if (!base) {
+               const char *sha1_file_directory = get_object_directory();
+               int len = strlen(sha1_file_directory);
+               base = xmalloc(len + 60);
+               memcpy(base, sha1_file_directory, len);
+               memset(base+len, 0, 60);
+               base[len] = '/';
+               base[len+3] = '/';
+               name = base + len + 1;
+       }
+       fill_sha1_path(name, sha1);
+       return base;
+}
+
+char *sha1_pack_name(const unsigned char *sha1)
+{
+       static const char hex[] = "0123456789abcdef";
+       static char *name, *base, *buf;
+       int i;
+
+       if (!base) {
+               const char *sha1_file_directory = get_object_directory();
+               int len = strlen(sha1_file_directory);
+               base = xmalloc(len + 60);
+               sprintf(base, "%s/pack/pack-1234567890123456789012345678901234567890.pack", sha1_file_directory);
+               name = base + len + 11;
+       }
+
+       buf = name;
+
+       for (i = 0; i < 20; i++) {
+               unsigned int val = *sha1++;
+               *buf++ = hex[val >> 4];
+               *buf++ = hex[val & 0xf];
+       }
+       
+       return base;
+}
+
+char *sha1_pack_index_name(const unsigned char *sha1)
+{
+       static const char hex[] = "0123456789abcdef";
+       static char *name, *base, *buf;
+       int i;
+
+       if (!base) {
+               const char *sha1_file_directory = get_object_directory();
+               int len = strlen(sha1_file_directory);
+               base = xmalloc(len + 60);
+               sprintf(base, "%s/pack/pack-1234567890123456789012345678901234567890.idx", sha1_file_directory);
+               name = base + len + 11;
+       }
+
+       buf = name;
+
+       for (i = 0; i < 20; i++) {
+               unsigned int val = *sha1++;
+               *buf++ = hex[val >> 4];
+               *buf++ = hex[val & 0xf];
+       }
+       
+       return base;
+}
+
+struct alternate_object_database *alt_odb;
+
+/*
+ * Prepare alternate object database registry.
+ * alt_odb points at an array of struct alternate_object_database.
+ * This array is terminated with an element that has both its base
+ * and name set to NULL.  alt_odb[n] comes from n'th non-empty
+ * element from colon separated ALTERNATE_DB_ENVIRONMENT environment
+ * variable, and its base points at a statically allocated buffer
+ * that contains "/the/directory/corresponding/to/.git/objects/...",
+ * while its name points just after the slash at the end of
+ * ".git/objects/" in the example above, and has enough space to hold
+ * 40-byte hex SHA1, an extra slash for the first level indirection,
+ * and the terminating NUL.
+ * This function allocates the alt_odb array and all the strings
+ * pointed by base fields of the array elements with one xmalloc();
+ * the string pool immediately follows the array.
+ */
+void prepare_alt_odb(void)
+{
+       int pass, totlen, i;
+       const char *cp, *last;
+       char *op = NULL;
+       const char *alt = gitenv(ALTERNATE_DB_ENVIRONMENT) ? : "";
+
+       if (alt_odb)
+               return;
+       /* The first pass counts how large an area to allocate to
+        * hold the entire alt_odb structure, including array of
+        * structs and path buffers for them.  The second pass fills
+        * the structure and prepares the path buffers for use by
+        * fill_sha1_path().
+        */
+       for (totlen = pass = 0; pass < 2; pass++) {
+               last = alt;
+               i = 0;
+               do {
+                       cp = strchr(last, ':') ? : last + strlen(last);
+                       if (last != cp) {
+                               /* 43 = 40-byte + 2 '/' + terminating NUL */
+                               int pfxlen = cp - last;
+                               int entlen = pfxlen + 43;
+                               if (pass == 0)
+                                       totlen += entlen;
+                               else {
+                                       alt_odb[i].base = op;
+                                       alt_odb[i].name = op + pfxlen + 1;
+                                       memcpy(op, last, pfxlen);
+                                       op[pfxlen] = op[pfxlen + 3] = '/';
+                                       op[entlen-1] = 0;
+                                       op += entlen;
+                               }
+                               i++;
+                       }
+                       while (*cp && *cp == ':')
+                               cp++;
+                       last = cp;
+               } while (*cp);
+               if (pass)
+                       break;
+               alt_odb = xmalloc(sizeof(*alt_odb) * (i + 1) + totlen);
+               alt_odb[i].base = alt_odb[i].name = NULL;
+               op = (char*)(&alt_odb[i+1]);
+       }
+}
+
+static char *find_sha1_file(const unsigned char *sha1, struct stat *st)
+{
+       int i;
+       char *name = sha1_file_name(sha1);
+
+       if (!stat(name, st))
+               return name;
+       prepare_alt_odb();
+       for (i = 0; (name = alt_odb[i].name) != NULL; i++) {
+               fill_sha1_path(name, sha1);
+               if (!stat(alt_odb[i].base, st))
+                       return alt_odb[i].base;
+       }
+       return NULL;
+}
+
+#define PACK_MAX_SZ (1<<26)
+static int pack_used_ctr;
+static unsigned long pack_mapped;
+struct packed_git *packed_git;
+
+static int check_packed_git_idx(const char *path, unsigned long *idx_size_,
+                               void **idx_map_)
+{
+       void *idx_map;
+       unsigned int *index;
+       unsigned long idx_size;
+       int nr, i;
+       int fd = open(path, O_RDONLY);
+       struct stat st;
+       if (fd < 0)
+               return -1;
+       if (fstat(fd, &st)) {
+               close(fd);
+               return -1;
+       }
+       idx_size = st.st_size;
+       idx_map = mmap(NULL, idx_size, PROT_READ, MAP_PRIVATE, fd, 0);
+       close(fd);
+       if (idx_map == MAP_FAILED)
+               return -1;
+
+       index = idx_map;
+       *idx_map_ = idx_map;
+       *idx_size_ = idx_size;
+
+       /* check index map */
+       if (idx_size < 4*256 + 20 + 20)
+               return error("index file too small");
+       nr = 0;
+       for (i = 0; i < 256; i++) {
+               unsigned int n = ntohl(index[i]);
+               if (n < nr)
+                       return error("non-monotonic index");
+               nr = n;
+       }
+
+       /*
+        * Total size:
+        *  - 256 index entries 4 bytes each
+        *  - 24-byte entries * nr (20-byte sha1 + 4-byte offset)
+        *  - 20-byte SHA1 of the packfile
+        *  - 20-byte SHA1 file checksum
+        */
+       if (idx_size != 4*256 + nr * 24 + 20 + 20)
+               return error("wrong index file size");
+
+       return 0;
+}
+
+static int unuse_one_packed_git(void)
+{
+       struct packed_git *p, *lru = NULL;
+
+       for (p = packed_git; p; p = p->next) {
+               if (p->pack_use_cnt || !p->pack_base)
+                       continue;
+               if (!lru || p->pack_last_used < lru->pack_last_used)
+                       lru = p;
+       }
+       if (!lru)
+               return 0;
+       munmap(lru->pack_base, lru->pack_size);
+       lru->pack_base = NULL;
+       return 1;
+}
+
+void unuse_packed_git(struct packed_git *p)
+{
+       p->pack_use_cnt--;
+}
+
+int use_packed_git(struct packed_git *p)
+{
+       if (!p->pack_size) {
+               struct stat st;
+               // We created the struct before we had the pack
+               stat(p->pack_name, &st);
+               if (!S_ISREG(st.st_mode))
+                       die("packfile %s not a regular file", p->pack_name);
+               p->pack_size = st.st_size;
+       }
+       if (!p->pack_base) {
+               int fd;
+               struct stat st;
+               void *map;
+
+               pack_mapped += p->pack_size;
+               while (PACK_MAX_SZ < pack_mapped && unuse_one_packed_git())
+                       ; /* nothing */
+               fd = open(p->pack_name, O_RDONLY);
+               if (fd < 0)
+                       die("packfile %s cannot be opened", p->pack_name);
+               if (fstat(fd, &st)) {
+                       close(fd);
+                       die("packfile %s cannot be opened", p->pack_name);
+               }
+               if (st.st_size != p->pack_size)
+                       die("packfile %s size mismatch.", p->pack_name);
+               map = mmap(NULL, p->pack_size, PROT_READ, MAP_PRIVATE, fd, 0);
+               close(fd);
+               if (map == MAP_FAILED)
+                       die("packfile %s cannot be mapped.", p->pack_name);
+               p->pack_base = map;
+
+               /* Check if the pack file matches with the index file.
+                * this is cheap.
+                */
+               if (memcmp((char*)(p->index_base) + p->index_size - 40,
+                          p->pack_base + p->pack_size - 20, 20)) {
+                             
+                       die("packfile %s does not match index.", p->pack_name);
+               }
+       }
+       p->pack_last_used = pack_used_ctr++;
+       p->pack_use_cnt++;
+       return 0;
+}
+
+struct packed_git *add_packed_git(char *path, int path_len)
+{
+       struct stat st;
+       struct packed_git *p;
+       unsigned long idx_size;
+       void *idx_map;
+
+       if (check_packed_git_idx(path, &idx_size, &idx_map))
+               return NULL;
+
+       /* do we have a corresponding .pack file? */
+       strcpy(path + path_len - 4, ".pack");
+       if (stat(path, &st) || !S_ISREG(st.st_mode)) {
+               munmap(idx_map, idx_size);
+               return NULL;
+       }
+       /* ok, it looks sane as far as we can check without
+        * actually mapping the pack file.
+        */
+       p = xmalloc(sizeof(*p) + path_len + 2);
+       strcpy(p->pack_name, path);
+       p->index_size = idx_size;
+       p->pack_size = st.st_size;
+       p->index_base = idx_map;
+       p->next = NULL;
+       p->pack_base = NULL;
+       p->pack_last_used = 0;
+       p->pack_use_cnt = 0;
+       return p;
+}
+
+struct packed_git *parse_pack_index(unsigned char *sha1)
+{
+       struct packed_git *p;
+       unsigned long idx_size;
+       void *idx_map;
+       char *path = sha1_pack_index_name(sha1);
+
+       if (check_packed_git_idx(path, &idx_size, &idx_map))
+               return NULL;
+
+       path = sha1_pack_name(sha1);
+
+       p = xmalloc(sizeof(*p) + strlen(path) + 2);
+       strcpy(p->pack_name, path);
+       p->index_size = idx_size;
+       p->pack_size = 0;
+       p->index_base = idx_map;
+       p->next = NULL;
+       p->pack_base = NULL;
+       p->pack_last_used = 0;
+       p->pack_use_cnt = 0;
+       memcpy(p->sha1, sha1, 20);
+       return p;
+}
+
+void install_packed_git(struct packed_git *pack)
+{
+       pack->next = packed_git;
+       packed_git = pack;
+}
+
+static void prepare_packed_git_one(char *objdir)
+{
+       char path[PATH_MAX];
+       int len;
+       DIR *dir;
+       struct dirent *de;
+
+       sprintf(path, "%s/pack", objdir);
+       len = strlen(path);
+       dir = opendir(path);
+       if (!dir)
+               return;
+       path[len++] = '/';
+       while ((de = readdir(dir)) != NULL) {
+               int namelen = strlen(de->d_name);
+               struct packed_git *p;
+
+               if (strcmp(de->d_name + namelen - 4, ".idx"))
+                       continue;
+
+               /* we have .idx.  Is it a file we can map? */
+               strcpy(path + len, de->d_name);
+               p = add_packed_git(path, len + namelen);
+               if (!p)
+                       continue;
+               p->next = packed_git;
+               packed_git = p;
+       }
+       closedir(dir);
+}
+
+void prepare_packed_git(void)
+{
+       int i;
+       static int run_once = 0;
+
+       if (run_once++)
+               return;
+
+       prepare_packed_git_one(get_object_directory());
+       prepare_alt_odb();
+       for (i = 0; alt_odb[i].base != NULL; i++) {
+               alt_odb[i].name[0] = 0;
+               prepare_packed_git_one(alt_odb[i].base);
+       }
+}
+
+int check_sha1_signature(const unsigned char *sha1, void *map, unsigned long size, const char *type)
+{
+       char header[100];
+       unsigned char real_sha1[20];
+       SHA_CTX c;
+
+       SHA1_Init(&c);
+       SHA1_Update(&c, header, 1+sprintf(header, "%s %lu", type, size));
+       SHA1_Update(&c, map, size);
+       SHA1_Final(real_sha1, &c);
+       return memcmp(sha1, real_sha1, 20) ? -1 : 0;
+}
+
+static void *map_sha1_file_internal(const unsigned char *sha1,
+                                   unsigned long *size)
+{
+       struct stat st;
+       void *map;
+       int fd;
+       char *filename = find_sha1_file(sha1, &st);
+
+       if (!filename) {
+               return NULL;
+       }
+
+       fd = open(filename, O_RDONLY | sha1_file_open_flag);
+       if (fd < 0) {
+               /* See if it works without O_NOATIME */
+               switch (sha1_file_open_flag) {
+               default:
+                       fd = open(filename, O_RDONLY);
+                       if (fd >= 0)
+                               break;
+               /* Fallthrough */
+               case 0:
+                       return NULL;
+               }
+
+               /* If it failed once, it will probably fail again.
+                * Stop using O_NOATIME
+                */
+               sha1_file_open_flag = 0;
+       }
+       map = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
+       close(fd);
+       if (map == MAP_FAILED)
+               return NULL;
+       *size = st.st_size;
+       return map;
+}
+
+int unpack_sha1_header(z_stream *stream, void *map, unsigned long mapsize, void *buffer, unsigned long size)
+{
+       /* Get the data stream */
+       memset(stream, 0, sizeof(*stream));
+       stream->next_in = map;
+       stream->avail_in = mapsize;
+       stream->next_out = buffer;
+       stream->avail_out = size;
+
+       inflateInit(stream);
+       return inflate(stream, 0);
+}
+
+static void *unpack_sha1_rest(z_stream *stream, void *buffer, unsigned long size)
+{
+       int bytes = strlen(buffer) + 1;
+       unsigned char *buf = xmalloc(1+size);
+
+       memcpy(buf, buffer + bytes, stream->total_out - bytes);
+       bytes = stream->total_out - bytes;
+       if (bytes < size) {
+               stream->next_out = buf + bytes;
+               stream->avail_out = size - bytes;
+               while (inflate(stream, Z_FINISH) == Z_OK)
+                       /* nothing */;
+       }
+       buf[size] = 0;
+       inflateEnd(stream);
+       return buf;
+}
+
+/*
+ * We used to just use "sscanf()", but that's actually way
+ * too permissive for what we want to check. So do an anal
+ * object header parse by hand.
+ */
+int parse_sha1_header(char *hdr, char *type, unsigned long *sizep)
+{
+       int i;
+       unsigned long size;
+
+       /*
+        * The type can be at most ten bytes (including the 
+        * terminating '\0' that we add), and is followed by
+        * a space. 
+        */
+       i = 10;
+       for (;;) {
+               char c = *hdr++;
+               if (c == ' ')
+                       break;
+               if (!--i)
+                       return -1;
+               *type++ = c;
+       }
+       *type = 0;
+
+       /*
+        * The length must follow immediately, and be in canonical
+        * decimal format (ie "010" is not valid).
+        */
+       size = *hdr++ - '0';
+       if (size > 9)
+               return -1;
+       if (size) {
+               for (;;) {
+                       unsigned long c = *hdr - '0';
+                       if (c > 9)
+                               break;
+                       hdr++;
+                       size = size * 10 + c;
+               }
+       }
+       *sizep = size;
+
+       /*
+        * The length must be followed by a zero byte
+        */
+       return *hdr ? -1 : 0;
+}
+
+void * unpack_sha1_file(void *map, unsigned long mapsize, char *type, unsigned long *size)
+{
+       int ret;
+       z_stream stream;
+       char hdr[8192];
+
+       ret = unpack_sha1_header(&stream, map, mapsize, hdr, sizeof(hdr));
+       if (ret < Z_OK || parse_sha1_header(hdr, type, size) < 0)
+               return NULL;
+
+       return unpack_sha1_rest(&stream, hdr, *size);
+}
+
+/* forward declaration for a mutually recursive function */
+static int packed_object_info(struct pack_entry *entry,
+                             char *type, unsigned long *sizep);
+
+static int packed_delta_info(unsigned char *base_sha1,
+                            unsigned long delta_size,
+                            unsigned long left,
+                            char *type,
+                            unsigned long *sizep,
+                            struct packed_git *p)
+{
+       struct pack_entry base_ent;
+
+       if (left < 20)
+               die("truncated pack file");
+
+       /* The base entry _must_ be in the same pack */
+       if (!find_pack_entry_one(base_sha1, &base_ent, p))
+               die("failed to find delta-pack base object %s",
+                   sha1_to_hex(base_sha1));
+
+       /* We choose to only get the type of the base object and
+        * ignore potentially corrupt pack file that expects the delta
+        * based on a base with a wrong size.  This saves tons of
+        * inflate() calls.
+        */
+
+       if (packed_object_info(&base_ent, type, NULL))
+               die("cannot get info for delta-pack base");
+
+       if (sizep) {
+               const unsigned char *data;
+               unsigned char delta_head[64];
+               unsigned long result_size;
+               z_stream stream;
+               int st;
+
+               memset(&stream, 0, sizeof(stream));
+
+               data = stream.next_in = base_sha1 + 20;
+               stream.avail_in = left - 20;
+               stream.next_out = delta_head;
+               stream.avail_out = sizeof(delta_head);
+
+               inflateInit(&stream);
+               st = inflate(&stream, Z_FINISH);
+               inflateEnd(&stream);
+               if ((st != Z_STREAM_END) &&
+                   stream.total_out != sizeof(delta_head))
+                       die("delta data unpack-initial failed");
+
+               /* Examine the initial part of the delta to figure out
+                * the result size.
+                */
+               data = delta_head;
+               get_delta_hdr_size(&data); /* ignore base size */
+
+               /* Read the result size */
+               result_size = get_delta_hdr_size(&data);
+               *sizep = result_size;
+       }
+       return 0;
+}
+
+static unsigned long unpack_object_header(struct packed_git *p, unsigned long offset,
+       enum object_type *type, unsigned long *sizep)
+{
+       unsigned shift;
+       unsigned char *pack, c;
+       unsigned long size;
+
+       if (offset >= p->pack_size)
+               die("object offset outside of pack file");
+
+       pack =  p->pack_base + offset;
+       c = *pack++;
+       offset++;
+       *type = (c >> 4) & 7;
+       size = c & 15;
+       shift = 4;
+       while (c & 0x80) {
+               if (offset >= p->pack_size)
+                       die("object offset outside of pack file");
+               c = *pack++;
+               offset++;
+               size += (c & 0x7f) << shift;
+               shift += 7;
+       }
+       *sizep = size;
+       return offset;
+}
+
+void packed_object_info_detail(struct pack_entry *e,
+                              char *type,
+                              unsigned long *size,
+                              unsigned long *store_size,
+                              int *delta_chain_length,
+                              unsigned char *base_sha1)
+{
+       struct packed_git *p = e->p;
+       unsigned long offset, left;
+       unsigned char *pack;
+       enum object_type kind;
+
+       offset = unpack_object_header(p, e->offset, &kind, size);
+       pack = p->pack_base + offset;
+       left = p->pack_size - offset;
+       if (kind != OBJ_DELTA)
+               *delta_chain_length = 0;
+       else {
+               int chain_length = 0;
+               memcpy(base_sha1, pack, 20);
+               do {
+                       struct pack_entry base_ent;
+                       unsigned long junk;
+
+                       find_pack_entry_one(pack, &base_ent, p);
+                       offset = unpack_object_header(p, base_ent.offset,
+                                                     &kind, &junk);
+                       pack = p->pack_base + offset;
+                       chain_length++;
+               } while (kind == OBJ_DELTA);
+               *delta_chain_length = chain_length;
+       }
+       switch (kind) {
+       case OBJ_COMMIT:
+               strcpy(type, "commit");
+               break;
+       case OBJ_TREE:
+               strcpy(type, "tree");
+               break;
+       case OBJ_BLOB:
+               strcpy(type, "blob");
+               break;
+       case OBJ_TAG:
+               strcpy(type, "tag");
+               break;
+       default:
+               die("corrupted pack file");
+       }
+       *store_size = 0; /* notyet */
+}
+
+static int packed_object_info(struct pack_entry *entry,
+                             char *type, unsigned long *sizep)
+{
+       struct packed_git *p = entry->p;
+       unsigned long offset, size, left;
+       unsigned char *pack;
+       enum object_type kind;
+       int retval;
+
+       if (use_packed_git(p))
+               die("cannot map packed file");
+
+       offset = unpack_object_header(p, entry->offset, &kind, &size);
+       pack = p->pack_base + offset;
+       left = p->pack_size - offset;
+
+       switch (kind) {
+       case OBJ_DELTA:
+               retval = packed_delta_info(pack, size, left, type, sizep, p);
+               unuse_packed_git(p);
+               return retval;
+       case OBJ_COMMIT:
+               strcpy(type, "commit");
+               break;
+       case OBJ_TREE:
+               strcpy(type, "tree");
+               break;
+       case OBJ_BLOB:
+               strcpy(type, "blob");
+               break;
+       case OBJ_TAG:
+               strcpy(type, "tag");
+               break;
+       default:
+               die("corrupted pack file");
+       }
+       if (sizep)
+               *sizep = size;
+       unuse_packed_git(p);
+       return 0;
+}
+
+/* forward declaration for a mutually recursive function */
+static void *unpack_entry(struct pack_entry *, char *, unsigned long *);
+
+static void *unpack_delta_entry(unsigned char *base_sha1,
+                               unsigned long delta_size,
+                               unsigned long left,
+                               char *type,
+                               unsigned long *sizep,
+                               struct packed_git *p)
+{
+       struct pack_entry base_ent;
+       void *data, *delta_data, *result, *base;
+       unsigned long data_size, result_size, base_size;
+       z_stream stream;
+       int st;
+
+       if (left < 20)
+               die("truncated pack file");
+       data = base_sha1 + 20;
+       data_size = left - 20;
+       delta_data = xmalloc(delta_size);
+
+       memset(&stream, 0, sizeof(stream));
+
+       stream.next_in = data;
+       stream.avail_in = data_size;
+       stream.next_out = delta_data;
+       stream.avail_out = delta_size;
+
+       inflateInit(&stream);
+       st = inflate(&stream, Z_FINISH);
+       inflateEnd(&stream);
+       if ((st != Z_STREAM_END) || stream.total_out != delta_size)
+               die("delta data unpack failed");
+
+       /* The base entry _must_ be in the same pack */
+       if (!find_pack_entry_one(base_sha1, &base_ent, p))
+               die("failed to find delta-pack base object %s",
+                   sha1_to_hex(base_sha1));
+       base = unpack_entry_gently(&base_ent, type, &base_size);
+       if (!base)
+               die("failed to read delta-pack base object %s",
+                   sha1_to_hex(base_sha1));
+       result = patch_delta(base, base_size,
+                            delta_data, delta_size,
+                            &result_size);
+       if (!result)
+               die("failed to apply delta");
+       free(delta_data);
+       free(base);
+       *sizep = result_size;
+       return result;
+}
+
+static void *unpack_non_delta_entry(unsigned char *data,
+                                   unsigned long size,
+                                   unsigned long left)
+{
+       int st;
+       z_stream stream;
+       unsigned char *buffer;
+
+       buffer = xmalloc(size + 1);
+       buffer[size] = 0;
+       memset(&stream, 0, sizeof(stream));
+       stream.next_in = data;
+       stream.avail_in = left;
+       stream.next_out = buffer;
+       stream.avail_out = size;
+
+       inflateInit(&stream);
+       st = inflate(&stream, Z_FINISH);
+       inflateEnd(&stream);
+       if ((st != Z_STREAM_END) || stream.total_out != size) {
+               free(buffer);
+               return NULL;
+       }
+
+       return buffer;
+}
+
+static void *unpack_entry(struct pack_entry *entry,
+                         char *type, unsigned long *sizep)
+{
+       struct packed_git *p = entry->p;
+       void *retval;
+
+       if (use_packed_git(p))
+               die("cannot map packed file");
+       retval = unpack_entry_gently(entry, type, sizep);
+       unuse_packed_git(p);
+       if (!retval)
+               die("corrupted pack file");
+       return retval;
+}
+
+/* The caller is responsible for use_packed_git()/unuse_packed_git() pair */
+void *unpack_entry_gently(struct pack_entry *entry,
+                         char *type, unsigned long *sizep)
+{
+       struct packed_git *p = entry->p;
+       unsigned long offset, size, left;
+       unsigned char *pack;
+       enum object_type kind;
+       void *retval;
+
+       offset = unpack_object_header(p, entry->offset, &kind, &size);
+       pack = p->pack_base + offset;
+       left = p->pack_size - offset;
+       switch (kind) {
+       case OBJ_DELTA:
+               retval = unpack_delta_entry(pack, size, left, type, sizep, p);
+               return retval;
+       case OBJ_COMMIT:
+               strcpy(type, "commit");
+               break;
+       case OBJ_TREE:
+               strcpy(type, "tree");
+               break;
+       case OBJ_BLOB:
+               strcpy(type, "blob");
+               break;
+       case OBJ_TAG:
+               strcpy(type, "tag");
+               break;
+       default:
+               return NULL;
+       }
+       *sizep = size;
+       retval = unpack_non_delta_entry(pack, size, left);
+       return retval;
+}
+
+int num_packed_objects(const struct packed_git *p)
+{
+       /* See check_packed_git_idx() */
+       return (p->index_size - 20 - 20 - 4*256) / 24;
+}
+
+int nth_packed_object_sha1(const struct packed_git *p, int n,
+                          unsigned char* sha1)
+{
+       void *index = p->index_base + 256;
+       if (n < 0 || num_packed_objects(p) <= n)
+               return -1;
+       memcpy(sha1, (index + 24 * n + 4), 20);
+       return 0;
+}
+
+int find_pack_entry_one(const unsigned char *sha1,
+                       struct pack_entry *e, struct packed_git *p)
+{
+       unsigned int *level1_ofs = p->index_base;
+       int hi = ntohl(level1_ofs[*sha1]);
+       int lo = ((*sha1 == 0x0) ? 0 : ntohl(level1_ofs[*sha1 - 1]));
+       void *index = p->index_base + 256;
+
+       do {
+               int mi = (lo + hi) / 2;
+               int cmp = memcmp(index + 24 * mi + 4, sha1, 20);
+               if (!cmp) {
+                       e->offset = ntohl(*((int*)(index + 24 * mi)));
+                       memcpy(e->sha1, sha1, 20);
+                       e->p = p;
+                       return 1;
+               }
+               if (cmp > 0)
+                       hi = mi;
+               else
+                       lo = mi+1;
+       } while (lo < hi);
+       return 0;
+}
+
+static int find_pack_entry(const unsigned char *sha1, struct pack_entry *e)
+{
+       struct packed_git *p;
+       prepare_packed_git();
+
+       for (p = packed_git; p; p = p->next) {
+               if (find_pack_entry_one(sha1, e, p))
+                       return 1;
+       }
+       return 0;
+}
+
+struct packed_git *find_sha1_pack(const unsigned char *sha1, 
+                                 struct packed_git *packs)
+{
+       struct packed_git *p;
+       struct pack_entry e;
+
+       for (p = packs; p; p = p->next) {
+               if (find_pack_entry_one(sha1, &e, p))
+                       return p;
+       }
+       return NULL;
+       
+}
+
+int sha1_object_info(const unsigned char *sha1, char *type, unsigned long *sizep)
+{
+       int status;
+       unsigned long mapsize, size;
+       void *map;
+       z_stream stream;
+       char hdr[128];
+
+       map = map_sha1_file_internal(sha1, &mapsize);
+       if (!map) {
+               struct pack_entry e;
+
+               if (!find_pack_entry(sha1, &e))
+                       return error("unable to find %s", sha1_to_hex(sha1));
+               return packed_object_info(&e, type, sizep);
+       }
+       if (unpack_sha1_header(&stream, map, mapsize, hdr, sizeof(hdr)) < 0)
+               status = error("unable to unpack %s header",
+                              sha1_to_hex(sha1));
+       if (parse_sha1_header(hdr, type, &size) < 0)
+               status = error("unable to parse %s header", sha1_to_hex(sha1));
+       else {
+               status = 0;
+               if (sizep)
+                       *sizep = size;
+       }
+       inflateEnd(&stream);
+       munmap(map, mapsize);
+       return status;
+}
+
+static void *read_packed_sha1(const unsigned char *sha1, char *type, unsigned long *size)
+{
+       struct pack_entry e;
+
+       if (!find_pack_entry(sha1, &e)) {
+               error("cannot read sha1_file for %s", sha1_to_hex(sha1));
+               return NULL;
+       }
+       return unpack_entry(&e, type, size);
+}
+
+void * read_sha1_file(const unsigned char *sha1, char *type, unsigned long *size)
+{
+       unsigned long mapsize;
+       void *map, *buf;
+       struct pack_entry e;
+
+       if (find_pack_entry(sha1, &e))
+               return read_packed_sha1(sha1, type, size);
+       map = map_sha1_file_internal(sha1, &mapsize);
+       if (map) {
+               buf = unpack_sha1_file(map, mapsize, type, size);
+               munmap(map, mapsize);
+               return buf;
+       }
+       return NULL;
+}
+
+void *read_object_with_reference(const unsigned char *sha1,
+                                const char *required_type,
+                                unsigned long *size,
+                                unsigned char *actual_sha1_return)
+{
+       char type[20];
+       void *buffer;
+       unsigned long isize;
+       unsigned char actual_sha1[20];
+
+       memcpy(actual_sha1, sha1, 20);
+       while (1) {
+               int ref_length = -1;
+               const char *ref_type = NULL;
+
+               buffer = read_sha1_file(actual_sha1, type, &isize);
+               if (!buffer)
+                       return NULL;
+               if (!strcmp(type, required_type)) {
+                       *size = isize;
+                       if (actual_sha1_return)
+                               memcpy(actual_sha1_return, actual_sha1, 20);
+                       return buffer;
+               }
+               /* Handle references */
+               else if (!strcmp(type, "commit"))
+                       ref_type = "tree ";
+               else if (!strcmp(type, "tag"))
+                       ref_type = "object ";
+               else {
+                       free(buffer);
+                       return NULL;
+               }
+               ref_length = strlen(ref_type);
+
+               if (memcmp(buffer, ref_type, ref_length) ||
+                   get_sha1_hex(buffer + ref_length, actual_sha1)) {
+                       free(buffer);
+                       return NULL;
+               }
+               free(buffer);
+               /* Now we have the ID of the referred-to object in
+                * actual_sha1.  Check again. */
+       }
+}
+
+char *write_sha1_file_prepare(void *buf,
+                             unsigned long len,
+                             const char *type,
+                             unsigned char *sha1,
+                             unsigned char *hdr,
+                             int *hdrlen)
+{
+       SHA_CTX c;
+
+       /* Generate the header */
+       *hdrlen = sprintf((char *)hdr, "%s %lu", type, len)+1;
+
+       /* Sha1.. */
+       SHA1_Init(&c);
+       SHA1_Update(&c, hdr, *hdrlen);
+       SHA1_Update(&c, buf, len);
+       SHA1_Final(sha1, &c);
+
+       return sha1_file_name(sha1);
+}
+
+int write_sha1_file(void *buf, unsigned long len, const char *type, unsigned char *returnsha1)
+{
+       int size;
+       unsigned char *compressed;
+       z_stream stream;
+       unsigned char sha1[20];
+       char *filename;
+       static char tmpfile[PATH_MAX];
+       unsigned char hdr[50];
+       int fd, hdrlen, ret;
+
+       /* Normally if we have it in the pack then we do not bother writing
+        * it out into .git/objects/??/?{38} file.
+        */
+       filename = write_sha1_file_prepare(buf, len, type, sha1, hdr, &hdrlen);
+       if (returnsha1)
+               memcpy(returnsha1, sha1, 20);
+       if (has_sha1_file(sha1))
+               return 0;
+       fd = open(filename, O_RDONLY);
+       if (fd >= 0) {
+               /*
+                * FIXME!!! We might do collision checking here, but we'd
+                * need to uncompress the old file and check it. Later.
+                */
+               close(fd);
+               return 0;
+       }
+
+       if (errno != ENOENT) {
+               fprintf(stderr, "sha1 file %s: %s", filename, strerror(errno));
+               return -1;
+       }
+
+       snprintf(tmpfile, sizeof(tmpfile), "%s/obj_XXXXXX", get_object_directory());
+
+       fd = mkstemp(tmpfile);
+       if (fd < 0) {
+               fprintf(stderr, "unable to create temporary sha1 filename %s: %s", tmpfile, strerror(errno));
+               return -1;
+       }
+
+       /* Set it up */
+       memset(&stream, 0, sizeof(stream));
+       deflateInit(&stream, Z_BEST_COMPRESSION);
+       size = deflateBound(&stream, len+hdrlen);
+       compressed = xmalloc(size);
+
+       /* Compress it */
+       stream.next_out = compressed;
+       stream.avail_out = size;
+
+       /* First header.. */
+       stream.next_in = hdr;
+       stream.avail_in = hdrlen;
+       while (deflate(&stream, 0) == Z_OK)
+               /* nothing */;
+
+       /* Then the data itself.. */
+       stream.next_in = buf;
+       stream.avail_in = len;
+       while (deflate(&stream, Z_FINISH) == Z_OK)
+               /* nothing */;
+       deflateEnd(&stream);
+       size = stream.total_out;
+
+       if (write(fd, compressed, size) != size)
+               die("unable to write file");
+       fchmod(fd, 0444);
+       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;
+}
+
+int write_sha1_to_fd(int fd, const unsigned char *sha1)
+{
+       ssize_t size;
+       unsigned long objsize;
+       int posn = 0;
+       void *map = map_sha1_file_internal(sha1, &objsize);
+       void *buf = map;
+       void *temp_obj = NULL;
+       z_stream stream;
+
+       if (!buf) {
+               unsigned char *unpacked;
+               unsigned long len;
+               char type[20];
+               char hdr[50];
+               int hdrlen;
+               // need to unpack and recompress it by itself
+               unpacked = read_packed_sha1(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);
+               temp_obj = buf = xmalloc(size);
+
+               /* Compress it */
+               stream.next_out = buf;
+               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);
+               
+               objsize = stream.total_out;
+       }
+
+       do {
+               size = write(fd, buf + posn, objsize - posn);
+               if (size <= 0) {
+                       if (!size) {
+                               fprintf(stderr, "write closed");
+                       } else {
+                               perror("write ");
+                       }
+                       return -1;
+               }
+               posn += size;
+       } while (posn < objsize);
+
+       if (map)
+               munmap(map, objsize);
+       if (temp_obj)
+               free(temp_obj);
+
+       return 0;
+}
+
+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);
+
+       int local;
+       z_stream stream;
+       unsigned char real_sha1[20];
+       unsigned char discard[4096];
+       int ret;
+       SHA_CTX c;
+
+       local = open(filename, O_WRONLY | O_CREAT | O_EXCL, 0666);
+
+       if (local < 0)
+               return error("Couldn't open %s\n", filename);
+
+       memset(&stream, 0, sizeof(stream));
+
+       inflateInit(&stream);
+
+       SHA1_Init(&c);
+
+       do {
+               ssize_t size;
+               if (*bufposn) {
+                       stream.avail_in = *bufposn;
+                       stream.next_in = (unsigned char *) buffer;
+                       do {
+                               stream.next_out = discard;
+                               stream.avail_out = sizeof(discard);
+                               ret = inflate(&stream, Z_SYNC_FLUSH);
+                               SHA1_Update(&c, discard, sizeof(discard) -
+                                           stream.avail_out);
+                       } while (stream.avail_in && ret == Z_OK);
+                       write(local, buffer, *bufposn - stream.avail_in);
+                       memmove(buffer, buffer + *bufposn - stream.avail_in,
+                               stream.avail_in);
+                       *bufposn = stream.avail_in;
+                       if (ret != Z_OK)
+                               break;
+               }
+               size = read(fd, buffer + *bufposn, bufsize - *bufposn);
+               if (size <= 0) {
+                       close(local);
+                       unlink(filename);
+                       if (!size)
+                               return error("Connection closed?");
+                       perror("Reading from connection");
+                       return -1;
+               }
+               *bufposn += size;
+       } while (1);
+       inflateEnd(&stream);
+
+       close(local);
+       SHA1_Final(real_sha1, &c);
+       if (ret != Z_STREAM_END) {
+               unlink(filename);
+               return error("File %s corrupted", sha1_to_hex(sha1));
+       }
+       if (memcmp(sha1, real_sha1, 20)) {
+               unlink(filename);
+               return error("File %s has bad hash\n", sha1_to_hex(sha1));
+       }
+       
+       return 0;
+}
+
+int has_pack_index(const unsigned char *sha1)
+{
+       struct stat st;
+       if (stat(sha1_pack_index_name(sha1), &st))
+               return 0;
+       return 1;
+}
+
+int has_pack_file(const unsigned char *sha1)
+{
+       struct stat st;
+       if (stat(sha1_pack_name(sha1), &st))
+               return 0;
+       return 1;
+}
+
+int has_sha1_pack(const unsigned char *sha1)
+{
+       struct pack_entry e;
+       return find_pack_entry(sha1, &e);
+}
+
+int has_sha1_file(const unsigned char *sha1)
+{
+       struct stat st;
+       struct pack_entry e;
+
+       if (find_pack_entry(sha1, &e))
+               return 1;
+       return find_sha1_file(sha1, &st) ? 1 : 0;
+}
+
+int index_fd(unsigned char *sha1, int fd, struct stat *st, int write_object, const char *type)
+{
+       unsigned long size = st->st_size;
+       void *buf;
+       int ret;
+       unsigned char hdr[50];
+       int hdrlen;
+
+       buf = "";
+       if (size)
+               buf = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0);
+       close(fd);
+       if (buf == MAP_FAILED)
+               return -1;
+
+       if (!type)
+               type = "blob";
+       if (write_object)
+               ret = write_sha1_file(buf, size, type, sha1);
+       else {
+               write_sha1_file_prepare(buf, size, type, sha1, hdr, &hdrlen);
+               ret = 0;
+       }
+       if (size)
+               munmap(buf, size);
+       return ret;
+}
diff --git a/sha1_name.c b/sha1_name.c
new file mode 100644 (file)
index 0000000..5d1e441
--- /dev/null
@@ -0,0 +1,231 @@
+#include "cache.h"
+#include "commit.h"
+
+static int find_short_object_filename(int len, const char *name, unsigned char *sha1)
+{
+       static char dirname[PATH_MAX];
+       char hex[40];
+       DIR *dir;
+       int found;
+
+       snprintf(dirname, sizeof(dirname), "%s/%.2s", get_object_directory(), name);
+       dir = opendir(dirname);
+       sprintf(hex, "%.2s", name);
+       found = 0;
+       if (dir) {
+               struct dirent *de;
+               while ((de = readdir(dir)) != NULL) {
+                       if (strlen(de->d_name) != 38)
+                               continue;
+                       if (memcmp(de->d_name, name + 2, len-2))
+                               continue;
+                       memcpy(hex + 2, de->d_name, 38);
+                       if (++found > 1)
+                               break;
+               }
+               closedir(dir);
+       }
+       if (found == 1)
+               return get_sha1_hex(hex, sha1) == 0;
+       return 0;
+}
+
+static int match_sha(unsigned len, const unsigned char *a, const unsigned char *b)
+{
+       do {
+               if (*a != *b)
+                       return 0;
+               a++;
+               b++;
+               len -= 2;
+       } while (len > 1);
+       if (len)
+               if ((*a ^ *b) & 0xf0)
+                       return 0;
+       return 1;
+}
+
+static int find_short_packed_object(int len, const unsigned char *match, unsigned char *sha1)
+{
+       struct packed_git *p;
+
+       prepare_packed_git();
+       for (p = packed_git; p; p = p->next) {
+               unsigned num = num_packed_objects(p);
+               unsigned first = 0, last = num;
+               while (first < last) {
+                       unsigned mid = (first + last) / 2;
+                       unsigned char now[20];
+                       int cmp;
+
+                       nth_packed_object_sha1(p, mid, now);
+                       cmp = memcmp(match, now, 20);
+                       if (!cmp) {
+                               first = mid;
+                               break;
+                       }
+                       if (cmp > 0) {
+                               first = mid+1;
+                               continue;
+                       }
+                       last = mid;
+               }
+               if (first < num) {
+                       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;
+                               }
+                       }
+               }
+       }
+       return 0;
+}
+
+static int get_short_sha1(const char *name, unsigned char *sha1)
+{
+       int i;
+       char canonical[40];
+       unsigned char res[20];
+
+       memset(res, 0, 20);
+       memset(canonical, 'x', 40);
+       for (i = 0;;i++) {
+               unsigned char c = name[i];
+               unsigned char val;
+               if (!c || i > 40)
+                       break;
+               if (c >= '0' && c <= '9')
+                       val = c - '0';
+               else if (c >= 'a' && c <= 'f')
+                       val = c - 'a' + 10;
+               else if (c >= 'A' && c <='F') {
+                       val = c - 'A' + 10;
+                       c -= 'A' - 'a';
+               }
+               else
+                       return -1;
+               canonical[i] = c;
+               if (!(i & 1))
+                       val <<= 4;
+               res[i >> 1] |= val;
+       }
+       if (i < 4)
+               return -1;
+       if (find_short_object_filename(i, canonical, sha1))
+               return 0;
+       if (find_short_packed_object(i, res, sha1))
+               return 0;
+       return -1;
+}
+
+static int get_sha1_file(const char *path, unsigned char *result)
+{
+       char buffer[60];
+       int fd = open(path, O_RDONLY);
+       int len;
+
+       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 get_sha1_basic(const char *str, int len, unsigned char *sha1)
+{
+       static const char *prefix[] = {
+               "",
+               "refs",
+               "refs/tags",
+               "refs/heads",
+               "refs/snap",
+               NULL
+       };
+       const char **p;
+
+       if (!get_sha1_hex(str, sha1))
+               return 0;
+
+       for (p = prefix; *p; p++) {
+               char *pathname = git_path("%s/%.*s", *p, len, str);
+               if (!get_sha1_file(pathname, sha1))
+                       return 0;
+       }
+
+       return -1;
+}
+
+static int get_sha1_1(const char *name, int len, unsigned char *sha1);
+
+static int get_parent(const char *name, int len,
+                     unsigned char *result, int idx)
+{
+       unsigned char sha1[20];
+       int ret = get_sha1_1(name, len, sha1);
+       struct commit *commit;
+       struct commit_list *p;
+
+       if (ret)
+               return ret;
+       commit = lookup_commit_reference(sha1);
+       if (!commit)
+               return -1;
+       if (parse_commit(commit))
+               return -1;
+       if (!idx) {
+               memcpy(result, commit->object.sha1, 20);
+               return 0;
+       }
+       p = commit->parents;
+       while (p) {
+               if (!--idx) {
+                       memcpy(result, p->item->object.sha1, 20);
+                       return 0;
+               }
+               p = p->next;
+       }
+       return -1;
+}
+
+static int get_sha1_1(const char *name, int len, unsigned char *sha1)
+{
+       int parent, ret;
+
+       /* foo^[0-9] or foo^ (== foo^1); we do not do more than 9 parents. */
+       if (len > 2 && name[len-2] == '^' &&
+           name[len-1] >= '0' && name[len-1] <= '9') {
+               parent = name[len-1] - '0';
+               len -= 2;
+       }
+       else if (len > 1 && name[len-1] == '^') {
+               parent = 1;
+               len--;
+       } else
+               parent = -1;
+
+       if (0 <= parent) {
+               ret = get_parent(name, len, sha1, parent);
+               if (!ret)
+                       return 0;
+               else if(parent>0)
+                       return ret;
+       }
+       ret = get_sha1_basic(name, len, sha1);
+       if (!ret)
+               return 0;
+       return get_short_sha1(name, sha1);
+}
+
+/*
+ * This is like "get_sha1_basic()", except it allows "sha1 expressions",
+ * notably "xyz^" for "parent of xyz"
+ */
+int get_sha1(const char *name, unsigned char *sha1)
+{
+       return get_sha1_1(name, strlen(name), sha1);
+}
diff --git a/show-index.c b/show-index.c
new file mode 100644 (file)
index 0000000..c21d660
--- /dev/null
@@ -0,0 +1,28 @@
+#include "cache.h"
+
+int main(int argc, char **argv)
+{
+       int i;
+       unsigned nr;
+       unsigned int entry[6];
+       static unsigned int top_index[256];
+
+       if (fread(top_index, sizeof(top_index), 1, stdin) != 1)
+               die("unable to read idex");
+       nr = 0;
+       for (i = 0; i < 256; i++) {
+               unsigned n = ntohl(top_index[i]);
+               if (n < nr)
+                       die("corrupt index file");
+               nr = n;
+       }
+       for (i = 0; i < nr; i++) {
+               unsigned offset;
+
+               if (fread(entry, 24, 1, stdin) != 1)
+                       die("unable to read entry %u/%u", i, nr);
+               offset = ntohl(entry[0]);
+               printf("%u %s\n", offset, sha1_to_hex((void *)(entry+1)));
+       }
+       return 0;
+}
diff --git a/show-rev-cache.c b/show-rev-cache.c
new file mode 100644 (file)
index 0000000..fa92b87
--- /dev/null
@@ -0,0 +1,18 @@
+#include "cache.h"
+#include "rev-cache.h"
+
+static char *show_rev_cache_usage =
+"git-show-rev-cache <rev-cache-file>";
+
+int main(int ac, char **av)
+{
+       while (1 < ac && av[0][1] == '-') {
+               /* do flags here */
+               break;
+               ac--; av++;
+       }
+       if (ac != 2)
+               usage(show_rev_cache_usage);
+
+       return read_rev_cache(av[1], stdout, 1);
+}
diff --git a/ssh-pull.c b/ssh-pull.c
new file mode 100644 (file)
index 0000000..b7f313b
--- /dev/null
@@ -0,0 +1,126 @@
+#include "cache.h"
+#include "commit.h"
+#include "rsh.h"
+#include "pull.h"
+#include "refs.h"
+
+static int fd_in;
+static int fd_out;
+
+static unsigned char remote_version = 0;
+static unsigned char local_version = 1;
+
+static ssize_t force_write(int fd, void *buffer, size_t length)
+{
+       ssize_t ret = 0;
+       while (ret < length) {
+               ssize_t size = write(fd, buffer + ret, length - ret);
+               if (size < 0) {
+                       return size;
+               }
+               if (size == 0) {
+                       return ret;
+               }
+               ret += size;
+       }
+       return ret;
+}
+
+void prefetch(unsigned char *sha1)
+{
+       char type = 'o';
+       force_write(fd_out, &type, 1);
+       force_write(fd_out, sha1, 20);
+       //memcpy(requested + 20 * prefetches++, sha1, 20);
+}
+
+static char conn_buf[4096];
+static size_t conn_buf_posn = 0;
+
+int fetch(unsigned char *sha1)
+{
+       int ret;
+       signed char remote;
+
+       if (conn_buf_posn) {
+               remote = conn_buf[0];
+               memmove(conn_buf, conn_buf + 1, --conn_buf_posn);
+       } else {
+               if (read(fd_in, &remote, 1) < 1)
+                       return -1;
+       }
+       //fprintf(stderr, "Got %d\n", remote);
+       if (remote < 0)
+               return remote;
+       ret = write_sha1_from_fd(sha1, fd_in, conn_buf, 4096, &conn_buf_posn);
+       if (!ret)
+               pull_say("got %s\n", sha1_to_hex(sha1));
+       return ret;
+}
+
+static int get_version(void)
+{
+       char type = 'v';
+       write(fd_out, &type, 1);
+       write(fd_out, &local_version, 1);
+       if (read(fd_in, &remote_version, 1) < 1) {
+               return error("Couldn't read version from remote end");
+       }
+       return 0;
+}
+
+int fetch_ref(char *ref, unsigned char *sha1)
+{
+       signed char remote;
+       char type = 'r';
+       write(fd_out, &type, 1);
+       write(fd_out, ref, strlen(ref) + 1);
+       read(fd_in, &remote, 1);
+       if (remote < 0)
+               return remote;
+       read(fd_in, sha1, 20);
+       return 0;
+}
+
+int main(int argc, char **argv)
+{
+       char *commit_id;
+       char *url;
+       int arg = 1;
+       const char *prog = getenv("GIT_SSH_PUSH") ? : "git-ssh-push";
+
+       while (arg < argc && argv[arg][0] == '-') {
+               if (argv[arg][1] == 't') {
+                       get_tree = 1;
+               } else if (argv[arg][1] == 'c') {
+                       get_history = 1;
+               } else if (argv[arg][1] == 'a') {
+                       get_all = 1;
+                       get_tree = 1;
+                       get_history = 1;
+               } else if (argv[arg][1] == 'v') {
+                       get_verbosely = 1;
+               } else if (argv[arg][1] == 'w') {
+                       write_ref = argv[arg + 1];
+                       arg++;
+               }
+               arg++;
+       }
+       if (argc < arg + 2) {
+               usage("git-ssh-pull [-c] [-t] [-a] [-v] [-d] [--recover] [-w ref] commit-id url");
+               return 1;
+       }
+       commit_id = argv[arg];
+       url = argv[arg + 1];
+
+       if (setup_connection(&fd_in, &fd_out, prog, url, arg, argv + 1))
+               return 1;
+
+       if (get_version())
+               return 1;
+
+       if (pull(commit_id))
+               return 1;
+
+       return 0;
+}
diff --git a/ssh-push.c b/ssh-push.c
new file mode 100644 (file)
index 0000000..ee424a8
--- /dev/null
@@ -0,0 +1,131 @@
+#include "cache.h"
+#include "rsh.h"
+#include "refs.h"
+
+#include <string.h>
+
+static unsigned char local_version = 1;
+static unsigned char remote_version = 0;
+
+static int verbose = 0;
+
+static int serve_object(int fd_in, int fd_out) {
+       ssize_t size;
+       unsigned char sha1[20];
+       signed char remote;
+       int posn = 0;
+       do {
+               size = read(fd_in, sha1 + posn, 20 - posn);
+               if (size < 0) {
+                       perror("git-ssh-push: read ");
+                       return -1;
+               }
+               if (!size)
+                       return -1;
+               posn += size;
+       } while (posn < 20);
+       
+       if (verbose)
+               fprintf(stderr, "Serving %s\n", sha1_to_hex(sha1));
+
+       remote = 0;
+       
+       if (!has_sha1_file(sha1)) {
+               fprintf(stderr, "git-ssh-push: could not find %s\n", 
+                       sha1_to_hex(sha1));
+               remote = -1;
+       }
+       
+       write(fd_out, &remote, 1);
+       
+       if (remote < 0)
+               return 0;
+       
+       return write_sha1_to_fd(fd_out, sha1);
+}
+
+static int serve_version(int fd_in, int fd_out)
+{
+       if (read(fd_in, &remote_version, 1) < 1)
+               return -1;
+       write(fd_out, &local_version, 1);
+       return 0;
+}
+
+static int serve_ref(int fd_in, int fd_out)
+{
+       char ref[PATH_MAX];
+       unsigned char sha1[20];
+       int posn = 0;
+       signed char remote = 0;
+       do {
+               if (read(fd_in, ref + posn, 1) < 1)
+                       return -1;
+               posn++;
+       } while (ref[posn - 1]);
+
+       if (verbose)
+               fprintf(stderr, "Serving %s\n", ref);
+
+       if (get_ref_sha1(ref, sha1))
+               remote = -1;
+       write(fd_out, &remote, 1);
+       if (remote)
+               return 0;
+       write(fd_out, sha1, 20);
+        return 0;
+}
+
+
+static void service(int fd_in, int fd_out) {
+       char type;
+       int retval;
+       do {
+               retval = read(fd_in, &type, 1);
+               if (retval < 1) {
+                       if (retval < 0)
+                               perror("git-ssh-push: read ");
+                       return;
+               }
+               if (type == 'v' && serve_version(fd_in, fd_out))
+                       return;
+               if (type == 'o' && serve_object(fd_in, fd_out))
+                       return;
+               if (type == 'r' && serve_ref(fd_in, fd_out))
+                       return;
+       } while (1);
+}
+
+static const char ssh_push_usage[] =
+       "git-ssh-push [-c] [-t] [-a] [-w ref] commit-id url";
+
+int main(int argc, char **argv)
+{
+       int arg = 1;
+        char *commit_id;
+        char *url;
+       int fd_in, fd_out;
+       const char *prog = getenv("GIT_SSH_PULL") ? : "git-ssh-pull";
+       unsigned char sha1[20];
+       char hex[41];
+
+       while (arg < argc && argv[arg][0] == '-') {
+               if (argv[arg][1] == 'w')
+                       arg++;
+                arg++;
+        }
+       if (argc < arg + 2)
+               usage(ssh_push_usage);
+       commit_id = argv[arg];
+       url = argv[arg + 1];
+       if (get_sha1(commit_id, sha1))
+               usage(ssh_push_usage);
+       memcpy(hex, sha1_to_hex(sha1), sizeof(hex));
+       argv[arg] = hex;
+
+       if (setup_connection(&fd_in, &fd_out, prog, url, arg, argv + 1))
+               return 1;
+
+       service(fd_in, fd_out);
+       return 0;
+}
diff --git a/strbuf.c b/strbuf.c
new file mode 100644 (file)
index 0000000..9d9d8be
--- /dev/null
+++ b/strbuf.c
@@ -0,0 +1,44 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include "strbuf.h"
+#include "cache.h"
+
+void strbuf_init(struct strbuf *sb) {
+       sb->buf = NULL;
+       sb->eof = sb->alloc = sb->len = 0;
+}
+
+static void strbuf_begin(struct strbuf *sb) {
+       free(sb->buf);
+       strbuf_init(sb);
+}
+
+static void inline strbuf_add(struct strbuf *sb, int ch) {
+       if (sb->alloc <= sb->len) {
+               sb->alloc = sb->alloc * 3 / 2 + 16;
+               sb->buf = xrealloc(sb->buf, sb->alloc);
+       }
+       sb->buf[sb->len++] = ch;
+}
+
+static void strbuf_end(struct strbuf *sb) {
+       strbuf_add(sb, 0);
+}
+
+void read_line(struct strbuf *sb, FILE *fp, int term) {
+       int ch;
+       strbuf_begin(sb);
+       if (feof(fp)) {
+               sb->eof = 1;
+               return;
+       }
+       while ((ch = fgetc(fp)) != EOF) {
+               if (ch == term)
+                       break;
+               strbuf_add(sb, ch);
+       }
+       if (ch == EOF && sb->len == 0)
+               sb->eof = 1;
+       strbuf_end(sb);
+}
+
diff --git a/strbuf.h b/strbuf.h
new file mode 100644 (file)
index 0000000..74cc012
--- /dev/null
+++ b/strbuf.h
@@ -0,0 +1,13 @@
+#ifndef STRBUF_H
+#define STRBUF_H
+struct strbuf {
+       int alloc;
+       int len;
+       int eof;
+       char *buf;
+};
+
+extern void strbuf_init(struct strbuf *);
+extern void read_line(struct strbuf *, FILE *, int);
+
+#endif /* STRBUF_H */
diff --git a/stripspace.c b/stripspace.c
new file mode 100644 (file)
index 0000000..96cd0a8
--- /dev/null
@@ -0,0 +1,48 @@
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+
+/*
+ * Remove empty lines from the beginning and end.
+ *
+ * Turn multiple consecutive empty lines into just one
+ * empty line.
+ */
+static void cleanup(char *line)
+{
+       int len = strlen(line);
+
+       if (len > 1 && line[len-1] == '\n') {
+               do {
+                       unsigned char c = line[len-2];
+                       if (!isspace(c))
+                               break;
+                       line[len-2] = '\n';
+                       len--;
+                       line[len] = 0;
+               } while (len > 1);
+       }
+}
+
+int main(int argc, char **argv)
+{
+       int empties = -1;
+       char line[1024];
+
+       while (fgets(line, sizeof(line), stdin)) {
+               cleanup(line);
+
+               /* Not just an empty line? */
+               if (line[0] != '\n') {
+                       if (empties > 0)
+                               putchar('\n');
+                       empties = 0;
+                       fputs(line, stdout);
+                       continue;
+               }
+               if (empties < 0)
+                       continue;
+               empties++;
+       }
+       return 0;
+}
diff --git a/t/Makefile b/t/Makefile
new file mode 100644 (file)
index 0000000..6882e23
--- /dev/null
@@ -0,0 +1,15 @@
+# Run tests
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+
+#GIT_TEST_OPTS=--verbose --debug
+
+T = $(wildcard t[0-9][0-9][0-9][0-9]-*.sh)
+
+all:
+       @$(foreach t,$T,echo "*** $t ***"; sh $t $(GIT_TEST_OPTS) || exit; )
+       @rm -fr trash
+
+clean:
+       rm -fr trash
diff --git a/t/README b/t/README
new file mode 100644 (file)
index 0000000..ab47ef9
--- /dev/null
+++ b/t/README
@@ -0,0 +1,208 @@
+Core GIT Tests
+==============
+
+This directory holds many test scripts for core GIT tools.  The
+first part of this short document describes how to run the tests
+and read their output.
+
+When fixing the tools or adding enhancements, you are strongly
+encouraged to add tests in this directory to cover what you are
+trying to fix or enhance.  The later part of this short document
+describes how your test scripts should be organized.
+
+
+Running Tests
+-------------
+
+The easiest way to run tests is to say "make".  This runs all
+the tests.
+
+    *** t0000-basic.sh ***
+    *   ok 1: .git/objects should be empty after git-init-db in an empty repo.
+    *   ok 2: .git/objects should have 256 subdirectories.
+    *   ok 3: git-update-cache without --add should fail adding.
+    ...
+    *   ok 23: no diff after checkout and git-update-cache --refresh.
+    * passed all 23 test(s)
+    *** t0100-environment-names.sh ***
+    *   ok 1: using old names should issue warnings.
+    *   ok 2: using old names but having new names should not issue warnings.
+    ...
+
+Or you can run each test individually from command line, like
+this:
+
+    $ sh ./t3001-ls-files-killed.sh
+    *   ok 1: git-update-cache --add to add various paths.
+    *   ok 2: git-ls-files -k to show killed files.
+    *   ok 3: validate git-ls-files -k output.
+    * passed all 3 test(s)
+
+You can pass --verbose (or -v), --debug (or -d), and --immediate
+(or -i) command line argument to the test.
+
+--verbose::
+       This makes the test more verbose.  Specifically, the
+       command being run and their output if any are also
+       output.
+
+--debug::
+       This may help the person who is developing a new test.
+       It causes the command defined with test_debug to run.
+
+--immediate::
+       This causes the test to immediately exit upon the first
+       failed test.
+
+
+Naming Tests
+------------
+
+The test files are named as:
+
+       tNNNN-commandname-details.sh
+
+where N is a decimal digit.
+
+First digit tells the family:
+
+       0 - the absolute basics and global stuff
+       1 - the basic commands concerning database
+       2 - the basic commands concerning the working tree
+       3 - the other basic commands (e.g. ls-files)
+       4 - the diff commands
+       5 - the pull and exporting commands
+       6 - the revision tree commands (even e.g. merge-base)
+
+Second digit tells the particular command we are testing.
+
+Third digit (optionally) tells the particular switch or group of switches
+we are testing.
+
+If you create files under t/ directory (i.e. here) that is not
+the top-level test script, never name the file to match the above
+pattern.  The Makefile here considers all such files as the
+top-level test script and tries to run all of them.  A care is
+especially needed if you are creating a common test library
+file, similar to test-lib.sh, because such a library file may
+not be suitable for standalone execution.
+
+
+Writing Tests
+-------------
+
+The test script is written as a shell script.  It should start
+with the standard "#!/bin/sh" with copyright notices, and an
+assignment to variable 'test_description', like this:
+
+       #!/bin/sh
+       #
+       # Copyright (c) 2005 Junio C Hamano
+       #
+
+       test_description='xxx test (option --frotz)
+
+       This test registers the following structure in the cache
+       and tries to run git-ls-files with option --frotz.'
+
+
+Source 'test-lib.sh'
+--------------------
+
+After assigning test_description, the test script should source
+test-lib.sh like this:
+
+       . ./test-lib.sh
+
+This test harness library does the following things:
+
+ - If the script is invoked with command line argument --help
+   (or -h), it shows the test_description and exits.
+
+ - Creates an empty test directory with an empty .git/objects
+   database and chdir(2) into it.  This directory is 't/trash'
+   if you must know, but I do not think you care.
+
+ - Defines standard test helper functions for your scripts to
+   use.  These functions are designed to make all scripts behave
+   consistently when command line arguments --verbose (or -v),
+   --debug (or -d), and --immediate (or -i) is given.
+
+
+End with test_done
+------------------
+
+Your script will be a sequence of tests, using helper functions
+from the test harness library.  At the end of the script, call
+'test_done'.
+
+
+Test harness library
+--------------------
+
+There are a handful helper functions defined in the test harness
+library for your script to use.
+
+ - test_expect_success <message> <script>
+
+   This takes two strings as parameter, and evaluates the
+   <script>.  If it yields success, test is considered
+   successful.  <message> should state what it is testing.
+
+   Example:
+
+       test_expect_success \
+           'git-write-tree should be able to write an empty tree.' \
+           'tree=$(git-write-tree)'
+
+ - test_expect_failure <message> <script>
+
+   This is the opposite of test_expect_success.  If <script>
+   yields success, test is considered a failure.
+
+   Example:
+
+       test_expect_failure \
+           'git-update-cache without --add should fail adding.' \
+           'git-update-cache should-be-empty'
+
+ - test_debug <script>
+
+   This takes a single argument, <script>, and evaluates it only
+   when the test script is started with --debug command line
+   argument.  This is primarily meant for use during the
+   development of a new test script.
+
+ - test_done
+
+   Your test script must have test_done at the end.  Its purpose
+   is to summarize successes and failures in the test script and
+   exit with an appropriate error code.
+
+
+Tips for Writing Tests
+----------------------
+
+As with any programming projects, existing programs are the best
+source of the information.  However, do _not_ emulate
+t0000-basic.sh when writing your tests.  The test is special in
+that it tries to validate the very core of GIT.  For example, it
+knows that there will be 256 subdirectories under .git/objects/,
+and it knows that the object ID of an empty tree is a certain
+40-byte string.  This is deliberately done so in t0000-basic.sh
+because the things the very basic core test tries to achieve is
+to serve as a basis for people who are changing the GIT internal
+drastically.  For these people, after making certain changes,
+not seeing failures from the basic test _is_ a failure.  And
+such drastic changes to the core GIT that even changes these
+otherwise supposedly stable object IDs should be accompanied by
+an update to t0000-basic.sh.
+
+However, other tests that simply rely on basic parts of the core
+GIT working properly should not have that level of intimate
+knowledge of the core GIT internals.  If all the test scripts
+hardcoded the object IDs like t0000-basic.sh does, that defeats
+the purpose of t0000-basic.sh, which is to isolate that level of
+validation in one place.  Your test also ends up needing
+updating when such a change to the internal happens, so do _not_
+do it and leave the low level of validation to t0000-basic.sh.
diff --git a/t/diff-lib.sh b/t/diff-lib.sh
new file mode 100755 (executable)
index 0000000..a912f43
--- /dev/null
@@ -0,0 +1,35 @@
+:
+
+_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"
+sanitize_diff_raw='/^:/s/ '"$_x40"' '"$_x40"' \([A-Z]\)[0-9]*  / X X \1#       /'
+compare_diff_raw () {
+    # When heuristics are improved, the score numbers would change.
+    # Ignore them while comparing.
+    # Also we do not check SHA1 hash generation in this test, which
+    # is a job for t0000-basic.sh
+
+    sed -e "$sanitize_diff_raw" <"$1" >.tmp-1
+    sed -e "$sanitize_diff_raw" <"$2" >.tmp-2
+    diff -u .tmp-1 .tmp-2 && rm -f .tmp-1 .tmp-2
+}
+
+sanitize_diff_raw_z='/^:/s/ '"$_x40"' '"$_x40"' \([A-Z]\)[0-9]*$/ X X \1#/'
+compare_diff_raw_z () {
+    # When heuristics are improved, the score numbers would change.
+    # Ignore them while comparing.
+    # Also we do not check SHA1 hash generation in this test, which
+    # is a job for t0000-basic.sh
+
+    tr '\0' '\012' <"$1" | sed -e "$sanitize_diff_raw_z" >.tmp-1
+    tr '\0' '\012' <"$2" | sed -e "$sanitize_diff_raw_z" >.tmp-2
+    diff -u .tmp-1 .tmp-2 && rm -f .tmp-1 .tmp-2
+}
+
+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
+    diff -u .tmp-1 .tmp-2 && rm -f .tmp-1 .tmp-2
+}
diff --git a/t/lib-read-tree-m-3way.sh b/t/lib-read-tree-m-3way.sh
new file mode 100755 (executable)
index 0000000..d6645b4
--- /dev/null
@@ -0,0 +1,158 @@
+: Included from t1000-read-tree-m-3way.sh and others
+# Original tree.
+mkdir Z
+for a in N D M
+do
+    for b in N D M
+    do
+        p=$a$b
+       echo This is $p from the original tree. >$p
+       echo This is Z/$p from the original tree. >Z/$p
+       test_expect_success \
+           "adding test file $p and Z/$p" \
+           'git-update-cache --add $p &&
+           git-update-cache --add Z/$p'
+    done
+done
+echo This is SS from the original tree. >SS
+test_expect_success \
+    'adding test file SS' \
+    'git-update-cache --add SS'
+cat >TT <<\EOF
+This is a trivial merge sample text.
+Branch A is expected to upcase this word, here.
+There are some filler lines to avoid diff context
+conflicts here,
+like this one,
+and this one,
+and this one is yet another one of them.
+At the very end, here comes another line, that is
+the word, expected to be upcased by Branch B.
+This concludes the trivial merge sample file.
+EOF
+test_expect_success \
+    'adding test file TT' \
+    'git-update-cache --add TT'
+test_expect_success \
+    'prepare initial tree' \
+    'tree_O=$(git-write-tree)'
+
+################################################################
+# Branch A and B makes the changes according to the above matrix.
+
+################################################################
+# Branch A
+
+to_remove=$(echo D? Z/D?)
+rm -f $to_remove
+test_expect_success \
+    'change in branch A (removal)' \
+    'git-update-cache --remove $to_remove'
+
+for p in M? Z/M?
+do
+    echo This is modified $p in the branch A. >$p
+    test_expect_success \
+       'change in branch A (modification)' \
+        "git-update-cache $p"
+done
+
+for p in AN AA Z/AN Z/AA
+do
+    echo This is added $p in the branch A. >$p
+    test_expect_success \
+       'change in branch A (addition)' \
+       "git-update-cache --add $p"
+done
+
+echo This is SS from the modified tree. >SS
+echo This is LL from the modified tree. >LL
+test_expect_success \
+    'change in branch A (addition)' \
+    'git-update-cache --add LL &&
+     git-update-cache SS'
+mv TT TT-
+sed -e '/Branch A/s/word/WORD/g' <TT- >TT
+rm -f TT-
+test_expect_success \
+    'change in branch A (edit)' \
+    'git-update-cache TT'
+
+mkdir DF
+echo Branch A makes a file at DF/DF, creating a directory DF. >DF/DF
+test_expect_success \
+    'change in branch A (change file to directory)' \
+    'git-update-cache --add DF/DF'
+
+test_expect_success \
+    'recording branch A tree' \
+    'tree_A=$(git-write-tree)'
+          
+################################################################
+# Branch B
+# Start from O
+
+rm -rf [NDMASLT][NDMASLT] Z DF
+mkdir Z
+test_expect_success \
+    'reading original tree and checking out' \
+    'git-read-tree $tree_O &&
+     git-checkout-cache -a'
+
+to_remove=$(echo ?D Z/?D)
+rm -f $to_remove
+test_expect_success \
+    'change in branch B (removal)' \
+    "git-update-cache --remove $to_remove"
+
+for p in ?M Z/?M
+do
+    echo This is modified $p in the branch B. >$p
+    test_expect_success \
+       'change in branch B (modification)' \
+       "git-update-cache $p"
+done
+
+for p in NA AA Z/NA Z/AA
+do
+    echo This is added $p in the branch B. >$p
+    test_expect_success \
+       'change in branch B (addition)' \
+       "git-update-cache --add $p"
+done
+echo This is SS from the modified tree. >SS
+echo This is LL from the modified tree. >LL
+test_expect_success \
+    'change in branch B (addition and modification)' \
+    'git-update-cache --add LL &&
+     git-update-cache SS'
+mv TT TT-
+sed -e '/Branch B/s/word/WORD/g' <TT- >TT
+rm -f TT-
+test_expect_success \
+    'change in branch B (modification)' \
+    'git-update-cache TT'
+
+echo Branch B makes a file at DF. >DF
+test_expect_success \
+    'change in branch B (addition of a file to conflict with directory)' \
+    'git-update-cache --add DF'
+
+test_expect_success \
+    'recording branch B tree' \
+    'tree_B=$(git-write-tree)'
+
+test_expect_success \
+    'keep contents of 3 trees for easy access' \
+    'rm -f .git/index &&
+     git-read-tree $tree_O &&
+     mkdir .orig-O &&
+     git-checkout-cache --prefix=.orig-O/ -f -q -a &&
+     rm -f .git/index &&
+     git-read-tree $tree_A &&
+     mkdir .orig-A &&
+     git-checkout-cache --prefix=.orig-A/ -f -q -a &&
+     rm -f .git/index &&
+     git-read-tree $tree_B &&
+     mkdir .orig-B &&
+     git-checkout-cache --prefix=.orig-B/ -f -q -a'
diff --git a/t/t0000-basic.sh b/t/t0000-basic.sh
new file mode 100755 (executable)
index 0000000..547488b
--- /dev/null
@@ -0,0 +1,179 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+
+test_description='Test the very basics part #1.
+
+The rest of the test suite does not check the basic operation of git
+plumbing commands to work very carefully.  Their job is to concentrate
+on tricky features that caused bugs in the past to detect regression.
+
+This test runs very basic features, like registering things in cache,
+writing tree, etc.
+
+Note that this test *deliberately* hard-codes many expected object
+IDs.  When object ID computation changes, like in the previous case of
+swapping compression and hashing order, the person who is making the
+modification *should* take notice and update the test vectors here.
+'
+. ./test-lib.sh
+
+################################################################
+# init-db has been done in an empty repository.
+# make sure it is empty.
+
+find .git/objects -type f -print >should-be-empty
+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 257 subdirectories.  258 is counting "objects"
+find .git/objects -type d -print >full-of-directories
+test_expect_success \
+    '.git/objects should have 257 subdirectories.' \
+    'test $(wc -l < full-of-directories) = 258'
+
+################################################################
+# Basics of the basics
+
+# updating a new file without --add should fail.
+test_expect_failure \
+    'git-update-cache without --add should fail adding.' \
+    'git-update-cache should-be-empty'
+
+# and with --add it should succeed, even if it is empty (it used to fail).
+test_expect_success \
+    'git-update-cache with --add should succeed.' \
+    'git-update-cache --add should-be-empty'
+
+test_expect_success \
+    'writing tree out with git-write-tree' \
+    'tree=$(git-write-tree)'
+
+# we know the shape and contents of the tree and know the object ID for it.
+test_expect_success \
+    'validate object ID of a known tree.' \
+    'test "$tree" = 7bb943559a305bdd6bdee2cef6e5df2413c3d30a'
+
+# Removing paths.
+rm -f should-be-empty full-of-directories
+test_expect_failure \
+    'git-update-cache without --remove should fail removing.' \
+    'git-update-cache should-be-empty'
+
+test_expect_success \
+    'git-update-cache with --remove should be able to remove.' \
+    'git-update-cache --remove should-be-empty'
+
+# Empty tree can be written with recent write-tree.
+test_expect_success \
+    'git-write-tree should be able to write an empty tree.' \
+    'tree=$(git-write-tree)'
+
+test_expect_success \
+    'validate object ID of a known tree.' \
+    'test "$tree" = 4b825dc642cb6eb9a060e54bf8d69288fbee4904'
+
+# Various types of objects
+mkdir path2 path3 path3/subp3
+for p in path0 path2/file2 path3/file3 path3/subp3/file3
+do
+    echo "hello $p" >$p
+    ln -s "hello $p" ${p}sym
+done
+test_expect_success \
+    'adding various types of objects with git-update-cache --add.' \
+    'find path* ! -type d -print0 | xargs -0 git-update-cache --add'
+
+# Show them and see that matches what we expect.
+test_expect_success \
+    'showing stage with git-ls-files --stage' \
+    'git-ls-files --stage >current'
+
+cat >expected <<\EOF
+100644 f87290f8eb2cbbea7857214459a0739927eab154 0      path0
+120000 15a98433ae33114b085f3eb3bb03b832b3180a01 0      path0sym
+100644 3feff949ed00a62d9f7af97c15cd8a30595e7ac7 0      path2/file2
+120000 d8ce161addc5173867a3c3c730924388daedbc38 0      path2/file2sym
+100644 0aa34cae68d0878578ad119c86ca2b5ed5b28376 0      path3/file3
+120000 8599103969b43aff7e430efea79ca4636466794f 0      path3/file3sym
+100644 00fb5908cb97c2564a9783c0c64087333b3b464f 0      path3/subp3/file3
+120000 6649a1ebe9e9f1c553b66f5a6e74136a07ccc57c 0      path3/subp3/file3sym
+EOF
+test_expect_success \
+    'validate git-ls-files output for a known tree.' \
+    'diff current expected'
+
+test_expect_success \
+    'writing tree out with git-write-tree.' \
+    'tree=$(git-write-tree)'
+test_expect_success \
+    'validate object ID for a known tree.' \
+    'test "$tree" = 087704a96baf1c2d1c869a8b084481e121c88b5b'
+
+test_expect_success \
+    'showing tree with git-ls-tree' \
+    'git-ls-tree $tree >current'
+cat >expected <<\EOF
+100644 blob f87290f8eb2cbbea7857214459a0739927eab154   path0
+120000 blob 15a98433ae33114b085f3eb3bb03b832b3180a01   path0sym
+040000 tree 58a09c23e2ca152193f2786e06986b7b6712bdbe   path2
+040000 tree 21ae8269cacbe57ae09138dcc3a2887f904d02b3   path3
+EOF
+test_expect_success \
+    'git-ls-tree output for a known tree.' \
+    'diff current expected'
+
+test_expect_success \
+    'showing tree with git-ls-tree -r' \
+    'git-ls-tree -r $tree >current'
+cat >expected <<\EOF
+100644 blob f87290f8eb2cbbea7857214459a0739927eab154   path0
+120000 blob 15a98433ae33114b085f3eb3bb03b832b3180a01   path0sym
+040000 tree 58a09c23e2ca152193f2786e06986b7b6712bdbe   path2
+100644 blob 3feff949ed00a62d9f7af97c15cd8a30595e7ac7   path2/file2
+120000 blob d8ce161addc5173867a3c3c730924388daedbc38   path2/file2sym
+040000 tree 21ae8269cacbe57ae09138dcc3a2887f904d02b3   path3
+100644 blob 0aa34cae68d0878578ad119c86ca2b5ed5b28376   path3/file3
+120000 blob 8599103969b43aff7e430efea79ca4636466794f   path3/file3sym
+040000 tree 3c5e5399f3a333eddecce7a9b9465b63f65f51e2   path3/subp3
+100644 blob 00fb5908cb97c2564a9783c0c64087333b3b464f   path3/subp3/file3
+120000 blob 6649a1ebe9e9f1c553b66f5a6e74136a07ccc57c   path3/subp3/file3sym
+EOF
+test_expect_success \
+    'git-ls-tree -r output for a known tree.' \
+    'diff current expected'
+
+################################################################
+rm .git/index
+test_expect_success \
+    'git-read-tree followed by write-tree should be idempotent.' \
+    'git-read-tree $tree &&
+     test -f .git/index &&
+     newtree=$(git-write-tree) &&
+     test "$newtree" = "$tree"'
+
+cat >expected <<\EOF
+:100644 100644 f87290f8eb2cbbea7857214459a0739927eab154 0000000000000000000000000000000000000000 M     path0
+:120000 120000 15a98433ae33114b085f3eb3bb03b832b3180a01 0000000000000000000000000000000000000000 M     path0sym
+:100644 100644 3feff949ed00a62d9f7af97c15cd8a30595e7ac7 0000000000000000000000000000000000000000 M     path2/file2
+:120000 120000 d8ce161addc5173867a3c3c730924388daedbc38 0000000000000000000000000000000000000000 M     path2/file2sym
+:100644 100644 0aa34cae68d0878578ad119c86ca2b5ed5b28376 0000000000000000000000000000000000000000 M     path3/file3
+:120000 120000 8599103969b43aff7e430efea79ca4636466794f 0000000000000000000000000000000000000000 M     path3/file3sym
+:100644 100644 00fb5908cb97c2564a9783c0c64087333b3b464f 0000000000000000000000000000000000000000 M     path3/subp3/file3
+:120000 120000 6649a1ebe9e9f1c553b66f5a6e74136a07ccc57c 0000000000000000000000000000000000000000 M     path3/subp3/file3sym
+EOF
+test_expect_success \
+    'validate git-diff-files output for a know cache/work tree state.' \
+    'git-diff-files >current && diff >/dev/null -b current expected'
+
+test_expect_success \
+    'git-update-cache --refresh should succeed.' \
+    'git-update-cache --refresh'
+
+test_expect_success \
+    'no diff after checkout and git-update-cache --refresh.' \
+    'git-diff-files >current && cmp -s current /dev/null'
+
+test_done
diff --git a/t/t0100-environment-names.sh b/t/t0100-environment-names.sh
new file mode 100755 (executable)
index 0000000..9f851bc
--- /dev/null
@@ -0,0 +1,84 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+
+test_description='general environment name warning test.
+
+This test makes sure that use of deprecated environment variables
+trigger the warnings from gitenv().'
+
+env_vars='GIT_AUTHOR_DATE:AUTHOR_DATE
+GIT_AUTHOR_EMAIL:AUTHOR_EMAIL
+GIT_AUTHOR_NAME:AUTHOR_NAME
+GIT_COMMITTER_EMAIL:COMMIT_AUTHOR_EMAIL
+GIT_COMMITTER_NAME:COMMIT_AUTHOR_NAME
+GIT_ALTERNATE_OBJECT_DIRECTORIES:SHA1_FILE_DIRECTORIES
+GIT_OBJECT_DIRECTORY:SHA1_FILE_DIRECTORY
+'
+
+. ./test-lib.sh
+
+export_them () {
+       for ev in $env_vars
+       do
+               new=$(expr "$ev" : '\(.*\):')
+               old=$(expr "$ev" : '.*:\(.*\)')
+               # Build and eval the following:
+               # case "${VAR+set}" in set) export VAR;; esac
+               evstr='case "${'$new'+set}" in set) export '$new';; esac'
+               eval "$evstr"
+               evstr='case "${'$old'+set}" in set) export '$old';; esac'
+               eval "$evstr"
+       done
+}
+
+date >path0
+git-update-cache --add path0
+tree=$(git-write-tree)
+
+AUTHOR_DATE='Wed May 11 23:55:18 2005'
+AUTHOR_EMAIL='author@example.xz'
+AUTHOR_NAME='A U Thor'
+COMMIT_AUTHOR_EMAIL='author@example.xz'
+COMMIT_AUTHOR_NAME='A U Thor'
+SHA1_FILE_DIRECTORY=.git/objects
+
+export_them
+
+echo 'foo' | git-commit-tree $tree >/dev/null 2>errmsg
+cat >expected-err <<\EOF
+warning: Attempting to use SHA1_FILE_DIRECTORY
+warning: GIT environment variables have been renamed.
+warning: Please adjust your scripts and environment.
+warning: old AUTHOR_DATE => new GIT_AUTHOR_DATE
+warning: old AUTHOR_EMAIL => new GIT_AUTHOR_EMAIL
+warning: old AUTHOR_NAME => new GIT_AUTHOR_NAME
+warning: old COMMIT_AUTHOR_EMAIL => new GIT_COMMITTER_EMAIL
+warning: old COMMIT_AUTHOR_NAME => new GIT_COMMITTER_NAME
+warning: old SHA1_FILE_DIRECTORY => new GIT_OBJECT_DIRECTORY
+EOF
+sed -ne '/^warning: /p' <errmsg >generated-err
+
+test_expect_success \
+    'using old names should issue warnings.' \
+    'cmp generated-err expected-err'
+
+for ev in $env_vars
+do
+       new=$(expr "$ev" : '\(.*\):')
+       old=$(expr "$ev" : '.*:\(.*\)')
+       # Build and eval the following:
+       # NEWENV=$OLDENV
+       evstr="$new=\$$old"
+       eval "$evstr"
+done
+export_them
+echo 'foo' | git-commit-tree $tree >/dev/null 2>errmsg
+sed -ne '/^warning: /p' <errmsg >generated-err
+
+test_expect_success \
+    'using old names but having new names should not issue warnings.' \
+    'cmp generated-err /dev/null'
+
+test_done
diff --git a/t/t0110-environment-names-old.sh b/t/t0110-environment-names-old.sh
new file mode 100755 (executable)
index 0000000..c548b9b
--- /dev/null
@@ -0,0 +1,132 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+
+test_description='Using new and old environment names.
+
+This test makes sure that use of deprecated environment variables
+still works, using both new and old names makes new one take precedence,
+and GIT_DIR and GIT_ALTERNATE_OBJECT_DIRECTORIES mechanism works.'
+
+env_vars='GIT_AUTHOR_DATE:AUTHOR_DATE
+GIT_AUTHOR_EMAIL:AUTHOR_EMAIL
+GIT_AUTHOR_NAME:AUTHOR_NAME
+GIT_COMMITTER_EMAIL:COMMIT_AUTHOR_EMAIL
+GIT_COMMITTER_NAME:COMMIT_AUTHOR_NAME
+GIT_ALTERNATE_OBJECT_DIRECTORIES:SHA1_FILE_DIRECTORIES
+GIT_OBJECT_DIRECTORY:SHA1_FILE_DIRECTORY
+'
+
+. ./test-lib.sh
+
+export_them () {
+       for ev in $env_vars
+       do
+               new=$(expr "$ev" : '\(.*\):')
+               old=$(expr "$ev" : '.*:\(.*\)')
+               # Build and eval the following:
+               # case "${VAR+set}" in set) export VAR;; esac
+               evstr='case "${'$new'+set}" in set) export '$new';; esac'
+               eval "$evstr"
+               evstr='case "${'$old'+set}" in set) export '$old';; esac'
+               eval "$evstr"
+       done
+}
+
+SHA1_FILE_DIRECTORY=.svn/objects ;# whoa
+export SHA1_FILE_DIRECTORY
+
+rm -fr .git
+mkdir .svn
+test_expect_success \
+    'using SHA1_FILE_DIRECTORY in git-init-db' \
+    'git-init-db && test -d .svn/objects/cb'
+
+unset SHA1_FILE_DIRECTORY
+GIT_DIR=.svn
+export GIT_DIR
+rm -fr .git .svn
+mkdir .svn
+test_expect_success \
+    'using GIT_DIR in git-init-db' \
+    'git-init-db && test -d .svn/objects/cb'
+
+date >path0
+test_expect_success \
+    'using GIT_DIR in git-update-cache' \
+    'git-update-cache --add path0 && test -f .svn/index'
+
+sedScript='s|\(..\)|.svn/objects/\1/|'
+
+test_expect_success \
+    'using GIT_DIR in git-write-tree' \
+    'tree=$(git-write-tree) &&
+     test -f $(echo "$tree" | sed -e "$sedScript")'
+
+AUTHOR_DATE='Sat May 14 00:00:00 2005 -0000'
+AUTHOR_EMAIL='author@example.xz'
+AUTHOR_NAME='A U Thor'
+COMMIT_AUTHOR_EMAIL='author@example.xz'
+COMMIT_AUTHOR_NAME='A U Thor'
+export_them
+
+test_expect_success \
+    'using GIT_DIR and old variable names in git-commit-tree' \
+    'commit=$(echo foo | git-commit-tree $tree) &&
+     test -f $(echo "$commit" | sed -e "$sedScript")'
+
+test_expect_success \
+    'using GIT_DIR in git-cat-file' \
+    'git-cat-file commit $commit >current'
+
+cat >expected <<\EOF
+author A U Thor <author@example.xz>
+committer A U Thor <author@example.xz>
+EOF
+test_expect_success \
+    'verify old AUTHOR variables were used correctly in commit' \
+    'sed -ne '\''/^\(author\)/s|>.*|>|p'\'' -e'\''/^\(committer\)/s|>.*|>|p'\''\    current > out && cmp out expected'
+
+unset GIT_DIR
+test_expect_success \
+    'git-init-db without GIT_DIR' \
+    'git-init-db && test -d .git && test -d .git/objects/ef'
+
+SHA1_FILE_DIRECTORIES=.svn/objects
+export SHA1_FILE_DIRECTORIES
+
+test_expect_success \
+    'using SHA1_FILE_DIRECTORIES with git-ls-tree' \
+    'git-ls-tree $commit && git-ls-tree $tree'
+
+GIT_AUTHOR_DATE='Sat May 14 12:00:00 2005 -0000'
+GIT_AUTHOR_EMAIL='rohtua@example.xz'
+GIT_AUTHOR_NAME='R O Htua'
+GIT_COMMITTER_EMAIL='rohtua@example.xz'
+GIT_COMMITTER_NAME='R O Htua'
+export_them
+
+sedScript='s|\(..\)|.git/objects/\1/|'
+test_expect_success \
+    'using new author variables with git-commit-tree' \
+    'commit2=$(echo foo | git-commit-tree $tree) &&
+     test -f $(echo "$commit2" | sed -e "$sedScript")'
+
+GIT_ALTERNATE_OBJECT_DIRECTORIES=.git/objects
+GIT_DIR=nowhere
+export GIT_DIR GIT_ALTERNATE_OBJECT_DIRECTORIES
+
+test_expect_success \
+    'git-cat-file with GIT_DIR and GIT_ALTERNATE_OBJECT_DIRECTORIES' \
+    'git-cat-file commit $commit2 >current'
+
+cat >expected <<\EOF
+author R O Htua <rohtua@example.xz>
+committer R O Htua <rohtua@example.xz>
+EOF
+test_expect_success \
+    'verify new AUTHOR variables were used correctly in commit.' \
+    'sed -ne '\''/^\(author\)/s|>.*|>|p'\'' -e'\''/^\(committer\)/s|>.*|>|p'\''\    current > out && cmp out expected'
+
+test_done
diff --git a/t/t1000-read-tree-m-3way.sh b/t/t1000-read-tree-m-3way.sh
new file mode 100755 (executable)
index 0000000..89f0e81
--- /dev/null
@@ -0,0 +1,517 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+
+test_description='Three way merge with read-tree -m
+
+This test tries three-way merge with read-tree -m
+
+There is one ancestor (called O for Original) and two branches A
+and B derived from it.  We want to do a 3-way merge between A and
+B, using O as the common ancestor.
+
+    merge A O B
+
+Decisions are made by comparing contents of O, A and B pathname
+by pathname.  The result is determined by the following guiding
+principle:
+
+ - If only A does something to it and B does not touch it, take
+   whatever A does.
+
+ - If only B does something to it and A does not touch it, take
+   whatever B does.
+
+ - If both A and B does something but in the same way, take
+   whatever they do.
+
+ - If A and B does something but different things, we need a
+   3-way merge:
+
+   - We cannot do anything about the following cases:
+
+     * O does not have it.  A and B both must be adding to the
+       same path independently.
+
+     * A deletes it.  B must be modifying.
+
+   - Otherwise, A and B are modifying.  Run 3-way merge.
+
+First, the case matrix.
+
+ - Vertical axis is for A'\''s actions.
+ - Horizontal axis is for B'\''s actions.
+
+.----------------------------------------------------------------.
+| A        B | No Action  |   Delete   |   Modify   |    Add     |
+|------------+------------+------------+------------+------------|
+| No Action  |            |            |            |            |
+|            | select O   | delete     | select B   | select B   |
+|            |            |            |            |            |
+|------------+------------+------------+------------+------------|
+| Delete     |            |            | ********** |    can     |
+|            | delete     | delete     | merge      |    not     |
+|            |            |            |            |  happen    |
+|------------+------------+------------+------------+------------|
+| Modify     |            | ********** | ?????????? |    can     |
+|            | select A   | merge      | select A=B |    not     |
+|            |            |            | merge      |  happen    |
+|------------+------------+------------+------------+------------|
+| Add        |            |    can     |    can     | ?????????? |
+|            | select A   |    not     |    not     | select A=B |
+|            |            |  happen    |  happen    | merge      |
+.----------------------------------------------------------------.
+
+In addition:
+
+ SS: a special case of MM, where A and B makes the same modification.
+ LL: a special case of AA, where A and B creates the same file.
+ TT: a special case of MM, where A and B makes mergeable changes.
+ DF: a special case, where A makes a directory and B makes a file.
+
+'
+. ./test-lib.sh
+. ../lib-read-tree-m-3way.sh
+
+################################################################
+# Trivial "majority when 3 stages exist" merge plus #2ALT, #3ALT
+# and #5ALT trivial merges.
+
+cat >expected <<\EOF
+100644 X 2     AA
+100644 X 3     AA
+100644 X 0     AN
+100644 X 1     DD
+100644 X 3     DF
+100644 X 2     DF/DF
+100644 X 1     DM
+100644 X 3     DM
+100644 X 1     DN
+100644 X 3     DN
+100644 X 0     LL
+100644 X 1     MD
+100644 X 2     MD
+100644 X 1     MM
+100644 X 2     MM
+100644 X 3     MM
+100644 X 0     MN
+100644 X 0     NA
+100644 X 1     ND
+100644 X 2     ND
+100644 X 0     NM
+100644 X 0     NN
+100644 X 0     SS
+100644 X 1     TT
+100644 X 2     TT
+100644 X 3     TT
+100644 X 2     Z/AA
+100644 X 3     Z/AA
+100644 X 0     Z/AN
+100644 X 1     Z/DD
+100644 X 1     Z/DM
+100644 X 3     Z/DM
+100644 X 1     Z/DN
+100644 X 3     Z/DN
+100644 X 1     Z/MD
+100644 X 2     Z/MD
+100644 X 1     Z/MM
+100644 X 2     Z/MM
+100644 X 3     Z/MM
+100644 X 0     Z/MN
+100644 X 0     Z/NA
+100644 X 1     Z/ND
+100644 X 2     Z/ND
+100644 X 0     Z/NM
+100644 X 0     Z/NN
+EOF
+
+_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"
+
+check_result () {
+    git-ls-files --stage | sed -e 's/ '"$_x40"' / X /' >current &&
+    diff -u expected current
+}
+
+# This is done on an empty work directory, which is the normal
+# merge person behaviour.
+test_expect_success \
+    '3-way merge with git-read-tree -m, empty cache' \
+    "rm -fr [NDMALTS][NDMALTSF] Z &&
+     rm .git/index &&
+     git-read-tree -m $tree_O $tree_A $tree_B &&
+     check_result"
+
+# This starts out with the first head, which is the normal
+# patch submitter behaviour.
+test_expect_success \
+    '3-way merge with git-read-tree -m, match H' \
+    "rm -fr [NDMALTS][NDMALTSF] Z &&
+     rm .git/index &&
+     git-read-tree $tree_A &&
+     git-checkout-cache -f -u -a &&
+     git-read-tree -m $tree_O $tree_A $tree_B &&
+     check_result"
+
+: <<\END_OF_CASE_TABLE
+
+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.
+
+"git-diff-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.
+ ------------------------------------------------------------------
+  3  missing exists  missing   no merge    must match A and be
+                                           up-to-date, if exists.
+                               ------------------------------------
+    (ALT)                      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.
+ ------------------------------------------------------------------
+  6  exists  missing missing   no merge    must not exist.
+                               ------------------------------------
+    (ALT)                      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.
+ ------------------------------------------------------------------
+  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
+ ------------------------------------------------------------------
+ 11  exists  O!=A    O!=B      no merge    must match A and be
+                     A!=B                  up-to-date, if exists.
+ ------------------------------------------------------------------
+ 12  exists  O!=A    O!=B      take A      must match A, if exists.
+                     A==B
+ ------------------------------------------------------------------
+ 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)
+                                           match A and be up-to-date,
+                                           or (2) match B.
+ ------------------------------------------------------------------
+ 15  exists  O==A    O==B      take B      must match A if exists.
+-------------------------------------------------------------------
+
+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).
+
+END_OF_CASE_TABLE
+
+test_expect_failure \
+    '1 - must not have an entry not in A.' \
+    "rm -f .git/index XX &&
+     echo XX >XX &&
+     git-update-cache --add XX &&
+     git-read-tree -m $tree_O $tree_A $tree_B"
+
+test_expect_success \
+    '2 - must match B in !O && !A && B case.' \
+    "rm -f .git/index NA &&
+     cp .orig-B/NA NA &&
+     git-update-cache --add NA &&
+     git-read-tree -m $tree_O $tree_A $tree_B"
+
+test_expect_success \
+    '2 - matching B alone is OK in !O && !A && B case.' \
+    "rm -f .git/index NA &&
+     cp .orig-B/NA NA &&
+     git-update-cache --add NA &&
+     echo extra >>NA &&
+     git-read-tree -m $tree_O $tree_A $tree_B"
+
+test_expect_success \
+    '3 - must match A in !O && A && !B case.' \
+    "rm -f .git/index AN &&
+     cp .orig-A/AN AN &&
+     git-update-cache --add AN &&
+     git-read-tree -m $tree_O $tree_A $tree_B &&
+     check_result"
+
+test_expect_success \
+    '3 - matching A alone is OK in !O && A && !B case.' \
+    "rm -f .git/index AN &&
+     cp .orig-A/AN AN &&
+     git-update-cache --add AN &&
+     echo extra >>AN &&
+     git-read-tree -m $tree_O $tree_A $tree_B"
+
+test_expect_failure \
+    '3 (fail) - must match A in !O && A && !B case.' \
+    "rm -f .git/index AN &&
+     cp .orig-A/AN AN &&
+     echo extra >>AN &&
+     git-update-cache --add AN &&
+     git-read-tree -m $tree_O $tree_A $tree_B"
+
+test_expect_success \
+    '4 - must match and be up-to-date in !O && A && B && A!=B case.' \
+    "rm -f .git/index AA &&
+     cp .orig-A/AA AA &&
+     git-update-cache --add AA &&
+     git-read-tree -m $tree_O $tree_A $tree_B &&
+     check_result"
+
+test_expect_failure \
+    '4 (fail) - must match and be up-to-date in !O && A && B && A!=B case.' \
+    "rm -f .git/index AA &&
+     cp .orig-A/AA AA &&
+     git-update-cache --add AA &&
+     echo extra >>AA &&
+     git-read-tree -m $tree_O $tree_A $tree_B"
+
+test_expect_failure \
+    '4 (fail) - must match and be up-to-date in !O && A && B && A!=B case.' \
+    "rm -f .git/index AA &&
+     cp .orig-A/AA AA &&
+     echo extra >>AA &&
+     git-update-cache --add AA &&
+     git-read-tree -m $tree_O $tree_A $tree_B"
+
+test_expect_success \
+    '5 - must match in !O && A && B && A==B case.' \
+    "rm -f .git/index LL &&
+     cp .orig-A/LL LL &&
+     git-update-cache --add LL &&
+     git-read-tree -m $tree_O $tree_A $tree_B &&
+     check_result"
+
+test_expect_success \
+    '5 - must match in !O && A && B && A==B case.' \
+    "rm -f .git/index LL &&
+     cp .orig-A/LL LL &&
+     git-update-cache --add LL &&
+     echo extra >>LL &&
+     git-read-tree -m $tree_O $tree_A $tree_B &&
+     check_result"
+
+test_expect_failure \
+    '5 (fail) - must match A in !O && A && B && A==B case.' \
+    "rm -f .git/index LL &&
+     cp .orig-A/LL LL &&
+     echo extra >>LL &&
+     git-update-cache --add LL &&
+     git-read-tree -m $tree_O $tree_A $tree_B"
+
+test_expect_failure \
+    '6 - must not exist in O && !A && !B case' \
+    "rm -f .git/index DD &&
+     echo DD >DD
+     git-update-cache --add DD &&
+     git-read-tree -m $tree_O $tree_A $tree_B"
+
+test_expect_failure \
+    '7 - must not exist in O && !A && B && O!=B case' \
+    "rm -f .git/index DM &&
+     cp .orig-B/DM DM &&
+     git-update-cache --add DM &&
+     git-read-tree -m $tree_O $tree_A $tree_B"
+
+test_expect_failure \
+    '8 - must not exist in O && !A && B && O==B case' \
+    "rm -f .git/index DN &&
+     cp .orig-B/DN DN &&
+     git-update-cache --add DN &&
+     git-read-tree -m $tree_O $tree_A $tree_B"
+
+test_expect_success \
+    '9 - must match and be up-to-date in O && A && !B && O!=A case' \
+    "rm -f .git/index MD &&
+     cp .orig-A/MD MD &&
+     git-update-cache --add MD &&
+     git-read-tree -m $tree_O $tree_A $tree_B &&
+     check_result"
+
+test_expect_failure \
+    '9 (fail) - must match and be up-to-date in O && A && !B && O!=A case' \
+    "rm -f .git/index MD &&
+     cp .orig-A/MD MD &&
+     git-update-cache --add MD &&
+     echo extra >>MD &&
+     git-read-tree -m $tree_O $tree_A $tree_B"
+
+test_expect_failure \
+    '9 (fail) - must match and be up-to-date in O && A && !B && O!=A case' \
+    "rm -f .git/index MD &&
+     cp .orig-A/MD MD &&
+     echo extra >>MD &&
+     git-update-cache --add MD &&
+     git-read-tree -m $tree_O $tree_A $tree_B"
+
+test_expect_success \
+    '10 - must match and be up-to-date in O && A && !B && O==A case' \
+    "rm -f .git/index ND &&
+     cp .orig-A/ND ND &&
+     git-update-cache --add ND &&
+     git-read-tree -m $tree_O $tree_A $tree_B &&
+     check_result"
+
+test_expect_failure \
+    '10 (fail) - must match and be up-to-date in O && A && !B && O==A case' \
+    "rm -f .git/index ND &&
+     cp .orig-A/ND ND &&
+     git-update-cache --add ND &&
+     echo extra >>ND &&
+     git-read-tree -m $tree_O $tree_A $tree_B"
+
+test_expect_failure \
+    '10 (fail) - must match and be up-to-date in O && A && !B && O==A case' \
+    "rm -f .git/index ND &&
+     cp .orig-A/ND ND &&
+     echo extra >>ND &&
+     git-update-cache --add ND &&
+     git-read-tree -m $tree_O $tree_A $tree_B"
+
+test_expect_success \
+    '11 - must match and be up-to-date in O && A && B && O!=A && O!=B && A!=B case' \
+    "rm -f .git/index MM &&
+     cp .orig-A/MM MM &&
+     git-update-cache --add MM &&
+     git-read-tree -m $tree_O $tree_A $tree_B &&
+     check_result"
+
+test_expect_failure \
+    '11 (fail) - must match and be up-to-date in O && A && B && O!=A && O!=B && A!=B case' \
+    "rm -f .git/index MM &&
+     cp .orig-A/MM MM &&
+     git-update-cache --add MM &&
+     echo extra >>MM &&
+     git-read-tree -m $tree_O $tree_A $tree_B"
+
+test_expect_failure \
+    '11 (fail) - must match and be up-to-date in O && A && B && O!=A && O!=B && A!=B case' \
+    "rm -f .git/index MM &&
+     cp .orig-A/MM MM &&
+     echo extra >>MM &&
+     git-update-cache --add MM &&
+     git-read-tree -m $tree_O $tree_A $tree_B"
+
+test_expect_success \
+    '12 - must match A in O && A && B && O!=A && A==B case' \
+    "rm -f .git/index SS &&
+     cp .orig-A/SS SS &&
+     git-update-cache --add SS &&
+     git-read-tree -m $tree_O $tree_A $tree_B &&
+     check_result"
+
+test_expect_success \
+    '12 - must match A in O && A && B && O!=A && A==B case' \
+    "rm -f .git/index SS &&
+     cp .orig-A/SS SS &&
+     git-update-cache --add SS &&
+     echo extra >>SS &&
+     git-read-tree -m $tree_O $tree_A $tree_B &&
+     check_result"
+
+test_expect_failure \
+    '12 (fail) - must match A in O && A && B && O!=A && A==B case' \
+    "rm -f .git/index SS &&
+     cp .orig-A/SS SS &&
+     echo extra >>SS &&
+     git-update-cache --add SS &&
+     git-read-tree -m $tree_O $tree_A $tree_B"
+
+test_expect_success \
+    '13 - must match A in O && A && B && O!=A && O==B case' \
+    "rm -f .git/index MN &&
+     cp .orig-A/MN MN &&
+     git-update-cache --add MN &&
+     git-read-tree -m $tree_O $tree_A $tree_B &&
+     check_result"
+
+test_expect_success \
+    '13 - must match A in O && A && B && O!=A && O==B case' \
+    "rm -f .git/index MN &&
+     cp .orig-A/MN MN &&
+     git-update-cache --add MN &&
+     echo extra >>MN &&
+     git-read-tree -m $tree_O $tree_A $tree_B &&
+     check_result"
+
+test_expect_success \
+    '14 - must match and be up-to-date in O && A && B && O==A && O!=B case' \
+    "rm -f .git/index NM &&
+     cp .orig-A/NM NM &&
+     git-update-cache --add NM &&
+     git-read-tree -m $tree_O $tree_A $tree_B &&
+     check_result"
+
+test_expect_success \
+    '14 - may match B in O && A && B && O==A && O!=B case' \
+    "rm -f .git/index NM &&
+     cp .orig-B/NM NM &&
+     git-update-cache --add NM &&
+     echo extra >>NM &&
+     git-read-tree -m $tree_O $tree_A $tree_B &&
+     check_result"
+
+test_expect_failure \
+    '14 (fail) - must match and be up-to-date in O && A && B && O==A && O!=B case' \
+    "rm -f .git/index NM &&
+     cp .orig-A/NM NM &&
+     git-update-cache --add NM &&
+     echo extra >>NM &&
+     git-read-tree -m $tree_O $tree_A $tree_B"
+
+test_expect_failure \
+    '14 (fail) - must match and be up-to-date in O && A && B && O==A && O!=B case' \
+    "rm -f .git/index NM &&
+     cp .orig-A/NM NM &&
+     echo extra >>NM &&
+     git-update-cache --add NM &&
+     git-read-tree -m $tree_O $tree_A $tree_B"
+
+test_expect_success \
+    '15 - must match A in O && A && B && O==A && O==B case' \
+    "rm -f .git/index NN &&
+     cp .orig-A/NN NN &&
+     git-update-cache --add NN &&
+     git-read-tree -m $tree_O $tree_A $tree_B &&
+     check_result"
+
+test_expect_success \
+    '15 - must match A in O && A && B && O==A && O==B case' \
+    "rm -f .git/index NN &&
+     cp .orig-A/NN NN &&
+     git-update-cache --add NN &&
+     echo extra >>NN &&
+     git-read-tree -m $tree_O $tree_A $tree_B &&
+     check_result"
+
+test_expect_failure \
+    '15 (fail) - must match A in O && A && B && O==A && O==B case' \
+    "rm -f .git/index NN &&
+     cp .orig-A/NN NN &&
+     echo extra >>NN &&
+     git-update-cache --add NN &&
+     git-read-tree -m $tree_O $tree_A $tree_B"
+
+test_done
diff --git a/t/t1001-read-tree-m-2way.sh b/t/t1001-read-tree-m-2way.sh
new file mode 100755 (executable)
index 0000000..b3e6bd5
--- /dev/null
@@ -0,0 +1,344 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+
+test_description='Two way merge with read-tree -m $H $M
+
+This test tries two-way merge (aka fast forward with carry forward).
+
+There is the head (called H) and another commit (called M), which is
+simply ahead of H.  The index and the work tree contains a state that
+is derived from H, but may also have local changes.  This test checks
+all the combinations described in the two-tree merge "carry forward"
+rules, found in <Documentation/git-rev-tree.txt>.
+
+In the test, these paths are used:
+        bozbar  - in H, stays in M, modified from bozbar to gnusto
+        frotz   - not in H added in M
+        nitfol  - in H, stays in M unmodified
+        rezrov  - in H, deleted in M
+        yomin   - not in H nor M
+'
+. ./test-lib.sh
+
+read_tree_twoway () {
+    git-read-tree -m "$1" "$2" && git-ls-files --stage
+}
+
+_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"
+compare_change () {
+       sed -n >current \
+           -e '/^--- /d; /^+++ /d; /^@@ /d;' \
+           -e 's/^\([-+][0-7][0-7][0-7][0-7][0-7][0-7]\) '"$_x40"' /\1 X /p' \
+           "$1"
+       diff -u expected current
+}
+
+check_cache_at () {
+       clean_if_empty=`git-diff-files "$1"`
+       case "$clean_if_empty" in
+       '')  echo "$1: clean" ;;
+       ?*)  echo "$1: dirty" ;;
+       esac
+       case "$2,$clean_if_empty" in
+       clean,)         :     ;;
+       clean,?*)       false ;;
+       dirty,)         false ;;
+       dirty,?*)       :     ;;
+       esac
+}
+
+cat >bozbar-old <<\EOF
+This is a sample file used in two-way fast forward merge
+tests.  Its second line ends with a magic word bozbar
+which will be modified by the merged head to gnusto.
+It has some extra lines so that external tools can
+successfully merge independent changes made to later
+lines (such as this one), avoiding line conflicts.
+EOF
+
+sed -e 's/bozbar/gnusto (earlier bozbar)/' bozbar-old >bozbar-new
+
+test_expect_success \
+    setup \
+    'echo frotz >frotz &&
+     echo nitfol >nitfol &&
+     cat bozbar-old >bozbar &&
+     echo rezrov >rezrov &&
+     echo yomin >yomin &&
+     git-update-cache --add nitfol bozbar rezrov &&
+     treeH=`git-write-tree` &&
+     echo treeH $treeH &&
+     git-ls-tree $treeH &&
+
+     cat bozbar-new >bozbar &&
+     git-update-cache --add frotz bozbar --force-remove rezrov &&
+     git-ls-files --stage >M.out &&
+     treeM=`git-write-tree` &&
+     echo treeM $treeM &&
+     git-ls-tree $treeM &&
+     git-diff-tree $treeH $treeM'
+
+test_expect_success \
+    '1, 2, 3 - no carry forward' \
+    'rm -f .git/index &&
+     read_tree_twoway $treeH $treeM &&
+     git-ls-files --stage >1-3.out &&
+     diff -u M.out 1-3.out &&
+     check_cache_at bozbar dirty &&
+     check_cache_at frotz dirty &&
+     check_cache_at nitfol dirty'
+
+echo '+100644 X 0      yomin' >expected
+
+test_expect_success \
+    '4 - carry forward local addition.' \
+    'rm -f .git/index &&
+     git-read-tree $treeH &&
+     git-checkout-cache -u -f -q -a &&
+     git-update-cache --add yomin &&
+     read_tree_twoway $treeH $treeM &&
+     git-ls-files --stage >4.out || exit
+     diff -u M.out 4.out >4diff.out
+     compare_change 4diff.out expected &&
+     check_cache_at yomin clean'
+
+test_expect_success \
+    '5 - carry forward local addition.' \
+    'rm -f .git/index &&
+     git-read-tree $treeH &&
+     git-checkout-cache -u -f -q -a &&
+     echo yomin >yomin &&
+     git-update-cache --add yomin &&
+     echo yomin yomin >yomin &&
+     read_tree_twoway $treeH $treeM &&
+     git-ls-files --stage >5.out || exit
+     diff -u M.out 5.out >5diff.out
+     compare_change 5diff.out expected &&
+     check_cache_at yomin dirty'
+
+test_expect_success \
+    '6 - local addition already has the same.' \
+    'rm -f .git/index &&
+     git-read-tree $treeH &&
+     git-checkout-cache -u -f -q -a &&
+     git-update-cache --add frotz &&
+     read_tree_twoway $treeH $treeM &&
+     git-ls-files --stage >6.out &&
+     diff -u M.out 6.out &&
+     check_cache_at frotz clean'
+
+test_expect_success \
+    '7 - local addition already has the same.' \
+    'rm -f .git/index &&
+     git-read-tree $treeH &&
+     git-checkout-cache -u -f -q -a &&
+     echo frotz >frotz &&
+     git-update-cache --add frotz &&
+     echo frotz frotz >frotz &&
+     read_tree_twoway $treeH $treeM &&
+     git-ls-files --stage >7.out &&
+     diff -u M.out 7.out &&
+     check_cache_at frotz dirty'
+
+test_expect_success \
+    '8 - conflicting addition.' \
+    'rm -f .git/index &&
+     git-read-tree $treeH &&
+     git-checkout-cache -u -f -q -a &&
+     echo frotz frotz >frotz &&
+     git-update-cache --add frotz &&
+     if read_tree_twoway $treeH $treeM; then false; else :; fi'
+
+test_expect_success \
+    '9 - conflicting addition.' \
+    'rm -f .git/index &&
+     git-read-tree $treeH &&
+     git-checkout-cache -u -f -q -a &&
+     echo frotz frotz >frotz &&
+     git-update-cache --add frotz &&
+     echo frotz >frotz &&
+     if read_tree_twoway $treeH $treeM; then false; else :; fi'
+
+test_expect_success \
+    '10 - path removed.' \
+    'rm -f .git/index &&
+     git-read-tree $treeH &&
+     git-checkout-cache -u -f -q -a &&
+     echo rezrov >rezrov &&
+     git-update-cache --add rezrov &&
+     read_tree_twoway $treeH $treeM &&
+     git-ls-files --stage >10.out &&
+     diff -u M.out 10.out'
+
+test_expect_success \
+    '11 - dirty path removed.' \
+    'rm -f .git/index &&
+     git-read-tree $treeH &&
+     git-checkout-cache -u -f -q -a &&
+     echo rezrov >rezrov &&
+     git-update-cache --add rezrov &&
+     echo rezrov rezrov >rezrov &&
+     if read_tree_twoway $treeH $treeM; then false; else :; fi'
+
+test_expect_success \
+    '12 - unmatching local changes being removed.' \
+    'rm -f .git/index &&
+     git-read-tree $treeH &&
+     git-checkout-cache -u -f -q -a &&
+     echo rezrov rezrov >rezrov &&
+     git-update-cache --add rezrov &&
+     if read_tree_twoway $treeH $treeM; then false; else :; fi'
+
+test_expect_success \
+    '13 - unmatching local changes being removed.' \
+    'rm -f .git/index &&
+     git-read-tree $treeH &&
+     git-checkout-cache -u -f -q -a &&
+     echo rezrov rezrov >rezrov &&
+     git-update-cache --add rezrov &&
+     echo rezrov >rezrov &&
+     if read_tree_twoway $treeH $treeM; then false; else :; fi'
+
+cat >expected <<EOF
+-100644 X 0    nitfol
++100644 X 0    nitfol
+EOF
+
+test_expect_success \
+    '14 - unchanged in two heads.' \
+    'rm -f .git/index &&
+     git-read-tree $treeH &&
+     git-checkout-cache -u -f -q -a &&
+     echo nitfol nitfol >nitfol &&
+     git-update-cache --add nitfol &&
+     read_tree_twoway $treeH $treeM &&
+     git-ls-files --stage >14.out || exit
+     diff -u M.out 14.out >14diff.out
+     compare_change 14diff.out expected &&
+     check_cache_at nitfol clean'
+
+test_expect_success \
+    '15 - unchanged in two heads.' \
+    'rm -f .git/index &&
+     git-read-tree $treeH &&
+     git-checkout-cache -u -f -q -a &&
+     echo nitfol nitfol >nitfol &&
+     git-update-cache --add nitfol &&
+     echo nitfol nitfol nitfol >nitfol &&
+     read_tree_twoway $treeH $treeM &&
+     git-ls-files --stage >15.out || exit
+     diff -u M.out 15.out >15diff.out
+     compare_change 15diff.out expected &&
+     check_cache_at nitfol dirty'
+
+test_expect_success \
+    '16 - conflicting local change.' \
+    'rm -f .git/index &&
+     git-read-tree $treeH &&
+     git-checkout-cache -u -f -q -a &&
+     echo bozbar bozbar >bozbar &&
+     git-update-cache --add bozbar &&
+     if read_tree_twoway $treeH $treeM; then false; else :; fi'
+
+test_expect_success \
+    '17 - conflicting local change.' \
+    'rm -f .git/index &&
+     git-read-tree $treeH &&
+     git-checkout-cache -u -f -q -a &&
+     echo bozbar bozbar >bozbar &&
+     git-update-cache --add bozbar &&
+     echo bozbar bozbar bozbar >bozbar &&
+     if read_tree_twoway $treeH $treeM; then false; else :; fi'
+
+test_expect_success \
+    '18 - local change already having a good result.' \
+    'rm -f .git/index &&
+     git-read-tree $treeH &&
+     git-checkout-cache -u -f -q -a &&
+     cat bozbar-new >bozbar &&
+     git-update-cache --add bozbar &&
+     read_tree_twoway $treeH $treeM &&
+     git-ls-files --stage >18.out &&
+     diff -u M.out 18.out &&
+     check_cache_at bozbar clean'
+
+test_expect_success \
+    '19 - local change already having a good result, further modified.' \
+    'rm -f .git/index &&
+     git-read-tree $treeH &&
+     git-checkout-cache -u -f -q -a &&
+     cat bozbar-new >bozbar &&
+     git-update-cache --add bozbar &&
+     echo gnusto gnusto >bozbar &&
+     read_tree_twoway $treeH $treeM &&
+     git-ls-files --stage >19.out &&
+     diff -u M.out 19.out &&
+     check_cache_at bozbar dirty'
+
+test_expect_success \
+    '20 - no local change, use new tree.' \
+    'rm -f .git/index &&
+     git-read-tree $treeH &&
+     git-checkout-cache -u -f -q -a &&
+     cat bozbar-old >bozbar &&
+     git-update-cache --add bozbar &&
+     read_tree_twoway $treeH $treeM &&
+     git-ls-files --stage >20.out &&
+     diff -u M.out 20.out &&
+     check_cache_at bozbar dirty'
+
+test_expect_success \
+    '21 - no local change, dirty cache.' \
+    'rm -f .git/index &&
+     git-read-tree $treeH &&
+     git-checkout-cache -u -f -q -a &&
+     cat bozbar-old >bozbar &&
+     git-update-cache --add bozbar &&
+     echo gnusto gnusto >bozbar &&
+     if read_tree_twoway $treeH $treeM; then false; else :; fi'
+
+# This fails with straight two-way fast forward.
+test_expect_success \
+    '22 - local change cache updated.' \
+    'rm -f .git/index &&
+     git-read-tree $treeH &&
+     git-checkout-cache -u -f -q -a &&
+     sed -e "s/such as/SUCH AS/" bozbar-old >bozbar &&
+     git-update-cache --add bozbar &&
+     if read_tree_twoway $treeH $treeM; then false; else :; fi'
+
+# Also make sure we did not break DF vs DF/DF case.
+test_expect_success \
+    'DF vs DF/DF case setup.' \
+    'rm -f .git/index &&
+     echo DF >DF &&
+     git-update-cache --add DF &&
+     treeDF=`git-write-tree` &&
+     echo treeDF $treeDF &&
+     git-ls-tree $treeDF &&
+
+     rm -f DF &&
+     mkdir DF &&
+     echo DF/DF >DF/DF &&
+     git-update-cache --add --remove DF DF/DF &&
+     treeDFDF=`git-write-tree` &&
+     echo treeDFDF $treeDFDF &&
+     git-ls-tree $treeDFDF &&
+     git-ls-files --stage >DFDF.out'
+
+test_expect_success \
+    'DF vs DF/DF case test.' \
+    'rm -f .git/index &&
+     rm -fr DF &&
+     echo DF >DF &&
+     git-update-cache --add DF &&
+     read_tree_twoway $treeDF $treeDFDF &&
+     git-ls-files --stage >DFDFcheck.out &&
+     diff -u DFDF.out DFDFcheck.out &&
+     check_cache_at DF/DF dirty &&
+     :'
+
+test_done
diff --git a/t/t1002-read-tree-m-u-2way.sh b/t/t1002-read-tree-m-u-2way.sh
new file mode 100755 (executable)
index 0000000..2f1ee79
--- /dev/null
@@ -0,0 +1,324 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+
+test_description='Two way merge with read-tree -m -u $H $M
+
+This is identical to t1001, but uses -u to update the work tree as well.
+
+'
+. ./test-lib.sh
+
+_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"
+compare_change () {
+       sed >current \
+           -e '/^--- /d; /^+++ /d; /^@@ /d;' \
+           -e 's/^\(.[0-7][0-7][0-7][0-7][0-7][0-7]\) '"$_x40"' /\1 X /' "$1"
+       diff -u expected current
+}
+
+check_cache_at () {
+       clean_if_empty=`git-diff-files "$1"`
+       case "$clean_if_empty" in
+       '')  echo "$1: clean" ;;
+       ?*)  echo "$1: dirty" ;;
+       esac
+       case "$2,$clean_if_empty" in
+       clean,)         :     ;;
+       clean,?*)       false ;;
+       dirty,)         false ;;
+       dirty,?*)       :     ;;
+       esac
+}
+
+test_expect_success \
+    setup \
+    'echo frotz >frotz &&
+     echo nitfol >nitfol &&
+     echo bozbar >bozbar &&
+     echo rezrov >rezrov &&
+     echo yomin >yomin &&
+     git-update-cache --add nitfol bozbar rezrov &&
+     treeH=`git-write-tree` &&
+     echo treeH $treeH &&
+     git-ls-tree $treeH &&
+
+     echo gnusto >bozbar &&
+     git-update-cache --add frotz bozbar --force-remove rezrov &&
+     git-ls-files --stage >M.out &&
+     treeM=`git-write-tree` &&
+     echo treeM $treeM &&
+     git-ls-tree $treeM &&
+     sum bozbar frotz nitfol >M.sum &&
+     git-diff-tree $treeH $treeM'
+
+test_expect_success \
+    '1, 2, 3 - no carry forward' \
+    'rm -f .git/index &&
+     git-read-tree -m -u $treeH $treeM &&
+     git-ls-files --stage >1-3.out &&
+     cmp M.out 1-3.out &&
+     sum bozbar frotz nitfol >actual3.sum &&
+     cmp M.sum actual3.sum &&
+     check_cache_at bozbar clean &&
+     check_cache_at frotz clean &&
+     check_cache_at nitfol clean'
+
+echo '+100644 X 0      yomin' >expected
+
+test_expect_success \
+    '4 - carry forward local addition.' \
+    'rm -f .git/index &&
+     git-update-cache --add yomin &&
+     git-read-tree -m -u $treeH $treeM &&
+     git-ls-files --stage >4.out || exit
+     diff --unified=0 M.out 4.out >4diff.out
+     compare_change 4diff.out expected &&
+     check_cache_at yomin clean &&
+     sum bozbar frotz nitfol >actual4.sum &&
+     cmp M.sum actual4.sum &&
+     echo yomin >yomin1 &&
+     diff yomin yomin1 &&
+     rm -f yomin1'
+
+test_expect_success \
+    '5 - carry forward local addition.' \
+    'rm -f .git/index &&
+     echo yomin >yomin &&
+     git-update-cache --add yomin &&
+     echo yomin yomin >yomin &&
+     git-read-tree -m -u $treeH $treeM &&
+     git-ls-files --stage >5.out || exit
+     diff --unified=0 M.out 5.out >5diff.out
+     compare_change 5diff.out expected &&
+     check_cache_at yomin dirty &&
+     sum bozbar frotz nitfol >actual5.sum &&
+     cmp M.sum actual5.sum &&
+     : dirty index should have prevented -u from checking it out. &&
+     echo yomin yomin >yomin1 &&
+     diff yomin yomin1 &&
+     rm -f yomin1'
+
+test_expect_success \
+    '6 - local addition already has the same.' \
+    'rm -f .git/index &&
+     git-update-cache --add frotz &&
+     git-read-tree -m -u $treeH $treeM &&
+     git-ls-files --stage >6.out &&
+     diff --unified=0 M.out 6.out &&
+     check_cache_at frotz clean &&
+     sum bozbar frotz nitfol >actual3.sum &&
+     cmp M.sum actual3.sum &&
+     echo frotz >frotz1 &&
+     diff frotz frotz1 &&
+     rm -f frotz1'
+
+test_expect_success \
+    '7 - local addition already has the same.' \
+    'rm -f .git/index &&
+     echo frotz >frotz &&
+     git-update-cache --add frotz &&
+     echo frotz frotz >frotz &&
+     git-read-tree -m -u $treeH $treeM &&
+     git-ls-files --stage >7.out &&
+     diff --unified=0 M.out 7.out &&
+     check_cache_at frotz dirty &&
+     sum bozbar frotz nitfol >actual7.sum &&
+     if cmp M.sum actual7.sum; then false; else :; fi &&
+     : dirty index should have prevented -u from checking it out. &&
+     echo frotz frotz >frotz1 &&
+     diff frotz frotz1 &&
+     rm -f frotz1'
+
+test_expect_success \
+    '8 - conflicting addition.' \
+    'rm -f .git/index &&
+     echo frotz frotz >frotz &&
+     git-update-cache --add frotz &&
+     if git-read-tree -m -u $treeH $treeM; then false; else :; fi'
+
+test_expect_success \
+    '9 - conflicting addition.' \
+    'rm -f .git/index &&
+     echo frotz frotz >frotz &&
+     git-update-cache --add frotz &&
+     echo frotz >frotz &&
+     if git-read-tree -m -u $treeH $treeM; then false; else :; fi'
+
+test_expect_success \
+    '10 - path removed.' \
+    'rm -f .git/index &&
+     echo rezrov >rezrov &&
+     git-update-cache --add rezrov &&
+     git-read-tree -m -u $treeH $treeM &&
+     git-ls-files --stage >10.out &&
+     cmp M.out 10.out &&
+     sum bozbar frotz nitfol >actual10.sum &&
+     cmp M.sum actual10.sum'
+
+test_expect_success \
+    '11 - dirty path removed.' \
+    'rm -f .git/index &&
+     echo rezrov >rezrov &&
+     git-update-cache --add rezrov &&
+     echo rezrov rezrov >rezrov &&
+     if git-read-tree -m -u $treeH $treeM; then false; else :; fi'
+
+test_expect_success \
+    '12 - unmatching local changes being removed.' \
+    'rm -f .git/index &&
+     echo rezrov rezrov >rezrov &&
+     git-update-cache --add rezrov &&
+     if git-read-tree -m -u $treeH $treeM; then false; else :; fi'
+
+test_expect_success \
+    '13 - unmatching local changes being removed.' \
+    'rm -f .git/index &&
+     echo rezrov rezrov >rezrov &&
+     git-update-cache --add rezrov &&
+     echo rezrov >rezrov &&
+     if git-read-tree -m -u $treeH $treeM; then false; else :; fi'
+
+cat >expected <<EOF
+-100644 X 0    nitfol
++100644 X 0    nitfol
+EOF
+
+test_expect_success \
+    '14 - unchanged in two heads.' \
+    'rm -f .git/index &&
+     echo nitfol nitfol >nitfol &&
+     git-update-cache --add nitfol &&
+     git-read-tree -m -u $treeH $treeM &&
+     git-ls-files --stage >14.out || exit
+     diff --unified=0 M.out 14.out >14diff.out
+     compare_change 14diff.out expected &&
+     sum bozbar frotz >actual14.sum &&
+     grep -v nitfol M.sum > expected14.sum &&
+     cmp expected14.sum actual14.sum &&
+     sum bozbar frotz nitfol >actual14a.sum &&
+     if cmp M.sum actual14a.sum; then false; else :; fi &&
+     check_cache_at nitfol clean &&
+     echo nitfol nitfol >nitfol1 &&
+     diff nitfol nitfol1 &&
+     rm -f nitfol1'
+
+test_expect_success \
+    '15 - unchanged in two heads.' \
+    'rm -f .git/index &&
+     echo nitfol nitfol >nitfol &&
+     git-update-cache --add nitfol &&
+     echo nitfol nitfol nitfol >nitfol &&
+     git-read-tree -m -u $treeH $treeM &&
+     git-ls-files --stage >15.out || exit
+     diff --unified=0 M.out 15.out >15diff.out
+     compare_change 15diff.out expected &&
+     check_cache_at nitfol dirty &&
+     sum bozbar frotz >actual15.sum &&
+     grep -v nitfol M.sum > expected15.sum &&
+     cmp expected15.sum actual15.sum &&
+     sum bozbar frotz nitfol >actual15a.sum &&
+     if cmp M.sum actual15a.sum; then false; else :; fi &&
+     echo nitfol nitfol nitfol >nitfol1 &&
+     diff nitfol nitfol1 &&
+     rm -f nitfol1'
+
+test_expect_success \
+    '16 - conflicting local change.' \
+    'rm -f .git/index &&
+     echo bozbar bozbar >bozbar &&
+     git-update-cache --add bozbar &&
+     if git-read-tree -m -u $treeH $treeM; then false; else :; fi'
+
+test_expect_success \
+    '17 - conflicting local change.' \
+    'rm -f .git/index &&
+     echo bozbar bozbar >bozbar &&
+     git-update-cache --add bozbar &&
+     echo bozbar bozbar bozbar >bozbar &&
+     if git-read-tree -m -u $treeH $treeM; then false; else :; fi'
+
+test_expect_success \
+    '18 - local change already having a good result.' \
+    'rm -f .git/index &&
+     echo gnusto >bozbar &&
+     git-update-cache --add bozbar &&
+     git-read-tree -m -u $treeH $treeM &&
+     git-ls-files --stage >18.out &&
+     diff --unified=0 M.out 18.out &&
+     check_cache_at bozbar clean &&
+     sum bozbar frotz nitfol >actual18.sum &&
+     cmp M.sum actual18.sum'
+
+test_expect_success \
+    '19 - local change already having a good result, further modified.' \
+    'rm -f .git/index &&
+     echo gnusto >bozbar &&
+     git-update-cache --add bozbar &&
+     echo gnusto gnusto >bozbar &&
+     git-read-tree -m -u $treeH $treeM &&
+     git-ls-files --stage >19.out &&
+     diff --unified=0 M.out 19.out &&
+     check_cache_at bozbar dirty &&
+     sum frotz nitfol >actual19.sum &&
+     grep -v bozbar  M.sum > expected19.sum &&
+     cmp expected19.sum actual19.sum &&
+     sum bozbar frotz nitfol >actual19a.sum &&
+     if cmp M.sum actual19a.sum; then false; else :; fi &&
+     echo gnusto gnusto >bozbar1 &&
+     diff bozbar bozbar1 &&
+     rm -f bozbar1'
+
+test_expect_success \
+    '20 - no local change, use new tree.' \
+    'rm -f .git/index &&
+     echo bozbar >bozbar &&
+     git-update-cache --add bozbar &&
+     git-read-tree -m -u $treeH $treeM &&
+     git-ls-files --stage >20.out &&
+     diff --unified=0 M.out 20.out &&
+     check_cache_at bozbar clean &&
+     sum bozbar frotz nitfol >actual20.sum &&
+     cmp M.sum actual20.sum'
+
+test_expect_success \
+    '21 - no local change, dirty cache.' \
+    'rm -f .git/index &&
+     echo bozbar >bozbar &&
+     git-update-cache --add bozbar &&
+     echo gnusto gnusto >bozbar &&
+     if git-read-tree -m -u $treeH $treeM; then false; else :; fi'
+
+# Also make sure we did not break DF vs DF/DF case.
+test_expect_success \
+    'DF vs DF/DF case setup.' \
+    'rm -f .git/index &&
+     echo DF >DF &&
+     git-update-cache --add DF &&
+     treeDF=`git-write-tree` &&
+     echo treeDF $treeDF &&
+     git-ls-tree $treeDF &&
+
+     rm -f DF &&
+     mkdir DF &&
+     echo DF/DF >DF/DF &&
+     git-update-cache --add --remove DF DF/DF &&
+     treeDFDF=`git-write-tree` &&
+     echo treeDFDF $treeDFDF &&
+     git-ls-tree $treeDFDF &&
+     git-ls-files --stage >DFDF.out'
+
+test_expect_success \
+    'DF vs DF/DF case test.' \
+    'rm -f .git/index &&
+     rm -fr DF &&
+     echo DF >DF &&
+     git-update-cache --add DF &&
+     git-read-tree -m -u $treeDF $treeDFDF &&
+     git-ls-files --stage >DFDFcheck.out &&
+     diff --unified=0 DFDF.out DFDFcheck.out &&
+     check_cache_at DF/DF clean'
+
+test_done
diff --git a/t/t1005-read-tree-m-2way-emu23.sh b/t/t1005-read-tree-m-2way-emu23.sh
new file mode 100755 (executable)
index 0000000..d80752d
--- /dev/null
@@ -0,0 +1,422 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+
+test_description='Two way merge with read-tree --emu23 $H $M
+
+This test tries two-way merge (aka fast forward with carry forward).
+
+There is the head (called H) and another commit (called M), which is
+simply ahead of H.  The index and the work tree contains a state that
+is derived from H, but may also have local changes.  This test checks
+all the combinations described in the two-tree merge "carry forward"
+rules, found in <Documentation/git-rev-tree.txt>.
+
+In the test, these paths are used:
+        bozbar  - in H, stays in M, modified from bozbar to gnusto
+        frotz   - not in H added in M
+        nitfol  - in H, stays in M unmodified
+        rezrov  - in H, deleted in M
+        yomin   - not in H nor M
+'
+. ./test-lib.sh
+
+read_tree_twoway () {
+    git-read-tree --emu23 "$1" "$2" &&
+    git-ls-files --stage &&
+    git-merge-cache git-merge-one-file-script -a &&
+    git-ls-files --stage
+}
+
+_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"
+compare_change () {
+       sed -n >current \
+           -e '/^--- /d; /^+++ /d; /^@@ /d;' \
+           -e 's/^\([-+][0-7][0-7][0-7][0-7][0-7][0-7]\) '"$_x40"' /\1 X /p' \
+           "$1"
+       diff -u expected current
+}
+
+check_cache_at () {
+       clean_if_empty=`git-diff-files "$1"`
+       case "$clean_if_empty" in
+       '')  echo "$1: clean" ;;
+       ?*)  echo "$1: dirty" ;;
+       esac
+       case "$2,$clean_if_empty" in
+       clean,)         :     ;;
+       clean,?*)       false ;;
+       dirty,)         false ;;
+       dirty,?*)       :     ;;
+       esac
+}
+
+check_stages () {
+    cat >expected_stages
+    git-ls-files --stage | sed -e "s/ $_x40 / X /" >current_stages
+    diff -u expected_stages current_stages
+}
+
+cat >bozbar-old <<\EOF
+This is a sample file used in two-way fast forward merge
+tests.  Its second line ends with a magic word bozbar
+which will be modified by the merged head to gnusto.
+It has some extra lines so that external tools can
+successfully merge independent changes made to later
+lines (such as this one), avoiding line conflicts.
+EOF
+
+sed -e 's/bozbar/gnusto (earlier bozbar)/' bozbar-old >bozbar-new
+
+test_expect_success \
+    setup \
+    'echo frotz >frotz &&
+     echo nitfol >nitfol &&
+     cat bozbar-old >bozbar &&
+     echo rezrov >rezrov &&
+     echo yomin >yomin &&
+     git-update-cache --add nitfol bozbar rezrov &&
+     treeH=`git-write-tree` &&
+     echo treeH $treeH &&
+     git-ls-tree $treeH &&
+
+     cat bozbar-new >bozbar &&
+     git-update-cache --add frotz bozbar --force-remove rezrov &&
+     git-ls-files --stage >M.out &&
+     treeM=`git-write-tree` &&
+     echo treeM $treeM &&
+     git-ls-tree $treeM &&
+     git-diff-tree $treeH $treeM'
+
+# "read-tree -m H I+H M" but I is empty so this is "read-tree -m H H M".
+#
+# bozbar [O && A && B && O==A && O!=B (#14) ==> B] take M by read-tree
+# frotz  [!O && !A && B (#2) ==> B]                take M by read-tree
+# nitfol [O && A && B && O==A && O==B (#15) ==> B] take M by read-tree
+# rezrov [O && A && !B && O==A (#10) ==> no merge] removed by script
+#
+# Earlier one did not have #2ALT so taking M was done by the script,
+# which also updated the work tree and making frotz clean.  With #2ALT,
+# this is resolved by read-tree itself and the path is left dirty
+# because we are not testing "read-tree -u --emu23".
+test_expect_success \
+    '1, 2, 3 - no carry forward' \
+    'rm -f .git/index &&
+     read_tree_twoway $treeH $treeM &&
+     git-ls-files --stage >1-3.out &&
+     diff -u M.out 1-3.out &&
+     check_cache_at bozbar dirty &&
+     check_cache_at frotz dirty && # same as pure 2-way again.
+     check_cache_at nitfol dirty'
+
+echo '+100644 X 0      yomin' >expected
+
+test_expect_success \
+    '4 - carry forward local addition.' \
+    'rm -f .git/index &&
+     git-read-tree $treeH &&
+     git-checkout-cache -u -f -q -a &&
+     git-update-cache --add yomin &&
+     read_tree_twoway $treeH $treeM &&
+     git-ls-files --stage >4.out || exit
+     diff -u M.out 4.out >4diff.out
+     compare_change 4diff.out expected &&
+     check_cache_at yomin clean'
+
+# "read-tree -m H I+H M" where !H && !M; so (I+H) not being up-to-date
+# should not matter.  Thanks to #3ALT, this is now possible.
+test_expect_success \
+    '5 - carry forward local addition.' \
+    'rm -f .git/index &&
+     git-read-tree $treeH &&
+     git-checkout-cache -u -f -q -a &&
+     echo yomin >yomin &&
+     git-update-cache --add yomin &&
+     echo yomin yomin >yomin &&
+     read_tree_twoway $treeH $treeM &&
+     git-ls-files --stage >5.out || exit
+     diff -u M.out 5.out >5diff.out
+     compare_change 5diff.out expected &&
+     check_cache_at yomin dirty'
+
+# "read-tree -m H I+H M" where !H && M && (I+H) == M, so this should
+# succeed (even the entry is clean), now thanks to #5ALT.
+test_expect_success \
+    '6 - local addition already has the same.' \
+    'rm -f .git/index &&
+     git-read-tree $treeH &&
+     git-checkout-cache -u -f -q -a &&
+     git-update-cache --add frotz &&
+     read_tree_twoway $treeH $treeM &&
+     git-ls-files --stage >6.out &&
+     diff -u M.out 6.out &&
+     check_cache_at frotz clean'
+
+# Exactly the same pattern as above but with dirty cache.  This also
+# should succeed, now thanks to #5ALT.
+test_expect_success \
+    '7 - local addition already has the same.' \
+    'rm -f .git/index &&
+     git-read-tree $treeH &&
+     git-checkout-cache -u -f -q -a &&
+     echo frotz >frotz &&
+     git-update-cache --add frotz &&
+     echo frotz frotz >frotz &&
+     read_tree_twoway $treeH $treeM &&
+     git-ls-files --stage >7.out &&
+     diff -u M.out 7.out &&
+     check_cache_at frotz dirty'
+
+test_expect_success \
+    '8 - conflicting addition.' \
+    'rm -f .git/index &&
+     git-read-tree $treeH &&
+     git-checkout-cache -u -f -q -a &&
+     echo frotz frotz >frotz &&
+     git-update-cache --add frotz &&
+     if read_tree_twoway $treeH $treeM; then false; else :; fi'
+
+test_expect_success \
+    '9 - conflicting addition.' \
+    'rm -f .git/index &&
+     git-read-tree $treeH &&
+     git-checkout-cache -u -f -q -a &&
+     echo frotz frotz >frotz &&
+     git-update-cache --add frotz &&
+     echo frotz >frotz &&
+     if read_tree_twoway $treeH $treeM; then false; else :; fi'
+
+test_expect_success \
+    '10 - path removed.' \
+    'rm -f .git/index &&
+     git-read-tree $treeH &&
+     git-checkout-cache -u -f -q -a &&
+     echo rezrov >rezrov &&
+     git-update-cache --add rezrov &&
+     read_tree_twoway $treeH $treeM &&
+     git-ls-files --stage >10.out &&
+     diff -u M.out 10.out'
+
+test_expect_success \
+    '11 - dirty path removed.' \
+    'rm -f .git/index &&
+     git-read-tree $treeH &&
+     git-checkout-cache -u -f -q -a &&
+     echo rezrov >rezrov &&
+     git-update-cache --add rezrov &&
+     echo rezrov rezrov >rezrov &&
+     if read_tree_twoway $treeH $treeM; then false; else :; fi'
+
+test_expect_success \
+    '12 - unmatching local changes being removed.' \
+    'rm -f .git/index &&
+     git-read-tree $treeH &&
+     git-checkout-cache -u -f -q -a &&
+     echo rezrov rezrov >rezrov &&
+     git-update-cache --add rezrov &&
+     if read_tree_twoway $treeH $treeM; then false; else :; fi'
+
+test_expect_success \
+    '13 - unmatching local changes being removed.' \
+    'rm -f .git/index &&
+     git-read-tree $treeH &&
+     git-checkout-cache -u -f -q -a &&
+     echo rezrov rezrov >rezrov &&
+     git-update-cache --add rezrov &&
+     echo rezrov >rezrov &&
+     if read_tree_twoway $treeH $treeM; then false; else :; fi'
+
+cat >expected <<EOF
+-100644 X 0    nitfol
++100644 X 0    nitfol
+EOF
+
+test_expect_success \
+    '14 - unchanged in two heads.' \
+    'rm -f .git/index &&
+     git-read-tree $treeH &&
+     git-checkout-cache -u -f -q -a &&
+     echo nitfol nitfol >nitfol &&
+     git-update-cache --add nitfol &&
+     read_tree_twoway $treeH $treeM &&
+     git-ls-files --stage >14.out || exit
+     diff -u M.out 14.out >14diff.out
+     compare_change 14diff.out expected &&
+     check_cache_at nitfol clean'
+
+test_expect_success \
+    '15 - unchanged in two heads.' \
+    'rm -f .git/index &&
+     git-read-tree $treeH &&
+     git-checkout-cache -u -f -q -a &&
+     echo nitfol nitfol >nitfol &&
+     git-update-cache --add nitfol &&
+     echo nitfol nitfol nitfol >nitfol &&
+     read_tree_twoway $treeH $treeM &&
+     git-ls-files --stage >15.out || exit
+     diff -u M.out 15.out >15diff.out
+     compare_change 15diff.out expected &&
+     check_cache_at nitfol dirty'
+
+# This is different from straight 2-way merge in that it leaves
+# three stages of bozbar in the index file without failing, so
+# the user can run git-diff-stages to examine the situation.
+# With #2ALT, frotz is resolved internally.
+test_expect_success \
+    '16 - conflicting local change.' \
+    'rm -f .git/index &&
+     git-read-tree $treeH &&
+     git-checkout-cache -u -f -q -a &&
+     echo bozbar bozbar >bozbar &&
+     git-update-cache --add bozbar &&
+     git-read-tree --emu23 $treeH $treeM &&
+     check_stages' <<\EOF
+100644 X 1     bozbar
+100644 X 2     bozbar
+100644 X 3     bozbar
+100644 X 0     frotz
+100644 X 0     nitfol
+100644 X 1     rezrov
+100644 X 2     rezrov
+EOF
+
+test_expect_success \
+    '17 - conflicting local change.' \
+    'rm -f .git/index &&
+     git-read-tree $treeH &&
+     git-checkout-cache -u -f -q -a &&
+     echo bozbar bozbar >bozbar &&
+     git-update-cache --add bozbar &&
+     echo bozbar bozbar bozbar >bozbar &&
+     if read_tree_twoway $treeH $treeM; then false; else :; fi'
+
+test_expect_success \
+    '18 - local change already having a good result.' \
+    'rm -f .git/index &&
+     git-read-tree $treeH &&
+     git-checkout-cache -u -f -q -a &&
+     cat bozbar-new >bozbar &&
+     git-update-cache --add bozbar &&
+     read_tree_twoway $treeH $treeM &&
+     git-ls-files --stage >18.out &&
+     diff -u M.out 18.out &&
+     check_cache_at bozbar clean'
+
+test_expect_success \
+    '19 - local change already having a good result, further modified.' \
+    'rm -f .git/index &&
+     git-read-tree $treeH &&
+     git-checkout-cache -u -f -q -a &&
+     cat bozbar-new >bozbar &&
+     git-update-cache --add bozbar &&
+     echo gnusto gnusto >bozbar &&
+     read_tree_twoway $treeH $treeM &&
+     git-ls-files --stage >19.out &&
+     diff -u M.out 19.out &&
+     check_cache_at bozbar dirty'
+
+test_expect_success \
+    '20 - no local change, use new tree.' \
+    'rm -f .git/index &&
+     git-read-tree $treeH &&
+     git-checkout-cache -u -f -q -a &&
+     cat bozbar-old >bozbar &&
+     git-update-cache --add bozbar &&
+     read_tree_twoway $treeH $treeM &&
+     git-ls-files --stage >20.out &&
+     diff -u M.out 20.out &&
+     check_cache_at bozbar dirty'
+
+test_expect_success \
+    '21 - no local change, dirty cache.' \
+    'rm -f .git/index &&
+     git-read-tree $treeH &&
+     git-checkout-cache -u -f -q -a &&
+     cat bozbar-old >bozbar &&
+     git-update-cache --add bozbar &&
+     echo gnusto gnusto >bozbar &&
+     if read_tree_twoway $treeH $treeM; then false; else :; fi'
+
+echo '-100644 X 0      bozbar
++100644 X 0    bozbar' >expected
+
+# This fails with straight two-way fast forward, but emu23
+# can merge them.
+test_expect_success \
+    '22 - local change cache updated.' \
+    'rm -f .git/index &&
+     git-read-tree $treeH &&
+     git-checkout-cache -u -f -q -a &&
+     sed -e "s/such as/SUCH AS/" bozbar-old >bozbar &&
+     git-update-cache --add bozbar &&
+     read_tree_twoway $treeH $treeM &&
+     git-ls-files --stage >22.out || exit
+     diff -u M.out 22.out >22diff.out
+     compare_change 22diff.out &&
+     check_cache_at bozbar clean'
+
+# Also make sure we did not break DF vs DF/DF case.
+test_expect_success \
+    'DF vs DF/DF case setup.' \
+    'rm -f .git/index &&
+     echo DF >DF &&
+     git-update-cache --add DF &&
+     treeDF=`git-write-tree` &&
+     echo treeDF $treeDF &&
+     git-ls-tree $treeDF &&
+     git-ls-files --stage >DF.out
+
+     rm -f DF &&
+     mkdir DF &&
+     echo DF/DF >DF/DF &&
+     git-update-cache --add --remove DF DF/DF &&
+     treeDFDF=`git-write-tree` &&
+     echo treeDFDF $treeDFDF &&
+     git-ls-tree $treeDFDF &&
+     git-ls-files --stage >DFDF.out'
+
+test_expect_success \
+    'DF vs DF/DF case test (#1)' \
+    'rm -f .git/index &&
+     rm -fr DF &&
+     echo DF >DF &&
+     git-update-cache --add DF &&
+     read_tree_twoway $treeDF $treeDFDF &&
+     git-ls-files --stage >DFDFcheck.out &&
+     diff -u DFDF.out DFDFcheck.out &&
+     check_cache_at DF/DF clean && # different from pure 2-way
+     :'
+
+# The other way around
+test_expect_success \
+    'DF vs DF/DF case test (#2)' \
+    'rm -f .git/index &&
+     rm -fr DF &&
+     mkdir DF &&
+     echo DF/DF >DF/DF &&
+     git-update-cache --add DF/DF &&
+     read_tree_twoway $treeDFDF $treeDF &&
+     git-ls-files --stage >DFDFcheck.out &&
+     diff -u DF.out DFDFcheck.out &&
+     check_cache_at DF clean && # different from pure 2-way
+     :'
+
+# Emu23 can grok I having more than H.  Make sure we did not
+# botch the conflict tests (fixed).
+test_expect_success \
+    'DF vs DF/DF case test (#3).' \
+    'rm -f .git/index &&
+     rm -fr DF &&
+     mkdir DF &&
+     echo DF/DF >DF/DF &&
+     git-update-cache --add DF/DF &&
+     # This should fail because I and H have a conflict
+     # at DF.
+     if git-read-tree --emu23 $treeDF $treeDFDF
+     then false
+     else true
+     fi'
+
+test_done
diff --git a/t/t1100-commit-tree-options.sh b/t/t1100-commit-tree-options.sh
new file mode 100755 (executable)
index 0000000..e59f724
--- /dev/null
@@ -0,0 +1,45 @@
+#!/bin/sh
+#
+# Copyright (C) 2005 Rene Scharfe
+#
+
+test_description='git-commit-tree options test
+
+This test checks that git-commit-tree can create a specific commit
+object by defining all environment variables that it understands.
+'
+
+. ./test-lib.sh
+
+cat >expected <<EOF
+tree 4b825dc642cb6eb9a060e54bf8d69288fbee4904
+author Author Name <author@email> 1117148400 +0000
+committer Committer Name <committer@email> 1117150200 +0000
+
+comment text
+EOF
+
+test_expect_success \
+    'test preparation: write empty tree' \
+    'git-write-tree >treeid'
+
+test_expect_success \
+    'construct commit' \
+    'echo comment text |
+     GIT_AUTHOR_NAME="Author Name" \
+     GIT_AUTHOR_EMAIL="author@email" \
+     GIT_AUTHOR_DATE="2005-05-26 23:00" \
+     GIT_COMMITTER_NAME="Committer Name" \
+     GIT_COMMITTER_EMAIL="committer@email" \
+     GIT_COMMITTER_DATE="2005-05-26 23:30" \
+     TZ= git-commit-tree `cat treeid` >commitid 2>/dev/null'
+
+test_expect_success \
+    'read commit' \
+    'git-cat-file commit `cat commitid` >commit'
+
+test_expect_success \
+    'compare commit' \
+    'diff expected commit'
+
+test_done
diff --git a/t/t2000-checkout-cache-clash.sh b/t/t2000-checkout-cache-clash.sh
new file mode 100755 (executable)
index 0000000..a2c4260
--- /dev/null
@@ -0,0 +1,53 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+
+test_description='git-checkout-cache test.
+
+This test registers the following filesystem structure in the
+cache:
+
+    path0       - a file
+    path1/file1 - a file in a directory
+
+And then tries to checkout in a work tree that has the following:
+
+    path0/file0 - a file in a directory
+    path1       - a file
+
+The git-checkout-cache command should fail when attempting to checkout
+path0, finding it is occupied by a directory, and path1/file1, finding
+path1 is occupied by a non-directory.  With "-f" flag, it should remove
+the conflicting paths and succeed.
+'
+. ./test-lib.sh
+
+date >path0
+mkdir path1
+date >path1/file1
+
+test_expect_success \
+    'git-update-cache --add various paths.' \
+    'git-update-cache --add path0 path1/file1'
+
+rm -fr path0 path1
+mkdir path0
+date >path0/file0
+date >path1
+
+test_expect_failure \
+    'git-checkout-cache without -f should fail on conflicting work tree.' \
+    'git-checkout-cache -a'
+
+test_expect_success \
+    'git-checkout-cache with -f should succeed.' \
+    'git-checkout-cache -f -a'
+
+test_expect_success \
+    'git-checkout-cache conflicting paths.' \
+    'test -f path0 && test -d path1 && test -f path1/file1'
+
+test_done
+
+
diff --git a/t/t2001-checkout-cache-clash.sh b/t/t2001-checkout-cache-clash.sh
new file mode 100755 (executable)
index 0000000..f0e3d1d
--- /dev/null
@@ -0,0 +1,87 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+
+test_description='git-checkout-cache test.
+
+This test registers the following filesystem structure in the cache:
+
+    path0/file0        - a file in a directory
+    path1/file1 - a file in a directory
+
+and attempts to check it out when the work tree has:
+
+    path0/file0 - a file in a directory
+    path1       - a symlink pointing at "path0"
+
+Checkout cache should fail to extract path1/file1 because the leading
+path path1 is occupied by a non-directory.  With "-f" it should remove
+the symlink path1 and create directory path1 and file path1/file1.
+'
+. ./test-lib.sh
+
+show_files() {
+       # show filesystem files, just [-dl] for type and name
+       find path? -ls |
+       sed -e 's/^[0-9]* * [0-9]* * \([-bcdl]\)[^ ]* *[0-9]* *[^ ]* *[^ ]* *[0-9]* [A-Z][a-z][a-z] [0-9][0-9] [^ ]* /fs: \1 /'
+       # what's in the cache, just mode and name
+       git-ls-files --stage |
+       sed -e 's/^\([0-9]*\) [0-9a-f]* [0-3] /ca: \1 /'
+       # what's in the tree, just mode and name.
+       git-ls-tree -r "$1" |
+       sed -e 's/^\([0-9]*\)   [^ ]*   [0-9a-f]*       /tr: \1 /'
+}
+
+mkdir path0
+date >path0/file0
+test_expect_success \
+    'git-update-cache --add path0/file0' \
+    'git-update-cache --add path0/file0'
+test_expect_success \
+    'writing tree out with git-write-tree' \
+    'tree1=$(git-write-tree)'
+test_debug 'show_files $tree1'
+
+mkdir path1
+date >path1/file1
+test_expect_success \
+    'git-update-cache --add path1/file1' \
+    'git-update-cache --add path1/file1'
+test_expect_success \
+    'writing tree out with git-write-tree' \
+    'tree2=$(git-write-tree)'
+test_debug 'show_files $tree2'
+
+rm -fr path1
+test_expect_success \
+    'read previously written tree and checkout.' \
+    'git-read-tree -m $tree1 && git-checkout-cache -f -a'
+test_debug 'show_files $tree1'
+
+ln -s path0 path1
+test_expect_success \
+    'git-update-cache --add a symlink.' \
+    'git-update-cache --add path1'
+test_expect_success \
+    'writing tree out with git-write-tree' \
+    'tree3=$(git-write-tree)'
+test_debug 'show_files $tree3'
+
+# Morten says "Got that?" here.
+# Test begins.
+
+test_expect_success \
+    'read previously written tree and checkout.' \
+    'git-read-tree $tree2 && git-checkout-cache -f -a'
+test_debug show_files $tree2
+
+test_expect_success \
+    'checking out conflicting path with -f' \
+    'test ! -h path0 && test -d path0 &&
+     test ! -h path1 && test -d path1 &&
+     test ! -h path0/file0 && test -f path0/file0 &&
+     test ! -h path1/file1 && test -f path1/file1'
+
+test_done
+
diff --git a/t/t2002-checkout-cache-u.sh b/t/t2002-checkout-cache-u.sh
new file mode 100755 (executable)
index 0000000..69146ac
--- /dev/null
@@ -0,0 +1,33 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+
+test_description='git-checkout-cache -u test.
+
+With -u flag, git-checkout-cache internally runs the equivalent of
+git-update-cache --refresh on the checked out entry.'
+
+. ./test-lib.sh
+
+test_expect_success \
+'preparation' '
+echo frotz >path0 &&
+git-update-cache --add path0 &&
+t=$(git-write-tree)'
+
+test_expect_failure \
+'without -u, git-checkout-cache smudges stat information.' '
+rm -f path0 &&
+git-read-tree $t &&
+git-checkout-cache -f -a &&
+git-diff-files | diff - /dev/null'
+
+test_expect_success \
+'with -u, git-checkout-cache picks up stat information from new files.' '
+rm -f path0 &&
+git-read-tree $t &&
+git-checkout-cache -u -f -a &&
+git-diff-files | diff - /dev/null'
+
+test_done
diff --git a/t/t2003-checkout-cache-mkdir.sh b/t/t2003-checkout-cache-mkdir.sh
new file mode 100755 (executable)
index 0000000..6ec2817
--- /dev/null
@@ -0,0 +1,95 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+
+test_description='git-checkout-cache --prefix test.
+
+This test makes sure that --prefix option works as advertised, and
+also verifies that such leading path may contain symlinks, unlike
+the GIT controlled paths.
+'
+
+. ./test-lib.sh
+
+test_expect_success \
+    'setup' \
+    'mkdir path1 &&
+    echo frotz >path0 &&
+    echo rezrov >path1/file1 &&
+    git-update-cache --add path0 path1/file1'
+
+test_expect_success \
+    'have symlink in place where dir is expected.' \
+    'rm -fr path0 path1 &&
+     mkdir path2 &&
+     ln -s path2 path1 &&
+     git-checkout-cache -f -a &&
+     test ! -h path1 && test -d path1 &&
+     test -f path1/file1 && test ! -f path2/file1'
+
+test_expect_success \
+    'use --prefix=path2/' \
+    'rm -fr path0 path1 path2 &&
+     mkdir path2 &&
+     git-checkout-cache --prefix=path2/ -f -a &&
+     test -f path2/path0 &&
+     test -f path2/path1/file1 &&
+     test ! -f path0 &&
+     test ! -f path1/file1'
+
+test_expect_success \
+    'use --prefix=tmp-' \
+    'rm -fr path0 path1 path2 tmp* &&
+     git-checkout-cache --prefix=tmp- -f -a &&
+     test -f tmp-path0 &&
+     test -f tmp-path1/file1 &&
+     test ! -f path0 &&
+     test ! -f path1/file1'
+
+test_expect_success \
+    'use --prefix=tmp- but with a conflicting file and dir' \
+    'rm -fr path0 path1 path2 tmp* &&
+     echo nitfol >tmp-path1 &&
+     mkdir tmp-path0 &&
+     git-checkout-cache --prefix=tmp- -f -a &&
+     test -f tmp-path0 &&
+     test -f tmp-path1/file1 &&
+     test ! -f path0 &&
+     test ! -f path1/file1'
+
+# Linus fix #1
+test_expect_success \
+    'use --prefix=tmp/orary/ where tmp is a symlink' \
+    'rm -fr path0 path1 path2 tmp* &&
+     mkdir tmp1 tmp1/orary &&
+     ln -s tmp1 tmp &&
+     git-checkout-cache --prefix=tmp/orary/ -f -a &&
+     test -d tmp1/orary &&
+     test -f tmp1/orary/path0 &&
+     test -f tmp1/orary/path1/file1 &&
+     test -h tmp'
+
+# Linus fix #2
+test_expect_success \
+    'use --prefix=tmp/orary- where tmp is a symlink' \
+    'rm -fr path0 path1 path2 tmp* &&
+     mkdir tmp1 &&
+     ln -s tmp1 tmp &&
+     git-checkout-cache --prefix=tmp/orary- -f -a &&
+     test -f tmp1/orary-path0 &&
+     test -f tmp1/orary-path1/file1 &&
+     test -h tmp'
+
+# Linus fix #3
+test_expect_success \
+    'use --prefix=tmp- where tmp-path1 is a symlink' \
+    'rm -fr path0 path1 path2 tmp* &&
+     mkdir tmp1 &&
+     ln -s tmp1 tmp-path1 &&
+     git-checkout-cache --prefix=tmp- -f -a &&
+     test -f tmp-path0 &&
+     test ! -h tmp-path1 &&
+     test -d tmp-path1 &&
+     test -f tmp-path1/file1'
+
diff --git a/t/t2100-update-cache-badpath.sh b/t/t2100-update-cache-badpath.sh
new file mode 100755 (executable)
index 0000000..86b7375
--- /dev/null
@@ -0,0 +1,51 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+
+test_description='git-update-cache nonsense-path test.
+
+This test creates the following structure in the cache:
+
+    path0       - a file
+    path1       - a symlink
+    path2/file2 - a file in a directory
+    path3/file3 - a file in a directory
+
+and tries to git-update-cache --add the following:
+
+    path0/file0 - a file in a directory
+    path1/file1 - a file in a directory
+    path2       - a file
+    path3       - a symlink
+
+All of the attempts should fail.
+'
+
+. ./test-lib.sh
+
+mkdir path2 path3
+date >path0
+ln -s xyzzy path1
+date >path2/file2
+date >path3/file3
+
+test_expect_success \
+    'git-update-cache --add to add various paths.' \
+    'git-update-cache --add -- path0 path1 path2/file2 path3/file3'
+
+rm -fr path?
+
+mkdir path0 path1
+date >path2
+ln -s frotz path3
+date >path0/file0
+date >path1/file1
+
+for p in path0/file0 path1/file1 path2 path3
+do
+       test_expect_failure \
+           "git-update-cache to add conflicting path $p should fail." \
+           "git-update-cache --add -- $p"
+done
+test_done
diff --git a/t/t3000-ls-files-others.sh b/t/t3000-ls-files-others.sh
new file mode 100755 (executable)
index 0000000..1f461e3
--- /dev/null
@@ -0,0 +1,34 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+
+test_description='git-ls-files test (--others should pick up symlinks).
+
+This test runs git-ls-files --others with the following on the
+filesystem.
+
+    path0       - a file
+    path1      - a symlink
+    path2/file2 - a file in a directory
+'
+. ./test-lib.sh
+
+date >path0
+ln -s xyzzy path1
+mkdir path2
+date >path2/file2
+test_expect_success \
+    'git-ls-files --others to show output.' \
+    'git-ls-files --others >output'
+cat >expected <<EOF
+output
+path0
+path1
+path2/file2
+EOF
+
+test_expect_success \
+    'git-ls-files --others should pick up symlinks.' \
+    'diff output expected'
+test_done
diff --git a/t/t3001-ls-files-others-exclude.sh b/t/t3001-ls-files-others-exclude.sh
new file mode 100755 (executable)
index 0000000..dbff346
--- /dev/null
@@ -0,0 +1,68 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+
+test_description='git-ls-files --others --exclude
+
+This test runs git-ls-files --others and tests --exclude patterns.
+'
+
+. ./test-lib.sh
+
+rm -fr one three
+for dir in . one one/two three
+do
+  mkdir -p $dir &&
+  for i in 1 2 3 4 5 6 7 8
+  do
+    >$dir/a.$i
+  done
+done
+
+cat >expect <<EOF
+a.2
+a.4
+a.5
+a.8
+one/a.3
+one/a.4
+one/a.5
+one/a.7
+one/two/a.2
+one/two/a.3
+one/two/a.5
+one/two/a.7
+one/two/a.8
+three/a.2
+three/a.3
+three/a.4
+three/a.5
+three/a.8
+EOF
+
+echo '.gitignore
+output
+expect
+.gitignore
+*.7
+!*.8' >.git/ignore
+
+echo '*.1
+/*.3
+!*.6' >.gitignore
+echo '*.2
+two/*.4
+!*.7
+*.8' >one/.gitignore
+echo '!*.2
+!*.8' >one/two/.gitignore
+
+test_expect_success \
+    'git-ls-files --others with various exclude options.' \
+    'git-ls-files --others \
+       --exclude=\*.6 \
+       --exclude-per-directory=.gitignore \
+       --exclude-from=.git/ignore \
+       >output &&
+     diff -u expect output'
diff --git a/t/t3010-ls-files-killed.sh b/t/t3010-ls-files-killed.sh
new file mode 100755 (executable)
index 0000000..c4d6d21
--- /dev/null
@@ -0,0 +1,61 @@
+#!/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-cache --add to add various paths.' \
+    "git-update-cache --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/t3100-ls-tree-restrict.sh b/t/t3100-ls-tree-restrict.sh
new file mode 100755 (executable)
index 0000000..61a7c7f
--- /dev/null
@@ -0,0 +1,131 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+
+test_description='git-ls-tree test.
+
+This test runs git-ls-tree with the following in a tree.
+
+    path0       - a file
+    path1      - a symlink
+    path2/foo   - a file in a directory
+    path2/bazbo - a symlink in a directory
+    path2/baz/b - a file in a directory in a directory
+
+The new path restriction code should do the right thing for path2 and
+path2/baz.  Also path0/ should snow nothing.
+'
+. ./test-lib.sh
+
+test_expect_success \
+    'setup' \
+    'mkdir path2 path2/baz &&
+     echo Hi >path0 &&
+     ln -s path0 path1 &&
+     echo Lo >path2/foo &&
+     ln -s ../path1 path2/bazbo &&
+     echo Mi >path2/baz/b &&
+     find path? \( -type f -o -type l \) -print |
+     xargs git-update-cache --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  path0
+120000 blob X  path1
+040000 tree X  path2
+EOF
+     test_output'
+
+test_expect_success \
+    'ls-tree recursive' \
+    'git-ls-tree -r $tree >current &&
+     cat >expected <<\EOF &&
+100644 blob X  path0
+120000 blob X  path1
+040000 tree X  path2
+040000 tree X  path2/baz
+100644 blob X  path2/baz/b
+120000 blob X  path2/bazbo
+100644 blob X  path2/foo
+EOF
+     test_output'
+
+test_expect_success \
+    'ls-tree filtered with path' \
+    'git-ls-tree $tree path >current &&
+     cat >expected <<\EOF &&
+EOF
+     test_output'
+
+
+test_expect_success \
+    'ls-tree filtered with path1 path0' \
+    'git-ls-tree $tree path1 path0 >current &&
+     cat >expected <<\EOF &&
+120000 blob X  path1
+100644 blob X  path0
+EOF
+     test_output'
+
+test_expect_success \
+    'ls-tree filtered with path0/' \
+    'git-ls-tree $tree path0/ >current &&
+     cat >expected <<\EOF &&
+EOF
+     test_output'
+
+test_expect_success \
+    'ls-tree filtered with path2' \
+    'git-ls-tree $tree path2 >current &&
+     cat >expected <<\EOF &&
+040000 tree X  path2
+040000 tree X  path2/baz
+120000 blob X  path2/bazbo
+100644 blob X  path2/foo
+EOF
+     test_output'
+
+test_expect_success \
+    'ls-tree filtered with path2/baz' \
+    'git-ls-tree $tree path2/baz >current &&
+     cat >expected <<\EOF &&
+040000 tree X  path2/baz
+100644 blob X  path2/baz/b
+EOF
+     test_output'
+
+test_expect_success \
+    'ls-tree filtered with path2' \
+    'git-ls-tree $tree path2 >current &&
+     cat >expected <<\EOF &&
+040000 tree X  path2
+040000 tree X  path2/baz
+120000 blob X  path2/bazbo
+100644 blob X  path2/foo
+EOF
+     test_output'
+
+test_expect_success \
+    'ls-tree filtered with path2/' \
+    'git-ls-tree $tree path2/ >current &&
+     cat >expected <<\EOF &&
+040000 tree X  path2
+040000 tree X  path2/baz
+120000 blob X  path2/bazbo
+100644 blob X  path2/foo
+EOF
+     test_output'
+
+test_done
diff --git a/t/t4000-diff-format.sh b/t/t4000-diff-format.sh
new file mode 100755 (executable)
index 0000000..3accb14
--- /dev/null
@@ -0,0 +1,62 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+
+test_description='Test built-in diff output engine.
+
+'
+. ./test-lib.sh
+
+echo >path0 'Line 1
+Line 2
+line 3'
+cat path0 >path1
+chmod +x path1
+
+test_expect_success \
+    'update-cache --add two files with and without +x.' \
+    'git-update-cache --add path0 path1'
+
+mv path0 path0-
+sed -e 's/line/Line/' <path0- >path0
+chmod +x path0
+rm -f path1
+test_expect_success \
+    'git-diff-files -p after editing work tree.' \
+    'git-diff-files -p >current'
+cat >expected <<\EOF
+diff --git a/path0 b/path0
+old mode 100644
+new mode 100755
+--- a/path0
++++ b/path0
+@@ -1,3 +1,3 @@
+ Line 1
+ Line 2
+-line 3
++Line 3
+diff --git a/path1 b/path1
+deleted file mode 100755
+--- a/path1
++++ /dev/null
+@@ -1,3 +0,0 @@
+-Line 1
+-Line 2
+-line 3
+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'
+
+test_done
diff --git a/t/t4001-diff-rename.sh b/t/t4001-diff-rename.sh
new file mode 100755 (executable)
index 0000000..80edae6
--- /dev/null
@@ -0,0 +1,66 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+
+test_description='Test rename detection in diff engine.
+
+'
+. ./test-lib.sh
+
+echo >path0 'Line 1
+Line 2
+Line 3
+Line 4
+Line 5
+Line 6
+Line 7
+Line 8
+Line 9
+Line 10
+line 11
+Line 12
+Line 13
+Line 14
+Line 15
+'
+
+test_expect_success \
+    'update-cache --add a file.' \
+    'git-update-cache --add path0'
+
+test_expect_success \
+    'write that tree.' \
+    'tree=$(git-write-tree) && echo $tree'
+
+sed -e 's/line/Line/' <path0 >path1
+rm -f path0
+test_expect_success \
+    'renamed and edited the file.' \
+    'git-update-cache --add --remove path0 path1'
+
+test_expect_success \
+    'git-diff-cache -p -M after rename and editing.' \
+    'git-diff-cache -p -M $tree >current'
+cat >expected <<\EOF
+diff --git a/path0 b/path1
+rename from path0
+rename to path1
+--- a/path0
++++ b/path1
+@@ -8,7 +8,7 @@ Line 7
+ Line 8
+ Line 9
+ Line 10
+-line 11
++Line 11
+ Line 12
+ Line 13
+ Line 14
+EOF
+
+test_expect_success \
+    'validate the output.' \
+    'diff -I "similarity.*" >/dev/null current expected'
+
+test_done
diff --git a/t/t4002-diff-basic.sh b/t/t4002-diff-basic.sh
new file mode 100755 (executable)
index 0000000..03fc4b9
--- /dev/null
@@ -0,0 +1,247 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+
+test_description='Test diff raw-output.
+
+'
+. ./test-lib.sh
+. ../lib-read-tree-m-3way.sh
+
+cat >.test-plain-OA <<\EOF
+:000000 100644 0000000000000000000000000000000000000000 ccba72ad3888a3520b39efcf780b9ee64167535d A     AA
+:000000 100644 0000000000000000000000000000000000000000 7e426fb079479fd67f6d81f984e4ec649a44bc25 A     AN
+:100644 000000 bcc68ef997017466d5c9094bcf7692295f588c9a 0000000000000000000000000000000000000000 D     DD
+:000000 040000 0000000000000000000000000000000000000000 6d50f65d3bdab91c63444294d38f08aeff328e42 A     DF
+:100644 000000 141c1f1642328e4bc46a7d801a71da392e66791e 0000000000000000000000000000000000000000 D     DM
+:100644 000000 35abde1506ddf806572ff4d407bd06885d0f8ee9 0000000000000000000000000000000000000000 D     DN
+:000000 100644 0000000000000000000000000000000000000000 1d41122ebdd7a640f29d3c9cc4f9d70094374762 A     LL
+:100644 100644 03f24c8c4700babccfd28b654e7e8eac402ad6cd 103d9f89b50b9aad03054b579be5e7aa665f2d57 M     MD
+:100644 100644 b258508afb7ceb449981bd9d63d2d3e971bf8d34 b431b272d829ff3aa4d1a5085f4394ab4d3305b6 M     MM
+:100644 100644 bd084b0c27c7b6cc34f11d6d0509a29be3caf970 a716d58de4a570e0038f5c307bd8db34daea021f M     MN
+:100644 100644 40c959f984c8b89a2b02520d17f00d717f024397 2ac547ae9614a00d1b28275de608131f7a0e259f M     SS
+:100644 100644 4ac13458899ab908ef3b1128fa378daefc88d356 4c86f9a85fbc5e6804ee2e17a797538fbe785bca M     TT
+:040000 040000 7d670fdcdb9929f6c7dac196ff78689cd1c566a1 5e5f22072bb39f6e12cf663a57cb634c76eefb49 M     Z
+EOF
+
+cat >.test-recursive-OA <<\EOF
+:000000 100644 0000000000000000000000000000000000000000 ccba72ad3888a3520b39efcf780b9ee64167535d A     AA
+:000000 100644 0000000000000000000000000000000000000000 7e426fb079479fd67f6d81f984e4ec649a44bc25 A     AN
+:100644 000000 bcc68ef997017466d5c9094bcf7692295f588c9a 0000000000000000000000000000000000000000 D     DD
+:000000 100644 0000000000000000000000000000000000000000 68a6d8b91da11045cf4aa3a5ab9f2a781c701249 A     DF/DF
+:100644 000000 141c1f1642328e4bc46a7d801a71da392e66791e 0000000000000000000000000000000000000000 D     DM
+:100644 000000 35abde1506ddf806572ff4d407bd06885d0f8ee9 0000000000000000000000000000000000000000 D     DN
+:000000 100644 0000000000000000000000000000000000000000 1d41122ebdd7a640f29d3c9cc4f9d70094374762 A     LL
+:100644 100644 03f24c8c4700babccfd28b654e7e8eac402ad6cd 103d9f89b50b9aad03054b579be5e7aa665f2d57 M     MD
+:100644 100644 b258508afb7ceb449981bd9d63d2d3e971bf8d34 b431b272d829ff3aa4d1a5085f4394ab4d3305b6 M     MM
+:100644 100644 bd084b0c27c7b6cc34f11d6d0509a29be3caf970 a716d58de4a570e0038f5c307bd8db34daea021f M     MN
+:100644 100644 40c959f984c8b89a2b02520d17f00d717f024397 2ac547ae9614a00d1b28275de608131f7a0e259f M     SS
+:100644 100644 4ac13458899ab908ef3b1128fa378daefc88d356 4c86f9a85fbc5e6804ee2e17a797538fbe785bca M     TT
+:000000 100644 0000000000000000000000000000000000000000 8acb8e9750e3f644bf323fcf3d338849db106c77 A     Z/AA
+:000000 100644 0000000000000000000000000000000000000000 087494262084cefee7ed484d20c8dc0580791272 A     Z/AN
+:100644 000000 879007efae624d2b1307214b24a956f0a8d686a8 0000000000000000000000000000000000000000 D     Z/DD
+:100644 000000 9b541b2275c06e3a7b13f28badf5294e2ae63df4 0000000000000000000000000000000000000000 D     Z/DM
+:100644 000000 beb5d38c55283d280685ea21a0e50cfcc0ca064a 0000000000000000000000000000000000000000 D     Z/DN
+:100644 100644 d41fda41b7ec4de46b43cb7ea42a45001ae393d5 a79ac3be9377639e1c7d1edf1ae1b3a5f0ccd8a9 M     Z/MD
+:100644 100644 4ca22bae2527d3d9e1676498a0fba3b355bd1278 61422ba9c2c873416061a88cd40a59a35b576474 M     Z/MM
+:100644 100644 b16d7b25b869f2beb124efa53467d8a1550ad694 a5c544c21cfcb07eb80a4d89a5b7d1570002edfd M     Z/MN
+EOF
+cat >.test-plain-OB <<\EOF
+:000000 100644 0000000000000000000000000000000000000000 6aa2b5335b16431a0ef71e5c0a28be69183cf6a2 A     AA
+:100644 000000 bcc68ef997017466d5c9094bcf7692295f588c9a 0000000000000000000000000000000000000000 D     DD
+:000000 100644 0000000000000000000000000000000000000000 71420ab81e254145d26d6fc0cddee64c1acd4787 A     DF
+:100644 100644 141c1f1642328e4bc46a7d801a71da392e66791e 3c4d8de5fbad08572bab8e10eef8dbb264cf0231 M     DM
+:000000 100644 0000000000000000000000000000000000000000 1d41122ebdd7a640f29d3c9cc4f9d70094374762 A     LL
+:100644 000000 03f24c8c4700babccfd28b654e7e8eac402ad6cd 0000000000000000000000000000000000000000 D     MD
+:100644 100644 b258508afb7ceb449981bd9d63d2d3e971bf8d34 19989d4559aae417fedee240ccf2ba315ea4dc2b M     MM
+:000000 100644 0000000000000000000000000000000000000000 15885881ea69115351c09b38371f0348a3fb8c67 A     NA
+:100644 000000 a4e179e4291e5536a5e1c82e091052772d2c5a93 0000000000000000000000000000000000000000 D     ND
+:100644 100644 c8f25781e8f1792e3e40b74225e20553041b5226 cdb9a8c3da571502ac30225e9c17beccb8387983 M     NM
+:100644 100644 40c959f984c8b89a2b02520d17f00d717f024397 2ac547ae9614a00d1b28275de608131f7a0e259f M     SS
+:100644 100644 4ac13458899ab908ef3b1128fa378daefc88d356 c4e4a12231b9fa79a0053cb6077fcb21bb5b135a M     TT
+:040000 040000 7d670fdcdb9929f6c7dac196ff78689cd1c566a1 1ba523955d5160681af65cb776411f574c1e8155 M     Z
+EOF
+cat >.test-recursive-OB <<\EOF
+:000000 100644 0000000000000000000000000000000000000000 6aa2b5335b16431a0ef71e5c0a28be69183cf6a2 A     AA
+:100644 000000 bcc68ef997017466d5c9094bcf7692295f588c9a 0000000000000000000000000000000000000000 D     DD
+:000000 100644 0000000000000000000000000000000000000000 71420ab81e254145d26d6fc0cddee64c1acd4787 A     DF
+:100644 100644 141c1f1642328e4bc46a7d801a71da392e66791e 3c4d8de5fbad08572bab8e10eef8dbb264cf0231 M     DM
+:000000 100644 0000000000000000000000000000000000000000 1d41122ebdd7a640f29d3c9cc4f9d70094374762 A     LL
+:100644 000000 03f24c8c4700babccfd28b654e7e8eac402ad6cd 0000000000000000000000000000000000000000 D     MD
+:100644 100644 b258508afb7ceb449981bd9d63d2d3e971bf8d34 19989d4559aae417fedee240ccf2ba315ea4dc2b M     MM
+:000000 100644 0000000000000000000000000000000000000000 15885881ea69115351c09b38371f0348a3fb8c67 A     NA
+:100644 000000 a4e179e4291e5536a5e1c82e091052772d2c5a93 0000000000000000000000000000000000000000 D     ND
+:100644 100644 c8f25781e8f1792e3e40b74225e20553041b5226 cdb9a8c3da571502ac30225e9c17beccb8387983 M     NM
+:100644 100644 40c959f984c8b89a2b02520d17f00d717f024397 2ac547ae9614a00d1b28275de608131f7a0e259f M     SS
+:100644 100644 4ac13458899ab908ef3b1128fa378daefc88d356 c4e4a12231b9fa79a0053cb6077fcb21bb5b135a M     TT
+:000000 100644 0000000000000000000000000000000000000000 6c0b99286d0bce551ac4a7b3dff8b706edff3715 A     Z/AA
+:100644 000000 879007efae624d2b1307214b24a956f0a8d686a8 0000000000000000000000000000000000000000 D     Z/DD
+:100644 100644 9b541b2275c06e3a7b13f28badf5294e2ae63df4 d77371d15817fcaa57eeec27f770c505ba974ec1 M     Z/DM
+:100644 000000 d41fda41b7ec4de46b43cb7ea42a45001ae393d5 0000000000000000000000000000000000000000 D     Z/MD
+:100644 100644 4ca22bae2527d3d9e1676498a0fba3b355bd1278 697aad7715a1e7306ca76290a3dd4208fbaeddfa M     Z/MM
+:000000 100644 0000000000000000000000000000000000000000 d12979c22fff69c59ca9409e7a8fe3ee25eaee80 A     Z/NA
+:100644 000000 a18393c636b98e9bd7296b8b437ea4992b72440c 0000000000000000000000000000000000000000 D     Z/ND
+:100644 100644 3fdbe17fd013303a2e981e1ca1c6cd6e72789087 7e09d6a3a14bd630913e8c75693cea32157b606d M     Z/NM
+EOF
+cat >.test-plain-AB <<\EOF
+:100644 100644 ccba72ad3888a3520b39efcf780b9ee64167535d 6aa2b5335b16431a0ef71e5c0a28be69183cf6a2 M     AA
+:100644 000000 7e426fb079479fd67f6d81f984e4ec649a44bc25 0000000000000000000000000000000000000000 D     AN
+:000000 100644 0000000000000000000000000000000000000000 71420ab81e254145d26d6fc0cddee64c1acd4787 A     DF
+:040000 000000 6d50f65d3bdab91c63444294d38f08aeff328e42 0000000000000000000000000000000000000000 D     DF
+:000000 100644 0000000000000000000000000000000000000000 3c4d8de5fbad08572bab8e10eef8dbb264cf0231 A     DM
+:000000 100644 0000000000000000000000000000000000000000 35abde1506ddf806572ff4d407bd06885d0f8ee9 A     DN
+:100644 000000 103d9f89b50b9aad03054b579be5e7aa665f2d57 0000000000000000000000000000000000000000 D     MD
+:100644 100644 b431b272d829ff3aa4d1a5085f4394ab4d3305b6 19989d4559aae417fedee240ccf2ba315ea4dc2b M     MM
+:100644 100644 a716d58de4a570e0038f5c307bd8db34daea021f bd084b0c27c7b6cc34f11d6d0509a29be3caf970 M     MN
+:000000 100644 0000000000000000000000000000000000000000 15885881ea69115351c09b38371f0348a3fb8c67 A     NA
+:100644 000000 a4e179e4291e5536a5e1c82e091052772d2c5a93 0000000000000000000000000000000000000000 D     ND
+:100644 100644 c8f25781e8f1792e3e40b74225e20553041b5226 cdb9a8c3da571502ac30225e9c17beccb8387983 M     NM
+:100644 100644 4c86f9a85fbc5e6804ee2e17a797538fbe785bca c4e4a12231b9fa79a0053cb6077fcb21bb5b135a M     TT
+:040000 040000 5e5f22072bb39f6e12cf663a57cb634c76eefb49 1ba523955d5160681af65cb776411f574c1e8155 M     Z
+EOF
+cat >.test-recursive-AB <<\EOF
+:100644 100644 ccba72ad3888a3520b39efcf780b9ee64167535d 6aa2b5335b16431a0ef71e5c0a28be69183cf6a2 M     AA
+:100644 000000 7e426fb079479fd67f6d81f984e4ec649a44bc25 0000000000000000000000000000000000000000 D     AN
+:000000 100644 0000000000000000000000000000000000000000 71420ab81e254145d26d6fc0cddee64c1acd4787 A     DF
+:100644 000000 68a6d8b91da11045cf4aa3a5ab9f2a781c701249 0000000000000000000000000000000000000000 D     DF/DF
+:000000 100644 0000000000000000000000000000000000000000 3c4d8de5fbad08572bab8e10eef8dbb264cf0231 A     DM
+:000000 100644 0000000000000000000000000000000000000000 35abde1506ddf806572ff4d407bd06885d0f8ee9 A     DN
+:100644 000000 103d9f89b50b9aad03054b579be5e7aa665f2d57 0000000000000000000000000000000000000000 D     MD
+:100644 100644 b431b272d829ff3aa4d1a5085f4394ab4d3305b6 19989d4559aae417fedee240ccf2ba315ea4dc2b M     MM
+:100644 100644 a716d58de4a570e0038f5c307bd8db34daea021f bd084b0c27c7b6cc34f11d6d0509a29be3caf970 M     MN
+:000000 100644 0000000000000000000000000000000000000000 15885881ea69115351c09b38371f0348a3fb8c67 A     NA
+:100644 000000 a4e179e4291e5536a5e1c82e091052772d2c5a93 0000000000000000000000000000000000000000 D     ND
+:100644 100644 c8f25781e8f1792e3e40b74225e20553041b5226 cdb9a8c3da571502ac30225e9c17beccb8387983 M     NM
+:100644 100644 4c86f9a85fbc5e6804ee2e17a797538fbe785bca c4e4a12231b9fa79a0053cb6077fcb21bb5b135a M     TT
+:100644 100644 8acb8e9750e3f644bf323fcf3d338849db106c77 6c0b99286d0bce551ac4a7b3dff8b706edff3715 M     Z/AA
+:100644 000000 087494262084cefee7ed484d20c8dc0580791272 0000000000000000000000000000000000000000 D     Z/AN
+:000000 100644 0000000000000000000000000000000000000000 d77371d15817fcaa57eeec27f770c505ba974ec1 A     Z/DM
+:000000 100644 0000000000000000000000000000000000000000 beb5d38c55283d280685ea21a0e50cfcc0ca064a A     Z/DN
+:100644 000000 a79ac3be9377639e1c7d1edf1ae1b3a5f0ccd8a9 0000000000000000000000000000000000000000 D     Z/MD
+:100644 100644 61422ba9c2c873416061a88cd40a59a35b576474 697aad7715a1e7306ca76290a3dd4208fbaeddfa M     Z/MM
+:100644 100644 a5c544c21cfcb07eb80a4d89a5b7d1570002edfd b16d7b25b869f2beb124efa53467d8a1550ad694 M     Z/MN
+:000000 100644 0000000000000000000000000000000000000000 d12979c22fff69c59ca9409e7a8fe3ee25eaee80 A     Z/NA
+:100644 000000 a18393c636b98e9bd7296b8b437ea4992b72440c 0000000000000000000000000000000000000000 D     Z/ND
+:100644 100644 3fdbe17fd013303a2e981e1ca1c6cd6e72789087 7e09d6a3a14bd630913e8c75693cea32157b606d M     Z/NM
+EOF
+
+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"
+z40='0000000000000000000000000000000000000000'
+cmp_diff_files_output () {
+    # diff-files never reports additions.  Also it does not fill in the
+    # object ID for the changed files because it wants you to look at the
+    # filesystem.
+    sed <"$2" >.test-tmp \
+       -e '/^:000000 /d;s/'$x40'\( [MCRNDU][0-9]*\)    /'$z40'\1       /' &&
+    diff "$1" .test-tmp
+}
+
+test_expect_success \
+    'diff-tree of known trees.' \
+    'git-diff-tree $tree_O $tree_A >.test-a &&
+     cmp -s .test-a .test-plain-OA'
+
+test_expect_success \
+    'diff-tree of known trees.' \
+    'git-diff-tree -r $tree_O $tree_A >.test-a &&
+     cmp -s .test-a .test-recursive-OA'
+
+test_expect_success \
+    'diff-tree of known trees.' \
+    'git-diff-tree $tree_O $tree_B >.test-a &&
+     cmp -s .test-a .test-plain-OB'
+
+test_expect_success \
+    'diff-tree of known trees.' \
+    'git-diff-tree -r $tree_O $tree_B >.test-a &&
+     cmp -s .test-a .test-recursive-OB'
+
+test_expect_success \
+    'diff-tree of known trees.' \
+    'git-diff-tree $tree_A $tree_B >.test-a &&
+     cmp -s .test-a .test-plain-AB'
+
+test_expect_success \
+    'diff-tree of known trees.' \
+    'git-diff-tree -r $tree_A $tree_B >.test-a &&
+     cmp -s .test-a .test-recursive-AB'
+
+test_expect_success \
+    'diff-cache O with A in cache' \
+    'git-read-tree $tree_A &&
+     git-diff-cache --cached $tree_O >.test-a &&
+     cmp -s .test-a .test-recursive-OA'
+
+test_expect_success \
+    'diff-cache O with B in cache' \
+    'git-read-tree $tree_B &&
+     git-diff-cache --cached $tree_O >.test-a &&
+     cmp -s .test-a .test-recursive-OB'
+
+test_expect_success \
+    'diff-cache A with B in cache' \
+    'git-read-tree $tree_B &&
+     git-diff-cache --cached $tree_A >.test-a &&
+     cmp -s .test-a .test-recursive-AB'
+
+test_expect_success \
+    'diff-files with O in cache and A checked out' \
+    'rm -fr Z [A-Z][A-Z] &&
+     git-read-tree $tree_A &&
+     git-checkout-cache -f -a &&
+     git-read-tree -m $tree_O || (exit 1)
+     git-update-cache --refresh >/dev/null ;# this can exit non-zero
+     git-diff-files >.test-a &&
+     cmp_diff_files_output .test-a .test-recursive-OA'
+
+test_expect_success \
+    'diff-files with O in cache and B checked out' \
+    'rm -fr Z [A-Z][A-Z] &&
+     git-read-tree $tree_B &&
+     git-checkout-cache -f -a &&
+     git-read-tree -m $tree_O || (exit 1)
+     git-update-cache --refresh >/dev/null ;# this can exit non-zero
+     git-diff-files >.test-a &&
+     cmp_diff_files_output .test-a .test-recursive-OB'
+
+test_expect_success \
+    'diff-files with A in cache and B checked out' \
+    'rm -fr Z [A-Z][A-Z] &&
+     git-read-tree $tree_B &&
+     git-checkout-cache -f -a &&
+     git-read-tree -m $tree_A || (exit 1)
+     git-update-cache --refresh >/dev/null ;# this can exit non-zero
+     git-diff-files >.test-a &&
+     cmp_diff_files_output .test-a .test-recursive-AB'
+
+################################################################
+# Now we have established the baseline, we do not have to
+# rely on individual object ID values that much.
+
+test_expect_success \
+    'diff-tree O A == diff-tree -R A O' \
+    'git-diff-tree $tree_O $tree_A >.test-a &&
+    git-diff-tree -R $tree_A $tree_O >.test-b &&
+    cmp -s .test-a .test-b'
+
+test_expect_success \
+    'diff-tree -r O A == diff-tree -r -R A O' \
+    'git-diff-tree -r $tree_O $tree_A >.test-a &&
+    git-diff-tree -r -R $tree_A $tree_O >.test-b &&
+    cmp -s .test-a .test-b'
+
+test_expect_success \
+    'diff-tree B A == diff-tree -R A B' \
+    'git-diff-tree $tree_B $tree_A >.test-a &&
+    git-diff-tree -R $tree_A $tree_B >.test-b &&
+    cmp -s .test-a .test-b'
+
+test_expect_success \
+    'diff-tree -r B A == diff-tree -r -R A B' \
+    'git-diff-tree -r $tree_B $tree_A >.test-a &&
+    git-diff-tree -r -R $tree_A $tree_B >.test-b &&
+    cmp -s .test-a .test-b'
+
+test_done
diff --git a/t/t4003-diff-rename-1.sh b/t/t4003-diff-rename-1.sh
new file mode 100755 (executable)
index 0000000..8e3091a
--- /dev/null
@@ -0,0 +1,128 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+
+test_description='More rename detection
+
+'
+. ./test-lib.sh
+. ../diff-lib.sh ;# test-lib chdir's into trash
+
+test_expect_success \
+    'prepare reference tree' \
+    'cat ../../COPYING >COPYING &&
+     echo frotz >rezrov &&
+    git-update-cache --add COPYING rezrov &&
+    tree=$(git-write-tree) &&
+    echo $tree'
+
+test_expect_success \
+    'prepare work tree' \
+    'sed -e 's/HOWEVER/However/' <COPYING >COPYING.1 &&
+    sed -e 's/GPL/G.P.L/g' <COPYING >COPYING.2 &&
+    rm -f COPYING &&
+    git-update-cache --add --remove COPYING COPYING.?'
+
+# tree has COPYING and rezrov.  work tree has COPYING.1 and COPYING.2,
+# both are slightly edited, and unchanged rezrov.  So we say you
+# copy-and-edit one, and rename-and-edit the other.  We do not say
+# anything about rezrov.
+
+GIT_DIFF_OPTS=--unified=0 git-diff-cache -M -p $tree >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 rename/copy detection (#1)' \
+    'compare_diff_patch current expected'
+
+test_expect_success \
+    'prepare work tree again' \
+    'mv COPYING.2 COPYING &&
+     git-update-cache --add --remove COPYING COPYING.1 COPYING.2'
+
+# tree has COPYING and rezrov.  work tree has COPYING and COPYING.1,
+# both are slightly edited, and unchanged rezrov.  So we say you
+# edited one, and copy-and-edit the other.  We do not say
+# anything about rezrov.
+
+GIT_DIFF_OPTS=--unified=0 git-diff-cache -C -p $tree >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 rename/copy detection (#2)' \
+    'compare_diff_patch current expected'
+
+test_expect_success \
+    'prepare work tree once again' \
+    'cat ../../COPYING >COPYING &&
+     git-update-cache --add --remove COPYING COPYING.1'
+
+# tree has COPYING and rezrov.  work tree has COPYING and COPYING.1,
+# but COPYING is not edited.  We say you copy-and-edit COPYING.1; this
+# is only possible because -C mode now reports the unmodified file to
+# the diff-core.  Unchanged rezrov, although being fed to
+# git-diff-cache as well, should not be mentioned.
+
+GIT_DIFF_OPTS=--unified=0 \
+    git-diff-cache -C --find-copies-harder -p $tree >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 rename/copy detection (#3)' \
+    'compare_diff_patch current expected'
+
+test_done
diff --git a/t/t4004-diff-rename-symlink.sh b/t/t4004-diff-rename-symlink.sh
new file mode 100755 (executable)
index 0000000..010dd87
--- /dev/null
@@ -0,0 +1,66 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+
+test_description='More rename detection tests.
+
+The rename detection logic should be able to detect pure rename or
+copy of symbolic links, but should not produce rename/copy followed
+by an edit for them.
+'
+. ./test-lib.sh
+
+test_expect_success \
+    'prepare reference tree' \
+    'echo xyzzy | tr -d '\\\\'012 >yomin &&
+     ln -s xyzzy frotz &&
+    git-update-cache --add frotz yomin &&
+    tree=$(git-write-tree) &&
+    echo $tree'
+
+test_expect_success \
+    'prepare work tree' \
+    'mv frotz rezrov &&
+     rm -f yomin &&
+     ln -s xyzzy nitfol &&
+     ln -s xzzzy bozbar &&
+    git-update-cache --add --remove frotz rezrov nitfol bozbar yomin'
+
+# tree has frotz pointing at xyzzy, and yomin that contains xyzzy to
+# confuse things.  work tree has rezrov (xyzzy) nitfol (xyzzy) and
+# bozbar (xzzzy).
+# rezrov and nitfol are rename/copy of frotz and bozbar should be
+# a new creation.
+
+GIT_DIFF_OPTS=--unified=0 git-diff-cache -M -p $tree >current
+cat >expected <<\EOF
+diff --git a/bozbar b/bozbar
+new file mode 120000
+--- /dev/null
++++ b/bozbar
+@@ -0,0 +1 @@
++xzzzy
+\ No newline at end of file
+diff --git a/frotz b/nitfol
+similarity index 100%
+copy from frotz
+copy to nitfol
+diff --git a/frotz b/rezrov
+similarity index 100%
+rename from frotz
+rename to rezrov
+diff --git a/yomin b/yomin
+deleted file mode 100644
+--- a/yomin
++++ /dev/null
+@@ -1 +0,0 @@
+-xyzzy
+\ No newline at end of file
+EOF
+
+test_expect_success \
+    'validate diff output' \
+    'diff -u current expected'
+
+test_done
diff --git a/t/t4005-diff-rename-2.sh b/t/t4005-diff-rename-2.sh
new file mode 100755 (executable)
index 0000000..cee06e4
--- /dev/null
@@ -0,0 +1,166 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+
+test_description='Same rename detection as t4003 but testing diff-raw.
+
+'
+. ./test-lib.sh
+. ../diff-lib.sh ;# test-lib chdir's into trash
+
+test_expect_success \
+    'prepare reference tree' \
+    'cat ../../COPYING >COPYING &&
+     echo frotz >rezrov &&
+    git-update-cache --add COPYING rezrov &&
+    tree=$(git-write-tree) &&
+    echo $tree'
+
+test_expect_success \
+    'prepare work tree' \
+    'sed -e 's/HOWEVER/However/' <COPYING >COPYING.1 &&
+    sed -e 's/GPL/G.P.L/g' <COPYING >COPYING.2 &&
+    rm -f COPYING &&
+    git-update-cache --add --remove COPYING COPYING.?'
+
+# tree has COPYING and rezrov.  work tree has COPYING.1 and COPYING.2,
+# both are slightly edited, and unchanged rezrov.  We say COPYING.1
+# and COPYING.2 are based on COPYING, and do not say anything about
+# rezrov.
+
+git-diff-cache -M $tree >current
+
+cat >expected <<\EOF
+:100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 0603b3238a076dc6c8022aedc6648fa523a17178 C1234 COPYING COPYING.1
+:100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 06c67961bbaed34a127f76d261f4c0bf73eda471 R1234 COPYING COPYING.2
+EOF
+
+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 \
+    'prepare work tree again' \
+    'mv COPYING.2 COPYING &&
+     git-update-cache --add --remove COPYING COPYING.1 COPYING.2'
+
+# tree has COPYING and rezrov.  work tree has COPYING and COPYING.1,
+# both are slightly edited, and unchanged rezrov.  We say COPYING.1
+# is based on COPYING and COPYING is still there, and do not say anything
+# about rezrov.
+
+git-diff-cache -C $tree >current
+cat >expected <<\EOF
+:100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 06c67961bbaed34a127f76d261f4c0bf73eda471 M     COPYING
+:100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 0603b3238a076dc6c8022aedc6648fa523a17178 C1234 COPYING COPYING.1
+EOF
+
+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
+# copy-edited COPYING.1, and unchanged rezrov.  We should not say
+# anything about rezrov nor COPYING, since the revised again diff-raw
+# nows how to say Copy.
+
+test_expect_success \
+    'prepare work tree once again' \
+    'cat ../../COPYING >COPYING &&
+     git-update-cache --add --remove COPYING COPYING.1'
+
+git-diff-cache -C --find-copies-harder $tree >current
+cat >expected <<\EOF
+:100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 0603b3238a076dc6c8022aedc6648fa523a17178 C1234 COPYING COPYING.1
+EOF
+
+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
diff --git a/t/t4006-diff-mode.sh b/t/t4006-diff-mode.sh
new file mode 100755 (executable)
index 0000000..90fd21f
--- /dev/null
@@ -0,0 +1,34 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+
+test_description='Test mode change diffs.
+
+'
+. ./test-lib.sh
+
+test_expect_success \
+    'setup' \
+    'echo frotz >rezrov &&
+     git-update-cache --add rezrov &&
+     tree=`git-write-tree` &&
+     echo $tree'
+
+test_expect_success \
+    'chmod' \
+    'chmod +x rezrov &&
+     git-update-cache rezrov &&
+     git-diff-cache $tree >current'
+
+_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"
+sed -e 's/\(:100644 100755\) \('"$_x40"'\) \2 /\1 X X /' <current >check
+echo ":100644 100755 X X M     rezrov" >expected
+
+test_expect_success \
+    'verify' \
+    'diff -u expected check'
+
+test_done
+
diff --git a/t/t4007-rename-3.sh b/t/t4007-rename-3.sh
new file mode 100755 (executable)
index 0000000..042390a
--- /dev/null
@@ -0,0 +1,90 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+
+test_description='Rename interaction with pathspec.
+
+'
+. ./test-lib.sh
+. ../diff-lib.sh ;# test-lib chdir's into trash
+
+test_expect_success \
+    'prepare reference tree' \
+    'mkdir path0 path1 &&
+     cp ../../COPYING path0/COPYING &&
+     git-update-cache --add path0/COPYING &&
+    tree=$(git-write-tree) &&
+    echo $tree'
+
+test_expect_success \
+    'prepare work tree' \
+    'cp path0/COPYING path1/COPYING &&
+     git-update-cache --add --remove path0/COPYING path1/COPYING'
+
+# In the tree, there is only path0/COPYING.  In the cache, path0 and
+# path1 both have COPYING and the latter is a copy of path0/COPYING.
+# Comparing the full tree with cache should tell us so.
+
+git-diff-cache -C --find-copies-harder $tree >current
+
+cat >expected <<\EOF
+:100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 6ff87c4664981e4397625791c8ea3bbb5f2279a3 C100  path0/COPYING   path1/COPYING
+EOF
+
+test_expect_success \
+    'validate the result (#1)' \
+    'compare_diff_raw current expected'
+
+# In the tree, there is only path0/COPYING.  In the cache, path0 and
+# path1 both have COPYING and the latter is a copy of path0/COPYING.
+# However when we say we care only about path1, we should just see
+# path1/COPYING suddenly appearing from nowhere, not detected as
+# a copy from path0/COPYING.
+
+git-diff-cache -C $tree path1 >current
+
+cat >expected <<\EOF
+:000000 100644 0000000000000000000000000000000000000000 6ff87c4664981e4397625791c8ea3bbb5f2279a3 A     path1/COPYING
+EOF
+
+test_expect_success \
+    'validate the result (#2)' \
+    'compare_diff_raw current expected'
+
+test_expect_success \
+    'tweak work tree' \
+    'rm -f path0/COPYING &&
+     git-update-cache --remove path0/COPYING'
+
+# In the tree, there is only path0/COPYING.  In the cache, path0 does
+# not have COPYING anymore and path1 has COPYING which is a copy of
+# path0/COPYING.  Showing the full tree with cache should tell us about
+# the rename.
+
+git-diff-cache -C $tree >current
+
+cat >expected <<\EOF
+:100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 6ff87c4664981e4397625791c8ea3bbb5f2279a3 R100  path0/COPYING   path1/COPYING
+EOF
+
+test_expect_success \
+    'validate the result (#3)' \
+    'compare_diff_raw current expected'
+
+# In the tree, there is only path0/COPYING.  In the cache, path0 does
+# not have COPYING anymore and path1 has COPYING which is a copy of
+# path0/COPYING.  When we say we care only about path1, we should just
+# see path1/COPYING appearing from nowhere.
+
+git-diff-cache -C $tree path1 >current
+
+cat >expected <<\EOF
+:000000 100644 0000000000000000000000000000000000000000 6ff87c4664981e4397625791c8ea3bbb5f2279a3 A     path1/COPYING
+EOF
+
+test_expect_success \
+    'validate the result (#4)' \
+    'compare_diff_raw current expected'
+
+test_done
diff --git a/t/t4008-diff-break-rewrite.sh b/t/t4008-diff-break-rewrite.sh
new file mode 100755 (executable)
index 0000000..4c971c9
--- /dev/null
@@ -0,0 +1,188 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+
+test_description='Break and then rename
+
+We have two very different files, file0 and file1, registered in a tree.
+
+We update file1 so drastically that it is more similar to file0, and
+then remove file0.  With -B, changes to file1 should be broken into
+separate delete and create, resulting in removal of file0, removal of
+original file1 and creation of completely rewritten file1.
+
+Further, with -B and -M together, these three modifications should
+turn into rename-edit of file0 into file1.
+
+Starting from the same two files in the tree, we swap file0 and file1.
+With -B, this should be detected as two complete rewrites, resulting in
+four changes in total.
+
+Further, with -B and -M together, these should turn into two renames.
+'
+. ./test-lib.sh
+. ../diff-lib.sh ;# test-lib chdir's into trash
+
+test_expect_success \
+    setup \
+    'cat ../../README >file0 &&
+     cat ../../COPYING >file1 &&
+    git-update-cache --add file0 file1 &&
+    tree=$(git-write-tree) &&
+    echo "$tree"'
+
+test_expect_success \
+    'change file1 with copy-edit of file0 and remove file0' \
+    'sed -e "s/git/GIT/" file0 >file1 &&
+     rm -f file0 &&
+    git-update-cache --remove file0 file1'
+
+test_expect_success \
+    'run diff with -B' \
+    'git-diff-cache -B --cached "$tree" >current'
+
+cat >expected <<\EOF
+:100644 000000 f5deac7be59e7eeab8657fd9ae706fd6a57daed2 0000000000000000000000000000000000000000 D     file0
+:100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 11e331465a89c394dc25c780de230043750c1ec8 M100  file1
+EOF
+
+test_expect_success \
+    'validate result of -B (#1)' \
+    'compare_diff_raw expected current'
+
+test_expect_success \
+    'run diff with -B and -M' \
+    'git-diff-cache -B -M "$tree" >current'
+
+cat >expected <<\EOF
+:100644 100644 f5deac7be59e7eeab8657fd9ae706fd6a57daed2 08bb2fb671deff4c03a4d4a0a1315dff98d5732c R100  file0   file1
+EOF
+
+test_expect_success \
+    'validate result of -B -M (#2)' \
+    'compare_diff_raw expected current'
+
+test_expect_success \
+    'swap file0 and file1' \
+    'rm -f file0 file1 &&
+     git-read-tree -m $tree &&
+     git-checkout-cache -f -u -a &&
+     mv file0 tmp &&
+     mv file1 file0 &&
+     mv tmp file1 &&
+     git-update-cache file0 file1'
+
+test_expect_success \
+    'run diff with -B' \
+    'git-diff-cache -B "$tree" >current'
+
+cat >expected <<\EOF
+:100644 100644 f5deac7be59e7eeab8657fd9ae706fd6a57daed2 6ff87c4664981e4397625791c8ea3bbb5f2279a3 M100  file0
+:100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 f5deac7be59e7eeab8657fd9ae706fd6a57daed2 M100  file1
+EOF
+
+test_expect_success \
+    'validate result of -B (#3)' \
+    'compare_diff_raw expected current'
+
+test_expect_success \
+    'run diff with -B and -M' \
+    'git-diff-cache -B -M "$tree" >current'
+
+cat >expected <<\EOF
+:100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 6ff87c4664981e4397625791c8ea3bbb5f2279a3 R100  file1   file0
+:100644 100644 f5deac7be59e7eeab8657fd9ae706fd6a57daed2 f5deac7be59e7eeab8657fd9ae706fd6a57daed2 R100  file0   file1
+EOF
+
+test_expect_success \
+    'validate result of -B -M (#4)' \
+    'compare_diff_raw expected current'
+
+test_expect_success \
+    'make file0 into something completely different' \
+    'rm -f file0 &&
+     ln -s frotz file0 &&
+     git-update-cache file0 file1'
+
+test_expect_success \
+    'run diff with -B' \
+    'git-diff-cache -B "$tree" >current'
+
+cat >expected <<\EOF
+:100644 120000 f5deac7be59e7eeab8657fd9ae706fd6a57daed2 67be421f88824578857624f7b3dc75e99a8a1481 T     file0
+:100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 f5deac7be59e7eeab8657fd9ae706fd6a57daed2 M100  file1
+EOF
+
+test_expect_success \
+    'validate result of -B (#5)' \
+    'compare_diff_raw expected current'
+
+test_expect_success \
+    'run diff with -B' \
+    'git-diff-cache -B -M "$tree" >current'
+
+# This should not mistake file0 as the copy source of new file1
+# due to type differences.
+cat >expected <<\EOF
+:100644 120000 f5deac7be59e7eeab8657fd9ae706fd6a57daed2 67be421f88824578857624f7b3dc75e99a8a1481 T     file0
+:100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 f5deac7be59e7eeab8657fd9ae706fd6a57daed2 M100  file1
+EOF
+
+test_expect_success \
+    'validate result of -B -M (#6)' \
+    'compare_diff_raw expected current'
+
+test_expect_success \
+    'run diff with -M' \
+    'git-diff-cache -M "$tree" >current'
+
+# This should not mistake file0 as the copy source of new file1
+# due to type differences.
+cat >expected <<\EOF
+:100644 120000 f5deac7be59e7eeab8657fd9ae706fd6a57daed2 67be421f88824578857624f7b3dc75e99a8a1481 T     file0
+:100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 f5deac7be59e7eeab8657fd9ae706fd6a57daed2 M     file1
+EOF
+
+test_expect_success \
+    'validate result of -M (#7)' \
+    'compare_diff_raw expected current'
+
+test_expect_success \
+    'file1 edited to look like file0 and file0 rename-edited to file2' \
+    'rm -f file0 file1 &&
+     git-read-tree -m $tree &&
+     git-checkout-cache -f -u -a &&
+     sed -e "s/git/GIT/" file0 >file1 &&
+     sed -e "s/git/GET/" file0 >file2 &&
+     rm -f file0
+     git-update-cache --add --remove file0 file1 file2'
+
+test_expect_success \
+    'run diff with -B' \
+    'git-diff-cache -B "$tree" >current'
+
+cat >expected <<\EOF
+:100644 000000 f5deac7be59e7eeab8657fd9ae706fd6a57daed2 0000000000000000000000000000000000000000 D     file0
+:100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 08bb2fb671deff4c03a4d4a0a1315dff98d5732c M100  file1
+:000000 100644 0000000000000000000000000000000000000000 f5deac7be59e7eeab8657fd9ae706fd6a57daed2 A     file2
+EOF
+
+test_expect_success \
+    'validate result of -B (#8)' \
+    'compare_diff_raw expected current'
+
+test_expect_success \
+    'run diff with -B -M' \
+    'git-diff-cache -B -M "$tree" >current'
+
+cat >expected <<\EOF
+:100644 100644 f5deac7be59e7eeab8657fd9ae706fd6a57daed2 08bb2fb671deff4c03a4d4a0a1315dff98d5732c C095  file0   file1
+:100644 100644 f5deac7be59e7eeab8657fd9ae706fd6a57daed2 59f832e5c8b3f7e486be15ad0cd3e95ba9af8998 R095  file0   file2
+EOF
+
+test_expect_success \
+    'validate result of -B -M (#9)' \
+    'compare_diff_raw expected current'
+
+test_done
diff --git a/t/t4009-diff-rename-4.sh b/t/t4009-diff-rename-4.sh
new file mode 100755 (executable)
index 0000000..6229a5b
--- /dev/null
@@ -0,0 +1,175 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+
+test_description='Same rename detection as t4003 but testing diff-raw -z.
+
+'
+. ./test-lib.sh
+. ../diff-lib.sh ;# test-lib chdir's into trash
+
+test_expect_success \
+    'prepare reference tree' \
+    'cat ../../COPYING >COPYING &&
+     echo frotz >rezrov &&
+    git-update-cache --add COPYING rezrov &&
+    tree=$(git-write-tree) &&
+    echo $tree'
+
+test_expect_success \
+    'prepare work tree' \
+    'sed -e 's/HOWEVER/However/' <COPYING >COPYING.1 &&
+    sed -e 's/GPL/G.P.L/g' <COPYING >COPYING.2 &&
+    rm -f COPYING &&
+    git-update-cache --add --remove COPYING COPYING.?'
+
+# tree has COPYING and rezrov.  work tree has COPYING.1 and COPYING.2,
+# both are slightly edited, and unchanged rezrov.  We say COPYING.1
+# and COPYING.2 are based on COPYING, and do not say anything about
+# rezrov.
+
+git-diff-cache -z -M $tree >current
+
+cat >expected <<\EOF
+:100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 0603b3238a076dc6c8022aedc6648fa523a17178 C1234
+COPYING
+COPYING.1
+:100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 06c67961bbaed34a127f76d261f4c0bf73eda471 R1234
+COPYING
+COPYING.2
+EOF
+
+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 \
+    'prepare work tree again' \
+    'mv COPYING.2 COPYING &&
+     git-update-cache --add --remove COPYING COPYING.1 COPYING.2'
+
+# tree has COPYING and rezrov.  work tree has COPYING and COPYING.1,
+# both are slightly edited, and unchanged rezrov.  We say COPYING.1
+# is based on COPYING and COPYING is still there, and do not say anything
+# about rezrov.
+
+git-diff-cache -z -C $tree >current
+cat >expected <<\EOF
+:100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 06c67961bbaed34a127f76d261f4c0bf73eda471 M
+COPYING
+:100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 0603b3238a076dc6c8022aedc6648fa523a17178 C1234
+COPYING
+COPYING.1
+EOF
+
+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
+# copy-edited COPYING.1, and unchanged rezrov.  We should not say
+# anything about rezrov nor COPYING, since the revised again diff-raw
+# nows how to say Copy.
+
+test_expect_success \
+    'prepare work tree once again' \
+    'cat ../../COPYING >COPYING &&
+     git-update-cache --add --remove COPYING COPYING.1'
+
+git-diff-cache -z -C --find-copies-harder $tree >current
+cat >expected <<\EOF
+:100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 0603b3238a076dc6c8022aedc6648fa523a17178 C1234
+COPYING
+COPYING.1
+EOF
+
+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
diff --git a/t/t4010-diff-pathspec.sh b/t/t4010-diff-pathspec.sh
new file mode 100755 (executable)
index 0000000..9f2c6f6
--- /dev/null
@@ -0,0 +1,65 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+
+test_description='Pathspec restrictions
+
+Prepare:
+        file0
+        path1/file1
+'
+. ./test-lib.sh
+. ../diff-lib.sh ;# test-lib chdir's into trash
+
+test_expect_success \
+    setup \
+    'echo frotz >file0 &&
+     mkdir path1 &&
+     echo rezrov >path1/file1 &&
+     git-update-cache --add file0 path1/file1 &&
+     tree=`git-write-tree` &&
+     echo "$tree" &&
+     echo nitfol >file0 &&
+     echo yomin >path1/file1 &&
+     git-update-cache file0 path1/file1' 
+
+cat >expected <<\EOF
+EOF
+test_expect_success \
+    'limit to path should show nothing' \
+    'git-diff-cache --cached $tree path >current &&
+     compare_diff_raw current expected'
+
+cat >expected <<\EOF
+:100644 100644 766498d93a4b06057a8e49d23f4068f1170ff38f 0a41e115ab61be0328a19b29f18cdcb49338d516 M     path1/file1
+EOF
+test_expect_success \
+    'limit to path1 should show path1/file1' \
+    'git-diff-cache --cached $tree path1 >current &&
+     compare_diff_raw current expected'
+
+cat >expected <<\EOF
+:100644 100644 766498d93a4b06057a8e49d23f4068f1170ff38f 0a41e115ab61be0328a19b29f18cdcb49338d516 M     path1/file1
+EOF
+test_expect_success \
+    'limit to path1/ should show path1/file1' \
+    'git-diff-cache --cached $tree path1/ >current &&
+     compare_diff_raw current expected'
+
+cat >expected <<\EOF
+:100644 100644 766498d93a4b06057a8e49d23f4068f1170ff38f 0a41e115ab61be0328a19b29f18cdcb49338d516 M     file0
+EOF
+test_expect_success \
+    'limit to file0 should show file0' \
+    'git-diff-cache --cached $tree file0 >current &&
+     compare_diff_raw current expected'
+
+cat >expected <<\EOF
+EOF
+test_expect_success \
+    'limit to file0/ should emit nothing.' \
+    'git-diff-cache --cached $tree file0/ >current &&
+     compare_diff_raw current expected'
+
+test_done
diff --git a/t/t4100-apply-stat.sh b/t/t4100-apply-stat.sh
new file mode 100755 (executable)
index 0000000..6579f06
--- /dev/null
@@ -0,0 +1,47 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+
+test_description='git-apply --stat --summary test.
+
+'
+. ./test-lib.sh
+
+test_expect_success \
+    'rename' \
+    'git-apply --stat --summary <../t4100/t-apply-1.patch >current &&
+    diff -u ../t4100/t-apply-1.expect current'
+
+test_expect_success \
+    'copy' \
+    'git-apply --stat --summary <../t4100/t-apply-2.patch >current &&
+    diff -u ../t4100/t-apply-2.expect current'
+
+test_expect_success \
+    'rewrite' \
+    'git-apply --stat --summary <../t4100/t-apply-3.patch >current &&
+    diff -u ../t4100/t-apply-3.expect current'
+
+test_expect_success \
+    'mode' \
+    'git-apply --stat --summary <../t4100/t-apply-4.patch >current &&
+    diff -u ../t4100/t-apply-4.expect current'
+
+test_expect_success \
+    'non git' \
+    'git-apply --stat --summary <../t4100/t-apply-5.patch >current &&
+    diff -u ../t4100/t-apply-5.expect current'
+
+test_expect_success \
+    'non git' \
+    'git-apply --stat --summary <../t4100/t-apply-6.patch >current &&
+    diff -u ../t4100/t-apply-6.expect current'
+
+test_expect_success \
+    'non git' \
+    'git-apply --stat --summary <../t4100/t-apply-7.patch >current &&
+    diff -u ../t4100/t-apply-7.expect current'
+
+test_done
+
diff --git a/t/t4100/t-apply-1.expect b/t/t4100/t-apply-1.expect
new file mode 100644 (file)
index 0000000..540e64d
--- /dev/null
@@ -0,0 +1,11 @@
+ Documentation/git-ssh-pull.txt |   12 ++++++------
+ Documentation/git-ssh-push.txt |   10 +++++-----
+ Documentation/git.txt          |    6 +++---
+ Makefile                       |    6 +++---
+ ssh-pull.c                     |    4 ++--
+ ssh-push.c                     |   14 +++++++-------
+ 6 files changed, 26 insertions(+), 26 deletions(-)
+ rename Documentation/{git-rpull.txt => git-ssh-pull.txt} (90%)
+ rename Documentation/{git-rpush.txt => git-ssh-push.txt} (71%)
+ rename rpull.c => ssh-pull.c (97%)
+ rename rpush.c => ssh-push.c (93%)
diff --git a/t/t4100/t-apply-1.patch b/t/t4100/t-apply-1.patch
new file mode 100644 (file)
index 0000000..de58751
--- /dev/null
@@ -0,0 +1,194 @@
+418aaf847a8b3ffffb4f777a2dd5262ca5ce0ef7 (from dc93841715dfa9a9cdda6f2c4a25eec831ea7aa0)
+diff --git a/Documentation/git-rpull.txt b/Documentation/git-ssh-pull.txt
+similarity index 90%
+rename from Documentation/git-rpull.txt
+rename to Documentation/git-ssh-pull.txt
+--- a/Documentation/git-rpull.txt
++++ b/Documentation/git-ssh-pull.txt
+@@ -1,21 +1,21 @@
+-git-rpull(1)
+-============
++git-ssh-pull(1)
++===============
+ v0.1, May 2005
+ NAME
+ ----
+-git-rpull - Pulls from a remote repository over ssh connection
++git-ssh-pull - Pulls from a remote repository over ssh connection
+ SYNOPSIS
+ --------
+-'git-rpull' [-c] [-t] [-a] [-d] [-v] [--recover] commit-id url
++'git-ssh-pull' [-c] [-t] [-a] [-d] [-v] [--recover] commit-id url
+ DESCRIPTION
+ -----------
+-Pulls from a remote repository over ssh connection, invoking git-rpush on
+-the other end.
++Pulls from a remote repository over ssh connection, invoking git-ssh-push
++on the other end.
+ OPTIONS
+ -------
+diff --git a/Documentation/git-rpush.txt b/Documentation/git-ssh-push.txt
+similarity index 71%
+rename from Documentation/git-rpush.txt
+rename to Documentation/git-ssh-push.txt
+--- a/Documentation/git-rpush.txt
++++ b/Documentation/git-ssh-push.txt
+@@ -1,19 +1,19 @@
+-git-rpush(1)
+-============
++git-ssh-push(1)
++===============
+ v0.1, May 2005
+ NAME
+ ----
+-git-rpush - Helper "server-side" program used by git-rpull
++git-ssh-push - Helper "server-side" program used by git-ssh-pull
+ SYNOPSIS
+ --------
+-'git-rpush'
++'git-ssh-push'
+ DESCRIPTION
+ -----------
+-Helper "server-side" program used by git-rpull.
++Helper "server-side" program used by git-ssh-pull.
+ Author
+diff --git a/Documentation/git.txt b/Documentation/git.txt
+--- a/Documentation/git.txt
++++ b/Documentation/git.txt
+@@ -148,7 +148,7 @@ link:git-resolve-script.html[git-resolve
+ link:git-tag-script.html[git-tag-script]::
+       An example script to create a tag object signed with GPG
+-link:git-rpull.html[git-rpull]::
++link:git-ssh-pull.html[git-ssh-pull]::
+       Pulls from a remote repository over ssh connection
+ Interogators:
+@@ -156,8 +156,8 @@ Interogators:
+ link:git-diff-helper.html[git-diff-helper]::
+       Generates patch format output for git-diff-*
+-link:git-rpush.html[git-rpush]::
+-      Helper "server-side" program used by git-rpull
++link:git-ssh-push.html[git-ssh-push]::
++      Helper "server-side" program used by git-ssh-pull
+diff --git a/Makefile b/Makefile
+--- a/Makefile
++++ b/Makefile
+@@ -30,7 +30,7 @@ PROG=   git-update-cache git-diff-files 
+       git-checkout-cache git-diff-tree git-rev-tree git-ls-files \
+       git-check-files git-ls-tree git-merge-base git-merge-cache \
+       git-unpack-file git-export git-diff-cache git-convert-cache \
+-      git-http-pull git-rpush git-rpull git-rev-list git-mktag \
++      git-http-pull git-ssh-push git-ssh-pull git-rev-list git-mktag \
+       git-diff-helper git-tar-tree git-local-pull git-write-blob \
+       git-get-tar-commit-id git-mkdelta git-apply git-stripspace
+@@ -105,8 +105,8 @@ git-diff-cache: diff-cache.c
+ git-convert-cache: convert-cache.c
+ git-http-pull: http-pull.c pull.c
+ git-local-pull: local-pull.c pull.c
+-git-rpush: rsh.c
+-git-rpull: rsh.c pull.c
++git-ssh-push: rsh.c
++git-ssh-pull: rsh.c pull.c
+ git-rev-list: rev-list.c
+ git-mktag: mktag.c
+ git-diff-helper: diff-helper.c
+diff --git a/rpull.c b/ssh-pull.c
+similarity index 97%
+rename from rpull.c
+rename to ssh-pull.c
+--- a/rpull.c
++++ b/ssh-pull.c
+@@ -64,13 +64,13 @@ int main(int argc, char **argv)
+               arg++;
+       }
+       if (argc < arg + 2) {
+-              usage("git-rpull [-c] [-t] [-a] [-v] [-d] [--recover] commit-id url");
++              usage("git-ssh-pull [-c] [-t] [-a] [-v] [-d] [--recover] commit-id url");
+               return 1;
+       }
+       commit_id = argv[arg];
+       url = argv[arg + 1];
+-      if (setup_connection(&fd_in, &fd_out, "git-rpush", url, arg, argv + 1))
++      if (setup_connection(&fd_in, &fd_out, "git-ssh-push", url, arg, argv + 1))
+               return 1;
+       if (get_version())
+diff --git a/rpush.c b/ssh-push.c
+similarity index 93%
+rename from rpush.c
+rename to ssh-push.c
+--- a/rpush.c
++++ b/ssh-push.c
+@@ -16,7 +16,7 @@ int serve_object(int fd_in, int fd_out) 
+       do {
+               size = read(fd_in, sha1 + posn, 20 - posn);
+               if (size < 0) {
+-                      perror("git-rpush: read ");
++                      perror("git-ssh-push: read ");
+                       return -1;
+               }
+               if (!size)
+@@ -30,7 +30,7 @@ int serve_object(int fd_in, int fd_out) 
+       buf = map_sha1_file(sha1, &objsize);
+       
+       if (!buf) {
+-              fprintf(stderr, "git-rpush: could not find %s\n", 
++              fprintf(stderr, "git-ssh-push: could not find %s\n", 
+                       sha1_to_hex(sha1));
+               remote = -1;
+       }
+@@ -45,9 +45,9 @@ int serve_object(int fd_in, int fd_out) 
+               size = write(fd_out, buf + posn, objsize - posn);
+               if (size <= 0) {
+                       if (!size) {
+-                              fprintf(stderr, "git-rpush: write closed");
++                              fprintf(stderr, "git-ssh-push: write closed");
+                       } else {
+-                              perror("git-rpush: write ");
++                              perror("git-ssh-push: write ");
+                       }
+                       return -1;
+               }
+@@ -71,7 +71,7 @@ void service(int fd_in, int fd_out) {
+               retval = read(fd_in, &type, 1);
+               if (retval < 1) {
+                       if (retval < 0)
+-                              perror("rpush: read ");
++                              perror("git-ssh-push: read ");
+                       return;
+               }
+               if (type == 'v' && serve_version(fd_in, fd_out))
+@@ -91,12 +91,12 @@ int main(int argc, char **argv)
+                 arg++;
+         }
+         if (argc < arg + 2) {
+-              usage("git-rpush [-c] [-t] [-a] commit-id url");
++              usage("git-ssh-push [-c] [-t] [-a] commit-id url");
+                 return 1;
+         }
+       commit_id = argv[arg];
+       url = argv[arg + 1];
+-      if (setup_connection(&fd_in, &fd_out, "git-rpull", url, arg, argv + 1))
++      if (setup_connection(&fd_in, &fd_out, "git-ssh-pull", url, arg, argv + 1))
+               return 1;
+       service(fd_in, fd_out);
diff --git a/t/t4100/t-apply-2.expect b/t/t4100/t-apply-2.expect
new file mode 100644 (file)
index 0000000..d1e6459
--- /dev/null
@@ -0,0 +1,5 @@
+ Makefile         |    2 +-
+ git-fetch-script |    5 -----
+ git-pull-script  |   34 +---------------------------------
+ 3 files changed, 2 insertions(+), 39 deletions(-)
+ copy git-pull-script => git-fetch-script (87%)
diff --git a/t/t4100/t-apply-2.patch b/t/t4100/t-apply-2.patch
new file mode 100644 (file)
index 0000000..cfdc808
--- /dev/null
@@ -0,0 +1,72 @@
+7ef76925d9c19ef74874e1735e2436e56d0c4897 (from 6b14d7faf0bad026a81a27bac07b47691f621b8f)
+diff --git a/Makefile b/Makefile
+--- a/Makefile
++++ b/Makefile
+@@ -20,7 +20,7 @@ INSTALL=install
+ SCRIPTS=git-apply-patch-script git-merge-one-file-script git-prune-script \
+       git-pull-script git-tag-script git-resolve-script git-whatchanged \
+-      git-deltafy-script
++      git-deltafy-script git-fetch-script
+ PROG=   git-update-cache git-diff-files git-init-db git-write-tree \
+       git-read-tree git-commit-tree git-cat-file git-fsck-cache \
+diff --git a/git-pull-script b/git-fetch-script
+similarity index 87%
+copy from git-pull-script
+copy to git-fetch-script
+--- a/git-pull-script
++++ b/git-fetch-script
+@@ -39,8 +39,3 @@ download_one "$merge_repo/$merge_name" "
+ echo "Getting object database"
+ download_objects "$merge_repo" "$(cat "$GIT_DIR"/MERGE_HEAD)"
+-
+-git-resolve-script \
+-      "$(cat "$GIT_DIR"/HEAD)" \
+-      "$(cat "$GIT_DIR"/MERGE_HEAD)" \
+-      "$merge_repo"
+diff --git a/git-pull-script b/git-pull-script
+--- a/git-pull-script
++++ b/git-pull-script
+@@ -6,39 +6,7 @@ merge_name=${2:-HEAD}
+ : ${GIT_DIR=.git}
+ : ${GIT_OBJECT_DIRECTORY="${SHA1_FILE_DIRECTORY-"$GIT_DIR/objects"}"}
+-download_one () {
+-      # remote_path="$1" local_file="$2"
+-      case "$1" in
+-      http://*)
+-              wget -q -O "$2" "$1" ;;
+-      /*)
+-              test -f "$1" && cat >"$2" "$1" ;;
+-      *)
+-              rsync -L "$1" "$2" ;;
+-      esac
+-}
+-
+-download_objects () {
+-      # remote_repo="$1" head_sha1="$2"
+-      case "$1" in
+-      http://*)
+-              git-http-pull -a "$2" "$1/"
+-              ;;
+-      /*)
+-              git-local-pull -l -a "$2" "$1/"
+-              ;;
+-      *)
+-              rsync -avz --ignore-existing \
+-                      "$1/objects/." "$GIT_OBJECT_DIRECTORY"/.
+-              ;;
+-      esac
+-}
+-
+-echo "Getting remote $merge_name"
+-download_one "$merge_repo/$merge_name" "$GIT_DIR"/MERGE_HEAD
+-
+-echo "Getting object database"
+-download_objects "$merge_repo" "$(cat "$GIT_DIR"/MERGE_HEAD)"
++git-fetch-script "$merge_repo" "$merge_name"
+ git-resolve-script \
+       "$(cat "$GIT_DIR"/HEAD)" \
diff --git a/t/t4100/t-apply-3.expect b/t/t4100/t-apply-3.expect
new file mode 100644 (file)
index 0000000..912a552
--- /dev/null
@@ -0,0 +1,7 @@
+ Documentation/git-ls-tree.txt |   20 +-
+ ls-tree.c                     |  459 ++++++++++++++++++++++-------------------
+ t/t3100-ls-tree-restrict.sh   |    3 
+ tree.c                        |    2 
+ tree.h                        |    1 
+ 5 files changed, 262 insertions(+), 223 deletions(-)
+ rewrite ls-tree.c (82%)
diff --git a/t/t4100/t-apply-3.patch b/t/t4100/t-apply-3.patch
new file mode 100644 (file)
index 0000000..90cdbaa
--- /dev/null
@@ -0,0 +1,567 @@
+6af1f0192ff8740fe77db7cf02c739ccfbdf119c (from 2bc2564145835996734d6ed5d1880f85b17233d6)
+diff --git a/Documentation/git-ls-tree.txt b/Documentation/git-ls-tree.txt
+--- a/Documentation/git-ls-tree.txt
++++ b/Documentation/git-ls-tree.txt
+@@ -4,23 +4,26 @@ v0.1, May 2005
+ NAME
+ ----
+-git-ls-tree - Displays a tree object in human readable form
++git-ls-tree - Lists the contents of a tree object.
+ SYNOPSIS
+ --------
+-'git-ls-tree' [-r] [-z] <tree-ish> [paths...]
++'git-ls-tree' [-d] [-r] [-z] <tree-ish> [paths...]
+ DESCRIPTION
+ -----------
+-Converts the tree object to a human readable (and script processable)
+-form.
++Lists the contents of a tree object, like what "/bin/ls -a" does
++in the current working directory.
+ OPTIONS
+ -------
+ <tree-ish>::
+       Id of a tree.
++-d::
++      show only the named tree entry itself, not its children
++
+ -r::
+       recurse into sub-trees
+@@ -28,18 +31,19 @@ OPTIONS
+       \0 line termination on output
+ paths::
+-      Optionally, restrict the output of git-ls-tree to specific
+-      paths. Directories will only list their tree blob ids.
+-      Implies -r.
++      When paths are given, shows them.  Otherwise implicitly
++      uses the root level of the tree as the sole path argument.
++
+ Output Format
+ -------------
+-        <mode>\t      <type>\t        <object>\t      <file>
++        <mode> SP <type> SP <object> TAB <file>
+ Author
+ ------
+ Written by Linus Torvalds <torvalds@osdl.org>
++Completely rewritten from scratch by Junio C Hamano <junkio@cox.net>
+ Documentation
+ --------------
+diff --git a/ls-tree.c b/ls-tree.c
+dissimilarity index 82%
+--- ls-tree.c
++++ ls-tree.c
+@@ -1,212 +1,247 @@
+-/*
+- * GIT - The information manager from hell
+- *
+- * Copyright (C) Linus Torvalds, 2005
+- */
+-#include "cache.h"
+-
+-static int line_termination = '\n';
+-static int recursive = 0;
+-
+-struct path_prefix {
+-      struct path_prefix *prev;
+-      const char *name;
+-};
+-
+-#define DEBUG(fmt, ...)       
+-
+-static int string_path_prefix(char *buff, size_t blen, struct path_prefix *prefix)
+-{
+-      int len = 0;
+-      if (prefix) {
+-              if (prefix->prev) {
+-                      len = string_path_prefix(buff,blen,prefix->prev);
+-                      buff += len;
+-                      blen -= len;
+-                      if (blen > 0) {
+-                              *buff = '/';
+-                              len++;
+-                              buff++;
+-                              blen--;
+-                      }
+-              }
+-              strncpy(buff,prefix->name,blen);
+-              return len + strlen(prefix->name);
+-      }
+-
+-      return 0;
+-}
+-
+-static void print_path_prefix(struct path_prefix *prefix)
+-{
+-      if (prefix) {
+-              if (prefix->prev) {
+-                      print_path_prefix(prefix->prev);
+-                      putchar('/');
+-              }
+-              fputs(prefix->name, stdout);
+-      }
+-}
+-
+-/*
+- * return:
+- *    -1 if prefix is *not* a subset of path
+- *     0 if prefix == path
+- *     1 if prefix is a subset of path
+- */
+-static int pathcmp(const char *path, struct path_prefix *prefix)
+-{
+-      char buff[PATH_MAX];
+-      int len,slen;
+-
+-      if (prefix == NULL)
+-              return 1;
+-
+-      len = string_path_prefix(buff, sizeof buff, prefix);
+-      slen = strlen(path);
+-
+-      if (slen < len)
+-              return -1;
+-
+-      if (strncmp(path,buff,len) == 0) {
+-              if (slen == len)
+-                      return 0;
+-              else
+-                      return 1;
+-      }
+-
+-      return -1;
+-}     
+-
+-/*
+- * match may be NULL, or a *sorted* list of paths
+- */
+-static void list_recursive(void *buffer,
+-                         const char *type,
+-                         unsigned long size,
+-                         struct path_prefix *prefix,
+-                         char **match, int matches)
+-{
+-      struct path_prefix this_prefix;
+-      this_prefix.prev = prefix;
+-
+-      if (strcmp(type, "tree"))
+-              die("expected a 'tree' node");
+-
+-      if (matches)
+-              recursive = 1;
+-
+-      while (size) {
+-              int namelen = strlen(buffer)+1;
+-              void *eltbuf = NULL;
+-              char elttype[20];
+-              unsigned long eltsize;
+-              unsigned char *sha1 = buffer + namelen;
+-              char *path = strchr(buffer, ' ') + 1;
+-              unsigned int mode;
+-              const char *matched = NULL;
+-              int mtype = -1;
+-              int mindex;
+-
+-              if (size < namelen + 20 || sscanf(buffer, "%o", &mode) != 1)
+-                      die("corrupt 'tree' file");
+-              buffer = sha1 + 20;
+-              size -= namelen + 20;
+-
+-              this_prefix.name = path;
+-              for ( mindex = 0; mindex < matches; mindex++) {
+-                      mtype = pathcmp(match[mindex],&this_prefix);
+-                      if (mtype >= 0) {
+-                              matched = match[mindex];
+-                              break;
+-                      }
+-              }
+-
+-              /*
+-               * If we're not matching, or if this is an exact match,
+-               * print out the info
+-               */
+-              if (!matches || (matched != NULL && mtype == 0)) {
+-                      printf("%06o %s %s\t", mode,
+-                             S_ISDIR(mode) ? "tree" : "blob",
+-                             sha1_to_hex(sha1));
+-                      print_path_prefix(&this_prefix);
+-                      putchar(line_termination);
+-              }
+-
+-              if (! recursive || ! S_ISDIR(mode))
+-                      continue;
+-
+-              if (matches && ! matched)
+-                      continue;
+-
+-              if (! (eltbuf = read_sha1_file(sha1, elttype, &eltsize)) ) {
+-                      error("cannot read %s", sha1_to_hex(sha1));
+-                      continue;
+-              }
+-
+-              /* If this is an exact directory match, we may have
+-               * directory files following this path. Match on them.
+-               * Otherwise, we're at a pach subcomponent, and we need
+-               * to try to match again.
+-               */
+-              if (mtype == 0)
+-                      mindex++;
+-
+-              list_recursive(eltbuf, elttype, eltsize, &this_prefix, &match[mindex], matches-mindex);
+-              free(eltbuf);
+-      }
+-}
+-
+-static int qcmp(const void *a, const void *b)
+-{
+-      return strcmp(*(char **)a, *(char **)b);
+-}
+-
+-static int list(unsigned char *sha1,char **path)
+-{
+-      void *buffer;
+-      unsigned long size;
+-      int npaths;
+-
+-      for (npaths = 0; path[npaths] != NULL; npaths++)
+-              ;
+-
+-      qsort(path,npaths,sizeof(char *),qcmp);
+-
+-      buffer = read_object_with_reference(sha1, "tree", &size, NULL);
+-      if (!buffer)
+-              die("unable to read sha1 file");
+-      list_recursive(buffer, "tree", size, NULL, path, npaths);
+-      free(buffer);
+-      return 0;
+-}
+-
+-static const char *ls_tree_usage = "git-ls-tree [-r] [-z] <key> [paths...]";
+-
+-int main(int argc, char **argv)
+-{
+-      unsigned char sha1[20];
+-
+-      while (1 < argc && argv[1][0] == '-') {
+-              switch (argv[1][1]) {
+-              case 'z':
+-                      line_termination = 0;
+-                      break;
+-              case 'r':
+-                      recursive = 1;
+-                      break;
+-              default:
+-                      usage(ls_tree_usage);
+-              }
+-              argc--; argv++;
+-      }
+-
+-      if (argc < 2)
+-              usage(ls_tree_usage);
+-      if (get_sha1(argv[1], sha1) < 0)
+-              usage(ls_tree_usage);
+-      if (list(sha1, &argv[2]) < 0)
+-              die("list failed");
+-      return 0;
+-}
++/*
++ * GIT - The information manager from hell
++ *
++ * Copyright (C) Linus Torvalds, 2005
++ */
++#include "cache.h"
++#include "blob.h"
++#include "tree.h"
++
++static int line_termination = '\n';
++#define LS_RECURSIVE 1
++#define LS_TREE_ONLY 2
++static int ls_options = 0;
++
++static struct tree_entry_list root_entry;
++
++static void prepare_root(unsigned char *sha1)
++{
++      unsigned char rsha[20];
++      unsigned long size;
++      void *buf;
++      struct tree *root_tree;
++
++      buf = read_object_with_reference(sha1, "tree", &size, rsha);
++      free(buf);
++      if (!buf)
++              die("Could not read %s", sha1_to_hex(sha1));
++
++      root_tree = lookup_tree(rsha);
++      if (!root_tree)
++              die("Could not read %s", sha1_to_hex(sha1));
++
++      /* Prepare a fake entry */
++      root_entry.directory = 1;
++      root_entry.executable = root_entry.symlink = 0;
++      root_entry.mode = S_IFDIR;
++      root_entry.name = "";
++      root_entry.item.tree = root_tree;
++      root_entry.parent = NULL;
++}
++
++static int prepare_children(struct tree_entry_list *elem)
++{
++      if (!elem->directory)
++              return -1;
++      if (!elem->item.tree->object.parsed) {
++              struct tree_entry_list *e;
++              if (parse_tree(elem->item.tree))
++                      return -1;
++              /* Set up the parent link */
++              for (e = elem->item.tree->entries; e; e = e->next)
++                      e->parent = elem;
++      }
++      return 0;
++}
++
++static struct tree_entry_list *find_entry_0(struct tree_entry_list *elem,
++                                          const char *path,
++                                          const char *path_end)
++{
++      const char *ep;
++      int len;
++
++      while (path < path_end) {
++              if (prepare_children(elem))
++                      return NULL;
++
++              /* In elem->tree->entries, find the one that has name
++               * that matches what is between path and ep.
++               */
++              elem = elem->item.tree->entries;
++
++              ep = strchr(path, '/');
++              if (!ep || path_end <= ep)
++                      ep = path_end;
++              len = ep - path;
++
++              while (elem) {
++                      if ((strlen(elem->name) == len) &&
++                          !strncmp(elem->name, path, len))
++                              break;
++                      elem = elem->next;
++              }
++              if (path_end <= ep || !elem)
++                      return elem;
++              while (*ep == '/' && ep < path_end)
++                      ep++;
++              path = ep;
++      }
++      return NULL;
++}
++
++static struct tree_entry_list *find_entry(const char *path,
++                                        const char *path_end)
++{
++      /* Find tree element, descending from root, that
++       * corresponds to the named path, lazily expanding
++       * the tree if possible.
++       */
++      if (path == path_end) {
++              /* Special.  This is the root level */
++              return &root_entry;
++      }
++      return find_entry_0(&root_entry, path, path_end);
++}
++
++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");
++}
++
++static const char *entry_hex(struct tree_entry_list *e)
++{
++      return sha1_to_hex(e->directory
++                         ? e->item.tree->object.sha1
++                         : e->item.blob->object.sha1);
++}
++
++/* forward declaration for mutually recursive routines */
++static int show_entry(struct tree_entry_list *, int);
++
++static int show_children(struct tree_entry_list *e, int level)
++{
++      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);
++              e = e->next;
++      }
++      return 0;
++}
++
++static int show_entry(struct tree_entry_list *e, int level)
++{
++      int err = 0; 
++
++      if (e != &root_entry) {
++              printf("%06o %s %s      ", e->mode, entry_type(e),
++                     entry_hex(e));
++              show_entry_name(e);
++              putchar(line_termination);
++      }
++
++      if (e->directory) {
++              /* If this is a directory, we have the following cases:
++               * (1) This is the top-level request (explicit path from the
++               *     command line, or "root" if there is no command line).
++               *  a. Without any flag.  We show direct children.  We do not 
++               *     recurse into them.
++               *  b. With -r.  We do recurse into children.
++               *  c. With -d.  We do not recurse into children.
++               * (2) We came here because our caller is either (1-a) or
++               *     (1-b).
++               *  a. Without any flag.  We do not show our children (which
++               *     are grandchildren for the original request).
++               *  b. With -r.  We continue to recurse into our children.
++               *  c. With -d.  We should not have come here to begin with.
++               */
++              if (level == 0 && !(ls_options & LS_TREE_ONLY))
++                      /* case (1)-a and (1)-b */
++                      err = err | show_children(e, level+1);
++              else if (level && ls_options & LS_RECURSIVE)
++                      /* case (2)-b */
++                      err = err | show_children(e, level+1);
++      }
++      return err;
++}
++
++static int list_one(const char *path, const char *path_end)
++{
++      int err = 0;
++      struct tree_entry_list *e = find_entry(path, path_end);
++      if (!e) {
++              /* traditionally ls-tree does not complain about
++               * missing path.  We may change this later to match
++               * what "/bin/ls -a" does, which is to complain.
++               */
++              return err;
++      }
++      err = err | show_entry(e, 0);
++      return err;
++}
++
++static int list(char **path)
++{
++      int i;
++      int err = 0;
++      for (i = 0; path[i]; i++) {
++              int len = strlen(path[i]);
++              while (0 <= len && path[i][len] == '/')
++                      len--;
++              err = err | list_one(path[i], path[i] + len);
++      }
++      return err;
++}
++
++static const char *ls_tree_usage =
++      "git-ls-tree [-d] [-r] [-z] <tree-ish> [path...]";
++
++int main(int argc, char **argv)
++{
++      static char *path0[] = { "", NULL };
++      char **path;
++      unsigned char sha1[20];
++
++      while (1 < argc && argv[1][0] == '-') {
++              switch (argv[1][1]) {
++              case 'z':
++                      line_termination = 0;
++                      break;
++              case 'r':
++                      ls_options |= LS_RECURSIVE;
++                      break;
++              case 'd':
++                      ls_options |= LS_TREE_ONLY;
++                      break;
++              default:
++                      usage(ls_tree_usage);
++              }
++              argc--; argv++;
++      }
++
++      if (argc < 2)
++              usage(ls_tree_usage);
++      if (get_sha1(argv[1], sha1) < 0)
++              usage(ls_tree_usage);
++
++      path = (argc == 2) ? path0 : (argv + 2);
++      prepare_root(sha1);
++      if (list(path) < 0)
++              die("list failed");
++      return 0;
++}
+diff --git a/t/t3100-ls-tree-restrict.sh b/t/t3100-ls-tree-restrict.sh
+--- a/t/t3100-ls-tree-restrict.sh
++++ b/t/t3100-ls-tree-restrict.sh
+@@ -74,8 +74,8 @@ test_expect_success \
+     'ls-tree filtered' \
+     'git-ls-tree $tree path1 path0 >current &&
+      cat >expected <<\EOF &&
+-100644 blob X path0
+ 120000 blob X path1
++100644 blob X path0
+ EOF
+      test_output'
+@@ -85,7 +85,6 @@ test_expect_success \
+      cat >expected <<\EOF &&
+ 040000 tree X path2
+ 040000 tree X path2/baz
+-100644 blob X path2/baz/b
+ 120000 blob X path2/bazbo
+ 100644 blob X path2/foo
+ EOF
+diff --git a/tree.c b/tree.c
+--- a/tree.c
++++ b/tree.c
+@@ -133,7 +133,7 @@ int parse_tree_buffer(struct tree *item,
+               }
+               if (obj)
+                       add_ref(&item->object, obj);
+-
++              entry->parent = NULL; /* needs to be filled by the user */
+               *list_p = entry;
+               list_p = &entry->next;
+       }
+diff --git a/tree.h b/tree.h
+--- a/tree.h
++++ b/tree.h
+@@ -16,6 +16,7 @@ struct tree_entry_list {
+               struct tree *tree;
+               struct blob *blob;
+       } item;
++      struct tree_entry_list *parent;
+ };
+ struct tree {
diff --git a/t/t4100/t-apply-4.expect b/t/t4100/t-apply-4.expect
new file mode 100644 (file)
index 0000000..1ec028b
--- /dev/null
@@ -0,0 +1,5 @@
+ t/t0000-basic.sh |    0 
+ t/test-lib.sh    |    0 
+ 2 files changed, 0 insertions(+), 0 deletions(-)
+ mode change 100644 => 100755 t/t0000-basic.sh
+ mode change 100644 => 100755 t/test-lib.sh
diff --git a/t/t4100/t-apply-4.patch b/t/t4100/t-apply-4.patch
new file mode 100644 (file)
index 0000000..4a56ab5
--- /dev/null
@@ -0,0 +1,7 @@
+ceede59ea90cebad52ba9c8263fef3fb6ef17593 (from 368f99d57e8ed17243f2e164431449d48bfca2fb)
+diff --git a/t/t0000-basic.sh b/t/t0000-basic.sh
+old mode 100644
+new mode 100755
+diff --git a/t/test-lib.sh b/t/test-lib.sh
+old mode 100644
+new mode 100755
diff --git a/t/t4100/t-apply-5.expect b/t/t4100/t-apply-5.expect
new file mode 100644 (file)
index 0000000..b387df1
--- /dev/null
@@ -0,0 +1,19 @@
+ Documentation/git-rpull.txt    |   50 -------------------
+ Documentation/git-rpush.txt    |   30 ------------
+ Documentation/git-ssh-pull.txt |   50 +++++++++++++++++++
+ Documentation/git-ssh-push.txt |   30 ++++++++++++
+ Documentation/git.txt          |    6 +-
+ Makefile                       |    6 +-
+ rpull.c                        |   83 --------------------------------
+ rpush.c                        |  104 ----------------------------------------
+ ssh-pull.c                     |   83 ++++++++++++++++++++++++++++++++
+ ssh-push.c                     |  104 ++++++++++++++++++++++++++++++++++++++++
+ 10 files changed, 273 insertions(+), 273 deletions(-)
+ delete Documentation/git-rpull.txt
+ delete Documentation/git-rpush.txt
+ create Documentation/git-ssh-pull.txt
+ create Documentation/git-ssh-push.txt
+ delete rpull.c
+ delete rpush.c
+ create ssh-pull.c
+ create ssh-push.c
diff --git a/t/t4100/t-apply-5.patch b/t/t4100/t-apply-5.patch
new file mode 100644 (file)
index 0000000..de11623
--- /dev/null
@@ -0,0 +1,612 @@
+diff a/Documentation/git-rpull.txt b/Documentation/git-rpull.txt
+--- a/Documentation/git-rpull.txt
++++ /dev/null
+@@ -1,50 +0,0 @@
+-git-rpull(1)
+-============
+-v0.1, May 2005
+-
+-NAME
+-----
+-git-rpull - Pulls from a remote repository over ssh connection
+-
+-
+-
+-SYNOPSIS
+---------
+-'git-rpull' [-c] [-t] [-a] [-d] [-v] [--recover] commit-id url
+-
+-DESCRIPTION
+------------
+-Pulls from a remote repository over ssh connection, invoking git-rpush on
+-the other end.
+-
+-OPTIONS
+--------
+--c::
+-      Get the commit objects.
+--t::
+-      Get trees associated with the commit objects.
+--a::
+-      Get all the objects.
+--d::
+-      Do not check for delta base objects (use this option
+-      only when you know the remote repository is not
+-      deltified).
+---recover::
+-      Check dependency of deltified object more carefully than
+-      usual, to recover after earlier pull that was interrupted.
+--v::
+-      Report what is downloaded.
+-
+-
+-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
+-
+diff a/Documentation/git-rpush.txt b/Documentation/git-rpush.txt
+--- a/Documentation/git-rpush.txt
++++ /dev/null
+@@ -1,30 +0,0 @@
+-git-rpush(1)
+-============
+-v0.1, May 2005
+-
+-NAME
+-----
+-git-rpush - Helper "server-side" program used by git-rpull
+-
+-
+-SYNOPSIS
+---------
+-'git-rpush'
+-
+-DESCRIPTION
+------------
+-Helper "server-side" program used by git-rpull.
+-
+-
+-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
+-
+diff a/Documentation/git-ssh-pull.txt b/Documentation/git-ssh-pull.txt
+--- /dev/null
++++ b/Documentation/git-ssh-pull.txt
+@@ -0,0 +1,50 @@
++git-ssh-pull(1)
++===============
++v0.1, May 2005
++
++NAME
++----
++git-ssh-pull - Pulls from a remote repository over ssh connection
++
++
++
++SYNOPSIS
++--------
++'git-ssh-pull' [-c] [-t] [-a] [-d] [-v] [--recover] commit-id url
++
++DESCRIPTION
++-----------
++Pulls from a remote repository over ssh connection, invoking git-ssh-push
++on the other end.
++
++OPTIONS
++-------
++-c::
++      Get the commit objects.
++-t::
++      Get trees associated with the commit objects.
++-a::
++      Get all the objects.
++-d::
++      Do not check for delta base objects (use this option
++      only when you know the remote repository is not
++      deltified).
++--recover::
++      Check dependency of deltified object more carefully than
++      usual, to recover after earlier pull that was interrupted.
++-v::
++      Report what is downloaded.
++
++
++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
++
+diff a/Documentation/git-ssh-push.txt b/Documentation/git-ssh-push.txt
+--- /dev/null
++++ b/Documentation/git-ssh-push.txt
+@@ -0,0 +1,30 @@
++git-ssh-push(1)
++===============
++v0.1, May 2005
++
++NAME
++----
++git-ssh-push - Helper "server-side" program used by git-ssh-pull
++
++
++SYNOPSIS
++--------
++'git-ssh-push'
++
++DESCRIPTION
++-----------
++Helper "server-side" program used by git-ssh-pull.
++
++
++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
++
+diff a/Documentation/git.txt b/Documentation/git.txt
+--- a/Documentation/git.txt
++++ b/Documentation/git.txt
+@@ -148,7 +148,7 @@ link:git-resolve-script.html[git-resolve
+ link:git-tag-script.html[git-tag-script]::
+       An example script to create a tag object signed with GPG
+-link:git-rpull.html[git-rpull]::
++link:git-ssh-pull.html[git-ssh-pull]::
+       Pulls from a remote repository over ssh connection
+ Interogators:
+@@ -156,8 +156,8 @@ Interogators:
+ link:git-diff-helper.html[git-diff-helper]::
+       Generates patch format output for git-diff-*
+-link:git-rpush.html[git-rpush]::
+-      Helper "server-side" program used by git-rpull
++link:git-ssh-push.html[git-ssh-push]::
++      Helper "server-side" program used by git-ssh-pull
+diff a/Makefile b/Makefile
+--- a/Makefile
++++ b/Makefile
+@@ -30,7 +30,7 @@ PROG=   git-update-cache git-diff-files 
+       git-checkout-cache git-diff-tree git-rev-tree git-ls-files \
+       git-check-files git-ls-tree git-merge-base git-merge-cache \
+       git-unpack-file git-export git-diff-cache git-convert-cache \
+-      git-http-pull git-rpush git-rpull git-rev-list git-mktag \
++      git-http-pull git-ssh-push git-ssh-pull git-rev-list git-mktag \
+       git-diff-helper git-tar-tree git-local-pull git-write-blob \
+       git-get-tar-commit-id git-mkdelta git-apply git-stripspace
+@@ -105,8 +105,8 @@ git-diff-cache: diff-cache.c
+ git-convert-cache: convert-cache.c
+ git-http-pull: http-pull.c pull.c
+ git-local-pull: local-pull.c pull.c
+-git-rpush: rsh.c
+-git-rpull: rsh.c pull.c
++git-ssh-push: rsh.c
++git-ssh-pull: rsh.c pull.c
+ git-rev-list: rev-list.c
+ git-mktag: mktag.c
+ git-diff-helper: diff-helper.c
+diff a/rpull.c b/rpull.c
+--- a/rpull.c
++++ /dev/null
+@@ -1,83 +0,0 @@
+-#include "cache.h"
+-#include "commit.h"
+-#include "rsh.h"
+-#include "pull.h"
+-
+-static int fd_in;
+-static int fd_out;
+-
+-static unsigned char remote_version = 0;
+-static unsigned char local_version = 1;
+-
+-int fetch(unsigned char *sha1)
+-{
+-      int ret;
+-      signed char remote;
+-      char type = 'o';
+-      if (has_sha1_file(sha1))
+-              return 0;
+-      write(fd_out, &type, 1);
+-      write(fd_out, sha1, 20);
+-      if (read(fd_in, &remote, 1) < 1)
+-              return -1;
+-      if (remote < 0)
+-              return remote;
+-      ret = write_sha1_from_fd(sha1, fd_in);
+-      if (!ret)
+-              pull_say("got %s\n", sha1_to_hex(sha1));
+-      return ret;
+-}
+-
+-int get_version(void)
+-{
+-      char type = 'v';
+-      write(fd_out, &type, 1);
+-      write(fd_out, &local_version, 1);
+-      if (read(fd_in, &remote_version, 1) < 1) {
+-              return error("Couldn't read version from remote end");
+-      }
+-      return 0;
+-}
+-
+-int main(int argc, char **argv)
+-{
+-      char *commit_id;
+-      char *url;
+-      int arg = 1;
+-
+-      while (arg < argc && argv[arg][0] == '-') {
+-              if (argv[arg][1] == 't') {
+-                      get_tree = 1;
+-              } else if (argv[arg][1] == 'c') {
+-                      get_history = 1;
+-              } else if (argv[arg][1] == 'd') {
+-                      get_delta = 0;
+-              } else if (!strcmp(argv[arg], "--recover")) {
+-                      get_delta = 2;
+-              } else if (argv[arg][1] == 'a') {
+-                      get_all = 1;
+-                      get_tree = 1;
+-                      get_history = 1;
+-              } else if (argv[arg][1] == 'v') {
+-                      get_verbosely = 1;
+-              }
+-              arg++;
+-      }
+-      if (argc < arg + 2) {
+-              usage("git-rpull [-c] [-t] [-a] [-v] [-d] [--recover] commit-id url");
+-              return 1;
+-      }
+-      commit_id = argv[arg];
+-      url = argv[arg + 1];
+-
+-      if (setup_connection(&fd_in, &fd_out, "git-rpush", url, arg, argv + 1))
+-              return 1;
+-
+-      if (get_version())
+-              return 1;
+-
+-      if (pull(commit_id))
+-              return 1;
+-
+-      return 0;
+-}
+diff a/rpush.c b/rpush.c
+--- a/rpush.c
++++ /dev/null
+@@ -1,104 +0,0 @@
+-#include "cache.h"
+-#include "rsh.h"
+-#include <sys/socket.h>
+-#include <errno.h>
+-
+-unsigned char local_version = 1;
+-unsigned char remote_version = 0;
+-
+-int serve_object(int fd_in, int fd_out) {
+-      ssize_t size;
+-      int posn = 0;
+-      char sha1[20];
+-      unsigned long objsize;
+-      void *buf;
+-      signed char remote;
+-      do {
+-              size = read(fd_in, sha1 + posn, 20 - posn);
+-              if (size < 0) {
+-                      perror("git-rpush: read ");
+-                      return -1;
+-              }
+-              if (!size)
+-                      return -1;
+-              posn += size;
+-      } while (posn < 20);
+-      
+-      /* fprintf(stderr, "Serving %s\n", sha1_to_hex(sha1)); */
+-      remote = 0;
+-      
+-      buf = map_sha1_file(sha1, &objsize);
+-      
+-      if (!buf) {
+-              fprintf(stderr, "git-rpush: could not find %s\n", 
+-                      sha1_to_hex(sha1));
+-              remote = -1;
+-      }
+-      
+-      write(fd_out, &remote, 1);
+-      
+-      if (remote < 0)
+-              return 0;
+-      
+-      posn = 0;
+-      do {
+-              size = write(fd_out, buf + posn, objsize - posn);
+-              if (size <= 0) {
+-                      if (!size) {
+-                              fprintf(stderr, "git-rpush: write closed");
+-                      } else {
+-                              perror("git-rpush: write ");
+-                      }
+-                      return -1;
+-              }
+-              posn += size;
+-      } while (posn < objsize);
+-      return 0;
+-}
+-
+-int serve_version(int fd_in, int fd_out)
+-{
+-      if (read(fd_in, &remote_version, 1) < 1)
+-              return -1;
+-      write(fd_out, &local_version, 1);
+-      return 0;
+-}
+-
+-void service(int fd_in, int fd_out) {
+-      char type;
+-      int retval;
+-      do {
+-              retval = read(fd_in, &type, 1);
+-              if (retval < 1) {
+-                      if (retval < 0)
+-                              perror("rpush: read ");
+-                      return;
+-              }
+-              if (type == 'v' && serve_version(fd_in, fd_out))
+-                      return;
+-              if (type == 'o' && serve_object(fd_in, fd_out))
+-                      return;
+-      } while (1);
+-}
+-
+-int main(int argc, char **argv)
+-{
+-      int arg = 1;
+-        char *commit_id;
+-        char *url;
+-      int fd_in, fd_out;
+-      while (arg < argc && argv[arg][0] == '-') {
+-                arg++;
+-        }
+-        if (argc < arg + 2) {
+-              usage("git-rpush [-c] [-t] [-a] commit-id url");
+-                return 1;
+-        }
+-      commit_id = argv[arg];
+-      url = argv[arg + 1];
+-      if (setup_connection(&fd_in, &fd_out, "git-rpull", url, arg, argv + 1))
+-              return 1;
+-
+-      service(fd_in, fd_out);
+-      return 0;
+-}
+diff a/ssh-pull.c b/ssh-pull.c
+--- /dev/null
++++ b/ssh-pull.c
+@@ -0,0 +1,83 @@
++#include "cache.h"
++#include "commit.h"
++#include "rsh.h"
++#include "pull.h"
++
++static int fd_in;
++static int fd_out;
++
++static unsigned char remote_version = 0;
++static unsigned char local_version = 1;
++
++int fetch(unsigned char *sha1)
++{
++      int ret;
++      signed char remote;
++      char type = 'o';
++      if (has_sha1_file(sha1))
++              return 0;
++      write(fd_out, &type, 1);
++      write(fd_out, sha1, 20);
++      if (read(fd_in, &remote, 1) < 1)
++              return -1;
++      if (remote < 0)
++              return remote;
++      ret = write_sha1_from_fd(sha1, fd_in);
++      if (!ret)
++              pull_say("got %s\n", sha1_to_hex(sha1));
++      return ret;
++}
++
++int get_version(void)
++{
++      char type = 'v';
++      write(fd_out, &type, 1);
++      write(fd_out, &local_version, 1);
++      if (read(fd_in, &remote_version, 1) < 1) {
++              return error("Couldn't read version from remote end");
++      }
++      return 0;
++}
++
++int main(int argc, char **argv)
++{
++      char *commit_id;
++      char *url;
++      int arg = 1;
++
++      while (arg < argc && argv[arg][0] == '-') {
++              if (argv[arg][1] == 't') {
++                      get_tree = 1;
++              } else if (argv[arg][1] == 'c') {
++                      get_history = 1;
++              } else if (argv[arg][1] == 'd') {
++                      get_delta = 0;
++              } else if (!strcmp(argv[arg], "--recover")) {
++                      get_delta = 2;
++              } else if (argv[arg][1] == 'a') {
++                      get_all = 1;
++                      get_tree = 1;
++                      get_history = 1;
++              } else if (argv[arg][1] == 'v') {
++                      get_verbosely = 1;
++              }
++              arg++;
++      }
++      if (argc < arg + 2) {
++              usage("git-ssh-pull [-c] [-t] [-a] [-v] [-d] [--recover] commit-id url");
++              return 1;
++      }
++      commit_id = argv[arg];
++      url = argv[arg + 1];
++
++      if (setup_connection(&fd_in, &fd_out, "git-ssh-push", url, arg, argv + 1))
++              return 1;
++
++      if (get_version())
++              return 1;
++
++      if (pull(commit_id))
++              return 1;
++
++      return 0;
++}
+diff a/ssh-push.c b/ssh-push.c
+--- /dev/null
++++ b/ssh-push.c
+@@ -0,0 +1,104 @@
++#include "cache.h"
++#include "rsh.h"
++#include <sys/socket.h>
++#include <errno.h>
++
++unsigned char local_version = 1;
++unsigned char remote_version = 0;
++
++int serve_object(int fd_in, int fd_out) {
++      ssize_t size;
++      int posn = 0;
++      char sha1[20];
++      unsigned long objsize;
++      void *buf;
++      signed char remote;
++      do {
++              size = read(fd_in, sha1 + posn, 20 - posn);
++              if (size < 0) {
++                      perror("git-ssh-push: read ");
++                      return -1;
++              }
++              if (!size)
++                      return -1;
++              posn += size;
++      } while (posn < 20);
++      
++      /* fprintf(stderr, "Serving %s\n", sha1_to_hex(sha1)); */
++      remote = 0;
++      
++      buf = map_sha1_file(sha1, &objsize);
++      
++      if (!buf) {
++              fprintf(stderr, "git-ssh-push: could not find %s\n", 
++                      sha1_to_hex(sha1));
++              remote = -1;
++      }
++      
++      write(fd_out, &remote, 1);
++      
++      if (remote < 0)
++              return 0;
++      
++      posn = 0;
++      do {
++              size = write(fd_out, buf + posn, objsize - posn);
++              if (size <= 0) {
++                      if (!size) {
++                              fprintf(stderr, "git-ssh-push: write closed");
++                      } else {
++                              perror("git-ssh-push: write ");
++                      }
++                      return -1;
++              }
++              posn += size;
++      } while (posn < objsize);
++      return 0;
++}
++
++int serve_version(int fd_in, int fd_out)
++{
++      if (read(fd_in, &remote_version, 1) < 1)
++              return -1;
++      write(fd_out, &local_version, 1);
++      return 0;
++}
++
++void service(int fd_in, int fd_out) {
++      char type;
++      int retval;
++      do {
++              retval = read(fd_in, &type, 1);
++              if (retval < 1) {
++                      if (retval < 0)
++                              perror("git-ssh-push: read ");
++                      return;
++              }
++              if (type == 'v' && serve_version(fd_in, fd_out))
++                      return;
++              if (type == 'o' && serve_object(fd_in, fd_out))
++                      return;
++      } while (1);
++}
++
++int main(int argc, char **argv)
++{
++      int arg = 1;
++        char *commit_id;
++        char *url;
++      int fd_in, fd_out;
++      while (arg < argc && argv[arg][0] == '-') {
++                arg++;
++        }
++        if (argc < arg + 2) {
++              usage("git-ssh-push [-c] [-t] [-a] commit-id url");
++                return 1;
++        }
++      commit_id = argv[arg];
++      url = argv[arg + 1];
++      if (setup_connection(&fd_in, &fd_out, "git-ssh-pull", url, arg, argv + 1))
++              return 1;
++
++      service(fd_in, fd_out);
++      return 0;
++}
diff --git a/t/t4100/t-apply-6.expect b/t/t4100/t-apply-6.expect
new file mode 100644 (file)
index 0000000..1c343d4
--- /dev/null
@@ -0,0 +1,5 @@
+ Makefile         |    2 +-
+ git-fetch-script |   41 +++++++++++++++++++++++++++++++++++++++++
+ git-pull-script  |   34 +---------------------------------
+ 3 files changed, 43 insertions(+), 34 deletions(-)
+ create git-fetch-script
diff --git a/t/t4100/t-apply-6.patch b/t/t4100/t-apply-6.patch
new file mode 100644 (file)
index 0000000..d975363
--- /dev/null
@@ -0,0 +1,101 @@
+diff a/Makefile b/Makefile
+--- a/Makefile
++++ b/Makefile
+@@ -20,7 +20,7 @@ INSTALL=install
+ SCRIPTS=git-apply-patch-script git-merge-one-file-script git-prune-script \
+       git-pull-script git-tag-script git-resolve-script git-whatchanged \
+-      git-deltafy-script
++      git-deltafy-script git-fetch-script
+ PROG=   git-update-cache git-diff-files git-init-db git-write-tree \
+       git-read-tree git-commit-tree git-cat-file git-fsck-cache \
+diff a/git-fetch-script b/git-fetch-script
+--- /dev/null
++++ b/git-fetch-script
+@@ -0,0 +1,41 @@
++#!/bin/sh
++#
++merge_repo=$1
++merge_name=${2:-HEAD}
++
++: ${GIT_DIR=.git}
++: ${GIT_OBJECT_DIRECTORY="${SHA1_FILE_DIRECTORY-"$GIT_DIR/objects"}"}
++
++download_one () {
++      # remote_path="$1" local_file="$2"
++      case "$1" in
++      http://*)
++              wget -q -O "$2" "$1" ;;
++      /*)
++              test -f "$1" && cat >"$2" "$1" ;;
++      *)
++              rsync -L "$1" "$2" ;;
++      esac
++}
++
++download_objects () {
++      # remote_repo="$1" head_sha1="$2"
++      case "$1" in
++      http://*)
++              git-http-pull -a "$2" "$1/"
++              ;;
++      /*)
++              git-local-pull -l -a "$2" "$1/"
++              ;;
++      *)
++              rsync -avz --ignore-existing \
++                      "$1/objects/." "$GIT_OBJECT_DIRECTORY"/.
++              ;;
++      esac
++}
++
++echo "Getting remote $merge_name"
++download_one "$merge_repo/$merge_name" "$GIT_DIR"/MERGE_HEAD
++
++echo "Getting object database"
++download_objects "$merge_repo" "$(cat "$GIT_DIR"/MERGE_HEAD)"
+diff a/git-pull-script b/git-pull-script
+--- a/git-pull-script
++++ b/git-pull-script
+@@ -6,39 +6,7 @@ merge_name=${2:-HEAD}
+ : ${GIT_DIR=.git}
+ : ${GIT_OBJECT_DIRECTORY="${SHA1_FILE_DIRECTORY-"$GIT_DIR/objects"}"}
+-download_one () {
+-      # remote_path="$1" local_file="$2"
+-      case "$1" in
+-      http://*)
+-              wget -q -O "$2" "$1" ;;
+-      /*)
+-              test -f "$1" && cat >"$2" "$1" ;;
+-      *)
+-              rsync -L "$1" "$2" ;;
+-      esac
+-}
+-
+-download_objects () {
+-      # remote_repo="$1" head_sha1="$2"
+-      case "$1" in
+-      http://*)
+-              git-http-pull -a "$2" "$1/"
+-              ;;
+-      /*)
+-              git-local-pull -l -a "$2" "$1/"
+-              ;;
+-      *)
+-              rsync -avz --ignore-existing \
+-                      "$1/objects/." "$GIT_OBJECT_DIRECTORY"/.
+-              ;;
+-      esac
+-}
+-
+-echo "Getting remote $merge_name"
+-download_one "$merge_repo/$merge_name" "$GIT_DIR"/MERGE_HEAD
+-
+-echo "Getting object database"
+-download_objects "$merge_repo" "$(cat "$GIT_DIR"/MERGE_HEAD)"
++git-fetch-script "$merge_repo" "$merge_name"
+ git-resolve-script \
+       "$(cat "$GIT_DIR"/HEAD)" \
diff --git a/t/t4100/t-apply-7.expect b/t/t4100/t-apply-7.expect
new file mode 100644 (file)
index 0000000..1283164
--- /dev/null
@@ -0,0 +1,6 @@
+ Documentation/git-ls-tree.txt |   20 +-
+ ls-tree.c                     |  333 +++++++++++++++++++++++------------------
+ t/t3100-ls-tree-restrict.sh   |    3 
+ tree.c                        |    2 
+ tree.h                        |    1 
+ 5 files changed, 199 insertions(+), 160 deletions(-)
diff --git a/t/t4100/t-apply-7.patch b/t/t4100/t-apply-7.patch
new file mode 100644 (file)
index 0000000..07c6589
--- /dev/null
@@ -0,0 +1,494 @@
+diff a/Documentation/git-ls-tree.txt b/Documentation/git-ls-tree.txt
+--- a/Documentation/git-ls-tree.txt
++++ b/Documentation/git-ls-tree.txt
+@@ -4,23 +4,26 @@ v0.1, May 2005
+ NAME
+ ----
+-git-ls-tree - Displays a tree object in human readable form
++git-ls-tree - Lists the contents of a tree object.
+ SYNOPSIS
+ --------
+-'git-ls-tree' [-r] [-z] <tree-ish> [paths...]
++'git-ls-tree' [-d] [-r] [-z] <tree-ish> [paths...]
+ DESCRIPTION
+ -----------
+-Converts the tree object to a human readable (and script processable)
+-form.
++Lists the contents of a tree object, like what "/bin/ls -a" does
++in the current working directory.
+ OPTIONS
+ -------
+ <tree-ish>::
+       Id of a tree.
++-d::
++      show only the named tree entry itself, not its children
++
+ -r::
+       recurse into sub-trees
+@@ -28,18 +31,19 @@ OPTIONS
+       \0 line termination on output
+ paths::
+-      Optionally, restrict the output of git-ls-tree to specific
+-      paths. Directories will only list their tree blob ids.
+-      Implies -r.
++      When paths are given, shows them.  Otherwise implicitly
++      uses the root level of the tree as the sole path argument.
++
+ Output Format
+ -------------
+-        <mode>\t      <type>\t        <object>\t      <file>
++        <mode> SP <type> SP <object> TAB <file>
+ Author
+ ------
+ Written by Linus Torvalds <torvalds@osdl.org>
++Completely rewritten from scratch by Junio C Hamano <junkio@cox.net>
+ Documentation
+ --------------
+diff a/ls-tree.c b/ls-tree.c
+--- a/ls-tree.c
++++ b/ls-tree.c
+@@ -4,188 +4,217 @@
+  * Copyright (C) Linus Torvalds, 2005
+  */
+ #include "cache.h"
++#include "blob.h"
++#include "tree.h"
+ static int line_termination = '\n';
+-static int recursive = 0;
++#define LS_RECURSIVE 1
++#define LS_TREE_ONLY 2
++static int ls_options = 0;
+-struct path_prefix {
+-      struct path_prefix *prev;
+-      const char *name;
+-};
+-
+-#define DEBUG(fmt, ...)       
+-
+-static int string_path_prefix(char *buff, size_t blen, struct path_prefix *prefix)
+-{
+-      int len = 0;
+-      if (prefix) {
+-              if (prefix->prev) {
+-                      len = string_path_prefix(buff,blen,prefix->prev);
+-                      buff += len;
+-                      blen -= len;
+-                      if (blen > 0) {
+-                              *buff = '/';
+-                              len++;
+-                              buff++;
+-                              blen--;
+-                      }
+-              }
+-              strncpy(buff,prefix->name,blen);
+-              return len + strlen(prefix->name);
+-      }
++static struct tree_entry_list root_entry;
+-      return 0;
++static void prepare_root(unsigned char *sha1)
++{
++      unsigned char rsha[20];
++      unsigned long size;
++      void *buf;
++      struct tree *root_tree;
++
++      buf = read_object_with_reference(sha1, "tree", &size, rsha);
++      free(buf);
++      if (!buf)
++              die("Could not read %s", sha1_to_hex(sha1));
++
++      root_tree = lookup_tree(rsha);
++      if (!root_tree)
++              die("Could not read %s", sha1_to_hex(sha1));
++
++      /* Prepare a fake entry */
++      root_entry.directory = 1;
++      root_entry.executable = root_entry.symlink = 0;
++      root_entry.mode = S_IFDIR;
++      root_entry.name = "";
++      root_entry.item.tree = root_tree;
++      root_entry.parent = NULL;
+ }
+-static void print_path_prefix(struct path_prefix *prefix)
++static int prepare_children(struct tree_entry_list *elem)
+ {
+-      if (prefix) {
+-              if (prefix->prev) {
+-                      print_path_prefix(prefix->prev);
+-                      putchar('/');
+-              }
+-              fputs(prefix->name, stdout);
++      if (!elem->directory)
++              return -1;
++      if (!elem->item.tree->object.parsed) {
++              struct tree_entry_list *e;
++              if (parse_tree(elem->item.tree))
++                      return -1;
++              /* Set up the parent link */
++              for (e = elem->item.tree->entries; e; e = e->next)
++                      e->parent = elem;
+       }
++      return 0;
+ }
+-/*
+- * return:
+- *    -1 if prefix is *not* a subset of path
+- *     0 if prefix == path
+- *     1 if prefix is a subset of path
+- */
+-static int pathcmp(const char *path, struct path_prefix *prefix)
+-{
+-      char buff[PATH_MAX];
+-      int len,slen;
++static struct tree_entry_list *find_entry_0(struct tree_entry_list *elem,
++                                          const char *path,
++                                          const char *path_end)
++{
++      const char *ep;
++      int len;
++
++      while (path < path_end) {
++              if (prepare_children(elem))
++                      return NULL;
+-      if (prefix == NULL)
+-              return 1;
++              /* In elem->tree->entries, find the one that has name
++               * that matches what is between path and ep.
++               */
++              elem = elem->item.tree->entries;
+-      len = string_path_prefix(buff, sizeof buff, prefix);
+-      slen = strlen(path);
++              ep = strchr(path, '/');
++              if (!ep || path_end <= ep)
++                      ep = path_end;
++              len = ep - path;
++
++              while (elem) {
++                      if ((strlen(elem->name) == len) &&
++                          !strncmp(elem->name, path, len))
++                              break;
++                      elem = elem->next;
++              }
++              if (path_end <= ep || !elem)
++                      return elem;
++              while (*ep == '/' && ep < path_end)
++                      ep++;
++              path = ep;
++      }
++      return NULL;
++}
+-      if (slen < len)
+-              return -1;
++static struct tree_entry_list *find_entry(const char *path,
++                                        const char *path_end)
++{
++      /* Find tree element, descending from root, that
++       * corresponds to the named path, lazily expanding
++       * the tree if possible.
++       */
++      if (path == path_end) {
++              /* Special.  This is the root level */
++              return &root_entry;
++      }
++      return find_entry_0(&root_entry, path, path_end);
++}
+-      if (strncmp(path,buff,len) == 0) {
+-              if (slen == len)
+-                      return 0;
+-              else
+-                      return 1;
++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);
++}
+-      return -1;
+-}     
++static const char *entry_type(struct tree_entry_list *e)
++{
++      return (e->directory ? "tree" : "blob");
++}
+-/*
+- * match may be NULL, or a *sorted* list of paths
+- */
+-static void list_recursive(void *buffer,
+-                         const char *type,
+-                         unsigned long size,
+-                         struct path_prefix *prefix,
+-                         char **match, int matches)
+-{
+-      struct path_prefix this_prefix;
+-      this_prefix.prev = prefix;
+-
+-      if (strcmp(type, "tree"))
+-              die("expected a 'tree' node");
+-
+-      if (matches)
+-              recursive = 1;
+-
+-      while (size) {
+-              int namelen = strlen(buffer)+1;
+-              void *eltbuf = NULL;
+-              char elttype[20];
+-              unsigned long eltsize;
+-              unsigned char *sha1 = buffer + namelen;
+-              char *path = strchr(buffer, ' ') + 1;
+-              unsigned int mode;
+-              const char *matched = NULL;
+-              int mtype = -1;
+-              int mindex;
+-
+-              if (size < namelen + 20 || sscanf(buffer, "%o", &mode) != 1)
+-                      die("corrupt 'tree' file");
+-              buffer = sha1 + 20;
+-              size -= namelen + 20;
+-
+-              this_prefix.name = path;
+-              for ( mindex = 0; mindex < matches; mindex++) {
+-                      mtype = pathcmp(match[mindex],&this_prefix);
+-                      if (mtype >= 0) {
+-                              matched = match[mindex];
+-                              break;
+-                      }
+-              }
++static const char *entry_hex(struct tree_entry_list *e)
++{
++      return sha1_to_hex(e->directory
++                         ? e->item.tree->object.sha1
++                         : e->item.blob->object.sha1);
++}
+-              /*
+-               * If we're not matching, or if this is an exact match,
+-               * print out the info
+-               */
+-              if (!matches || (matched != NULL && mtype == 0)) {
+-                      printf("%06o %s %s\t", mode,
+-                             S_ISDIR(mode) ? "tree" : "blob",
+-                             sha1_to_hex(sha1));
+-                      print_path_prefix(&this_prefix);
+-                      putchar(line_termination);
+-              }
++/* forward declaration for mutually recursive routines */
++static int show_entry(struct tree_entry_list *, int);
+-              if (! recursive || ! S_ISDIR(mode))
+-                      continue;
++static int show_children(struct tree_entry_list *e, int level)
++{
++      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);
++              e = e->next;
++      }
++      return 0;
++}
+-              if (matches && ! matched)
+-                      continue;
++static int show_entry(struct tree_entry_list *e, int level)
++{
++      int err = 0; 
+-              if (! (eltbuf = read_sha1_file(sha1, elttype, &eltsize)) ) {
+-                      error("cannot read %s", sha1_to_hex(sha1));
+-                      continue;
+-              }
++      if (e != &root_entry) {
++              printf("%06o %s %s      ", e->mode, entry_type(e),
++                     entry_hex(e));
++              show_entry_name(e);
++              putchar(line_termination);
++      }
+-              /* If this is an exact directory match, we may have
+-               * directory files following this path. Match on them.
+-               * Otherwise, we're at a pach subcomponent, and we need
+-               * to try to match again.
++      if (e->directory) {
++              /* If this is a directory, we have the following cases:
++               * (1) This is the top-level request (explicit path from the
++               *     command line, or "root" if there is no command line).
++               *  a. Without any flag.  We show direct children.  We do not 
++               *     recurse into them.
++               *  b. With -r.  We do recurse into children.
++               *  c. With -d.  We do not recurse into children.
++               * (2) We came here because our caller is either (1-a) or
++               *     (1-b).
++               *  a. Without any flag.  We do not show our children (which
++               *     are grandchildren for the original request).
++               *  b. With -r.  We continue to recurse into our children.
++               *  c. With -d.  We should not have come here to begin with.
+                */
+-              if (mtype == 0)
+-                      mindex++;
+-
+-              list_recursive(eltbuf, elttype, eltsize, &this_prefix, &match[mindex], matches-mindex);
+-              free(eltbuf);
++              if (level == 0 && !(ls_options & LS_TREE_ONLY))
++                      /* case (1)-a and (1)-b */
++                      err = err | show_children(e, level+1);
++              else if (level && ls_options & LS_RECURSIVE)
++                      /* case (2)-b */
++                      err = err | show_children(e, level+1);
+       }
++      return err;
+ }
+-static int qcmp(const void *a, const void *b)
++static int list_one(const char *path, const char *path_end)
+ {
+-      return strcmp(*(char **)a, *(char **)b);
++      int err = 0;
++      struct tree_entry_list *e = find_entry(path, path_end);
++      if (!e) {
++              /* traditionally ls-tree does not complain about
++               * missing path.  We may change this later to match
++               * what "/bin/ls -a" does, which is to complain.
++               */
++              return err;
++      }
++      err = err | show_entry(e, 0);
++      return err;
+ }
+-static int list(unsigned char *sha1,char **path)
++static int list(char **path)
+ {
+-      void *buffer;
+-      unsigned long size;
+-      int npaths;
+-
+-      for (npaths = 0; path[npaths] != NULL; npaths++)
+-              ;
+-
+-      qsort(path,npaths,sizeof(char *),qcmp);
+-
+-      buffer = read_object_with_reference(sha1, "tree", &size, NULL);
+-      if (!buffer)
+-              die("unable to read sha1 file");
+-      list_recursive(buffer, "tree", size, NULL, path, npaths);
+-      free(buffer);
+-      return 0;
++      int i;
++      int err = 0;
++      for (i = 0; path[i]; i++) {
++              int len = strlen(path[i]);
++              while (0 <= len && path[i][len] == '/')
++                      len--;
++              err = err | list_one(path[i], path[i] + len);
++      }
++      return err;
+ }
+-static const char *ls_tree_usage = "git-ls-tree [-r] [-z] <key> [paths...]";
++static const char *ls_tree_usage =
++      "git-ls-tree [-d] [-r] [-z] <tree-ish> [path...]";
+ int main(int argc, char **argv)
+ {
++      static char *path0[] = { "", NULL };
++      char **path;
+       unsigned char sha1[20];
+       while (1 < argc && argv[1][0] == '-') {
+@@ -194,7 +223,10 @@ int main(int argc, char **argv)
+                       line_termination = 0;
+                       break;
+               case 'r':
+-                      recursive = 1;
++                      ls_options |= LS_RECURSIVE;
++                      break;
++              case 'd':
++                      ls_options |= LS_TREE_ONLY;
+                       break;
+               default:
+                       usage(ls_tree_usage);
+@@ -206,7 +238,10 @@ int main(int argc, char **argv)
+               usage(ls_tree_usage);
+       if (get_sha1(argv[1], sha1) < 0)
+               usage(ls_tree_usage);
+-      if (list(sha1, &argv[2]) < 0)
++
++      path = (argc == 2) ? path0 : (argv + 2);
++      prepare_root(sha1);
++      if (list(path) < 0)
+               die("list failed");
+       return 0;
+ }
+diff a/t/t3100-ls-tree-restrict.sh b/t/t3100-ls-tree-restrict.sh
+--- a/t/t3100-ls-tree-restrict.sh
++++ b/t/t3100-ls-tree-restrict.sh
+@@ -74,8 +74,8 @@ test_expect_success \
+     'ls-tree filtered' \
+     'git-ls-tree $tree path1 path0 >current &&
+      cat >expected <<\EOF &&
+-100644 blob X path0
+ 120000 blob X path1
++100644 blob X path0
+ EOF
+      test_output'
+@@ -85,7 +85,6 @@ test_expect_success \
+      cat >expected <<\EOF &&
+ 040000 tree X path2
+ 040000 tree X path2/baz
+-100644 blob X path2/baz/b
+ 120000 blob X path2/bazbo
+ 100644 blob X path2/foo
+ EOF
+diff a/tree.c b/tree.c
+--- a/tree.c
++++ b/tree.c
+@@ -133,7 +133,7 @@ int parse_tree_buffer(struct tree *item,
+               }
+               if (obj)
+                       add_ref(&item->object, obj);
+-
++              entry->parent = NULL; /* needs to be filled by the user */
+               *list_p = entry;
+               list_p = &entry->next;
+       }
+diff a/tree.h b/tree.h
+--- a/tree.h
++++ b/tree.h
+@@ -16,6 +16,7 @@ struct tree_entry_list {
+               struct tree *tree;
+               struct blob *blob;
+       } item;
++      struct tree_entry_list *parent;
+ };
+ struct tree {
diff --git a/t/t4101-apply-nonl.sh b/t/t4101-apply-nonl.sh
new file mode 100755 (executable)
index 0000000..380ef15
--- /dev/null
@@ -0,0 +1,32 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+
+test_description='git-apply should handle files with incomplete lines.
+
+'
+. ./test-lib.sh
+
+# setup
+
+(echo a; echo b) >frotz.0
+(echo a; echo b; echo c) >frotz.1
+(echo a; echo b | tr -d '\012') >frotz.2
+(echo a; echo c; echo b | tr -d '\012') >frotz.3
+
+for i in 0 1 2 3
+do
+  for j in 0 1 2 3
+  do
+    test $i -eq $j && continue
+    diff -u frotz.$i frotz.$j |
+    sed -e '
+       /^---/s|.*|--- a/frotz|
+       /^+++/s|.*|+++ b/frotz|' >diff.$i-$j
+    cat frotz.$i >frotz
+    test_expect_success \
+        "apply diff between $i and $j" \
+       "git-apply <diff.$i-$j && diff frotz.$j frotz"
+  done
+done
diff --git a/t/t5000-tar-tree.sh b/t/t5000-tar-tree.sh
new file mode 100755 (executable)
index 0000000..6d72ed3
--- /dev/null
@@ -0,0 +1,94 @@
+#!/bin/sh
+#
+# Copyright (C) 2005 Rene Scharfe
+#
+
+test_description='git-tar-tree and git-get-tar-commit-id test
+
+This test covers the topics of file contents, commit date handling and
+commit id embedding:
+
+  The contents of the repository is compared to the extracted tar
+  archive.  The repository contains simple text files, symlinks and a
+  binary file (/bin/sh).  Only pathes shorter than 99 characters are
+  used.
+
+  git-tar-tree applies the commit date to every file in the archive it
+  creates.  The test sets the commit date to a specific value and checks
+  if the tar archive contains that value.
+
+  When giving git-tar-tree a commit id (in contrast to a tree id) it
+  embeds this commit id into the tar archive as a comment.  The test
+  checks the ability of git-get-tar-commit-id to figure it out from the
+  tar file.
+
+'
+
+. ./test-lib.sh
+
+test_expect_success \
+    'populate workdir' \
+    'mkdir a b c &&
+     echo simple textfile >a/a &&
+     mkdir a/bin &&
+     cp /bin/sh a/bin &&
+     ln -s a a/l1 &&
+     (cd a && find .) | sort >a.lst'
+
+test_expect_success \
+    'add files to repository' \
+    'find a -type f | xargs git-update-cache --add &&
+     find a -type l | xargs git-update-cache --add &&
+     treeid=`git-write-tree` &&
+     echo $treeid >treeid &&
+     TZ= GIT_COMMITTER_DATE="2005-05-27 22:00:00" \
+     git-commit-tree $treeid </dev/null >.git/HEAD'
+
+test_expect_success \
+    'git-tar-tree' \
+    'git-tar-tree HEAD >b.tar'
+
+test_expect_success \
+    'validate file modification time' \
+    'TZ= 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 &&
+     diff expected.mtime b.mtime'
+
+test_expect_success \
+    'git-get-tar-commit-id' \
+    'git-get-tar-commit-id <b.tar >b.commitid &&
+     diff .git/HEAD b.commitid'
+
+test_expect_success \
+    'extract tar archive' \
+    '(cd b && tar xf -) <b.tar'
+
+test_expect_success \
+    'validate filenames' \
+    '(cd b/a && find .) | sort >b.lst &&
+     diff a.lst b.lst'
+
+test_expect_success \
+    'validate file contents' \
+    'diff -r a b/a'
+
+test_expect_success \
+    'git-tar-tree with prefix' \
+    'git-tar-tree HEAD prefix >c.tar'
+
+test_expect_success \
+    'extract tar archive with prefix' \
+    '(cd c && tar xf -) <c.tar'
+
+test_expect_success \
+    'validate filenames with prefix' \
+    '(cd c/prefix/a && find .) | sort >c.lst &&
+     diff a.lst c.lst'
+
+test_expect_success \
+    'validate file contents with prefix' \
+    'diff -r a c/prefix/a'
+
+test_done
diff --git a/t/t5300-pack-object.sh b/t/t5300-pack-object.sh
new file mode 100755 (executable)
index 0000000..b0b9329
--- /dev/null
@@ -0,0 +1,168 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+
+test_description='git-pack-object
+
+'
+. ./test-lib.sh
+
+TRASH=`pwd`
+
+test_expect_success \
+    'setup' \
+    'rm -f .git/index*
+     for i in a b c
+     do
+            dd if=/dev/zero bs=4k count=1 | tr "\\0" $i >$i &&
+            git-update-cache --add $i || exit
+     done &&
+     cat c >d && echo foo >>d && git-update-cache --add d &&
+     tree=`git-write-tree` &&
+     commit=`git-commit-tree $tree </dev/null` && {
+        echo $tree &&
+        echo $commit &&
+        git-ls-tree $tree | sed -e "s/.* \\([0-9a-f]*\\)       .*/\\1/"
+     } >obj-list && {
+        git-diff-tree --root -p $commit &&
+        while read object
+        do
+           t=`git-cat-file -t $object` &&
+           git-cat-file $t $object || exit 1
+        done <obj-list
+     } >expect'
+
+test_expect_success \
+    'pack without delta' \
+    'packname_1=$(git-pack-objects --window=0 test-1 <obj-list)'
+
+rm -fr .git2
+mkdir .git2
+
+test_expect_success \
+    'unpack without delta' \
+    "GIT_OBJECT_DIRECTORY=.git2/objects &&
+     export GIT_OBJECT_DIRECTORY &&
+     git-init-db &&
+     git-unpack-objects -n <test-1-${packname_1}.pack &&
+     git-unpack-objects <test-1-${packname_1}.pack"
+
+unset GIT_OBJECT_DIRECTORY
+cd $TRASH/.git2
+
+test_expect_success \
+    'check unpack without delta' \
+    '(cd ../.git && find objects -type f -print) |
+     while read path
+     do
+         cmp $path ../.git/$path || {
+            echo $path differs.
+            exit 1
+        }
+     done'
+cd $TRASH
+
+test_expect_success \
+    'pack with delta' \
+    'pwd &&
+     packname_2=$(git-pack-objects test-2 <obj-list)'
+
+rm -fr .git2
+mkdir .git2
+
+test_expect_success \
+    'unpack with delta' \
+    'GIT_OBJECT_DIRECTORY=.git2/objects &&
+     export GIT_OBJECT_DIRECTORY &&
+     git-init-db &&
+     git-unpack-objects -n <test-2-${packname_2}.pack &&
+     git-unpack-objects <test-2-${packname_2}.pack'
+
+unset GIT_OBJECT_DIRECTORY
+cd $TRASH/.git2
+test_expect_success \
+    'check unpack with delta' \
+    '(cd ../.git && find objects -type f -print) |
+     while read path
+     do
+         cmp $path ../.git/$path || {
+            echo $path differs.
+            exit 1
+        }
+     done'
+cd $TRASH
+
+rm -fr .git2
+mkdir .git2
+
+test_expect_success \
+    'use packed objects' \
+    'GIT_OBJECT_DIRECTORY=.git2/objects &&
+     export GIT_OBJECT_DIRECTORY &&
+     git-init-db &&
+     cp test-1-${packname_1}.pack test-1-${packname_1}.idx .git2/objects/pack && {
+        git-diff-tree --root -p $commit &&
+        while read object
+        do
+           t=`git-cat-file -t $object` &&
+           git-cat-file $t $object || exit 1
+        done <obj-list
+    } >current &&
+    diff expect current'
+
+
+test_expect_success \
+    'use packed deltified objects' \
+    'GIT_OBJECT_DIRECTORY=.git2/objects &&
+     export GIT_OBJECT_DIRECTORY &&
+     rm -f .git2/objects/pack/test-?.idx &&
+     cp test-2-${packname_2}.pack test-2-${packname_2}.idx .git2/objects/pack && {
+        git-diff-tree --root -p $commit &&
+        while read object
+        do
+           t=`git-cat-file -t $object` &&
+           git-cat-file $t $object || exit 1
+        done <obj-list
+    } >current &&
+    diff expect current'
+
+unset GIT_OBJECT_DIRECTORY
+
+test_expect_success \
+    'verify pack' \
+    'git-verify-pack test-1-${packname_1}.idx test-2-${packname_2}.idx'
+
+test_expect_success \
+    'corrupt a pack and see if verify catches' \
+    'cp test-1-${packname_1}.idx test-3.idx &&
+     cp test-2-${packname_2}.pack test-3.pack &&
+     if git-verify-pack test-3.idx
+     then false
+     else :;
+     fi &&
+
+     cp test-1-${packname_1}.pack test-3.pack &&
+     dd if=/dev/zero of=test-3.pack count=1 bs=1 conv=notrunc seek=2 &&
+     if git-verify-pack test-3.idx
+     then false
+     else :;
+     fi &&
+
+     cp test-1-${packname_1}.pack test-3.pack &&
+     dd if=/dev/zero of=test-3.pack count=1 bs=1 conv=notrunc seek=7 &&
+     if git-verify-pack test-3.idx
+     then false
+     else :;
+     fi &&
+
+     cp test-1-${packname_1}.pack test-3.pack &&
+     dd if=/dev/zero of=test-3.pack count=1 bs=1 conv=notrunc seek=12 &&
+     if git-verify-pack test-3.idx
+     then false
+     else :;
+     fi &&
+
+     :'
+
+test_done
diff --git a/t/t5400-send-pack.sh b/t/t5400-send-pack.sh
new file mode 100755 (executable)
index 0000000..19a8f12
--- /dev/null
@@ -0,0 +1,54 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+
+test_description='See why rewinding head breaks send-pack
+
+'
+. ./test-lib.sh
+
+cnt='1'
+test_expect_success setup '
+       tree=$(git-write-tree) &&
+       commit=$(echo "Commit #0" | git-commit-tree $tree) &&
+       zero=$commit &&
+       parent=$zero &&
+       for i in $cnt
+       do
+           sleep 1 &&
+           commit=$(echo "Commit #$i" | git-commit-tree $tree -p $parent) &&
+           parent=$commit || exit
+       done &&
+       echo "$commit" >.git/HEAD &&
+       git clone -l ./. victim &&
+       cd victim &&
+       git log &&
+       cd .. &&
+       echo $zero >.git/HEAD &&
+       parent=$zero &&
+       for i in $cnt
+       do
+           sleep 1 &&
+           commit=$(echo "Rebase #$i" | git-commit-tree $tree -p $parent) &&
+           parent=$commit || exit
+       done &&
+       echo "$commit" >.git/HEAD &&
+       echo Rebase &&
+       git log'
+
+test_expect_success \
+        'pushing rewound head should not barf but require --force' ' 
+       # should not fail but refuse to update.
+       git-send-pack ./victim/.git/ master &&
+       if cmp victim/.git/refs/heads/master .git/refs/heads/master
+       then
+               # should have been left as it was!
+               false
+       else
+               true
+       fi &&
+       # this should update
+       git-send-pack --force ./victim/.git/ master &&
+       cmp victim/.git/refs/heads/master .git/refs/heads/master
+'
diff --git a/t/t6000lib.sh b/t/t6000lib.sh
new file mode 100755 (executable)
index 0000000..01f796e
--- /dev/null
@@ -0,0 +1,109 @@
+[ -d .git/refs/tags ] || mkdir -p .git/refs/tags
+
+:> sed.script
+
+# Answer the sha1 has associated with the tag. The tag must exist in .git or .git/refs/tags
+tag()
+{
+       _tag=$1
+       [ -f .git/refs/tags/$_tag ] || error "tag: \"$_tag\" does not exist"
+       cat .git/refs/tags/$_tag
+}
+
+# Generate a commit using the text specified to make it unique and the tree
+# named by the tag specified.
+unique_commit()
+{
+       _text=$1
+        _tree=$2
+       shift 2
+       echo $_text | git-commit-tree $(tag $_tree) "$@"
+}
+
+# Save the output of a command into the tag specified. Prepend
+# a substitution script for the tag onto the front of sed.script
+save_tag()
+{
+       _tag=$1 
+       [ -n "$_tag" ] || error "usage: save_tag tag commit-args ..."
+       shift 1
+       "$@" >.git/refs/tags/$_tag
+
+        echo "s/$(tag $_tag)/$_tag/g" > sed.script.tmp
+       cat sed.script >> sed.script.tmp
+       rm sed.script
+       mv sed.script.tmp sed.script
+}
+
+# Replace unhelpful sha1 hashses with their symbolic equivalents 
+entag()
+{
+       sed -f sed.script
+}
+
+# Execute a command after first saving, then setting the GIT_AUTHOR_EMAIL
+# tag to a specified value. Restore the original value on return.
+as_author()
+{
+       _author=$1
+       shift 1
+        _save=$GIT_AUTHOR_EMAIL
+
+       export GIT_AUTHOR_EMAIL="$_author"
+       "$@"
+        export GIT_AUTHOR_EMAIL="$_save"
+}
+
+commit_date()
+{
+        _commit=$1
+       git-cat-file commit $_commit | sed -n "s/^committer .*> \([0-9]*\) .*/\1/p" 
+}
+
+on_committer_date()
+{
+    _date=$1
+    shift 1
+    GIT_COMMITTER_DATE=$_date "$@"
+}
+
+# Execute a command and suppress any error output.
+hide_error()
+{
+       "$@" 2>/dev/null
+}
+
+check_output()
+{
+       _name=$1
+       shift 1
+       if eval "$*" | entag > $_name.actual
+       then
+               diff $_name.expected $_name.actual
+       else
+               return 1;
+       fi
+}
+
+# Turn a reasonable test description into a reasonable test name.
+# All alphanums translated into -'s which are then compressed and stripped
+# from front and back.
+name_from_description()
+{
+        tr "'" '-' | tr '~`!@#$%^&*()_+={}[]|\;:"<>,/? ' '-' | tr -s '-' | tr '[A-Z]' '[a-z]' | sed "s/^-*//;s/-*\$//"
+}
+
+
+# Execute the test described by the first argument, by eval'ing
+# command line specified in the 2nd argument. Check the status code
+# is zero and that the output matches the stream read from 
+# stdin.
+test_output_expect_success()
+{      
+       _description=$1
+        _test=$2
+        [ $# -eq 2 ] || error "usage: test_output_expect_success description test <<EOF ... EOF"
+        _name=$(echo $_description | name_from_description)
+       cat > $_name.expected
+       test_expect_success "$_description" "check_output $_name \"$_test\"" 
+}
diff --git a/t/t6001-rev-list-merge-order.sh b/t/t6001-rev-list-merge-order.sh
new file mode 100755 (executable)
index 0000000..7fe744e
--- /dev/null
@@ -0,0 +1,462 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Jon Seymour
+#
+
+test_description='Tests git-rev-list --merge-order functionality'
+
+. ./test-lib.sh
+. ../t6000lib.sh # t6xxx specific functions
+
+if git-rev-list --merge-order 2>&1 | grep 'OpenSSL not linked' >/dev/null
+then
+    test_expect_success 'skipping merge-order test' :
+    test_done
+    exit
+fi    
+
+# test-case specific test function
+check_adjacency()
+{
+    read previous
+    echo "= $previous"
+    while read next
+    do
+        if ! (git-cat-file commit $previous | grep "^parent $next" >/dev/null)
+        then
+            echo "^ $next"
+        else
+            echo "| $next"
+        fi
+        previous=$next
+    done
+}
+
+list_duplicates()
+{
+    "$@" | sort | uniq -d
+}
+
+grep_stderr()
+{
+    args=$1
+    shift 1
+    "$@" 2>&1 | grep "$args"
+}
+
+date >path0
+git-update-cache --add path0
+save_tag tree git-write-tree
+on_committer_date "1971-08-16 00:00:00" hide_error save_tag root unique_commit root tree
+on_committer_date "1971-08-16 00:00:01" save_tag l0 unique_commit l0 tree -p root
+on_committer_date "1971-08-16 00:00:02" save_tag l1 unique_commit l1 tree -p l0
+on_committer_date "1971-08-16 00:00:03" save_tag l2 unique_commit l2 tree -p l1
+on_committer_date "1971-08-16 00:00:04" save_tag a0 unique_commit a0 tree -p l2
+on_committer_date "1971-08-16 00:00:05" save_tag a1 unique_commit a1 tree -p a0
+on_committer_date "1971-08-16 00:00:06" save_tag b1 unique_commit b1 tree -p a0
+on_committer_date "1971-08-16 00:00:07" save_tag c1 unique_commit c1 tree -p b1
+on_committer_date "1971-08-16 00:00:08" as_author foobar@example.com save_tag b2 unique_commit b2 tree -p b1
+on_committer_date "1971-08-16 00:00:09" save_tag b3 unique_commit b2 tree -p b2
+on_committer_date "1971-08-16 00:00:10" save_tag c2 unique_commit c2 tree -p c1 -p b2
+on_committer_date "1971-08-16 00:00:11" save_tag c3 unique_commit c3 tree -p c2
+on_committer_date "1971-08-16 00:00:12" save_tag a2 unique_commit a2 tree -p a1
+on_committer_date "1971-08-16 00:00:13" save_tag a3 unique_commit a3 tree -p a2
+on_committer_date "1971-08-16 00:00:14" save_tag b4 unique_commit b4 tree -p b3 -p a3
+on_committer_date "1971-08-16 00:00:15" save_tag a4 unique_commit a4 tree -p a3 -p b4 -p c3
+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
+on_committer_date "1971-08-16 00:00:19" save_tag m1 unique_commit m1 tree -p a4 -p c3
+on_committer_date "1971-08-16 00:00:20" save_tag m2 unique_commit m2 tree -p c3 -p a4
+on_committer_date "1971-08-16 00:00:21" hide_error save_tag alt_root unique_commit alt_root tree
+on_committer_date "1971-08-16 00:00:22" save_tag r0 unique_commit r0 tree -p alt_root
+on_committer_date "1971-08-16 00:00:23" save_tag r1 unique_commit r1 tree -p r0
+on_committer_date "1971-08-16 00:00:24" save_tag l5r1 unique_commit l5r1 tree -p l5 -p r1
+on_committer_date "1971-08-16 00:00:25" save_tag r1l5 unique_commit r1l5 tree -p r1 -p l5
+
+
+#
+# note: as of 20/6, it isn't possible to create duplicate parents, so this
+# can't be tested.
+#
+#on_committer_date "1971-08-16 00:00:20" save_tag m3 unique_commit m3 tree -p c3 -p a4 -p c3
+hide_error save_tag e1 as_author e@example.com unique_commit e1 tree
+save_tag e2 as_author e@example.com unique_commit e2 tree -p e1
+save_tag f1 as_author f@example.com unique_commit f1 tree -p e1
+save_tag e3 as_author e@example.com unique_commit e3 tree -p e2
+save_tag f2 as_author f@example.com unique_commit f2 tree -p f1
+save_tag e4 as_author e@example.com unique_commit e4 tree -p e3 -p f2
+save_tag e5 as_author e@example.com unique_commit e5 tree -p e4
+save_tag f3 as_author f@example.com unique_commit f3 tree -p f2
+save_tag f4 as_author f@example.com unique_commit f4 tree -p f3
+save_tag e6 as_author e@example.com unique_commit e6 tree -p e5 -p f4
+save_tag f5 as_author f@example.com unique_commit f5 tree -p f4
+save_tag f6 as_author f@example.com unique_commit f6 tree -p f5 -p e6
+save_tag e7 as_author e@example.com unique_commit e7 tree -p e6
+save_tag e8 as_author e@example.com unique_commit e8 tree -p e7
+save_tag e9 as_author e@example.com unique_commit e9 tree -p e8
+save_tag f7 as_author f@example.com unique_commit f7 tree -p f6
+save_tag f8 as_author f@example.com unique_commit f8 tree -p f7
+save_tag f9 as_author f@example.com unique_commit f9 tree -p f8
+save_tag e10 as_author e@example.com unique_commit e1 tree -p e9 -p f8
+
+hide_error save_tag g0 unique_commit g0 tree
+save_tag g1 unique_commit g1 tree -p g0
+save_tag h1 unique_commit g2 tree -p g0
+save_tag g2 unique_commit g3 tree -p g1 -p h1
+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
+
+test_expect_success 'rev-list has correct number of entries' 'git-rev-list HEAD | wc -l | tr -s " "' <<EOF
+19
+EOF
+
+normal_adjacency_count=$(git-rev-list HEAD | check_adjacency | grep -c "\^" | tr -d ' ')
+merge_order_adjacency_count=$(git-rev-list --merge-order HEAD | check_adjacency | grep -c "\^" | tr -d ' ')
+test_expect_success '--merge-order produces as many or fewer discontinuities' '[ $merge_order_adjacency_count -le $normal_adjacency_count ]'
+test_output_expect_success 'simple merge order' 'git-rev-list --merge-order --show-breaks HEAD' <<EOF
+= l5
+| l4
+| l3
+= a4
+| c3
+| c2
+| c1
+^ b4
+| b3
+| b2
+| b1
+^ a3
+| a2
+| a1
+= a0
+| l2
+| l1
+| l0
+= root
+EOF
+
+test_output_expect_success 'two diamonds merge order (g6)' 'git-rev-list --merge-order --show-breaks g4' <<EOF
+= g4
+| h2
+^ g3
+= g2
+| h1
+^ g1
+= g0
+EOF
+
+test_output_expect_success 'multiple heads' 'git-rev-list --merge-order a3 b3 c3' <<EOF
+c3
+c2
+c1
+b3
+b2
+b1
+a3
+a2
+a1
+a0
+l2
+l1
+l0
+root
+EOF
+
+test_output_expect_success 'multiple heads, prune at a1' 'git-rev-list --merge-order a3 b3 c3 ^a1' <<EOF
+c3
+c2
+c1
+b3
+b2
+b1
+a3
+a2
+EOF
+
+test_output_expect_success 'multiple heads, prune at l1' 'git-rev-list --merge-order a3 b3 c3 ^l1' <<EOF
+c3
+c2
+c1
+b3
+b2
+b1
+a3
+a2
+a1
+a0
+l2
+EOF
+
+test_output_expect_success 'cross-epoch, head at l5, prune at l1' 'git-rev-list --merge-order l5 ^l1' <<EOF
+l5
+l4
+l3
+a4
+c3
+c2
+c1
+b4
+b3
+b2
+b1
+a3
+a2
+a1
+a0
+l2
+EOF
+
+test_output_expect_success 'duplicated head arguments' 'git-rev-list --merge-order l5 l5 ^l1' <<EOF
+l5
+l4
+l3
+a4
+c3
+c2
+c1
+b4
+b3
+b2
+b1
+a3
+a2
+a1
+a0
+l2
+EOF
+
+test_output_expect_success 'prune near merge' 'git-rev-list --merge-order a4 ^c3' <<EOF
+a4
+b4
+b3
+a3
+a2
+a1
+EOF
+
+test_output_expect_success "head has no parent" 'git-rev-list --merge-order --show-breaks root' <<EOF
+= root
+EOF
+
+test_output_expect_success "two nodes - one head, one base" 'git-rev-list --merge-order --show-breaks l0' <<EOF
+= l0
+= root
+EOF
+
+test_output_expect_success "three nodes one head, one internal, one base" 'git-rev-list --merge-order --show-breaks l1' <<EOF
+= l1
+| l0
+= root
+EOF
+
+test_output_expect_success "linear prune l2 ^root" 'git-rev-list --merge-order --show-breaks l2 ^root' <<EOF
+^ l2
+| l1
+| l0
+EOF
+
+test_output_expect_success "linear prune l2 ^l0" 'git-rev-list --merge-order --show-breaks l2 ^l0' <<EOF
+^ l2
+| l1
+EOF
+
+test_output_expect_success "linear prune l2 ^l1" 'git-rev-list --merge-order --show-breaks l2 ^l1' <<EOF
+^ l2
+EOF
+
+test_output_expect_success "linear prune l5 ^a4" 'git-rev-list --merge-order --show-breaks l5 ^a4' <<EOF
+^ l5
+| l4
+| l3
+EOF
+
+test_output_expect_success "linear prune l5 ^l3" 'git-rev-list --merge-order --show-breaks l5 ^l3' <<EOF
+^ l5
+| l4
+EOF
+
+test_output_expect_success "linear prune l5 ^l4" 'git-rev-list --merge-order --show-breaks l5 ^l4' <<EOF
+^ l5
+EOF
+
+test_output_expect_success "max-count 10 - merge order" 'git-rev-list --merge-order --show-breaks --max-count=10 l5' <<EOF
+= l5
+| l4
+| l3
+= a4
+| c3
+| c2
+| c1
+^ b4
+| b3
+| b2
+EOF
+
+test_output_expect_success "max-count 10 - non merge order" 'git-rev-list --max-count=10 l5' <<EOF
+l5
+l4
+l3
+a4
+b4
+a3
+a2
+c3
+c2
+b3
+EOF
+
+test_output_expect_success '--max-age=c3, no --merge-order' "git-rev-list --max-age=$(commit_date c3) l5" <<EOF
+l5
+l4
+l3
+a4
+b4
+a3
+a2
+c3
+EOF
+
+test_output_expect_success '--max-age=c3, --merge-order' "git-rev-list --merge-order --max-age=$(commit_date c3) l5" <<EOF
+l5
+l4
+l3
+a4
+c3
+b4
+a3
+a2
+EOF
+
+test_output_expect_success 'one specified head reachable from another a4, c3, --merge-order' "list_duplicates git-rev-list --merge-order a4 c3" <<EOF
+EOF
+
+test_output_expect_success 'one specified head reachable from another c3, a4, --merge-order' "list_duplicates git-rev-list --merge-order c3 a4" <<EOF
+EOF
+
+test_output_expect_success 'one specified head reachable from another a4, c3, no --merge-order' "list_duplicates git-rev-list a4 c3" <<EOF
+EOF
+
+test_output_expect_success 'one specified head reachable from another c3, a4, no --merge-order' "list_duplicates git-rev-list c3 a4" <<EOF
+EOF
+
+test_output_expect_success 'graph with c3 and a4 parents of head' "list_duplicates git-rev-list m1" <<EOF
+EOF
+
+test_output_expect_success 'graph with a4 and c3 parents of head' "list_duplicates git-rev-list m2" <<EOF
+EOF
+
+test_expect_success "head ^head --merge-order" 'git-rev-list --merge-order --show-breaks a3 ^a3' <<EOF
+EOF
+
+#
+# can't test this now - duplicate parents can't be created
+#
+#test_output_expect_success 'duplicate parents' 'git-rev-list --parents --merge-order --show-breaks m3' <<EOF
+#= m3 c3 a4 c3
+#| a4 c3 b4 a3
+#| b4 a3 b3
+#| b3 b2
+#^ a3 a2
+#| a2 a1
+#| a1 a0
+#^ c3 c2
+#| c2 b2 c1
+#| b2 b1
+#^ c1 b1
+#| b1 a0
+#= a0 l2
+#| l2 l1
+#| l1 l0
+#| l0 root
+#= root
+#EOF
+
+test_expect_success "head ^head no --merge-order" 'git-rev-list a3 ^a3' <<EOF
+EOF
+
+test_output_expect_success 'simple merge order (l5r1)' 'git-rev-list --merge-order --show-breaks l5r1' <<EOF
+= l5r1
+| r1
+| r0
+| alt_root
+^ l5
+| l4
+| l3
+| a4
+| c3
+| c2
+| c1
+^ b4
+| b3
+| b2
+| b1
+^ a3
+| a2
+| a1
+| a0
+| l2
+| l1
+| l0
+= root
+EOF
+
+test_output_expect_success 'simple merge order (r1l5)' 'git-rev-list --merge-order --show-breaks r1l5' <<EOF
+= r1l5
+| l5
+| l4
+| l3
+| a4
+| c3
+| c2
+| c1
+^ b4
+| b3
+| b2
+| b1
+^ a3
+| a2
+| a1
+| a0
+| l2
+| l1
+| l0
+| root
+^ r1
+| r0
+= alt_root
+EOF
+
+test_output_expect_success "don't print things unreachable from one branch" "git-rev-list a3 ^b3 --merge-order" <<EOF
+a3
+a2
+a1
+EOF
+
+test_output_expect_success "--merge-order a4 l3" "git-rev-list --merge-order a4 l3" <<EOF
+l3
+a4
+c3
+c2
+c1
+b4
+b3
+b2
+b1
+a3
+a2
+a1
+a0
+l2
+l1
+l0
+root
+EOF
+
+#
+#
+
+test_done
diff --git a/t/t6002-rev-list-bisect.sh b/t/t6002-rev-list-bisect.sh
new file mode 100755 (executable)
index 0000000..99d86ae
--- /dev/null
@@ -0,0 +1,241 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Jon Seymour
+#
+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
+#
+test_bisection_diff()
+{
+       _max_diff=$1
+       _bisect_option=$2
+       shift 2
+       _bisection=$(git-rev-list $_bisect_option "$@")
+       _list_size=$(git-rev-list "$@" | wc -l)
+        _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 ]"
+}
+
+date >path0
+git-update-cache --add path0
+save_tag tree git-write-tree
+on_committer_date "1971-08-16 00:00:00" hide_error save_tag root unique_commit root tree
+on_committer_date "1971-08-16 00:00:01" save_tag l0 unique_commit l0 tree -p root
+on_committer_date "1971-08-16 00:00:02" save_tag l1 unique_commit l1 tree -p l0
+on_committer_date "1971-08-16 00:00:03" save_tag l2 unique_commit l2 tree -p l1
+on_committer_date "1971-08-16 00:00:04" save_tag a0 unique_commit a0 tree -p l2
+on_committer_date "1971-08-16 00:00:05" save_tag a1 unique_commit a1 tree -p a0
+on_committer_date "1971-08-16 00:00:06" save_tag b1 unique_commit b1 tree -p a0
+on_committer_date "1971-08-16 00:00:07" save_tag c1 unique_commit c1 tree -p b1
+on_committer_date "1971-08-16 00:00:08" save_tag b2 unique_commit b2 tree -p b1
+on_committer_date "1971-08-16 00:00:09" save_tag b3 unique_commit b2 tree -p b2
+on_committer_date "1971-08-16 00:00:10" save_tag c2 unique_commit c2 tree -p c1 -p b2
+on_committer_date "1971-08-16 00:00:11" save_tag c3 unique_commit c3 tree -p c2
+on_committer_date "1971-08-16 00:00:12" save_tag a2 unique_commit a2 tree -p a1
+on_committer_date "1971-08-16 00:00:13" save_tag a3 unique_commit a3 tree -p a2
+on_committer_date "1971-08-16 00:00:14" save_tag b4 unique_commit b4 tree -p b3 -p a3
+on_committer_date "1971-08-16 00:00:15" save_tag a4 unique_commit a4 tree -p a3 -p b4 -p c3
+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
+
+
+#     E
+#    / \
+#   e1  |
+#   |   |
+#   e2  |
+#   |   |
+#   e3  |
+#   |   |
+#   e4  |
+#   |   |
+#   |   f1
+#   |   |
+#   |   f2
+#   |   |
+#   |   f3
+#   |   |
+#   |   f4
+#   |   |
+#   e5  |
+#   |   |
+#   e6  |
+#   |   |
+#   e7  |
+#   |   |
+#   e8  |
+#    \ /
+#     F
+
+
+on_committer_date "1971-08-16 00:00:00" hide_error save_tag F unique_commit F tree
+on_committer_date "1971-08-16 00:00:01" save_tag e8 unique_commit e8 tree -p F
+on_committer_date "1971-08-16 00:00:02" save_tag e7 unique_commit e7 tree -p e8
+on_committer_date "1971-08-16 00:00:03" save_tag e6 unique_commit e6 tree -p e7
+on_committer_date "1971-08-16 00:00:04" save_tag e5 unique_commit e5 tree -p e6
+on_committer_date "1971-08-16 00:00:05" save_tag f4 unique_commit f4 tree -p F
+on_committer_date "1971-08-16 00:00:06" save_tag f3 unique_commit f3 tree -p f4
+on_committer_date "1971-08-16 00:00:07" save_tag f2 unique_commit f2 tree -p f3
+on_committer_date "1971-08-16 00:00:08" save_tag f1 unique_commit f1 tree -p f2
+on_committer_date "1971-08-16 00:00:09" save_tag e4 unique_commit e4 tree -p e5
+on_committer_date "1971-08-16 00:00:10" save_tag e3 unique_commit e3 tree -p e4
+on_committer_date "1971-08-16 00:00:11" save_tag e2 unique_commit e2 tree -p e3
+on_committer_date "1971-08-16 00:00:12" save_tag e1 unique_commit e1 tree -p e2
+on_committer_date "1971-08-16 00:00:13" save_tag E unique_commit E tree -p e1 -p f1
+
+on_committer_date "1971-08-16 00:00:00" hide_error save_tag U unique_commit U tree
+on_committer_date "1971-08-16 00:00:01" save_tag u0 unique_commit u0 tree -p U
+on_committer_date "1971-08-16 00:00:01" save_tag u1 unique_commit u1 tree -p u0
+on_committer_date "1971-08-16 00:00:02" save_tag u2 unique_commit u2 tree -p u0
+on_committer_date "1971-08-16 00:00:03" save_tag u3 unique_commit u3 tree -p u0
+on_committer_date "1971-08-16 00:00:04" save_tag u4 unique_commit u4 tree -p u0
+on_committer_date "1971-08-16 00:00:05" save_tag u5 unique_commit u5 tree -p u0
+on_committer_date "1971-08-16 00:00:06" save_tag V unique_commit V tree -p u1 -p u2 -p u3 -p u4 -p u5
+
+test_sequence()
+{
+       _bisect_option=$1       
+       
+       test_bisection_diff 0 $_bisect_option l0 ^root
+       test_bisection_diff 0 $_bisect_option l1 ^root
+       test_bisection_diff 0 $_bisect_option l2 ^root
+       test_bisection_diff 0 $_bisect_option a0 ^root
+       test_bisection_diff 0 $_bisect_option a1 ^root
+       test_bisection_diff 0 $_bisect_option a2 ^root
+       test_bisection_diff 0 $_bisect_option a3 ^root
+       test_bisection_diff 0 $_bisect_option b1 ^root
+       test_bisection_diff 0 $_bisect_option b2 ^root
+       test_bisection_diff 0 $_bisect_option b3 ^root
+       test_bisection_diff 0 $_bisect_option c1 ^root
+       test_bisection_diff 0 $_bisect_option c2 ^root
+       test_bisection_diff 0 $_bisect_option c3 ^root
+       test_bisection_diff 0 $_bisect_option E ^F
+       test_bisection_diff 0 $_bisect_option e1 ^F
+       test_bisection_diff 0 $_bisect_option e2 ^F
+       test_bisection_diff 0 $_bisect_option e3 ^F
+       test_bisection_diff 0 $_bisect_option e4 ^F
+       test_bisection_diff 0 $_bisect_option e5 ^F
+       test_bisection_diff 0 $_bisect_option e6 ^F
+       test_bisection_diff 0 $_bisect_option e7 ^F
+       test_bisection_diff 0 $_bisect_option f1 ^F
+       test_bisection_diff 0 $_bisect_option f2 ^F
+       test_bisection_diff 0 $_bisect_option f3 ^F
+       test_bisection_diff 0 $_bisect_option f4 ^F
+       test_bisection_diff 0 $_bisect_option E ^F
+
+       test_bisection_diff 1 $_bisect_option V ^U
+       test_bisection_diff 0 $_bisect_option V ^U ^u1 ^u2 ^u3
+       test_bisection_diff 0 $_bisect_option u1 ^U
+       test_bisection_diff 0 $_bisect_option u2 ^U
+       test_bisection_diff 0 $_bisect_option u3 ^U
+       test_bisection_diff 0 $_bisect_option u4 ^U
+       test_bisection_diff 0 $_bisect_option u5 ^U
+       
+#
+# the following illustrate's Linus' binary bug blatt idea. 
+#
+# assume the bug is actually at l3, but you don't know that - all you know is that l3 is broken
+# and it wasn't broken before
+#
+# keep bisecting the list, advancing the "bad" head and accumulating "good" heads until
+# the bisection point is the head - this is the bad point.
+#
+
+test_output_expect_success "--bisect l5 ^root" 'git-rev-list $_bisect_option l5 ^root' <<EOF
+c3
+EOF
+
+test_output_expect_success "$_bisect_option l5 ^root ^c3" 'git-rev-list $_bisect_option l5 ^root ^c3' <<EOF
+b4
+EOF
+
+test_output_expect_success "$_bisect_option l5 ^root ^c3 ^b4" 'git-rev-list $_bisect_option l5 ^c3 ^b4' <<EOF
+l3
+EOF
+
+test_output_expect_success "$_bisect_option l3 ^root ^c3 ^b4" 'git-rev-list $_bisect_option l3 ^root ^c3 ^b4' <<EOF
+a4
+EOF
+
+test_output_expect_success "$_bisect_option l5 ^b3 ^a3 ^b4 ^a4" 'git-rev-list $_bisect_option l3 ^b3 ^a3 ^a4' <<EOF
+l3
+EOF
+
+#
+# if l3 is bad, then l4 is bad too - so advance the bad pointer by making b4 the known bad head
+#
+
+test_output_expect_success "$_bisect_option l4 ^a2 ^a3 ^b ^a4" 'git-rev-list $_bisect_option l4 ^a2 ^a3 ^a4' <<EOF
+l3
+EOF
+
+test_output_expect_success "$_bisect_option l3 ^a2 ^a3 ^b ^a4" 'git-rev-list $_bisect_option l3 ^a2 ^a3 ^a4' <<EOF
+l3
+EOF
+
+# found!
+
+#
+# as another example, let's consider a4 to be the bad head, in which case
+#
+
+test_output_expect_success "$_bisect_option a4 ^a2 ^a3 ^b4" 'git-rev-list $_bisect_option a4 ^a2 ^a3 ^b4' <<EOF
+c2
+EOF
+
+test_output_expect_success "$_bisect_option a4 ^a2 ^a3 ^b4 ^c2" 'git-rev-list $_bisect_option a4 ^a2 ^a3 ^b4 ^c2' <<EOF
+c3
+EOF
+
+test_output_expect_success "$_bisect_option a4 ^a2 ^a3 ^b4 ^c2 ^c3" 'git-rev-list $_bisect_option a4 ^a2 ^a3 ^b4 ^c2 ^c3' <<EOF
+a4
+EOF
+
+# found!
+
+#
+# or consider c3 to be the bad head
+#
+
+test_output_expect_success "$_bisect_option a4 ^a2 ^a3 ^b4" 'git-rev-list $_bisect_option a4 ^a2 ^a3 ^b4' <<EOF
+c2
+EOF
+
+test_output_expect_success "$_bisect_option c3 ^a2 ^a3 ^b4 ^c2" 'git-rev-list $_bisect_option c3 ^a2 ^a3 ^b4 ^c2' <<EOF
+c3
+EOF
+
+# found!
+
+}
+
+test_sequence "--bisect"
+
+#
+#
+test_done
diff --git a/t/t6003-rev-list-topo-order.sh b/t/t6003-rev-list-topo-order.sh
new file mode 100755 (executable)
index 0000000..eb41f86
--- /dev/null
@@ -0,0 +1,408 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Jon Seymour
+#
+
+test_description='Tests git-rev-list --topo-order functionality'
+
+. ./test-lib.sh
+. ../t6000lib.sh # t6xxx specific functions
+
+list_duplicates()
+{
+    "$@" | sort | uniq -d
+}
+
+date >path0
+git-update-cache --add path0
+save_tag tree git-write-tree
+on_committer_date "1971-08-16 00:00:00" hide_error save_tag root unique_commit root tree
+on_committer_date "1971-08-16 00:00:01" save_tag l0 unique_commit l0 tree -p root
+on_committer_date "1971-08-16 00:00:02" save_tag l1 unique_commit l1 tree -p l0
+on_committer_date "1971-08-16 00:00:03" save_tag l2 unique_commit l2 tree -p l1
+on_committer_date "1971-08-16 00:00:04" save_tag a0 unique_commit a0 tree -p l2
+on_committer_date "1971-08-16 00:00:05" save_tag a1 unique_commit a1 tree -p a0
+on_committer_date "1971-08-16 00:00:06" save_tag b1 unique_commit b1 tree -p a0
+on_committer_date "1971-08-16 00:00:07" save_tag c1 unique_commit c1 tree -p b1
+on_committer_date "1971-08-16 00:00:08" as_author foobar@example.com save_tag b2 unique_commit b2 tree -p b1
+on_committer_date "1971-08-16 00:00:09" save_tag b3 unique_commit b2 tree -p b2
+on_committer_date "1971-08-16 00:00:10" save_tag c2 unique_commit c2 tree -p c1 -p b2
+on_committer_date "1971-08-16 00:00:11" save_tag c3 unique_commit c3 tree -p c2
+on_committer_date "1971-08-16 00:00:12" save_tag a2 unique_commit a2 tree -p a1
+on_committer_date "1971-08-16 00:00:13" save_tag a3 unique_commit a3 tree -p a2
+on_committer_date "1971-08-16 00:00:14" save_tag b4 unique_commit b4 tree -p b3 -p a3
+on_committer_date "1971-08-16 00:00:15" save_tag a4 unique_commit a4 tree -p a3 -p b4 -p c3
+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
+on_committer_date "1971-08-16 00:00:19" save_tag m1 unique_commit m1 tree -p a4 -p c3
+on_committer_date "1971-08-16 00:00:20" save_tag m2 unique_commit m2 tree -p c3 -p a4
+on_committer_date "1971-08-16 00:00:21" hide_error save_tag alt_root unique_commit alt_root tree
+on_committer_date "1971-08-16 00:00:22" save_tag r0 unique_commit r0 tree -p alt_root
+on_committer_date "1971-08-16 00:00:23" save_tag r1 unique_commit r1 tree -p r0
+on_committer_date "1971-08-16 00:00:24" save_tag l5r1 unique_commit l5r1 tree -p l5 -p r1
+on_committer_date "1971-08-16 00:00:25" save_tag r1l5 unique_commit r1l5 tree -p r1 -p l5
+
+
+#
+# note: as of 20/6, it isn't possible to create duplicate parents, so this
+# can't be tested.
+#
+#on_committer_date "1971-08-16 00:00:20" save_tag m3 unique_commit m3 tree -p c3 -p a4 -p c3
+hide_error save_tag e1 as_author e@example.com unique_commit e1 tree
+save_tag e2 as_author e@example.com unique_commit e2 tree -p e1
+save_tag f1 as_author f@example.com unique_commit f1 tree -p e1
+save_tag e3 as_author e@example.com unique_commit e3 tree -p e2
+save_tag f2 as_author f@example.com unique_commit f2 tree -p f1
+save_tag e4 as_author e@example.com unique_commit e4 tree -p e3 -p f2
+save_tag e5 as_author e@example.com unique_commit e5 tree -p e4
+save_tag f3 as_author f@example.com unique_commit f3 tree -p f2
+save_tag f4 as_author f@example.com unique_commit f4 tree -p f3
+save_tag e6 as_author e@example.com unique_commit e6 tree -p e5 -p f4
+save_tag f5 as_author f@example.com unique_commit f5 tree -p f4
+save_tag f6 as_author f@example.com unique_commit f6 tree -p f5 -p e6
+save_tag e7 as_author e@example.com unique_commit e7 tree -p e6
+save_tag e8 as_author e@example.com unique_commit e8 tree -p e7
+save_tag e9 as_author e@example.com unique_commit e9 tree -p e8
+save_tag f7 as_author f@example.com unique_commit f7 tree -p f6
+save_tag f8 as_author f@example.com unique_commit f8 tree -p f7
+save_tag f9 as_author f@example.com unique_commit f9 tree -p f8
+save_tag e10 as_author e@example.com unique_commit e1 tree -p e9 -p f8
+
+hide_error save_tag g0 unique_commit g0 tree
+save_tag g1 unique_commit g1 tree -p g0
+save_tag h1 unique_commit g2 tree -p g0
+save_tag g2 unique_commit g3 tree -p g1 -p h1
+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
+
+test_expect_success 'rev-list has correct number of entries' 'git-rev-list HEAD | wc -l | tr -s " "' <<EOF
+19
+EOF
+
+test_output_expect_success 'simple topo order' 'git-rev-list --topo-order  HEAD' <<EOF
+l5
+l4
+l3
+a4
+c3
+c2
+c1
+b4
+a3
+a2
+a1
+b3
+b2
+b1
+a0
+l2
+l1
+l0
+root
+EOF
+
+test_output_expect_success 'two diamonds topo order (g6)' 'git-rev-list --topo-order  g4' <<EOF
+g4
+h2
+g3
+g2
+h1
+g1
+g0
+EOF
+
+test_output_expect_success 'multiple heads' 'git-rev-list --topo-order a3 b3 c3' <<EOF
+b3
+c3
+c2
+b2
+c1
+b1
+a3
+a2
+a1
+a0
+l2
+l1
+l0
+root
+EOF
+
+test_output_expect_success 'multiple heads, prune at a1' 'git-rev-list --topo-order a3 b3 c3 ^a1' <<EOF
+b3
+c3
+c2
+b2
+c1
+b1
+a3
+a2
+EOF
+
+test_output_expect_success 'multiple heads, prune at l1' 'git-rev-list --topo-order a3 b3 c3 ^l1' <<EOF
+b3
+c3
+c2
+b2
+c1
+b1
+a3
+a2
+a1
+a0
+l2
+EOF
+
+test_output_expect_success 'cross-epoch, head at l5, prune at l1' 'git-rev-list --topo-order l5 ^l1' <<EOF
+l5
+l4
+l3
+a4
+c3
+c2
+c1
+b4
+a3
+a2
+a1
+b3
+b2
+b1
+a0
+l2
+EOF
+
+test_output_expect_success 'duplicated head arguments' 'git-rev-list --topo-order l5 l5 ^l1' <<EOF
+l5
+l4
+l3
+a4
+c3
+c2
+c1
+b4
+a3
+a2
+a1
+b3
+b2
+b1
+a0
+l2
+EOF
+
+test_output_expect_success 'prune near topo' 'git-rev-list --topo-order a4 ^c3' <<EOF
+a4
+b4
+a3
+a2
+a1
+b3
+EOF
+
+test_output_expect_success "head has no parent" 'git-rev-list --topo-order  root' <<EOF
+root
+EOF
+
+test_output_expect_success "two nodes - one head, one base" 'git-rev-list --topo-order  l0' <<EOF
+l0
+root
+EOF
+
+test_output_expect_success "three nodes one head, one internal, one base" 'git-rev-list --topo-order  l1' <<EOF
+l1
+l0
+root
+EOF
+
+test_output_expect_success "linear prune l2 ^root" 'git-rev-list --topo-order  l2 ^root' <<EOF
+l2
+l1
+l0
+EOF
+
+test_output_expect_success "linear prune l2 ^l0" 'git-rev-list --topo-order  l2 ^l0' <<EOF
+l2
+l1
+EOF
+
+test_output_expect_success "linear prune l2 ^l1" 'git-rev-list --topo-order  l2 ^l1' <<EOF
+l2
+EOF
+
+test_output_expect_success "linear prune l5 ^a4" 'git-rev-list --topo-order  l5 ^a4' <<EOF
+l5
+l4
+l3
+EOF
+
+test_output_expect_success "linear prune l5 ^l3" 'git-rev-list --topo-order  l5 ^l3' <<EOF
+l5
+l4
+EOF
+
+test_output_expect_success "linear prune l5 ^l4" 'git-rev-list --topo-order  l5 ^l4' <<EOF
+l5
+EOF
+
+test_output_expect_success "max-count 10 - topo order" 'git-rev-list --topo-order  --max-count=10 l5' <<EOF
+l5
+l4
+l3
+a4
+c3
+c2
+c1
+b4
+a3
+a2
+EOF
+
+test_output_expect_success "max-count 10 - non topo order" 'git-rev-list --max-count=10 l5' <<EOF
+l5
+l4
+l3
+a4
+b4
+a3
+a2
+c3
+c2
+b3
+EOF
+
+test_output_expect_success '--max-age=c3, no --topo-order' "git-rev-list --max-age=$(commit_date c3) l5" <<EOF
+l5
+l4
+l3
+a4
+b4
+a3
+a2
+c3
+EOF
+
+#
+# this test fails on --topo-order - a fix is required
+#
+#test_output_expect_success '--max-age=c3, --topo-order' "git-rev-list --topo-order --max-age=$(commit_date c3) l5" <<EOF
+#l5
+#l4
+#l3
+#a4
+#c3
+#b4
+#a3
+#a2
+#EOF
+
+test_output_expect_success 'one specified head reachable from another a4, c3, --topo-order' "list_duplicates git-rev-list --topo-order a4 c3" <<EOF
+EOF
+
+test_output_expect_success 'one specified head reachable from another c3, a4, --topo-order' "list_duplicates git-rev-list --topo-order c3 a4" <<EOF
+EOF
+
+test_output_expect_success 'one specified head reachable from another a4, c3, no --topo-order' "list_duplicates git-rev-list a4 c3" <<EOF
+EOF
+
+test_output_expect_success 'one specified head reachable from another c3, a4, no --topo-order' "list_duplicates git-rev-list c3 a4" <<EOF
+EOF
+
+test_output_expect_success 'graph with c3 and a4 parents of head' "list_duplicates git-rev-list m1" <<EOF
+EOF
+
+test_output_expect_success 'graph with a4 and c3 parents of head' "list_duplicates git-rev-list m2" <<EOF
+EOF
+
+test_expect_success "head ^head --topo-order" 'git-rev-list --topo-order  a3 ^a3' <<EOF
+EOF
+
+test_expect_success "head ^head no --topo-order" 'git-rev-list a3 ^a3' <<EOF
+EOF
+
+test_output_expect_success 'simple topo order (l5r1)' 'git-rev-list --topo-order  l5r1' <<EOF
+l5r1
+r1
+r0
+alt_root
+l5
+l4
+l3
+a4
+c3
+c2
+c1
+b4
+a3
+a2
+a1
+b3
+b2
+b1
+a0
+l2
+l1
+l0
+root
+EOF
+
+test_output_expect_success 'simple topo order (r1l5)' 'git-rev-list --topo-order  r1l5' <<EOF
+r1l5
+l5
+l4
+l3
+a4
+c3
+c2
+c1
+b4
+a3
+a2
+a1
+b3
+b2
+b1
+a0
+l2
+l1
+l0
+root
+r1
+r0
+alt_root
+EOF
+
+test_output_expect_success "don't print things unreachable from one branch" "git-rev-list a3 ^b3 --topo-order" <<EOF
+a3
+a2
+a1
+EOF
+
+test_output_expect_success "--topo-order a4 l3" "git-rev-list --topo-order a4 l3" <<EOF
+l3
+a4
+c3
+c2
+c1
+b4
+a3
+a2
+a1
+b3
+b2
+b1
+a0
+l2
+l1
+l0
+root
+EOF
+
+#
+#
+
+test_done
diff --git a/t/t6101-rev-parse-parents.sh b/t/t6101-rev-parse-parents.sh
new file mode 100644 (file)
index 0000000..4f57a28
--- /dev/null
@@ -0,0 +1,33 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Johannes Schindelin
+#
+
+test_description='Test git-rev-parse with different parent options'
+
+. ./test-lib.sh
+. ../t6000lib.sh # t6xxx specific functions
+
+date >path0
+git-update-cache --add path0
+save_tag tree git-write-tree
+hide_error save_tag start unique_commit "start" tree
+save_tag second unique_commit "second" tree -p start
+hide_error save_tag start2 unique_commit "start2" tree
+save_tag two_parents unique_commit "next" tree -p second -p start2
+save_tag final unique_commit "final" tree -p two_parents
+
+test_expect_success 'start is valid' 'git-rev-parse start | grep "^[0-9a-f]\{40\}$"'
+test_expect_success 'start^0' "test $(cat .git/refs/tags/start) = $(git-rev-parse start^0)"
+test_expect_success 'start^1 not valid' "test $(git-rev-parse start^1) = start^1"
+test_expect_success 'second^1 = second^' "test $(git-rev-parse second^1) = $(git-rev-parse second^)"
+test_expect_success 'final^1^1^1' "test $(git-rev-parse start) = $(git-rev-parse final^1^1^1)"
+test_expect_success 'final^1^1^1 = final^^^' "test $(git-rev-parse final^1^1^1) = $(git-rev-parse final^^^)"
+test_expect_success 'final^1^2' "test $(git-rev-parse start2) = $(git-rev-parse final^1^2)"
+test_expect_success 'final^1^2 != final^1^1' "test $(git-rev-parse final^1^2) != $(git-rev-parse final^1^1)"
+test_expect_success 'final^1^3 not valid' "test $(git-rev-parse final^1^3) = final^1^3"
+test_expect_failure '--verify start2^1' 'git-rev-parse --verify start2^1'
+test_expect_success '--verify start2^0' 'git-rev-parse --verify start2^0'
+
+test_done
+
diff --git a/t/test-lib.sh b/t/test-lib.sh
new file mode 100755 (executable)
index 0000000..f97f8eb
--- /dev/null
@@ -0,0 +1,152 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+
+# For repeatability, reset the environment to known value.
+LANG=C
+TZ=UTC
+export LANG TZ
+unset AUTHOR_DATE
+unset AUTHOR_EMAIL
+unset AUTHOR_NAME
+unset COMMIT_AUTHOR_EMAIL
+unset COMMIT_AUTHOR_NAME
+unset GIT_ALTERNATE_OBJECT_DIRECTORIES
+unset GIT_AUTHOR_DATE
+unset GIT_AUTHOR_EMAIL
+unset GIT_AUTHOR_NAME
+unset GIT_COMMITTER_EMAIL
+unset GIT_COMMITTER_NAME
+unset GIT_DIFF_OPTS
+unset GIT_DIR
+unset GIT_EXTERNAL_DIFF
+unset GIT_INDEX_FILE
+unset GIT_OBJECT_DIRECTORY
+unset SHA1_FILE_DIRECTORIES
+unset SHA1_FILE_DIRECTORY
+
+# Each test should start with something like this, after copyright notices:
+#
+# test_description='Description of this test...
+# This test checks if command xyzzy does the right thing...
+# '
+# . ./test-lib.sh
+
+error () {
+       echo "* error: $*"
+       exit 1
+}
+
+say () {
+       echo "* $*"
+}
+
+test "${test_description}" != "" ||
+error "Test script did not set test_description."
+
+while test "$#" -ne 0
+do
+       case "$1" in
+       -d|--d|--de|--deb|--debu|--debug)
+               debug=t; shift ;;
+       -i|--i|--im|--imm|--imme|--immed|--immedi|--immedia|--immediat|--immediate)
+               immediate=t; shift ;;
+       -h|--h|--he|--hel|--help)
+               echo "$test_description"
+               exit 0 ;;
+       -v|--v|--ve|--ver|--verb|--verbo|--verbos|--verbose)
+               verbose=t; shift ;;
+       *)
+               break ;;
+       esac
+done
+
+if test "$verbose" = "t"
+then
+       exec 4>&2 3>&1
+else
+       exec 4>/dev/null 3>/dev/null
+fi
+
+test_failure=0
+test_count=0
+
+
+# You are not expected to call test_ok_ and test_failure_ directly, use
+# the text_expect_* functions instead.
+
+test_ok_ () {
+       test_count=$(expr "$test_count" + 1)
+       say "  ok $test_count: $@"
+}
+
+test_failure_ () {
+       test_count=$(expr "$test_count" + 1)
+       test_failure=$(expr "$test_failure" + 1);
+       say "FAIL $test_count: $1"
+       shift
+       echo "$@" | sed -e 's/^/        /'
+       test "$immediate" == "" || exit 1
+}
+
+
+test_debug () {
+       test "$debug" == "" || eval "$1"
+}
+
+test_expect_failure () {
+       test "$#" == 2 ||
+       error "bug in the test script: not 2 parameters to test-expect-failure"
+       say >&3 "expecting failure: $2"
+       if eval >&3 2>&4 "$2"
+       then
+               test_failure_ "$@"
+       else
+               test_ok_ "$1"
+       fi
+}
+
+test_expect_success () {
+       test "$#" == 2 ||
+       error "bug in the test script: not 2 parameters to test-expect-success"
+       say >&3 "expecting success: $2"
+       if eval >&3 2>&4 "$2"
+       then
+               test_ok_ "$1"
+       else
+               test_failure_ "$@"
+       fi
+}
+
+test_done () {
+       case "$test_failure" in
+       0)      
+               # We could:
+               # cd .. && rm -fr trash
+               # but that means we forbid any tests that use their own
+               # subdirectory from calling test_done without coming back
+               # to where they started from.
+               # The Makefile provided will clean this test area so
+               # we will leave things as they are.
+
+               say "passed all $test_count test(s)"
+               exit 0 ;;
+
+       *)
+               say "failed $test_failure among $test_count test(s)"
+               exit 1 ;;
+
+       esac
+}
+
+# Test the binaries we have just built.  The tests are kept in
+# t/ subdirectory and are run in trash subdirectory.
+PATH=$(pwd)/..:$PATH
+
+# Test repository
+test=trash
+rm -fr "$test"
+mkdir "$test"
+cd "$test"
+git-init-db 2>/dev/null || error "cannot run git-init-db"
diff --git a/tag.c b/tag.c
new file mode 100644 (file)
index 0000000..b1ab75f
--- /dev/null
+++ b/tag.c
@@ -0,0 +1,100 @@
+#include "tag.h"
+#include "cache.h"
+
+const char *tag_type = "tag";
+
+struct object *deref_tag(struct object *o)
+{
+       while (o && o->type == tag_type)
+               o = parse_object(((struct tag *)o)->tagged->sha1);
+       return o;
+}
+
+struct tag *lookup_tag(const unsigned char *sha1)
+{
+        struct object *obj = lookup_object(sha1);
+        if (!obj) {
+                struct tag *ret = xmalloc(sizeof(struct tag));
+                memset(ret, 0, sizeof(struct tag));
+                created_object(sha1, &ret->object);
+                ret->object.type = tag_type;
+                return ret;
+        }
+       if (!obj->type)
+               obj->type = tag_type;
+        if (obj->type != tag_type) {
+                error("Object %s is a %s, not a tree", 
+                      sha1_to_hex(sha1), obj->type);
+                return NULL;
+        }
+        return (struct tag *) obj;
+}
+
+int parse_tag_buffer(struct tag *item, void *data, unsigned long size)
+{
+       int typelen, taglen;
+       unsigned char object[20];
+       const char *type_line, *tag_line, *sig_line;
+       char type[20];
+
+        if (item->object.parsed)
+                return 0;
+        item->object.parsed = 1;
+
+       if (size < 64)
+               return -1;
+       if (memcmp("object ", data, 7) || get_sha1_hex(data + 7, object))
+               return -1;
+
+       type_line = data + 48;
+       if (memcmp("\ntype ", type_line-1, 6))
+               return -1;
+
+       tag_line = strchr(type_line, '\n');
+       if (!tag_line || memcmp("tag ", ++tag_line, 4))
+               return -1;
+
+       sig_line = strchr(tag_line, '\n');
+       if (!sig_line)
+               return -1;
+       sig_line++;
+
+       typelen = tag_line - type_line - strlen("type \n");
+       if (typelen >= 20)
+               return -1;
+       memcpy(type, type_line + 5, typelen);
+       type[typelen] = '\0';
+       taglen = sig_line - tag_line - strlen("tag \n");
+       item->tag = xmalloc(taglen + 1);
+       memcpy(item->tag, tag_line + 4, taglen);
+       item->tag[taglen] = '\0';
+
+       item->tagged = lookup_object_type(object, type);
+       if (item->tagged)
+               add_ref(&item->object, item->tagged);
+
+       return 0;
+}
+
+int parse_tag(struct tag *item)
+{
+       char type[20];
+       void *data;
+       unsigned long size;
+       int ret;
+
+       if (item->object.parsed)
+               return 0;
+       data = read_sha1_file(item->object.sha1, type, &size);
+       if (!data)
+               return error("Could not read %s",
+                            sha1_to_hex(item->object.sha1));
+       if (strcmp(type, tag_type)) {
+               free(data);
+               return error("Object %s not a tag",
+                            sha1_to_hex(item->object.sha1));
+       }
+       ret = parse_tag_buffer(item, data, size);
+       free(data);
+       return ret;
+}
diff --git a/tag.h b/tag.h
new file mode 100644 (file)
index 0000000..36e5324
--- /dev/null
+++ b/tag.h
@@ -0,0 +1,20 @@
+#ifndef TAG_H
+#define TAG_H
+
+#include "object.h"
+
+extern const char *tag_type;
+
+struct tag {
+       struct object object;
+       struct object *tagged;
+       char *tag;
+       char *signature; /* not actually implemented */
+};
+
+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 *);
+
+#endif /* TAG_H */
diff --git a/tar-tree.c b/tar-tree.c
new file mode 100644 (file)
index 0000000..2716ae3
--- /dev/null
@@ -0,0 +1,439 @@
+/*
+ * Copyright (c) 2005 Rene Scharfe
+ */
+#include <time.h>
+#include "cache.h"
+
+#define RECORDSIZE     (512)
+#define BLOCKSIZE      (RECORDSIZE * 20)
+
+#define TYPEFLAG_AUTO          '\0'
+#define TYPEFLAG_REG           '0'
+#define TYPEFLAG_LNK           '2'
+#define TYPEFLAG_DIR           '5'
+#define TYPEFLAG_GLOBAL_HEADER 'g'
+#define TYPEFLAG_EXT_HEADER    'x'
+
+#define EXT_HEADER_PATH                1
+#define EXT_HEADER_LINKPATH    2
+
+static const char tar_tree_usage[] = "git-tar-tree <key> [basedir]";
+
+static char block[BLOCKSIZE];
+static unsigned long offset;
+
+static const char *basedir;
+static time_t archive_time;
+
+struct path_prefix {
+       struct path_prefix *prev;
+       const char *name;
+};
+
+/* tries hard to write, either succeeds or dies in the attempt */
+static void reliable_write(void *buf, unsigned long size)
+{
+       while (size > 0) {
+               long ret = write(1, buf, size);
+               if (ret < 0) {
+                       if (errno == EAGAIN)
+                               continue;
+                       if (errno == EPIPE)
+                               exit(0);
+                       die("git-tar-tree: %s", strerror(errno));
+               } else if (!ret) {
+                       die("git-tar-tree: disk full?");
+               }
+               size -= ret;
+               buf += ret;
+       }
+}
+
+/* writes out the whole block, but only if it is full */
+static void write_if_needed(void)
+{
+       if (offset == BLOCKSIZE) {
+               reliable_write(block, BLOCKSIZE);
+               offset = 0;
+       }
+}
+
+/* acquire the next record from the buffer; user must call write_if_needed() */
+static char *get_record(void)
+{
+       char *p = block + offset;
+       memset(p, 0, RECORDSIZE);
+       offset += RECORDSIZE;
+       return p;
+}
+
+/*
+ * The end of tar archives is marked by 1024 nul bytes and after that
+ * follows the rest of the block (if any).
+ */
+static void write_trailer(void)
+{
+       get_record();
+       write_if_needed();
+       get_record();
+       write_if_needed();
+       while (offset) {
+               get_record();
+               write_if_needed();
+       }
+}
+
+/*
+ * queues up writes, so that all our write(2) calls write exactly one
+ * full block; pads writes to RECORDSIZE
+ */
+static void write_blocked(void *buf, unsigned long size)
+{
+       unsigned long tail;
+
+       if (offset) {
+               unsigned long chunk = BLOCKSIZE - offset;
+               if (size < chunk)
+                       chunk = size;
+               memcpy(block + offset, buf, chunk);
+               size -= chunk;
+               offset += chunk;
+               buf += chunk;
+               write_if_needed();
+       }
+       while (size >= BLOCKSIZE) {
+               reliable_write(buf, BLOCKSIZE);
+               size -= BLOCKSIZE;
+               buf += BLOCKSIZE;
+       }
+       if (size) {
+               memcpy(block + offset, buf, size);
+               buf += size;
+               offset += size;
+       }
+       tail = offset % RECORDSIZE;
+       if (tail)  {
+               memset(block + offset, 0, RECORDSIZE - tail);
+               offset += RECORDSIZE - tail;
+       }
+       write_if_needed();
+}
+
+static void append_string(char **p, const char *s)
+{
+       unsigned int len = strlen(s);
+       memcpy(*p, s, len);
+       *p += len;
+}
+
+static void append_char(char **p, char c)
+{
+       **p = c;
+       *p += 1;
+}
+
+static void append_path_prefix(char **buffer, struct path_prefix *prefix)
+{
+       if (!prefix)
+               return;
+       append_path_prefix(buffer, prefix->prev);
+       append_string(buffer, prefix->name);
+       append_char(buffer, '/');
+}
+
+static unsigned int path_prefix_len(struct path_prefix *prefix)
+{
+       if (!prefix)
+               return 0;
+       return path_prefix_len(prefix->prev) + strlen(prefix->name) + 1;
+}
+
+static void append_path(char **p, int is_dir, const char *basepath,
+                        struct path_prefix *prefix, const char *path)
+{
+       if (basepath) {
+               append_string(p, basepath);
+               append_char(p, '/');
+       }
+       append_path_prefix(p, prefix);
+       append_string(p, path);
+       if (is_dir)
+               append_char(p, '/');
+}
+
+static unsigned int path_len(int is_dir, const char *basepath,
+                             struct path_prefix *prefix, const char *path)
+{
+       unsigned int len = 0;
+       if (basepath)
+               len += strlen(basepath) + 1;
+       len += path_prefix_len(prefix) + strlen(path);
+       if (is_dir)
+               len++;
+       return len;
+}
+
+static void append_extended_header_prefix(char **p, unsigned int size,
+                                          const char *keyword)
+{
+       int len = sprintf(*p, "%u %s=", size, keyword);
+       *p += len;
+}
+
+static unsigned int extended_header_len(const char *keyword,
+                                        unsigned int valuelen)
+{
+       /* "%u %s=%s\n" */
+       unsigned int len = 1 + 1 + strlen(keyword) + 1 + valuelen + 1;
+       if (len > 9)
+               len++;
+       if (len > 99)
+               len++;
+       return len;
+}
+
+static void append_extended_header(char **p, const char *keyword,
+                                   const char *value, unsigned int len)
+{
+       unsigned int size = extended_header_len(keyword, len);
+       append_extended_header_prefix(p, size, keyword);
+       memcpy(*p, value, len);
+       *p += len;
+       append_char(p, '\n');
+}
+
+static void write_header(const unsigned char *, char, const char *, struct path_prefix *,
+                         const char *, unsigned int, void *, unsigned long);
+
+/* stores a pax extended header directly in the block buffer */
+static void write_extended_header(const char *headerfilename, int is_dir,
+                                  unsigned int flags, const char *basepath,
+                                  struct path_prefix *prefix,
+                                  const char *path, unsigned int namelen,
+                                  void *content, unsigned int contentsize)
+{
+       char *buffer, *p;
+       unsigned int pathlen, size, linkpathlen = 0;
+
+       size = pathlen = extended_header_len("path", namelen);
+       if (flags & EXT_HEADER_LINKPATH) {
+               linkpathlen = extended_header_len("linkpath", contentsize);
+               size += linkpathlen;
+       }
+       write_header(NULL, TYPEFLAG_EXT_HEADER, NULL, NULL, headerfilename,
+                    0100600, NULL, size);
+
+       buffer = p = malloc(size);
+       if (!buffer)
+               die("git-tar-tree: %s", strerror(errno));
+       append_extended_header_prefix(&p, pathlen, "path");
+       append_path(&p, is_dir, basepath, prefix, path);
+       append_char(&p, '\n');
+       if (flags & EXT_HEADER_LINKPATH)
+               append_extended_header(&p, "linkpath", content, contentsize);
+       write_blocked(buffer, size);
+       free(buffer);
+}
+
+static void write_global_extended_header(const unsigned char *sha1)
+{
+       char *p;
+       unsigned int size;
+
+       size = extended_header_len("comment", 40);
+       write_header(NULL, TYPEFLAG_GLOBAL_HEADER, NULL, NULL,
+                    "pax_global_header", 0100600, NULL, size);
+
+       p = get_record();
+       append_extended_header(&p, "comment", sha1_to_hex(sha1), 40);
+       write_if_needed();
+}
+
+/* stores a ustar header directly in the block buffer */
+static void write_header(const unsigned char *sha1, char typeflag, const char *basepath,
+                         struct path_prefix *prefix, const char *path,
+                         unsigned int mode, void *buffer, unsigned long size)
+{
+       unsigned int namelen; 
+       char *header = NULL;
+       unsigned int checksum = 0;
+       int i;
+       unsigned int ext_header = 0;
+
+       if (typeflag == TYPEFLAG_AUTO) {
+               if (S_ISDIR(mode))
+                       typeflag = TYPEFLAG_DIR;
+               else if (S_ISLNK(mode))
+                       typeflag = TYPEFLAG_LNK;
+               else
+                       typeflag = TYPEFLAG_REG;
+       }
+
+       namelen = path_len(S_ISDIR(mode), basepath, prefix, path);
+       if (namelen > 100)
+               ext_header |= EXT_HEADER_PATH;
+       if (typeflag == TYPEFLAG_LNK && size > 100)
+               ext_header |= EXT_HEADER_LINKPATH;
+
+       /* the extended header must be written before the normal one */
+       if (ext_header) {
+               char headerfilename[51];
+               sprintf(headerfilename, "%s.paxheader", sha1_to_hex(sha1));
+               write_extended_header(headerfilename, S_ISDIR(mode),
+                                     ext_header, basepath, prefix, path,
+                                     namelen, buffer, size);
+       }
+
+       header = get_record();
+
+       if (ext_header) {
+               sprintf(header, "%s.data", sha1_to_hex(sha1));
+       } else {
+               char *p = header;
+               append_path(&p, S_ISDIR(mode), basepath, prefix, path);
+       }
+
+       if (typeflag == TYPEFLAG_LNK) {
+               if (ext_header & EXT_HEADER_LINKPATH) {
+                       sprintf(&header[157], "see %s.paxheader",
+                               sha1_to_hex(sha1));
+               } else {
+                       if (buffer)
+                               strncpy(&header[157], buffer, size);
+               }
+       }
+
+       if (S_ISDIR(mode))
+               mode |= 0755;   /* GIT doesn't store permissions of dirs */
+       if (S_ISLNK(mode))
+               mode |= 0777;   /* ... nor of symlinks */
+       sprintf(&header[100], "%07o", mode & 07777);
+
+       /* XXX: should we provide more meaningful info here? */
+       sprintf(&header[108], "%07o", 0);       /* uid */
+       sprintf(&header[116], "%07o", 0);       /* gid */
+       strncpy(&header[265], "git", 31);       /* uname */
+       strncpy(&header[297], "git", 31);       /* gname */
+
+       if (S_ISDIR(mode) || S_ISLNK(mode))
+               size = 0;
+       sprintf(&header[124], "%011lo", size);
+       sprintf(&header[136], "%011lo", archive_time);
+
+       header[156] = typeflag;
+
+       memcpy(&header[257], "ustar", 6);
+       memcpy(&header[263], "00", 2);
+
+       sprintf(&header[329], "%07o", 0);       /* devmajor */
+       sprintf(&header[337], "%07o", 0);       /* devminor */
+
+       memset(&header[148], ' ', 8);
+       for (i = 0; i < RECORDSIZE; i++)
+               checksum += header[i];
+       sprintf(&header[148], "%07o", checksum & 0x1fffff);
+
+       write_if_needed();
+}
+
+static void traverse_tree(void *buffer, unsigned long size,
+                         struct path_prefix *prefix)
+{
+       struct path_prefix this_prefix;
+       this_prefix.prev = prefix;
+
+       while (size) {
+               int namelen = strlen(buffer)+1;
+               void *eltbuf;
+               char elttype[20];
+               unsigned long eltsize;
+               unsigned char *sha1 = buffer + namelen;
+               char *path = strchr(buffer, ' ') + 1;
+               unsigned int mode;
+
+               if (size < namelen + 20 || sscanf(buffer, "%o", &mode) != 1)
+                       die("corrupt 'tree' file");
+               buffer = sha1 + 20;
+               size -= namelen + 20;
+
+               eltbuf = read_sha1_file(sha1, elttype, &eltsize);
+               if (!eltbuf)
+                       die("cannot read %s", sha1_to_hex(sha1));
+               write_header(sha1, TYPEFLAG_AUTO, basedir, prefix, path,
+                            mode, eltbuf, eltsize);
+               if (!strcmp(elttype, "tree")) {
+                       this_prefix.name = path;
+                       traverse_tree(eltbuf, eltsize, &this_prefix);
+               } else if (!strcmp(elttype, "blob") && !S_ISLNK(mode)) {
+                       write_blocked(eltbuf, eltsize);
+               }
+               free(eltbuf);
+       }
+}
+
+/* get commit time from committer line of commit object */
+static time_t commit_time(void * buffer, unsigned long size)
+{
+       time_t result = 0;
+       char *p = buffer;
+
+       while (size > 0) {
+               char *endp = memchr(p, '\n', size);
+               if (!endp || endp == p)
+                       break;
+               *endp = '\0';
+               if (endp - p > 10 && !memcmp(p, "committer ", 10)) {
+                       char *nump = strrchr(p, '>');
+                       if (!nump)
+                               break;
+                       nump++;
+                       result = strtoul(nump, &endp, 10);
+                       if (*endp != ' ')
+                               result = 0;
+                       break;
+               }
+               size -= endp - p - 1;
+               p = endp + 1;
+       }
+       return result;
+}
+
+int main(int argc, char **argv)
+{
+       unsigned char sha1[20];
+       unsigned char commit_sha1[20];
+       void *buffer;
+       unsigned long size;
+
+       switch (argc) {
+       case 3:
+               basedir = argv[2];
+               /* FALLTHROUGH */
+       case 2:
+               if (get_sha1(argv[1], sha1) < 0)
+                       usage(tar_tree_usage);
+               break;
+       default:
+               usage(tar_tree_usage);
+       }
+
+       buffer = read_object_with_reference(sha1, "commit", &size, commit_sha1);
+       if (buffer) {
+               write_global_extended_header(commit_sha1);
+               archive_time = commit_time(buffer, size);
+               free(buffer);
+       }
+       buffer = read_object_with_reference(sha1, "tree", &size, NULL);
+       if (!buffer)
+               die("not a reference to a tag, commit or tree object: %s",
+                   sha1_to_hex(sha1));
+       if (!archive_time)
+               archive_time = time(NULL);
+       if (basedir)
+               write_header((unsigned char *)"0", TYPEFLAG_DIR, NULL, NULL,
+                       basedir, 040755, NULL, 0);
+       traverse_tree(buffer, size, NULL);
+       free(buffer);
+       write_trailer();
+       return 0;
+}
diff --git a/templates/Makefile b/templates/Makefile
new file mode 100644 (file)
index 0000000..6b2a900
--- /dev/null
@@ -0,0 +1,38 @@
+# make and install sample templates
+
+INSTALL=install
+prefix=$(HOME)
+template_dir=$(prefix)/share/git-core/templates/
+# dest=
+
+all: boilerplates 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:
+       ls *--* 2>/dev/null | \
+       while read boilerplate; \
+       do \
+               case "$$boilerplate" in *~) continue ;; esac && \
+               dst=`echo "$$boilerplate" | sed -e 's|^this|.|;s|--|/|g'` && \
+               dir=`expr "$$dst" : '\(.*\)/'` && \
+               mkdir -p blt/$$dir && \
+               case "$$boilerplate" in \
+               *--) ;; \
+               *) cp $$boilerplate blt/$$dst ;; \
+               esac || exit; \
+       done || exit
+
+# If you need build-tailored templates, build them into blt/
+# directory yourself here.
+custom:
+       : no custom templates yet
+
+clean:
+       rm -rf blt
+
+install: all
+       $(INSTALL) -d -m755 $(dest)$(template_dir)
+       tar Ccf blt - . | tar Cxf $(dest)$(template_dir) -
diff --git a/templates/branches-- b/templates/branches--
new file mode 100644 (file)
index 0000000..fae8870
--- /dev/null
@@ -0,0 +1 @@
+: this is just to ensure the directory exists.
diff --git a/templates/hooks--post-update b/templates/hooks--post-update
new file mode 100644 (file)
index 0000000..bcba893
--- /dev/null
@@ -0,0 +1,8 @@
+#!/bin/sh
+#
+# An example hook script to prepare a packed repository for use over
+# dumb transports.
+#
+# To enable this hook, make this file executable by "chmod +x post-update".
+
+exec git-update-server-info
diff --git a/templates/hooks--update b/templates/hooks--update
new file mode 100644 (file)
index 0000000..540ade0
--- /dev/null
@@ -0,0 +1,21 @@
+#!/bin/sh
+#
+# An example hook script to mail out commit update information.
+#
+# To enable this hook:
+# (1) change the recipient e-mail address
+# (2) make this file executable by "chmod +x update".
+#
+
+recipient="commit-list@mydomain.xz"
+
+if expr "$2" : '0*$' >/dev/null
+then
+       echo "Created a new ref, with the following commits:"
+       git-rev-list --pretty "$2"
+else
+       echo "New commits:"
+       git-rev-list --pretty "$3" "^$2"
+fi |
+mail -s "Changes to ref $1" "$recipient"
+exit 0
diff --git a/templates/info--exclude b/templates/info--exclude
new file mode 100644 (file)
index 0000000..2c87b72
--- /dev/null
@@ -0,0 +1,6 @@
+# git-ls-files --others --exclude-from=.git/info/exclude
+# Lines that start with '#' are comments.
+# For a project mostly in C, the following would be a good set of
+# exclude patterns (uncomment them if you want to use them):
+# *.[oa]
+# *~
diff --git a/templates/this--description b/templates/this--description
new file mode 100644 (file)
index 0000000..c6f25e8
--- /dev/null
@@ -0,0 +1 @@
+Unnamed repository; edit this file to name it for gitweb.
diff --git a/test-date.c b/test-date.c
new file mode 100644 (file)
index 0000000..6fe3e28
--- /dev/null
@@ -0,0 +1,20 @@
+#include <stdio.h>
+#include <time.h>
+
+#include "cache.h"
+
+int main(int argc, char **argv)
+{
+       int i;
+
+       for (i = 1; i < argc; i++) {
+               char result[100];
+               time_t t;
+
+               memcpy(result, "bad", 4);
+               parse_date(argv[i], result, sizeof(result));
+               t = strtoul(result, NULL, 0);
+               printf("%s -> %s -> %s", argv[i], result, ctime(&t));
+       }
+       return 0;
+}
diff --git a/test-delta.c b/test-delta.c
new file mode 100644 (file)
index 0000000..1be8ee0
--- /dev/null
@@ -0,0 +1,83 @@
+/*
+ * test-delta.c: test code to exercise diff-delta.c and patch-delta.c
+ *
+ * (C) 2005 Nicolas Pitre <nico@cam.org>
+ *
+ * This code 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.
+ */
+
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/mman.h>
+#include "delta.h"
+
+static const char usage[] =
+       "test-delta (-d|-p) <from_file> <data_file> <out_file>";
+
+int main(int argc, char *argv[])
+{
+       int fd;
+       struct stat st;
+       void *from_buf, *data_buf, *out_buf;
+       unsigned long from_size, data_size, out_size;
+
+       if (argc != 5 || (strcmp(argv[1], "-d") && strcmp(argv[1], "-p"))) {
+               fprintf(stderr, "Usage: %s\n", usage);
+               return 1;
+       }
+
+       fd = open(argv[2], O_RDONLY);
+       if (fd < 0 || fstat(fd, &st)) {
+               perror(argv[2]);
+               return 1;
+       }
+       from_size = st.st_size;
+       from_buf = mmap(NULL, from_size, PROT_READ, MAP_PRIVATE, fd, 0);
+       if (from_buf == MAP_FAILED) {
+               perror(argv[2]);
+               close(fd);
+               return 1;
+       }
+       close(fd);
+
+       fd = open(argv[3], O_RDONLY);
+       if (fd < 0 || fstat(fd, &st)) {
+               perror(argv[3]);
+               return 1;
+       }
+       data_size = st.st_size;
+       data_buf = mmap(NULL, data_size, PROT_READ, MAP_PRIVATE, fd, 0);
+       if (data_buf == MAP_FAILED) {
+               perror(argv[3]);
+               close(fd);
+               return 1;
+       }
+       close(fd);
+
+       if (argv[1][1] == 'd')
+               out_buf = diff_delta(from_buf, from_size,
+                                    data_buf, data_size,
+                                    &out_size, 0);
+       else
+               out_buf = patch_delta(from_buf, from_size,
+                                     data_buf, data_size,
+                                     &out_size);
+       if (!out_buf) {
+               fprintf(stderr, "delta operation failed (returned NULL)\n");
+               return 1;
+       }
+
+       fd = open (argv[4], O_WRONLY|O_CREAT|O_TRUNC, 0666);
+       if (fd < 0 || write(fd, out_buf, out_size) != out_size) {
+               perror(argv[4]);
+               return 1;
+       }
+
+       return 0;
+}
diff --git a/tools/Makefile b/tools/Makefile
new file mode 100644 (file)
index 0000000..e423af9
--- /dev/null
@@ -0,0 +1,26 @@
+#
+# Make Linus git-tools
+#
+CC=gcc
+COPTS=-O2
+CFLAGS=-g $(COPTS) -Wall
+INSTALL=install
+HOME=$(shell echo $$HOME)
+prefix=$(HOME)
+bindir=$(prefix)/bin
+# dest=
+
+PROGRAMS=git-mailsplit git-mailinfo
+SCRIPTS=git-applymbox git-applypatch
+
+git-%: %.c
+       $(CC) $(CFLAGS) -o $@ $(filter %.c,$^)
+
+all: $(PROGRAMS)
+
+install: $(PROGRAMS) $(SCRIPTS)
+       $(INSTALL) -m755 -d $(dest)$(bindir)
+       $(INSTALL) $(PROGRAMS) $(SCRIPTS) $(dest)$(bindir)
+
+clean:
+       rm -f $(PROGRAMS) *.o
diff --git a/tools/git-applymbox b/tools/git-applymbox
new file mode 100755 (executable)
index 0000000..e44f5d7
--- /dev/null
@@ -0,0 +1,89 @@
+#!/bin/sh
+##
+## "dotest" is my stupid name for my patch-application script, which
+## I never got around to renaming after I tested it. We're now on the
+## second generation of scripts, still called "dotest".
+##
+## Update: Ryan Anderson finally shamed me into naming this "applymbox".
+##
+## You give it a mbox-format collection of emails, and it will try to
+## apply them to the kernel using "applypatch"
+##
+## applymbox [ -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.
+
+query_apply= continue= resume=t
+while case "$#" in 0) break ;; esac
+do
+       case "$1" in
+       -q)     query_apply=t ;;
+       -c)     continue="$2"; resume=f; shift ;;
+       -*)     usage ;;
+       *)      break ;;
+       esac
+       shift
+done
+
+case "$continue" in
+'')
+       rm -rf .dotest
+       mkdir .dotest
+       git-mailsplit "$1" .dotest || exit 1
+       shift
+esac
+
+case "$query_apply" in
+t)     touch .dotest/.query_apply
+esac
+
+signoff="$1"
+set x .dotest/0*
+shift
+while case "$#" in 0) break;; esac
+do
+    i="$1" 
+    case "$resume,$continue" in
+    f,$i)      resume=t;;
+    f,*)       continue;;
+    *)
+           git-mailinfo .dotest/msg .dotest/patch <$i >.dotest/info || exit 1
+           git-stripspace < .dotest/msg > .dotest/msg-clean
+           ;;
+    esac
+    while :; # for fixing up and retry
+    do
+       git-applypatch .dotest/msg-clean .dotest/patch .dotest/info "$signoff"
+       case "$?" in
+       0 | 2 )
+               # 2 is a special exit code from applypatch to indicate that
+               # the patch wasn't applied, but continue anyway 
+               ;;
+       *)
+               ret=$?
+               if test -f .dotest/.query_apply
+               then
+                       echo >&2 "* Patch failed."
+                       echo >&2 "* You could fix it up in your editor and"
+                       echo >&2 "  retry.  If you want to do so, say yes here"
+                       echo >&2 "  AFTER fixing .dotest/patch up."
+                       echo >&2 -n "Retry [y/N]? "
+                       read yesno
+                       case "$yesno" in
+                       [Yy]*)
+                               continue ;;
+                       esac
+               fi
+               exit $ret
+       esac
+       break
+    done
+    shift
+done
+# return to pristine
+rm -fr .dotest
diff --git a/tools/git-applypatch b/tools/git-applypatch
new file mode 100755 (executable)
index 0000000..5a3a44b
--- /dev/null
@@ -0,0 +1,64 @@
+#!/bin/sh
+##
+## applypatch takes four file arguments, and uses those to
+## apply the unpacked patch (surprise surprise) that they
+## represent to the current tree.
+##
+## The arguments are:
+##     $1 - file with commit message
+##     $2 - file with the actual patch
+##     $3 - "info" file with Author, email and subject
+##     $4 - optional file containing signoff to add
+##
+signoff="$4"
+final=.dotest/final-commit
+##
+## If this file exists, we ask before applying
+##
+query_apply=.dotest/.query_apply
+MSGFILE=$1
+PATCHFILE=$2
+INFO=$3
+EDIT=${VISUAL:-$EDITOR}
+EDIT=${EDIT:-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)"
+
+if [ -n "$signoff" -a -f "$signoff" ]; then
+       cat $signoff >> $MSGFILE
+fi
+
+(echo "[PATCH] $SUBJECT" ; if [ -s $MSGFILE ]; then echo ; cat $MSGFILE; fi ) > $final
+
+f=0
+[ -f $query_apply ] || f=1
+
+while [ $f -eq 0 ]; do
+       echo "Commit Body is:"
+       echo "--------------------------"
+       cat $final
+       echo "--------------------------"
+       echo -n "Apply? [y]es/[n]o/[e]dit/[a]ccept all "
+       read reply
+       case $reply in
+               y|Y) f=1;;
+               n|N) exit 2;;   # special value to tell dotest to keep going
+               e|E) $EDIT $final;;
+               a|A) rm -f $query_apply
+                    f=1;;
+       esac
+done
+
+echo
+echo Applying "'$SUBJECT'"
+echo
+
+git-apply --index $PATCHFILE || exit 1
+tree=$(git-write-tree) || exit 1
+echo Wrote tree $tree
+commit=$(git-commit-tree $tree -p $(cat .git/HEAD) < $final) || exit 1
+echo Committed: $commit
+echo $commit > .git/HEAD
diff --git a/tools/mailinfo.c b/tools/mailinfo.c
new file mode 100644 (file)
index 0000000..fb2ea2b
--- /dev/null
@@ -0,0 +1,268 @@
+/*
+ * Another stupid program, this one parsing the headers of an
+ * email to figure out authorship and subject
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+
+static FILE *cmitmsg, *patchfile;
+
+static char line[1000];
+static char date[1000];
+static char name[1000];
+static char email[1000];
+static char subject[1000];
+
+static char *sanity_check(char *name, char *email)
+{
+       int len = strlen(name);
+       if (len < 3 || len > 60)
+               return email;
+       if (strchr(name, '@') || strchr(name, '<') || strchr(name, '>'))
+               return email;
+       return name;
+}
+
+static int handle_from(char *line)
+{
+       char *at = strchr(line, '@');
+       char *dst;
+
+       if (!at)
+               return 0;
+
+       /*
+        * If we already have one email, don't take any confusing lines
+        */
+       if (*email && strchr(at+1, '@'))
+               return 0;
+
+       while (at > line) {
+               char c = at[-1];
+               if (isspace(c) || c == '<')
+                       break;
+               at--;
+       }
+       dst = email;
+       for (;;) {
+               unsigned char c = *at;
+               if (!c || c == '>' || isspace(c))
+                       break;
+               *at++ = ' ';
+               *dst++ = c;
+       }
+       *dst++ = 0;
+
+       at = line + strlen(line);
+       while (at > line) {
+               unsigned char c = *--at;
+               if (isalnum(c))
+                       break;
+               *at = 0;
+       }
+
+       at = line;
+       for (;;) {
+               unsigned char c = *at;
+               if (!c)
+                       break;
+               if (isalnum(c))
+                       break;
+               at++;
+       }
+
+       at = sanity_check(at, email);
+       
+       strcpy(name, at);
+       return 1;
+}
+
+static void handle_date(char *line)
+{
+       strcpy(date, line);
+}
+
+static void handle_subject(char *line)
+{
+       strcpy(subject, line);
+}
+
+static void check_line(char *line, int len)
+{
+       if (!memcmp(line, "From:", 5) && isspace(line[5]))
+               handle_from(line+6);
+       else if (!memcmp(line, "Date:", 5) && isspace(line[5]))
+               handle_date(line+6);
+       else if (!memcmp(line, "Subject:", 8) && isspace(line[8]))
+               handle_subject(line+9);
+}
+
+static char * cleanup_subject(char *subject)
+{
+       for (;;) {
+               char *p;
+               int len, remove;
+               switch (*subject) {
+               case 'r': case 'R':
+                       if (!memcmp("e:", subject+1, 2)) {
+                               subject +=3;
+                               continue;
+                       }
+                       break;
+               case ' ': case '\t': case ':':
+                       subject++;
+                       continue;
+
+               case '[':
+                       p = strchr(subject, ']');
+                       if (!p) {
+                               subject++;
+                               continue;
+                       }
+                       len = strlen(p);
+                       remove = p - subject;
+                       if (remove <= len *2) {
+                               subject = p+1;
+                               continue;
+                       }       
+                       break;
+               }
+               return subject;
+       }
+}                      
+
+static void cleanup_space(char *buf)
+{
+       unsigned char c;
+       while ((c = *buf) != 0) {
+               buf++;
+               if (isspace(c)) {
+                       buf[-1] = ' ';
+                       c = *buf;
+                       while (isspace(c)) {
+                               int len = strlen(buf);
+                               memmove(buf, buf+1, len);
+                               c = *buf;
+                       }
+               }
+       }
+}
+
+static void handle_rest(void)
+{
+       FILE *out = cmitmsg;
+       char *sub = cleanup_subject(subject);
+       cleanup_space(name);
+       cleanup_space(date);
+       cleanup_space(email);
+       cleanup_space(sub);
+       printf("Author: %s\nEmail: %s\nSubject: %s\nDate: %s\n\n", name, email, sub, date);
+
+       do {
+               if (!memcmp("diff -", line, 6) ||
+                   !memcmp("---", line, 3) ||
+                   !memcmp("Index: ", line, 7))
+                       out = patchfile;
+
+               fputs(line, out);
+       } while (fgets(line, sizeof(line), stdin) != NULL);
+
+       if (out == cmitmsg) {
+               fprintf(stderr, "No patch found\n");
+               exit(1);
+       }
+
+       fclose(cmitmsg);
+       fclose(patchfile);
+}
+
+static int eatspace(char *line)
+{
+       int len = strlen(line);
+       while (len > 0 && isspace(line[len-1]))
+               line[--len] = 0;
+       return len;
+}
+
+static void handle_body(void)
+{
+       int has_from = 0;
+       int has_date = 0;
+
+       /* First lines of body can have From: and Date: */
+       while (fgets(line, sizeof(line), stdin) != NULL) {
+               int len = eatspace(line);
+               if (!len)
+                       continue;
+               if (!memcmp("From:", line, 5) && isspace(line[5])) {
+                       if (!has_from && handle_from(line+6)) {
+                               has_from = 1;
+                               continue;
+                       }
+               }
+               if (!memcmp("Date:", line, 5) && isspace(line[5])) {
+                       if (!has_date) {
+                               handle_date(line+6);
+                               has_date = 1;
+                               continue;
+                       }
+               }
+               line[len] = '\n';
+               handle_rest();
+               break;
+       }
+}
+
+static int read_one_header_line(char *line, int sz, FILE *in)
+{
+       int ofs = 0;
+       while (ofs < sz) {
+               int peek, len;
+               if (fgets(line + ofs, sz - ofs, in) == NULL)
+                       return ofs;
+               len = eatspace(line + ofs);
+               if (len == 0)
+                       return ofs;
+               peek = fgetc(in); ungetc(peek, in);
+               if (peek == ' ' || peek == '\t') {
+                       /* Yuck, 2822 header "folding" */
+                       ofs += len;
+                       continue;
+               }
+               return ofs + len;
+       }
+       return ofs;
+}
+
+static void usage(void)
+{
+       fprintf(stderr, "mailinfo msg-file patch-file < email\n");
+       exit(1);
+}
+
+int main(int argc, char ** argv)
+{
+       if (argc != 3)
+               usage();
+       cmitmsg = fopen(argv[1], "w");
+       if (!cmitmsg) {
+               perror(argv[1]);
+               exit(1);
+       }
+       patchfile = fopen(argv[2], "w");
+       if (!patchfile) {
+               perror(argv[2]);
+               exit(1);
+       }
+       while (1) {
+               int len = read_one_header_line(line, sizeof(line), stdin);
+               if (!len) {
+                       handle_body();
+                       break;
+               }
+               check_line(line, len);
+       }
+       return 0;
+}
diff --git a/tools/mailsplit.c b/tools/mailsplit.c
new file mode 100644 (file)
index 0000000..a3238c2
--- /dev/null
@@ -0,0 +1,145 @@
+/*
+ * Totally braindamaged mbox splitter program.
+ *
+ * It just splits a mbox into a list of files: "0001" "0002" ..
+ * so you can process them further from there.
+ */
+#include <unistd.h>
+#include <stdlib.h>
+#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>
+
+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 int is_from_line(const char *line, int len)
+{
+       const char *colon;
+
+       if (len < 20 || memcmp("From ", line, 5))
+               return 0;
+
+       colon = line + len - 2;
+       line += 5;
+       for (;;) {
+               if (colon < line)
+                       return 0;
+               if (*--colon == ':')
+                       break;
+       }
+
+       if (!isdigit(colon[-4]) ||
+           !isdigit(colon[-2]) ||
+           !isdigit(colon[-1]) ||
+           !isdigit(colon[ 1]) ||
+           !isdigit(colon[ 2]))
+               return 0;
+
+       /* year */
+       if (strtol(colon+3, NULL, 10) <= 90)
+               return 0;
+
+       /* Ok, close enough */
+       return 1;
+}
+
+static int parse_email(const void *map, unsigned long size)
+{
+       unsigned long offset;
+
+       if (size < 6 || memcmp("From ", map, 5))
+               goto corrupt;
+
+       /* Make sure we don't trigger on this first line */
+       map++; size--; offset=1;
+
+       /*
+        * Search for a line beginning 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:
+       fprintf(stderr, "corrupt mailbox\n");
+       exit(1);
+}
+
+int main(int argc, 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);
+       }
+       size = st.st_size;
+       map = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0);
+       if (map == MAP_FAILED) {
+               perror("mmap");
+               close(fd);
+               exit(1);
+       }
+       close(fd);
+       nr = 0;
+       do {
+               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");
+                       exit(1);
+               }
+               close(fd);
+               map += len;
+               size -= len;
+       } while (size > 0);
+       return 0;
+}
diff --git a/tree.c b/tree.c
new file mode 100644 (file)
index 0000000..8f490b8
--- /dev/null
+++ b/tree.c
@@ -0,0 +1,214 @@
+#include "tree.h"
+#include "blob.h"
+#include "cache.h"
+#include <stdlib.h>
+
+const char *tree_type = "tree";
+
+static int read_one_entry(unsigned char *sha1, const char *base, int baselen, const char *pathname, unsigned mode, int stage)
+{
+       int len = strlen(pathname);
+       unsigned int size = cache_entry_size(baselen + len);
+       struct cache_entry *ce = xmalloc(size);
+
+       memset(ce, 0, size);
+
+       ce->ce_mode = create_ce_mode(mode);
+       ce->ce_flags = create_ce_flags(baselen + len, stage);
+       memcpy(ce->name, base, baselen);
+       memcpy(ce->name + baselen, pathname, len+1);
+       memcpy(ce->sha1, sha1, 20);
+       return add_cache_entry(ce, ADD_CACHE_OK_TO_ADD|ADD_CACHE_SKIP_DFCHECK);
+}
+
+static int match_tree_entry(const char *base, int baselen, const char *path, unsigned int mode, const char **paths)
+{
+       const char *match;
+       int pathlen;
+
+       if (!paths)
+               return 1;
+       pathlen = strlen(path);
+       while ((match = *paths++) != NULL) {
+               int matchlen = strlen(match);
+
+               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;
+}
+
+static int read_tree_recursive(void *buffer, unsigned long size,
+                              const char *base, int baselen,
+                              int stage, const char **match)
+{
+       while (size) {
+               int len = strlen(buffer)+1;
+               unsigned char *sha1 = buffer + len;
+               char *path = strchr(buffer, ' ')+1;
+               unsigned int mode;
+
+               if (size < len + 20 || sscanf(buffer, "%o", &mode) != 1)
+                       return -1;
+
+               buffer = sha1 + 20;
+               size -= len + 20;
+
+               if (!match_tree_entry(base, baselen, path, mode, match))
+                       continue;
+
+               if (S_ISDIR(mode)) {
+                       int retval;
+                       int pathlen = strlen(path);
+                       char *newbase;
+                       void *eltbuf;
+                       char elttype[20];
+                       unsigned long eltsize;
+
+                       eltbuf = read_sha1_file(sha1, elttype, &eltsize);
+                       if (!eltbuf || strcmp(elttype, "tree")) {
+                               if (eltbuf) free(eltbuf);
+                               return -1;
+                       }
+                       newbase = xmalloc(baselen + 1 + pathlen);
+                       memcpy(newbase, base, baselen);
+                       memcpy(newbase + baselen, path, pathlen);
+                       newbase[baselen + pathlen] = '/';
+                       retval = read_tree_recursive(eltbuf, eltsize,
+                                                    newbase,
+                                                    baselen + pathlen + 1,
+                                                    stage, match);
+                       free(eltbuf);
+                       free(newbase);
+                       if (retval)
+                               return -1;
+                       continue;
+               }
+               if (read_one_entry(sha1, base, baselen, path, mode, stage) < 0)
+                       return -1;
+       }
+       return 0;
+}
+
+int read_tree(void *buffer, unsigned long size, int stage, const char **match)
+{
+       return read_tree_recursive(buffer, size, "", 0, stage, match);
+}
+
+struct tree *lookup_tree(const unsigned char *sha1)
+{
+       struct object *obj = lookup_object(sha1);
+       if (!obj) {
+               struct tree *ret = xmalloc(sizeof(struct tree));
+               memset(ret, 0, sizeof(struct tree));
+               created_object(sha1, &ret->object);
+               ret->object.type = tree_type;
+               return ret;
+       }
+       if (!obj->type)
+               obj->type = tree_type;
+       if (obj->type != tree_type) {
+               error("Object %s is a %s, not a tree", 
+                     sha1_to_hex(sha1), obj->type);
+               return NULL;
+       }
+       return (struct tree *) obj;
+}
+
+int parse_tree_buffer(struct tree *item, void *buffer, unsigned long size)
+{
+       void *bufptr = buffer;
+       struct tree_entry_list **list_p;
+
+       if (item->object.parsed)
+               return 0;
+       item->object.parsed = 1;
+       list_p = &item->entries;
+       while (size) {
+               struct object *obj;
+               struct tree_entry_list *entry;
+               int len = 1+strlen(bufptr);
+               unsigned char *file_sha1 = bufptr + len;
+               char *path = strchr(bufptr, ' ');
+               unsigned int mode;
+               if (size < len + 20 || !path || 
+                   sscanf(bufptr, "%o", &mode) != 1)
+                       return -1;
+
+               entry = xmalloc(sizeof(struct tree_entry_list));
+               entry->name = strdup(path + 1);
+               entry->directory = S_ISDIR(mode) != 0;
+               entry->executable = (mode & S_IXUSR) != 0;
+               entry->symlink = S_ISLNK(mode) != 0;
+               entry->zeropad = *(char *)bufptr == '0';
+               entry->mode = mode;
+               entry->next = NULL;
+
+               bufptr += len + 20;
+               size -= len + 20;
+
+               if (entry->directory) {
+                       entry->item.tree = lookup_tree(file_sha1);
+                       obj = &entry->item.tree->object;
+               } else {
+                       entry->item.blob = lookup_blob(file_sha1);
+                       obj = &entry->item.blob->object;
+               }
+               if (obj)
+                       add_ref(&item->object, obj);
+               entry->parent = NULL; /* needs to be filled by the user */
+               *list_p = entry;
+               list_p = &entry->next;
+       }
+       return 0;
+}
+
+int parse_tree(struct tree *item)
+{
+        char type[20];
+        void *buffer;
+        unsigned long size;
+        int ret;
+
+       if (item->object.parsed)
+               return 0;
+       buffer = read_sha1_file(item->object.sha1, type, &size);
+       if (!buffer)
+               return error("Could not read %s",
+                            sha1_to_hex(item->object.sha1));
+       if (strcmp(type, tree_type)) {
+               free(buffer);
+               return error("Object %s not a tree",
+                            sha1_to_hex(item->object.sha1));
+       }
+       ret = parse_tree_buffer(item, buffer, size);
+       free(buffer);
+       return ret;
+}
diff --git a/tree.h b/tree.h
new file mode 100644 (file)
index 0000000..0df065a
--- /dev/null
+++ b/tree.h
@@ -0,0 +1,35 @@
+#ifndef TREE_H
+#define TREE_H
+
+#include "object.h"
+
+extern const char *tree_type;
+
+struct tree_entry_list {
+       struct tree_entry_list *next;
+       unsigned directory : 1;
+       unsigned executable : 1;
+       unsigned symlink : 1;
+       unsigned zeropad : 1;
+       unsigned int mode;
+       char *name;
+       union {
+               struct object *any;
+               struct tree *tree;
+               struct blob *blob;
+       } item;
+       struct tree_entry_list *parent;
+};
+
+struct tree {
+       struct object object;
+       struct tree_entry_list *entries;
+};
+
+struct tree *lookup_tree(const unsigned char *sha1);
+
+int parse_tree_buffer(struct tree *item, void *buffer, unsigned long size);
+
+int parse_tree(struct tree *tree);
+
+#endif /* TREE_H */
diff --git a/unpack-file.c b/unpack-file.c
new file mode 100644 (file)
index 0000000..d4ac3a5
--- /dev/null
@@ -0,0 +1,34 @@
+#include "cache.h"
+
+static char *create_temp_file(unsigned char *sha1)
+{
+       static char path[50];
+       void *buf;
+       char type[100];
+       unsigned long size;
+       int fd;
+
+       buf = read_sha1_file(sha1, type, &size);
+       if (!buf || strcmp(type, "blob"))
+               die("unable to read blob object %s", sha1_to_hex(sha1));
+
+       strcpy(path, ".merge_file_XXXXXX");
+       fd = mkstemp(path);
+       if (fd < 0)
+               die("unable to create temp-file");
+       if (write(fd, buf, size) != size)
+               die("unable to write temp-file");
+       close(fd);
+       return path;
+}
+
+int main(int argc, char **argv)
+{
+       unsigned char sha1[20];
+
+       if (argc != 2 || get_sha1(argv[1], sha1))
+               usage("git-unpack-file <sha1>");
+
+       puts(create_temp_file(sha1));
+       return 0;
+}
diff --git a/unpack-objects.c b/unpack-objects.c
new file mode 100644 (file)
index 0000000..7e7be5c
--- /dev/null
@@ -0,0 +1,316 @@
+#include "cache.h"
+#include "object.h"
+#include "delta.h"
+#include "pack.h"
+
+#include <sys/time.h>
+
+static int dry_run, quiet;
+static const char unpack_usage[] = "git-unpack-objects < pack-file";
+
+/* We always read in 4kB chunks. */
+static unsigned char buffer[4096];
+static unsigned long offset, len, eof;
+static SHA_CTX ctx;
+
+/*
+ * Make sure at least "min" bytes are available in the buffer, and
+ * return the pointer to the buffer.
+ */
+static void * fill(int min)
+{
+       if (min <= len)
+               return buffer + offset;
+       if (eof)
+               die("unable to fill input");
+       if (min > sizeof(buffer))
+               die("cannot fill %d bytes", min);
+       if (offset) {
+               SHA1_Update(&ctx, buffer, offset);
+               memcpy(buffer, buffer + offset, len);
+               offset = 0;
+       }
+       do {
+               int ret = read(0, buffer + len, sizeof(buffer) - len);
+               if (ret <= 0) {
+                       if (!ret)
+                               die("early EOF");
+                       if (errno == EAGAIN || errno == EINTR)
+                               continue;
+                       die("read error on input: %s", strerror(errno));
+               }
+               len += ret;
+       } while (len < min);
+       return buffer;
+}
+
+static void use(int bytes)
+{
+       if (bytes > len)
+               die("used more bytes than were available");
+       len -= bytes;
+       offset += bytes;
+}
+
+static void *get_data(unsigned long size)
+{
+       z_stream stream;
+       void *buf = xmalloc(size);
+
+       memset(&stream, 0, sizeof(stream));
+
+       stream.next_out = buf;
+       stream.avail_out = size;
+       stream.next_in = fill(1);
+       stream.avail_in = len;
+       inflateInit(&stream);
+
+       for (;;) {
+               int ret = inflate(&stream, 0);
+               use(len - stream.avail_in);
+               if (stream.total_out == size && ret == Z_STREAM_END)
+                       break;
+               if (ret != Z_OK)
+                       die("inflate returned %d\n", ret);
+               stream.next_in = fill(1);
+               stream.avail_in = len;
+       }
+       inflateEnd(&stream);
+       return buf;
+}
+
+struct delta_info {
+       unsigned char base_sha1[20];
+       unsigned long size;
+       void *delta;
+       struct delta_info *next;
+};
+
+static struct delta_info *delta_list;
+
+static void add_delta_to_list(unsigned char *base_sha1, void *delta, unsigned long size)
+{
+       struct delta_info *info = xmalloc(sizeof(*info));
+
+       memcpy(info->base_sha1, base_sha1, 20);
+       info->size = size;
+       info->delta = delta;
+       info->next = delta_list;
+       delta_list = info;
+}
+
+static void added_object(unsigned char *sha1, const char *type, void *data, unsigned long size);
+
+static void write_object(void *buf, unsigned long size, const char *type)
+{
+       unsigned char sha1[20];
+       if (write_sha1_file(buf, size, type, sha1) < 0)
+               die("failed to write object");
+       added_object(sha1, type, buf, size);
+}
+
+static int resolve_delta(const char *type,
+       void *base, unsigned long base_size, 
+       void *delta, unsigned long delta_size)
+{
+       void *result;
+       unsigned long result_size;
+
+       result = patch_delta(base, base_size,
+                            delta, delta_size,
+                            &result_size);
+       if (!result)
+               die("failed to apply delta");
+       free(delta);
+       write_object(result, result_size, type);
+       free(result);
+       return 0;
+}
+
+static void added_object(unsigned char *sha1, const char *type, void *data, unsigned long size)
+{
+       struct delta_info **p = &delta_list;
+       struct delta_info *info;
+
+       while ((info = *p) != NULL) {
+               if (!memcmp(info->base_sha1, sha1, 20)) {
+                       *p = info->next;
+                       p = &delta_list;
+                       resolve_delta(type, data, size, info->delta, info->size);
+                       free(info);
+                       continue;
+               }
+               p = &info->next;
+       }
+}
+
+static int unpack_non_delta_entry(enum object_type kind, unsigned long size)
+{
+       void *buf = get_data(size);
+       const char *type;
+
+       switch (kind) {
+       case OBJ_COMMIT: type = "commit"; break;
+       case OBJ_TREE:   type = "tree"; break;
+       case OBJ_BLOB:   type = "blob"; break;
+       case OBJ_TAG:    type = "tag"; break;
+       default: die("bad type %d", kind);
+       }
+       if (!dry_run)
+               write_object(buf, size, type);
+       free(buf);
+       return 0;
+}
+
+static int unpack_delta_entry(unsigned long delta_size)
+{
+       void *delta_data, *base;
+       unsigned long base_size;
+       char type[20];
+       unsigned char base_sha1[20];
+       int result;
+
+       memcpy(base_sha1, fill(20), 20);
+       use(20);
+
+       delta_data = get_data(delta_size);
+       if (dry_run) {
+               free(delta_data);
+               return 0;
+       }
+
+       if (!has_sha1_file(base_sha1)) {
+               add_delta_to_list(base_sha1, delta_data, delta_size);
+               return 0;
+       }
+       base = read_sha1_file(base_sha1, type, &base_size);
+       if (!base)
+               die("failed to read delta-pack base object %s", sha1_to_hex(base_sha1));
+       result = resolve_delta(type, base, base_size, delta_data, delta_size);
+       free(base);
+       return result;
+}
+
+static void unpack_one(unsigned nr, unsigned total)
+{
+       unsigned shift;
+       unsigned char *pack, c;
+       unsigned long size;
+       enum object_type type;
+
+       pack = fill(1);
+       c = *pack;
+       use(1);
+       type = (c >> 4) & 7;
+       size = (c & 15);
+       shift = 4;
+       while (c & 0x80) {
+               pack = fill(1);
+               c = *pack++;
+               use(1);
+               size += (c & 0x7f) << shift;
+               shift += 7;
+       }
+       if (!quiet) {
+               static unsigned long last_sec;
+               static unsigned last_percent;
+               struct timeval now;
+               unsigned percentage = (nr * 100) / total;
+
+               gettimeofday(&now, NULL);
+               if (percentage != last_percent || now.tv_sec != last_sec) {
+                       last_sec = now.tv_sec;
+                       last_percent = percentage;
+                       fprintf(stderr, "%4u%% (%u/%u) done\r", percentage, nr, total);
+               }
+       }
+       switch (type) {
+       case OBJ_COMMIT:
+       case OBJ_TREE:
+       case OBJ_BLOB:
+       case OBJ_TAG:
+               unpack_non_delta_entry(type, size);
+               return;
+       case OBJ_DELTA:
+               unpack_delta_entry(size);
+               return;
+       default:
+               die("bad object type %d", type);
+       }
+}
+
+/*
+ * We unpack from the end, older files first. Now, usually
+ * there are deltas etc, so we'll not actually write the
+ * objects in that order, but we might as well try..
+ */
+static void unpack_all(void)
+{
+       int i;
+       struct pack_header *hdr = fill(sizeof(struct pack_header));
+       unsigned version = ntohl(hdr->hdr_version);
+       unsigned nr_objects = ntohl(hdr->hdr_entries);
+
+       if (ntohl(hdr->hdr_signature) != PACK_SIGNATURE)
+               die("bad pack file");
+       if (version != PACK_VERSION)
+               die("unable to handle pack file version %d", version);
+       fprintf(stderr, "Unpacking %d objects\n", nr_objects);
+
+       use(sizeof(struct pack_header));
+       for (i = 0; i < nr_objects; i++)
+               unpack_one(i+1, nr_objects);
+       if (delta_list)
+               die("unresolved deltas left after unpacking");
+}
+
+int main(int argc, char **argv)
+{
+       int i;
+       unsigned char sha1[20];
+
+       for (i = 1 ; i < argc; i++) {
+               const char *arg = argv[i];
+
+               if (*arg == '-') {
+                       if (!strcmp(arg, "-n")) {
+                               dry_run = 1;
+                               continue;
+                       }
+                       if (!strcmp(arg, "-q")) {
+                               quiet = 1;
+                               continue;
+                       }
+                       usage(unpack_usage);
+               }
+
+               /* We don't take any non-flag arguments now.. Maybe some day */
+               usage(unpack_usage);
+       }
+       SHA1_Init(&ctx);
+       unpack_all();
+       SHA1_Update(&ctx, buffer, offset);
+       SHA1_Final(sha1, &ctx);
+       if (memcmp(fill(20), sha1, 20))
+               die("final sha1 did not match");
+       use(20);
+
+       /* Write the last part of the buffer to stdout */
+       while (len) {
+               int ret = write(1, buffer + offset, len);
+               if (!ret)
+                       break;
+               if (ret < 0) {
+                       if (errno == EAGAIN || errno == EINTR)
+                               continue;
+                       break;
+               }
+               len -= ret;
+               offset += ret;
+       }
+
+       /* All done */
+       if (!quiet)
+               fprintf(stderr, "\n");
+       return 0;
+}
diff --git a/update-cache.c b/update-cache.c
new file mode 100644 (file)
index 0000000..1fcc59a
--- /dev/null
@@ -0,0 +1,401 @@
+/*
+ * GIT - The information manager from hell
+ *
+ * Copyright (C) Linus Torvalds, 2005
+ */
+#include "cache.h"
+
+/*
+ * Default to not allowing changes to the list of files. The
+ * tool doesn't actually care, but this makes it harder to add
+ * files to the revision control by mistake by doing something
+ * like "git-update-cache *" 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 force_remove;
+
+/* Three functions to allow overloaded pointer return; see linux/err.h */
+static inline void *ERR_PTR(long error)
+{
+       return (void *) error;
+}
+
+static inline long PTR_ERR(const void *ptr)
+{
+       return (long) ptr;
+}
+
+static inline long IS_ERR(const void *ptr)
+{
+       return (unsigned long)ptr > (unsigned long)-1000L;
+}
+
+static int add_file_to_cache(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)) {
+               /* When we used to have "path" and now we want to add
+                * "path/file", we need a way to remove "path" before
+                * being able to add "path/file".  However,
+                * "git-update-cache --remove path" would not work.
+                * --force-remove can be used but this is more user
+                * friendly, especially since we can do the opposite
+                * case just fine without --force-remove.
+                */
+               if (status == 0 || (errno == ENOENT || errno == ENOTDIR)) {
+                       if (allow_remove)
+                               return remove_file_from_cache(path);
+               }
+               return error("open(\"%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 -1;
+               if (index_fd(ce->sha1, fd, &st, !info_only, NULL) < 0)
+                       return -1;
+               break;
+       case S_IFLNK:
+               target = xmalloc(st.st_size+1);
+               if (readlink(path, target, st.st_size+1) != st.st_size) {
+                       free(target);
+                       return -1;
+               }
+               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 -1;
+               free(target);
+               break;
+       default:
+               return -1;
+       }
+       option = allow_add ? ADD_CACHE_OK_TO_ADD : 0;
+       option |= allow_replace ? ADD_CACHE_OK_TO_REPLACE : 0;
+       return add_cache_entry(ce, option);
+}
+
+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
+ * _does_ do is to "re-match" the stat information of a file
+ * with the cache, so that you can refresh the cache for a
+ * file that hasn't been changed but where the stat entry is
+ * out of date.
+ *
+ * For example, you'd want to do this after doing a "git-read-tree",
+ * to link up the stat cache details with the proper files.
+ */
+static struct cache_entry *refresh_entry(struct cache_entry *ce)
+{
+       struct stat st;
+       struct cache_entry *updated;
+       int changed, size;
+
+       if (lstat(ce->name, &st) < 0)
+               return ERR_PTR(-errno);
+
+       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);
+
+       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:
+               return ERR_PTR(-EINVAL);
+       }
+
+       size = ce_size(ce);
+       updated = xmalloc(size);
+       memcpy(updated, ce, size);
+       fill_stat_cache_info(updated, &st);
+       return updated;
+}
+
+static int refresh_cache(void)
+{
+       int i;
+       int has_errors = 0;
+
+       for (i = 0; i < active_nr; i++) {
+               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--;
+                       continue;
+               }
+
+               new = refresh_entry(ce);
+               if (IS_ERR(new)) {
+                       if (not_new && PTR_ERR(new) == -ENOENT)
+                               continue;
+                       if (quiet)
+                               continue;
+                       printf("%s: needs update\n", ce->name);
+                       has_errors = 1;
+                       continue;
+               }
+               active_cache_changed = 1;
+               /* You can NOT just free active_cache[i] here, since it
+                * might not be necessarily malloc()ed but can also come
+                * from mmap(). */
+               active_cache[i] = new;
+       }
+       return has_errors;
+}
+
+/*
+ * We fundamentally don't like some paths: we don't want
+ * dot or dot-dot anywhere, and for obvious reasons don't
+ * want to recurse into ".git" either.
+ *
+ * Also, we don't want double slashes or slashes at the
+ * end that can make pathnames ambiguous.
+ */
+static int verify_dotfile(const char *rest)
+{
+       /*
+        * The first character was '.', but that
+        * has already been discarded, we now test
+        * the rest.
+        */
+       switch (*rest) {
+       /* "." is not allowed */
+       case '\0': case '/':
+               return 0;
+
+       /*
+        * ".git" followed by  NUL or slash is bad. This
+        * shares the path end test with the ".." case.
+        */
+       case 'g':
+               if (rest[1] != 'i')
+                       break;
+               if (rest[2] != 't')
+                       break;
+               rest += 2;
+       /* fallthrough */
+       case '.':
+               if (rest[1] == '\0' || rest[1] == '/')
+                       return 0;
+       }
+       return 1;
+}
+
+static int verify_path(char *path)
+{
+       char c;
+
+       goto inside;
+       for (;;) {
+               if (!c)
+                       return 1;
+               if (c == '/') {
+inside:
+                       c = *path++;
+                       switch (c) {
+                       default:
+                               continue;
+                       case '/': case '\0':
+                               break;
+                       case '.':
+                               if (verify_dotfile(path))
+                                       continue;
+                       }
+                       return 0;
+               }
+               c = *path++;
+       }
+}
+
+static int add_cacheinfo(char *arg1, char *arg2, char *arg3)
+{
+       int size, len, option;
+       unsigned int mode;
+       unsigned char sha1[20];
+       struct cache_entry *ce;
+
+       if (sscanf(arg1, "%o", &mode) != 1)
+               return -1;
+       if (get_sha1_hex(arg2, sha1))
+               return -1;
+       if (!verify_path(arg3))
+               return -1;
+
+       len = strlen(arg3);
+       size = cache_entry_size(len);
+       ce = xmalloc(size);
+       memset(ce, 0, size);
+
+       memcpy(ce->sha1, sha1, 20);
+       memcpy(ce->name, arg3, len);
+       ce->ce_flags = htons(len);
+       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);
+}
+
+static struct cache_file cache_file;
+
+int main(int argc, char **argv)
+{
+       int i, newfd, entries, has_errors = 0;
+       int allow_options = 1;
+
+       newfd = hold_index_file_for_update(&cache_file, get_index_file());
+       if (newfd < 0)
+               die("unable to create new cachefile");
+
+       entries = read_cache();
+       if (entries < 0)
+               die("cache corrupted");
+
+       for (i = 1 ; i < argc; i++) {
+               char *path = argv[i];
+
+               if (allow_options && *path == '-') {
+                       if (!strcmp(path, "--")) {
+                               allow_options = 0;
+                               continue;
+                       }
+                       if (!strcmp(path, "-q")) {
+                               quiet = 1;
+                               continue;
+                       }
+                       if (!strcmp(path, "--add")) {
+                               allow_add = 1;
+                               continue;
+                       }
+                       if (!strcmp(path, "--replace")) {
+                               allow_replace = 1;
+                               continue;
+                       }
+                       if (!strcmp(path, "--remove")) {
+                               allow_remove = 1;
+                               continue;
+                       }
+                       if (!strcmp(path, "--refresh")) {
+                               has_errors |= refresh_cache();
+                               continue;
+                       }
+                       if (!strcmp(path, "--cacheinfo")) {
+                               if (i+3 >= argc)
+                                       die("git-update-cache: --cacheinfo <mode> <sha1> <path>");
+                               if (add_cacheinfo(argv[i+1], argv[i+2], argv[i+3]))
+                                       die("git-update-cache: --cacheinfo cannot add %s", argv[i+3]);
+                               i += 3;
+                               continue;
+                       }
+                       if (!strcmp(path, "--info-only")) {
+                               info_only = 1;
+                               continue;
+                       }
+                       if (!strcmp(path, "--force-remove")) {
+                               force_remove = 1;
+                               continue;
+                       }
+
+                       if (!strcmp(path, "--ignore-missing")) {
+                               not_new = 1;
+                               continue;
+                       }
+                       die("unknown option %s", 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-cache: --force-remove cannot remove %s", path);
+                       continue;
+               }
+               if (add_file_to_cache(path))
+                       die("Unable to add %s to database", path);
+       }
+       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-server-info.c b/update-server-info.c
new file mode 100644 (file)
index 0000000..e824f62
--- /dev/null
@@ -0,0 +1,23 @@
+#include "cache.h"
+
+static const char update_server_info_usage[] =
+"git-update-server-info [--force]";
+
+int main(int ac, char **av)
+{
+       int i;
+       int force = 0;
+       for (i = 1; i < ac; i++) {
+               if (av[i][0] == '-') {
+                       if (!strcmp("--force", av[i]) ||
+                           !strcmp("-f", av[i]))
+                               force = 1;
+                       else
+                               usage(update_server_info_usage);
+               }
+       }
+       if (i != ac)
+               usage(update_server_info_usage);
+
+       return !!update_server_info(force);
+}
diff --git a/upload-pack.c b/upload-pack.c
new file mode 100644 (file)
index 0000000..95d3812
--- /dev/null
@@ -0,0 +1,185 @@
+#include "cache.h"
+#include "refs.h"
+#include "pkt-line.h"
+
+static const char upload_pack_usage[] = "git-upload-pack <dir>";
+
+#define MAX_HAS (16)
+#define MAX_NEEDS (256)
+static int nr_has = 0, nr_needs = 0;
+static unsigned char has_sha1[MAX_HAS][20];
+static unsigned char needs_sha1[MAX_NEEDS][20];
+
+static int strip(char *line, int len)
+{
+       if (len && line[len-1] == '\n')
+               line[--len] = 0;
+       return len;
+}
+
+static void create_pack_file(void)
+{
+       int fd[2];
+       pid_t pid;
+
+       if (pipe(fd) < 0)
+               die("git-upload-pack: unable to create pipe");
+       pid = fork();
+       if (pid < 0)
+               die("git-upload-pack: unable to fork git-rev-list");
+
+       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;
+
+               dup2(fd[1], 1);
+               close(0);
+               close(fd[0]);
+               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;
+               }
+               *p++ = NULL;
+               execvp("git-rev-list", argv);
+               die("git-upload-pack: unable to exec git-rev-list");
+       }
+       dup2(fd[0], 0);
+       close(fd[0]);
+       close(fd[1]);
+       execlp("git-pack-objects", "git-pack-objects", "--stdout", NULL);
+       die("git-upload-pack: unable to exec git-pack-objects");
+}
+
+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;
+       }
+       return 1;
+}
+
+static int get_common_commits(void)
+{
+       static char line[1000];
+       unsigned char sha1[20];
+       int len;
+
+       for(;;) {
+               len = packet_read_line(0, line, sizeof(line));
+
+               if (!len) {
+                       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;
+                       }
+                       continue;
+               }
+               if (!strcmp(line, "done")) {
+                       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)
+{
+       static char line[1000];
+       int len, needs;
+
+       needs = 0;
+       for (;;) {
+               len = packet_read_line(0, line, sizeof(line));
+               if (!len)
+                       return needs;
+
+               /*
+                * This is purely theoretical right now: git-fetch-pack only
+                * ever asks for a single HEAD
+                */
+               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++;
+       }
+}
+
+static int send_ref(const char *refname, const unsigned char *sha1)
+{
+       packet_write(1, "%s %s\n", sha1_to_hex(sha1), refname);
+       return 0;
+}
+
+static int upload_pack(void)
+{
+       head_ref(send_ref);
+       for_each_ref(send_ref);
+       packet_flush(1);
+       nr_needs = receive_needs();
+       if (!nr_needs)
+               return 0;
+       get_common_commits();
+       create_pack_file();
+       return 0;
+}
+
+int main(int argc, char **argv)
+{
+       const char *dir;
+       if (argc != 2)
+               usage(upload_pack_usage);
+       dir = argv[1];
+
+       /* chdir to the directory. If that fails, try appending ".git" */
+       if (chdir(dir) < 0) {
+               if (chdir(mkpath("%s.git", dir)) < 0)
+                       die("git-upload-pack unable to chdir to %s", dir);
+       }
+       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);
+       setenv("GIT_DIR", ".", 1);
+       upload_pack();
+       return 0;
+}
diff --git a/usage.c b/usage.c
new file mode 100644 (file)
index 0000000..86211c9
--- /dev/null
+++ b/usage.c
@@ -0,0 +1,39 @@
+/*
+ * GIT - The information manager from hell
+ *
+ * Copyright (C) Linus Torvalds, 2005
+ */
+#include "cache.h"
+
+static void report(const char *prefix, const char *err, va_list params)
+{
+       fputs(prefix, stderr);
+       vfprintf(stderr, err, params);
+       fputs("\n", stderr);
+}
+
+void usage(const char *err)
+{
+       fprintf(stderr, "usage: %s\n", err);
+       exit(1);
+}
+
+void die(const char *err, ...)
+{
+       va_list params;
+
+       va_start(params, err);
+       report("fatal: ", err, params);
+       va_end(params);
+       exit(1);
+}
+
+int error(const char *err, ...)
+{
+       va_list params;
+
+       va_start(params, err);
+       report("error: ", err, params);
+       va_end(params);
+       return -1;
+}
diff --git a/var.c b/var.c
new file mode 100644 (file)
index 0000000..3f13126
--- /dev/null
+++ b/var.c
@@ -0,0 +1,65 @@
+/*
+ * GIT - The information manager from hell
+ *
+ * Copyright (C) Eric Biederman, 2005
+ */
+#include "cache.h"
+#include <stdio.h>
+#include <errno.h>
+#include <string.h>
+
+static const char var_usage[] = "git-var [-l | <variable>]";
+
+struct git_var {
+       const char *name;
+       char *(*read)(void);
+};
+static struct git_var git_vars[] = {
+       { "GIT_COMMITTER_IDENT", git_committer_info },
+       { "GIT_AUTHOR_IDENT",   git_author_info },
+       { "", NULL },
+};
+
+static void list_vars(void)
+{
+       struct git_var *ptr;
+       for(ptr = git_vars; ptr->read; ptr++) {
+               printf("%s=%s\n", ptr->name, ptr->read());
+       }
+}
+
+static const char *read_var(const char *var)
+{
+       struct git_var *ptr;
+       const char *val;
+       val = NULL;
+       for(ptr = git_vars; ptr->read; ptr++) {
+               if (strcmp(var, ptr->name) == 0) {
+                       val = ptr->read();
+                       break;
+               }
+       }
+       return val;
+}
+
+int main(int argc, char **argv)
+{
+       const char *val;
+       if (argc != 2) {
+               usage(var_usage);
+       }
+       setup_ident();
+       val = NULL;
+
+       if (strcmp(argv[1], "-l") == 0) {
+               list_vars();
+               return 0;
+       }
+       val = read_var(argv[1]);
+       if (!val)
+               usage(var_usage);
+       
+       printf("%s\n", val);
+       
+       return 0;
+}
diff --git a/verify-pack.c b/verify-pack.c
new file mode 100644 (file)
index 0000000..80b60a6
--- /dev/null
@@ -0,0 +1,57 @@
+#include "cache.h"
+#include "pack.h"
+
+static int verify_one_pack(char *arg, int verbose)
+{
+       int len = strlen(arg);
+       struct packed_git *g;
+       
+       while (1) {
+               /* Should name foo.idx, but foo.pack may be named;
+                * convert it to foo.idx
+                */
+               if (!strcmp(arg + len - 5, ".pack")) {
+                       strcpy(arg + len - 5, ".idx");
+                       len--;
+               }
+               /* Should name foo.idx now */
+               if ((g = add_packed_git(arg, len)))
+                       break;
+               /* No?  did you name just foo? */
+               strcpy(arg + len, ".idx");
+               len += 4;
+               if ((g = add_packed_git(arg, len)))
+                       break;
+               return error("packfile %s not found.", arg);
+       }
+       return verify_pack(g, verbose);
+}
+
+static const char verify_pack_usage[] = "git-verify-pack [-v] <pack>...";
+
+int main(int ac, char **av)
+{
+       int errs = 0;
+       int verbose = 0;
+       int no_more_options = 0;
+
+       while (1 < ac) {
+               char path[PATH_MAX];
+
+               if (!no_more_options && av[1][0] == '-') {
+                       if (!strcmp("-v", av[1]))
+                               verbose = 1;
+                       else if (!strcmp("--", av[1]))
+                               no_more_options = 1;
+                       else
+                               usage(verify_pack_usage);
+               }
+               else {
+                       strcpy(path, av[1]);
+                       if (verify_one_pack(path, verbose))
+                               errs++;
+               }
+               ac--; av++;
+       }
+       return !!errs;
+}
diff --git a/write-tree.c b/write-tree.c
new file mode 100644 (file)
index 0000000..2b2c6b7
--- /dev/null
@@ -0,0 +1,152 @@
+/*
+ * GIT - The information manager from hell
+ *
+ * Copyright (C) Linus Torvalds, 2005
+ */
+#include "cache.h"
+
+static int missing_ok = 0;
+
+static int check_valid_sha1(unsigned char *sha1)
+{
+       int ret;
+
+       /* If we were anal, we'd check that the sha1 of the contents actually matches */
+       ret = has_sha1_file(sha1);
+       if (ret == 0)
+               perror(sha1_file_name(sha1));
+       return ret ? 0 : -1;
+}
+
+static int write_tree(struct cache_entry **cachep, int maxentries, const char *base, int baselen, unsigned char *returnsha1)
+{
+       unsigned char subdir_sha1[20];
+       unsigned long size, offset;
+       char *buffer;
+       int nr;
+
+       /* Guess at some random initial size */
+       size = 8192;
+       buffer = xmalloc(size);
+       offset = 0;
+
+       nr = 0;
+       while (nr < maxentries) {
+               struct cache_entry *ce = cachep[nr];
+               const char *pathname = ce->name, *filename, *dirname;
+               int pathlen = ce_namelen(ce), entrylen;
+               unsigned char *sha1;
+               unsigned int mode;
+
+               /* Did we hit the end of the directory? Return how many we wrote */
+               if (baselen >= pathlen || memcmp(base, pathname, baselen))
+                       break;
+
+               sha1 = ce->sha1;
+               mode = ntohl(ce->ce_mode);
+
+               /* Do we have _further_ subdirectories? */
+               filename = pathname + baselen;
+               dirname = strchr(filename, '/');
+               if (dirname) {
+                       int subdir_written;
+
+                       subdir_written = write_tree(cachep + nr, maxentries - nr, pathname, dirname-pathname+1, subdir_sha1);
+                       nr += subdir_written;
+
+                       /* Now we need to write out the directory entry into this tree.. */
+                       mode = S_IFDIR;
+                       pathlen = dirname - pathname;
+
+                       /* ..but the directory entry doesn't count towards the total count */
+                       nr--;
+                       sha1 = subdir_sha1;
+               }
+
+               if (!missing_ok && check_valid_sha1(sha1) < 0)
+                       exit(1);
+
+               entrylen = pathlen - baselen;
+               if (offset + entrylen + 100 > size) {
+                       size = alloc_nr(offset + entrylen + 100);
+                       buffer = xrealloc(buffer, size);
+               }
+               offset += sprintf(buffer + offset, "%o %.*s", mode, entrylen, filename);
+               buffer[offset++] = 0;
+               memcpy(buffer + offset, sha1, 20);
+               offset += 20;
+               nr++;
+       }
+
+       write_sha1_file(buffer, offset, "tree", returnsha1);
+       free(buffer);
+       return nr;
+}
+
+int main(int argc, char **argv)
+{
+       int i, funny;
+       int entries = read_cache();
+       unsigned char sha1[20];
+       
+       if (argc == 2) {
+               if (!strcmp(argv[1], "--missing-ok"))
+                       missing_ok = 1;
+               else
+                       die("unknown option %s", argv[1]);
+       }
+       
+       if (argc > 2)
+               die("too many options");
+
+       if (entries < 0)
+               die("git-write-tree: error reading cache");
+
+       /* Verify that the tree is merged */
+       funny = 0;
+       for (i = 0; i < entries; i++) {
+               struct cache_entry *ce = active_cache[i];
+               if (ntohs(ce->ce_flags) & ~CE_NAMEMASK) {
+                       if (10 < ++funny) {
+                               fprintf(stderr, "...\n");
+                               break;
+                       }
+                       fprintf(stderr, "%s: unmerged (%s)\n", ce->name, sha1_to_hex(ce->sha1));
+               }
+       }
+       if (funny)
+               die("git-write-tree: not able to write tree");
+
+       /* Also verify that the cache does not have path and path/file
+        * at the same time.  At this point we know the cache has only
+        * stage 0 entries.
+        */
+       funny = 0;
+       for (i = 0; i < entries - 1; i++) {
+               /* path/file always comes after path because of the way
+                * the cache is sorted.  Also path can appear only once,
+                * which means conflicting one would immediately follow.
+                */
+               const char *this_name = active_cache[i]->name;
+               const char *next_name = active_cache[i+1]->name;
+               int this_len = strlen(this_name);
+               if (this_len < strlen(next_name) &&
+                   strncmp(this_name, next_name, this_len) == 0 &&
+                   next_name[this_len] == '/') {
+                       if (10 < ++funny) {
+                               fprintf(stderr, "...\n");
+                               break;
+                       }
+                       fprintf(stderr, "You have both %s and %s\n",
+                               this_name, next_name);
+               }
+       }
+       if (funny)
+               die("git-write-tree: not able to write tree");
+
+       /* Ok, write it out */
+       if (write_tree(active_cache, entries, "", 0, sha1) != entries)
+               die("git-write-tree: internal error");
+       printf("%s\n", sha1_to_hex(sha1));
+       return 0;
+}