Do a cross-project merge of Paul Mackerras' gitk visualizer
authorLinus Torvalds <torvalds@ppc970.osdl.org>
Wed, 22 Jun 2005 21:05:02 +0000 (14:05 -0700)
committerLinus Torvalds <torvalds@ppc970.osdl.org>
Wed, 22 Jun 2005 21:05:02 +0000 (14:05 -0700)
gitk is really quite incredibly cool, and is great for visualizing what
is going on in a git repository.  It's especially useful when you are
looking at what has changed since a particular version, since it
gracefully handles partial trees (and this also avoids the expense of
looking at _all_ changes in a big project).

For example, to see what changed in a merge after a "git pull", do

gitk ORIG_HEAD..

to see only the new things.  Or you can simply do "gitk v2.6.12.." to
see what has changed since the v2.6.12 tag etc.

This merge itself is pretty interesting too, since it shows off a
feature of git itself that is incredibly cool: you can merge a
_separate_ git project into another git project.  Not only does this
keep all the history of the original project, it also makes it possible
to continue to merge with the original project and the union of the two
projects.

I don't think anybody else can do that.

203 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/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-check-files.txt [new file with mode: 0644]
Documentation/git-checkout-cache.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-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-fsck-cache.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-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-mkdelta.txt [new file with mode: 0644]
Documentation/git-mktag.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-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-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-update-cache.txt [new file with mode: 0644]
Documentation/git-write-blob.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/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]
cache.h [new file with mode: 0644]
cat-file.c [new file with mode: 0644]
check-files.c [new file with mode: 0644]
checkout-cache.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]
convert-cache.c [new file with mode: 0644]
count-delta.c [new file with mode: 0644]
count-delta.h [new file with mode: 0644]
cvs2git.c [new file with mode: 0644]
date.c [new file with mode: 0644]
delta.c [new file with mode: 0644]
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]
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-checkout-script [new file with mode: 0755]
git-commit-script [new file with mode: 0755]
git-cvsimport-script [new file with mode: 0755]
git-deltafy-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-log-script [new file with mode: 0755]
git-merge-one-file-script [new file with mode: 0755]
git-prune-script [new file with mode: 0755]
git-pull-script [new file with mode: 0755]
git-reset-script [new file with mode: 0755]
git-resolve-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-whatchanged [new file with mode: 0755]
gitenv.c [new file with mode: 0644]
http-pull.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]
mkdelta.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]
patch-delta.c [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]
pull.c [new file with mode: 0644]
pull.h [new file with mode: 0644]
read-cache.c [new file with mode: 0644]
read-tree.c [new file with mode: 0644]
refs.c [new file with mode: 0644]
refs.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]
sha1_file.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: 0644]
t/lib-read-tree-m-3way.sh [new file with mode: 0644]
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: 0644]
t/t1100-commit-tree-options.sh [new file with mode: 0644]
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: 0644]
t/t2100-update-cache-badpath.sh [new file with mode: 0755]
t/t3000-ls-files-others.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: 0644]
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: 0644]
t/t4003-diff-rename-1.sh [new file with mode: 0644]
t/t4004-diff-rename-symlink.sh [new file with mode: 0644]
t/t4005-diff-rename-2.sh [new file with mode: 0644]
t/t4006-diff-mode.sh [new file with mode: 0644]
t/t4007-rename-3.sh [new file with mode: 0644]
t/t4008-diff-break-rewrite.sh [new file with mode: 0644]
t/t4009-diff-rename-4.sh [new file with mode: 0644]
t/t4010-diff-pathspec.sh [new file with mode: 0644]
t/t4100-apply-stat.sh [new file with mode: 0644]
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/t5000-tar-tree.sh [new file with mode: 0644]
t/t5100-delta-pull.sh [new file with mode: 0644]
t/t6001-rev-list-merge-order.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]
test-date.c [new file with mode: 0644]
test-delta.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]
update-cache.c [new file with mode: 0644]
usage.c [new file with mode: 0644]
write-blob.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..e3844f2
--- /dev/null
@@ -0,0 +1,41 @@
+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))
+
+#
+# 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)
+
+# 'include' dependencies
+git-diff-%.txt: diff-format.txt
+       touch $@
+
+clean:
+       rm -f *.xml *.html *.1 *.7
+
+%.html : %.txt
+       asciidoc -b css-embedded -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..4e2c452
--- /dev/null
@@ -0,0 +1,212 @@
+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 <cvsroot> <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 a
+subdirectory named <module>.
+
+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 can be reasonably chatty, but on some not very
+scientific tests it averaged about eight 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.
+
+
+Emulating CVS behaviour
+-----------------------
+
+
+FIXME! Talk about setting up several repositories, and pulling and
+pushing between them. Talk about merging, and branches. Some of this
+needs to be in the tutorial too.
+
+
+
+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
+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 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..d6ce035
--- /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 a/<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 extention 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/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..81eb155
--- /dev/null
@@ -0,0 +1,72 @@
+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] [-] [<file>...]
+
+DESCRIPTION
+-----------
+Reads supplied diff output and applies it on a GIT index file
+and a work tree.
+
+OPTIONS
+-------
+<file>...::
+       The files to read patch from.
+
+-::
+       Instead of reading from a file, read from standard input.
+
+--no-merge::
+       The default mode of operation is the merge behaviour
+       which is not quite 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.
+
+--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.
+
+--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.
+
+--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.
+
+
+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..48fb377
--- /dev/null
@@ -0,0 +1,55 @@
+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 | <type>) <object>
+
+DESCRIPTION
+-----------
+Provides content or type of objects in the repository. The type
+is required if '-t' is not being used to find the object type.
+
+OPTIONS
+-------
+<object>::
+       The sha1 identifier of the object.
+
+-t::
+       Instead of the content, show the object type 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>.
+
+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-check-files.txt b/Documentation/git-check-files.txt
new file mode 100644 (file)
index 0000000..6146098
--- /dev/null
@@ -0,0 +1,50 @@
+git-check-files(1)
+==================
+v0.1, May 2005
+
+NAME
+----
+git-check-files - Verify a list of files are up-to-date
+
+
+
+SYNOPSIS
+--------
+'git-check-files' <file>...
+
+DESCRIPTION
+-----------
+Check that a list of files are up-to-date between the filesystem and
+the cache. Used to verify a patch target before doing a patch.
+
+Files that do not exist on the filesystem are considered up-to-date
+(whether or not they are in the cache).
+
+Emits an error message on failure:
+
+preparing to update existing file <file> not in cache::
+         <file> exists but is not in the cache
+
+preparing to update file <file> not uptodate in cache::
+         <file> on disk is not up-to-date with the cache
+
+Exits with a status code indicating success if all files are
+up-to-date.
+
+See Also
+--------
+link:git-update-cache.html[git-update-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-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-commit-tree.txt b/Documentation/git-commit-tree.txt
new file mode 100644 (file)
index 0000000..c0dc1f4
--- /dev/null
@@ -0,0 +1,88 @@
+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
+
+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-diff-cache.txt b/Documentation/git-diff-cache.txt
new file mode 100644 (file)
index 0000000..f6dd703
--- /dev/null
@@ -0,0 +1,176 @@
+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' [-p] [-r] [-z] [-m] [--cached] [-R] [-B] [-M] [-C] [--find-copies-harder] [-O<orderfile>] [-S<string>] [--pickaxe-all] <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
+-------
+<tree-ish>::
+       The id of a tree object to diff against.
+
+-p::
+       Generate patch (see section on generating patches)
+
+-r::
+       This flag does not mean anything.  It is there only to match
+       "git-diff-tree".  Unlike "git-diff-tree", "git-diff-cache"
+       always looks at all the subdirectories.
+
+-z::
+       \0 line termination on output
+
+-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.
+
+--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 index
+       contents (the ones I'd write with a "git-write-tree")
+
+For example, let's say that you have worked on your index file, 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..32e9a1e
--- /dev/null
@@ -0,0 +1,84 @@
+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' [-p] [-q] [-r] [-z] [-R] [-B] [-M] [-C] [--find-copies-harder] [-O<orderfile>] [-S<string>] [--pickaxe-all] [<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
+-------
+-p::
+       generate patch (see section on generating patches).
+
+-q::
+       Remain silent even on nonexisting files
+
+-R::
+       Swap two inputs; that is, show differences from on-disk files
+       to cache contents.
+
+-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::
+       This flag does not mean anything.  It is there only to match
+       git-diff-tree.  Unlike git-diff-tree, git-diff-files always looks
+       at all the subdirectories.
+
+
+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..e7e59c8
--- /dev/null
@@ -0,0 +1,83 @@
+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' [-p] [-r] [-z] [-R] [-B] [-M] [-C] [--find-copies-harder] [-O<orderfile>] [-S<string>] [--pickaxe-all] <stage1> <stage2> [<path>...]
+
+DESCRIPTION
+-----------
+Compares the content and mode of the blobs in two stages in an
+unmerged index file.
+
+OPTIONS
+-------
+<stage1>,<stage2>::
+       The stage number to be compared.
+
+-p::
+       Generate patch (see section on generating patches)
+
+-r::
+       This flag does not mean anything.  It is there only to match
+       "git-diff-tree".  Unlike "git-diff-tree", "git-diff-stages"
+       always looks at all the subdirectories.
+
+-z::
+       \0 line termination on output
+
+-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 <stage2> to
+       <stage1>.
+
+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..ab0ba4f
--- /dev/null
@@ -0,0 +1,170 @@
+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' [-p] [-r] [-z] [--stdin] [-m] [-s] [-v] [-t] [-R] [-B] [-M] [-C] [--find-copies-harder] [-O<orderfile>] [-S<string>] [--pickaxe-all] <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
+-------
+<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.
+
+-p::
+       generate patch (see section on generating patches).  For
+       git-diff-tree, this flag implies '-r' as well.
+
+-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.
+
+-R::
+       Swap two input trees.
+
+-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::
+       recurse
+
+-t::
+       show tree entry itself as well as subtrees.  Implies -r.
+
+-z::
+       \0 line termination on output
+
+--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 complete path comonent.
+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-fsck-cache.txt b/Documentation/git-fsck-cache.txt
new file mode 100644 (file)
index 0000000..ff97504
--- /dev/null
@@ -0,0 +1,128 @@
+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] [--delta-depth] [--unreachable] [--cache] [<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.
+
+--delta-depth::
+       Report back the length of the longest delta chain found.
+
+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)
+
+or, for Cogito users:
+
+       git-fsck-cache --unreachable $(cat .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.
+       (note: this error occured during early git development when
+       the database format changed.)
+
+Environment Variables
+---------------------
+
+GIT_OBJECT_DIRECTORY::
+       used to specify the object database root (usually .git/objects)
+
+GIT_INDEX_FILE::
+       used to specify the 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-http-pull.txt b/Documentation/git-http-pull.txt
new file mode 100644 (file)
index 0000000..e4b7b37
--- /dev/null
@@ -0,0 +1,46 @@
+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] [-v] [-d] [--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.
+-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 --git a/Documentation/git-init-db.txt b/Documentation/git-init-db.txt
new file mode 100644 (file)
index 0000000..99f96f7
--- /dev/null
@@ -0,0 +1,40 @@
+git-init-db(1)
+==============
+v0.1, May 2005
+
+NAME
+----
+git-init-db - Creates an empty git object database
+
+
+SYNOPSIS
+--------
+'git-init-db'
+
+DESCRIPTION
+-----------
+This simply creates an empty git object database - basically a `.git`
+directory and `.git/object/??/` directories.
+
+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/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..777bdbd
--- /dev/null
@@ -0,0 +1,50 @@
+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.
+-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.
+-w::
+        Writes the commit-id into the filename under $GIT_DIR/refs/ 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..be83ab1
--- /dev/null
@@ -0,0 +1,108 @@
+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>]
+
+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.
+       Allows the use of the famous dontdiff file as follows to find
+       out about uncommitted files just as dontdiff is used with
+       the diff command:
+            git-ls-files --others --exclude-from=dontdiff
+
+-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 Cogito) to see what should eventually be recorded at the
+path. (see read-cache for more information on state)
+
+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-tree.txt b/Documentation/git-ls-tree.txt
new file mode 100644 (file)
index 0000000..958b56d
--- /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.
+
+-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, shows 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..3fb4d49
--- /dev/null
@@ -0,0 +1,84 @@
+git-merge-cache(1)
+==================
+v0.1, May 2005
+
+NAME
+----
+git-merge-cache - Runs a merge for files needing merging
+
+
+SYNOPSIS
+--------
+'git-merge-cache' [-o] <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 future 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.
+
+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
+ditribution.
+
+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-mkdelta.txt b/Documentation/git-mkdelta.txt
new file mode 100644 (file)
index 0000000..240d59a
--- /dev/null
@@ -0,0 +1,45 @@
+git-mkdelta(1)
+==============
+May 2005
+
+NAME
+----
+git-mkdelta - Creates a delta object
+
+
+SYNOPSIS
+--------
+'git-mkdelta' [-v] [-d N | --max-depth=N ] <reference_object> <target_object> [ <next_object> ... ]
+
+DESCRIPTION
+-----------
+Creates a delta object to replace <reference_object> by using an
+ordered list of potential objects to deltafy against earlier objects
+in the list.
+
+A cap on the depth of delta references can be provided as well,
+otherwise the default is to not have any limit.  A limit of 0 will
+also undeltafy a given object.
+
+
+OPTIONS
+-------
+-v::
+       Verbose
+
+-d|--max-depth::
+       limit the number of delta references in a chain
+       If 0 then all objects are undeltafied.
+       
+Author
+------
+Git is 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/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-prune-script.txt b/Documentation/git-prune-script.txt
new file mode 100644 (file)
index 0000000..537b790
--- /dev/null
@@ -0,0 +1,32 @@
+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/refs/heads/\*` and `.git/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..44fd09a
--- /dev/null
@@ -0,0 +1,31 @@
+git-pull-script(1)
+==================
+v0.1, May 2005
+
+NAME
+----
+git-pull-script - Script used by Linus to pull and merge a remote repository
+
+
+SYNOPSIS
+--------
+'git-pull-script'
+
+DESCRIPTION
+-----------
+This script is used by Linus to pull from a remote repository and perform
+a merge.
+
+
+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-read-tree.txt b/Documentation/git-read-tree.txt
new file mode 100644 (file)
index 0000000..7665946
--- /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 performs 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 "script
+  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-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..fe86c9c
--- /dev/null
@@ -0,0 +1,75 @@
+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 ] [ *--merge-order* [ *--show-breaks* ] ] <commit>
+
+DESCRIPTION
+-----------
+Lists commit objects in reverse chronological order starting at the
+given commit, taking ancestry relationship into account.  This is
+useful to produce human-readable log output.
+
+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-ssh-pull.txt b/Documentation/git-ssh-pull.txt
new file mode 100644 (file)
index 0000000..3397fba
--- /dev/null
@@ -0,0 +1,59 @@
+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.
+-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.
+-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..c55ce7d
--- /dev/null
@@ -0,0 +1,55 @@
+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::
+        Either the hash or the filename under $GIT_DIR/refs/ to push.
+
+-c::
+        Get the commit objects.
+-t::
+        Get tree associated with the requested commit object.
+-a::
+        Get all the objects.
+-d::
+        Do not check for delta base objects (use this option
+        only when you know the local repository is not
+        deltified).
+--recover::
+        Check dependency of deltified object more carefully than
+        usual, to recover after earlier push that was interrupted.
+-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..daf350b
--- /dev/null
@@ -0,0 +1,32 @@
+git-tag-script(1)
+=================
+v0.1, May 2005
+
+NAME
+----
+git-tag-script - An example script to create a tag object signed with GPG
+
+
+
+SYNOPSIS
+--------
+'git-tag-script'
+
+DESCRIPTION
+-----------
+This is an example script that uses "git-mktag" to create a tag object
+signed with GPG.
+
+
+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..dc8d0fa
--- /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 as 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-update-cache.txt b/Documentation/git-update-cache.txt
new file mode 100644 (file)
index 0000000..947f2bd
--- /dev/null
@@ -0,0 +1,108 @@
+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>]\*
+            [--] [<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.
+       
+--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
+-----------------
+'--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
+
+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-write-blob.txt b/Documentation/git-write-blob.txt
new file mode 100644 (file)
index 0000000..22d7555
--- /dev/null
@@ -0,0 +1,33 @@
+git-write-blob(1)
+=================
+v0.1, May 2005
+
+NAME
+----
+git-write-blob - Creates a blob from a file
+
+
+SYNOPSIS
+--------
+'git-write-blob' <any-file-on-the-filesystem>
+
+DESCRIPTION
+-----------
+Writes the contents of the named file (which can be outside of the work
+tree) as a blob into the object database, and reports its object ID to its
+standard output.  This is used by "git-merge-one-file-script" to update the
+cache without modifying files in the 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-write-tree.txt b/Documentation/git-write-tree.txt
new file mode 100644 (file)
index 0000000..458d97a
--- /dev/null
@@ -0,0 +1,52 @@
+git-write-tree(1)
+=================
+v0.1, May 2005
+
+NAME
+----
+git-write-tree - Creates a tree from the current cache
+
+
+SYNOPSIS
+--------
+'git-write-tree'
+
+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".
+
+
+
+
+////////////////////////////////////////////////////////////////
+
+Producing man pages and html
+
+To create a set of html pages run:
+  perl split-docs.pl -html < core-git.txt
+
+To create a set of man pages run:
+  perl split-docs.pl -man < core-git.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.txt b/Documentation/git.txt
new file mode 100644 (file)
index 0000000..971012b
--- /dev/null
@@ -0,0 +1,308 @@
+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 and those that
+interrogate and compare them.
+
+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-mkdelta.html[git-mkdelta]::
+       Creates a delta object
+
+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-write-blob.html[git-write-blob]::
+       Creates a blob 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-check-files.html[git-check-files]::
+       Verify a list of files are up-to-date
+
+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
+
+The interrogate commands may create files - and you can force them to
+touch the working file set - but in general they don't
+
+
+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-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-merge-one-file-script.html[git-merge-one-file-script]::
+       The standard helper program to use with "git-merge-cache"
+
+link:git-pull-script.html[git-pull-script]::
+       Script used by Linus to pull and merge a remote repository
+
+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
+
+link:git-ssh-pull.html[git-ssh-pull]::
+       Pulls from a remote repository over ssh connection
+
+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
+
+It can interrogate (but never updates) the following areas:
+
+   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)
+     snap/     ????
+   ...         Everything else isn't shared
+   HEAD        Symlink to refs/heads/<something>
+
+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/tutorial.txt b/Documentation/tutorial.txt
new file mode 100644 (file)
index 0000000..957ea96
--- /dev/null
@@ -0,0 +1,538 @@
+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" > a
+       echo "Silly example" > b
+
+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 a b
+
+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 "a". 
+
+[ Digression: don't confuse that object with the file "a" itself.  The
+  object is literally just those specific _contents_ of the file, and
+  however much you later change the contents in file "a", 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 "a" first:
+
+       echo "It's a new day for git" >> a
+
+and you can now, since you told git about the previous state of "a", 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 "a" 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/a b/a
+       --- a/a
+       +++ b/a
+       @@ -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 "a".
+
+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.
+
+
+       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 does exactly as I've described) it should be
+
+       3ede4ed7e895432c0a247f09d71a76db53bd0fa4
+
+which is another incomprehensible object name. Again, if you want to,
+you can use "git-cat-file -t 3ede4.." 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 3ede4ed7e895432c0a247f09d71a76db53bd0fa4
+
+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.
+
+
+       Making a change
+       ---------------
+
+Remember how we did the "git-update-cache" on file "a" and then we
+changed "a" afterward, and could compare the new state of "a" 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 "a", 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 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.
+
+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 a
+
+(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 "a" 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 "a" in the index, we can commit the new
+version. We could do it by writing the tree by hand, 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 the fact is, git has a simple helper
+script for doing all of the non-initial commits that does all of this
+for you, and starts up an editor to let you write your commit message
+yourself, so let's just use that:
+
+       git commit
+
+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.
+
+
+       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 no "git clone" command: if you want 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/linux/kernel/git/torvalds/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" file 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"
+file first, to tell git-checkout-cache to _force_ overwriting of any old
+files). 
+
+You have now successfully copied somebody else's (mine) remote
+repository, and checked it out. 
+
+[ to be continued.. cvs2git, tagging versions, branches, merging.. ]
diff --git a/INSTALL b/INSTALL
new file mode 100644 (file)
index 0000000..101485d
--- /dev/null
+++ b/INSTALL
@@ -0,0 +1,64 @@
+
+               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".  git-http-pull uses this.  You can disable building of
+         that program if you just want to get started. 
+
+       - "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..3372269
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,155 @@
+# -DCOLLISION_CHECK if you believe that SHA1's
+# 1461501637330902918203684832716283019655932542976 hashes do not give you
+# enough guarantees about no collisions between objects ever hapenning.
+#
+# -DUSE_NSEC if you want git to care about sub-second file mtimes and ctimes.
+# -DUSE_STDEV if you want git to care about st_dev changing
+#
+# Note that you need some new 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).
+COPTS=-O2
+CFLAGS=-g $(COPTS) -Wall
+
+prefix=$(HOME)
+bin=$(prefix)/bin
+# dest=
+
+CC=gcc
+AR=ar
+INSTALL=install
+
+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-deltafy-script 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
+
+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-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-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 \
+       git-cvs2git git-diff-stages git-rev-parse
+
+all: $(PROG)
+
+install: $(PROG) $(SCRIPTS)
+       $(INSTALL) $(PROG) $(SCRIPTS) $(dest)$(bin)
+
+LIB_OBJS=read-cache.o sha1_file.o usage.o object.o commit.o tree.o blob.o \
+        tag.o delta.o date.o index.o diff-delta.o patch-delta.o entry.o \
+        epoch.o refs.o
+LIB_FILE=libgit.a
+LIB_H=cache.h object.h blob.h tree.h commit.h tag.h delta.h epoch.h
+
+LIB_H += strbuf.h
+LIB_OBJS += strbuf.o
+
+LIB_H += diff.h count-delta.h
+LIB_OBJS += diff.o diffcore-rename.o diffcore-pickaxe.o diffcore-pathspec.o \
+       count-delta.o diffcore-break.o diffcore-order.o
+
+LIB_OBJS += gitenv.o
+
+LIBS = $(LIB_FILE)
+LIBS += -lz
+
+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>
+  LIBS += -lcrypto
+endif
+endif
+
+CFLAGS += '-DSHA1_HEADER=$(SHA1_HEADER)'
+
+$(LIB_FILE): $(LIB_OBJS)
+       $(AR) rcs $@ $(LIB_OBJS)
+
+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 $@ $^
+
+git-%: %.c $(LIB_FILE)
+       $(CC) $(CFLAGS) -o $@ $(filter %.c,$^) $(LIBS)
+
+git-update-cache: update-cache.c
+git-diff-files: diff-files.c
+git-init-db: init-db.c
+git-write-tree: write-tree.c
+git-read-tree: read-tree.c
+git-commit-tree: commit-tree.c
+git-cat-file: cat-file.c
+git-fsck-cache: fsck-cache.c
+git-checkout-cache: checkout-cache.c
+git-diff-tree: diff-tree.c
+git-rev-tree: rev-tree.c
+git-ls-files: ls-files.c
+git-check-files: check-files.c
+git-ls-tree: ls-tree.c
+git-merge-base: merge-base.c
+git-merge-cache: merge-cache.c
+git-unpack-file: unpack-file.c
+git-export: export.c
+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-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
+git-tar-tree: tar-tree.c
+git-write-blob: write-blob.c
+git-mkdelta: mkdelta.c
+git-stripspace: stripspace.c
+git-cvs2git: cvs2git.c
+git-diff-stages: diff-stages.c
+git-rev-parse: rev-parse.c
+
+git-http-pull: LIBS += -lcurl
+git-rev-list: LIBS += -lssl
+
+# Library objects..
+blob.o: $(LIB_H)
+tree.o: $(LIB_H)
+commit.o: $(LIB_H)
+tag.o: $(LIB_H)
+object.o: $(LIB_H)
+read-cache.o: $(LIB_H)
+sha1_file.o: $(LIB_H)
+usage.o: $(LIB_H)
+strbuf.o: $(LIB_H)
+gitenv.o: $(LIB_H)
+entry.o: $(LIB_H)
+diff.o: $(LIB_H) diffcore.h
+diffcore-rename.o : $(LIB_H) diffcore.h
+diffcore-pathspec.o : $(LIB_H) diffcore.h
+diffcore-pickaxe.o : $(LIB_H) diffcore.h
+diffcore-break.o : $(LIB_H) diffcore.h
+diffcore-order.o : $(LIB_H) diffcore.h
+epoch.o: $(LIB_H)
+
+test: all
+       $(MAKE) -C t/ all
+
+clean:
+       rm -f *.o mozilla-sha1/*.o ppc/*.o $(PROG) $(LIB_FILE)
+       $(MAKE) -C Documentation/ clean
+
+backup: clean
+       cd .. ; tar czvf dircache.tar.gz dir-cache
diff --git a/README b/README
new file mode 100644 (file)
index 0000000..f5deac7
--- /dev/null
+++ b/README
@@ -0,0 +1,509 @@
+////////////////////////////////////////////////////////////////
+
+       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 five different object types: "blob",
+"tree", "commit", "tag" and "delta"
+
+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.
+
+A "delta" object is used internally by the object database to minimise
+disk usage. Instead of storing the entire contents of a revision, git
+can behave in a similar manner to RCS et al and simply store a delta.
+
+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 or
+the delta. (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 created with link:git-write-blob.html[git-write-blob] 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]
+
+Delta Object
+~~~~~~~~~~~~
+
+The "delta" object is used internally by the object database to
+minimise storage usage by using xdeltas (byte level diffs). Deltas can
+form chains of arbitrary length as RCS does (although this is
+configureable at creation time). Most operations won't see or even be
+aware of delta objects as they are automatically 'applied' and appear
+as 'real' git objects In other words, if you write your own routines
+to look at the contents of the object database then you need to know
+about this - otherwise you don't. Actually, that's not quite true -
+one important area where deltas are likely to prove very valuable is
+in reducing bandwidth loads - so the more sophisticated network tools
+for git repositories will be aware of them too.
+
+Finally, git repositories can (and must) be deltafied in the
+background - the work to calculate the differences does not take place
+automatically at commit time.
+
+A delta can be created (or undeltafied) with
+link:git-mkdelta.html[git-mkdelta] it's raw data cannot be accessed at
+present.
+
+
+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..31f134c
--- /dev/null
+++ b/apply.c
@@ -0,0 +1,1451 @@
+/*
+ * 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 "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 [--stat] [--summary] [--check] [--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, 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;
+               }
+       }
+       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;
+}
+
+const char pluses[] = "++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++";
+const char minuses[]= "----------------------------------------------------------------------";
+
+static void show_stats(struct patch *patch)
+{
+       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)
+               name += len - max;
+       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 |%5d %.*s%.*s\n",
+               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;
+               fprintf(stderr, "Fragment applied at offset %d\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:%d", 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,
+                      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, 0755) < 0) {
+                       if (errno != EEXIST)
+                               break;
+               }
+       }
+       free(buf);
+}
+
+/*
+ * 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 int create_regular_file(const char *path, unsigned int mode)
+{
+       int ret = open(path, O_WRONLY | O_CREAT | O_TRUNC, mode);
+
+       if (ret < 0 && errno == ENOENT) {
+               create_subdirectories(path);
+               ret = open(path, O_WRONLY | O_CREAT | O_TRUNC, mode);
+       }
+       return ret;
+}
+
+static int create_symlink(const char *buf, const char *path)
+{
+       int ret = symlink(buf, path);
+
+       if (ret < 0 && errno == ENOENT) {
+               create_subdirectories(path);
+               ret = symlink(buf, path);
+       }
+       return ret;
+}
+
+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;
+       if (S_ISREG(mode)) {
+               int fd;
+               mode = (mode & 0100) ? 0777 : 0666;
+               fd = create_regular_file(path, mode);
+               if (fd < 0)
+                       die("unable to create file %s (%s)", path, strerror(errno));
+               if (write(fd, buf, size) != size)
+                       die("unable to write file %s", path);
+               close(fd);
+               add_index_file(path, mode, buf, size);
+               return;
+       }
+       if (S_ISLNK(mode)) {
+               if (size && buf[size-1] == '\n')
+                       size--;
+               buf[size] = 0;
+               if (create_symlink(buf, path) < 0)
+                       die("unable to write symlink %s", path);
+               add_index_file(path, mode, buf, size);
+               return;
+       }
+       die("unable to write file mode %o", mode);
+}
+
+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)
+{
+       if (!list)
+               die("No changes");
+
+       while (list) {
+               write_out_one_result(list);
+               list = list->next;
+       }
+}
+
+static struct cache_file cache_file;
+
+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;
+
+       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;
+               patch_stats(patch);
+               *listp = patch;
+               listp = &patch->next;
+               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);
+
+       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 (!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, "--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/cache.h b/cache.h
new file mode 100644 (file)
index 0000000..6a7525a
--- /dev/null
+++ b/cache.h
@@ -0,0 +1,236 @@
+#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 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
+
+/*
+ * 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"
+
+extern char *get_object_directory(void);
+extern char *get_refs_directory(void);
+extern char *get_index_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 */
+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 index_fd(unsigned char *sha1, int fd, struct stat *st);
+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 *sha1_file_name(const unsigned char *sha1);
+
+/* Read and unpack a sha1 file into memory, write memory to a sha1 file */
+extern void * map_sha1_file(const unsigned char *sha1, unsigned long *size);
+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_delta_base(const unsigned char *, unsigned char *);
+extern int sha1_file_size(const unsigned 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 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);
+
+extern int write_sha1_from_fd(const unsigned char *sha1, int fd);
+
+extern int has_sha1_file(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;
+extern int error(const char *err, ...);
+
+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(char *date, char *buf, int bufsize);
+void datestamp(char *buf, int bufsize);
+
+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);
+
+#endif /* CACHE_H */
diff --git a/cat-file.c b/cat-file.c
new file mode 100644 (file)
index 0000000..be41f51
--- /dev/null
@@ -0,0 +1,49 @@
+/*
+ * 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 | tagname] <sha1>");
+
+       if (!strcmp("-t", argv[1])) {
+               buf = read_sha1_file(sha1, type, &size);
+               if (buf) {
+                       buf = type;
+                       size = strlen(type);
+                       type[size] = '\n';
+                       size++;
+               }
+       } 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/check-files.c b/check-files.c
new file mode 100644 (file)
index 0000000..6fd69e7
--- /dev/null
@@ -0,0 +1,47 @@
+/*
+ * check-files.c
+ *
+ * Check that a set of files are up-to-date in the filesystem or
+ * do not exist. Used to verify a patch target before doing a patch.
+ *
+ * Copyright (C) 2005 Linus Torvalds
+ */
+#include "cache.h"
+
+static void check_file(const char *path)
+{
+       int fd = open(path, O_RDONLY);
+       struct cache_entry *ce;
+       struct stat st;
+       int pos, changed;
+
+       /* Nonexistent is fine */
+       if (fd < 0) {
+               if (errno != ENOENT)
+                       die("%s: %s", path, strerror(errno));
+               return;
+       }
+
+       /* Exists but is not in the cache is not fine */
+       pos = cache_name_pos(path, strlen(path));
+       if (pos < 0)
+               die("preparing to update existing file '%s' not in cache", path);
+       ce = active_cache[pos];
+
+       if (lstat(path, &st) < 0)
+               die("lstat(%s): %s", path, strerror(errno));
+
+       changed = ce_match_stat(ce, &st);
+       if (changed)
+               die("preparing to update file '%s' not uptodate in cache", path);
+}
+
+int main(int argc, char **argv)
+{
+       int i;
+
+       read_cache();
+       for (i = 1; i < argc ; i++)
+               check_file(argv[i]);
+       return 0;
+}
diff --git a/checkout-cache.c b/checkout-cache.c
new file mode 100644 (file)
index 0000000..82ddbe4
--- /dev/null
@@ -0,0 +1,145 @@
+/*
+ * 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;
+}
+
+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 (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/commit-tree.c b/commit-tree.c
new file mode 100644 (file)
index 0000000..0d504e7
--- /dev/null
@@ -0,0 +1,197 @@
+/*
+ * 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 remove_special(char *p)
+{
+       char c;
+       char *dst = p, *src = p;
+
+       for (;;) {
+               c = *src;
+               src++;
+               switch(c) {
+               case '\n': case '<': case '>':
+                       continue;
+               }
+               *dst++ = c;
+               if (!c)
+                       break;
+       }
+
+       /*
+        * Go back, and remove crud from the end: some people
+        * have commas etc in their gecos field
+        */
+       dst--;
+       while (--dst >= p) {
+               unsigned char c = *dst;
+               switch (c) {
+               case ',': case ';': case '.':
+                       *dst = 0;
+                       continue;
+               }
+               break;
+       }
+}
+
+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 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, len;
+       int parents = 0;
+       unsigned char tree_sha1[20];
+       unsigned char commit_sha1[20];
+       char *gecos, *realgecos, *commitgecos;
+       char *email, *commitemail, realemail[1000];
+       char date[50], realdate[50];
+       char *audate, *cmdate;
+       char comment[1000];
+       struct passwd *pw;
+       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]);
+       pw = getpwuid(getuid());
+       if (!pw)
+               die("You don't exist. Go away!");
+       realgecos = pw->pw_gecos;
+       len = strlen(pw->pw_name);
+       memcpy(realemail, pw->pw_name, len);
+       realemail[len] = '@';
+       gethostname(realemail+len+1, sizeof(realemail)-len-1);
+       if (!strchr(realemail+len+1, '.')) {
+               strcat(realemail, ".");
+               getdomainname(realemail+strlen(realemail), sizeof(realemail)-strlen(realemail)-1);
+       }
+
+       datestamp(realdate, sizeof(realdate));
+       strcpy(date, realdate);
+
+       commitgecos = gitenv("GIT_COMMITTER_NAME") ? : realgecos;
+       commitemail = gitenv("GIT_COMMITTER_EMAIL") ? : realemail;
+       gecos = gitenv("GIT_AUTHOR_NAME") ? : realgecos;
+       email = gitenv("GIT_AUTHOR_EMAIL") ? : realemail;
+       audate = gitenv("GIT_AUTHOR_DATE");
+       if (audate)
+               parse_date(audate, date, sizeof(date));
+       cmdate = gitenv("GIT_COMMITTER_DATE");
+       if (cmdate)
+               parse_date(cmdate, realdate, sizeof(realdate));
+
+       remove_special(gecos); remove_special(realgecos); remove_special(commitgecos);
+       remove_special(email); remove_special(realemail); remove_special(commitemail);
+
+       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 <%s> %s\n", gecos, email, date);
+       add_buffer(&buffer, &size, "committer %s <%s> %s\n\n", commitgecos, commitemail, realdate);
+
+       /* 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..738590f
--- /dev/null
+++ b/commit.c
@@ -0,0 +1,330 @@
+#include <ctype.h>
+#include "tag.h"
+#include "commit.h"
+#include "cache.h"
+
+const char *commit_type = "commit";
+
+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 = parse_object(sha1);
+
+       if (!obj)
+               return NULL;
+       if (obj->type == tag_type)
+               obj = ((struct tag *)obj)->tagged;
+       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;
+}
+
+int parse_commit_buffer(struct commit *item, void *buffer, unsigned long size)
+{
+       void *bufptr = buffer;
+       unsigned char parent[20];
+       struct commit_list **pptr;
+
+       if (item->object.parsed)
+               return 0;
+       item->object.parsed = 1;
+       get_sha1_hex(bufptr + 5, parent);
+       item->tree = lookup_tree(parent);
+       if (item->tree)
+               add_ref(&item->object, &item->tree->object);
+       bufptr += 46; /* "tree " + "hex sha1" + "\n" */
+       pptr = &item->parents;
+       while (!memcmp(bufptr, "parent ", 7) &&
+              !get_sha1_hex(bufptr + 7, parent)) {
+               struct commit *new_parent = lookup_commit(parent);
+               if (new_parent) {
+                       pptr = &commit_list_insert(new_parent, pptr)->next;
+                       add_ref(&item->object, &new_parent->object);
+               }
+               bufptr += 48;
+       }
+       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);
+       }
+}
+
+void insert_by_date(struct commit_list **list, struct commit *item)
+{
+       struct commit_list **pp = list;
+       struct commit_list *p;
+       while ((p = *pp) != NULL) {
+               if (p->item->date < item->date) {
+                       break;
+               }
+               pp = &p->next;
+       }
+       commit_list_insert(item, pp);
+}
+
+       
+void sort_by_date(struct commit_list **list)
+{
+       struct commit_list *ret = NULL;
+       while (*list) {
+               insert_by_date(&ret, (*list)->item);
+               *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(list, commit);
+               }
+               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_author_info(enum cmit_fmt fmt, char *buf, const char *line, int len)
+{
+       char *date;
+       unsigned int namelen;
+       unsigned long time;
+       int tz, ret;
+
+       line += strlen("author ");
+       date = strchr(line, '>');
+       if (!date)
+               return 0;
+       namelen = ++date - line;
+       time = strtoul(date, &date, 10);
+       tz = strtol(date, NULL, 10);
+
+       ret = sprintf(buf, "Author: %.*s\n", 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;
+       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;
+
+       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;
+                               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_author_info(fmt, buf + offset, line, linelen);
+                       continue;
+               }
+
+               if (is_empty_line(line, linelen)) {
+                       if (!body)
+                               continue;
+                       if (fmt == CMIT_FMT_SHORT)
+                               break;
+               } else {
+                       body = 1;
+               }
+               memset(buf + offset, ' ', 4);
+               memcpy(buf + offset + 4, line, linelen);
+               offset += linelen + 4;
+       }
+       /* 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;
+}
+
diff --git a/commit.h b/commit.h
new file mode 100644 (file)
index 0000000..57c1b62
--- /dev/null
+++ b/commit.h
@@ -0,0 +1,56 @@
+#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);
+
+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
+};
+
+extern unsigned long pretty_print_commit(enum cmit_fmt fmt, const char *msg, unsigned long len, char *buf, unsigned long space);
+
+void insert_by_date(struct commit_list **list, struct commit *item);
+
+/** 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);
+#endif /* COMMIT_H */
diff --git a/convert-cache.c b/convert-cache.c
new file mode 100644 (file)
index 0000000..77f8bff
--- /dev/null
@@ -0,0 +1,312 @@
+#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("bad sha1");
+       entry = convert_entry(sha1);
+       memcpy(buffer, sha1_to_hex(entry->new_sha1), 40);
+}
+
+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");
+               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;
+
+       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);
+       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..c7f3767
--- /dev/null
@@ -0,0 +1,97 @@
+/*
+ * 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 "count-delta.h"
+
+static unsigned long get_hdr_size(const unsigned char **datap)
+{
+       const unsigned char *data = *datap;
+       unsigned long size;
+       unsigned char cmd;
+       int i;
+       size = i = 0;
+       cmd = *data++;
+       while (cmd) {
+               if (cmd & 1)
+                       size |= *data++ << i;
+               i += 8;
+               cmd >>= 1;
+       }
+       *datap = data;
+       return size;
+}
+
+/*
+ * 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;
+
+       /* the smallest delta size possible is 6 bytes */
+       if (delta_size < 6)
+               return -1;
+
+       data = delta_buf;
+       top = delta_buf + delta_size;
+
+       src_size = get_hdr_size(&data);
+       dst_size = get_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/cvs2git.c b/cvs2git.c
new file mode 100644 (file)
index 0000000..ab05908
--- /dev/null
+++ b/cvs2git.c
@@ -0,0 +1,329 @@
+/*
+ * cvs2git
+ *
+ * Copyright (C) Linus Torvalds 2005
+ */
+
+#include <stdio.h>
+#include <ctype.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+static int verbose = 0;
+
+/*
+ * This is a really stupid program that takes cvsps output, and
+ * generates a a long _shell_script_ that will create the GIT archive
+ * from it. 
+ *
+ * You've been warned. I told you it was stupid.
+ *
+ * NOTE NOTE NOTE! In order to do branches correctly, this needs
+ * the fixed cvsps that has the "Ancestor branch" tag output.
+ * Hopefully David Mansfield will update his distribution soon
+ * enough (he's the one who wrote the patch, so at least we don't
+ * have to figt maintainer issues ;)
+ *
+ * Usage:
+ *
+ *     TZ=UTC cvsps -A |
+ *             git-cvs2git --cvsroot=[root] --module=[module] > script
+ *
+ * Creates a shell script that will generate the .git archive of
+ * the names CVS repository.
+ *
+ *     TZ=UTC cvsps -s 1234- -A |
+ *             git-cvs2git -u --cvsroot=[root] --module=[module] > script
+ *
+ * Creates a shell script that will update the .git archive with
+ * CVS changes from patchset 1234 until the last one.
+ *
+ * IMPORTANT NOTE ABOUT "cvsps"! This requires version 2.1 or better,
+ * and the "TZ=UTC" and the "-A" flag is required for sane results!
+ */
+enum state {
+       Header,
+       Log,
+       Members
+};
+
+static const char *cvsroot;
+static const char *cvsmodule;
+
+static char date[100];
+static char author[100];
+static char branch[100];
+static char ancestor[100];
+static char tag[100];
+static char log[32768];
+static int loglen = 0;
+static int initial_commit = 1;
+
+static void lookup_author(char *n, char **name, char **email)
+{
+       /*
+        * FIXME!!! I'm lazy and stupid.
+        *
+        * This could be something like
+        *
+        *      printf("lookup_author '%s'\n", n);
+        *      *name = "$author_name";
+        *      *email = "$author_email";
+        *
+        * and that would allow the script to do its own
+        * lookups at run-time.
+        */
+       *name = n;
+       *email = n;
+}
+
+static void prepare_commit(void)
+{
+       char *author_name, *author_email;
+       char *src_branch;
+
+       lookup_author(author, &author_name, &author_email);
+
+       printf("export GIT_COMMITTER_NAME=%s\n", author_name);
+       printf("export GIT_COMMITTER_EMAIL=%s\n", author_email);
+       printf("export GIT_COMMITTER_DATE='+0000 %s'\n", date);
+
+       printf("export GIT_AUTHOR_NAME=%s\n", author_name);
+       printf("export GIT_AUTHOR_EMAIL=%s\n", author_email);
+       printf("export GIT_AUTHOR_DATE='+0000 %s'\n", date);
+
+       if (initial_commit)
+               return;
+
+       src_branch = *ancestor ? ancestor : branch;
+       if (!strcmp(src_branch, "HEAD"))
+               src_branch = "master";
+       printf("ln -sf refs/heads/'%s' .git/HEAD\n", src_branch);
+
+       /*
+        * Even if cvsps claims an ancestor, we'll let the new
+        * branch name take precedence if it already exists
+        */
+       if (*ancestor) {
+               src_branch = branch;
+               if (!strcmp(src_branch, "HEAD"))
+                       src_branch = "master";
+               printf("[ -e .git/refs/heads/'%s' ] && ln -sf refs/heads/'%s' .git/HEAD\n",
+                       src_branch, src_branch);
+       }
+
+       printf("git-read-tree -m HEAD || exit 1\n");
+       printf("git-checkout-cache -f -u -a\n");
+}
+
+static void commit(void)
+{
+       const char *cmit_parent = initial_commit ? "" : "-p HEAD";
+       const char *dst_branch;
+       char *space;
+       int i;
+
+       printf("tree=$(git-write-tree)\n");
+       printf("cat > .cmitmsg <<EOFMSG\n");
+
+       /* Escape $ characters, and remove control characters */
+       for (i = 0; i < loglen; i++) {
+               unsigned char c = log[i];
+
+               switch (c) {
+               case '$':
+               case '\\':
+               case '`':
+                       putchar('\\');
+                       break;
+               case 0 ... 31:
+                       if (c == '\n' || c == '\t')
+                               break;
+               case 128 ... 159:
+                       continue;
+               }
+               putchar(c);
+       }
+       printf("\nEOFMSG\n");
+       printf("commit=$(cat .cmitmsg | git-commit-tree $tree %s)\n", cmit_parent);
+
+       dst_branch = branch;
+       if (!strcmp(dst_branch, "HEAD"))
+               dst_branch = "master";
+
+       printf("echo $commit > .git/refs/heads/'%s'\n", dst_branch);
+
+       space = strchr(tag, ' ');
+       if (space)
+               *space = 0;
+       if (strcmp(tag, "(none)"))
+               printf("echo $commit > .git/refs/tags/'%s'\n", tag);
+
+       printf("echo 'Committed (to %s):' ; cat .cmitmsg; echo\n", dst_branch);
+
+       *date = 0;
+       *author = 0;
+       *branch = 0;
+       *ancestor = 0;
+       *tag = 0;
+       loglen = 0;
+
+       initial_commit = 0;
+}
+
+static void update_file(char *line)
+{
+       char *name, *version;
+       char *dir;
+
+       while (isspace(*line))
+               line++;
+       name = line;
+       line = strchr(line, ':');
+       if (!line)
+               return;
+       *line++ = 0;
+       line = strchr(line, '>');
+       if (!line)
+               return;
+       *line++ = 0;
+       version = line;
+       line = strchr(line, '(');
+       if (line) {     /* "(DEAD)" */
+               printf("git-update-cache --force-remove '%s'\n", name);
+               return;
+       }
+
+       dir = strrchr(name, '/');
+       if (dir)
+               printf("mkdir -p %.*s\n", (int)(dir - name), name);
+
+       printf("cvs -q -d %s checkout -d .git-tmp -r%s '%s/%s'\n", 
+               cvsroot, version, cvsmodule, name);
+       printf("mv -f .git-tmp/%s %s\n", dir ? dir+1 : name, name);
+       printf("rm -rf .git-tmp\n");
+       printf("git-update-cache --add -- '%s'\n", name);
+}
+
+struct hdrentry {
+       const char *name;
+       char *dest;
+} hdrs[] = {
+       { "Date:", date },
+       { "Author:", author },
+       { "Branch:", branch },
+       { "Ancestor branch:", ancestor },
+       { "Tag:", tag },
+       { "Log:", NULL },
+       { NULL, NULL }
+};
+
+int main(int argc, char **argv)
+{
+       static char line[1000];
+       enum state state = Header;
+       int i;
+
+       for (i = 1; i < argc; i++) {
+               const char *arg = argv[i];
+               if (!memcmp(arg, "--cvsroot=", 10)) {
+                       cvsroot = arg + 10;
+                       continue;
+               }
+               if (!memcmp(arg, "--module=", 9)) {
+                       cvsmodule = arg+9;
+                       continue;
+               } 
+               if (!strcmp(arg, "-v")) {
+                       verbose = 1;
+                       continue;
+               }
+               if (!strcmp(arg, "-u")) {
+                       initial_commit = 0;
+                       continue;
+               }
+       }
+
+
+       if (!cvsroot)
+               cvsroot = getenv("CVSROOT");
+
+       if (!cvsmodule || !cvsroot) {
+               fprintf(stderr, "I need a CVSROOT and module name\n");
+               exit(1);
+       }
+
+       if (initial_commit) {
+               printf("[ -d .git ] && exit 1\n");
+                   printf("git-init-db\n");
+               printf("mkdir -p .git/refs/heads\n");
+               printf("mkdir -p .git/refs/tags\n");
+               printf("ln -sf refs/heads/master .git/HEAD\n");
+       }
+
+       while (fgets(line, sizeof(line), stdin) != NULL) {
+               int linelen = strlen(line);
+
+               while (linelen && isspace(line[linelen-1]))
+                       line[--linelen] = 0;
+
+               switch (state) {
+               struct hdrentry *entry;
+
+               case Header:
+                       if (verbose)
+                               printf("# H: %s\n", line);
+                       for (entry = hdrs ; entry->name ; entry++) {
+                               int len = strlen(entry->name);
+                               char *val;
+
+                               if (memcmp(entry->name, line, len))
+                                       continue;
+                               if (!entry->dest) {
+                                       state = Log;
+                                       break;
+                               }
+                               val = line + len;
+                               linelen -= len;
+                               while (isspace(*val)) {
+                                       val++;
+                                       linelen--;
+                               }
+                               memcpy(entry->dest, val, linelen+1);
+                               break;
+                       }
+                       continue;
+
+               case Log:
+                       if (verbose)
+                               printf("# L: %s\n", line);
+                       if (!strcmp(line, "Members:")) {
+                               while (loglen && isspace(log[loglen-1]))
+                                       log[--loglen] = 0;
+                               prepare_commit();
+                               state = Members;
+                               continue;
+                       }
+                               
+                       if (loglen + linelen + 5 > sizeof(log))
+                               continue;
+                       memcpy(log + loglen, line, linelen);
+                       loglen += linelen;
+                       log[loglen++] = '\n';
+                       continue;
+
+               case Members:
+                       if (verbose)
+                               printf("# M: %s\n", line);
+                       if (!linelen) {
+                               commit();
+                               state = Header;
+                               continue;
+                       }
+                       update_file(line);
+                       continue;
+               }
+       }
+       return 0;
+}
diff --git a/date.c b/date.c
new file mode 100644 (file)
index 0000000..ff922fe
--- /dev/null
+++ b/date.c
@@ -0,0 +1,455 @@
+/*
+ * 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, 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(char *date, struct tm *tm, int *offset)
+{
+       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))
+                       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(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(char *date, char *result, int maxlen)
+{
+       struct tm tm;
+       int offset, sign;
+       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;
+
+       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);
+               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;
+
+       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/delta.c b/delta.c
new file mode 100644 (file)
index 0000000..db85989
--- /dev/null
+++ b/delta.c
@@ -0,0 +1,114 @@
+#include "object.h"
+#include "blob.h"
+#include "tree.h"
+#include "commit.h"
+#include "tag.h"
+#include "delta.h"
+#include "cache.h"
+
+/* the delta object definition (it can alias any other object) */
+struct delta {
+       union {
+               struct object object;
+               struct blob blob;
+               struct tree tree;
+               struct commit commit;
+               struct tag tag;
+       } u;
+};
+
+struct delta *lookup_delta(const unsigned char *sha1)
+{
+       struct object *obj = lookup_object(sha1);
+       if (!obj) {
+               struct delta *ret = xmalloc(sizeof(struct delta));
+               memset(ret, 0, sizeof(struct delta));
+               created_object(sha1, &ret->u.object);
+               return ret;
+       }
+       return (struct delta *) obj;
+}
+
+int parse_delta_buffer(struct delta *item, void *buffer, unsigned long size)
+{
+       struct object *reference;
+       struct object_list *p;
+
+       if (item->u.object.delta)
+               return 0;
+       item->u.object.delta = 1;
+       if (size <= 20)
+               return -1;
+       reference = lookup_object(buffer);
+       if (!reference) {
+               struct delta *ref = xmalloc(sizeof(struct delta));
+               memset(ref, 0, sizeof(struct delta));
+               created_object(buffer, &ref->u.object);
+               reference = &ref->u.object;
+       }
+
+       p = xmalloc(sizeof(*p));
+       p->item = &item->u.object;
+       p->next = reference->attached_deltas;
+       reference->attached_deltas = p;
+       return 0;
+}
+
+int process_deltas(void *src, unsigned long src_size, const char *src_type,
+                  struct object_list *delta_list)
+{
+       int deepest = 0;
+       do {
+               struct object *obj = delta_list->item;
+               static char type[10];
+               void *map, *delta, *buf;
+               unsigned long map_size, delta_size, buf_size;
+               map = map_sha1_file(obj->sha1, &map_size);
+               if (!map)
+                       continue;
+               delta = unpack_sha1_file(map, map_size, type, &delta_size);
+               munmap(map, map_size);
+               if (!delta)
+                       continue;
+               if (strcmp(type, "delta") || delta_size <= 20) {
+                       free(delta);
+                       continue;
+               }
+               buf = patch_delta(src, src_size,
+                                 delta+20, delta_size-20,
+                                 &buf_size);
+               free(delta);
+               if (!buf)
+                       continue;
+               if (check_sha1_signature(obj->sha1, buf, buf_size, src_type) < 0)
+                       printf("sha1 mismatch for delta %s\n", sha1_to_hex(obj->sha1));
+               if (obj->type && obj->type != src_type) {
+                       error("got %s when expecting %s for delta %s",
+                             src_type, obj->type, sha1_to_hex(obj->sha1));
+                       free(buf);
+                       continue;
+               }
+               obj->type = src_type;
+               if (src_type == blob_type) {
+                       parse_blob_buffer((struct blob *)obj, buf, buf_size);
+               } else if (src_type == tree_type) {
+                       parse_tree_buffer((struct tree *)obj, buf, buf_size);
+               } else if (src_type == commit_type) {
+                       parse_commit_buffer((struct commit *)obj, buf, buf_size);
+               } else if (src_type == tag_type) {
+                       parse_tag_buffer((struct tag *)obj, buf, buf_size);
+               } else {
+                       error("unknown object type %s", src_type);
+                       free(buf);
+                       continue;
+               }
+               if (obj->attached_deltas) {
+                       int depth = process_deltas(buf, buf_size, src_type,
+                                                  obj->attached_deltas);
+                       if (deepest < depth)
+                               deepest = depth;
+               }
+               free(buf);
+       } while ((delta_list = delta_list->next));
+       return deepest + 1;
+}
diff --git a/delta.h b/delta.h
new file mode 100644 (file)
index 0000000..df97ff8
--- /dev/null
+++ b/delta.h
@@ -0,0 +1,21 @@
+#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);
+extern void *patch_delta(void *src_buf, unsigned long src_size,
+                        void *delta_buf, unsigned long delta_size,
+                        unsigned long *dst_size);
+
+/* handling of delta objects */
+struct delta;
+struct object_list;
+extern struct delta *lookup_delta(const unsigned char *sha1);
+extern int parse_delta_buffer(struct delta *item, void *buffer, unsigned long size);
+extern int parse_delta(struct delta *item, unsigned char sha1);
+extern int process_deltas(void *src, unsigned long src_size,
+                         const char *src_type, struct object_list *delta);
+
+#endif
diff --git a/diff-cache.c b/diff-cache.c
new file mode 100644 (file)
index 0000000..603a6b7
--- /dev/null
@@ -0,0 +1,282 @@
+#include "cache.h"
+#include "diff.h"
+
+static int cached_only = 0;
+static int diff_output_format = DIFF_FORMAT_HUMAN;
+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)
+{
+       while (entries) {
+               struct cache_entry *ce = *ac;
+               int same = (entries > 1) && ce_same_name(ce, ac[1]);
+
+               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");
+               }
+
+               /*
+                * 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 char *diff_cache_usage =
+"git-diff-cache [-p] [-r] [-z] [-m] [--cached] [-R] [-B] [-M] [-C] [--find-copies-harder] [-O<orderfile>] [-S<string>] [--pickaxe-all] <tree-ish> [<path>...]";
+
+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;
+               }
+               if (!strcmp(arg, "-p")) {
+                       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_output_format = DIFF_FORMAT_MACHINE;
+                       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))
+               die("unable to read tree object %s", tree_name);
+
+       ret = diff_cache(active_cache, active_nr);
+
+       diffcore_std(pathspec ? : NULL,
+                    detect_rename, diff_score_opt,
+                    pickaxe, pickaxe_opts,
+                    diff_break_opt,
+                    orderfile, diff_filter);
+       diff_flush(diff_output_format);
+       return ret;
+}
diff --git a/diff-delta.c b/diff-delta.c
new file mode 100644 (file)
index 0000000..480f03c
--- /dev/null
@@ -0,0 +1,333 @@
+/*
+ * 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)
+{
+       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 */
+       orig = out + outpos++;
+       *orig = i = 0;
+       do {
+               if (from_size & 0xff) {
+                       *orig |= (1 << i);
+                       out[outpos++] = from_size;
+               }
+               i++;
+               from_size >>= 8;
+       } while (from_size);
+
+       /* store target buffer size */
+       orig = out + outpos++;
+       *orig = i = 0;
+       do {
+               if (to_size & 0xff) {
+                       *orig |= (1 << i);
+                       out[outpos++] = to_size;
+               }
+               i++;
+               to_size >>= 8;
+       } while (to_size);
+
+       inscnt = 0;
+       moff = 0;
+       while (data < top) {
+               msize = 0;
+               fp = adler32(0, data, MIN(top - data, BLK_SIZE));
+               i = HASH(fp, bdf.fphbits);
+               for (brec = bdf.fphash[i]; brec; brec = brec->next) {
+                       if (brec->fp == fp) {
+                               csize = 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;
+               }
+
+               /* 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..4d60017
--- /dev/null
@@ -0,0 +1,145 @@
+/*
+ * 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 [-p] [-q] [-r] [-z] [-R] [-B] [-M] [-C] [--find-copies-harder] [-O<orderfile>] [-S<string>] [--pickaxe-all] [<path>...]";
+
+static int diff_output_format = DIFF_FORMAT_HUMAN;
+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, };
+       int entries = read_cache();
+       int i;
+
+       while (1 < argc && argv[1][0] == '-') {
+               if (!strcmp(argv[1], "-p"))
+                       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_output_format = DIFF_FORMAT_MACHINE;
+               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--;
+       }
+
+       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_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((1 < argc) ? argv + 1 : NULL,
+                    detect_rename, diff_score_opt,
+                    pickaxe, pickaxe_opts,
+                    diff_break_opt,
+                    orderfile, diff_filter);
+       diff_flush(diff_output_format);
+       return 0;
+}
diff --git a/diff-helper.c b/diff-helper.c
new file mode 100644 (file)
index 0000000..63aff69
--- /dev/null
@@ -0,0 +1,153 @@
+/*
+ * 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);
+}
+
+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 == 'R' || status == 'C')
+                               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..9d8cc73
--- /dev/null
@@ -0,0 +1,126 @@
+/*
+ * Copyright (c) 2005 Junio C Hamano
+ */
+
+#include "cache.h"
+#include "diff.h"
+
+static int diff_output_format = DIFF_FORMAT_HUMAN;
+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 char *diff_stages_usage =
+"git-diff-stages [-p] [-r] [-z] [-R] [-B] [-M] [-C] [--find-copies-harder] [-O<orderfile>] [-S<string>] [--pickaxe-all] <stage1> <stage2> [<path>...]";
+
+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"))
+                       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_output_format = DIFF_FORMAT_MACHINE;
+               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);
+       return 0;
+}
diff --git a/diff-tree.c b/diff-tree.c
new file mode 100644 (file)
index 0000000..7446e09
--- /dev/null
@@ -0,0 +1,571 @@
+#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_HUMAN;
+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);
+               return 0;
+       }
+       if (header) {
+               const char *fmt = "%s";
+               if (diff_output_format == DIFF_FORMAT_MACHINE)
+                       fmt = "%s%c";
+               
+               printf(fmt, header, 0);
+               header = NULL;
+       }
+       diff_flush(diff_output_format);
+       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 char *generate_header(const char *commit, const char *parent, const char *msg, unsigned long len)
+{
+       static char this_header[16384];
+       int offset;
+
+       offset = sprintf(this_header, "%s%s (from %s)\n", header_prefix, commit, parent);
+       if (verbose_header) {
+               offset += pretty_print_commit(commit_format, msg, len, this_header + offset, sizeof(this_header) - offset);
+               this_header[offset++] = '\n';
+               this_header[offset++] = 0;
+       }
+
+       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 char *diff_tree_usage =
+"git-diff-tree [-p] [-r] [-z] [--stdin] [-m] [-s] [-v] [--pretty] [-t] [-R] [-B] [-M] [-C] [--find-copies-header] [-O<orderfile>] [-S<string>] [--pickaxe-all] <tree-ish> <tree-ish>";
+
+static 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;
+       usage(diff_tree_usage);
+}
+
+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")) {
+                       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, "-z")) {
+                       diff_output_format = DIFF_FORMAT_MACHINE;
+                       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..5cb340c
--- /dev/null
+++ b/diff.c
@@ -0,0 +1,1221 @@
+/*
+ * Copyright (C) 2005 Junio C Hamano
+ */
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <signal.h>
+#include "cache.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;
+}
+
+/* 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_expand     result
+ *  name     ==> name      ==> 'name'
+ *  a b      ==> a b       ==> 'a b'
+ *  a'b      ==> a'\''b    ==> 'a'\''b'
+ */
+static char *sq_expand(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 = 1, cp = src; *cp; cnt++, cp++)
+               if (*cp == '\'')
+                       cnt += 3;
+
+       buf = xmalloc(cnt);
+       bp = buf;
+       while ((c = *src++)) {
+               if (c != '\'')
+                       *bp++ = c;
+               else {
+                       bp = strcpy(bp, "'\\''");
+                       bp += 4;
+               }
+       }
+       *bp = 0;
+       return buf;
+}
+
+static struct diff_tempfile {
+       const char *name; /* filename external diff should read from */
+       char hex[41];
+       char mode[10];
+       char tmp_path[50];
+} 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 *diff_cmd = "diff -L'%s%s' -L'%s%s'";
+       const char *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_expand(name_a);
+       name_sq[1] = sq_expand(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_expand(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);
+               s->should_munmap = 1;
+               close(fd);
+       }
+       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_file_size(s->sha1, &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;
+
+       strcpy(temp->tmp_path, ".diff_XXXXXX");
+       fd = mkstemp(temp->tmp_path);
+       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 'C':
+               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 'R':
+               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 'M':
+               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 *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 'C': case 'R':
+               two_paths = 1;
+               break;
+       case 'N': case 'D':
+               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);
+}
+
+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 = 'U';
+               else if (!DIFF_FILE_VALID(p->one))
+                       p->status = 'N';
+               else if (!DIFF_FILE_VALID(p->two))
+                       p->status = 'D';
+               else if (DIFF_PAIR_TYPE_CHANGED(p))
+                       p->status = 'T';
+
+               /* 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 = 'C';
+                               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 = 'C';
+                               break;
+                       }
+                       if (!p->status)
+                               p->status = 'R';
+               }
+               else if (memcmp(p->one->sha1, p->two->sha1, 20) ||
+                        p->one->mode != p->two->mode)
+                       p->status = 'M';
+               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 = 'X';
+               }
+       }
+       diff_debug_queue("resolve-rename-copy done", q);
+}
+
+void diff_flush(int diff_output_style)
+{
+       struct diff_queue_struct *q = &diff_queued_diff;
+       int i;
+       int line_termination = '\n';
+       int inter_name_termination = '\t';
+
+       if (diff_output_style == DIFF_FORMAT_MACHINE)
+               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 == 'X'))
+                       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_HUMAN:
+               case DIFF_FORMAT_MACHINE:
+                       diff_flush_raw(p, line_termination,
+                                      inter_name_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, 'A')) {
+               /* All-or-none */
+               int found;
+               for (i = found = 0; !found && i < q->nr; i++) {
+                       struct diff_filepair *p = q->queue[i];
+                       if (((p->status == 'M') &&
+                            ((p->score && strchr(filter, 'B')) ||
+                             (!p->score && strchr(filter, 'M')))) ||
+                           ((p->status != 'M') && 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 == 'M') &&
+                            ((p->score && strchr(filter, 'B')) ||
+                             (!p->score && strchr(filter, 'M')))) ||
+                           ((p->status != 'M') && 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..9f0852d
--- /dev/null
+++ b/diff.h
@@ -0,0 +1,65 @@
+/*
+ * 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);
+
+extern int diff_queue_is_empty(void);
+
+#define DIFF_FORMAT_HUMAN      0
+#define DIFF_FORMAT_MACHINE    1
+#define DIFF_FORMAT_PATCH      2
+#define DIFF_FORMAT_NO_OUTPUT  3
+
+extern void diff_flush(int output_style);
+
+#endif /* DIFF_H */
diff --git a/diffcore-break.c b/diffcore-break.c
new file mode 100644 (file)
index 0000000..920062b
--- /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);
+
+       /* 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..a03862c
--- /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 (-1 == (int)(long)map)
+               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..4c26b42
--- /dev/null
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2005 Junio C Hamano
+ */
+#include "cache.h"
+#include "diff.h"
+#include "diffcore.h"
+
+static int contains(struct diff_filespec *one,
+                   const char *needle, unsigned long len)
+{
+       unsigned long offset, sz;
+       const char *data;
+       if (diff_populate_filespec(one, 0))
+               return 0;
+       sz = one->size;
+       data = one->data;
+       for (offset = 0; offset + len <= sz; offset++)
+                    if (!strncmp(needle, data + offset, len))
+                            return 1;
+       return 0;
+}
+
+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..8fb45f0
--- /dev/null
@@ -0,0 +1,411 @@
+/*
+ * 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;
+       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 = diff_delta(src->data, src->size,
+                          dst->data, dst->size,
+                          &delta_size);
+
+       /* 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..874516e
--- /dev/null
+++ b/entry.c
@@ -0,0 +1,160 @@
+#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, 0755)) {
+                       if (errno == EEXIST) {
+                               struct stat st;
+                               if (len > state->base_dir_len && state->force && !unlink(buf) && !mkdir(buf, 0755))
+                                       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, int force)
+{
+       int fd;
+
+       mode = (mode & 0100) ? 0777 : 0666;
+       fd = open(path, O_WRONLY | O_TRUNC | O_CREAT, mode);
+       if (fd < 0) {
+               if (errno == EISDIR && force) {
+                       remove_subtree(path);
+                       fd = open(path, O_WRONLY | O_TRUNC | O_CREAT, mode);
+               }
+       }
+       return fd;
+}
+
+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), state->force);
+               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);
+       } 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..35756a3
--- /dev/null
+++ b/epoch.c
@@ -0,0 +1,656 @@
+/*
+ * 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()
+{
+       if (!context) {
+               context = BN_CTX_new();
+       }
+       return context;
+}
+
+static struct fraction *new_zero()
+{
+       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()
+{
+       if (!one) {
+               one = new_zero();
+               BN_one(&one->numerator);
+       }
+       return one;
+}
+
+static struct fraction *get_zero()
+{
+       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(&pending, parent);
+                                       commit_list_insert(parent, &cleaner);
+                               } else {
+                                       if (!compare(&parent_node->pending, get_zero()))
+                                               insert_by_date(&pending, parent);
+                                       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;
+       struct commit_list *reversed_parents = NULL;
+
+       head->object.flags |= VISITED;
+
+       /*
+        * parse_commit() builds the parent list in reverse order with respect
+        * to the order of the git-commit-tree arguments. So we need to reverse
+        * this list to output the oldest (or most "local") commits last.
+        */
+       for (parents = head->parents; parents; parents = parents->next)
+               commit_list_insert(parents->item, &reversed_parents);
+
+       /*
+        * 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.
+        */
+
+       while (reversed_parents) {
+               struct commit *parent = pop_commit(&reversed_parents);
+
+               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 (reversed_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)
+{
+       unsigned int seen = 0;
+       int action = CONTINUE;
+
+       while (*stack && (action != STOP)) {
+               struct commit *next = pop_commit(stack);
+               seen |= next->object.flags;
+               if (*stack)
+                       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);
+                       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) {
+               struct commit *next = list->item;
+
+               if (!(next->object.flags & UNINTERESTING)) {
+                       if (next->object.flags & DUPCHECK) {
+                               fprintf(stderr, "%s: duplicate commit %s ignored\n",
+                                       __FUNCTION__, sha1_to_hex(next->object.sha1));
+                       } else {
+                               next->object.flags |= DUPCHECK;
+                               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)) {
+                               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);
+       }
+
+       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..0c1385a
--- /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);
+
+#define UNINTERESTING   (1u<<2)
+#define BOUNDARY        (1u<<3)
+#define VISITED         (1u<<4)
+#define DISCONTINUITY   (1u<<5)
+#define DUPCHECK        (1u<<6)
+#define LAST_EPOCH_FLAG (1u<<6)
+
+
+#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/fsck-cache.c b/fsck-cache.c
new file mode 100644 (file)
index 0000000..d69c426
--- /dev/null
@@ -0,0 +1,504 @@
+#include <sys/types.h>
+#include <dirent.h>
+
+#include "cache.h"
+#include "commit.h"
+#include "tree.h"
+#include "blob.h"
+#include "tag.h"
+#include "delta.h"
+
+#define REACHABLE 0x0001
+
+static int show_root = 0;
+static int show_tags = 0;
+static int show_unreachable = 0;
+static int show_max_delta_depth = 0;
+static int keep_cache_objects = 0; 
+static unsigned char head_sha1[20];
+
+static void expand_deltas(void)
+{
+       int i, max_depth = 0;
+
+       /*
+        * To be as efficient as possible we look for delta heads and
+        * recursively process them going backward, and parsing
+        * resulting objects along the way.  This allows for processing
+        * each delta objects only once regardless of the delta depth.
+        */
+       for (i = 0; i < nr_objs; i++) {
+               struct object *obj = objs[i];
+               if (obj->parsed && !obj->delta && obj->attached_deltas) {
+                       int depth = 0;
+                       char type[10];
+                       unsigned long size;
+                       void *buf = read_sha1_file(obj->sha1, type, &size);
+                       if (!buf)
+                               continue;
+                       depth = process_deltas(buf, size, obj->type,
+                                              obj->attached_deltas);
+                       if (max_depth < depth)
+                               max_depth = depth;
+               }
+       }
+       if (show_max_delta_depth)
+               printf("maximum delta depth = %d\n", max_depth);
+}
+                                                                                                                       
+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 (obj->delta)
+                               printf("unresolved delta %s\n",
+                                      sha1_to_hex(obj->sha1));
+                       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)
+                               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));
+               }
+
+               /* Don't bother with tag reachability. */
+               if (obj->type == tag_type)
+                       continue;
+
+               if (show_unreachable && !(obj->flags & REACHABLE)) {
+                       if (obj->attached_deltas)
+                               printf("foreign delta reference %s\n", 
+                                      sha1_to_hex(obj->sha1));
+                       else
+                               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 has_full_path = 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;
+
+               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:
+                       break;
+               default:
+                       printf("tree %s has entry %o %s\n",
+                               sha1_to_hex(item->object.sha1),
+                               entry->mode, entry->name);
+               }
+
+               if (last) {
+                       switch (verify_ordered(last, entry)) {
+                       case TREE_UNORDERED:
+                               fprintf(stderr, "tree %s not ordered\n",
+                                       sha1_to_hex(item->object.sha1));
+                               return -1;
+                       case TREE_HAS_DUPS:
+                               fprintf(stderr, "tree %s has duplicate entries for '%s'\n",
+                                       sha1_to_hex(item->object.sha1),
+                                       entry->name);
+                               return -1;
+                       default:
+                               break;
+                       }
+               }
+
+               last = entry;
+       }
+
+       if (has_full_path) {
+               fprintf(stderr, "warning: git-fsck-cache: tree %s "
+                       "has full pathnames in it\n", 
+                       sha1_to_hex(item->object.sha1));
+       }
+
+       return 0;
+}
+
+static int fsck_commit(struct commit *commit)
+{
+       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);
+       if (!obj->type && obj->delta)
+               return 0;
+       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 read_sha1_reference(const char *path)
+{
+       char hexname[60];
+       unsigned char sha1[20];
+       int fd = open(path, O_RDONLY), len;
+       struct object *obj;
+
+       if (fd < 0)
+               return -1;
+
+       len = read(fd, hexname, sizeof(hexname));
+       close(fd);
+       if (len < 40)
+               return -1;
+
+       if (get_sha1_hex(hexname, sha1) < 0)
+               return -1;
+
+       obj = lookup_object(sha1);
+       if (!obj)
+               return error("%s: invalid sha1 pointer %.40s", path, hexname);
+
+       obj->used = 1;
+       mark_reachable(obj, REACHABLE);
+       return 0;
+}
+
+static int find_file_objects(const char *base, const char *name)
+{
+       int baselen = strlen(base);
+       int namelen = strlen(name);
+       char *path = xmalloc(baselen + namelen + 2);
+       struct stat st;
+
+       memcpy(path, base, baselen);
+       path[baselen] = '/';
+       memcpy(path + baselen + 1, name, namelen+1);
+       if (stat(path, &st) < 0)
+               return 0;
+
+       /*
+        * Recurse into directories
+        */
+       if (S_ISDIR(st.st_mode)) {
+               int count = 0;
+               DIR *dir = opendir(path);
+               if (dir) {
+                       struct dirent *de;
+                       while ((de = readdir(dir)) != NULL) {
+                               if (de->d_name[0] == '.')
+                                       continue;
+                               count += find_file_objects(path, de->d_name);
+                       }
+                       closedir(dir);
+               }
+               return count;
+       }
+       if (S_ISREG(st.st_mode))
+               return read_sha1_reference(path) == 0;
+       return 0;
+}
+
+static void get_default_heads(void)
+{
+       char *git_dir = gitenv(GIT_DIR_ENVIRONMENT) ? : DEFAULT_GIT_DIR_ENVIRONMENT;
+       int count = find_file_objects(git_dir, "refs");
+       if (!count)
+               die("No default references");
+}
+
+int main(int argc, char **argv)
+{
+       int i, heads;
+       char *sha1_dir;
+
+       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, "--delta-depth")) {
+                       show_max_delta_depth = 1;
+                       continue;
+               }
+               if (!strcmp(arg, "--cache")) {
+                       keep_cache_objects = 1;
+                       continue;
+               }
+               if (*arg == '-')
+                       usage("git-fsck-cache [--tags] [[--unreachable] [--cache] <head-sha1>*]");
+       }
+
+       sha1_dir = get_object_directory();
+       for (i = 0; i < 256; i++) {
+               static char dir[4096];
+               sprintf(dir, "%s/%02x", sha1_dir, i);
+               fsck_dir(i, dir);
+       }
+       fsck_sha1_list();
+
+       expand_deltas();
+
+       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-checkout-script b/git-checkout-script
new file mode 100755 (executable)
index 0000000..4b3ae4a
--- /dev/null
@@ -0,0 +1,51 @@
+#!/bin/sh
+: ${GIT_DIR=.git}
+old=$(git-rev-parse HEAD)
+new=
+force=
+branch=
+while [ "$#" != "0" ]; do
+    arg="$1"
+    shift
+    case "$arg" in
+       "-f")
+               force=1
+               ;;
+       *)
+               rev=$(git-rev-parse "$arg")
+               if [ -z "$rev" ]; then
+                       echo "unknown flag $arg"
+                       exit 1
+               fi
+               if [ "$new" ]; then
+                       echo "Multiple revisions?"
+                       exit 1
+               fi
+               new="$rev"
+               if [ -f "$GIT_DIR/refs/heads/$arg" ]; then
+                       branch="$arg"
+               fi
+               ;;
+    esac
+    i=$(($i+1))
+done
+[ -z "$new" ] && new=$old
+
+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
+       [ "$branch" ] && ln -sf "refs/heads/$branch" "$GIT_DIR/HEAD"
+       rm -f "$GIT_DIR/MERGE_HEAD"
+fi
diff --git a/git-commit-script b/git-commit-script
new file mode 100755 (executable)
index 0000000..57f5333
--- /dev/null
@@ -0,0 +1,49 @@
+#!/bin/sh
+: ${GIT_DIR=.git}
+if [ ! -d $GIT_DIR ]; then
+       echo Not a git directory 1>&2
+       exit 1
+fi
+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"
+               echo "#"
+               git-ls-files | sed 's/^/# New file: /'
+               echo "#"
+       ) > .editmsg
+       PARENTS=""
+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"
+               echo "#"
+               PARENTS="-p HEAD -p MERGE_HEAD"
+       fi > .editmsg
+       git-status-script >> .editmsg
+fi
+if [ "$?" != "0" ]
+then
+       cat .editmsg
+       rm .editmsg
+       exit 1
+fi
+${VISUAL:-${EDITOR:-vi}} .editmsg
+grep -v '^#' < .editmsg | git-stripspace > .cmitmsg
+[ -s .cmitmsg ] && 
+       tree=$(git-write-tree) &&
+       commit=$(cat .cmitmsg | git-commit-tree $tree $PARENTS) &&
+       echo $commit > $GIT_DIR/HEAD &&
+       rm -f -- $GIT_DIR/MERGE_HEAD
+ret="$?"
+rm -f .cmitmsg .editmsg
+exit "$ret"
diff --git a/git-cvsimport-script b/git-cvsimport-script
new file mode 100755 (executable)
index 0000000..0ba6746
--- /dev/null
@@ -0,0 +1,38 @@
+#!/bin/sh
+
+usage () {
+       echo "Usage: git cvsimport [-v] [-z fuzz] <cvsroot> <module>"
+       exit 1
+}
+
+CVS2GIT=""
+CVSPS="--cvs-direct -x -A"
+while true; do
+       case "$1" in
+       -v) CVS2GIT="$1" ;;
+       -z) shift; CVSPS="$CVSPS -z $1" ;;
+       -*) usage ;;
+       *)  break ;;
+       esac
+       shift
+done
+
+export CVSROOT="$1"
+export MODULE="$2"
+if [ ! "$CVSROOT" ] || [ ! "$MODULE" ] ; then
+       usage
+fi
+
+cvsps -h 2>&1 | grep -q "cvsps version 2.1" >& /dev/null || {
+       echo "I need cvsps version 2.1"
+       exit 1
+}
+
+mkdir "$MODULE" || exit 1
+cd "$MODULE"
+
+TZ=UTC cvsps $CVSPS $MODULE > .git-cvsps-result
+[ -s .git-cvsps-result ] || exit 1
+git-cvs2git $CVS2GIT --cvsroot="$CVSROOT" --module="$MODULE" < .git-cvsps-result > .git-create-script || exit 1
+sh .git-create-script
+
diff --git a/git-deltafy-script b/git-deltafy-script
new file mode 100755 (executable)
index 0000000..476d879
--- /dev/null
@@ -0,0 +1,81 @@
+#!/bin/bash
+
+# Example script to deltify an entire GIT repository based on the commit list.
+# The most recent version of a file is the reference and previous versions
+# are made delta against the best earlier version available. And so on for
+# successive versions going back in time.  This way the increasing delta
+# overhead is pushed towards older versions of any given file.
+#
+# The -d argument allows to provide a limit on the delta chain depth.
+# If 0 is passed then everything is undeltafied.  Limiting the delta
+# depth is meaningful for subsequent access performance to old revisions.
+# A value of 16 might be a good compromize between performance and good
+# space saving.  Current default is unbounded.
+#
+# The --max-behind=30 argument is passed to git-mkdelta so to keep
+# combinations and memory usage bounded a bit.  If you have lots of memory
+# and CPU power you may remove it (or set to 0) to let git-mkdelta find the
+# best delta match regardless of the number of revisions for a given file.
+# You can also make the value smaller to make it faster and less
+# memory hungry.  A value of 5 ought to still give pretty good results.
+# When set to 0 or ommitted then look behind is unbounded.  Note that
+# git-mkdelta might die with a segmentation fault in that case if it
+# runs out of memory.  Note that the GIT repository will still be consistent
+# even if git-mkdelta dies unexpectedly.
+
+set -e
+
+max_depth=
+[ "$1" == "-d" ] && max_depth="--max-depth=$2" && shift 2
+
+overlap=30
+max_behind="--max-behind=$overlap"
+
+function process_list() {
+       if [ "$list" ]; then
+               echo "Processing $curr_file"
+               echo "$list" | xargs git-mkdelta $max_depth $max_behind -v
+       fi
+}
+
+rev_list=""
+curr_file=""
+
+git-rev-list HEAD |
+while true; do
+       # Let's batch revisions into groups of 1000 to give it a chance to
+       # scale with repositories containing long revision lists.  We also
+       # overlap with the previous batch the size of mkdelta's look behind
+       # value in order to account for the processing discontinuity.
+       rev_list="$(echo -e -n "$rev_list" | tail --lines=$overlap)"
+       for i in $(seq 1000); do
+               read rev || break
+               rev_list="$rev_list$rev\n"
+       done
+       echo -e -n "$rev_list" |
+       git-diff-tree -r -t --stdin |
+       awk '/^:/ { if ($5 == "M") printf "%s %s\n%s %s\n", $4, $6, $3, $6 }' |
+       LC_ALL=C sort -s -k 2 | uniq |
+       while read sha1 file; do
+               if [ "$file" == "$curr_file" ]; then
+                       list="$list $sha1"
+               else
+                       process_list
+                       curr_file="$file"
+                       list="$sha1"
+               fi
+       done
+       [ "$rev" ] || break
+done
+process_list
+
+curr_file="root directory"
+list="$(
+       git-rev-list HEAD |
+       while read commit; do
+               git-cat-file commit $commit |
+               sed -n 's/tree //p;Q'
+       done
+       )"
+process_list
+
diff --git a/git-diff-script b/git-diff-script
new file mode 100755 (executable)
index 0000000..673853b
--- /dev/null
@@ -0,0 +1,16 @@
+#!/bin/sh
+rev=($(git-rev-parse --revs-only "$@"))
+flags=($(git-rev-parse --no-revs "$@"))
+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;;
+*)
+       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..2e62f00
--- /dev/null
@@ -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"/FETCH_HEAD || exit 1
+
+echo "Getting object database"
+download_objects "$merge_repo" "$(cat "$GIT_DIR"/FETCH_HEAD)" || exit 1
diff --git a/git-log-script b/git-log-script
new file mode 100755 (executable)
index 0000000..feca5e9
--- /dev/null
@@ -0,0 +1,2 @@
+#!/bin/sh
+git-rev-list --pretty $(git-rev-parse --default HEAD "$@") | LESS=-S ${PAGER:-less}
diff --git a/git-merge-one-file-script b/git-merge-one-file-script
new file mode 100755 (executable)
index 0000000..88ad3ed
--- /dev/null
@@ -0,0 +1,84 @@
+#!/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"
+       rm -f -- "$4" &&
+               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-prune-script b/git-prune-script
new file mode 100755 (executable)
index 0000000..ec9f72d
--- /dev/null
@@ -0,0 +1,25 @@
+#!/bin/sh
+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_DIR=.git}
+: ${GIT_OBJECT_DIRECTORY="${SHA1_FILE_DIRECTORY-"$GIT_DIR/objects"}"}
+
+git-fsck-cache --cache --unreachable "$@" |
+sed -ne '/unreachable /{
+    s/unreachable [^ ][^ ]* //
+    s|\(..\)|\1/|p
+}' | {
+       cd "$GIT_OBJECT_DIRECTORY" || exit
+       xargs -r $dryrun rm -f
+}
+
diff --git a/git-pull-script b/git-pull-script
new file mode 100755 (executable)
index 0000000..90ee0f3
--- /dev/null
@@ -0,0 +1,21 @@
+#!/bin/sh
+#
+merge_repo=$1
+
+merge_name=$(echo "$1" | sed 's:\.git/*$::')
+merge_head=HEAD
+if [ "$2" ]
+then
+   merge_name="'$2' branch of $merge_name"
+   merge_head="refs/heads/$2"
+fi
+
+: ${GIT_DIR=.git}
+: ${GIT_OBJECT_DIRECTORY="${SHA1_FILE_DIRECTORY-"$GIT_DIR/objects"}"}
+
+git-fetch-script "$merge_repo" "$merge_head" || exit 1
+
+git-resolve-script \
+       "$(cat "$GIT_DIR"/HEAD)" \
+       "$(cat "$GIT_DIR"/FETCH_HEAD)" \
+       "$merge_name"
diff --git a/git-reset-script b/git-reset-script
new file mode 100755 (executable)
index 0000000..fe77338
--- /dev/null
@@ -0,0 +1,5 @@
+#!/bin/sh
+: ${GIT_DIR=.git}
+git-read-tree --reset 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..bf2fb2d
--- /dev/null
@@ -0,0 +1,69 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Linus Torvalds
+#
+# Resolve two trees.
+#
+head=$(git-rev-parse --revs-only "$1")
+merge=$(git-rev-parse --revs-only "$2")
+merge_repo="$3"
+
+: ${GIT_DIR=.git}
+: ${GIT_OBJECT_DIRECTORY="${SHA1_FILE_DIRECTORY-"$GIT_DIR/objects"}"}
+
+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_repo" ]; then
+       echo "git-resolve-script <head> <remote> <merge-repo-name>"
+       exit 1
+fi
+
+dropheads
+echo $head > "$GIT_DIR"/ORIG_HEAD
+echo $merge > "$GIT_DIR"/LAST_MERGE
+
+common=$(git-merge-base $head $merge)
+if [ -z "$common" ]; then
+       echo "Unable to find common commit between" $merge $head
+       exit 1
+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
+merge_msg="Merge $merge_repo"
+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
+               echo "Automatic merge failed, fix up by hand"
+               exit 1
+       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-shortlog b/git-shortlog
new file mode 100755 (executable)
index 0000000..a147e7b
--- /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 (@$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..7d47fde
--- /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";;
+    N*) 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..281d192
--- /dev/null
@@ -0,0 +1,12 @@
+#!/bin/sh
+# Copyright (c) 2005 Linus Torvalds
+
+: ${GIT_DIR=.git}
+
+object=${2:-$(cat "$GIT_DIR"/HEAD)}
+type=$(git-cat-file -t $object) || exit 1
+( echo -e "object $object\ntype $type\ntag $1\n"; cat ) > .tmp-tag
+rm -f .tmp-tag.asc
+gpg -bsa .tmp-tag && cat .tmp-tag.asc >> .tmp-tag
+git-mktag < .tmp-tag
+#rm .tmp-tag .tmp-tag.sig
diff --git a/git-whatchanged b/git-whatchanged
new file mode 100755 (executable)
index 0000000..6fbd115
--- /dev/null
@@ -0,0 +1,4 @@
+#!/bin/sh
+git-rev-list $(git-rev-parse --default HEAD --revs-only "$@") |
+       git-diff-tree --stdin --pretty -r $(git-rev-parse --no-revs "$@") |
+       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/http-pull.c b/http-pull.c
new file mode 100644 (file)
index 0000000..ec53dad
--- /dev/null
@@ -0,0 +1,187 @@
+#include "cache.h"
+#include "commit.h"
+
+#include "pull.h"
+
+#include <curl/curl.h>
+#include <curl/easy.h>
+
+static CURL *curl;
+
+static char *base;
+
+static SHA_CTX c;
+static z_stream stream;
+
+static int local;
+static int zret;
+
+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;
+}
+
+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 %s\n", filename);
+
+       memset(&stream, 0, sizeof(stream));
+
+       inflateInit(&stream);
+
+       SHA1_Init(&c);
+
+       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))
+               return error("Couldn't get %s for %s\n", url, hex);
+
+       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] == '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;
+               } 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();
+
+       base = url;
+
+       if (pull(commit_id))
+               return 1;
+
+       curl_global_cleanup();
+       return 0;
+}
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..1aa0d72
--- /dev/null
+++ b/init-db.c
@@ -0,0 +1,91 @@
+/*
+ * GIT - The information manager from hell
+ *
+ * Copyright (C) Linus Torvalds, 2005
+ */
+#include "cache.h"
+
+static void safe_create_dir(const char *dir)
+{
+       if (mkdir(dir, 0755) < 0) {
+               if (errno != EEXIST) {
+                       perror(dir);
+                       exit(1);
+               }
+       }
+}
+
+static void create_default_files(const char *git_dir)
+{
+       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);
+               }
+       }
+}
+
+/*
+ * 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;
+       int len, i;
+
+       /*
+        * 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);
+
+       /*
+        * 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);
+       }
+       return 0;
+}
diff --git a/local-pull.c b/local-pull.c
new file mode 100644 (file)
index 0000000..535bd8c
--- /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 */
+
+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 (-1 == (int)(long)map) {
+                       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] == '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] == '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..c6c32d9
--- /dev/null
@@ -0,0 +1,350 @@
+/*
+ * 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 int nr_excludes;
+static const char **excludes;
+static int excludes_alloc;
+
+static void add_exclude(const char *string)
+{
+       if (nr_excludes == excludes_alloc) {
+               excludes_alloc = alloc_nr(excludes_alloc);
+               excludes = realloc(excludes, excludes_alloc*sizeof(char *));
+       }
+       excludes[nr_excludes++] = string;
+}
+
+static void add_excludes_from_file(const char *fname)
+{
+       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;
+       }
+       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) {
+                               buf[i] = 0;
+                               add_exclude(entry);
+                       }
+                       entry = buf + i + 1;
+               }
+       }
+       return;
+
+err:   perror(fname);
+       exit(1);
+}
+
+static int excluded(const char *pathname)
+{
+       int i;
+       if (nr_excludes) {
+               const char *basename = strrchr(pathname, '/');
+               basename = (basename) ? basename+1 : pathname;
+               for (i = 0; i < nr_excludes; i++)
+                       if (fnmatch(excludes[i], basename, 0) == 0)
+                               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 currently ignore all names starting with a dot.
+ * That likely will not change.
+ */
+static void read_directory(const char *path, const char *base, int baselen)
+{
+       DIR *dir = opendir(path);
+
+       if (dir) {
+               struct dirent *de;
+               char fullname[MAXPATHLEN + 1];
+               memcpy(fullname, 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;
+                       if (excluded(de->d_name) != show_ignored)
+                               continue;
+                       len = strlen(de->d_name);
+                       memcpy(fullname + baselen, de->d_name, len+1);
+
+                       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);
+       }
+}
+
+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>) ]";
+
+int main(int argc, char **argv)
+{
+       int i;
+
+       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) {
+                       add_exclude(argv[++i]);
+               } else if (!strncmp(arg, "--exclude=", 10)) {
+                       add_exclude(arg+10);
+               } else if (!strcmp(arg, "-X") && i+1 < argc) {
+                       add_excludes_from_file(argv[++i]);
+               } else if (!strncmp(arg, "--exclude-from=", 15)) {
+                       add_excludes_from_file(arg+15);
+               } else
+                       usage(ls_files_usage);
+       }
+
+       if (show_ignored && !nr_excludes) {
+               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..450bff2
--- /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 = 0;
+               }
+               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..12ebb95
--- /dev/null
@@ -0,0 +1,72 @@
+#include <stdlib.h>
+#include "cache.h"
+#include "commit.h"
+
+static struct commit *process_list(struct commit_list **list_p, int this_mark,
+                                  int other_mark)
+{
+       struct commit *item = (*list_p)->item;
+
+       if (item->object.flags & other_mark) {
+               return item;
+       } else {
+               pop_most_recent_commit(list_p, this_mark);
+       }
+       return NULL;
+}
+
+static struct commit *common_ancestor(struct commit *rev1, struct commit *rev2)
+{
+       struct commit_list *rev1list = NULL;
+       struct commit_list *rev2list = NULL;
+
+       commit_list_insert(rev1, &rev1list);
+       rev1->object.flags |= 0x1;
+       commit_list_insert(rev2, &rev2list);
+       rev2->object.flags |= 0x2;
+
+       parse_commit(rev1);
+       parse_commit(rev2);
+
+       while (rev1list || rev2list) {
+               struct commit *ret;
+               if (!rev1list) {
+                       // process 2
+                       ret = process_list(&rev2list, 0x2, 0x1);
+               } else if (!rev2list) {
+                       // process 1
+                       ret = process_list(&rev1list, 0x1, 0x2);
+               } else if (rev1list->item->date < rev2list->item->date) {
+                       // process 2
+                       ret = process_list(&rev2list, 0x2, 0x1);
+               } else {
+                       // process 1
+                       ret = process_list(&rev1list, 0x1, 0x2);
+               }
+               if (ret) {
+                       free_commit_list(rev1list);
+                       free_commit_list(rev2list);
+                       return ret;
+               }
+       }
+       return NULL;
+}
+
+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);
+       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..37c72d2
--- /dev/null
@@ -0,0 +1,128 @@
+#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;
+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
+                       die("merge program failed");
+       }
+}
+
+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] <merge-program> (-a | <filename>*)");
+
+       read_cache();
+
+       i = 1;
+       if (!strcmp(argv[1], "-o")) {
+               one_shot = 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)
+               die("merge program failed");
+       return 0;
+}
diff --git a/mkdelta.c b/mkdelta.c
new file mode 100644 (file)
index 0000000..6470a94
--- /dev/null
+++ b/mkdelta.c
@@ -0,0 +1,363 @@
+/*
+ * Deltafication of a GIT database.
+ *
+ * (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 "cache.h"
+#include "delta.h"
+
+static int replace_object(char *buf, unsigned long size, unsigned char *sha1)
+{
+       char tmpfile[PATH_MAX];
+       int fd;
+
+       snprintf(tmpfile, sizeof(tmpfile), "%s/obj_XXXXXX", get_object_directory());
+       fd = mkstemp(tmpfile);
+       if (fd < 0)
+               return error("%s: %s\n", tmpfile, strerror(errno));
+       if (write(fd, buf, size) != size) {
+               perror("unable to write file");
+               close(fd);
+               unlink(tmpfile);
+               return -1;
+       }
+       fchmod(fd, 0444);
+       close(fd);
+       if (rename(tmpfile, sha1_file_name(sha1))) {
+               perror("unable to replace original object");
+               unlink(tmpfile);
+               return -1;
+       }
+       return 0;
+}
+
+static void *create_object(unsigned char *buf, unsigned long len,
+                          char *hdr, int hdrlen, unsigned long *retsize)
+{
+       unsigned char *compressed;
+       unsigned long size;
+       z_stream stream;
+
+       /* 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 = (unsigned char *)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);
+       *retsize = stream.total_out;
+       return compressed;
+}
+
+static int restore_original_object(unsigned char *buf, unsigned long len,
+                                  char *type, unsigned char *sha1)
+{
+       char hdr[50];
+       int hdrlen, ret;
+       void *compressed;
+       unsigned long size;
+
+       hdrlen = sprintf(hdr, "%s %lu", type, len)+1;
+       compressed = create_object(buf, len, hdr, hdrlen, &size);
+       ret = replace_object(compressed, size, sha1);
+       free(compressed);
+       return ret;
+}
+
+static void *create_delta_object(unsigned char *buf, unsigned long len,
+                                unsigned char *sha1_ref, unsigned long *size)
+{
+       char hdr[50];
+       int hdrlen;
+
+       /* Generate the header + sha1 of reference for delta */
+       hdrlen = sprintf(hdr, "delta %lu", len+20)+1;
+       memcpy(hdr + hdrlen, sha1_ref, 20);
+       hdrlen += 20;
+
+       return create_object(buf, len, hdr, hdrlen, size);
+}
+
+static void *get_buffer(unsigned char *sha1, char *type,
+                       unsigned long *size, unsigned long *compsize)
+{
+       unsigned long mapsize;
+       void *map = map_sha1_file(sha1, &mapsize);
+       if (map) {
+               void *buffer = unpack_sha1_file(map, mapsize, type, size);
+               munmap(map, mapsize);
+               if (compsize)
+                       *compsize = mapsize;
+               if (buffer)
+                       return buffer;
+       }
+       error("unable to get object %s", sha1_to_hex(sha1));
+       return NULL;
+}
+
+static void *expand_delta(void *delta, unsigned long *size, char *type,
+                         unsigned int *depth, unsigned char **links)
+{
+       void *buf = NULL;
+       unsigned int level = (*depth)++;
+       if (*size < 20) {
+               error("delta object is bad");
+               free(delta);
+       } else {
+               unsigned long ref_size;
+               void *ref = get_buffer(delta, type, &ref_size, NULL);
+               if (ref && !strcmp(type, "delta"))
+                       ref = expand_delta(ref, &ref_size, type, depth, links);
+               else if (ref)
+{
+                       *links = xmalloc(*depth * 20);
+}
+               if (ref) {
+                       buf = patch_delta(ref, ref_size, delta+20, *size-20, size);
+                       free(ref);
+                       if (buf)
+                               memcpy(*links + level*20, delta, 20);
+                       else
+                               free(*links);
+               }
+               free(delta);
+       }
+       return buf;
+}
+
+static char *mkdelta_usage =
+"mkdelta [--max-depth=N] [--max-behind=N] <reference_sha1> <target_sha1> [<next_sha1> ...]";
+
+struct delta {
+       unsigned char sha1[20];         /* object sha1 */
+       unsigned long size;             /* object size */
+       void *buf;                      /* object content */
+       unsigned char *links;           /* delta reference links */
+       unsigned int depth;             /* delta depth */
+};
+       
+int main(int argc, char **argv)
+{
+       struct delta *ref, trg;
+       char ref_type[20], trg_type[20], *skip_reason;
+       void *best_buf;
+       unsigned long best_size, orig_size, orig_compsize;
+       unsigned int r, orig_ref, best_ref, nb_refs, next_ref, max_refs = 0;
+       unsigned int i, duplicate, skip_lvl, verbose = 0, quiet = 0;
+       unsigned int max_depth = -1;
+
+       for (i = 1; i < argc; i++) {
+               if (!strcmp(argv[i], "-v")) {
+                       verbose = 1;
+                       quiet = 0;
+               } else if (!strcmp(argv[i], "-q")) {
+                       quiet = 1;
+                       verbose = 0;
+               } else if (!strcmp(argv[i], "-d") && i+1 < argc) {
+                       max_depth = atoi(argv[++i]);
+               } else if (!strncmp(argv[i], "--max-depth=", 12)) {
+                       max_depth = atoi(argv[i]+12);
+               } else if (!strcmp(argv[i], "-b") && i+1 < argc) {
+                       max_refs = atoi(argv[++i]);
+               } else if (!strncmp(argv[i], "--max-behind=", 13)) {
+                       max_refs = atoi(argv[i]+13);
+               } else
+                       break;
+       }
+
+       if (i + (max_depth != 0) >= argc)
+               usage(mkdelta_usage);
+
+       if (!max_refs || max_refs > argc - i)
+               max_refs = argc - i;
+       ref = xmalloc(max_refs * sizeof(*ref));
+       for (r = 0; r < max_refs; r++)
+               ref[r].buf = ref[r].links = NULL;
+       next_ref = nb_refs = 0;
+
+       do {
+               if (get_sha1(argv[i], trg.sha1))
+                       die("bad sha1 %s", argv[i]);
+               trg.buf = get_buffer(trg.sha1, trg_type, &trg.size, &orig_compsize);
+               if (trg.buf && !trg.size) {
+                       if (verbose)
+                               printf("skip    %s (object is empty)\n", argv[i]);
+                       continue;
+               }
+               orig_size = trg.size;
+               orig_ref = -1;
+               trg.depth = 0;
+               trg.links = NULL;
+               if (trg.buf && !strcmp(trg_type, "delta")) {
+                       for (r = 0; r < nb_refs; r++)
+                               if (!memcmp(trg.buf, ref[r].sha1, 20))
+                                       break;
+                       if (r < nb_refs) {
+                               /* no need to reload the reference object */
+                               trg.depth = ref[r].depth + 1;
+                               trg.links = xmalloc(trg.depth*20);
+                               memcpy(trg.links, trg.buf, 20);
+                               memcpy(trg.links+20, ref[r].links, ref[r].depth*20);
+                               trg.buf = patch_delta(ref[r].buf, ref[r].size,
+                                                     trg.buf+20, trg.size-20,
+                                                     &trg.size);
+                               strcpy(trg_type, ref_type);
+                               orig_ref = r;
+                       } else {
+                               trg.buf = expand_delta(trg.buf, &trg.size, trg_type,
+                                                      &trg.depth, &trg.links);
+                       }
+               }
+               if (!trg.buf)
+                       die("unable to read target object %s", argv[i]);
+
+               if (!nb_refs) {
+                       strcpy(ref_type, trg_type);
+               } else if (max_depth && strcmp(ref_type, trg_type)) {
+                       die("type mismatch for object %s", argv[i]);
+               }
+
+               duplicate = 0;
+               best_buf = NULL;
+               best_size = -1;
+               best_ref = -1;
+               skip_lvl = 0;
+               skip_reason = NULL;
+               for (r = 0; max_depth && r < nb_refs; r++) {
+                       void *delta_buf, *comp_buf;
+                       unsigned long delta_size, comp_size;
+                       unsigned int l;
+
+                       duplicate = !memcmp(trg.sha1, ref[r].sha1, 20);
+                       if (duplicate) {
+                               skip_reason = "already seen";
+                               break;
+                       }
+                       if (ref[r].depth >= max_depth) {
+                               if (skip_lvl < 1) {
+                                       skip_reason = "exceeding max link depth";
+                                       skip_lvl = 1;
+                               }
+                               continue;
+                       }
+                       for (l = 0; l < ref[r].depth; l++)
+                               if (!memcmp(trg.sha1, ref[r].links + l*20, 20))
+                                       break;
+                       if (l != ref[r].depth) {
+                               if (skip_lvl < 2) {
+                                       skip_reason = "would create a loop";
+                                       skip_lvl = 2;
+                               }
+                               continue;
+                       }
+                       if (trg.depth < max_depth && r == orig_ref) {
+                               if (skip_lvl < 3) {
+                                       skip_reason = "delta already in place";
+                                       skip_lvl = 3;
+                               }
+                               continue;
+                       }
+                       delta_buf = diff_delta(ref[r].buf, ref[r].size,
+                                              trg.buf, trg.size, &delta_size);
+                       if (!delta_buf)
+                               die("out of memory");
+                       if (trg.depth < max_depth &&
+                           delta_size+20 >= orig_size) {
+                               /* no need to even try to compress if original
+                                  object is smaller than this delta */
+                               free(delta_buf);
+                               if (skip_lvl < 4) {
+                                       skip_reason = "no size reduction";
+                                       skip_lvl = 4;
+                               }
+                               continue;
+                       }
+                       comp_buf = create_delta_object(delta_buf, delta_size,
+                                                      ref[r].sha1, &comp_size);
+                       if (!comp_buf)
+                               die("out of memory");
+                       free(delta_buf);
+                       if (trg.depth < max_depth &&
+                           comp_size >= orig_compsize) {
+                               free(comp_buf);
+                               if (skip_lvl < 5) {
+                                       skip_reason = "no size reduction";
+                                       skip_lvl = 5;
+                               }
+                               continue;
+                       }
+                       if ((comp_size < best_size) ||
+                           (comp_size == best_size &&
+                            ref[r].depth < ref[best_ref].depth)) {
+                               free(best_buf);
+                               best_buf = comp_buf;
+                               best_size = comp_size;
+                               best_ref = r;
+                       }
+               }
+
+               if (best_buf) {
+                       if (replace_object(best_buf, best_size, trg.sha1))
+                               die("unable to write delta for %s", argv[i]);
+                       free(best_buf);
+                       free(trg.links);
+                       trg.depth = ref[best_ref].depth + 1;
+                       trg.links = xmalloc(trg.depth*20);
+                       memcpy(trg.links, ref[best_ref].sha1, 20);
+                       memcpy(trg.links+20, ref[best_ref].links, ref[best_ref].depth*20);
+                       if (!quiet)
+                               printf("delta   %s (size=%ld.%02ld%% depth=%d dist=%d)\n",
+                                      argv[i], best_size*100 / orig_compsize,
+                                      (best_size*10000 / orig_compsize)%100,
+                                      trg.depth,
+                                      (next_ref - best_ref + max_refs)
+                                      % (max_refs + 1) + 1);
+               } else if (trg.depth > max_depth) {
+                       if (restore_original_object(trg.buf, trg.size, trg_type, trg.sha1))
+                               die("unable to restore %s", argv[i]);
+                       if (!quiet)
+                               printf("undelta %s (depth was %d)\n",
+                                      argv[i], trg.depth);
+                       trg.depth = 0;
+                       free(trg.links);
+                       trg.links = NULL;
+               } else if (skip_reason && verbose) {
+                       printf("skip    %s (%s)\n", argv[i], skip_reason);
+               }
+
+               if (!duplicate) {
+                       free(ref[next_ref].buf);
+                       free(ref[next_ref].links);
+                       ref[next_ref] = trg;
+                       if (++next_ref > nb_refs)
+                               nb_refs = next_ref;
+                       if (next_ref == max_refs)
+                               next_ref = 0;
+               } else {
+                       free(trg.buf);
+                       free(trg.links);
+               }
+       } while (++i < argc);
+
+       return 0;
+}
diff --git a/mktag.c b/mktag.c
new file mode 100644 (file)
index 0000000..8cbbef6
--- /dev/null
+++ b/mktag.c
@@ -0,0 +1,130 @@
+#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;
+
+       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;
+       }
+
+       /* 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 "
+       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..21f872e
--- /dev/null
+++ b/object.c
@@ -0,0 +1,164 @@
+#include "object.h"
+#include "blob.h"
+#include "tree.h"
+#include "commit.h"
+#include "cache.h"
+#include "tag.h"
+#include "delta.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 (!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;
+       }
+}
+
+struct object *parse_object(const unsigned char *sha1)
+{
+       unsigned long mapsize;
+       void *map = map_sha1_file(sha1, &mapsize);
+       if (map) {
+               int is_delta;
+               struct object *obj;
+               char type[100];
+               unsigned long size;
+               void *buffer = unpack_sha1_file(map, mapsize, type, &size);
+               munmap(map, mapsize);
+               if (!buffer)
+                       return NULL;
+               is_delta = !strcmp(type, "delta");
+               if (!is_delta && check_sha1_signature(sha1, buffer, size, type) < 0)
+                       printf("sha1 mismatch %s\n", sha1_to_hex(sha1));
+               if (is_delta) {
+                       struct delta *delta = lookup_delta(sha1);
+                       parse_delta_buffer(delta, buffer, size);
+                       obj = (struct object *) delta;
+               } else 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;
+}
diff --git a/object.h b/object.h
new file mode 100644 (file)
index 0000000..1bd59ac
--- /dev/null
+++ b/object.h
@@ -0,0 +1,39 @@
+#ifndef OBJECT_H
+#define OBJECT_H
+
+struct object_list {
+       struct object *item;
+       struct object_list *next;
+};
+
+struct object {
+       unsigned parsed : 1;
+       unsigned used : 1;
+       unsigned delta : 1;
+       unsigned int flags;
+       unsigned char sha1[20];
+       const char *type;
+       struct object_list *refs;
+       struct object_list *attached_deltas;
+       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);
+
+void add_ref(struct object *refer, struct object *target);
+
+void mark_reachable(struct object *obj, unsigned int mask);
+
+#endif /* OBJECT_H */
diff --git a/patch-delta.c b/patch-delta.c
new file mode 100644 (file)
index 0000000..a8d75ee
--- /dev/null
@@ -0,0 +1,88 @@
+/*
+ * 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;
+       int i;
+
+       /* the smallest delta size possible is 6 bytes */
+       if (delta_size < 6)
+               return NULL;
+
+       data = delta_buf;
+       top = delta_buf + delta_size;
+
+       /* make sure the orig file size matches what we expect */
+       size = i = 0;
+       cmd = *data++;
+       while (cmd) {
+               if (cmd & 1)
+                       size |= *data++ << i;
+               i += 8;
+               cmd >>= 1;
+       }
+       if (size != src_size)
+               return NULL;
+
+       /* now the result size */
+       size = i = 0;
+       cmd = *data++;
+       while (cmd) {
+               if (cmd & 1)
+                       size |= *data++ << i;
+               i += 8;
+               cmd >>= 1;
+       }
+       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/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/pull.c b/pull.c
new file mode 100644 (file)
index 0000000..91d9db6
--- /dev/null
+++ b/pull.c
@@ -0,0 +1,189 @@
+#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;
+/* 1 means "get delta", 2 means "really check delta harder */
+int get_delta = 1;
+int get_all = 0;
+int get_verbosely = 0;
+static unsigned char current_commit_sha1[20];
+
+static const char commitS[] = "commit";
+static const char treeS[] = "tree";
+static const char blobS[] = "blob";
+
+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);
+       }
+       else if (get_delta < 2)
+               return 0;
+
+       if (get_delta) {
+               unsigned char delta_sha1[20];
+               status = sha1_delta_base(sha1, delta_sha1);
+               if (0 < status)
+                       status = make_sure_we_have_it(what, delta_sha1);
+       }
+       return status;
+}
+
+static int process_unknown(unsigned char *sha1);
+
+static int process_tree(unsigned char *sha1)
+{
+       struct tree *tree = lookup_tree(sha1);
+       struct tree_entry_list *entries;
+
+       if (parse_tree(tree))
+               return -1;
+
+       for (entries = tree->entries; entries; entries = entries->next) {
+               const char *what = entries->directory ? treeS : blobS;
+               if (make_sure_we_have_it(what, entries->item.tree->object.sha1))
+                       return -1;
+               if (entries->directory) {
+                       if (process_tree(entries->item.tree->object.sha1))
+                               return -1;
+               }
+       }
+       return 0;
+}
+
+static int process_commit(unsigned char *sha1)
+{
+       struct commit *obj = lookup_commit(sha1);
+
+       if (make_sure_we_have_it(commitS, sha1))
+               return -1;
+
+       if (parse_commit(obj))
+               return -1;
+
+       if (get_tree) {
+               if (make_sure_we_have_it(treeS, obj->tree->object.sha1))
+                       return -1;
+               if (process_tree(obj->tree->object.sha1))
+                       return -1;
+               if (!get_all)
+                       get_tree = 0;
+       }
+       if (get_history) {
+               struct commit_list *parents = obj->parents;
+               for (; parents; parents = parents->next) {
+                       if (has_sha1_file(parents->item->object.sha1))
+                               continue;
+                       if (make_sure_we_have_it(NULL,
+                                                parents->item->object.sha1)) {
+                               /* The server might not have it, and
+                                * we don't mind. 
+                                */
+                               continue;
+                       }
+                       if (process_commit(parents->item->object.sha1))
+                               return -1;
+                       memcpy(current_commit_sha1, sha1, 20);
+               }
+       }
+       return 0;
+}
+
+static int process_tag(unsigned char *sha1)
+{
+       struct tag *obj = lookup_tag(sha1);
+
+       if (parse_tag(obj))
+               return -1;
+       return process_unknown(obj->tagged->sha1);
+}
+
+static int process_unknown(unsigned char *sha1)
+{
+       struct object *obj;
+       if (make_sure_we_have_it("object", sha1))
+               return -1;
+       obj = parse_object(sha1);
+       if (!obj)
+               return error("Unable to parse object %s", sha1_to_hex(sha1));
+       if (obj->type == commit_type)
+               return process_commit(sha1);
+       if (obj->type == tree_type)
+               return process_tree(sha1);
+       if (obj->type == blob_type)
+               return 0;
+       if (obj->type == tag_type)
+               return process_tag(sha1);
+       return error("Unable to determine requirement of type %s for %s",
+                    obj->type, sha1_to_hex(sha1));
+}
+
+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_unknown(sha1))
+               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..bd5e7bd
--- /dev/null
+++ b/pull.h
@@ -0,0 +1,37 @@
+#ifndef PULL_H
+#define PULL_H
+
+/** To be provided by the particular implementation. **/
+extern int fetch(unsigned char *sha1);
+
+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 zero to skip the check for delta object base;
+ * set to two to check delta dependency even for objects we already have.
+ */
+extern int get_delta;
+
+/* 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/read-cache.c b/read-cache.c
new file mode 100644 (file)
index 0000000..9b6ce46
--- /dev/null
@@ -0,0 +1,463 @@
+/*
+ * 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);
+}
+
+/*
+ * 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);
+       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 (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;
+       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, len);
+               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;
+               if (pos < active_nr) {
+                       struct cache_entry *p = active_cache[pos];
+                       if (ce_namelen(p) <= len)
+                               continue;
+                       if (p->name[len] != '/')
+                               continue;
+                       if (memcmp(p->name, name, len))
+                               continue;
+                       break;
+               }
+       }
+       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;
+       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 (!ce_stage(ce) && 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 = (void *)-1;
+       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 (-1 == (int)(long)map)
+               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..0d5ded5
--- /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);
+       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 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/refs.c b/refs.c
new file mode 100644 (file)
index 0000000..9973d1f
--- /dev/null
+++ b/refs.c
@@ -0,0 +1,173 @@
+#include "refs.h"
+#include "cache.h"
+
+#include <errno.h>
+
+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..60cf480
--- /dev/null
+++ b/refs.h
@@ -0,0 +1,21 @@
+#ifndef REFS_H
+#define REFS_H
+
+/** 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-list.c b/rev-list.c
new file mode 100644 (file)
index 0000000..16920ef
--- /dev/null
@@ -0,0 +1,317 @@
+#include "cache.h"
+#include "commit.h"
+#include "epoch.h"
+
+#define SEEN           (1u << 0)
+#define INTERESTING    (1u << 1)
+#define COUNTED                (1u << 2)
+#define SHOWN          (LAST_EPOCH_FLAG << 2)
+
+static const char rev_list_usage[] =
+       "usage: git-rev-list [OPTION] commit-id <commit-id>\n"
+                     "  --max-count=nr\n"
+                     "  --max-age=epoch\n"
+                     "  --min-age=epoch\n"
+                     "  --header\n"
+                     "  --pretty\n"
+                     "  --merge-order [ --show-breaks ]";
+
+static int bisect_list = 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 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;
+               }
+       }
+       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);
+       }       
+}
+
+static int filter_commit(struct commit * commit)
+{
+       if (merge_order && 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)) {
+               if (!merge_order)
+                       return STOP;
+               else {
+                       stop_traversal = 1;
+                       return CONTINUE;
+               }
+       }
+       if (max_count != -1 && !max_count--)
+               return STOP;
+       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 void show_commit_list(struct commit_list *list)
+{
+       while (list) {
+               struct commit *commit = pop_most_recent_commit(&list, SEEN);
+
+               if (process_commit(commit) == STOP)
+                       break;
+       }
+}
+
+static void mark_parents_uninteresting(struct commit *commit)
+{
+       struct commit_list *parents = commit->parents;
+
+       while (parents) {
+               struct commit *commit = parents->item;
+               commit->object.flags |= UNINTERESTING;
+               parents = parents->next;
+       }
+}
+
+static int everybody_uninteresting(struct commit_list *list)
+{
+       while (list) {
+               struct commit *commit = list->item;
+               list = list->next;
+               if (commit->object.flags & UNINTERESTING)
+                       continue;
+               return 0;
+       }
+       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;
+}
+
+struct commit_list *limit_list(struct commit_list *list)
+{
+       struct commit_list *newlist = NULL;
+       struct commit_list **p = &newlist;
+       do {
+               struct commit *commit = pop_most_recent_commit(&list, SEEN);
+               struct object *obj = &commit->object;
+
+               if (obj->flags & UNINTERESTING) {
+                       mark_parents_uninteresting(commit);
+                       if (everybody_uninteresting(list))
+                               break;
+                       continue;
+               }
+               p = &commit_list_insert(commit, p)->next;
+       } while (list);
+       if (bisect_list)
+               newlist = find_bisection(newlist);
+       return newlist;
+}
+
+static 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;
+       usage(rev_list_usage);  
+}                      
+
+
+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];
+               unsigned char sha1[20];
+               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';
+                       prefix = "commit ";
+                       continue;
+               }
+               if (!strcmp(arg, "--parents")) {
+                       show_parents = 1;
+                       continue;
+               }
+               if (!strcmp(arg, "--bisect")) {
+                       bisect_list = 1;
+                       continue;
+               }
+               if (!strncmp(arg, "--merge-order", 13)) {
+                       merge_order = 1;
+                       continue;
+               }
+               if (!strncmp(arg, "--show-breaks", 13)) {
+                       show_breaks = 1;
+                       continue;
+               }
+
+               flags = 0;
+               if (*arg == '^') {
+                       flags = UNINTERESTING;
+                       arg++;
+                       limited = 1;
+               }
+               if (get_sha1(arg, sha1) || (show_breaks && !merge_order))
+                       usage(rev_list_usage);
+               commit = lookup_commit_reference(sha1);
+               if (!commit || parse_commit(commit) < 0)
+                       die("bad commit object %s", arg);
+               commit->object.flags |= flags;
+               commit_list_insert(commit, &list);
+       }
+
+       if (!list)
+               usage(rev_list_usage);
+
+       if (!merge_order) {             
+               if (limited)
+                       list = limit_list(list);
+               show_commit_list(list);
+       } else {
+               if (sort_list_in_merge_order(list, &process_commit)) {
+                         die("merge order sort failed\n");
+               }
+       }
+
+       return 0;
+}
diff --git a/rev-parse.c b/rev-parse.c
new file mode 100644 (file)
index 0000000..40707ac
--- /dev/null
@@ -0,0 +1,178 @@
+/*
+ * rev-parse.c
+ *
+ * Copyright (C) Linus Torvalds, 2005
+ */
+#include "cache.h"
+#include "commit.h"
+
+static int get_extended_sha1(char *name, unsigned char *sha1);
+
+/*
+ * 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",
+               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 int get_parent(char *name, unsigned char *result, int idx)
+{
+       unsigned char sha1[20];
+       int ret = get_extended_sha1(name, 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;
+       p = commit->parents;
+       while (p) {
+               if (!--idx) {
+                       memcpy(result, p->item->object.sha1, 20);
+                       return 0;
+               }
+               p = p->next;
+       }
+       return -1;
+}
+
+/*
+ * This is like "get_sha1()", except it allows "sha1 expressions",
+ * notably "xyz^" for "parent of xyz"
+ */
+static int get_extended_sha1(char *name, unsigned char *sha1)
+{
+       int parent;
+       int len = strlen(name);
+
+       parent = 1;
+       if (len > 2 && name[len-1] >= '1' && name[len-1] <= '9') {
+               parent = name[len-1] - '0';
+               len--;
+       }
+       if (len > 1 && name[len-1] == '^') {
+               int ret;
+               name[len-1] = 0;
+               ret = get_parent(name, sha1, parent);
+               name[len-1] = '^';
+               if (!ret)
+                       return 0;
+       }
+       return get_sha1(name, sha1);
+}
+
+int main(int argc, char **argv)
+{
+       int i, as_is = 0, revs_only = 0, no_revs = 0;
+       char *def = NULL;
+       unsigned char sha1[20];
+
+       for (i = 1; i < argc; i++) {
+               char *arg = argv[i];
+               char *dotdot;
+       
+               if (as_is) {
+                       printf("%s\n", arg);
+                       continue;
+               }
+               if (*arg == '-') {
+                       if (!strcmp(arg, "--")) {
+                               if (def) {
+                                       printf("%s\n", def);
+                                       def = NULL;
+                               }
+                               if (revs_only)
+                                       break;
+                               as_is = 1;
+                       }
+                       if (!strcmp(arg, "--default")) {
+                               if (def)
+                                       printf("%s\n", def);
+                               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 (revs_only | no_revs) {
+                               if (is_rev_argument(arg) != revs_only)
+                                       continue;
+                       }
+                       printf("%s\n", arg);
+                       continue;
+               }
+               dotdot = strstr(arg, "..");
+               if (dotdot) {
+                       unsigned char end[20];
+                       char *n = dotdot+2;
+                       *dotdot = 0;
+                       if (!get_extended_sha1(arg, sha1)) {
+                               if (!*n)
+                                       n = "HEAD";
+                               if (!get_extended_sha1(n, end)) {
+                                       if (no_revs)
+                                               continue;
+                                       def = NULL;
+                                       printf("%s\n", sha1_to_hex(end));
+                                       printf("^%s\n", sha1_to_hex(sha1));
+                                       continue;
+                               }
+                       }
+                       *dotdot = '.';
+               }
+               if (!get_extended_sha1(arg, sha1)) {
+                       if (no_revs)
+                               continue;
+                       def = NULL;
+                       printf("%s\n", sha1_to_hex(sha1));
+                       continue;
+               }
+               if (*arg == '^' && !get_extended_sha1(arg+1, sha1)) {
+                       if (no_revs)
+                               continue;
+                       def = NULL;
+                       printf("^%s\n", sha1_to_hex(sha1));
+                       continue;
+               }
+               if (def) {
+                       printf("%s\n", def);
+                       def = NULL;
+               }
+               if (revs_only)
+                       continue;
+               printf("%s\n", arg);
+       }
+       if (def)
+               printf("%s\n", def);
+       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..fe87e58
--- /dev/null
+++ b/rsh.c
@@ -0,0 +1,68 @@
+#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()) {
+               close(sv[1]);
+               dup2(sv[0], 0);
+               dup2(sv[0], 1);
+               execlp("ssh", "ssh", 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/sha1_file.c b/sha1_file.c
new file mode 100644 (file)
index 0000000..6d6073d
--- /dev/null
@@ -0,0 +1,767 @@
+/*
+ * GIT - The information manager from hell
+ *
+ * Copyright (C) Linus Torvalds, 2005
+ *
+ * This handles basic git sha1 object files - packing, unpacking,
+ * creation etc.
+ */
+#include "cache.h"
+#include "delta.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 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 char *git_dir, *git_object_dir, *git_index_file, *git_refs_dir;
+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);
+       }
+}
+
+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;
+}
+
+int get_sha1(const char *str, unsigned char *sha1)
+{
+       static char pathname[PATH_MAX];
+       static const char *prefix[] = {
+               "",
+               "refs",
+               "refs/tags",
+               "refs/heads",
+               "refs/snap",
+               NULL
+       };
+       const char **p;
+
+       if (!get_sha1_hex(str, sha1))
+               return 0;
+
+       if (!git_dir)
+               setup_git_env();
+       for (p = prefix; *p; p++) {
+               snprintf(pathname, sizeof(pathname), "%s/%s/%s",
+                        git_dir, *p, str);
+               if (!get_sha1_file(pathname, sha1))
+                       return 0;
+       }
+
+       return -1;
+}
+
+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;
+}
+
+static struct alternate_object_database {
+       char *base;
+       char *name;
+} *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.
+ */
+static void prepare_alt_odb(void)
+{
+       int pass, totlen, i;
+       const char *cp, *last;
+       char *op = NULL;
+       const char *alt = gitenv(ALTERNATE_DB_ENVIRONMENT) ? : "";
+
+       /* 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;
+       if (!alt_odb)
+               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;
+}
+
+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;
+}
+
+void *map_sha1_file(const unsigned char *sha1, unsigned long *size)
+{
+       struct stat st;
+       void *map;
+       int fd;
+       char *filename = find_sha1_file(sha1, &st);
+
+       if (!filename) {
+               error("cannot map sha1 file %s", sha1_to_hex(sha1));
+               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:
+                       perror(filename);
+                       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 (-1 == (int)(long)map)
+               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);
+}
+
+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);
+}
+
+int sha1_delta_base(const unsigned char *sha1, unsigned char *base_sha1)
+{
+       int ret;
+       unsigned long mapsize, size;
+       void *map;
+       z_stream stream;
+       char hdr[64], type[20];
+       void *delta_data_head;
+
+       map = map_sha1_file(sha1, &mapsize);
+       if (!map)
+               return -1;
+       ret = unpack_sha1_header(&stream, map, mapsize, hdr, sizeof(hdr));
+       if (ret < Z_OK || parse_sha1_header(hdr, type, &size) < 0) {
+               ret = -1;
+               goto out;
+       }
+       if (strcmp(type, "delta")) {
+               ret = 0;
+               goto out;
+       }
+
+       delta_data_head = hdr + strlen(hdr) + 1;
+       ret = 1;
+       memcpy(base_sha1, delta_data_head, 20);
+ out:
+       inflateEnd(&stream);
+       munmap(map, mapsize);
+       return ret;
+}
+
+int sha1_file_size(const unsigned char *sha1, unsigned long *sizep)
+{
+       int ret, status;
+       unsigned long mapsize, size;
+       void *map;
+       z_stream stream;
+       char hdr[64], type[20];
+       const unsigned char *data;
+       unsigned char cmd;
+       int i;
+
+       map = map_sha1_file(sha1, &mapsize);
+       if (!map)
+               return -1;
+       ret = unpack_sha1_header(&stream, map, mapsize, hdr, sizeof(hdr));
+       status = -1;
+       if (ret < Z_OK || parse_sha1_header(hdr, type, &size) < 0)
+               goto out;
+       if (strcmp(type, "delta")) {
+               *sizep = size;
+               status = 0;
+               goto out;
+       }
+
+       /* We are dealing with a delta object.  Inflated, the first
+        * 20 bytes hold the base object SHA1, and delta data follows
+        * immediately after it.
+        *
+        * The initial part of the delta starts at delta_data_head +
+        * 20.  Borrow code from patch-delta to read the result size.
+        */
+       data = (unsigned char *)(hdr + strlen(hdr) + 1 + 20);
+
+       /* Skip over the source size; we are not interested in
+        * it and we cannot verify it because we do not want
+        * to read the base object.
+        */
+       cmd = *data++;
+       while (cmd) {
+               if (cmd & 1)
+                       data++;
+               cmd >>= 1;
+       }
+       /* Read the result size */
+       size = i = 0;
+       cmd = *data++;
+       while (cmd) {
+               if (cmd & 1)
+                       size |= *data++ << i;
+               i += 8;
+               cmd >>= 1;
+       }
+       *sizep = size;
+       status = 0;
+ out:
+       inflateEnd(&stream);
+       munmap(map, mapsize);
+       return status;
+}
+
+void * read_sha1_file(const unsigned char *sha1, char *type, unsigned long *size)
+{
+       unsigned long mapsize;
+       void *map, *buf;
+
+       map = map_sha1_file(sha1, &mapsize);
+       if (map) {
+               buf = unpack_sha1_file(map, mapsize, type, size);
+               munmap(map, mapsize);
+               if (buf && !strcmp(type, "delta")) {
+                       void *ref = NULL, *delta = buf;
+                       unsigned long ref_size, delta_size = *size;
+                       buf = NULL;
+                       if (delta_size > 20)
+                               ref = read_sha1_file(delta, type, &ref_size);
+                       if (ref)
+                               buf = patch_delta(ref, ref_size,
+                                                 delta+20, delta_size-20, 
+                                                 size);
+                       free(delta);
+                       free(ref);
+               }
+               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;
+               }
+               /* Now we have the ID of the referred-to object in
+                * actual_sha1.  Check again. */
+       }
+}
+
+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];
+       SHA_CTX c;
+       char *filename;
+       static char tmpfile[PATH_MAX];
+       unsigned char hdr[50];
+       int fd, hdrlen, ret;
+
+       /* 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);
+
+       if (returnsha1)
+               memcpy(returnsha1, sha1, 20);
+
+       filename = sha1_file_name(sha1);
+       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_from_fd(const unsigned char *sha1, int fd)
+{
+       char *filename = sha1_file_name(sha1);
+
+       int local;
+       z_stream stream;
+       unsigned char real_sha1[20];
+       unsigned char buf[4096];
+       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;
+               size = read(fd, buf, 4096);
+               if (size <= 0) {
+                       close(local);
+                       unlink(filename);
+                       if (!size)
+                               return error("Connection closed?");
+                       perror("Reading from connection");
+                       return -1;
+               }
+               write(local, buf, size);
+               stream.avail_in = size;
+               stream.next_in = buf;
+               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);
+               
+       } while (ret == Z_OK);
+       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_sha1_file(const unsigned char *sha1)
+{
+       struct stat st;
+       return !!find_sha1_file(sha1, &st);
+}
+
+int index_fd(unsigned char *sha1, int fd, struct stat *st)
+{
+       unsigned long size = st->st_size;
+       void *buf;
+       int ret;
+
+       buf = "";
+       if (size)
+               buf = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0);
+       close(fd);
+       if ((int)(long)buf == -1)
+               return -1;
+
+       ret = write_sha1_file(buf, size, "blob", sha1);
+       if (size)
+               munmap(buf, size);
+       return ret;
+}
diff --git a/ssh-pull.c b/ssh-pull.c
new file mode 100644 (file)
index 0000000..2748412
--- /dev/null
@@ -0,0 +1,101 @@
+#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;
+
+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 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] == '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;
+               } 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..12fb9fc
--- /dev/null
@@ -0,0 +1,130 @@
+#include "cache.h"
+#include "rsh.h"
+#include "refs.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;
+       unsigned 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;
+}
+
+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 (get_ref_sha1(ref, sha1))
+               remote = -1;
+       write(fd_out, &remote, 1);
+       if (remote)
+               return 0;
+       write(fd_out, sha1, 20);
+        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;
+               if (type == 'r' && serve_ref(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;
+       const char *prog = getenv("GIT_SSH_PULL") ? : "git-ssh-pull";
+
+       while (arg < argc && argv[arg][0] == '-') {
+               if (argv[arg][1] == 'w')
+                       arg++;
+                arg++;
+        }
+        if (argc < arg + 2) {
+               usage("git-ssh-push [-c] [-t] [-a] [-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;
+
+       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..2a94fdb
--- /dev/null
+++ b/t/README
@@ -0,0 +1,200 @@
+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.
+
+
+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 100644 (file)
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 100644 (file)
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..4462f57
--- /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 256 subdirectories.  257 is counting "objects"
+find .git/objects -type d -print >full-of-directories
+test_expect_success \
+    '.git/objects should have 256 subdirectories.' \
+    'test $(wc -l < full-of-directories) = 257'
+
+################################################################
+# 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..309baa5
--- /dev/null
@@ -0,0 +1,288 @@
+#!/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 () {
+       cat current
+       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
+}
+
+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 &&
+     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-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 &&
+     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-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 &&
+     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 &&
+     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 &&
+     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 &&
+     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 &&
+     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 &&
+     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 &&
+     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 &&
+     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 &&
+     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 &&
+     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 &&
+     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 &&
+     echo gnusto >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 &&
+     echo gnusto >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 &&
+     echo bozbar >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 &&
+     echo bozbar >bozbar &&
+     git-update-cache --add bozbar &&
+     echo gnusto gnusto >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..aebe21a
--- /dev/null
@@ -0,0 +1,307 @@
+#!/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 &&
+     sha1sum bozbar frotz nitfol >M.sha1 &&
+     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 &&
+     sha1sum -c M.sha1 &&
+     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 &&
+     sha1sum -c M.sha1 &&
+     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 &&
+     sha1sum -c M.sha1 &&
+     : 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 &&
+     sha1sum -c M.sha1 &&
+     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 &&
+     if sha1sum -c M.sha1; 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 &&
+     sha1sum -c M.sha1'
+
+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 &&
+     check_cache_at nitfol clean &&
+     grep -v nitfol M.sha1 | sha1sum -c &&
+     if sha1sum -c M.sha1; then false; else :; fi &&
+     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 &&
+     grep -v nitfol M.sha1 | sha1sum -c &&
+     if sha1sum -c M.sha1; 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 &&
+     sha1sum -c M.sha1'
+
+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 &&
+     grep -v bozbar M.sha1 | sha1sum -c &&
+     if sha1sum -c M.sha1; 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 &&
+     sha1sum -c M.sha1'
+
+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 100644 (file)
index 0000000..bb955ed
--- /dev/null
@@ -0,0 +1,327 @@
+#!/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 () {
+       cat current
+       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
+}
+
+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 &&
+     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-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 &&
+     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-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 &&
+     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 &&
+     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 &&
+     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 &&
+     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 &&
+     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 &&
+     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 &&
+     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 &&
+     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 &&
+     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 &&
+     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 &&
+     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 &&
+     echo gnusto >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 &&
+     echo gnusto >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 &&
+     echo bozbar >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 &&
+     echo bozbar >bozbar &&
+     git-update-cache --add bozbar &&
+     echo gnusto gnusto >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 clean && # different from pure 2-way
+     :'
+
+test_done
diff --git a/t/t1100-commit-tree-options.sh b/t/t1100-commit-tree-options.sh
new file mode 100644 (file)
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 100644 (file)
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/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 100644 (file)
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 100644 (file)
index 0000000..5119323
--- /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 N     AA
+:000000 100644 0000000000000000000000000000000000000000 7e426fb079479fd67f6d81f984e4ec649a44bc25 N     AN
+:100644 000000 bcc68ef997017466d5c9094bcf7692295f588c9a 0000000000000000000000000000000000000000 D     DD
+:000000 040000 0000000000000000000000000000000000000000 6d50f65d3bdab91c63444294d38f08aeff328e42 N     DF
+:100644 000000 141c1f1642328e4bc46a7d801a71da392e66791e 0000000000000000000000000000000000000000 D     DM
+:100644 000000 35abde1506ddf806572ff4d407bd06885d0f8ee9 0000000000000000000000000000000000000000 D     DN
+:000000 100644 0000000000000000000000000000000000000000 1d41122ebdd7a640f29d3c9cc4f9d70094374762 N     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 N     AA
+:000000 100644 0000000000000000000000000000000000000000 7e426fb079479fd67f6d81f984e4ec649a44bc25 N     AN
+:100644 000000 bcc68ef997017466d5c9094bcf7692295f588c9a 0000000000000000000000000000000000000000 D     DD
+:000000 100644 0000000000000000000000000000000000000000 68a6d8b91da11045cf4aa3a5ab9f2a781c701249 N     DF/DF
+:100644 000000 141c1f1642328e4bc46a7d801a71da392e66791e 0000000000000000000000000000000000000000 D     DM
+:100644 000000 35abde1506ddf806572ff4d407bd06885d0f8ee9 0000000000000000000000000000000000000000 D     DN
+:000000 100644 0000000000000000000000000000000000000000 1d41122ebdd7a640f29d3c9cc4f9d70094374762 N     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 N     Z/AA
+:000000 100644 0000000000000000000000000000000000000000 087494262084cefee7ed484d20c8dc0580791272 N     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 N     AA
+:100644 000000 bcc68ef997017466d5c9094bcf7692295f588c9a 0000000000000000000000000000000000000000 D     DD
+:000000 100644 0000000000000000000000000000000000000000 71420ab81e254145d26d6fc0cddee64c1acd4787 N     DF
+:100644 100644 141c1f1642328e4bc46a7d801a71da392e66791e 3c4d8de5fbad08572bab8e10eef8dbb264cf0231 M     DM
+:000000 100644 0000000000000000000000000000000000000000 1d41122ebdd7a640f29d3c9cc4f9d70094374762 N     LL
+:100644 000000 03f24c8c4700babccfd28b654e7e8eac402ad6cd 0000000000000000000000000000000000000000 D     MD
+:100644 100644 b258508afb7ceb449981bd9d63d2d3e971bf8d34 19989d4559aae417fedee240ccf2ba315ea4dc2b M     MM
+:000000 100644 0000000000000000000000000000000000000000 15885881ea69115351c09b38371f0348a3fb8c67 N     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 N     AA
+:100644 000000 bcc68ef997017466d5c9094bcf7692295f588c9a 0000000000000000000000000000000000000000 D     DD
+:000000 100644 0000000000000000000000000000000000000000 71420ab81e254145d26d6fc0cddee64c1acd4787 N     DF
+:100644 100644 141c1f1642328e4bc46a7d801a71da392e66791e 3c4d8de5fbad08572bab8e10eef8dbb264cf0231 M     DM
+:000000 100644 0000000000000000000000000000000000000000 1d41122ebdd7a640f29d3c9cc4f9d70094374762 N     LL
+:100644 000000 03f24c8c4700babccfd28b654e7e8eac402ad6cd 0000000000000000000000000000000000000000 D     MD
+:100644 100644 b258508afb7ceb449981bd9d63d2d3e971bf8d34 19989d4559aae417fedee240ccf2ba315ea4dc2b M     MM
+:000000 100644 0000000000000000000000000000000000000000 15885881ea69115351c09b38371f0348a3fb8c67 N     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 N     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 N     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 N     DF
+:040000 000000 6d50f65d3bdab91c63444294d38f08aeff328e42 0000000000000000000000000000000000000000 D     DF
+:000000 100644 0000000000000000000000000000000000000000 3c4d8de5fbad08572bab8e10eef8dbb264cf0231 N     DM
+:000000 100644 0000000000000000000000000000000000000000 35abde1506ddf806572ff4d407bd06885d0f8ee9 N     DN
+:100644 000000 103d9f89b50b9aad03054b579be5e7aa665f2d57 0000000000000000000000000000000000000000 D     MD
+:100644 100644 b431b272d829ff3aa4d1a5085f4394ab4d3305b6 19989d4559aae417fedee240ccf2ba315ea4dc2b M     MM
+:100644 100644 a716d58de4a570e0038f5c307bd8db34daea021f bd084b0c27c7b6cc34f11d6d0509a29be3caf970 M     MN
+:000000 100644 0000000000000000000000000000000000000000 15885881ea69115351c09b38371f0348a3fb8c67 N     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 N     DF
+:100644 000000 68a6d8b91da11045cf4aa3a5ab9f2a781c701249 0000000000000000000000000000000000000000 D     DF/DF
+:000000 100644 0000000000000000000000000000000000000000 3c4d8de5fbad08572bab8e10eef8dbb264cf0231 N     DM
+:000000 100644 0000000000000000000000000000000000000000 35abde1506ddf806572ff4d407bd06885d0f8ee9 N     DN
+:100644 000000 103d9f89b50b9aad03054b579be5e7aa665f2d57 0000000000000000000000000000000000000000 D     MD
+:100644 100644 b431b272d829ff3aa4d1a5085f4394ab4d3305b6 19989d4559aae417fedee240ccf2ba315ea4dc2b M     MM
+:100644 100644 a716d58de4a570e0038f5c307bd8db34daea021f bd084b0c27c7b6cc34f11d6d0509a29be3caf970 M     MN
+:000000 100644 0000000000000000000000000000000000000000 15885881ea69115351c09b38371f0348a3fb8c67 N     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 N     Z/DM
+:000000 100644 0000000000000000000000000000000000000000 beb5d38c55283d280685ea21a0e50cfcc0ca064a N     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 N     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 100644 (file)
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 100644 (file)
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 100644 (file)
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 100644 (file)
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 100644 (file)
index 0000000..ab83ea3
--- /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 N     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 N     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 100644 (file)
index 0000000..040d0dd
--- /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 N     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 100644 (file)
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 100644 (file)
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 100644 (file)
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/t5000-tar-tree.sh b/t/t5000-tar-tree.sh
new file mode 100644 (file)
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/t5100-delta-pull.sh b/t/t5100-delta-pull.sh
new file mode 100644 (file)
index 0000000..8693c5c
--- /dev/null
@@ -0,0 +1,79 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+
+test_description='Test pulling deltified objects
+
+'
+. ./test-lib.sh
+
+locate_obj='s|\(..\)|.git/objects/\1/|'
+
+test_expect_success \
+    setup \
+    'cat ../README >a &&
+    git-update-cache --add a &&
+    a0=`git-ls-files --stage |
+        sed -e '\''s/^[0-7]* \([0-9a-f]*\) .*/\1/'\''` &&
+
+    sed -e 's/test/TEST/g' ../README >a &&
+    git-update-cache a &&
+    a1=`git-ls-files --stage |
+        sed -e '\''s/^[0-7]* \([0-9a-f]*\) .*/\1/'\''` &&
+    tree=`git-write-tree` &&
+    commit=`git-commit-tree $tree </dev/null` &&
+    a0f=`echo "$a0" | sed -e "$locate_obj"` &&
+    a1f=`echo "$a1" | sed -e "$locate_obj"` &&
+    echo commit $commit &&
+    echo a0 $a0 &&
+    echo a1 $a1 &&
+    ls -l $a0f $a1f &&
+    echo $commit >.git/HEAD &&
+    git-mkdelta -v $a0 $a1 &&
+    ls -l $a0f $a1f'
+
+# Now commit has a tree that records delitified "a" whose SHA1 is a1.
+# Create a new repo and pull this commit into it.
+
+test_expect_success \
+    'setup and cd into new repo' \
+    'mkdir dest && cd dest && rm -fr .git && git-init-db'
+     
+test_expect_success \
+    'pull from deltified repo into a new repo without -d' \
+    'rm -fr .git a && git-init-db &&
+     git-local-pull -v -a $commit ../.git/ &&
+     git-cat-file blob $a1 >a &&
+     diff -u a ../a'
+
+test_expect_failure \
+    'pull from deltified repo into a new repo with -d' \
+    'rm -fr .git a && git-init-db &&
+     git-local-pull -v -a -d $commit ../.git/ &&
+     git-cat-file blob $a1 >a &&
+     diff -u a ../a'
+
+test_expect_failure \
+    'pull from deltified repo after delta failure without --recover' \
+    'rm -f a &&
+     git-local-pull -v -a $commit ../.git/ &&
+     git-cat-file blob $a1 >a &&
+     diff -u a ../a'
+
+test_expect_success \
+    'pull from deltified repo after delta failure with --recover' \
+    'rm -f a &&
+     git-local-pull -v -a --recover $commit ../.git/ &&
+     git-cat-file blob $a1 >a &&
+     diff -u a ../a'
+
+test_expect_success \
+    'missing-tree or missing-blob should be re-fetched without --recover' \
+    'rm -f a $a0f $a1f &&
+     git-local-pull -v -a $commit ../.git/ &&
+     git-cat-file blob $a1 >a &&
+     diff -u a ../a'
+
+test_done
+
diff --git a/t/t6001-rev-list-merge-order.sh b/t/t6001-rev-list-merge-order.sh
new file mode 100644 (file)
index 0000000..a996441
--- /dev/null
@@ -0,0 +1,489 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Jon Seymour
+#
+
+test_description='Tests git-rev-list --merge-order functionality'
+
+. ./test-lib.sh
+
+#
+# TODO: move the following block (upto --- end ...) into testlib.sh
+#
+[ -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
+       sed_script="s/$(tag $_tag)/$_tag/g${sed_script+;}$sed_script"
+}
+
+# Replace unhelpful sha1 hashses with their symbolic equivalents 
+entag()
+{
+       sed "$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 "$@" | 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" 
+}
+
+# --- end of stuff to move ---
+
+# 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
+#
+# 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
+
+#
+# cd to t/trash and use 
+#
+#    git-rev-list ... 2>&1 | sed "$(cat sed.script)" 
+#
+# if you ever want to manually debug the operation of git-rev-list
+#
+echo $sed_script > sed.script
+
+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 | sort' <<EOF
+a4
+b2
+b3
+b4
+c1
+c2
+c3
+l3
+l4
+l5
+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_done
diff --git a/t/test-lib.sh b/t/test-lib.sh
new file mode 100755 (executable)
index 0000000..d3f71d1
--- /dev/null
@@ -0,0 +1,150 @@
+#!/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: $@"
+       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..2b25fc0
--- /dev/null
+++ b/tag.c
@@ -0,0 +1,93 @@
+#include "tag.h"
+#include "cache.h"
+
+const char *tag_type = "tag";
+
+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..3e8c32d
--- /dev/null
+++ b/tag.h
@@ -0,0 +1,19 @@
+#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);
+
+#endif /* TAG_H */
diff --git a/tar-tree.c b/tar-tree.c
new file mode 100644 (file)
index 0000000..673ac66
--- /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);
+
+       printf(&header[329], "%07o", 0);        /* devmajor */
+       printf(&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/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..8751e27
--- /dev/null
@@ -0,0 +1,79 @@
+/*
+ * 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]);
+               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]);
+               return 1;
+       }
+       close(fd);
+
+       if (argv[1][1] == 'd')
+               out_buf = diff_delta(from_buf, from_size,
+                                    data_buf, data_size, &out_size);
+       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/tree.c b/tree.c
new file mode 100644 (file)
index 0000000..2432f09
--- /dev/null
+++ b/tree.c
@@ -0,0 +1,164 @@
+#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);
+}
+
+static int read_tree_recursive(void *buffer, unsigned long size,
+                              const char *base, int baselen, int stage)
+{
+       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 (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);
+                       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)
+{
+       return read_tree_recursive(buffer, size, "", 0, stage);
+}
+
+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->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..74fe09d
--- /dev/null
+++ b/tree.h
@@ -0,0 +1,33 @@
+#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 int mode;
+       char *name;
+       union {
+               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/update-cache.c b/update-cache.c
new file mode 100644 (file)
index 0000000..7c2698d
--- /dev/null
@@ -0,0 +1,413 @@
+/*
+ * 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;
+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) < 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 (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 match_data(int fd, void *buffer, unsigned long size)
+{
+       while (size) {
+               char compare[1024];
+               int ret = read(fd, compare, sizeof(compare));
+
+               if (ret <= 0 || ret > size || memcmp(buffer, compare, ret))
+                       return -1;
+               size -= ret;
+               buffer += ret;
+       }
+       return 0;
+}
+
+static int compare_data(struct cache_entry *ce, unsigned long expected_size)
+{
+       int match = -1;
+       int fd = open(ce->name, O_RDONLY);
+
+       if (fd >= 0) {
+               void *buffer;
+               unsigned long size;
+               char type[20];
+
+               buffer = read_sha1_file(ce->sha1, type, &size);
+               if (buffer) {
+                       if (size == expected_size && !strcmp(type, "blob"))
+                               match = match_data(fd, buffer, size);
+                       free(buffer);
+               }
+               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.st_size))
+                       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, "--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/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/write-blob.c b/write-blob.c
new file mode 100644 (file)
index 0000000..8bfd576
--- /dev/null
@@ -0,0 +1,25 @@
+/*
+ * GIT - The information manager from hell
+ *
+ * Copyright (C) Linus Torvalds, 2005
+ */
+#include "cache.h"
+
+int main(int argc, char **argv)
+{
+       int i;
+
+       for (i = 1 ; i < argc; i++) {
+               char *path = argv[i];
+               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) < 0)
+                       die("Unable to add blob %s to database", path);
+               printf("%s\n", sha1_to_hex(sha1));
+       }
+       return 0;
+}
diff --git a/write-tree.c b/write-tree.c
new file mode 100644 (file)
index 0000000..b8bf883
--- /dev/null
@@ -0,0 +1,141 @@
+/*
+ * GIT - The information manager from hell
+ *
+ * Copyright (C) Linus Torvalds, 2005
+ */
+#include "cache.h"
+
+static int check_valid_sha1(unsigned char *sha1)
+{
+       char *filename = sha1_file_name(sha1);
+       int ret;
+
+       /* If we were anal, we'd check that the sha1 of the contents actually matches */
+       ret = access(filename, R_OK);
+       if (ret)
+               perror(filename);
+       return ret;
+}
+
+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 (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 (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;
+}