]> git.ipfire.org Git - thirdparty/git.git/commitdiff
GIT 0.99.9a v0.99.9a
authorJunio C Hamano <junkio@cox.net>
Mon, 31 Oct 2005 02:06:39 +0000 (18:06 -0800)
committerJunio C Hamano <junkio@cox.net>
Mon, 31 Oct 2005 02:06:39 +0000 (18:06 -0800)
... to contain the RPM workaround.

Signed-off-by: Junio C Hamano <junkio@cox.net>
197 files changed:
.gitignore
Documentation/Makefile
Documentation/cvs-migration.txt
Documentation/diff-format.txt
Documentation/diff-options.txt
Documentation/diffcore.txt
Documentation/git-add.txt
Documentation/git-am.txt [new file with mode: 0644]
Documentation/git-apply.txt
Documentation/git-applymbox.txt
Documentation/git-archimport.txt
Documentation/git-cat-file.txt
Documentation/git-check-ref-format.txt [new file with mode: 0644]
Documentation/git-checkout-index.txt
Documentation/git-checkout.txt
Documentation/git-cherry-pick.txt
Documentation/git-clone-pack.txt
Documentation/git-clone.txt
Documentation/git-commit-tree.txt
Documentation/git-commit.txt
Documentation/git-convert-objects.txt
Documentation/git-cvsimport.txt
Documentation/git-daemon.txt
Documentation/git-diff-files.txt
Documentation/git-diff-index.txt
Documentation/git-diff-stages.txt
Documentation/git-diff-tree.txt
Documentation/git-fetch-pack.txt
Documentation/git-fetch.txt
Documentation/git-format-patch.txt
Documentation/git-fsck-objects.txt
Documentation/git-grep.txt
Documentation/git-hash-object.txt
Documentation/git-http-fetch.txt
Documentation/git-index-pack.txt [new file with mode: 0644]
Documentation/git-init-db.txt
Documentation/git-local-fetch.txt
Documentation/git-log.txt
Documentation/git-ls-files.txt
Documentation/git-ls-remote.txt
Documentation/git-ls-tree.txt
Documentation/git-mailsplit.txt
Documentation/git-merge-base.txt
Documentation/git-merge-index.txt
Documentation/git-merge-one-file.txt
Documentation/git-merge.txt
Documentation/git-mktag.txt
Documentation/git-mv.txt [new file with mode: 0644]
Documentation/git-name-rev.txt [new file with mode: 0644]
Documentation/git-octopus.txt
Documentation/git-pack-objects.txt
Documentation/git-patch-id.txt
Documentation/git-peek-remote.txt
Documentation/git-prune-packed.txt
Documentation/git-prune.txt
Documentation/git-pull.txt
Documentation/git-push.txt
Documentation/git-read-tree.txt
Documentation/git-receive-pack.txt
Documentation/git-rename.txt
Documentation/git-repack.txt
Documentation/git-resolve.txt
Documentation/git-rev-list.txt
Documentation/git-rev-parse.txt
Documentation/git-send-email.txt
Documentation/git-send-pack.txt
Documentation/git-shell.txt [new file with mode: 0644]
Documentation/git-shortlog.txt
Documentation/git-show-branch.txt
Documentation/git-show-index.txt
Documentation/git-ssh-fetch.txt
Documentation/git-ssh-upload.txt
Documentation/git-status.txt
Documentation/git-svnimport.txt [new file with mode: 0644]
Documentation/git-tag.txt
Documentation/git-tar-tree.txt
Documentation/git-unpack-file.txt
Documentation/git-unpack-objects.txt
Documentation/git-update-index.txt
Documentation/git-update-server-info.txt
Documentation/git-upload-pack.txt
Documentation/git-var.txt
Documentation/git-verify-pack.txt
Documentation/git-whatchanged.txt
Documentation/git-write-tree.txt
Documentation/git.txt
Documentation/glossary.txt
Documentation/hooks.txt
Documentation/pull-fetch-param.txt
Documentation/repository-layout.txt
Documentation/tutorial.txt
Makefile
apply.c
cache.h
checkout-index.c
clone-pack.c
commit-tree.c
commit.c
compat/mmap.c [new file with mode: 0644]
config.c [new file with mode: 0644]
connect.c
convert-objects.c
copy.c [new file with mode: 0644]
ctype.c [new file with mode: 0644]
daemon.c
date.c
debian/changelog
debian/control
diff-files.c
diff-index.c
diff-tree.c
diff.c
diff.h
entry.c
environment.c [new file with mode: 0644]
fetch-pack.c
fetch.c
fsck-objects.c
git-add.sh
git-am.sh [new file with mode: 0755]
git-applymbox.sh
git-applypatch.sh
git-checkout.sh
git-commit.sh
git-core.spec.in
git-count-objects.sh
git-cvsimport.perl
git-diff.sh
git-fetch.sh
git-findtags.perl [new file with mode: 0755]
git-format-patch.sh
git-ls-remote.sh
git-merge-recursive.py
git-merge.sh
git-mv.perl [new file with mode: 0755]
git-parse-remote.sh
git-prune.sh
git-push.sh
git-rename.perl
git-repack.sh
git-reset.sh
git-sh-setup.sh
git-shortlog.perl
git-status.sh
git-svnimport.perl [new file with mode: 0755]
git-tag.sh
git.sh
gitk
http-fetch.c
ident.c
index-pack.c [new file with mode: 0644]
init-db.c
local-fetch.c
ls-files.c
ls-tree.c
mailsplit.c
name-rev.c [new file with mode: 0644]
pack-objects.c
patch-id.c
prune-packed.c
quote.c
quote.h
read-cache.c
read-tree.c
refs.c
rev-list.c
rev-parse.c
rsh.c
server-info.c
sha1_file.c
sha1_name.c
shell.c [new file with mode: 0644]
show-branch.c
t/Makefile
t/diff-lib.sh
t/t0000-basic.sh
t/t1200-tutorial.sh [new file with mode: 0644]
t/t3300-funny-names.sh [new file with mode: 0755]
t/t4000-diff-format.sh
t/t4001-diff-rename.sh
t/t4004-diff-rename-symlink.sh
t/t5000-tar-tree.sh
t/t5300-pack-object.sh
t/t5500-fetch-pack.sh [new file with mode: 0644]
t/t5501-old-fetch-and-upload.sh [new file with mode: 0755]
t/t6001-rev-list-merge-order.sh
t/t6002-rev-list-bisect.sh
t/t6003-rev-list-topo-order.sh
t/test-lib.sh
templates/.gitignore
templates/Makefile
tree-diff.c [new file with mode: 0644]
update-index.c
update-ref.c
upload-pack.c
var.c
verify-pack.c

index e90e2c350371b4fdf865f2d63935b3681f09648f..927c89cbacccf39f12979d02f6f9f473ac13e8e4 100644 (file)
@@ -1,5 +1,6 @@
 git
 git-add
+git-am
 git-apply
 git-applymbox
 git-applypatch
@@ -7,9 +8,11 @@ git-archimport
 git-bisect
 git-branch
 git-cat-file
+git-check-ref-format
 git-checkout
 git-checkout-index
 git-cherry
+git-cherry-pick
 git-clone
 git-clone-pack
 git-commit
@@ -25,6 +28,7 @@ git-diff-stages
 git-diff-tree
 git-fetch
 git-fetch-pack
+git-findtags
 git-fmt-merge-msg
 git-format-patch
 git-fsck-objects
@@ -32,6 +36,7 @@ git-get-tar-commit-id
 git-grep
 git-hash-object
 git-http-fetch
+git-index-pack
 git-init-db
 git-local-fetch
 git-log
@@ -49,6 +54,8 @@ git-merge-recursive
 git-merge-resolve
 git-merge-stupid
 git-mktag
+git-name-rev
+git-mv
 git-octopus
 git-pack-objects
 git-parse-remote
@@ -73,6 +80,7 @@ git-revert
 git-send-email
 git-send-pack
 git-sh-setup
+git-shell
 git-shortlog
 git-show-branch
 git-show-index
@@ -82,6 +90,7 @@ git-ssh-push
 git-ssh-upload
 git-status
 git-stripspace
+git-svnimport
 git-symbolic-ref
 git-tag
 git-tar-tree
@@ -101,3 +110,6 @@ git-core-*/?*
 *.dsc
 *.deb
 git-core.spec
+*.exe
+libgit.a
+*.o
index bb21d6af44e6aa591c2adb33e0447853ac1c65c5..3cfa360a9e4876286bfb37230daca6af8c799725 100644 (file)
@@ -17,14 +17,14 @@ DOC_HTML += $(patsubst %,%.html,$(ARTICLES) $(SP_ARTICLES))
 DOC_MAN1=$(patsubst %.txt,%.1,$(MAN1_TXT))
 DOC_MAN7=$(patsubst %.txt,%.7,$(MAN7_TXT))
 
-prefix=$(HOME)
+prefix?=$(HOME)
 bin=$(prefix)/bin
 mandir=$(prefix)/man
 man1=$(mandir)/man1
 man7=$(mandir)/man7
 # DESTDIR=
 
-INSTALL=install
+INSTALL?=install
 
 #
 # Please note that there is a minor bug in asciidoc.
index 4361278dab8acc11e2090477fbebea232f6f29b8..57436f00783a7f6682431b24a5719effd04faf32 100644 (file)
@@ -1,6 +1,5 @@
-Git for CVS users
+git for CVS users
 =================
-v0.99.5, Aug 2005
 
 Ok, so you're a CVS user. That's ok, it's a treatable condition, and the
 first step to recovery is admitting you have a problem. The fact that
@@ -8,7 +7,7 @@ you are reading this file means that you may be well on that path
 already.
 
 The thing about CVS is that it absolutely sucks as a source control
-manager, and you'll thus be happy with almost anything else. Git,
+manager, and you'll thus be happy with almost anything else. git,
 however, may be a bit 'too' different (read: "good") for your taste, and
 does a lot of things differently. 
 
@@ -16,7 +15,7 @@ One particular suckage of CVS is very hard to work around: CVS is
 basically a tool for tracking 'file' history, while git is a tool for
 tracking 'project' history.  This sometimes causes problems if you are
 used to doing very strange things in CVS, in particular if you're doing
-things like making branches of just a subset of the project.  Git can't
+things like making branches of just a subset of the project.  git can't
 track that, since git never tracks things on the level of an individual
 file, only on the whole project level. 
 
@@ -33,7 +32,7 @@ and notes on converting from CVS to git.
 
 Second: CVS has the notion of a "repository" as opposed to the thing
 that you're actually working in (your working directory, or your
-"checked out tree").  Git does not have that notion at all, and all git
+"checked out tree").  git does not have that notion at all, and all git
 working directories 'are' the repositories.  However, you can easily
 emulate the CVS model by having one special "global repository", which
 people can synchronize with.  See details later, but in the meantime
@@ -50,7 +49,7 @@ gone through the git tutorial, and generally familiarized yourself with
 how to commit stuff etc in git) is to create a git'ified version of your
 CVS archive.
 
-Happily, that's very easy indeed. Git will do it for you, although git
+Happily, that's very easy indeed. git will do it for you, although git
 will need the help of a program called "cvsps":
 
        http://www.cobite.com/cvsps/
@@ -136,7 +135,7 @@ technically possible, and there are at least two specialized scripts out
 there that can be used to get equivalent information (see the git
 mailing list archives for details). 
 
-Git has a couple of alternatives, though, that you may find sufficient
+git has a couple of alternatives, though, that you may find sufficient
 or even superior depending on your use.  One is called "git-whatchanged"
 (for obvious reasons) and the other one is called "pickaxe" ("a tool for
 the software archeologist"). 
@@ -209,7 +208,7 @@ show anything for commits that do not touch this "if" statement.
 Also, in the original context, the same statement might have
 appeared at first in a different file and later the file was
 renamed to "a-file.c".  CVS annotate would not help you to go
-back across such a rename, but GIT would still help you in such
+back across such a rename, but git would still help you in such
 a situation.  For that, you can give the -C flag to
 git-diff-tree, like this:
 
index dacd8fb53488fadd7ebb6c1964e60a60072eae80..d1d0d2d3dc8760a8030313de3dd14a942d0fc2bf 100644 (file)
@@ -55,6 +55,11 @@ Example:
 :100644 100644 5be4a4...... 000000...... M file.c
 ------------------------------------------------
 
+When `-z` option is not used, TAB, LF, and backslash characters
+in pathnames are represented as `\t`, `\n`, and `\\`,
+respectively.
+
+
 Generating patches with -p
 --------------------------
 
@@ -106,7 +111,7 @@ For a path that is unmerged, 'GIT_EXTERNAL_DIFF' is called with 1
 parameter, <path>.
 
 
-Git specific extension to diff format
+git specific extension to diff format
 -------------------------------------
 
 What -p option produces is slightly different from the
@@ -137,3 +142,7 @@ the file that rename/copy produces, respectively.
        rename to <path>
        similarity index <number>
        dissimilarity index <number>
+       index <hash>..<hash> <mode>
+
+3.  TAB, LF, and backslash characters in pathnames are
+    represented as `\t`, `\n`, and `\\`, respectively.
index 06500d04b7f56507cd8bc379cb34b91b7056bf18..32005b03f3bee40413d7ff0d33e143de85b194c6 100644 (file)
@@ -4,10 +4,6 @@
 -u::
        Synonym for "-p".
 
--r::
-       Look recursively in subdirectories; only used by "git-diff-tree";
-       other diff commands always work recursively.
-
 -z::
        \0 line termination on output
 
index 9d20a4ff19bc42497b15220cb3ad5a04594f6753..cb4e562004e58439a0055d9ed6a6bdab249dfcdc 100644 (file)
@@ -6,13 +6,12 @@ June 2005
 Introduction
 ------------
 
-The diff commands git-diff-index, git-diff-files, and
-git-diff-tree can be told to manipulate differences they find
-in unconventional ways before showing diff(1) output.  The
-manipulation is collectively called "diffcore transformation".
-This short note describes what they are and how to use them to
-produce diff outputs that are easier to understand than the
-conventional kind.
+The diff commands git-diff-index, git-diff-files, git-diff-tree, and
+git-diff-stages can be told to manipulate differences they find in
+unconventional ways before showing diff(1) output.  The manipulation
+is collectively called "diffcore transformation".  This short note
+describes what they are and how to use them to produce diff outputs
+that are easier to understand than the conventional kind.
 
 
 The chain of operation
@@ -29,7 +28,10 @@ files:
  - git-diff-files compares contents of the index file and the
    working directory;
 
- - git-diff-tree compares contents of two "tree" objects.
+ - git-diff-tree compares contents of two "tree" objects;
+
+ - git-diff-stages compares contents of blobs at two stages in an
+   unmerged index file.
 
 In all of these cases, the commands themselves compare
 corresponding paths in the two sets of files.  The result of
@@ -65,14 +67,23 @@ format sections of the manual for git-diff-\* commands) or
 diff-patch format.
 
 
-diffcore-pathspec
------------------
+diffcore-pathspec: For Ignoring Files Outside Our Consideration
+---------------------------------------------------------------
 
 The first transformation in the chain is diffcore-pathspec, and
 is controlled by giving the pathname parameters to the
 git-diff-* commands on the command line.  The pathspec is used
 to limit the world diff operates in.  It removes the filepairs
-outside the specified set of pathnames.
+outside the specified set of pathnames.  E.g. If the input set 
+of filepairs included:
+
+------------------------------------------------
+:100644 100644 bcd1234... 0123456... M junkfile
+------------------------------------------------
+
+but the command invocation was "git-diff-files myfile", then the
+junkfile entry would be removed from the list because only "myfile"
+is under consideration.
 
 Implementation note.  For performance reasons, git-diff-tree
 uses the pathname parameters on the command line to cull set of
@@ -80,8 +91,8 @@ filepairs it feeds the diffcore mechanism itself, and does not
 use diffcore-pathspec, but the end result is the same.
 
 
-diffcore-break
---------------
+diffcore-break: For Splitting Up "Complete Rewrites"
+----------------------------------------------------
 
 The second transformation in the chain is diffcore-break, and is
 controlled by the -B option to the git-diff-* commands.  This is
@@ -115,8 +126,8 @@ the original is used), and can be customized by giving a number
 after "-B" option (e.g. "-B75" to tell it to use 75%).
 
 
-diffcore-rename
----------------
+diffcore-rename: For Detection Renames and Copies
+-------------------------------------------------
 
 This transformation is used to detect renames and copies, and is
 controlled by the -M option (to detect renames) and the -C option
@@ -136,16 +147,16 @@ merges these filepairs and creates:
 :100644 100644 0123456... 0123456... R100 fileX file0
 ------------------------------------------------
 
-When the "-C" option is used, the original contents of modified
-files and contents of unchanged files are considered as
-candidates of the source files in rename/copy operation, in
-addition to the deleted files.  If the input were like these
-filepairs, that talk about a modified file fileY and a newly
+When the "-C" option is used, the original contents of modified files,
+and deleted files (and also unmodified files, if the
+"\--find-copies-harder" option is used) are considered as candidates
+of the source files in rename/copy operation.  If the input were like
+these filepairs, that talk about a modified file fileY and a newly
 created file file0:
 
 ------------------------------------------------
 :100644 100644 0123456... 1234567... M fileY
-:000000 100644 0000000... 0123456... A file0
+:000000 100644 0000000... bcd3456... A file0
 ------------------------------------------------
 
 the original contents of fileY and the resulting contents of
@@ -154,14 +165,14 @@ changed to:
 
 ------------------------------------------------
 :100644 100644 0123456... 1234567... M fileY
-:100644 100644 0123456... 0123456... C100 fileY file0
+:100644 100644 0123456... bcd3456... C100 fileY file0
 ------------------------------------------------
 
 In both rename and copy detection, the same "extent of changes"
 algorithm used in diffcore-break is used to determine if two
 files are "similar enough", and can be customized to use
-similarity score different from the default 50% by giving a
-number after "-M" or "-C" option (e.g. "-M8" to tell it to use
+a similarity score different from the default of 50% by giving a
+number after the "-M" or "-C" option (e.g. "-M8" to tell it to use
 8/10 = 80%).
 
 Note.  When the "-C" option is used with `\--find-copies-harder`
@@ -173,8 +184,8 @@ git-diff-\* commands can detect copies only if the file that was
 copied happened to have been modified in the same changeset.
 
 
-diffcore-merge-broken
----------------------
+diffcore-merge-broken: For Putting "Complete Rewrites" Back Together
+--------------------------------------------------------------------
 
 This transformation is used to merge filepairs broken by
 diffcore-break, and not transformed into rename/copy by
@@ -215,8 +226,8 @@ prefixed with '-', followed by the entire contents of new
 version prefixed with '+'.
 
 
-diffcore-pickaxe
-----------------
+diffcore-pickaxe: For Detecting Addition/Deletion of Specified String
+---------------------------------------------------------------------
 
 This transformation is used to find filepairs that represent
 changes that touch a specified string, and is controlled by the
@@ -230,7 +241,7 @@ string appeared in this changeset".  It also checks for the
 opposite case that loses the specified string.
 
 When `\--pickaxe-all` is not in effect, diffcore-pickaxe leaves
-only such filepairs that touches the specified string in its
+only such filepairs that touch the specified string in its
 output.  When `\--pickaxe-all` is used, diffcore-pickaxe leaves all
 filepairs intact if there is such a filepair, or makes the
 output empty otherwise.  The latter behaviour is designed to
@@ -238,19 +249,19 @@ make reviewing of the changes in the context of the whole
 changeset easier.
 
 
-diffcore-order
---------------
+diffcore-order: For Sorting the Output Based on Filenames
+---------------------------------------------------------
 
 This is used to reorder the filepairs according to the user's
 (or project's) taste, and is controlled by the -O option to the
 git-diff-* commands.
 
-This takes a text file each of whose line is a shell glob
+This takes a text file each of whose lines is a shell glob
 pattern.  Filepairs that match a glob pattern on an earlier line
 in the file are output before ones that match a later line, and
 filepairs that do not match any glob pattern are output last.
 
-As an example, typical orderfile for the core GIT probably
+As an example, a typical orderfile for the core git probably
 would look like this:
 
 ------------------------------------------------
index 4a03b4cfc6a2e497ceb5d61284928904af2877e1..32300297d6b04f71679b6052945a4c77a7fc640b 100644 (file)
@@ -7,7 +7,7 @@ git-add - Add files to the cache.
 
 SYNOPSIS
 --------
-'git-add' <file>...
+'git-add' [-n] [-v] <file>...
 
 DESCRIPTION
 -----------
@@ -19,6 +19,13 @@ OPTIONS
 <file>...::
        Files to add to the cache.
 
+-n::
+        Don't actually add the file(s), just show if they exist.
+
+-v::
+        Be verbose.
+
+
 Author
 ------
 Written by Linus Torvalds <torvalds@osdl.org>
diff --git a/Documentation/git-am.txt b/Documentation/git-am.txt
new file mode 100644 (file)
index 0000000..704dc51
--- /dev/null
@@ -0,0 +1,90 @@
+git-am(1)
+=========
+
+NAME
+----
+git-am - Apply a series of patches in a mailbox
+
+
+SYNOPSIS
+--------
+'git-am' [--signoff] [--dotest=<dir>] [--utf8] [--3way] <mbox>...
+'git-am' [--skip]
+
+DESCRIPTION
+-----------
+Splits mail messages in a mailbox into commit log message,
+authorship information and patches, and applies them to the
+current branch.
+
+OPTIONS
+-------
+--signoff::
+       Add `Signed-off-by:` line to the commit message, using
+       the committer identity of yourself.
+
+--dotest=<dir>::
+       Instead of `.dotest` directory, use <dir> as a working
+       area to store extracted patches.
+
+--utf8, --keep::
+       Pass `--utf8` and `--keep` flags to `git-mailinfo` (see
+       gitlink:git-mailinfo[1]).
+
+--3way::
+       When the patch does not apply cleanly, fall back on
+       3-way merge, if the patch records the identity of blobs
+       it is supposed to apply to, and we have those blobs
+       locally.
+
+--skip::
+       Skip the current patch.  This is only meaningful when
+       restarting an aborted patch.
+
+--interactive::
+       Run interactively, just like git-applymbox.
+
+
+DISCUSSION
+----------
+
+When initially invoking it, you give it names of the mailboxes
+to crunch.  Upon seeing the first patch that does not apply, it
+aborts in the middle, just like 'git-applymbox' does.  You can
+recover from this in one of two ways:
+
+. skip the current one by re-running the command with '--skip'
+  option.
+
+. hand resolve the conflict in the working directory, run 'git
+  diff HEAD' to extract the merge result into a patch form and
+  replacing the patch in .dotest/patch file.  After doing this,
+  run `git-reset --hard HEAD` to bring the working tree to the
+  state before half-applying the patch, then re-run the command
+  without any options.
+
+The command refuses to process new mailboxes while `.dotest`
+directory exists, so if you decide to start over from scratch,
+run `rm -f .dotest` before running the command with mailbox
+names.
+
+
+SEE ALSO
+--------
+gitlink:git-applymbox[1], gitlink:git-applypatch[1].
+
+
+Author
+------
+Written by Junio C Hamano <junkio@cox.net>
+
+Documentation
+--------------
+Documentation by Petr Baudis, Junio C Hamano and the git-list <git@vger.kernel.org>.
+
+This manual page is a stub. You can help the git documentation by expanding it.
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
index 8cbbb4b85366f644fcbf8db1fe9780ea0f424e14..eb8f90683724c69f1a47dfcc53d93e1d6accb3d0 100644 (file)
@@ -1,15 +1,14 @@
 git-apply(1)
 ============
-v0.1, June 2005
 
 NAME
 ----
-git-apply - Apply patch on a GIT index file and a work tree
+git-apply - Apply patch on a git index file and a work tree
 
 
 SYNOPSIS
 --------
-'git-apply' [--no-merge] [--stat] [--summary] [--check] [--index] [--show-files] [--apply] [<patch>...]
+'git-apply' [--stat] [--numstat] [--summary] [--check] [--index] [--apply] [--index-info] [-z] [<patch>...]
 
 DESCRIPTION
 -----------
@@ -22,15 +21,16 @@ OPTIONS
        The files to read patch from.  '-' can be used to read
        from the standard input.
 
---no-merge::
-       The default mode of operation is the merge behaviour
-       which is not implemented yet.  This flag explicitly
-       tells the program not to use the merge behaviour.
-
 --stat::
        Instead of applying the patch, output diffstat for the
        input.  Turns off "apply".
 
+--numstat::
+       Similar to \--stat, but shows number of added and
+       deleted lines in decimal notation and pathname without
+       abbreviation, to make it more machine friendly.  Turns
+       off "apply".
+
 --summary::
        Instead of applying the patch, output a condensed
        summary of information obtained from git diff extended
@@ -51,8 +51,19 @@ OPTIONS
        up-to-date, it is flagged as an error.  This flag also
        causes the index file to be updated.
 
---show-files::
-       Show summary of files that are affected by the patch.
+--index-info::
+       Newer git-diff output has embedded 'index information'
+       for each blob to help identify the original version that
+       the patch applies to.  When this flag is given, and if
+       the original version of the blob is available locally,
+       outputs information about them to the standard output.
+
+-z::
+       When showing the index information, do not munge paths,
+       but use NUL terminated machine readable format.  Without
+       this flag, the pathnames output will have TAB, LF, and
+       backslash characters replaced with `\t`, `\n`, and `\\`,
+       respectively.
 
 --apply::
        If you use any of the options marked ``Turns off
index bb543788c0c24d33e6fd2f163f46d791f1c0ae88..f74c6a49b3c5d52ed662a83832c7d06915fac65b 100644 (file)
@@ -8,7 +8,7 @@ git-applymbox - Apply a series of patches in a mailbox
 
 SYNOPSIS
 --------
-'git-applymbox' [-u] [-k] [-q] ( -c .dotest/<num> | <mbox> ) [ <signoff> ]
+'git-applymbox' [-u] [-k] [-q] [-m] ( -c .dotest/<num> | <mbox> ) [ <signoff> ]
 
 DESCRIPTION
 -----------
@@ -33,6 +33,14 @@ OPTIONS
        munging, and is most useful when used to read back 'git
        format-patch --mbox' output.
 
+-m::
+       Patches are applied with `git-apply` command, and unless
+       it cleanly applies without fuzz, the processing fails.
+       With this flag, if a tree that the patch applies cleanly
+       is found in a repository, the patch is applied to the
+       tree and then a 3-way merge between the resulting tree
+       and the current tree.
+
 -u::
        By default, the commit log message, author name and
        author email are taken from the e-mail without any
@@ -67,7 +75,7 @@ OPTIONS
 
 SEE ALSO
 --------
-gitlink:git-applypatch[1].
+gitlink:git-am[1], gitlink:git-applypatch[1].
 
 
 Author
index b6793cf55e56243e9ccfabe83161348bbab9dc1e..fcda0125af92ff9c3d72d711a78d23118787608c 100644 (file)
@@ -3,7 +3,7 @@ git-archimport(1)
 
 NAME
 ----
-git-archimport - Import an Arch repository into GIT
+git-archimport - Import an Arch repository into git
 
 
 SYNOPSIS
@@ -40,14 +40,14 @@ incremental imports.
 
 MERGES
 ------
-Patch merge data from Arch is used to mark merges in GIT as well. GIT 
+Patch merge data from Arch is used to mark merges in git as well. git 
 does not care much about tracking patches, and only considers a merge when a
 branch incorporates all the commits since the point they forked. The end result
-is that GIT will have a good idea of how far branches have diverged. So the 
+is that git will have a good idea of how far branches have diverged. So the 
 import process does lose some patch-trading metadata.
 
 Fortunately, when you try and merge branches imported from Arch, 
-GIT will find a good merge base, and it has a good chance of identifying 
+git will find a good merge base, and it has a good chance of identifying 
 patches that have been traded out-of-sequence between the branches. 
 
 OPTIONS
index f21a6e06aa2ca762ad3ae20d756c569f4210652e..ab4dcae21caa190a41c19ec24b4dc2d71a5a73fb 100644 (file)
@@ -1,6 +1,5 @@
 git-cat-file(1)
 ===============
-v0.1, May 2005
 
 NAME
 ----
diff --git a/Documentation/git-check-ref-format.txt b/Documentation/git-check-ref-format.txt
new file mode 100644 (file)
index 0000000..636e951
--- /dev/null
@@ -0,0 +1,50 @@
+git-check-ref-format(1)
+=======================
+
+NAME
+----
+git-check-ref-format - Make sure ref name is well formed.
+
+SYNOPSIS
+--------
+'git-check-ref-format' <refname>
+
+DESCRIPTION
+-----------
+Checks if a given 'refname' is acceptable, and exits non-zero if
+it is not.
+
+A reference is used in git to specify branches and tags.  A
+branch head is stored under `$GIT_DIR/refs/heads` directory, and
+a tag is stored under `$GIT_DIR/refs/tags` directory.  git
+imposes the following rules on how refs are named:
+
+. It could be named hierarchically (i.e. separated with slash
+  `/`), but each of its component cannot begin with a dot `.`;
+
+. It cannot have two consecutive dots `..` anywhere;
+
+. It cannot have ASCII control character (i.e. bytes whose
+  values are lower than \040, or \177 `DEL`), space, tilde `~`,
+  caret `{caret}`, or colon `:` anywhere;
+
+. It cannot end with a slash `/`.
+
+These rules makes it easy for shell script based tools to parse
+refnames, and also avoids ambiguities in certain refname
+expressions (see gitlink:git-rev-parse[1]).  Namely:
+
+. double-dot `..` are often used as in `ref1..ref2`, and in some
+  context this notation means `{caret}ref1 ref2` (i.e. not in
+  ref1 and in ref2).
+
+. tilde `~` and caret `{caret}` are used to introduce postfix
+  'nth parent' and 'peel onion' operation.
+
+. colon `:` is used as in `srcref:dstref` to mean "use srcref\'s
+  value and store it in dstref" in fetch and push operations.
+
+
+GIT
+---
+Part of the gitlink:git[7] suite
index 70645ae8923cd84a989d3226423517c72c7ab242..589dc9ad12943767c73658904d54a574d6592f54 100644 (file)
@@ -1,6 +1,5 @@
 git-checkout-index(1)
 =====================
-v0.1, May 2005
 
 NAME
 ----
index f753c149a5f2c45bd57742475e9d81a25c309a2c..b7bb1b4c74ad3f0e06ddf23487b7735cb321952b 100644 (file)
@@ -7,12 +7,24 @@ git-checkout - Checkout and switch to a branch.
 
 SYNOPSIS
 --------
-'git-checkout' [-f] [-b <new_branch>] [<branch>]
+'git-checkout' [-f] [-b <new_branch>] [<branch>] [<paths>...]
 
 DESCRIPTION
 -----------
-Updates the index and working tree to reflect the specified branch,
-<branch>. Updates HEAD to be <branch> or, if specified, <new_branch>.
+
+When <paths> are not given, this command switches branches, by
+updating the index and working tree to reflect the specified
+branch, <branch>, and updating HEAD to be <branch> or, if
+specified, <new_branch>.
+
+When <paths> are given, this command does *not* switch
+branches.  It updates the named paths in the working tree from
+the index file (i.e. it runs `git-checkout-index -f -u`).  In
+this case, `-f` and `-b` options are meaningless and giving
+either of them results in an error.  <branch> argument can be
+used to specify a specific tree-ish to update the index for the
+given paths before updating the working tree.
+
 
 OPTIONS
 -------
@@ -29,6 +41,30 @@ OPTIONS
        Branch to checkout; may be any object ID that resolves to a
        commit. Defaults to HEAD.
 
+
+EXAMPLE
+-------
+
+The following sequence checks out the `master` branch, reverts
+the `Makefile` to two revisions back, deletes hello.c by
+mistake, and gets it back from the index.
+
+------------
+$ git checkout master
+$ git checkout master~2 Makefile
+$ rm -f hello.c
+$ git checkout hello.c
+------------
+
+If you have an unfortunate branch that is named `hello.c`, the
+last step above would be confused as an instruction to switch to
+that branch.  You should instead write:
+
+------------
+$ git checkout -- hello.c
+------------
+
+
 Author
 ------
 Written by Linus Torvalds <torvalds@osdl.org>
index 8462e068531140debbcd0bc08293ec20528972b4..26e04677972f410cbec93c72ba2210c28d1f39bc 100644 (file)
@@ -1,6 +1,5 @@
 git-cherry-pick(1)
 ==================
-v0.99.5 Aug 2005
 
 NAME
 ----
index a7868e53d72a02fde93cc3665c162341b5dac847..cfc7b62f31b6470b9d9011b49d73d135a0623dd9 100644 (file)
@@ -1,6 +1,5 @@
 git-clone-pack(1)
 =================
-v0.1, July 2005
 
 NAME
 ----
@@ -9,27 +8,23 @@ git-clone-pack - Clones a repository by receiving packed objects.
 
 SYNOPSIS
 --------
-'git-clone-pack' [-q] [--exec=<git-upload-pack>] [<host>:]<directory> [<head>...]
+'git-clone-pack' [--exec=<git-upload-pack>] [<host>:]<directory> [<head>...]
 
 DESCRIPTION
 -----------
 Clones a repository into the current repository by invoking
 'git-upload-pack', possibly on the remote host via ssh, in
-the named repository, and invoking 'git-unpack-objects' locally
-to receive the pack.
+the named repository, and stores the sent pack in the local
+repository.
 
 OPTIONS
 -------
--q::
-       Pass '-q' flag to 'git-unpack-objects'; this makes the
-       cloning process less verbose.
-
 --exec=<git-upload-pack>::
        Use this to specify the path to 'git-upload-pack' on the
        remote side, if it is not found on your $PATH.
        Installations of sshd ignore the user's environment
        setup scripts for login shells (e.g. .bash_profile) and
-       your privately installed GIT may not be found on the system
+       your privately installed git may not be found on the system
        default $PATH.  Another workaround suggested is to set
        up your $PATH in ".bashrc", but this flag is for people
        who do not want to pay the overhead for non-interactive
index 7d713c73858e1c786d61ec1359241c658ef012b4..dd92cdefc2a2c504aea8ca23ce0e01746020036f 100644 (file)
@@ -1,6 +1,5 @@
 git-clone(1)
 ============
-v0.1, July 2005
 
 NAME
 ----
@@ -9,7 +8,7 @@ git-clone - Clones a repository.
 
 SYNOPSIS
 --------
-'git clone' [-l [-s]] [-q] [-n] [-u <upload-pack>] <repository> <directory>
+'git-clone' [-l [-s]] [-q] [-n] [-u <upload-pack>] <repository> <directory>
 
 DESCRIPTION
 -----------
index 9aba868fa4c7f41fa03bcaac89bd65ca7fbbf394..b64cd6a84b7f08164b55445999a12460a3d3c52c 100644 (file)
@@ -1,6 +1,5 @@
 git-commit-tree(1)
 ==================
-v0.1, May 2005
 
 NAME
 ----
@@ -49,8 +48,8 @@ A commit encapsulates:
 - committer name and email and the commit time.
 
 If not provided, "git-commit-tree" uses your name, hostname and domain to
-provide author and committer info. This can be overridden using the
-following environment variables.
+provide author and committer info. This can be overridden by
+either `.git/config` file, or using the following environment variables.
 
        GIT_AUTHOR_NAME
        GIT_AUTHOR_EMAIL
@@ -60,10 +59,17 @@ following environment variables.
 
 (nb "<", ">" and "\n"s are stripped)
 
+In `.git/config` file, the following items are used:
+
+       [user]
+               name = "Your Name"
+               email = "your@email.address.xz"
+
 A commit comment is read from stdin (max 999 chars). If a changelog
 entry is not provided via "<" redirection, "git-commit-tree" will just wait
 for one to be entered and terminated with ^D.
 
+
 Diagnostics
 -----------
 You don't exist. Go away!::
index 790a8eb0ddf13146dde12bf944e998ef778bde5a..1edc278c64827b47d8b672106433417510342392 100644 (file)
@@ -1,6 +1,5 @@
 git-commit(1)
 =============
-v0.99.4, Aug 2005
 
 NAME
 ----
@@ -8,7 +7,7 @@ git-commit - Record your changes
 
 SYNOPSIS
 --------
-'git commit' [-a] [-s] [-v] [(-c | -C) <commit> | -F <file> | -m <msg>] [-e] <file>...
+'git-commit' [-a] [-s] [-v] [(-c | -C) <commit> | -F <file> | -m <msg>] [-e] <file>...
 
 DESCRIPTION
 -----------
index 6ce62dc6721e34d28ee7af66cc063602efc4ccbb..b1220c06e193ddf3942da6e5e0faf91368d0886a 100644 (file)
@@ -1,10 +1,9 @@
 git-convert-objects(1)
 ======================
-v0.1, May 2005
 
 NAME
 ----
-git-convert-objects - Converts old-style GIT repository
+git-convert-objects - Converts old-style git repository
 
 
 SYNOPSIS
@@ -13,7 +12,7 @@ SYNOPSIS
 
 DESCRIPTION
 -----------
-Converts old-style GIT repository to the latest format
+Converts old-style git repository to the latest format
 
 
 Author
index b5753a46dc4bdf8fc426acd32f3add9f95887a50..f5248c91cbb22901266bba31273a276698fe562c 100644 (file)
@@ -1,6 +1,5 @@
 git-cvsimport(1)
 ================
-v0.1, July 2005
 
 NAME
 ----
@@ -11,7 +10,7 @@ SYNOPSIS
 --------
 'git-cvsimport' [ -o <branch-for-HEAD> ] [ -h ] [ -v ]
                        [ -d <CVSROOT> ] [ -p <options-for-cvsps> ]
-                       [ -C <GIT_repository> ] [ -i ] [ -k ]
+                       [ -C <git_repository> ] [ -i ] [ -k ]
                        [ -s <subst> ] [ -m ] [ -M regex ] [ <CVS_module> ]
 
 
@@ -31,7 +30,7 @@ OPTIONS
        are supported.
 
 -C <target-dir>::
-        The GIT repository to import to.  If the directory doesn't
+        The git repository to import to.  If the directory doesn't
         exist, it will be created.  Default is the current directory.
 
 -i::
index 065f2aa721feef01d57c5dc6ecab79223eda5363..67c5f22a7d8179e18be9f0e7cac59866ee5dd530 100644 (file)
@@ -3,11 +3,12 @@ git-daemon(1)
 
 NAME
 ----
-git-daemon - A really simple server for GIT repositories.
+git-daemon - A really simple server for git repositories.
 
 SYNOPSIS
 --------
-'git-daemon' [--verbose] [--syslog] [--inetd | --port=n]
+'git-daemon' [--verbose] [--syslog] [--inetd | --port=n] [--export-all]
+             [--timeout=n] [--init-timeout=n] [directory...]
 
 DESCRIPTION
 -----------
@@ -20,18 +21,35 @@ what directory to upload, and it verifies that the directory is ok.
 
 It verifies that the directory has the magic file "git-daemon-export-ok", and
 it will refuse to export any git directory that hasn't explicitly been marked
-for export this way.
+for export this way (unless the '--export-all' parameter is specified). If you
+pass some directory paths as 'git-daemon' arguments, you can further restrict
+the offers to a whitelist comprising of those.
 
 This is ideally suited for read-only updates, ie pulling from git repositories.
 
 OPTIONS
 -------
+--export-all::
+       Allow pulling from all directories that look like GIT repositories
+       (have the 'objects' subdirectory and a 'HEAD' file), even if they
+       do not have the 'git-daemon-export-ok' file.
+
 --inetd::
        Have the server run as an inetd service.
 
 --port::
        Listen on an alternative port.
 
+--init-timeout::
+       Timeout between the moment the connection is established and the
+       client request is received (typically a rather low value, since
+       that should be basically immediate).
+
+--timeout::
+       Timeout for specific client sub-requests. This includes the time
+       it takes for the server to process the sub-request and time spent
+       waiting for next client's request.
+
 --syslog::
        Log to syslog instead of stderr. Note that this option does not imply
        --verbose, thus by default only error conditions will be logged.
index eb9fb742592bd70d11415c95342e2b75450b361f..e3873888f2236acb3ac04a6cda3784d61a2ce1a2 100644 (file)
@@ -1,6 +1,5 @@
 git-diff-files(1)
 =================
-v0.1, May 2005
 
 NAME
 ----
index 8b6a953c03aa7971f8f7e6e7b7573c4b0fa64007..2fc3eed710dbfeda8911097a8b5f493a220e5f3c 100644 (file)
@@ -1,6 +1,5 @@
 git-diff-index(1)
 =================
-v0.1, May 2005
 
 NAME
 ----
index 276d7bdc43c3e95e2192e38b6d30e730ab2c3a68..28c60fc7e40a042c5cdbad22b103a9d93faeaae6 100644 (file)
@@ -1,6 +1,5 @@
 git-diff-stages(1)
 ==================
-v0.1, June 2005
 
 NAME
 ----
index 339a92287abd10be3b592c2fcb32788470f9f867..f57c8d0d8191dae8be8db930ef8b38ddc9c42623 100644 (file)
@@ -1,6 +1,5 @@
 git-diff-tree(1)
 ================
-v0.1, May 2005
 
 NAME
 ----
@@ -9,7 +8,7 @@ git-diff-tree - Compares the content and mode of blobs found via two tree object
 
 SYNOPSIS
 --------
-'git-diff-tree' [--stdin] [-m] [-s] [-v] [--pretty] [-t] [<common diff options>] <tree-ish> [<tree-ish>] [<path>...]
+'git-diff-tree' [--stdin] [-m] [-s] [-v] [--pretty] [-t] [-r] [--root] [<common diff options>] <tree-ish> [<tree-ish>] [<path>...]
 
 DESCRIPTION
 -----------
@@ -34,6 +33,9 @@ include::diff-options.txt[]
        Note that this parameter does not provide any wildcard or regexp
        features.
 
+-r::
+        recurse into sub-trees
+
 -t::
        show tree entry itself as well as subtrees.  Implies -r.
 
index 1d281820cfe7f492cb32369b8b8cb60a5d040d89..ea6faab059810e8eef858d58c041b7d98a7de841 100644 (file)
@@ -1,6 +1,5 @@
 git-fetch-pack(1)
 =================
-v0.1, July 2005
 
 NAME
 ----
@@ -35,7 +34,7 @@ OPTIONS
        remote side, if is not found on your $PATH.
        Installations of sshd ignores the user's environment
        setup scripts for login shells (e.g. .bash_profile) and
-       your privately installed GIT may not be found on the system
+       your privately installed git may not be found on the system
        default $PATH.  Another workaround suggested is to set
        up your $PATH in ".bashrc", but this flag is for people
        who do not want to pay the overhead for non-interactive
index 1fa9f4dbf4198e35f49e299f772c054f50251b63..71693650c4194514f62dd5d8524b446dd8641805 100644 (file)
@@ -1,6 +1,5 @@
 git-fetch(1)
 ============
-v0.99.5, Aug 2005
 
 NAME
 ----
@@ -26,6 +25,11 @@ OPTIONS
 -------
 include::pull-fetch-param.txt[]
 
+-a, \--append::
+       Append ref names and object names of fetched refs to the
+       existing contents of $GIT_DIR/FETCH_HEAD.  Without this
+       option old data in $GIT_DIR/FETCH_HEAD will be overwritten.
+
 -u, \--update-head-ok::
        By default 'git-fetch' refuses to update the head which
        corresponds to the current branch.  This flag disables the
index a1483ffd0f865eb486686d7c8050090b389a81c3..f3ef4c1e046a8b5997bbc0d361739c21f6ccd62a 100644 (file)
@@ -8,7 +8,7 @@ git-format-patch - Prepare patches for e-mail submission.
 
 SYNOPSIS
 --------
-'git-format-patch' [-n][-o <dir>][-k][--mbox][--diff-options] <his> [<mine>]
+'git-format-patch' [-n][-o <dir>|--stdout][-k][--mbox][--diff-options] <his> [<mine>]
 
 DESCRIPTION
 -----------
@@ -54,6 +54,10 @@ OPTIONS
        concatenated together and fed to `git-applymbox`.
        Implies --author and --date.
 
+--stdout::
+       This flag generates the mbox formatted output to the
+       standard output, instead of saving them into a file per
+       patch and implies --mbox.
 
 Author
 ------
index ba251a5152e8b7d3bd2de508a02f3b7c5170949d..5dc9dbdd78af931e7c0a69d2b2425287e0614687 100644 (file)
@@ -1,6 +1,5 @@
 git-fsck-objects(1)
 ===================
-v0.1, May 2005
 
 NAME
 ----
@@ -42,22 +41,22 @@ index file and all SHA1 references in .git/refs/* as heads.
        ($GIT_DIR/objects), making sure that it is consistent and
        complete without referring to objects found in alternate
        object pools listed in GIT_ALTERNATE_OBJECT_DIRECTORIES,
-       nor packed GIT archives found in $GIT_DIR/objects/pack;
+       nor packed git archives found in $GIT_DIR/objects/pack;
        cannot be used with --full.
 
 --full::
        Check not just objects in GIT_OBJECT_DIRECTORY
        ($GIT_DIR/objects), but also the ones found in alternate
        object pools listed in GIT_ALTERNATE_OBJECT_DIRECTORIES,
-       and in packed GIT archives found in $GIT_DIR/objects/pack
+       and in packed git archives found in $GIT_DIR/objects/pack
        and corresponding pack subdirectories in alternate
        object pools; cannot be used with --standalone.
 
 --strict::
        Enable more strict checking, namely to catch a file mode
        recorded with g+w bit set, which was created by older
-       versions of GIT.  Existing repositories, including the
-       Linux kernel, GIT itself, and sparse repository have old
+       versions of git.  Existing repositories, including the
+       Linux kernel, git itself, and sparse repository have old
        objects that triggers this check, but it is recommended
        to check new projects with this flag.
 
@@ -81,7 +80,7 @@ Any corrupt objects you will have to find in backups or other archives
 the hopes that somebody else has the object you have corrupted).
 
 Of course, "valid tree" doesn't mean that it wasn't generated by some
-evil person, and the end result might be crap. Git is a revision
+evil person, and the end result might be crap. git is a revision
 tracking system, not a quality assurance system ;)
 
 Extracted Diagnostics
index 5f082167c1b55f1606f0a9ff48db0e03d10faa7b..017579348319477c6356fcfce309ce68746d6899 100644 (file)
@@ -1,6 +1,5 @@
 git-grep(1)
 ===========
-v0.99.6, Sep 2005
 
 NAME
 ----
index 935cc66b38aeae8988f0e052301c71958f1e92ed..9239f11135d849ff0411e9ebef21ed8b1c83afd6 100644 (file)
@@ -1,6 +1,5 @@
 git-hash-object(1)
 ==================
-v0.1, May 2005
 
 NAME
 ----
index c03427256800a08dc5107018297a32e5e47a85d2..088624f6cc47c8ce2f72704d2d30d65248956204 100644 (file)
@@ -1,10 +1,9 @@
 git-http-fetch(1)
 =================
-v0.1, May 2005
 
 NAME
 ----
-git-http-fetch - Downloads a remote GIT repository via HTTP
+git-http-fetch - Downloads a remote git repository via HTTP
 
 
 SYNOPSIS
@@ -13,7 +12,7 @@ SYNOPSIS
 
 DESCRIPTION
 -----------
-Downloads a remote GIT repository via HTTP.
+Downloads a remote git repository via HTTP.
 
 -c::
        Get the commit objects.
diff --git a/Documentation/git-index-pack.txt b/Documentation/git-index-pack.txt
new file mode 100644 (file)
index 0000000..71ce557
--- /dev/null
@@ -0,0 +1,44 @@
+git-index-pack(1)
+=================
+
+NAME
+----
+git-index-pack - Build pack index file for an existing packed archive
+
+
+SYNOPSIS
+--------
+'git-index-pack' [-o <index-file>] <pack-file>
+
+
+DESCRIPTION
+-----------
+Reads a packed archive (.pack) from the specified file, and
+builds a pack index file (.idx) for it.  The packed archive
+together with the pack index can then be placed in the
+objects/pack/ directory of a git repository.
+
+
+OPTIONS
+-------
+-o <index-file>::
+       Write the generated pack index into the specified
+       file.  Without this option the name of pack index
+       file is constructed from the name of packed archive
+       file by replacing .pack with .idx (and the program
+       fails if the name of packed archive does not end
+       with .pack).
+
+
+Author
+------
+Written by Sergey Vlasov <vsu@altlinux.ru>
+
+Documentation
+-------------
+Documentation by Sergey Vlasov
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
index ef2d04a612bb79f9db0aa58b71089bf505cc2d72..ef1826ae672cb6537d497d027e5babe4988a42b0 100644 (file)
@@ -1,6 +1,5 @@
 git-init-db(1)
 ==============
-v0.1, May 2005
 
 NAME
 ----
index ccf973592422f5e77315ffe7da6075bb1e0244ca..87abec1c4e46ad53e018264046091af4455da307 100644 (file)
@@ -1,10 +1,9 @@
 git-local-fetch(1)
 ==================
-v0.1, May 2005
 
 NAME
 ----
-git-local-fetch - Duplicates another GIT repository on a local system
+git-local-fetch - Duplicates another git repository on a local system
 
 
 SYNOPSIS
@@ -13,7 +12,7 @@ SYNOPSIS
 
 DESCRIPTION
 -----------
-Duplicates another GIT repository on a local system.
+Duplicates another git repository on a local system.
 
 OPTIONS
 -------
index 2a0e5aceabfbc43026d70160e67e3900e6ac013b..13a3998302a2817bb36d7555f171311ce19a3d0c 100644 (file)
@@ -1,6 +1,5 @@
 git-log(1)
 ==========
-v0.99.4, Aug 2005
 
 NAME
 ----
@@ -9,7 +8,7 @@ git-log - Show commit logs
 
 SYNOPSIS
 --------
-'git log' <option>...
+'git-log' <option>...
 
 DESCRIPTION
 -----------
index f4501d62cd106635333ec4880f9d1c0aeacd8343..8c1784d2e3f982c405863e8f79695bb92145a5f5 100644 (file)
@@ -54,7 +54,7 @@ OPTIONS
        succeed.
 
 -z::
-       \0 line termination on output
+       \0 line termination on output.
 
 -x|--exclude=<pattern>::
        Skips files matching pattern.
@@ -100,6 +100,10 @@ the dircache records up to three such pairs; one from tree O in stage
 the user (or the porcelain) to see what should eventually be recorded at the
 path. (see git-read-tree for more information on state)
 
+When `-z` option is not used, TAB, LF, and backslash characters
+in pathnames are represented as `\t`, `\n`, and `\\`,
+respectively.
+
 
 Exclude Patterns
 ----------------
index 89bd609d653d27646de8e9de12766c6fe0887575..c0a80d4089d88417d0d91dbf44f728608ea1c467 100644 (file)
@@ -1,6 +1,5 @@
 git-ls-remote(1)
 ================
-v0.1, May 2005
 
 NAME
 ----
index 0d159fd5cc6c98a3bd4fe824c5f8a9a5b340fb28..ba0438e9ad444d3647af192f3483f27a73f8e294 100644 (file)
@@ -1,6 +1,5 @@
 git-ls-tree(1)
 ==============
-v0.1, May 2005
 
 NAME
 ----
@@ -39,6 +38,10 @@ Output Format
 -------------
         <mode> SP <type> SP <object> TAB <file>
 
+When `-z` option is not used, TAB, LF, and backslash characters
+in pathnames are represented as `\t`, `\n`, and `\\`,
+respectively.
+
 
 Author
 ------
index 557d2e9056f028409be78d086c96f9b8de4b16a9..03a9477664310c2dc5ee9bddc0b34a99b8ab6687 100644 (file)
@@ -7,7 +7,7 @@ git-mailsplit - Totally braindamaged mbox splitter program.
 
 SYNOPSIS
 --------
-'git-mailsplit' <mbox> <directory>
+'git-mailsplit' [-d<prec>] [<mbox>] <directory>
 
 DESCRIPTION
 -----------
@@ -17,14 +17,23 @@ directory so you can process them further from there.
 OPTIONS
 -------
 <mbox>::
-       Mbox file to split.
+       Mbox file to split.  If not given, the mbox is read from
+       the standard input.
 
 <directory>::
        Directory in which to place the individual messages.
 
+-d<prec>::
+       Instead of the default 4 digits with leading zeros,
+       different precision can be specified for the generated
+       filenames.
+
+
 Author
 ------
 Written by Linus Torvalds <torvalds@osdl.org>
+and Junio C Hamano <junkio@cox.net>
+
 
 Documentation
 --------------
index e4692163ea96e8efc3b2a5042057e4ec789c0d75..d1d56f194aff866f49c5d0514ce9b47def456bfd 100644 (file)
@@ -1,6 +1,5 @@
 git-merge-base(1)
 =================
-v0.1, May 2005
 
 NAME
 ----
index 5caee90adc43d2f59893f3e59fda23b5a05462a9..d072fdaa4f74a6508abcae17001b1321c73bb000 100644 (file)
@@ -1,6 +1,5 @@
 git-merge-index(1)
 ==================
-v0.1, May 2005
 
 NAME
 ----
index 712739777b6c98449d2efdd5b6ce31724f3f5db6..86aad37c6acaf0c37995221456bffc61ed3bc883 100644 (file)
@@ -1,6 +1,5 @@
 git-merge-one-file(1)
 =====================
-v0.99.4, Aug 2005
 
 NAME
 ----
index dca363dd5ec7022d605d5730be65e6066072de1d..a007a8b15a1a4598aabd844d09e45db65db6e3a5 100644 (file)
@@ -1,6 +1,5 @@
 git-merge(1)
 ============
-v0.99.6, Sep 2005
 
 NAME
 ----
index 44ed4b5408e8597e99b7f75033031fa75e5c201f..2860a3d1ba54c712bb30febfa37693f3efbdb7bf 100644 (file)
@@ -1,6 +1,5 @@
 git-mktag(1)
 ============
-v0.1, May 2005
 
 NAME
 ----
diff --git a/Documentation/git-mv.txt b/Documentation/git-mv.txt
new file mode 100644 (file)
index 0000000..f2d5882
--- /dev/null
@@ -0,0 +1,51 @@
+git-mv(1)
+=========
+
+NAME
+----
+git-mv - Script used to move or rename a file, directory or symlink.
+
+
+SYNOPSIS
+--------
+'git-mv' [-f] [-n] <source> <destination>
+'git-mv' [-f] [-k] [-n] <source> ... <destination directory>
+
+DESCRIPTION
+-----------
+This script is used to move or rename a file, directory or symlink.
+In the first form, it renames <source>, which must exist and be either
+a file, symlink or directory, to <destination>, which must not exist.
+In the second form, the last argument has to be an existing
+directory; the given sources will be moved into this directory.
+
+The index is updated after successful completion, but the change must still be
+committed.
+
+OPTIONS
+-------
+-f::
+       Force renaming or moving even targets exist
+-k::
+        Skip move or rename actions which would lead to an error
+       condition. An error happens when a source is neither existing nor
+        controlled by GIT, or when it would overwrite an existing
+        file unless '-f' is given.
+-n::
+       Do nothing; only show what would happen
+
+
+Author
+------
+Written by Linus Torvalds <torvalds@osdl.org>
+Rewritten by Ryan Anderson <ryan@michonline.com>
+Move functionality added by Josef Weidendorfer <Josef.Weidendorfer@gmx.de>
+
+Documentation
+--------------
+Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>.
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
diff --git a/Documentation/git-name-rev.txt b/Documentation/git-name-rev.txt
new file mode 100644 (file)
index 0000000..e37b0b8
--- /dev/null
@@ -0,0 +1,66 @@
+git-name-rev(1)
+===============
+
+NAME
+----
+git-name-rev - Find symbolic names for given revs.
+
+
+SYNOPSIS
+--------
+'git-name-rev' [--tags] ( --all | --stdin | <commitish>... )
+
+DESCRIPTION
+-----------
+Finds symbolic names suitable for human digestion for revisions given in any
+format parsable by git-rev-parse.
+
+
+OPTIONS
+-------
+
+--tags::
+       Do not use branch names, but only tags to name the commits
+
+--all::
+       List all commits reachable from all refs
+
+--stdin::
+       Read from stdin, append "(<rev_name>)" to all sha1's of name'able
+       commits, and pass to stdout
+
+EXAMPLE
+-------
+
+Given a commit, find out where it is relative to the local refs. Say somebody
+wrote you about that phantastic commit 33db5f4d9027a10e477ccf054b2c1ab94f74c85a.
+Of course, you look into the commit, but that only tells you what happened, but
+not the context.
+
+Enter git-name-rev:
+
+------------
+% git name-rev 33db5f4d9027a10e477ccf054b2c1ab94f74c85a
+------------
+
+Now you are wiser, because you know that it happened 940 revisions before v0.99.
+
+Another nice thing you can do is:
+
+------------
+% git log | git name-rev --stdin
+------------
+
+
+Author
+------
+Written by Johannes Schindelin <Johannes.Schindelin@gmx.de>
+
+Documentation
+--------------
+Documentation by Johannes Schindelin.
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
index 881c317ac5f36b42c7d7866c4653323ce76301b6..6e32ea347c415a1849d58f80779f45257c9898df 100644 (file)
@@ -1,6 +1,5 @@
 git-octopus(1)
 ==============
-v0.99.5, Aug 2005
 
 NAME
 ----
index 44aba940d7f0da88c2760633fe2cad8f415e1f31..d1e93dbb372624d68fa96eb8bed141f1e76f0308 100644 (file)
@@ -1,6 +1,5 @@
 git-pack-objects(1)
 ===================
-v0.1, July 2005
 
 NAME
 ----
@@ -9,7 +8,7 @@ git-pack-objects - Create a packed archive of objects.
 
 SYNOPSIS
 --------
-'git-pack-objects' [--incremental] [--window=N] [--depth=N] {--stdout | base-name} < object-list
+'git-pack-objects' [--local] [--incremental] [--window=N] [--depth=N] {--stdout | base-name} < object-list
 
 
 DESCRIPTION
@@ -31,7 +30,7 @@ transport by their peers.
 
 Placing both in the pack/ subdirectory of $GIT_OBJECT_DIRECTORY (or
 any of the directories on $GIT_ALTERNATE_OBJECT_DIRECTORIES)
-enables GIT to read from such an archive.
+enables git to read from such an archive.
 
 
 OPTIONS
@@ -65,6 +64,11 @@ base-name::
        This flag causes an object already in a pack ignored
        even if it appears in the standard input.
 
+--local::
+       This flag is similar to `--incremental`; instead of
+       ignoring all packed objects, it only ignores objects
+       that are packed and not in the local object store
+       (i.e. borrowed from an alternate).
 
 Author
 ------
index 3e560a37e835bb59f12754591c5c32bac671f57c..c8bd1977790b2611d4b7974fc3a716d99b888a74 100644 (file)
@@ -18,6 +18,12 @@ ID" are almost guaranteed to be the same thing.
 
 IOW, you can use this thing to look for likely duplicate commits.
 
+When dealing with git-diff-tree output, it takes advantage of
+the fact that the patch is prefixed with the object name of the
+commit, and outputs two 40-byte hexadecimal string.  The first
+string is the patch ID, and the second string is the commit ID.
+This can be used to make a mapping from patch ID to commit ID.
+
 OPTIONS
 -------
 <patch>::
index c1527f1bcf1a1ec956176b3b9b2fb3f7b94a2a16..915d3f8a06a6577b093acd79d00f93731fc7dec2 100644 (file)
@@ -1,6 +1,5 @@
 git-peek-remote(1)
 ==================
-v0.1, July 2005
 
 NAME
 ----
@@ -23,7 +22,7 @@ OPTIONS
        remote side, if it is not found on your $PATH. Some
        installations of sshd ignores the user's environment
        setup scripts for login shells (e.g. .bash_profile) and
-       your privately installed GIT may not be found on the system
+       your privately installed git may not be found on the system
        default $PATH.  Another workaround suggested is to set
        up your $PATH in ".bashrc", but this flag is for people
        who do not want to pay the overhead for non-interactive
index 5c48a0937ac7aa1a41a585646e40fd03302e1b5d..28a1500d39a503e8341ad9c39c1c1597b1653d7c 100644 (file)
@@ -1,6 +1,5 @@
 git-prune-packed(1)
 =====================
-v0.1, August 2005
 
 NAME
 ----
index d1676cbfc8354982a0d18f4fd52faa2381532ae4..3367c9b21452db7e73ec29f03d36fd2d11b56b97 100644 (file)
@@ -1,6 +1,5 @@
 git-prune(1)
 ============
-v0.99.5, Aug 2005
 
 NAME
 ----
index c35d2eb54cfccbf8aa40d5d7a3a844f4a4d7172d..bae05dee99ebaa7866885d3d8a36cde81e5d700e 100644 (file)
@@ -1,6 +1,5 @@
 git-pull(1)
 ===========
-v0.99.4, Aug 2005
 
 NAME
 ----
@@ -25,6 +24,10 @@ OPTIONS
 -------
 include::pull-fetch-param.txt[]
 
+-a, \--append::
+       Append ref names and object names of fetched refs to the
+       existing contents of $GIT_DIR/FETCH_HEAD.  Without this
+       option old data in $GIT_DIR/FETCH_HEAD will be overwritten.
 
 Author
 ------
index 809ac8ba069a8a97a686d62f1bb586005836f896..f45ac5ee4910198fa351e56bb40333612787fcd2 100644 (file)
@@ -21,6 +21,15 @@ OPTIONS
 -------
 include::pull-fetch-param.txt[]
 
+\--all::
+       Instead of naming each ref to push, specifies all refs
+       to be pushed.
+
+-f, \--force::
+       Usually, the command refuses to update a local ref that is
+       not an ancestor of the remote ref used to overwrite it.
+       This flag disables the check.  What this means is that the
+       local repository can lose commits; use it with care.
 
 Author
 ------
index 5653baccaffe888bc5a238e0fef8212045ab187d..7db5fb579597373128c9cbb44ef02adf5e9da8a0 100644 (file)
@@ -1,6 +1,5 @@
 git-read-tree(1)
 ================
-v0.1, May 2005
 
 NAME
 ----
index fb4b76b6c2459bc8341ff879c961170b015d1054..8afde14373723a9894e4da5b7f2281b18e75e93f 100644 (file)
@@ -1,6 +1,5 @@
 git-receive-pack(1)
 ===================
-v0.1, July 2005
 
 NAME
 ----
index 21928dc0716971c00f5d00bd752373e749bb6b07..583cb0315e1d9c50cbb19575a0e61b30b5429e38 100644 (file)
@@ -1,6 +1,5 @@
 git-rename(1)
 =============
-v0.1, May 2005
 
 NAME
 ----
index bd830ada274335b723f3382b9b4a554fa3c15701..0c1ae49ed7b61a34af62c3e29f84207a5a3c3082 100644 (file)
@@ -1,6 +1,5 @@
 git-repack(1)
 =============
-v0.99.5, August 2005
 
 NAME
 ----
index 7d3eb79033ae857fdaa164d18ef5607fb91f38d0..4e57c2b2877b1594b3a467c0930348221e98c672 100644 (file)
@@ -1,6 +1,5 @@
 git-resolve(1)
 ==============
-v0.99.5, Aug 2005
 
 NAME
 ----
@@ -9,7 +8,7 @@ git-resolve - Merge two commits
 
 SYNOPSIS
 --------
-'git resolve' <current> <merged> <message>
+'git-resolve' <current> <merged> <message>
 
 DESCRIPTION
 -----------
index f386a3a79bd1752b051de7c1e07d549bfa432de7..064ccb1f8707bc6e82d43bcabb20bb30aec6236f 100644 (file)
@@ -1,6 +1,5 @@
 git-rev-list(1)
 ===============
-v0.1, May 2005
 
 NAME
 ----
@@ -9,7 +8,18 @@ git-rev-list - Lists commit objects in reverse chronological order
 
 SYNOPSIS
 --------
-'git-rev-list' [ *--max-count*=number ] [ *--max-age*=timestamp ] [ *--min-age*=timestamp ] [ *--bisect* ] [ *--pretty* ] [ *--objects* ] [ *--merge-order* [ *--show-breaks* ] ] <commit> [ <commit> ...] [ ^<commit> ...]
+'git-rev-list' [ \--max-count=number ]
+       [ \--max-age=timestamp ]
+       [ \--min-age=timestamp ]
+       [ \--sparse ]
+       [ \--no-merges ]
+       [ \--all ]
+       [ [ \--merge-order [ \--show-breaks ] ] | [ \--topo-order ] | ]
+       [ \--parents ]
+       [ \--objects [ \--unpacked ] ]
+       [ \--pretty | \--header | ]
+       [ \--bisect ]
+       <commit>... [ \-- <paths>... ]
 
 DESCRIPTION
 -----------
@@ -17,22 +27,34 @@ Lists commit objects in reverse chronological order starting at the
 given commit(s), taking ancestry relationship into account.  This is
 useful to produce human-readable log output.
 
-Commits which are stated with a preceding '^' cause listing to stop at
-that point. Their parents are implied. "git-rev-list foo bar ^baz" thus
+Commits which are stated with a preceding '{caret}' cause listing to stop at
+that point. Their parents are implied. "git-rev-list foo bar {caret}baz" thus
 means "list all the commits which are included in 'foo' and 'bar', but
 not in 'baz'".
 
+A special notation <commit1>..<commit2> can be used as a
+short-hand for {caret}<commit1> <commit2>.
+
+
 OPTIONS
 -------
 --pretty::
        Print the contents of the commit changesets in human-readable form.
 
+--header::
+       Print the contents of the commit in raw-format; each
+       record is separated with a NUL character.
+
 --objects::
        Print the object IDs of any object referenced by the listed commits.
        'git-rev-list --objects foo ^bar' thus means "send me all object IDs
        which I need to download if I have the commit object 'bar', but
        not 'foo'".
 
+--unpacked::
+       Only useful with `--objects`; print the object IDs that
+       are not in packs.
+
 --bisect::
        Limit output to the one commit object which is roughly halfway
        between the included and excluded commits. Thus, if 'git-rev-list
@@ -43,6 +65,30 @@ OPTIONS
        repeatedly generate and test new 'midpoint's until the commit chain
        is of length one.
 
+--max-count::
+       Limit the number of commits output.
+
+--max-age=timestamp, --min-age=timestamp::
+       Limit the commits output to specified time range.
+
+--sparse::
+       When optional paths are given, the command outputs only
+       the commits that changes at least one of them, and also
+       ignores merges that do not touch the given paths.  This
+       flag makes the command output all eligible commits
+       (still subject to count and age limitation), but apply
+       merge simplification nevertheless.
+
+--all::
+       Pretend as if all the refs in `$GIT_DIR/refs/` are
+       listed on the command line as <commit>.
+
+--topo-order::
+       By default, the commits are shown in reverse
+       chronological order.  This option makes them appear in
+       topological order (i.e. descendant commits are shown
+       before their parents).
+
 --merge-order::
        When specified the commit history is decomposed into a unique
        sequence of minimal, non-linear epochs and maximal, linear epochs.
@@ -85,7 +131,8 @@ Commits marked with (^) are not parents of the immediately preceding commit.
 These "breaks" represent necessary discontinuities implied by trying to
 represent an arbtirary DAG in a linear form.
 +
-*--show-breaks* is only valid if *--merge-order* is also specified.
+`--show-breaks` is only valid if `--merge-order` is also specified.
+
 
 Author
 ------
index 99fd90e376d100c9de4dd19a5b616e55f5a70f69..099db294f4316fc39b451e2f54f9736bce9baf3b 100644 (file)
@@ -79,8 +79,9 @@ OPTIONS
 SPECIFYING REVISIONS
 --------------------
 
-A revision parameter typically names a commit object.  They use
-what is called an 'extended SHA1' syntax.
+A revision parameter typically, but not necessarily, names a
+commit object.  They use what is called an 'extended SHA1'
+syntax.
 
 * The full SHA1 object name (40-byte hexadecimal string), or
   a substring of such that is unique within the repository.
@@ -91,7 +92,7 @@ what is called an 'extended SHA1' syntax.
 * A symbolic ref name.  E.g. 'master' typically means the commit
   object referenced by $GIT_DIR/refs/heads/master.  If you
   happen to have both heads/master and tags/master, you can
-  explicitly say 'heads/master' to tell GIT which one you mean.
+  explicitly say 'heads/master' to tell git which one you mean.
 
 * A suffix '{caret}' to a revision parameter means the first parent of
   that commit object.  '{caret}<n>' means the <n>th parent (i.e.
@@ -106,6 +107,18 @@ what is called an 'extended SHA1' syntax.
   equivalent to rev{caret}{caret}{caret} which is equivalent to\
   rev{caret}1{caret}1{caret}1.
 
+* A suffix '{caret}' followed by an object type name enclosed in
+  brace pair (e.g. `v0.99.8{caret}\{commit\}`) means the object
+  could be a tag, and dereference the tag recursively until an
+  object of that type is found or the object cannot be
+  dereferenced anymore (in which case, barf).  `rev{caret}0`
+  introduced earlier is a short-hand for `rev{caret}\{commit\}`.
+
+* A suffix '{caret}' followed by an empty brace pair
+  (e.g. `v0.99.8{caret}\{\}`) means the object could be a tag,
+  and dereference the tag recursively until a non-tag object is
+  found.
+
 'git-rev-parse' also accepts a prefix '{caret}' to revision parameter,
 which is passed to 'git-rev-list'.  Two revision parameters
 concatenated with '..' is a short-hand for writing a range
index b95e33db528ce76ae495147b034df9adeaac1273..b9bec55e53ad191e4cb0b052551aa7e45049013e 100644 (file)
@@ -1,6 +1,5 @@
 git-send-email(1)
 =================
-v0.1, July 2005
 
 NAME
 ----
index 219dfc2ef6541216f2c46329e3e82664232b4b35..577f06a21460a4e06eaa4cc4994b3f6ea80601ff 100644 (file)
@@ -1,6 +1,5 @@
 git-send-pack(1)
 ================
-v0.1, July 2005
 
 NAME
 ----
diff --git a/Documentation/git-shell.txt b/Documentation/git-shell.txt
new file mode 100644 (file)
index 0000000..3f4d804
--- /dev/null
@@ -0,0 +1,35 @@
+git-shell(1)
+============
+
+NAME
+----
+git-shell - Restricted login shell for GIT over SSH only
+
+
+SYNOPSIS
+--------
+'git-shell -c <command> <argument>'
+
+DESCRIPTION
+-----------
+This is meant to be used as a login shell for SSH accounts you want
+to restrict to GIT pull/push access only. It permits execution only
+of server-side GIT commands implementing the pull/push functionality.
+The commands can be executed only by the '-c' option; the shell is not
+interactive.
+
+Currently, only the `git-receive-pack` and `git-upload-pack` commands
+are permitted to be called, with a single required argument.
+
+Author
+------
+Written by Linus Torvalds <torvalds@osdl.org>
+
+Documentation
+--------------
+Documentation by Petr Baudis and the git-list <git@vger.kernel.org>.
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
index a852e9b865b24aba71d7a0e79e237d21c19746ca..65ca77fbf657520a59b308fc3e99ed79b61cff95 100644 (file)
@@ -1,6 +1,5 @@
 git-shortlog(1)
 ===============
-v0.99.4, Aug 2005
 
 NAME
 ----
@@ -9,7 +8,7 @@ git-shortlog - Summarize 'git log' output.
 
 SYNOPSIS
 --------
-'git log --pretty=short | git shortlog'
+'git-log --pretty=short | git shortlog'
 
 DESCRIPTION
 -----------
index 5b80d5aa2a7a8c58bd525563996184f1100506c5..c6c97b21c320ce7fa6533bfc153f6fac7d45a7c0 100644 (file)
@@ -1,6 +1,5 @@
 git-show-branch(1)
 ==================
-v0.99.5, Aug 2005
 
 NAME
 ----
@@ -8,7 +7,7 @@ git-show-branch - Show branches and their commits.
 
 SYNOPSIS
 --------
-'git show-branch [--all] [--heads] [--tags] [--more=<n> | --list | --independent | --merge-base] <reference>...'
+'git-show-branch [--all] [--heads] [--tags] [--more=<n> | --list | --independent | --merge-base] [--no-name | --sha1-name] <reference>...'
 
 DESCRIPTION
 -----------
@@ -45,6 +44,15 @@ OPTIONS
        Among the <reference>s given, display only the ones that
        cannot be reached from any other <reference>.
 
+--no-name::
+       Do not show naming strings for each commit.
+
+--sha1-name::
+       Instead of naming the commits using the path to reach
+       them from heads (e.g. "master~2" to mean the grandparent
+       of "master"), name them with the unique prefix of their
+       object names.
+
 Note that --more, --list, --independent and --merge-base options
 are mutually exclusive.
 
@@ -89,21 +97,6 @@ whose commit message is "Add 'git show-branch'.  "fixes" branch
 adds one commit 'Introduce "reset type"'.  "mhf" branch has many
 other commits.
 
-When only one head is given, the output format changes slightly
-to conserve space.  The '+' sign to show which commit is
-reachable from which head and the first N lines to show the list
-of heads being displayed are both meaningless so they are
-omitted.  Also the label given to each commit does not repeat
-the name of the branch because it is obvious.
-
-------------------------------------------------
-$ git show-branch --more=4 master
-[master] Add 'git show-branch'.
-[~1] Add a new extended SHA1 syntax <name>~<num>
-[~2] Fix "git-diff A B"
-[~3] git-ls-files: generalized pathspecs
-[~4] Make "git-ls-files" work in subdirectories
-------------------------------------------------
 
 Author
 ------
index 72720ada22ca7c74fe4c9a0c78897029ccf84141..be09b62bebb2b98fa8b0f39e47d29267126fa2a6 100644 (file)
@@ -1,6 +1,5 @@
 git-show-index(1)
 =================
-v0.1, July 2005
 
 NAME
 ----
@@ -14,7 +13,7 @@ SYNOPSIS
 
 DESCRIPTION
 -----------
-Reads given idx file for packed GIT archive created with
+Reads given idx file for packed git archive created with
 git-pack-objects command, and dumps its contents.
 
 The information it outputs is subset of what you can get from
index e3887ace09f88633801548be0b36890b6a02020b..b7116b30e0f47958d54c066776adbd318c84edb3 100644 (file)
@@ -1,6 +1,5 @@
 git-ssh-fetch(1)
 ================
-v0.1, May 2005
 
 NAME
 ----
index b625019812554a41ee7702040b0ea7a8e21ab7ad..702674e45d17fbdb6dfc60797ffcb2dbedb841bf 100644 (file)
@@ -1,6 +1,5 @@
 git-ssh-upload(1)
 =================
-v0.1, Jun 2005
 
 NAME
 ----
index 6d49a5aa0df8cef4853d817e1e328b14cc190055..753fc0866d2883af59ed54fd7ca566f7694172e3 100644 (file)
@@ -1,6 +1,5 @@
 git-status(1)
 =============
-v0.99.4, Aug 2005
 
 NAME
 ----
@@ -9,7 +8,7 @@ git-status - Show working tree status.
 
 SYNOPSIS
 --------
-'git status'
+'git-status'
 
 DESCRIPTION
 -----------
diff --git a/Documentation/git-svnimport.txt b/Documentation/git-svnimport.txt
new file mode 100644 (file)
index 0000000..88bdc08
--- /dev/null
@@ -0,0 +1,137 @@
+git-svnimport(1)
+================
+v0.1, July 2005
+
+NAME
+----
+git-svnimport - Import a SVN repository into git
+
+
+SYNOPSIS
+--------
+'git-svnimport' [ -o <branch-for-HEAD> ] [ -h ] [ -v ] [ -d | -D ]
+                       [ -C <GIT_repository> ] [ -i ] [ -u ] [-l limit_nr_changes]
+                       [ -b branch_subdir ] [ -t trunk_subdir ] [ -T tag_subdir ]
+                       [ -s start_chg ] [ -m ] [ -M regex ]
+                       <SVN_repository_URL> [ <path> ]
+
+
+DESCRIPTION
+-----------
+Imports a SVN repository into git. It will either create a new
+repository, or incrementally import into an existing one.
+
+SVN access is done by the SVN:: Perl module.
+
+git-svnimport assumes that SVN repositories are organized into one
+"trunk" directory where the main development happens, "branch/FOO"
+directories for branches, and "/tags/FOO" directories for tags.
+Other subdirectories are ignored.
+
+git-svnimport creates a file ".git/svn2git", which is required for
+incremental SVN imports.
+
+OPTIONS
+-------
+-C <target-dir>::
+        The GIT repository to import to.  If the directory doesn't
+        exist, it will be created.  Default is the current directory.
+
+-s <start_rev>::
+        Start importing at this SVN change number. The  default is 1.
++
+When importing incementally, you might need to edit the .git/svn2git file.
+
+-i::
+       Import-only: don't perform a checkout after importing.  This option
+       ensures the working directory and cache remain untouched and will
+       not create them if they do not exist.
+
+-t <trunk_subdir>::
+       Name the SVN trunk. Default "trunk".
+
+-T <tag_subdir>::
+       Name the SVN subdirectory for tags. Default "tags".
+
+-b <branch_subdir>::
+       Name the SVN subdirectory for branches. Default "branches".
+
+-o <branch-for-HEAD>::
+       The 'trunk' branch from SVN is imported to the 'origin' branch within
+       the git repository. Use this option if you want to import into a
+       different branch.
+
+-m::
+       Attempt to detect merges based on the commit message. This option
+       will enable default regexes that try to capture the name source
+       branch name from the commit message.
+
+-M <regex>::
+       Attempt to detect merges based on the commit message with a custom
+       regex. It can be used with -m to also see the default regexes.
+       You must escape forward slashes.
+
+-l <max_num_changes>::
+       Limit the number of SVN changesets we pull before quitting.
+       This option is necessary because the SVN library has serious memory
+       leaks; the recommended value for nontrivial imports is 100.
+
+       git-svnimport will still exit with a zero exit code. You can check
+       the size of the file ".git/svn2git" to determine whether to call
+       the importer again.
+
+-v::
+       Verbosity: let 'svnimport' report what it is doing.
+
+-d::
+       Use direct HTTP requests if possible. The "<path>" argument is used
+       only for retrieving the SVN logs; the path to the contents is
+       included in the SVN log.
+
+-D::
+       Use direct HTTP requests if possible. The "<path>" argument is used
+       for retrieving the logs, as well as for the contents.
++
+There's no safe way to automatically find out which of these options to
+use, so you need to try both. Usually, the one that's wrong will die
+with a 40x error pretty quickly.
+
+<SVN_repository_URL>::
+       The URL of the SVN module you want to import. For local
+       repositories, use "file:///absolute/path".
++
+If you're using the "-d" or "-D" option, this is the URL of the SVN
+repository itself; it usually ends in "/svn".
+
+<SVN_repository_URL>::
+       The URL of the SVN module you want to import. For local
+       repositories, use "file:///absolute/path".
+
+<path>
+       The path to the module you want to check out.
+
+-h::
+       Print a short usage message and exit.
+
+OUTPUT
+------
+If '-v' is specified, the script reports what it is doing.
+
+Otherwise, success is indicated the Unix way, i.e. by simply exiting with
+a zero exit status.
+
+Author
+------
+Written by Matthias Urlichs <smurf@smurf.noris.de>, with help from
+various participants of the git-list <git@vger.kernel.org>.
+
+Based on a cvs2git script by the same author.
+
+Documentation
+--------------
+Documentation by Matthias Urlichs <smurf@smurf.noris.de>.
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
index e11f51c266a76c6a5b09e99bc4900b64e640ea67..3984812cecc4453b4f3fb84f017f3cd4c50751cb 100644 (file)
@@ -8,19 +8,27 @@ git-tag -  Create a tag object signed with GPG
 
 SYNOPSIS
 --------
-'git-tag' [-a | -s] [-f] [-m <msg>] <name> [<head>]
+'git-tag' [-a | -s | -u <key-id>] [-f] [-m <msg>] <name> [<head>]
 
 DESCRIPTION
 -----------
-Adds a "tag" reference in .git/refs/tags/
+Adds a 'tag' reference in .git/refs/tags/
 
-Unless "-f" is given, the tag must not yet exist in ".git/refs/tags"
+Unless `-f` is given, the tag must not yet exist in
+`.git/refs/tags/` directory.
 
-If "-s" or "-a" is passed, the user will be prompted for a tag message.
-and a tag object is created.  Otherwise just the SHA1 object
-name of the commit object is written.
+If one of `-a`, `-s`, or `-u <key-id>` is passed, the command
+creates a 'tag' object, and requires the tag message.  Unless
+`-m <msg>` is given, an editor is started for the user to type
+in the tag message.
 
-A GnuPG signed tag object will be created when "-s" is used.
+Otherwise just the SHA1 object name of the commit object is
+written (i.e. an lightweight tag).
+
+A GnuPG signed tag object will be created when `-s` or `-u
+<key-id>` is used.  When `-u <key-id>` is not used, the
+committer identity for the current user is used to find the
+GnuPG key for signing.
 
 
 Author
index 480a0cf0ba0b1ef0c657bf296902f9cb66a5e424..2139b6ff8ce3df6d1c872be1410349e2637eb4f1 100644 (file)
@@ -1,6 +1,5 @@
 git-tar-tree(1)
 ===============
-v0.1, May 2005
 
 NAME
 ----
index 3903b2d99b66d47d8a9275c115e779ef228a1233..213dc8196b90170cc14e35f7c637815b39a7ff8d 100644 (file)
@@ -1,6 +1,5 @@
 git-unpack-file(1)
 ==================
-v0.1, May 2005
 
 NAME
 ----
index 9b982d996f04ba29d16debf1392485f6b5d99f09..b716ba1ad35b400fd8c862145283a32298a94f4a 100644 (file)
@@ -1,6 +1,5 @@
 git-unpack-objects(1)
 =====================
-v0.1, July 2005
 
 NAME
 ----
index 6fa1d980f6144a51b7adc865baa6b1dbf5e753d0..58b9e49af5f269f59b6e884430322cfca8673c75 100644 (file)
@@ -9,12 +9,13 @@ git-update-index - Modifies the index or directory cache
 SYNOPSIS
 --------
 'git-update-index'
-            [--add] [--remove] [--refresh] [--replace]
-            [--ignore-missing]
-            [--force-remove]
+            [--add] [--remove | --force-remove] [--replace] 
+            [--refresh [-q] [--unmerged] [--ignore-missing]]
             [--cacheinfo <mode> <object> <file>]\*
-            [--info-only]
+            [--chmod=(+|-)x]
+            [--info-only] [--index-info]
             [-z] [--stdin]
+            [--verbose]
             [--] [<file>]\*
 
 DESCRIPTION
@@ -42,12 +43,28 @@ OPTIONS
        Looks at the current cache and checks to see if merges or
        updates are needed by checking stat() information.
 
+-q::
+        Quiet.  If --refresh finds that the cache needs an update, the
+        default behavior is to error out.  This option makes
+        git-update-index continue anyway.
+
+--unmerged::
+        If --refresh finds unmerged changes in the cache, the default 
+        behavior is to error out.  This option makes git-update-index 
+        continue anyway.
+
 --ignore-missing::
        Ignores missing files during a --refresh
 
 --cacheinfo <mode> <object> <path>::
        Directly insert the specified info into the cache.
        
+--index-info::
+        Read index info from stdin.
+
+--chmod=(+|-)x::
+        Set the execute permissions on the updated files.        
+
 --info-only::
        Do not create objects in the object database for all
        <file> arguments that follow this flag; just insert
@@ -70,6 +87,9 @@ OPTIONS
        read list of paths from the standard input.  Paths are
        separated by LF (i.e. one path per line) by default.
 
+--verbose::
+        Report what is being added and removed from index.
+
 -z::
        Only meaningful with `--stdin`; paths are separated with
        NUL character instead of LF.
@@ -79,7 +99,7 @@ OPTIONS
 
 <file>::
        Files to act on.
-       Note that files begining with '.' are discarded. This includes
+       Note that files beginning with '.' are discarded. This includes
        `./file` and `dir/./file`. If you don't want this, then use     
        cleaner names.
        The same applies to directories ending '/' and paths with '//'
@@ -121,6 +141,17 @@ To update and refresh only the files already checked out:
    git-checkout-index -n -f -a && git-update-index --ignore-missing --refresh
 
 
+Configuration
+-------------
+
+The command honors `core.filemode` configuration variable.  If
+your repository is on an filesystem whose executable bits are
+unreliable, this should be set to 'false'.  This causes the
+command to ignore differences in file modes recorded in the
+index and the file mode on the filesystem if they differ only on
+executable bit.   On such an unfortunate filesystem, you may
+need to use `git-update-index --chmod=`.
+
 Author
 ------
 Written by Linus Torvalds <torvalds@osdl.org>
index 2efd5400a754097f117e2ae80088fb57f783fda7..3d0dea07fbca680c852959ec1e62d77421642b44 100644 (file)
@@ -1,6 +1,5 @@
 git-update-server-info(1)
 =========================
-v0.1, July 2005
 
 NAME
 ----
index 98815b6a10c837274ce6462f86b745c9fa32094b..3d8f8ef6670e84b5601fdcd3e74717a4f3a0fde4 100644 (file)
@@ -1,6 +1,5 @@
 git-upload-pack(1)
 ==================
-v0.1, July 2005
 
 NAME
 ----
index c1c71720889b7b9aadf093870f82289ae4bd9d51..c22d34f5fb9df2ce567f72a996f3d85b424ecf69 100644 (file)
@@ -1,6 +1,5 @@
 git-var(1)
 ==========
-v0.1, July 2005
 
 NAME
 ----
index b100aa765a2de023a4c18b1ef9643a03b98243c4..cd74ffd3912adee956bf139c453092635b5650c2 100644 (file)
@@ -1,10 +1,9 @@
 git-verify-pack(1)
 ==================
-v0.1, June 2005
 
 NAME
 ----
-git-verify-pack - Validate packed GIT archive files.
+git-verify-pack - Validate packed git archive files.
 
 
 SYNOPSIS
@@ -14,7 +13,7 @@ SYNOPSIS
 
 DESCRIPTION
 -----------
-Reads given idx file for packed GIT archive created with
+Reads given idx file for packed git archive created with
 git-pack-objects command and verifies idx file and the
 corresponding pack file.
 
index db2efea14cf00335d5bb470a1b42f6cbe452c2e0..e6f57d9bc1a318ed332c3b46a10d6de824a37a07 100644 (file)
@@ -1,6 +1,5 @@
 git-whatchanged(1)
 ==================
-v0.99.4, Aug 2005
 
 NAME
 ----
@@ -9,7 +8,7 @@ git-whatchanged - Show logs with difference each commit introduces.
 
 SYNOPSIS
 --------
-'git whatchanged' <option>...
+'git-whatchanged' <option>...
 
 DESCRIPTION
 -----------
index 71e16d128a7636c7ebcb3674bdea5f90c26ce721..51be44d1f24cb3195fa1784b2f29b7c6f9392708 100644 (file)
@@ -1,6 +1,5 @@
 git-write-tree(1)
 =================
-v0.1, May 2005
 
 NAME
 ----
index e14102127958d98b1bf37a8d7560320249495ad7..59d0dc87606548ee02d60936b9cb872b1add3f8b 100644 (file)
@@ -1,6 +1,5 @@
 git(7)
 ======
-v0.99.6, Sep 2005
 
 NAME
 ----
@@ -69,6 +68,9 @@ gitlink:git-commit-tree[1]::
 gitlink:git-hash-object[1]::
        Computes the object ID from a file.
 
+gitlink:git-index-pack.html[1]::
+       Build pack index file for an existing packed archive.
+
 gitlink:git-init-db[1]::
        Creates an empty git object database
 
@@ -131,6 +133,9 @@ gitlink:git-ls-tree[1]::
 gitlink:git-merge-base[1]::
        Finds as good a common ancestor as possible for a merge
 
+gitlink:git-name-rev[1]::
+       Find symbolic names for given revs
+
 gitlink:git-rev-list[1]::
        Lists commit objects in reverse chronological order
 
@@ -147,7 +152,7 @@ gitlink:git-var[1]::
        Displays a git logical variable
 
 gitlink:git-verify-pack[1]::
-       Validates packed GIT archive files
+       Validates packed git archive files
 
 The interrogate commands may create files - and you can force them to
 touch the working file set - but in general they don't
@@ -164,11 +169,11 @@ gitlink:git-fetch-pack[1]::
        Updates from a remote repository.
 
 gitlink:git-http-fetch[1]::
-       Downloads a remote GIT repository via HTTP
+       Downloads a remote git repository via HTTP
        Previously this command was known as git-http-pull.
 
 gitlink:git-local-fetch[1]::
-       Duplicates another GIT repository on a local system
+       Duplicates another git repository on a local system
        Previously this command was known as git-local-pull.
 
 gitlink:git-peek-remote[1]::
@@ -180,6 +185,9 @@ gitlink:git-receive-pack[1]::
 gitlink:git-send-pack[1]::
        Pushes to a remote repository, intelligently.
 
+gitlink:git-shell[1]::
+       Restricted shell for GIT-only SSH access.
+
 gitlink:git-ssh-fetch[1]::
        Pulls from a remote repository over ssh connection
        Previously this command was known as git-ssh-pull.
@@ -204,6 +212,9 @@ gitlink:git-add[1]::
        Add paths to the index file.
        Previously this command was known as git-add-script.
 
+gitlink:git-am[1]::
+       Apply patches from a mailbox, but cooler.
+
 gitlink:git-applymbox[1]::
        Apply patches from a mailbox.
 
@@ -257,6 +268,9 @@ gitlink:git-ls-remote[1]::
 gitlink:git-merge[1]::
        Grand unified merge driver.
 
+gitlink:git-mv[1]::
+       Move or rename a file, a directory, or a symlink.
+
 gitlink:git-octopus[1]::
        Merge more than two commits.
        Previously this command was known as git-octopus-script.
@@ -323,7 +337,7 @@ gitlink:git-archimport[1]::
        Previously this command was known as git-archimport-script.
 
 gitlink:git-convert-objects[1]::
-       Converts old-style GIT repository
+       Converts old-style git repository
        Previously this command was known as git-convert-cache.
 
 gitlink:git-cvsimport[1]::
@@ -342,17 +356,29 @@ gitlink:git-relink[1]::
        Hardlink common objects in local repositories.
        Previously this command was known as git-relink-script.
 
+gitlink:git-svnimport[1]::
+       Import a SVN repository into git.
+
 gitlink:git-sh-setup[1]::
        Common git shell script setup code.
        Previously this command was known as git-sh-setup-script.
 
+gitlink:git-symbolic-ref[1]::
+       Read and modify symbolic refs
+
 gitlink:git-tag[1]::
        An example script to create a tag object signed with GPG
        Previously this command was known as git-tag-script.
 
+gitlink:git-update-ref[1]::
+       Update the object name stored in a ref safely.
+
 
 Interrogators:
 
+gitlink:git-check-ref-format[1]::
+       Make sure ref name is well formed.
+
 gitlink:git-cherry[1]::
        Find commits not merged upstream.
 
@@ -361,7 +387,7 @@ gitlink:git-count-objects[1]::
        Previously this command was known as git-count-objects-script.
 
 gitlink:git-daemon[1]::
-       A really simple server for GIT repositories.
+       A really simple server for git repositories.
 
 gitlink:git-get-tar-commit-id[1]::
        Extract commit ID from an archive created using git-tar-tree.
@@ -390,6 +416,9 @@ gitlink:git-send-email[1]::
        Send patch e-mails out of "format-patch --mbox" output.
        Previously this command was known as git-send-email-script.
 
+gitlink:git-symbolic-refs[1]::
+       Read and modify symbolic refs.
+
 gitlink:git-stripspace[1]::
        Filter out empty lines.
 
@@ -401,6 +430,37 @@ gitlink:gitk[1]::
        gitk.
 
 
+Configuration Mechanism
+-----------------------
+
+Starting from 0.99.9 (actually mid 0.99.8.GIT), .git/config file
+is used to hold per-repository configuration options.  It is a
+simple text file modelled after `.ini` format familiar to some
+people.  Here is an example:
+
+------------
+#
+# This is the config file, and
+# a '#' or ';' character indicates
+# a comment
+#
+
+; core variables
+[core]
+       ; Don't trust file modes
+       filemode = false
+
+; user identity
+[user]
+       name = "Junio C Hamano"
+       email = "junkio@twinsun.com"
+
+------------
+
+Various commands read from the configuration file and adjust
+their operation accordingly.
+
+
 Identifier Terminology
 ----------------------
 <object>::
index a069b7bb0cfbdacfc0ac2dd2183981b806792805..eb7b4710246c627d178c518d39c565ce48a02f02 100644 (file)
@@ -1,5 +1,5 @@
 object::
-       The unit of storage in GIT. It is uniquely identified by
+       The unit of storage in git. It is uniquely identified by
        the SHA1 of its contents. Consequently, an object can not
        be changed.
 
index 57f47208716fd1f51f40d8d8104b4e764e0a013d..7ee3571bc09642f07ec50d5b3cfc9d802119889e 100644 (file)
@@ -1,6 +1,5 @@
-Hooks used by GIT
+Hooks used by git
 =================
-v0.99.6, Sep 2005
 
 Hooks are little scripts you can place in `$GIT_DIR/hooks`
 directory to trigger action at certain points.  When
index 8dbddbf63fc92c1ae85b2bdbda3266b92d67407d..e8db9d7ca5bae5b5accdc8508e20a743ff053213 100644 (file)
@@ -6,7 +6,7 @@
 ===============================================================
 - Rsync URL:           rsync://remote.machine/path/to/repo.git/
 - HTTP(s) URL:         http://remote.machine/path/to/repo.git/
-- GIT URL:             git://remote.machine/path/to/repo.git/
+- git URL:             git://remote.machine/path/to/repo.git/
                        or remote.machine:/path/to/repo.git/
 - Local directory:     /path/to/repo.git/
 ===============================================================
@@ -75,13 +75,3 @@ Some short-cut notations are also supported.
   pushing.  That is, do not store it locally if
   fetching, and update the same name if pushing.
 
--a, \--append::
-       Append ref names and object names of fetched refs to the
-       existing contents of $GIT_DIR/FETCH_HEAD.  Without this
-       option old data in $GIT_DIR/FETCH_HEAD will be overwritten.
-
--f, \--force::
-       Usually, the command refuses to update a local ref that is
-       not an ancestor of the remote ref used to overwrite it.
-       This flag disables the check.  What this means is that the
-       local repository can lose commits; use it with care.
index d20fa80d872b94bdf56d95ac41ce9341c777281a..1b5f2282411486ad903939ed2e56997a27484670 100644 (file)
@@ -1,6 +1,5 @@
-GIT repository layout
+git repository layout
 =====================
-v0.99.5, Sep 2005
 
 You may find these things in your git repository (`.git`
 directory for a repository associated with your working tree, or
@@ -120,7 +119,7 @@ info/grafts::
 info/exclude::
        This file, by convention among Porcelains, stores the
        exclude pattern list.  `git status` looks at it, but
-       otherwise it is not looked at by any of the core GIT
+       otherwise it is not looked at by any of the core git
        commands.
 
 remotes::
index 36f42e051c610a89c5bbebb56c52817815c81adf..b9f737e9640a4a4a5c9554c8d6bdd1b7847ec5b2 100644 (file)
@@ -1,6 +1,5 @@
 A short git tutorial
 ====================
-v0.99.5, Aug 2005
 
 Introduction
 ------------
@@ -52,7 +51,9 @@ your new project. You will now have a `.git` directory, and you can
 inspect that with `ls`. For your new empty project, it should show you
 three entries, among other things:
 
- - a symlink called `HEAD`, pointing to `refs/heads/master`
+ - a symlink called `HEAD`, pointing to `refs/heads/master` (if your
+   platform does not have native symlinks, it is a file containing the
+   line "ref: refs/heads/master")
 +
 Don't worry about the fact that the file that the `HEAD` link points to
 doesn't even exist yet -- you haven't created the commit that will
@@ -161,7 +162,7 @@ you'll have to use the object name, not the filename of the object:
        git-cat-file -t 557db03de997c86a4a028e1ebd3a1ceb225be238
 
 where the `-t` tells `git-cat-file` to tell you what the "type" of the
-object is. Git will tell you that you have a "blob" object (ie just a
+object is. git will tell you that you have a "blob" object (ie just a
 regular file), and you can see the contents with
 
        git-cat-file "blob" 557db03
@@ -228,6 +229,7 @@ which will spit out
 
 ------------
 diff --git a/hello b/hello
+index 557db03..263414f 100644
 --- a/hello
 +++ b/hello
 @@ -1 +1,2 @@
@@ -290,13 +292,16 @@ also wants to get a commit message
 on its standard input, and it will write out the resulting object name for the
 commit to its standard output.
 
-And this is where we start using the `.git/HEAD` file. The `HEAD` file is
-supposed to contain the reference to the top-of-tree, and since that's
-exactly what `git-commit-tree` spits out, we can do this all with a simple
-shell pipeline:
+And this is where we create the `.git/refs/heads/master` file
+which is pointed at by `HEAD`. This file is supposed to contain
+the reference to the top-of-tree of the master branch, and since
+that's exactly what `git-commit-tree` spits out, we can do this
+all with a sequence of simple shell commands:
 
 ------------------------------------------------
-echo "Initial commit" | git-commit-tree $(git-write-tree) > .git/HEAD
+tree=$(git-write-tree)
+commit=$(echo 'Initial commit' | git-commit-tree $tree)
+git-update-ref HEAD $(commit)
 ------------------------------------------------
 
 which will say:
@@ -378,7 +383,7 @@ come from the working tree or not.
 
 This is not hard to understand, as soon as you realize that git simply
 never knows (or cares) about files that it is not told about
-explicitly. Git will never go *looking* for files to compare, it
+explicitly. git will never go *looking* for files to compare, it
 expects you to tell it what the files are, and that's what the index
 is there for.
 ================
@@ -544,7 +549,7 @@ name for the state at that point.
 Copying repositories
 --------------------
 
-Git repositories are normally totally self-sufficient, and it's worth noting
+git repositories are normally totally self-sufficient, and it's worth noting
 that unlike CVS, for example, there is no separate notion of
 "repository" and "working tree". A git repository normally *is* the
 working tree, with the local git information hidden in the `.git`
@@ -692,7 +697,9 @@ other point in the history than the current `HEAD`, you can do so by
 just telling `git checkout` what the base of the checkout would be.
 In other words, if you have an earlier tag or branch, you'd just do
 
-       git checkout -b mybranch earlier-commit
+------------
+git checkout -b mybranch earlier-commit
+------------
 
 and it would create the new branch `mybranch` at the earlier commit,
 and check out the state at that time.
@@ -700,17 +707,29 @@ and check out the state at that time.
 
 You can always just jump back to your original `master` branch by doing
 
-       git checkout master
+------------
+git checkout master
+------------
 
 (or any other branch-name, for that matter) and if you forget which
 branch you happen to be on, a simple
 
-       ls -l .git/HEAD
+------------
+ls -l .git/HEAD
+------------
 
-will tell you where it's pointing. To get the list of branches
-you have, you can say
+will tell you where it's pointing (Note that on platforms with bad or no
+symlink support, you have to execute
 
-       git branch
+------------
+cat .git/HEAD
+------------
+
+instead). To get the list of branches you have, you can say
+
+------------
+git branch
+------------
 
 which is nothing more than a simple script around `ls .git/refs/heads`.
 There will be asterisk in front of the branch you are currently on.
@@ -718,7 +737,9 @@ There will be asterisk in front of the branch you are currently on.
 Sometimes you may wish to create a new branch _without_ actually
 checking it out and switching to it. If so, just use the command
 
-       git branch <branchname> [startingpoint]
+------------
+git branch <branchname> [startingpoint]
+------------
 
 which will simply _create_ the branch, but will not do anything further. 
 You can then later -- once you decide that you want to actually develop
@@ -844,7 +865,6 @@ $ git show-branch master mybranch
  ! [mybranch] Some work.
 --
 +  [master] Merged "mybranch" changes.
-+  [master~1] Some fun.
 ++ [mybranch] Some work.
 ------------------------------------------------
 
@@ -871,8 +891,10 @@ Now, let's pretend you are the one who did all the work in
 to the `master` branch. Let's go back to `mybranch`, and run
 resolve to get the "upstream changes" back to your branch.
 
-       git checkout mybranch
-       git resolve HEAD master "Merge upstream changes."
+------------
+git checkout mybranch
+git resolve HEAD master "Merge upstream changes."
+------------
 
 This outputs something like this (the actual commit object names
 would be different)
@@ -951,7 +973,7 @@ This transport is the same as SSH transport but uses `sh` to run
 both ends on the local machine instead of running other end on
 the remote machine via `ssh`.
 
-GIT Native::
+git Native::
        `git://remote.machine/path/to/repo.git/`
 +
 This transport was designed for anonymous downloading.  Like SSH
@@ -972,13 +994,13 @@ necessary objects.  Because of this behaviour, they are
 sometimes also called 'commit walkers'.
 +
 The 'commit walkers' are sometimes also called 'dumb
-transports', because they do not require any GIT aware smart
-server like GIT Native transport does.  Any stock HTTP server
+transports', because they do not require any git aware smart
+server like git Native transport does.  Any stock HTTP server
 would suffice.
 +
 There are (confusingly enough) `git-ssh-fetch` and `git-ssh-upload`
 programs, which are 'commit walkers'; they outlived their
-usefulness when GIT Native and SSH transports were introduced,
+usefulness when git Native and SSH transports were introduced,
 and not used by `git pull` or `git push` scripts.
 
 Once you fetch from the remote repository, you `resolve` that
@@ -1082,19 +1104,23 @@ done only once.
 on the remote machine. The communication between the two over
 the network internally uses an SSH connection.
 
-Your private repository's GIT directory is usually `.git`, but
+Your private repository's git directory is usually `.git`, but
 your public repository is often named after the project name,
 i.e. `<project>.git`. Let's create such a public repository for
 project `my-git`. After logging into the remote machine, create
 an empty directory:
 
-       mkdir my-git.git
+------------
+mkdir my-git.git
+------------
 
-Then, make that directory into a GIT repository by running
+Then, make that directory into a git repository by running
 `git init-db`, but this time, since its name is not the usual
 `.git`, we do things slightly differently:
 
-       GIT_DIR=my-git.git git-init-db
+------------
+GIT_DIR=my-git.git git-init-db
+------------
 
 Make sure this directory is available for others you want your
 changes to be pulled by via the transport of your choice. Also
@@ -1118,7 +1144,9 @@ Your "public repository" is now ready to accept your changes.
 Come back to the machine you have your private repository. From
 there, run this command:
 
-       git push <public-host>:/path/to/my-git.git master
+------------
+git push <public-host>:/path/to/my-git.git master
+------------
 
 This synchronizes your public repository to match the named
 branch head (i.e. `master` in this case) and objects reachable
@@ -1128,7 +1156,9 @@ As a real example, this is how I update my public git
 repository. Kernel.org mirror network takes care of the
 propagation to other publicly visible machines:
 
-       git push master.kernel.org:/pub/scm/git/git.git/ 
+------------
+git push master.kernel.org:/pub/scm/git/git.git/ 
+------------
 
 
 Packing your repository
@@ -1141,7 +1171,9 @@ not so convenient to transport over the network. Since git objects are
 immutable once they are created, there is a way to optimize the
 storage by "packing them together". The command
 
-       git repack
+------------
+git repack
+------------
 
 will do it for you. If you followed the tutorial examples, you
 would have accumulated about 17 objects in `.git/objects/??/`
@@ -1165,7 +1197,9 @@ Our programs are always perfect ;-).
 Once you have packed objects, you do not need to leave the
 unpacked objects that are contained in the pack file anymore.
 
-       git prune-packed
+------------
+git prune-packed
+------------
 
 would remove them for you.
 
index a0d4de5a25404bb907302a13ab7a670eaeb0d11d..5bb51085e1896f3e732d102c3342629c1d19c5a6 100644 (file)
--- a/Makefile
+++ b/Makefile
 # Define NEEDS_SOCKET if linking with libc is not enough (SunOS,
 # Patrick Mauritz).
 #
+# Define NO_MMAP if you want to avoid mmap.
+#
 # Define WITH_OWN_SUBPROCESS_PY if you want to use with python 2.3.
 #
+# Define NO_IPV6 if you lack IPv6 support and getaddrinfo().
+#
 # Define COLLISION_CHECK below if you believe that SHA1's
 # 1461501637330902918203684832716283019655932542976 hashes do not give you
 # sufficient guarantee that no collisions between objects will ever happen.
@@ -48,7 +52,7 @@
 
 # DEFINES += -DUSE_STDEV
 
-GIT_VERSION = 0.99.8g
+GIT_VERSION = 0.99.9a
 
 CFLAGS = -g -O2 -Wall
 ALL_CFLAGS = $(CFLAGS) $(PLATFORM_DEFINES) $(DEFINES)
@@ -83,44 +87,45 @@ SCRIPT_SH = \
        git-repack.sh git-request-pull.sh git-reset.sh \
        git-resolve.sh git-revert.sh git-sh-setup.sh git-status.sh \
        git-tag.sh git-verify-tag.sh git-whatchanged.sh git.sh \
-       git-applymbox.sh git-applypatch.sh \
+       git-applymbox.sh git-applypatch.sh git-am.sh \
        git-merge.sh git-merge-stupid.sh git-merge-octopus.sh \
        git-merge-resolve.sh git-grep.sh
 
 SCRIPT_PERL = \
        git-archimport.perl git-cvsimport.perl git-relink.perl \
-       git-rename.perl git-shortlog.perl git-fmt-merge-msg.perl
+       git-rename.perl git-shortlog.perl git-fmt-merge-msg.perl \
+       git-findtags.perl git-svnimport.perl git-mv.perl
 
 SCRIPT_PYTHON = \
        git-merge-recursive.py
 
 # The ones that do not have to link with lcrypto nor lz.
 SIMPLE_PROGRAMS = \
-       git-get-tar-commit-id git-mailinfo git-mailsplit git-stripspace \
-       git-daemon git-var
+       git-get-tar-commit-id$X git-mailinfo$X git-mailsplit$X \
+       git-stripspace$X git-var$X git-daemon$X
 
 # ... and all the rest
 PROGRAMS = \
-       git-apply git-cat-file \
-       git-checkout-index git-clone-pack git-commit-tree \
-       git-convert-objects git-diff-files \
-       git-diff-index git-diff-stages \
-       git-diff-tree git-fetch-pack git-fsck-objects \
-       git-hash-object git-init-db \
-       git-local-fetch git-ls-files git-ls-tree git-merge-base \
-       git-merge-index git-mktag git-pack-objects git-patch-id \
-       git-peek-remote git-prune-packed git-read-tree \
-       git-receive-pack git-rev-list git-rev-parse \
-       git-send-pack git-show-branch \
-       git-show-index git-ssh-fetch \
-       git-ssh-upload git-tar-tree git-unpack-file \
-       git-unpack-objects git-update-index git-update-server-info \
-       git-upload-pack git-verify-pack git-write-tree \
-       git-update-ref git-symbolic-ref git-check-ref-format \
-       $(SIMPLE_PROGRAMS)
+       git-apply$X git-cat-file$X \
+       git-checkout-index$X git-clone-pack$X git-commit-tree$X \
+       git-convert-objects$X git-diff-files$X \
+       git-diff-index$X git-diff-stages$X \
+       git-diff-tree$X git-fetch-pack$X git-fsck-objects$X \
+       git-hash-object$X git-index-pack$X git-init-db$X \
+       git-local-fetch$X git-ls-files$X git-ls-tree$X git-merge-base$X \
+       git-merge-index$X git-mktag$X git-pack-objects$X git-patch-id$X \
+       git-peek-remote$X git-prune-packed$X git-read-tree$X \
+       git-receive-pack$X git-rev-list$X git-rev-parse$X \
+       git-send-pack$X git-show-branch$X git-shell$X \
+       git-show-index$X git-ssh-fetch$X \
+       git-ssh-upload$X git-tar-tree$X git-unpack-file$X \
+       git-unpack-objects$X git-update-index$X git-update-server-info$X \
+       git-upload-pack$X git-verify-pack$X git-write-tree$X \
+       git-update-ref$X git-symbolic-ref$X git-check-ref-format$X \
+       git-name-rev$X $(SIMPLE_PROGRAMS)
 
 # Backward compatibility -- to be removed after 1.0
-PROGRAMS += git-ssh-pull git-ssh-push
+PROGRAMS += git-ssh-pull$X git-ssh-push$X
 
 GIT_LIST_TWEAK =
 
@@ -146,7 +151,7 @@ LIB_H = \
 
 DIFF_OBJS = \
        diff.o diffcore-break.o diffcore-order.o diffcore-pathspec.o \
-       diffcore-pickaxe.o diffcore-rename.o
+       diffcore-pickaxe.o diffcore-rename.o tree-diff.o
 
 LIB_OBJS = \
        blob.o commit.o connect.o count-delta.o csum-file.o \
@@ -154,19 +159,34 @@ LIB_OBJS = \
        object.o pack-check.o patch-delta.o path.o pkt-line.o \
        quote.o read-cache.o refs.o run-command.o \
        server-info.o setup.o sha1_file.o sha1_name.o strbuf.o \
-       tag.o tree.o usage.o $(DIFF_OBJS)
+       tag.o tree.o usage.o config.o environment.o ctype.o copy.o \
+       $(DIFF_OBJS)
 
 LIBS = $(LIB_FILE)
 LIBS += -lz
 
+# Shell quote;
+# Result of this needs to be placed inside ''
+shq = $(subst ','\'',$(1))
+# This has surrounding ''
+shellquote = '$(call shq,$(1))'
+
 #
 # Platform specific tweaks
 #
-ifeq ($(shell uname -s),Darwin)
+
+# We choose to avoid "if .. else if .. else .. endif endif"
+# because maintaining the nesting to match is a pain.  If
+# we had "elif" things would have been much nicer...
+uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not')
+uname_M := $(shell sh -c 'uname -m 2>/dev/null || echo not')
+uname_O := $(shell sh -c 'uname -o 2>/dev/null || echo not')
+
+ifeq ($(uname_S),Darwin)
        NEEDS_SSL_WITH_CRYPTO = YesPlease
        NEEDS_LIBICONV = YesPlease
 endif
-ifeq ($(shell uname -s),SunOS)
+ifeq ($(uname_S),SunOS)
        NEEDS_SOCKET = YesPlease
        NEEDS_NSL = YesPlease
        SHELL_PATH = /bin/bash
@@ -176,14 +196,23 @@ ifeq ($(shell uname -s),SunOS)
        TAR = gtar
        PLATFORM_DEFINES += -D__EXTENSIONS__
 endif
-ifneq (,$(findstring arm,$(shell uname -m)))
-       ARM_SHA1 = YesPlease
+ifeq ($(uname_O),Cygwin)
+       NO_STRCASESTR = YesPlease
+       NEEDS_LIBICONV = YesPlease
+       NO_IPV6 = YesPlease
+       X = .exe
+       PLATFORM_DEFINES += -DUSE_SYMLINK_HEAD=0
 endif
-ifeq ($(shell uname -s),OpenBSD)
+ifeq ($(uname_S),OpenBSD)
        NO_STRCASESTR = YesPlease
        NEEDS_LIBICONV = YesPlease
        PLATFORM_DEFINES += -I/usr/local/include -L/usr/local/lib
 endif
+ifneq (,$(findstring arm,$(uname_M)))
+       ARM_SHA1 = YesPlease
+endif
+
+-include config.mak
 
 ifndef NO_CURL
        ifdef CURLDIR
@@ -193,7 +222,7 @@ ifndef NO_CURL
        else
                CURL_LIBCURL = -lcurl
        endif
-       PROGRAMS += git-http-fetch
+       PROGRAMS += git-http-fetch$X
 endif
 
 ifndef SHELL_PATH
@@ -217,7 +246,7 @@ ifndef NO_OPENSSL
                OPENSSL_LINK =
        endif
 else
-       DEFINES += '-DNO_OPENSSL'
+       DEFINES += -DNO_OPENSSL
        MOZILLA_SHA1 = 1
        OPENSSL_LIBSSL =
 endif
@@ -250,6 +279,13 @@ ifdef NO_STRCASESTR
        DEFINES += -Dstrcasestr=gitstrcasestr -DNO_STRCASESTR=1
        LIB_OBJS += compat/strcasestr.o
 endif
+ifdef NO_MMAP
+       DEFINES += -Dmmap=gitfakemmap -Dmunmap=gitfakemunmap -DNO_MMAP
+       LIB_OBJS += compat/mmap.o
+endif
+ifdef NO_IPV6
+       DEFINES += -DNO_IPV6 -Dsockaddr_storage=sockaddr_in
+endif
 
 ifdef PPC_SHA1
        SHA1_HEADER = "ppc/sha1.h"
@@ -269,14 +305,14 @@ endif
 endif
 endif
 
-DEFINES += '-DSHA1_HEADER=$(SHA1_HEADER)'
+DEFINES += -DSHA1_HEADER=$(call shellquote,$(SHA1_HEADER))
 
 SCRIPTS = $(patsubst %.sh,%,$(SCRIPT_SH)) \
          $(patsubst %.perl,%,$(SCRIPT_PERL)) \
          $(patsubst %.py,%,$(SCRIPT_PYTHON)) \
-         gitk
+         gitk git-cherry-pick
 
-export TAR INSTALL DESTDIR SHELL_PATH
+export prefix TAR INSTALL DESTDIR SHELL_PATH template_dir
 ### Build rules
 
 all: $(PROGRAMS) $(SCRIPTS)
@@ -286,58 +322,67 @@ all:
 
 git: git.sh Makefile
        rm -f $@+ $@
-       sed -e '1s|#!.*/sh|#!$(SHELL_PATH)|' \
+       sed -e '1s|#!.*/sh|#!$(call shq,$(SHELL_PATH))|' \
            -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \
+           -e 's/@@X@@/$(X)/g' \
            $(GIT_LIST_TWEAK) <$@.sh >$@+
        chmod +x $@+
        mv $@+ $@
 
 $(filter-out git,$(patsubst %.sh,%,$(SCRIPT_SH))) : % : %.sh
        rm -f $@
-       sed -e '1s|#!.*/sh|#!$(SHELL_PATH)|' $@.sh >$@
+       sed -e '1s|#!.*/sh|#!$(call shq,$(SHELL_PATH))|' \
+           -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \
+           $@.sh >$@
        chmod +x $@
 
 $(patsubst %.perl,%,$(SCRIPT_PERL)) : % : %.perl
        rm -f $@
-       sed -e '1s|#!.*perl|#!$(PERL_PATH)|' $@.perl >$@
+       sed -e '1s|#!.*perl|#!$(call shq,$(PERL_PATH))|' \
+           -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \
+           $@.perl >$@
        chmod +x $@
 
 $(patsubst %.py,%,$(SCRIPT_PYTHON)) : % : %.py
        rm -f $@
-       sed -e '1s|#!.*python|#!$(PYTHON_PATH)|' \
-           -e 's|@@GIT_PYTHON_PATH@@|$(GIT_PYTHON_DIR)|g' \
-               $@.py >$@
+       sed -e '1s|#!.*python|#!$(call shq,$(PYTHON_PATH))|' \
+           -e 's|@@GIT_PYTHON_PATH@@|$(call shq,$(GIT_PYTHON_DIR))|g' \
+           -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \
+           $@.py >$@
        chmod +x $@
 
+git-cherry-pick: git-revert
+       cp $< $@
+
 %.o: %.c
        $(CC) -o $*.o -c $(ALL_CFLAGS) $<
 %.o: %.S
        $(CC) -o $*.o -c $(ALL_CFLAGS) $<
 
-git-%: %.o $(LIB_FILE)
+git-%$X: %.o $(LIB_FILE)
        $(CC) $(ALL_CFLAGS) -o $@ $(filter %.o,$^) $(LIBS)
 
-git-mailinfo : SIMPLE_LIB += $(LIB_4_ICONV)
+git-mailinfo$X : SIMPLE_LIB += $(LIB_4_ICONV)
 $(SIMPLE_PROGRAMS) : $(LIB_FILE)
-$(SIMPLE_PROGRAMS) : git-% : %.o
+$(SIMPLE_PROGRAMS) : git-%$X : %.o
        $(CC) $(ALL_CFLAGS) -o $@ $(filter %.o,$^) $(LIB_FILE) $(SIMPLE_LIB)
 
-git-http-fetch: fetch.o
-git-local-fetch: fetch.o
-git-ssh-fetch: rsh.o fetch.o
-git-ssh-upload: rsh.o
-git-ssh-pull: rsh.o fetch.o
-git-ssh-push: rsh.o
+git-http-fetch$X: fetch.o
+git-local-fetch$X: fetch.o
+git-ssh-fetch$X: rsh.o fetch.o
+git-ssh-upload$X: rsh.o
+git-ssh-pull$X: rsh.o fetch.o
+git-ssh-push$X: rsh.o
 
-git-http-fetch: LIBS += $(CURL_LIBCURL)
-git-rev-list: LIBS += $(OPENSSL_LIBSSL)
+git-http-fetch$X: LIBS += $(CURL_LIBCURL)
+git-rev-list$X: LIBS += $(OPENSSL_LIBSSL)
 
 init-db.o: init-db.c
        $(CC) -c $(ALL_CFLAGS) \
-               -DDEFAULT_GIT_TEMPLATE_DIR='"$(template_dir)"' $*.c
+               -DDEFAULT_GIT_TEMPLATE_DIR=$(call shellquote,"$(template_dir)") $*.c
 
 $(LIB_OBJS): $(LIB_H)
-$(patsubst git-%,%.o,$(PROGRAMS)): $(LIB_H)
+$(patsubst git-%$X,%.o,$(PROGRAMS)): $(LIB_H)
 $(DIFF_OBJS): diffcore.h
 
 $(LIB_FILE): $(LIB_OBJS)
@@ -352,10 +397,10 @@ doc:
 test: all
        $(MAKE) -C t/ all
 
-test-date: test-date.c date.o
+test-date$X: test-date.c date.o
        $(CC) $(ALL_CFLAGS) -o $@ test-date.c date.o
 
-test-delta: test-delta.c diff-delta.o patch-delta.o
+test-delta$X: test-delta.c diff-delta.o patch-delta.o
        $(CC) $(ALL_CFLAGS) -o $@ $^
 
 check:
@@ -366,13 +411,12 @@ check:
 ### Installation rules
 
 install: $(PROGRAMS) $(SCRIPTS)
-       $(INSTALL) -d -m755 $(DESTDIR)$(bindir)
-       $(INSTALL) $(PROGRAMS) $(SCRIPTS) $(DESTDIR)$(bindir)
-       $(INSTALL) git-revert $(DESTDIR)$(bindir)/git-cherry-pick
-       sh ./cmd-rename.sh $(DESTDIR)$(bindir)
+       $(INSTALL) -d -m755 $(call shellquote,$(DESTDIR)$(bindir))
+       $(INSTALL) $(PROGRAMS) $(SCRIPTS) $(call shellquote,$(DESTDIR)$(bindir))
+       sh ./cmd-rename.sh $(call shellquote,$(DESTDIR)$(bindir))
        $(MAKE) -C templates install
-       $(INSTALL) -d -m755 $(DESTDIR)$(GIT_PYTHON_DIR)
-       $(INSTALL) $(PYMODULES) $(DESTDIR)$(GIT_PYTHON_DIR)
+       $(INSTALL) -d -m755 $(call shellquote,$(DESTDIR)$(GIT_PYTHON_DIR))
+       $(INSTALL) $(PYMODULES) $(call shellquote,$(DESTDIR)$(GIT_PYTHON_DIR))
 
 install-doc:
        $(MAKE) -C Documentation install
diff --git a/apply.c b/apply.c
index c58d9a249135872c7df13e2b99c24fde355367ca..3e53b3438169bcaaf4db97669b062ebe14335fe6 100644 (file)
--- a/apply.c
+++ b/apply.c
@@ -5,36 +5,28 @@
  *
  * This applies patches on top of some (arbitrary) version of the SCM.
  *
- * NOTE! It does all its work in the index file, and only cares about
- * the files in the working directory if you tell it to "merge" the
- * patch apply.
- *
- * Even when merging it always takes the source from the index, and
- * uses the working tree as a "branch" for a 3-way merge.
  */
-#include <ctype.h>
 #include <fnmatch.h>
 #include "cache.h"
 #include "quote.h"
 
-// We default to the merge behaviour, since that's what most people would
-// expect.
-//
 //  --check turns on checking that the working tree matches the
 //    files that are being modified, but doesn't apply the patch
 //  --stat does just a diffstat, and doesn't actually apply
-//  --show-files shows the directory changes
+//  --numstat does numeric diffstat, and doesn't actually apply
+//  --index-info shows the old and new index info for paths if available.
 //
-static int merge_patch = 1;
 static int check_index = 0;
 static int write_index = 0;
 static int diffstat = 0;
+static int numstat = 0;
 static int summary = 0;
 static int check = 0;
 static int apply = 1;
-static int show_files = 0;
+static int show_index_info = 0;
+static int line_termination = '\n';
 static const char apply_usage[] =
-"git-apply [--no-merge] [--stat] [--summary] [--check] [--index] [--apply] [--show-files] <patch>...";
+"git-apply [--stat] [--numstat] [--summary] [--check] [--index] [--apply] [--index-info] [-z] <patch>...";
 
 /*
  * For "diff-stat" like behaviour, we keep track of the biggest change
@@ -67,6 +59,8 @@ struct patch {
        struct fragment *fragments;
        char *result;
        unsigned long resultsize;
+       char old_sha1_prefix[41];
+       char new_sha1_prefix[41];
        struct patch *next;
 };
 
@@ -366,6 +360,38 @@ static int gitdiff_dissimilarity(const char *line, struct patch *patch)
        return 0;
 }
 
+static int gitdiff_index(const char *line, struct patch *patch)
+{
+       /* index line is N hexadecimal, "..", N hexadecimal,
+        * and optional space with octal mode.
+        */
+       const char *ptr, *eol;
+       int len;
+
+       ptr = strchr(line, '.');
+       if (!ptr || ptr[1] != '.' || 40 <= ptr - line)
+               return 0;
+       len = ptr - line;
+       memcpy(patch->old_sha1_prefix, line, len);
+       patch->old_sha1_prefix[len] = 0;
+
+       line = ptr + 2;
+       ptr = strchr(line, ' ');
+       eol = strchr(line, '\n');
+
+       if (!ptr || eol < ptr)
+               ptr = eol;
+       len = ptr - line;
+
+       if (40 <= len)
+               return 0;
+       memcpy(patch->new_sha1_prefix, line, len);
+       patch->new_sha1_prefix[len] = 0;
+       if (*ptr == ' ')
+               patch->new_mode = patch->old_mode = strtoul(ptr+1, NULL, 8);
+       return 0;
+}
+
 /*
  * This is normal for a diff that doesn't change anything: we'll fall through
  * into the next diff. Tell the parser to break out.
@@ -565,6 +591,7 @@ static int parse_git_header(char *line, int len, unsigned int size, struct patch
                        { "rename to ", gitdiff_renamedst },
                        { "similarity index ", gitdiff_similarity },
                        { "dissimilarity index ", gitdiff_dissimilarity },
+                       { "index ", gitdiff_index },
                        { "", gitdiff_unrecognized },
                };
                int i;
@@ -793,7 +820,10 @@ static int parse_fragment(char *line, unsigned long size, struct patch *patch, s
                 /* We allow "\ No newline at end of file". Depending
                  * on locale settings when the patch was produced we
                  * don't know what this line looks like. The only
-                 * thing we do know is that it begins with "\ ". */
+                 * thing we do know is that it begins with "\ ".
+                * Checking for 12 is just for sanity check -- any
+                * l10n of "\ No newline..." is at least that long.
+                */
                case '\\':
                        if (len < 12 || memcmp(line, "\\ ", 2))
                                return -1;
@@ -1156,17 +1186,39 @@ static int check_patch(struct patch *patch)
 
        if (old_name) {
                int changed;
+               int stat_ret = lstat(old_name, &st);
 
-               if (lstat(old_name, &st) < 0)
-                       return error("%s: %s", old_name, strerror(errno));
                if (check_index) {
                        int pos = cache_name_pos(old_name, strlen(old_name));
                        if (pos < 0)
-                               return error("%s: does not exist in index", old_name);
+                               return error("%s: does not exist in index",
+                                            old_name);
+                       if (stat_ret < 0) {
+                               struct checkout costate;
+                               if (errno != ENOENT)
+                                       return error("%s: %s", old_name,
+                                                    strerror(errno));
+                               /* checkout */
+                               costate.base_dir = "";
+                               costate.base_dir_len = 0;
+                               costate.force = 0;
+                               costate.quiet = 0;
+                               costate.not_new = 0;
+                               costate.refresh_cache = 1;
+                               if (checkout_entry(active_cache[pos],
+                                                  &costate) ||
+                                   lstat(old_name, &st))
+                                       return -1;
+                       }
+
                        changed = ce_match_stat(active_cache[pos], &st);
                        if (changed)
-                               return error("%s: does not match index", old_name);
+                               return error("%s: does not match index",
+                                            old_name);
                }
+               else if (stat_ret < 0)
+                       return error("%s: %s", old_name, strerror(errno));
+
                if (patch->is_new < 0)
                        patch->is_new = 0;
                st.st_mode = ntohl(create_ce_mode(st.st_mode));
@@ -1218,32 +1270,38 @@ static int check_patch_list(struct patch *patch)
        return error;
 }
 
-static void show_file(int c, unsigned int mode, const char *name)
+static inline int is_null_sha1(const unsigned char *sha1)
 {
-       printf("%c %o %s\n", c, mode, name);
+       return !memcmp(sha1, null_sha1, 20);
 }
 
-static void show_file_list(struct patch *patch)
+static void show_index_list(struct patch *list)
 {
-       for (;patch ; patch = patch->next) {
-               if (patch->is_rename) {
-                       show_file('-', patch->old_mode, patch->old_name);
-                       show_file('+', patch->new_mode, patch->new_name);
-                       continue;
-               }
-               if (patch->is_copy || patch->is_new) {
-                       show_file('+', patch->new_mode, patch->new_name);
-                       continue;
-               }
-               if (patch->is_delete) {
-                       show_file('-', patch->old_mode, patch->old_name);
-                       continue;
-               }
-               if (patch->old_mode && patch->new_mode && patch->old_mode != patch->new_mode) {
-                       printf("M %o:%o %s\n", patch->old_mode, patch->new_mode, patch->old_name);
-                       continue;
-               }
-               printf("M %o %s\n", patch->old_mode, patch->old_name);
+       struct patch *patch;
+
+       /* Once we start supporting the reverse patch, it may be
+        * worth showing the new sha1 prefix, but until then...
+        */
+       for (patch = list; patch; patch = patch->next) {
+               const unsigned char *sha1_ptr;
+               unsigned char sha1[20];
+               const char *name;
+
+               name = patch->old_name ? patch->old_name : patch->new_name;
+               if (patch->is_new)
+                       sha1_ptr = null_sha1;
+               else if (get_sha1(patch->old_sha1_prefix, sha1))
+                       die("sha1 information is lacking or useless (%s).",
+                           name);
+               else
+                       sha1_ptr = sha1;
+
+               printf("%06o %s ",patch->old_mode, sha1_to_hex(sha1_ptr));
+               if (line_termination && quote_c_style(name, NULL, NULL, 0))
+                       quote_c_style(name, NULL, stdout, 0);
+               else
+                       fputs(name, stdout);
+               putchar(line_termination);
        }
 }
 
@@ -1261,6 +1319,20 @@ static void stat_patch_list(struct patch *patch)
        printf(" %d files changed, %d insertions(+), %d deletions(-)\n", files, adds, dels);
 }
 
+static void numstat_patch_list(struct patch *patch)
+{
+       for ( ; patch; patch = patch->next) {
+               const char *name;
+               name = patch->old_name ? patch->old_name : patch->new_name;
+               printf("%d\t%d\t", patch->lines_added, patch->lines_deleted);
+               if (line_termination && quote_c_style(name, NULL, NULL, 0))
+                       quote_c_style(name, NULL, stdout, 0);
+               else
+                       fputs(name, stdout);
+               putchar('\n');
+       }
+}
+
 static void show_file_mode_name(const char *newdelete, unsigned int mode, const char *name)
 {
        if (mode)
@@ -1588,12 +1660,15 @@ static int apply_patch(int fd)
                        die("Unable to write new cachefile");
        }
 
-       if (show_files)
-               show_file_list(list);
+       if (show_index_info)
+               show_index_list(list);
 
        if (diffstat)
                stat_patch_list(list);
 
+       if (numstat)
+               numstat_patch_list(list);
+
        if (summary)
                summary_patch_list(list);
 
@@ -1622,16 +1697,16 @@ int main(int argc, char **argv)
                        excludes = x;
                        continue;
                }
-               /* NEEDSWORK: this does not do anything at this moment. */
-               if (!strcmp(arg, "--no-merge")) {
-                       merge_patch = 0;
-                       continue;
-               }
                if (!strcmp(arg, "--stat")) {
                        apply = 0;
                        diffstat = 1;
                        continue;
                }
+               if (!strcmp(arg, "--numstat")) {
+                       apply = 0;
+                       numstat = 1;
+                       continue;
+               }
                if (!strcmp(arg, "--summary")) {
                        apply = 0;
                        summary = 1;
@@ -1650,8 +1725,13 @@ int main(int argc, char **argv)
                        apply = 1;
                        continue;
                }
-               if (!strcmp(arg, "--show-files")) {
-                       show_files = 1;
+               if (!strcmp(arg, "--index-info")) {
+                       apply = 0;
+                       show_index_info = 1;
+                       continue;
+               }
+               if (!strcmp(arg, "-z")) {
+                       line_termination = 0;
                        continue;
                }
                fd = open(arg, O_RDONLY);
diff --git a/cache.h b/cache.h
index 201ce990897aaa49779fb45d7656d963739bd85e..677c6acc350155d606e2ba05a9bbf5eb28637a31 100644 (file)
--- a/cache.h
+++ b/cache.h
@@ -11,7 +11,9 @@
 #include <string.h>
 #include <errno.h>
 #include <limits.h>
+#ifndef NO_MMAP
 #include <sys/mman.h>
+#endif
 #include <sys/param.h>
 #include <netinet/in.h>
 #include <sys/types.h>
@@ -165,6 +167,7 @@ extern int ce_match_stat(struct cache_entry *ce, struct stat *st);
 extern int ce_modified(struct cache_entry *ce, struct stat *st);
 extern int ce_path_match(const struct cache_entry *ce, const char **pathspec);
 extern int index_fd(unsigned char *sha1, int fd, struct stat *st, int write_object, const char *type);
+extern int index_path(unsigned char *sha1, const char *path, struct stat *st, int write_object);
 extern void fill_stat_cache_info(struct cache_entry *ce, struct stat *st);
 
 struct cache_file {
@@ -175,6 +178,8 @@ extern int hold_index_file_for_update(struct cache_file *, const char *path);
 extern int commit_index_file(struct cache_file *);
 extern void rollback_index_file(struct cache_file *);
 
+extern int trust_executable_bit;
+
 #define MTIME_CHANGED  0x0001
 #define CTIME_CHANGED  0x0002
 #define OWNER_CHANGED  0x0004
@@ -189,6 +194,7 @@ extern char *git_path(const char *fmt, ...) __attribute__((format (printf, 1, 2)
 extern char *sha1_file_name(const unsigned char *sha1);
 extern char *sha1_pack_name(const unsigned char *sha1);
 extern char *sha1_pack_index_name(const unsigned char *sha1);
+extern const char *find_unique_abbrev(const unsigned char *sha1, int);
 extern const unsigned char null_sha1[20];
 
 int git_mkstemp(char *path, size_t n, const char *template);
@@ -218,6 +224,7 @@ extern int read_tree(void *buffer, unsigned long size, int stage, const char **p
 extern int write_sha1_from_fd(const unsigned char *sha1, int fd, char *buffer,
                              size_t bufsize, size_t *bufposn);
 extern int write_sha1_to_fd(int fd, const unsigned char *sha1);
+extern int move_temp_to_file(const char *tmpfile, char *filename);
 
 extern int has_sha1_pack(const unsigned char *sha1);
 extern int has_sha1_file(const unsigned char *sha1);
@@ -306,6 +313,7 @@ extern struct packed_git {
        void *pack_base;
        unsigned int pack_last_used;
        unsigned int pack_use_cnt;
+       int pack_local;
        unsigned char sha1[20];
        char pack_name[0]; /* something like ".git/objects/pack/xxxxx.pack" */
 } *packed_git;
@@ -332,6 +340,7 @@ extern int match_refs(struct ref *src, struct ref *dst, struct ref ***dst_tail,
                      int nr_refspec, char **refspec, int all);
 extern int get_ack(int fd, unsigned char *result_sha1);
 extern struct ref **get_remote_heads(int in, struct ref **list, int nr_match, char **match, int ignore_funny);
+extern int server_supports(const char *feature);
 
 extern struct packed_git *parse_pack_index(unsigned char *sha1);
 extern struct packed_git *parse_pack_index_file(const unsigned char *sha1,
@@ -345,7 +354,7 @@ extern struct packed_git *find_sha1_pack(const unsigned char *sha1,
 
 extern int use_packed_git(struct packed_git *);
 extern void unuse_packed_git(struct packed_git *);
-extern struct packed_git *add_packed_git(char *, int);
+extern struct packed_git *add_packed_git(char *, int, int);
 extern int num_packed_objects(const struct packed_git *p);
 extern int nth_packed_object_sha1(const struct packed_git *, int, unsigned char*);
 extern int find_pack_entry_one(const unsigned char *, struct pack_entry *, struct packed_git *);
@@ -355,4 +364,55 @@ extern void packed_object_info_detail(struct pack_entry *, char *, unsigned long
 /* Dumb servers support */
 extern int update_server_info(int);
 
+#ifdef NO_MMAP
+
+#ifndef PROT_READ
+#define PROT_READ 1
+#define PROT_WRITE 2
+#define MAP_PRIVATE 1
+#define MAP_FAILED ((void*)-1)
+#endif
+
+extern void *gitfakemmap(void *start, size_t length, int prot , int flags, int fd, off_t offset);
+extern int gitfakemunmap(void *start, size_t length);
+
+#endif
+
+typedef int (*config_fn_t)(const char *, const char *);
+extern int git_default_config(const char *, const char *);
+extern int git_config(config_fn_t fn);
+extern int git_config_int(const char *, const char *);
+extern int git_config_bool(const char *, const char *);
+
+#define MAX_GITNAME (1000)
+extern char git_default_email[MAX_GITNAME];
+extern char git_default_name[MAX_GITNAME];
+
+/* Sane ctype - no locale, and works with signed chars */
+#undef isspace
+#undef isdigit
+#undef isalpha
+#undef isalnum
+#undef tolower
+#undef toupper
+extern unsigned char sane_ctype[256];
+#define GIT_SPACE 0x01
+#define GIT_DIGIT 0x02
+#define GIT_ALPHA 0x04
+#define sane_istest(x,mask) ((sane_ctype[(unsigned char)(x)] & (mask)) != 0)
+#define isspace(x) sane_istest(x,GIT_SPACE)
+#define isdigit(x) sane_istest(x,GIT_DIGIT)
+#define isalpha(x) sane_istest(x,GIT_ALPHA)
+#define isalnum(x) sane_istest(x,GIT_ALPHA | GIT_DIGIT)
+#define tolower(x) sane_case((unsigned char)(x), 0x20)
+#define toupper(x) sane_case((unsigned char)(x), 0)
+
+static inline int sane_case(int x, int high)
+{
+       if (sane_istest(x, GIT_ALPHA))
+               x = (x & ~0x20) | high;
+       return x;
+}
+
+extern int copy_fd(int ifd, int ofd);
 #endif /* CACHE_H */
index 596d320fb67983a7f899f184024dde26caec956e..dab3778a9585bcfde98be436255542df13aacddb 100644 (file)
@@ -63,15 +63,20 @@ static int checkout_file(const char *name)
 
 static int checkout_all(void)
 {
-       int i;
+       int i, errs = 0;
 
        for (i = 0; i < active_nr ; i++) {
                struct cache_entry *ce = active_cache[i];
                if (ce_stage(ce))
                        continue;
                if (checkout_entry(ce, &state) < 0)
-                       return -1;
+                       errs++;
        }
+       if (errs)
+               /* we have already done our error reporting.
+                * exit with the same code as die().
+                */
+               exit(128);
        return 0;
 }
 
index 2ac35f6825fa84e1f18ba191d943b367744ba9a0..960921903eaa712523af0b03098970127729f363 100644 (file)
@@ -3,8 +3,8 @@
 #include "pkt-line.h"
 #include <sys/wait.h>
 
-static int quiet;
-static const char clone_pack_usage[] = "git-clone-pack [-q] [--exec=<git-upload-pack>] [<host>:]<directory> [<heads>]*";
+static const char clone_pack_usage[] =
+"git-clone-pack [--exec=<git-upload-pack>] [<host>:]<directory> [<heads>]*";
 static const char *exec = "git-upload-pack";
 
 static void clone_handshake(int fd[2], struct ref *ref)
@@ -112,11 +112,143 @@ static void write_refs(struct ref *ref)
        free(head_path);
 }
 
+static int finish_pack(const char *pack_tmp_name)
+{
+       int pipe_fd[2];
+       pid_t pid;
+       char idx[PATH_MAX];
+       char final[PATH_MAX];
+       char hash[41];
+       unsigned char sha1[20];
+       char *cp;
+       int err = 0;
+
+       if (pipe(pipe_fd) < 0)
+               die("git-clone-pack: unable to set up pipe");
+
+       strcpy(idx, pack_tmp_name); /* ".git/objects/pack-XXXXXX" */
+       cp = strrchr(idx, '/');
+       memcpy(cp, "/pidx", 5);
+
+       pid = fork();
+       if (pid < 0)
+               die("git-clone-pack: unable to fork off git-index-pack");
+       if (!pid) {
+               close(0);
+               dup2(pipe_fd[1], 1);
+               close(pipe_fd[0]);
+               close(pipe_fd[1]);
+               execlp("git-index-pack","git-index-pack",
+                      "-o", idx, pack_tmp_name, NULL);
+               error("cannot exec git-index-pack <%s> <%s>",
+                     idx, pack_tmp_name);
+               exit(1);
+       }
+       close(pipe_fd[1]);
+       if (read(pipe_fd[0], hash, 40) != 40) {
+               error("git-clone-pack: unable to read from git-index-pack");
+               err = 1;
+       }
+       close(pipe_fd[0]);
+
+       for (;;) {
+               int status, code;
+               int retval = waitpid(pid, &status, 0);
+
+               if (retval < 0) {
+                       if (errno == EINTR)
+                               continue;
+                       error("waitpid failed (%s)", strerror(retval));
+                       goto error_die;
+               }
+               if (WIFSIGNALED(status)) {
+                       int sig = WTERMSIG(status);
+                       error("git-index-pack died of signal %d", sig);
+                       goto error_die;
+               }
+               if (!WIFEXITED(status)) {
+                       error("git-index-pack died of unnatural causes %d",
+                             status);
+                       goto error_die;
+               }
+               code = WEXITSTATUS(status);
+               if (code) {
+                       error("git-index-pack died with error code %d", code);
+                       goto error_die;
+               }
+               if (err)
+                       goto error_die;
+               break;
+       }
+       hash[40] = 0;
+       if (get_sha1_hex(hash, sha1)) {
+               error("git-index-pack reported nonsense '%s'", hash);
+               goto error_die;
+       }
+       /* Now we have pack in pack_tmp_name[], and
+        * idx in idx[]; rename them to their final names.
+        */
+       snprintf(final, sizeof(final),
+                "%s/pack/pack-%s.pack", get_object_directory(), hash);
+       move_temp_to_file(pack_tmp_name, final);
+       chmod(final, 0444);
+       snprintf(final, sizeof(final),
+                "%s/pack/pack-%s.idx", get_object_directory(), hash);
+       move_temp_to_file(idx, final);
+       chmod(final, 0444);
+       return 0;
+
+ error_die:
+       unlink(idx);
+       unlink(pack_tmp_name);
+       exit(1);
+}
+
+static int clone_without_unpack(int fd[2])
+{
+       char tmpfile[PATH_MAX];
+       int ofd, ifd;
+
+       ifd = fd[0];
+       snprintf(tmpfile, sizeof(tmpfile),
+                "%s/pack/tmp-XXXXXX", get_object_directory());
+       ofd = mkstemp(tmpfile);
+       if (ofd < 0)
+               return error("unable to create temporary file %s", tmpfile);
+
+       while (1) {
+               char buf[8192];
+               ssize_t sz, wsz, pos;
+               sz = read(ifd, buf, sizeof(buf));
+               if (sz == 0)
+                       break;
+               if (sz < 0) {
+                       error("error reading pack (%s)", strerror(errno));
+                       close(ofd);
+                       unlink(tmpfile);
+                       return -1;
+               }
+               pos = 0;
+               while (pos < sz) {
+                       wsz = write(ofd, buf + pos, sz - pos);
+                       if (wsz < 0) {
+                               error("error writing pack (%s)",
+                                     strerror(errno));
+                               close(ofd);
+                               unlink(tmpfile);
+                               return -1;
+                       }
+                       pos += wsz;
+               }
+       }
+       close(ofd);
+       return finish_pack(tmpfile);
+}
+
 static int clone_pack(int fd[2], int nr_match, char **match)
 {
        struct ref *refs;
        int status;
-       pid_t pid;
 
        get_remote_heads(fd[0], &refs, nr_match, match, 1);
        if (!refs) {
@@ -124,35 +256,12 @@ static int clone_pack(int fd[2], int nr_match, char **match)
                die("no matching remote head");
        }
        clone_handshake(fd, refs);
-       pid = fork();
-       if (pid < 0)
-               die("git-clone-pack: unable to fork off git-unpack-objects");
-       if (!pid) {
-               dup2(fd[0], 0);
-               close(fd[0]);
-               close(fd[1]);
-               execlp("git-unpack-objects", "git-unpack-objects",
-                       quiet ? "-q" : NULL, NULL);
-               die("git-unpack-objects exec failed");
-       }
-       close(fd[0]);
-       close(fd[1]);
-       while (waitpid(pid, &status, 0) < 0) {
-               if (errno != EINTR)
-                       die("waiting for git-unpack-objects: %s", strerror(errno));
-       }
-       if (WIFEXITED(status)) {
-               int code = WEXITSTATUS(status);
-               if (code)
-                       die("git-unpack-objects died with error code %d", code);
+
+       status = clone_without_unpack(fd);
+
+       if (!status)
                write_refs(refs);
-               return 0;
-       }
-       if (WIFSIGNALED(status)) {
-               int sig = WTERMSIG(status);
-               die("git-unpack-objects died of signal %d", sig);
-       }
-       die("Sherlock Holmes! git-unpack-objects died of unnatural causes %d!", status);
+       return status;
 }
 
 int main(int argc, char **argv)
@@ -168,14 +277,14 @@ int main(int argc, char **argv)
                char *arg = argv[i];
 
                if (*arg == '-') {
-                       if (!strcmp("-q", arg)) {
-                               quiet = 1;
+                       if (!strcmp("-q", arg))
                                continue;
-                       }
                        if (!strncmp("--exec=", arg, 7)) {
                                exec = arg + 7;
                                continue;
                        }
+                       if (!strcmp("--keep", arg))
+                               continue;
                        usage(clone_pack_usage);
                }
                dest = arg;
index b1ef0b590ab879fbbc93d04a2f8f488a223ea58d..b60299fed0442edec21f123e92774238d4533bea 100644 (file)
@@ -5,10 +5,6 @@
  */
 #include "cache.h"
 
-#include <pwd.h>
-#include <time.h>
-#include <ctype.h>
-
 #define BLOCKING (1ul << 14)
 
 /*
@@ -89,6 +85,9 @@ int main(int argc, char **argv)
        char *buffer;
        unsigned int size;
 
+       setup_ident();
+       git_config(git_default_config);
+
        if (argc < 2 || get_sha1_hex(argv[1], tree_sha1) < 0)
                usage(commit_tree_usage);
 
@@ -104,7 +103,6 @@ int main(int argc, char **argv)
        }
        if (!parents)
                fprintf(stderr, "Committing initial tree %s\n", argv[1]);
-       setup_ident();
 
        init_buffer(&buffer, &size);
        add_buffer(&buffer, &size, "tree %s\n", sha1_to_hex(tree_sha1));
index f735f981bb2d4d7594e416bcb728ac06d09ebd0c..8f403180e5903bfa3da9df76b2cda213135c58d3 100644 (file)
--- a/commit.c
+++ b/commit.c
@@ -1,4 +1,3 @@
-#include <ctype.h>
 #include "tag.h"
 #include "commit.h"
 #include "cache.h"
diff --git a/compat/mmap.c b/compat/mmap.c
new file mode 100644 (file)
index 0000000..a051c47
--- /dev/null
@@ -0,0 +1,50 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include "../cache.h"
+
+void *gitfakemmap(void *start, size_t length, int prot , int flags, int fd, off_t offset)
+{
+       int n = 0;
+
+       if (start != NULL || !(flags & MAP_PRIVATE))
+               die("Invalid usage of gitfakemmap.");
+
+       if (lseek(fd, offset, SEEK_SET) < 0) {
+               errno = EINVAL;
+               return MAP_FAILED;
+       }
+
+       start = xmalloc(length);
+       if (start == NULL) {
+               errno = ENOMEM;
+               return MAP_FAILED;
+       }
+
+       while (n < length) {
+               int count = read(fd, start+n, length-n);
+
+               if (count == 0) {
+                       memset(start+n, 0, length-n);
+                       break;
+               }
+
+               if (count < 0) {
+                       free(start);
+                       errno = EACCES;
+                       return MAP_FAILED;
+               }
+
+               n += count;
+       }
+
+       return start;
+}
+
+int gitfakemunmap(void *start, size_t length)
+{
+       free(start);
+       return 0;
+}
+
diff --git a/config.c b/config.c
new file mode 100644 (file)
index 0000000..519fecf
--- /dev/null
+++ b/config.c
@@ -0,0 +1,236 @@
+
+#include "cache.h"
+
+#define MAXNAME (256)
+
+static FILE *config_file;
+static int config_linenr;
+static int get_next_char(void)
+{
+       int c;
+       FILE *f;
+
+       c = '\n';
+       if ((f = config_file) != NULL) {
+               c = fgetc(f);
+               if (c == '\n')
+                       config_linenr++;
+               if (c == EOF) {
+                       config_file = NULL;
+                       c = '\n';
+               }
+       }
+       return c;
+}
+
+static char *parse_value(void)
+{
+       static char value[1024];
+       int quote = 0, comment = 0, len = 0, space = 0;
+
+       for (;;) {
+               int c = get_next_char();
+               if (len >= sizeof(value))
+                       return NULL;
+               if (c == '\n') {
+                       if (quote)
+                               return NULL;
+                       value[len] = 0;
+                       return value;
+               }
+               if (comment)
+                       continue;
+               if (isspace(c) && !quote) {
+                       space = 1;
+                       continue;
+               }
+               if (space) {
+                       if (len)
+                               value[len++] = ' ';
+                       space = 0;
+               }
+               if (c == '\\') {
+                       c = get_next_char();
+                       switch (c) {
+                       case '\n':
+                               continue;
+                       case 't':
+                               c = '\t';
+                               break;
+                       case 'b':
+                               c = '\b';
+                               break;
+                       case 'n':
+                               c = '\n';
+                               break;
+                       /* Some characters escape as themselves */
+                       case '\\': case '"':
+                               break;
+                       /* Reject unknown escape sequences */
+                       default:
+                               return NULL;
+                       }
+                       value[len++] = c;
+                       continue;
+               }
+               if (c == '"') {
+                       quote = 1-quote;
+                       continue;
+               }
+               if (!quote) {
+                       if (c == ';' || c == '#') {
+                               comment = 1;
+                               continue;
+                       }
+               }
+               value[len++] = c;
+       }
+}
+
+static int get_value(config_fn_t fn, char *name, unsigned int len)
+{
+       int c;
+       char *value;
+
+       /* Get the full name */
+       for (;;) {
+               c = get_next_char();
+               if (c == EOF)
+                       break;
+               if (!isalnum(c))
+                       break;
+               name[len++] = tolower(c);
+               if (len >= MAXNAME)
+                       return -1;
+       }
+       name[len] = 0;
+       while (c == ' ' || c == '\t')
+               c = get_next_char();
+
+       value = NULL;
+       if (c != '\n') {
+               if (c != '=')
+                       return -1;
+               value = parse_value();
+               if (!value)
+                       return -1;
+       }
+       return fn(name, value);
+}
+
+static int get_base_var(char *name)
+{
+       int baselen = 0;
+
+       for (;;) {
+               int c = get_next_char();
+               if (c == EOF)
+                       return -1;
+               if (c == ']')
+                       return baselen;
+               if (!isalnum(c))
+                       return -1;
+               if (baselen > MAXNAME / 2)
+                       return -1;
+               name[baselen++] = tolower(c);
+       }
+}
+
+static int git_parse_file(config_fn_t fn)
+{
+       int comment = 0;
+       int baselen = 0;
+       static char var[MAXNAME];
+
+       for (;;) {
+               int c = get_next_char();
+               if (c == '\n') {
+                       /* EOF? */
+                       if (!config_file)
+                               return 0;
+                       comment = 0;
+                       continue;
+               }
+               if (comment || isspace(c))
+                       continue;
+               if (c == '#' || c == ';') {
+                       comment = 1;
+                       continue;
+               }
+               if (c == '[') {
+                       baselen = get_base_var(var);
+                       if (baselen <= 0)
+                               break;
+                       var[baselen++] = '.';
+                       var[baselen] = 0;
+                       continue;
+               }
+               if (!isalpha(c))
+                       break;
+               var[baselen] = tolower(c);
+               if (get_value(fn, var, baselen+1) < 0)
+                       break;
+       }
+       die("bad config file line %d", config_linenr);
+}
+
+int git_config_int(const char *name, const char *value)
+{
+       if (value && *value) {
+               char *end;
+               int val = strtol(value, &end, 0);
+               if (!*end)
+                       return val;
+       }
+       die("bad config value for '%s'", name);
+}
+
+int git_config_bool(const char *name, const char *value)
+{
+       if (!value)
+               return 1;
+       if (!*value)
+               return 0;
+       if (!strcasecmp(value, "true"))
+               return 1;
+       if (!strcasecmp(value, "false"))
+               return 0;
+       return git_config_int(name, value) != 0;
+}
+
+int git_default_config(const char *var, const char *value)
+{
+       /* This needs a better name */
+       if (!strcmp(var, "core.filemode")) {
+               trust_executable_bit = git_config_bool(var, value);
+               return 0;
+       }
+
+       if (!strcmp(var, "user.name")) {
+               strncpy(git_default_name, value, sizeof(git_default_name));
+               return 0;
+       }
+
+       if (!strcmp(var, "user.email")) {
+               strncpy(git_default_email, value, sizeof(git_default_email));
+               return 0;
+       }
+
+       /* Add other config variables here.. */
+       return 0;
+}
+
+int git_config(config_fn_t fn)
+{
+       int ret;
+       FILE *f = fopen(git_path("config"), "r");
+
+       ret = -1;
+       if (f) {
+               config_file = f;
+               config_linenr = 1;
+               ret = git_parse_file(fn);
+               fclose(f);
+       }
+       return ret;
+}
index e21d39a3574ff0815520187d21624970b19da92f..c2badc71aa2ccf63fa6d9ef02a5ee76feb54e5c9 100644 (file)
--- a/connect.c
+++ b/connect.c
@@ -8,6 +8,8 @@
 #include <arpa/inet.h>
 #include <netdb.h>
 
+static char *server_capabilities = NULL;
+
 /*
  * Read all the refs from the other end
  */
@@ -20,7 +22,7 @@ struct ref **get_remote_heads(int in, struct ref **list,
                unsigned char old_sha1[20];
                static char buffer[1000];
                char *name;
-               int len;
+               int len, name_len;
 
                len = packet_read_line(in, buffer, sizeof(buffer));
                if (!len)
@@ -36,6 +38,13 @@ struct ref **get_remote_heads(int in, struct ref **list,
                    check_ref_format(name + 5))
                        continue;
 
+               name_len = strlen(name);
+               if (len != name_len + 41) {
+                       if (server_capabilities)
+                               free(server_capabilities);
+                       server_capabilities = strdup(name + name_len + 1);
+               }
+
                if (nr_match && !path_match(name, nr_match, match))
                        continue;
                ref = xcalloc(1, sizeof(*ref) + len - 40);
@@ -47,6 +56,12 @@ struct ref **get_remote_heads(int in, struct ref **list,
        return list;
 }
 
+int server_supports(const char *feature)
+{
+       return server_capabilities &&
+               strstr(server_capabilities, feature) != NULL;
+}
+
 int get_ack(int fd, unsigned char *result_sha1)
 {
        static char line[1000];
@@ -59,8 +74,11 @@ int get_ack(int fd, unsigned char *result_sha1)
        if (!strcmp(line, "NAK"))
                return 0;
        if (!strncmp(line, "ACK ", 3)) {
-               if (!get_sha1_hex(line+4, result_sha1))
+               if (!get_sha1_hex(line+4, result_sha1)) {
+                       if (strstr(line+45, "continue"))
+                               return 2;
                        return 1;
+               }
        }
        die("git-fetch_pack: expected ACK/NAK, got '%s'", line);
 }
@@ -291,12 +309,18 @@ static enum protocol get_protocol(const char *name)
                return PROTO_SSH;
        if (!strcmp(name, "git"))
                return PROTO_GIT;
+       if (!strcmp(name, "git+ssh"))
+               return PROTO_SSH;
+       if (!strcmp(name, "ssh+git"))
+               return PROTO_SSH;
        die("I don't handle protocol '%s'", name);
 }
 
 #define STR_(s)        # s
 #define STR(s) STR_(s)
 
+#ifndef NO_IPV6
+
 static int git_tcp_connect(int fd[2], const char *prog, char *host, char *path)
 {
        int sockfd = -1;
@@ -353,6 +377,77 @@ static int git_tcp_connect(int fd[2], const char *prog, char *host, char *path)
        return 0;
 }
 
+#else /* NO_IPV6 */
+
+static int git_tcp_connect(int fd[2], const char *prog, char *host, char *path)
+{
+       int sockfd = -1;
+       char *colon, *end;
+       char *port = STR(DEFAULT_GIT_PORT), *ep;
+       struct hostent *he;
+       struct sockaddr_in sa;
+       char **ap;
+       unsigned int nport;
+
+       if (host[0] == '[') {
+               end = strchr(host + 1, ']');
+               if (end) {
+                       *end = 0;
+                       end++;
+                       host++;
+               } else
+                       end = host;
+       } else
+               end = host;
+       colon = strchr(end, ':');
+
+       if (colon) {
+               *colon = 0;
+               port = colon + 1;
+       }
+
+
+       he = gethostbyname(host);
+       if (!he)
+               die("Unable to look up %s (%s)", host, hstrerror(h_errno));
+       nport = strtoul(port, &ep, 10);
+       if ( ep == port || *ep ) {
+               /* Not numeric */
+               struct servent *se = getservbyname(port,"tcp");
+               if ( !se )
+                       die("Unknown port %s\n", port);
+               nport = se->s_port;
+       }
+
+       for (ap = he->h_addr_list; *ap; ap++) {
+               sockfd = socket(he->h_addrtype, SOCK_STREAM, 0);
+               if (sockfd < 0)
+                       continue;
+
+               memset(&sa, 0, sizeof sa);
+               sa.sin_family = he->h_addrtype;
+               sa.sin_port = htons(nport);
+               memcpy(&sa.sin_addr, ap, he->h_length);
+
+               if (connect(sockfd, (struct sockaddr *)&sa, sizeof sa) < 0) {
+                       close(sockfd);
+                       sockfd = -1;
+                       continue;
+               }
+               break;
+       }
+
+       if (sockfd < 0)
+               die("unable to connect a socket (%s)", strerror(errno));
+
+       fd[0] = sockfd;
+       fd[1] = sockfd;
+       packet_write(sockfd, "%s %s\n", prog, path);
+       return 0;
+}
+
+#endif /* NO_IPV6 */
+
 /*
  * Yeah, yeah, fixme. Need to pass in the heads etc.
  */
index 9ad0c77678a740c82c91b4f99de039e12605808d..a892013f0f37480fbf13a9511f2146d1681ba136 100644 (file)
@@ -1,6 +1,5 @@
 #define _XOPEN_SOURCE /* glibc2 needs this */
 #include <time.h>
-#include <ctype.h>
 #include "cache.h"
 
 struct entry {
diff --git a/copy.c b/copy.c
new file mode 100644 (file)
index 0000000..2009275
--- /dev/null
+++ b/copy.c
@@ -0,0 +1,37 @@
+#include "cache.h"
+
+int copy_fd(int ifd, int ofd)
+{
+       while (1) {
+               int len;
+               char buffer[8192];
+               char *buf = buffer;
+               len = read(ifd, buffer, sizeof(buffer));
+               if (!len)
+                       break;
+               if (len < 0) {
+                       if (errno == EAGAIN)
+                               continue;
+                       return error("copy-fd: read returned %s",
+                                    strerror(errno));
+               }
+               while (1) {
+                       int written = write(ofd, buf, len);
+                       if (written > 0) {
+                               buf += written;
+                               len -= written;
+                               if (!len)
+                                       break;
+                       }
+                       if (!written)
+                               return error("copy-fd: write returned 0");
+                       if (errno == EAGAIN || errno == EINTR)
+                               continue;
+                       return error("copy-fd: write returned %s",
+                                    strerror(errno));
+               }
+       }
+       close(ifd);
+       return 0;
+}
+
diff --git a/ctype.c b/ctype.c
new file mode 100644 (file)
index 0000000..56bdffa
--- /dev/null
+++ b/ctype.c
@@ -0,0 +1,23 @@
+/*
+ * Sane locale-independent, ASCII ctype.
+ *
+ * No surprises, and works with signed and unsigned chars.
+ */
+#include "cache.h"
+
+#define SS GIT_SPACE
+#define AA GIT_ALPHA
+#define DD GIT_DIGIT
+
+unsigned char sane_ctype[256] = {
+        0,  0,  0,  0,  0,  0,  0,  0,  0, SS, SS,  0,  0, SS,  0,  0,         /* 0-15 */
+        0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,         /* 16-15 */
+       SS,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,         /* 32-15 */
+       DD, DD, DD, DD, DD, DD, DD, DD, DD, DD,  0,  0,  0,  0,  0,  0,         /* 48-15 */
+        0, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA,         /* 64-15 */
+       AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA,  0,  0,  0,  0,  0,         /* 80-15 */
+        0, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA,         /* 96-15 */
+       AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA,  0,  0,  0,  0,  0,         /* 112-15 */
+       /* Nothing in the 128.. range */
+};
+
index bd278b01c11ed831623e3c6db111f53c2e08d050..c3f86410d4fe392a2ced76b09e6af8ba8af534d5 100644 (file)
--- a/daemon.c
+++ b/daemon.c
@@ -1,13 +1,14 @@
-#include "cache.h"
-#include "pkt-line.h"
 #include <signal.h>
 #include <sys/wait.h>
 #include <sys/socket.h>
 #include <sys/time.h>
+#include <sys/poll.h>
 #include <netdb.h>
 #include <netinet/in.h>
 #include <arpa/inet.h>
 #include <syslog.h>
+#include "pkt-line.h"
+#include "cache.h"
 
 static int log_syslog;
 static int verbose;
@@ -144,7 +145,7 @@ static int set_dir(const char *dir)
 
        if ( chdir(dir) )
                return -1;
-       
+
        /*
         * Security on the cheap.
         *
@@ -375,6 +376,7 @@ static void handle(int incoming, struct sockaddr *addr, int addrlen)
                inet_ntop(AF_INET, &sin_addr->sin_addr, addrbuf, sizeof(addrbuf));
                port = sin_addr->sin_port;
 
+#ifndef NO_IPV6
        } else if (addr->sa_family == AF_INET6) {
                struct sockaddr_in6 *sin6_addr = (void *) addr;
 
@@ -384,6 +386,7 @@ static void handle(int incoming, struct sockaddr *addr, int addrlen)
                strcat(buf, "]");
 
                port = sin6_addr->sin6_port;
+#endif
        }
        loginfo("Connection from %s:%d", addrbuf, port);
 
@@ -416,16 +419,16 @@ static void child_handler(int signo)
        }
 }
 
-static int serve(int port)
+#ifndef NO_IPV6
+
+static int socksetup(int port, int **socklist_p)
 {
-       struct addrinfo hints, *ai0, *ai;
-       int gai;
        int socknum = 0, *socklist = NULL;
        int maxfd = -1;
-       fd_set fds_init, fds;
        char pbuf[NI_MAXSERV];
 
-       signal(SIGCHLD, child_handler);
+       struct addrinfo hints, *ai0, *ai;
+       int gai;
 
        sprintf(pbuf, "%d", port);
        memset(&hints, 0, sizeof(hints));
@@ -438,8 +441,6 @@ static int serve(int port)
        if (gai)
                die("getaddrinfo() failed: %s\n", gai_strerror(gai));
 
-       FD_ZERO(&fds_init);
-
        for (ai = ai0; ai; ai = ai->ai_next) {
                int sockfd;
                int *newlist;
@@ -478,23 +479,63 @@ static int serve(int port)
                socklist = newlist;
                socklist[socknum++] = sockfd;
 
-               FD_SET(sockfd, &fds_init);
                if (maxfd < sockfd)
                        maxfd = sockfd;
        }
 
        freeaddrinfo(ai0);
 
-       if (socknum == 0)
-               die("unable to allocate any listen sockets on port %u", port);
+       *socklist_p = socklist;
+       return socknum;
+}
+
+#else /* NO_IPV6 */
+
+static int socksetup(int port, int **socklist_p)
+{
+       struct sockaddr_in sin;
+       int sockfd;
+
+       sockfd = socket(AF_INET, SOCK_STREAM, 0);
+       if (sockfd < 0)
+               return 0;
+
+       memset(&sin, 0, sizeof sin);
+       sin.sin_family = AF_INET;
+       sin.sin_addr.s_addr = htonl(INADDR_ANY);
+       sin.sin_port = htons(port);
+
+       if ( bind(sockfd, (struct sockaddr *)&sin, sizeof sin) < 0 ) {
+               close(sockfd);
+               return 0;
+       }
+
+       *socklist_p = xmalloc(sizeof(int));
+       **socklist_p = sockfd;
+}
+
+#endif
+
+static int service_loop(int socknum, int *socklist)
+{
+       struct pollfd *pfd;
+       int i;
+
+       pfd = xcalloc(socknum, sizeof(struct pollfd));
+
+       for (i = 0; i < socknum; i++) {
+               pfd[i].fd = socklist[i];
+               pfd[i].events = POLLIN;
+       }
+
+       signal(SIGCHLD, child_handler);
 
        for (;;) {
                int i;
-               fds = fds_init;
 
-               if (select(maxfd + 1, &fds, NULL, NULL, NULL) < 0) {
+               if (poll(pfd, socknum, -1) < 0) {
                        if (errno != EINTR) {
-                               error("select failed, resuming: %s",
+                               error("poll failed, resuming: %s",
                                      strerror(errno));
                                sleep(1);
                        }
@@ -502,12 +543,10 @@ static int serve(int port)
                }
 
                for (i = 0; i < socknum; i++) {
-                       int sockfd = socklist[i];
-
-                       if (FD_ISSET(sockfd, &fds)) {
+                       if (pfd[i].revents & POLLIN) {
                                struct sockaddr_storage ss;
-                               int sslen = sizeof(ss);
-                               int incoming = accept(sockfd, (struct sockaddr *)&ss, &sslen);
+                               unsigned int sslen = sizeof(ss);
+                               int incoming = accept(pfd[i].fd, (struct sockaddr *)&ss, &sslen);
                                if (incoming < 0) {
                                        switch (errno) {
                                        case EAGAIN:
@@ -524,6 +563,17 @@ static int serve(int port)
        }
 }
 
+static int serve(int port)
+{
+       int socknum, *socklist;
+
+       socknum = socksetup(port, &socklist);
+       if (socknum == 0)
+               die("unable to allocate any listen sockets on port %u", port);
+
+       return service_loop(socknum, socklist);
+}
+
 int main(int argc, char **argv)
 {
        int port = DEFAULT_GIT_PORT;
@@ -579,7 +629,7 @@ int main(int argc, char **argv)
        if (inetd_mode) {
                fclose(stderr); //FIXME: workaround
                return execute();
+       } else {
+               return serve(port);
        }
-
-       return serve(port);
 }
diff --git a/date.c b/date.c
index b21cadc4d6811d7b333c90a072d56c077ff21ae7..63f5a0919768112c0d3301e7afaa59edcce7da36 100644 (file)
--- a/date.c
+++ b/date.c
@@ -4,7 +4,6 @@
  * Copyright (C) Linus Torvalds, 2005
  */
 
-#include <ctype.h>
 #include <time.h>
 
 #include "cache.h"
index 2f8c13389fe798d5a9bae6ad6ddefa4abc01461d..7d18483428af0bfda846d22cdf2960d9b03d5b11 100644 (file)
@@ -1,44 +1,14 @@
-git-core (0.99.8g-0) unstable; urgency=low
+git-core (0.99.9a-0) unstable; urgency=low
 
-  * GIT 0.99.8g
+  * GIT 0.99.9a
 
- -- Junio C Hamano <junkio@cox.net>  Sun, 23 Oct 2005 02:01:44 -0700
+ -- Junio C Hamano <junkio@cox.net>  Sun, 30 Oct 2005 15:03:32 -0800
 
-git-core (0.99.8f-0) unstable; urgency=low
+git-core (0.99.9-0) unstable; urgency=low
 
-  * GIT 0.99.8f
+  * GIT 0.99.9
 
- -- Junio C Hamano <junkio@cox.net>  Wed, 19 Oct 2005 02:29:24 -0700
-
-git-core (0.99.8e-0) unstable; urgency=low
-
-  * GIT 0.99.8e
-
- -- Junio C Hamano <junkio@cox.net>  Mon, 17 Oct 2005 17:45:08 -0700
-
-git-core (0.99.8d-0) unstable; urgency=low
-
-  * GIT 0.99.8d
-
- -- Junio C Hamano <junkio@cox.net>  Sat, 15 Oct 2005 17:22:58 -0700
-
-git-core (0.99.8c-0) unstable; urgency=low
-
-  * GIT 0.99.8c
-
- -- Junio C Hamano <junkio@cox.net>  Sun,  9 Oct 2005 19:19:16 -0700
-
-git-core (0.99.8b-0) unstable; urgency=low
-
-  * GIT 0.99.8b
-
- -- Junio C Hamano <junkio@cox.net>  Wed,  5 Oct 2005 15:41:24 -0700
-
-git-core (0.99.8a-0) unstable; urgency=low
-
-  * GIT 0.99.8a
-
- -- Junio C Hamano <junkio@cox.net>  Mon,  3 Oct 2005 16:27:32 -0700
+ -- Junio C Hamano <junkio@cox.net>  Sat, 29 Oct 2005 14:34:30 -0700
 
 git-core (0.99.8-0) unstable; urgency=low
 
index 5d75c325e2dad1393cf5845dc9ccc6d54743e0e1..1f45f93b365ee11d743105582d1d29eea0fb0232 100644 (file)
@@ -2,14 +2,14 @@ Source: git-core
 Section: devel
 Priority: optional
 Maintainer: Junio C Hamano <junkio@cox.net>
-Build-Depends-Indep: libz-dev, libssl-dev, libcurl3-dev, asciidoc (>= 6.0.3), xmlto, debhelper (>= 4.0.0), bc
+Build-Depends-Indep: libz-dev, libssl-dev, libcurl3-dev|libcurl3-gnutls-dev|libcurl3-openssl-dev, asciidoc (>= 6.0.3), xmlto, debhelper (>= 4.0.0), bc
 Standards-Version: 3.6.1
 
 Package: git-core
 Architecture: any
-Depends: ${shlibs:Depends}, ${perl:Depends}, ${misc:Depends}, patch, rcs
-Recommends: rsync, curl, ssh, libmail-sendmail-perl, libemail-valid-perl, python (>= 2.4.0), less
-Suggests: cogito
+Depends: ${shlibs:Depends}, ${perl:Depends}, ${misc:Depends}, rcs
+Recommends: rsync, curl, ssh, libmail-sendmail-perl, libemail-valid-perl, libsvn-core-perl (>= 1.2.1), python (>= 2.4.0), less
+Suggests: cogito, patch
 Conflicts: git, cogito (<< 0.13)
 Description: The git content addressable filesystem
  GIT comes in two layers. The bottom layer is merely an extremely fast
index 5e598322ff038159630930b6926a173424b66418..17899390b80f4bfc03718e92029b5c4de65055b6 100644 (file)
@@ -38,8 +38,14 @@ int main(int argc, const char **argv)
        const char *prefix = setup_git_directory();
        int entries, i;
 
+       git_config(git_default_config);
        diff_setup(&diff_options);
        while (1 < argc && argv[1][0] == '-') {
+               if (!strcmp(argv[1], "--")) {
+                       argv++;
+                       argc--;
+                       break;
+               }
                if (!strcmp(argv[1], "-q"))
                        silent = 1;
                else if (!strcmp(argv[1], "-r"))
@@ -80,7 +86,7 @@ int main(int argc, const char **argv)
 
        for (i = 0; i < entries; i++) {
                struct stat st;
-               unsigned int oldmode;
+               unsigned int oldmode, newmode;
                struct cache_entry *ce = active_cache[i];
                int changed;
 
@@ -110,7 +116,13 @@ int main(int argc, const char **argv)
                if (!changed && !diff_options.find_copies_harder)
                        continue;
                oldmode = ntohl(ce->ce_mode);
-               show_modified(oldmode, DIFF_FILE_CANON_MODE(st.st_mode),
+
+               newmode = DIFF_FILE_CANON_MODE(st.st_mode);
+               if (!trust_executable_bit &&
+                   S_ISREG(newmode) && S_ISREG(oldmode) &&
+                   ((newmode ^ oldmode) == 0111))
+                       newmode = oldmode;
+               show_modified(oldmode, newmode,
                              ce->sha1, (changed ? null_sha1 : ce->sha1),
                              ce->name);
        }
index 62b36cc8ee2bad8e4c37c4c7e2a99d6d4abc2f88..c9a9f4c74d96ea22b2a6c4c7593bccc3d5396457 100644 (file)
@@ -15,7 +15,7 @@ static void show_file(const char *prefix,
 }
 
 static int get_stat_data(struct cache_entry *ce,
-                        unsigned char **sha1p, unsigned int *modep)
+                        unsigned char ** sha1p, unsigned int *modep)
 {
        unsigned char *sha1 = ce->sha1;
        unsigned int mode = ce->ce_mode;
@@ -35,6 +35,10 @@ static int get_stat_data(struct cache_entry *ce,
                changed = ce_match_stat(ce, &st);
                if (changed) {
                        mode = create_ce_mode(st.st_mode);
+                       if (!trust_executable_bit &&
+                           S_ISREG(mode) && S_ISREG(ce->ce_mode) &&
+                           ((mode ^ ce->ce_mode) == 0111))
+                               mode = ce->ce_mode;
                        sha1 = no_sha1;
                }
        }
@@ -49,7 +53,9 @@ static void show_new_file(struct cache_entry *new)
        unsigned char *sha1;
        unsigned int mode;
 
-       /* New file in the index: it might actually be different in the working copy */
+       /* New file in the index: it might actually be different in
+        * the working copy.
+        */
        if (get_stat_data(new, &sha1, &mode) < 0)
                return;
 
@@ -174,6 +180,7 @@ int main(int argc, const char **argv)
        int allow_options = 1;
        int i;
 
+       git_config(git_default_config);
        diff_setup(&diff_options);
        for (i = 1; i < argc; i++) {
                const char *arg = argv[i];
index b2d74eb1d1d74c87dd2530aba4638da760913916..ed323d877cad1e78e08f84c1e9ee1ebb09f05d91 100644 (file)
@@ -1,4 +1,3 @@
-#include <ctype.h>
 #include "cache.h"
 #include "diff.h"
 #include "commit.h"
 static int show_root_diff = 0;
 static int verbose_header = 0;
 static int ignore_merges = 1;
-static int recursive = 0;
-static int show_tree_entry_in_recursive = 0;
 static int read_stdin = 0;
 
-static struct diff_options diff_options;
-
 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(&diff_options, 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 (!diff_options.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(&diff_options, mode1, mode2,
-                                   sha1, sha2, base, path1);
-               retval = diff_tree_sha1(sha1, sha2, newbase);
-               free(newbase);
-               return retval;
-       }
-
-       diff_change(&diff_options, 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 struct diff_options diff_options;
 
 static void call_diff_setup_done(void)
 {
@@ -286,7 +42,7 @@ static int diff_tree_sha1_top(const unsigned char *old,
        int ret;
 
        call_diff_setup_done();
-       ret = diff_tree_sha1(old, new, base);
+       ret = diff_tree_sha1(old, new, base, &diff_options);
        call_diff_flush();
        return ret;
 }
@@ -295,13 +51,17 @@ static int diff_root_tree(const unsigned char *new, const char *base)
 {
        int retval;
        void *tree;
-       unsigned long size;
+       struct tree_desc empty, real;
 
        call_diff_setup_done();
-       tree = read_object_with_reference(new, "tree", &size, NULL);
+       tree = read_object_with_reference(new, "tree", &real.size, NULL);
        if (!tree)
                die("unable to read root tree (%s)", sha1_to_hex(new));
-       retval = diff_tree("", 0, tree, size, base);
+       real.buf = tree;
+
+       empty.buf = "";
+       empty.size = 0;
+       retval = diff_tree(&empty, &real, base, &diff_options);
        free(tree);
        call_diff_flush();
        return retval;
@@ -388,17 +148,11 @@ static int diff_tree_stdin(char *line)
        return diff_tree_commit(commit, line);
 }
 
-static int count_paths(const char **paths)
-{
-       int i = 0;
-       while (*paths++)
-               i++;
-       return i;
-}
-
 static const char diff_tree_usage[] =
-"git-diff-tree [--stdin] [-m] [-s] [-v] [--pretty] [-t] "
-"[<common diff options>] <tree-ish> <tree-ish>"
+"git-diff-tree [--stdin] [-m] [-s] [-v] [--pretty] [-t] [-r] [--root] "
+"[<common diff options>] <tree-ish> [<tree-ish>] [<path>...]\n"
+"  -r            diff recursively\n"
+"  --root        include the initial commit as diff against /dev/null\n"
 COMMON_DIFF_OPTIONS_HELP;
 
 int main(int argc, const char **argv)
@@ -408,6 +162,7 @@ int main(int argc, const char **argv)
        unsigned char sha1[2][20];
        const char *prefix = setup_git_directory();
 
+       git_config(git_default_config);
        nr_sha1 = 0;
        diff_setup(&diff_options);
 
@@ -445,11 +200,12 @@ int main(int argc, const char **argv)
                        break;
                }
                if (!strcmp(arg, "-r")) {
-                       recursive = 1;
+                       diff_options.recursive = 1;
                        continue;
                }
                if (!strcmp(arg, "-t")) {
-                       recursive = show_tree_entry_in_recursive = 1;
+                       diff_options.recursive = 1;
+                       diff_options.tree_in_recursive = 1;
                        continue;
                }
                if (!strcmp(arg, "-m")) {
@@ -478,17 +234,9 @@ int main(int argc, const char **argv)
                usage(diff_tree_usage);
        }
        if (diff_options.output_format == DIFF_FORMAT_PATCH)
-               recursive = 1;
+               diff_options.recursive = 1;
 
-       paths = get_pathspec(prefix, argv);
-       if (paths) {
-               int i;
-
-               nr_paths = count_paths(paths);
-               pathlens = xmalloc(nr_paths * sizeof(int));
-               for (i=0; i<nr_paths; i++)
-                       pathlens[i] = strlen(paths[i]);
-       }
+       diff_tree_setup_paths(get_pathspec(prefix, argv));
 
        switch (nr_sha1) {
        case 0:
diff --git a/diff.c b/diff.c
index 7d06b035ae8b6a53f10a7f94251a24d911baa509..ec94a96a5d02fe4204cc69f03de2966264378f75 100644 (file)
--- a/diff.c
+++ b/diff.c
@@ -13,6 +13,46 @@ static const char *diff_opts = "-pu";
 
 static int use_size_cache;
 
+static char *quote_one(const char *str)
+{
+       int needlen;
+       char *xp;
+
+       if (!str)
+               return NULL;
+       needlen = quote_c_style(str, NULL, NULL, 0);
+       if (!needlen)
+               return strdup(str);
+       xp = xmalloc(needlen + 1);
+       quote_c_style(str, xp, NULL, 0);
+       return xp;
+}
+
+static char *quote_two(const char *one, const char *two)
+{
+       int need_one = quote_c_style(one, NULL, NULL, 1);
+       int need_two = quote_c_style(two, NULL, NULL, 1);
+       char *xp;
+
+       if (need_one + need_two) {
+               if (!need_one) need_one = strlen(one);
+               if (!need_two) need_one = strlen(two);
+
+               xp = xmalloc(need_one + need_two + 3);
+               xp[0] = '"';
+               quote_c_style(one, xp + 1, NULL, 1);
+               quote_c_style(two, xp + need_one + 1, NULL, 1);
+               strcpy(xp + need_one + need_two + 1, "\"");
+               return xp;
+       }
+       need_one = strlen(one);
+       need_two = strlen(two);
+       xp = xmalloc(need_one + need_two + 1);
+       strcpy(xp, one);
+       strcpy(xp + need_one, two);
+       return xp;
+}
+
 static const char *external_diff(void)
 {
        static const char *external_diff_cmd = NULL;
@@ -133,55 +173,52 @@ static void builtin_diff(const char *name_a,
                         int complete_rewrite)
 {
        int i, next_at, cmd_size;
-       const char *const diff_cmd = "diff -L%s%s -L%s%s";
-       const char *const diff_arg  = "%s %s||:"; /* "||:" is to return 0 */
+       const char *const diff_cmd = "diff -L%s -L%s";
+       const char *const diff_arg  = "-- %s %s||:"; /* "||:" is to return 0 */
        const char *input_name_sq[2];
-       const char *path0[2];
-       const char *path1[2];
-       const char *name_sq[2];
+       const char *label_path[2];
        char *cmd;
 
-       name_sq[0] = sq_quote(name_a);
-       name_sq[1] = sq_quote(name_b);
-
-       /* diff_cmd and diff_arg have 6 %s in total which makes
-        * the sum of these strings 12 bytes larger than required.
+       /* diff_cmd and diff_arg have 4 %s in total which makes
+        * the sum of these strings 8 bytes larger than required.
         * we use 2 spaces around diff-opts, and we need to count
-        * terminating NUL, so we subtract 9 here.
+        * terminating NUL; we used to subtract 5 here, but we do not
+        * care about small leaks in this subprocess that is about
+        * to exec "diff" anymore.
         */
-       cmd_size = (strlen(diff_cmd) + strlen(diff_opts) +
-                       strlen(diff_arg) - 9);
+       cmd_size = (strlen(diff_cmd) + strlen(diff_opts) + strlen(diff_arg)
+                   + 128);
+
        for (i = 0; i < 2; i++) {
                input_name_sq[i] = sq_quote(temp[i].name);
-               if (!strcmp(temp[i].name, "/dev/null")) {
-                       path0[i] = "/dev/null";
-                       path1[i] = "";
-               } else {
-                       path0[i] = i ? "b/" : "a/";
-                       path1[i] = name_sq[i];
-               }
-               cmd_size += (strlen(path0[i]) + strlen(path1[i]) +
-                            strlen(input_name_sq[i]));
+               if (!strcmp(temp[i].name, "/dev/null"))
+                       label_path[i] = "/dev/null";
+               else if (!i)
+                       label_path[i] = sq_quote(quote_two("a/", name_a));
+               else
+                       label_path[i] = sq_quote(quote_two("b/", name_b));
+               cmd_size += (strlen(label_path[i]) + strlen(input_name_sq[i]));
        }
 
        cmd = xmalloc(cmd_size);
 
        next_at = 0;
        next_at += snprintf(cmd+next_at, cmd_size-next_at,
-                           diff_cmd,
-                           path0[0], path1[0], path0[1], path1[1]);
+                           diff_cmd, label_path[0], label_path[1]);
        next_at += snprintf(cmd+next_at, cmd_size-next_at,
                            " %s ", diff_opts);
        next_at += snprintf(cmd+next_at, cmd_size-next_at,
                            diff_arg, input_name_sq[0], input_name_sq[1]);
 
-       printf("diff --git a/%s b/%s\n", name_a, name_b);
-       if (!path1[0][0]) {
+       printf("diff --git %s %s\n",
+              quote_two("a/", name_a), quote_two("b/", name_b));
+       if (label_path[0][0] == '/') {
+               /* dev/null */
                printf("new file mode %s\n", temp[1].mode);
                if (xfrm_msg && xfrm_msg[0])
                        puts(xfrm_msg);
        }
-       else if (!path1[1][0]) {
+       else if (label_path[1][0] == '/') {
                printf("deleted file mode %s\n", temp[0].mode);
                if (xfrm_msg && xfrm_msg[0])
                        puts(xfrm_msg);
@@ -596,15 +633,32 @@ static void run_external_diff(const char *pgm,
        remove_tempfile();
 }
 
+static void diff_fill_sha1_info(struct diff_filespec *one)
+{
+       if (DIFF_FILE_VALID(one)) {
+               if (!one->sha1_valid) {
+                       struct stat st;
+                       if (stat(one->path, &st) < 0)
+                               die("stat %s", one->path);
+                       if (index_path(one->sha1, one->path, &st, 0))
+                               die("cannot hash %s\n", one->path);
+               }
+       }
+       else
+               memset(one->sha1, 0, 20);
+}
+
 static void run_diff(struct diff_filepair *p)
 {
        const char *pgm = external_diff();
-       char msg_[PATH_MAX*2+200], *xfrm_msg;
+       char msg[PATH_MAX*2+300], *xfrm_msg;
        struct diff_filespec *one;
        struct diff_filespec *two;
        const char *name;
        const char *other;
+       char *name_munged, *other_munged;
        int complete_rewrite = 0;
+       int len;
 
        if (DIFF_PAIR_UNMERGED(p)) {
                /* unmerged */
@@ -615,40 +669,63 @@ static void run_diff(struct diff_filepair *p)
 
        name = p->one->path;
        other = (strcmp(name, p->two->path) ? p->two->path : NULL);
+       name_munged = quote_one(name);
+       other_munged = quote_one(other);
        one = p->one; two = p->two;
+
+       diff_fill_sha1_info(one);
+       diff_fill_sha1_info(two);
+
+       len = 0;
        switch (p->status) {
        case DIFF_STATUS_COPIED:
-               sprintf(msg_,
-                       "similarity index %d%%\n"
-                       "copy from %s\n"
-                       "copy to %s",
-                       (int)(0.5 + p->score * 100.0/MAX_SCORE),
-                       name, other);
-               xfrm_msg = msg_;
+               len += snprintf(msg + len, sizeof(msg) - len,
+                               "similarity index %d%%\n"
+                               "copy from %s\n"
+                               "copy to %s\n",
+                               (int)(0.5 + p->score * 100.0/MAX_SCORE),
+                               name_munged, other_munged);
                break;
        case DIFF_STATUS_RENAMED:
-               sprintf(msg_,
-                       "similarity index %d%%\n"
-                       "rename from %s\n"
-                       "rename to %s",
-                       (int)(0.5 + p->score * 100.0/MAX_SCORE),
-                       name, other);
-               xfrm_msg = msg_;
+               len += snprintf(msg + len, sizeof(msg) - len,
+                               "similarity index %d%%\n"
+                               "rename from %s\n"
+                               "rename to %s\n",
+                               (int)(0.5 + p->score * 100.0/MAX_SCORE),
+                               name_munged, other_munged);
                break;
        case DIFF_STATUS_MODIFIED:
                if (p->score) {
-                       sprintf(msg_,
-                               "dissimilarity index %d%%",
-                               (int)(0.5 + p->score * 100.0/MAX_SCORE));
-                       xfrm_msg = msg_;
+                       len += snprintf(msg + len, sizeof(msg) - len,
+                                       "dissimilarity index %d%%\n",
+                                       (int)(0.5 + p->score *
+                                             100.0/MAX_SCORE));
                        complete_rewrite = 1;
                        break;
                }
                /* fallthru */
        default:
-               xfrm_msg = NULL;
+               /* nothing */
+               ;
        }
 
+       if (memcmp(one->sha1, two->sha1, 20)) {
+               char one_sha1[41];
+               memcpy(one_sha1, sha1_to_hex(one->sha1), 41);
+
+               len += snprintf(msg + len, sizeof(msg) - len,
+                               "index %.7s..%.7s", one_sha1,
+                               sha1_to_hex(two->sha1));
+               if (one->mode == two->mode)
+                       len += snprintf(msg + len, sizeof(msg) - len,
+                                       " %06o", one->mode);
+               len += snprintf(msg + len, sizeof(msg) - len, "\n");
+       }
+
+       if (len)
+               msg[--len] = 0;
+       xfrm_msg = len ? msg : NULL;
+
        if (!pgm &&
            DIFF_FILE_VALID(one) && DIFF_FILE_VALID(two) &&
            (S_IFMT & one->mode) != (S_IFMT & two->mode)) {
@@ -665,6 +742,9 @@ static void run_diff(struct diff_filepair *p)
        else
                run_external_diff(pgm, name, other, one, two, xfrm_msg,
                                  complete_rewrite);
+
+       free(name_munged);
+       free(other_munged);
 }
 
 void diff_setup(struct diff_options *options)
@@ -674,6 +754,9 @@ void diff_setup(struct diff_options *options)
        options->line_termination = '\n';
        options->break_opt = -1;
        options->rename_limit = -1;
+
+       options->change = diff_change;
+       options->add_remove = diff_addremove;
 }
 
 int diff_setup_done(struct diff_options *options)
@@ -841,16 +924,13 @@ static void diff_flush_raw(struct diff_filepair *p,
 {
        int two_paths;
        char status[10];
+       const char *path_one, *path_two;
 
+       path_one = p->one->path;
+       path_two = p->two->path;
        if (line_termination) {
-               const char *const err =
-                       "path %s cannot be expressed without -z";
-               if (strchr(p->one->path, line_termination) ||
-                   strchr(p->one->path, inter_name_termination))
-                       die(err, p->one->path);
-               if (strchr(p->two->path, line_termination) ||
-                   strchr(p->two->path, inter_name_termination))
-                       die(err, p->two->path);
+               path_one = quote_one(path_one);
+               path_two = quote_one(path_two);
        }
 
        if (p->score)
@@ -878,16 +958,29 @@ static void diff_flush_raw(struct diff_filepair *p,
                       p->one->mode, p->two->mode, sha1_to_hex(p->one->sha1));
                printf("%s ", sha1_to_hex(p->two->sha1));
        }
-       printf("%s%c%s",status, inter_name_termination, p->one->path);
+       printf("%s%c%s", status, inter_name_termination, path_one);
        if (two_paths)
-               printf("%c%s", inter_name_termination, p->two->path);
+               printf("%c%s", inter_name_termination, path_two);
        putchar(line_termination);
+       if (path_one != p->one->path)
+               free((void*)path_one);
+       if (path_two != p->two->path)
+               free((void*)path_two);
 }
 
 static void diff_flush_name(struct diff_filepair *p,
+                           int inter_name_termination,
                            int line_termination)
 {
-       printf("%s%c", p->two->path, line_termination);
+       char *path = p->two->path;
+
+       if (line_termination)
+               path = quote_one(p->two->path);
+       else
+               path = p->two->path;
+       printf("%s%c", path, line_termination);
+       if (p->two->path != path)
+               free(path);
 }
 
 int diff_unmodified_pair(struct diff_filepair *p)
@@ -1074,7 +1167,9 @@ void diff_flush(struct diff_options *options)
                                       diff_output_format);
                        break;
                case DIFF_FORMAT_NAME:
-                       diff_flush_name(p, line_termination);
+                       diff_flush_name(p,
+                                       inter_name_termination,
+                                       line_termination);
                        break;
                }
                diff_free_filepair(q->queue[i]);
diff --git a/diff.h b/diff.h
index 2f4a7b463b699e65848656279287e2d8e2048c16..12590791cbefe8717891365b6d417f60c51f684f 100644 (file)
--- a/diff.h
+++ b/diff.h
@@ -8,11 +8,31 @@
        (S_ISREG(mode) ? (S_IFREG | ce_permissions(mode)) : \
        S_ISLNK(mode) ? S_IFLNK : S_IFDIR)
 
+struct tree_desc {
+       void *buf;
+       unsigned long size;
+};
+
+struct diff_options;
+
+typedef void (*change_fn_t)(struct diff_options *options,
+                unsigned old_mode, unsigned new_mode,
+                const unsigned char *old_sha1,
+                const unsigned char *new_sha1,
+                const char *base, const char *path);
+
+typedef void (*add_remove_fn_t)(struct diff_options *options,
+                   int addremove, unsigned mode,
+                   const unsigned char *sha1,
+                   const char *base, const char *path);
+
 struct diff_options {
        const char **paths;
        const char *filter;
        const char *orderfile;
        const char *pickaxe;
+       unsigned recursive:1,
+                tree_in_recursive:1;
        int break_opt;
        int detect_rename;
        int find_copies_harder;
@@ -23,8 +43,17 @@ struct diff_options {
        int reverse_diff;
        int rename_limit;
        int setup;
+
+       change_fn_t change;
+       add_remove_fn_t add_remove;
 };
 
+extern void diff_tree_setup_paths(const char **paths);
+extern int diff_tree(struct tree_desc *t1, struct tree_desc *t2,
+                    const char *base, struct diff_options *opt);
+extern int diff_tree_sha1(const unsigned char *old, const unsigned char *new,
+                         const char *base, struct diff_options *opt);
+
 extern void diff_addremove(struct diff_options *,
                           int addremove,
                           unsigned mode,
@@ -62,7 +91,6 @@ extern void diffcore_std_no_resolve(struct diff_options *);
 
 #define COMMON_DIFF_OPTIONS_HELP \
 "\ncommon diff options:\n" \
-"  -r            diff recursively (only meaningful in diff-tree)\n" \
 "  -z            output diff-raw with lines terminated with NUL.\n" \
 "  -p            output patch format.\n" \
 "  -u            synonym for -p.\n" \
diff --git a/entry.c b/entry.c
index b8426dbd0dae619b9f2023f9cf46f4d8c3df0a24..15b34eb6f9ac1db569487a3b732740f2d35831a7 100644 (file)
--- a/entry.c
+++ b/entry.c
@@ -132,7 +132,7 @@ int checkout_entry(struct cache_entry *ce, struct checkout *state)
                if (!state->force) {
                        if (!state->quiet)
                                fprintf(stderr, "git-checkout-index: %s already exists\n", path);
-                       return 0;
+                       return -1;
                }
 
                /*
diff --git a/environment.c b/environment.c
new file mode 100644 (file)
index 0000000..1dc7af5
--- /dev/null
@@ -0,0 +1,75 @@
+/*
+ * We put all the git config variables in this same object
+ * file, so that programs can link against the config parser
+ * without having to link against all the rest of git.
+ *
+ * In particular, no need to bring in libz etc unless needed,
+ * even if you might want to know where the git directory etc
+ * are.
+ */
+#include "cache.h"
+
+char git_default_email[MAX_GITNAME];
+char git_default_name[MAX_GITNAME];
+int trust_executable_bit = 1;
+
+static char *git_dir, *git_object_dir, *git_index_file, *git_refs_dir,
+       *git_graft_file;
+static void setup_git_env(void)
+{
+       git_dir = getenv(GIT_DIR_ENVIRONMENT);
+       if (!git_dir)
+               git_dir = DEFAULT_GIT_DIR_ENVIRONMENT;
+       git_object_dir = getenv(DB_ENVIRONMENT);
+       if (!git_object_dir) {
+               git_object_dir = xmalloc(strlen(git_dir) + 9);
+               sprintf(git_object_dir, "%s/objects", git_dir);
+       }
+       git_refs_dir = xmalloc(strlen(git_dir) + 6);
+       sprintf(git_refs_dir, "%s/refs", git_dir);
+       git_index_file = getenv(INDEX_ENVIRONMENT);
+       if (!git_index_file) {
+               git_index_file = xmalloc(strlen(git_dir) + 7);
+               sprintf(git_index_file, "%s/index", git_dir);
+       }
+       git_graft_file = getenv(GRAFT_ENVIRONMENT);
+       if (!git_graft_file)
+               git_graft_file = strdup(git_path("info/grafts"));
+}
+
+char *get_git_dir(void)
+{
+       if (!git_dir)
+               setup_git_env();
+       return git_dir;
+}
+
+char *get_object_directory(void)
+{
+       if (!git_object_dir)
+               setup_git_env();
+       return git_object_dir;
+}
+
+char *get_refs_directory(void)
+{
+       if (!git_refs_dir)
+               setup_git_env();
+       return git_refs_dir;
+}
+
+char *get_index_file(void)
+{
+       if (!git_index_file)
+               setup_git_env();
+       return git_index_file;
+}
+
+char *get_graft_file(void)
+{
+       if (!git_graft_file)
+               setup_git_env();
+       return git_graft_file;
+}
+
+
index 969e72a7816701ed3f86ab42db1472346cab991e..3df991100bdd51f4527703d1ce1d016b611f0d5a 100644 (file)
@@ -12,41 +12,168 @@ static const char fetch_pack_usage[] =
 "git-fetch-pack [-q] [-v] [--exec=upload-pack] [host:]directory <refs>...";
 static const char *exec = "git-upload-pack";
 
+#define COMPLETE       (1U << 0)
+#define COMMON         (1U << 1)
+#define COMMON_REF     (1U << 2)
+#define SEEN           (1U << 3)
+#define POPPED         (1U << 4)
+
+static struct commit_list *rev_list = NULL;
+static int non_common_revs = 0, multi_ack = 0;
+
+static void rev_list_push(struct commit *commit, int mark)
+{
+       if (!(commit->object.flags & mark)) {
+               commit->object.flags |= mark;
+
+               if (!(commit->object.parsed))
+                       parse_commit(commit);
+
+               insert_by_date(commit, &rev_list);
+
+               if (!(commit->object.flags & COMMON))
+                       non_common_revs++;
+       }
+}
+
+static int rev_list_insert_ref(const char *path, const unsigned char *sha1)
+{
+       struct object *o = deref_tag(parse_object(sha1));
+
+       if (o->type == commit_type)
+               rev_list_push((struct commit *)o, SEEN);
+
+       return 0;
+}
+
+/*
+   This function marks a rev and its ancestors as common.
+   In some cases, it is desirable to mark only the ancestors (for example
+   when only the server does not yet know that they are common).
+*/
+
+static void mark_common(struct commit *commit,
+               int ancestors_only, int dont_parse)
+{
+       if (commit != NULL && !(commit->object.flags & COMMON)) {
+               struct object *o = (struct object *)commit;
+
+               if (!ancestors_only)
+                       o->flags |= COMMON;
+
+               if (!(o->flags & SEEN))
+                       rev_list_push(commit, SEEN);
+               else {
+                       struct commit_list *parents;
+
+                       if (!ancestors_only && !(o->flags & POPPED))
+                               non_common_revs--;
+                       if (!o->parsed && !dont_parse)
+                               parse_commit(commit);
+
+                       for (parents = commit->parents;
+                                       parents;
+                                       parents = parents->next)
+                               mark_common(parents->item, 0, dont_parse);
+               }
+       }
+}
+
+/*
+  Get the next rev to send, ignoring the common.
+*/
+
+static const unsigned char* get_rev()
+{
+       struct commit *commit = NULL;
+
+       while (commit == NULL) {
+               unsigned int mark;
+               struct commit_list* parents;
+
+               if (rev_list == NULL || non_common_revs == 0)
+                       return NULL;
+
+               commit = rev_list->item;
+               if (!(commit->object.parsed))
+                       parse_commit(commit);
+               commit->object.flags |= POPPED;
+               if (!(commit->object.flags & COMMON))
+                       non_common_revs--;
+       
+               parents = commit->parents;
+
+               if (commit->object.flags & COMMON) {
+                       /* do not send "have", and ignore ancestors */
+                       commit = NULL;
+                       mark = COMMON | SEEN;
+               } else if (commit->object.flags & COMMON_REF)
+                       /* send "have", and ignore ancestors */
+                       mark = COMMON | SEEN;
+               else
+                       /* send "have", also for its ancestors */
+                       mark = SEEN;
+
+               while (parents) {
+                       if (!(parents->item->object.flags & SEEN))
+                               rev_list_push(parents->item, mark);
+                       if (mark & COMMON)
+                               mark_common(parents->item, 1, 0);
+                       parents = parents->next;
+               }
+
+               rev_list = rev_list->next;
+       }
+
+       return commit->object.sha1;
+}
+
 static int find_common(int fd[2], unsigned char *result_sha1,
                       struct ref *refs)
 {
        int fetching;
-       static char line[1000];
        int count = 0, flushes = 0, retval;
-       FILE *revs;
+       const unsigned char *sha1;
 
-       revs = popen("git-rev-list $(git-rev-parse --all)", "r");
-       if (!revs)
-               die("unable to run 'git-rev-list'");
+       for_each_ref(rev_list_insert_ref);
 
        fetching = 0;
        for ( ; refs ; refs = refs->next) {
                unsigned char *remote = refs->old_sha1;
-               unsigned char *local = refs->new_sha1;
+               struct object *o;
 
-               if (!memcmp(remote, local, 20))
+               /*
+                * If that object is complete (i.e. it is an ancestor of a
+                * local ref), we tell them we have it but do not have to
+                * tell them about its ancestors, which they already know
+                * about.
+                *
+                * We use lookup_object here because we are only
+                * interested in the case we *know* the object is
+                * reachable and we have already scanned it.
+                */
+               if (((o = lookup_object(remote)) != NULL) &&
+                               (o->flags & COMPLETE)) {
                        continue;
-               packet_write(fd[1], "want %s\n", sha1_to_hex(remote));
+               }
+
+               packet_write(fd[1], "want %s%s\n", sha1_to_hex(remote),
+                       multi_ack ? " multi_ack" : "");
                fetching++;
        }
        packet_flush(fd[1]);
        if (!fetching)
                return 1;
-       flushes = 1;
+
+       flushes = 0;
        retval = -1;
-       while (fgets(line, sizeof(line), revs) != NULL) {
-               unsigned char sha1[20];
-               if (get_sha1_hex(line, sha1))
-                       die("git-fetch-pack: expected object name, got crud");
+       while ((sha1 = get_rev())) {
                packet_write(fd[1], "have %s\n", sha1_to_hex(sha1));
                if (verbose)
                        fprintf(stderr, "have %s\n", sha1_to_hex(sha1));
                if (!(31 & ++count)) {
+                       int ack;
+
                        packet_flush(fd[1]);
                        flushes++;
 
@@ -56,32 +183,51 @@ static int find_common(int fd[2], unsigned char *result_sha1,
                         */
                        if (count == 32)
                                continue;
-                       if (get_ack(fd[0], result_sha1)) {
-                               flushes = 0;
-                               retval = 0;
-                               if (verbose)
-                                       fprintf(stderr, "got ack\n");
-                               break;
-                       }
+
+                       do {
+                               ack = get_ack(fd[0], result_sha1);
+                               if (verbose && ack)
+                                       fprintf(stderr, "got ack %d %s\n", ack,
+                                                       sha1_to_hex(result_sha1));
+                               if (ack == 1) {
+                                       flushes = 0;
+                                       multi_ack = 0;
+                                       retval = 0;
+                                       goto done;
+                               } else if (ack == 2) {
+                                       struct commit *commit =
+                                               lookup_commit(result_sha1);
+                                       mark_common(commit, 0, 1);
+                                       retval = 0;
+                               }
+                       } while (ack);
                        flushes--;
                }
        }
-       pclose(revs);
+done:
        packet_write(fd[1], "done\n");
        if (verbose)
                fprintf(stderr, "done\n");
-       while (flushes) {
-               flushes--;
-               if (get_ack(fd[0], result_sha1)) {
+       if (retval != 0) {
+               multi_ack = 0;
+               flushes++;
+       }
+       while (flushes || multi_ack) {
+               int ack = get_ack(fd[0], result_sha1);
+               if (ack) {
                        if (verbose)
-                               fprintf(stderr, "got ack\n");
-                       return 0;
+                               fprintf(stderr, "got ack (%d) %s\n", ack,
+                                       sha1_to_hex(result_sha1));
+                       if (ack == 1)
+                               return 0;
+                       multi_ack = 1;
+                       continue;
                }
+               flushes--;
        }
        return retval;
 }
 
-#define COMPLETE       (1U << 0)
 static struct commit_list *complete = NULL;
 
 static int mark_complete(const char *path, const unsigned char *sha1)
@@ -89,10 +235,13 @@ static int mark_complete(const char *path, const unsigned char *sha1)
        struct object *o = parse_object(sha1);
 
        while (o && o->type == tag_type) {
+               struct tag *t = (struct tag *) o;
+               if (!t->tagged)
+                       break; /* broken repository */
                o->flags |= COMPLETE;
-               o = parse_object(((struct tag *)o)->tagged->sha1);
+               o = parse_object(t->tagged->sha1);
        }
-       if (o->type == commit_type) {
+       if (o && o->type == commit_type) {
                struct commit *commit = (struct commit *)o;
                commit->object.flags |= COMPLETE;
                insert_by_date(commit, &complete);
@@ -110,7 +259,29 @@ static void mark_recent_complete_commits(unsigned long cutoff)
        }
 }
 
-static int everything_local(struct ref *refs)
+static void filter_refs(struct ref **refs, int nr_match, char **match)
+{
+       struct ref *prev, *current, *next;
+
+       if (!nr_match)
+               return;
+
+       for (prev = NULL, current = *refs; current; current = next) {
+               next = current->next;
+               if ((!memcmp(current->name, "refs/", 5) &&
+                                       check_ref_format(current->name + 5)) ||
+                               !path_match(current->name, nr_match, match)) {
+                       if (prev == NULL)
+                               *refs = next;
+                       else
+                               prev->next = next;
+                       free(current);
+               } else
+                       prev = current;
+       }
+}
+
+static int everything_local(struct ref **refs, int nr_match, char **match)
 {
        struct ref *ref;
        int retval;
@@ -119,7 +290,7 @@ static int everything_local(struct ref *refs)
        track_object_refs = 0;
        save_commit_buffer = 0;
 
-       for (ref = refs; ref; ref = ref->next) {
+       for (ref = *refs; ref; ref = ref->next) {
                struct object *o;
 
                o = parse_object(ref->old_sha1);
@@ -141,28 +312,47 @@ static int everything_local(struct ref *refs)
        if (cutoff)
                mark_recent_complete_commits(cutoff);
 
-       for (retval = 1; refs ; refs = refs->next) {
-               const unsigned char *remote = refs->old_sha1;
+       /*
+        * Mark all complete remote refs as common refs.
+        * Don't mark them common yet; the server has to be told so first.
+        */
+       for (ref = *refs; ref; ref = ref->next) {
+               struct object *o = deref_tag(lookup_object(ref->old_sha1));
+
+               if (!o || o->type != commit_type || !(o->flags & COMPLETE))
+                       continue;
+
+               if (!(o->flags & SEEN)) {
+                       rev_list_push((struct commit *)o, COMMON_REF | SEEN);
+
+                       mark_common((struct commit *)o, 1, 1);
+               }
+       }
+
+       filter_refs(refs, nr_match, match);
+
+       for (retval = 1, ref = *refs; ref ; ref = ref->next) {
+               const unsigned char *remote = ref->old_sha1;
                unsigned char local[20];
                struct object *o;
 
-               o = parse_object(remote);
+               o = lookup_object(remote);
                if (!o || !(o->flags & COMPLETE)) {
                        retval = 0;
                        if (!verbose)
                                continue;
                        fprintf(stderr,
                                "want %s (%s)\n", sha1_to_hex(remote),
-                               refs->name);
+                               ref->name);
                        continue;
                }
 
-               memcpy(refs->new_sha1, local, 20);
+               memcpy(ref->new_sha1, local, 20);
                if (!verbose)
                        continue;
                fprintf(stderr,
                        "already have %s (%s)\n", sha1_to_hex(remote),
-                       refs->name);
+                       ref->name);
        }
        return retval;
 }
@@ -174,12 +364,17 @@ static int fetch_pack(int fd[2], int nr_match, char **match)
        int status;
        pid_t pid;
 
-       get_remote_heads(fd[0], &ref, nr_match, match, 1);
+       get_remote_heads(fd[0], &ref, 0, NULL, 0);
+       if (server_supports("multi_ack")) {
+               if (verbose)
+                       fprintf(stderr, "Server supports multi_ack\n");
+               multi_ack = 1;
+       }
        if (!ref) {
                packet_flush(fd[1]);
                die("no matching remote head");
        }
-       if (everything_local(ref)) {
+       if (everything_local(&ref, nr_match, match)) {
                packet_flush(fd[1]);
                goto all_done;
        }
diff --git a/fetch.c b/fetch.c
index 3e073d3584c25e35da0163689508599659328605..73bde07aeaeea67e44aef7aec1b79d5806e80a1b 100644 (file)
--- a/fetch.c
+++ b/fetch.c
@@ -165,7 +165,7 @@ static int loop(void)
                 * the queue because we needed to fetch it first.
                 */
                if (! (obj->flags & TO_SCAN)) {
-                       if (!has_sha1_file(obj->sha1) && fetch(obj->sha1)) {
+                       if (fetch(obj->sha1)) {
                                report_missing(obj->type
                                               ? obj->type
                                               : "object", obj->sha1);
index 65cec7d12b6fecc95e842069d2afa1e87bceebc1..17d05363e08b48275cb38631e82f0f48321fa728 100644 (file)
@@ -329,9 +329,8 @@ static int fsck_dir(int i, char *path)
        DIR *dir = opendir(path);
        struct dirent *de;
 
-       if (!dir) {
-               return error("missing sha1 directory '%s'", path);
-       }
+       if (!dir)
+               return 0;
 
        while ((de = readdir(dir)) != NULL) {
                char name[100];
index 3d364db2517b6a19de6b8e26f6d61a86589c1fcc..b5fe46aa20865d6785390fd22406d32b00a77845 100755 (executable)
@@ -1,15 +1,21 @@
 #!/bin/sh
 
+usage() {
+    die "usage: git add [-n] [-v] <file>..."
+}
+
 show_only=
 verbose=
 while : ; do
   case "$1" in
     -n)
        show_only=true
-       verbose=true
        ;;
     -v)
-       verbose=true
+       verbose=--verbose
+       ;;
+    -*)
+       usage
        ;;
     *)
        break
@@ -19,14 +25,19 @@ while : ; do
 done
 
 GIT_DIR=$(git-rev-parse --git-dir) || exit
-global_exclude=
-if [ -f "$GIT_DIR/info/exclude" ]; then
-   global_exclude="--exclude-from=$GIT_DIR/info/exclude"
-fi
-for i in $(git-ls-files --others \
-       $global_exclude --exclude-per-directory=.gitignore \
-       "$@")
-do
-   [ "$verbose" ] && echo "  $i"
-   [ "$show_only" ] || git-update-index --add -- "$i" || exit
-done
+
+if test -f "$GIT_DIR/info/exclude"
+then
+       git-ls-files -z \
+       --exclude-from="$GIT_DIR/info/exclude" \
+       --others --exclude-per-directory=.gitignore -- "$@"
+else
+       git-ls-files -z \
+       --others --exclude-per-directory=.gitignore -- "$@"
+fi |
+case "$show_only" in
+true)
+       xargs -0 echo ;;
+*)
+       git-update-index --add $verbose -z --stdin ;;
+esac
diff --git a/git-am.sh b/git-am.sh
new file mode 100755 (executable)
index 0000000..115ebad
--- /dev/null
+++ b/git-am.sh
@@ -0,0 +1,351 @@
+#!/bin/sh
+#
+#
+. git-sh-setup || die "Not a git archive"
+
+files=$(git-diff-index --cached --name-only HEAD) || exit
+if [ "$files" ]; then
+   echo "Dirty index: cannot apply patches (dirty: $files)" >&2
+   exit 1
+fi
+
+usage () {
+    echo >&2 "usage: $0 [--signoff] [--dotest=<dir>] [--utf8] [--3way] <mbox>"
+    echo >&2 " or, when resuming"
+    echo >&2 " $0 [--skip]"
+    exit 1;
+}
+
+stop_here () {
+    echo "$1" >"$dotest/next"
+    exit 1
+}
+
+go_next () {
+       rm -f "$dotest/$msgnum" "$dotest/msg" "$dotest/msg-clean" \
+               "$dotest/patch" "$dotest/info"
+       echo "$next" >"$dotest/next"
+       this=$next
+}
+
+fall_back_3way () {
+    O_OBJECT=`cd "$GIT_OBJECT_DIRECTORY" && pwd`
+
+    rm -fr "$dotest"/patch-merge-*
+    mkdir "$dotest/patch-merge-tmp-dir"
+
+    # First see if the patch records the index info that we can use.
+    if git-apply -z --index-info "$dotest/patch" \
+       >"$dotest/patch-merge-index-info" 2>/dev/null &&
+       GIT_INDEX_FILE="$dotest/patch-merge-tmp-index" \
+       git-update-index -z --index-info <"$dotest/patch-merge-index-info" &&
+       GIT_INDEX_FILE="$dotest/patch-merge-tmp-index" \
+       git-write-tree >"$dotest/patch-merge-base+" &&
+       # index has the base tree now.
+       (
+           cd "$dotest/patch-merge-tmp-dir" &&
+           GIT_INDEX_FILE="../patch-merge-tmp-index" \
+           GIT_OBJECT_DIRECTORY="$O_OBJECT" \
+           git-apply --index <../patch
+        )
+    then
+       echo Using index info to reconstruct a base tree...
+       mv "$dotest/patch-merge-base+" "$dotest/patch-merge-base"
+       mv "$dotest/patch-merge-tmp-index" "$dotest/patch-merge-index"
+    else
+       # Otherwise, try nearby trees that can be used to apply the
+       # patch.
+       (
+           N=10
+
+           # Hoping the patch is against our recent commits...
+           git-rev-list --max-count=$N HEAD
+
+           # or hoping the patch is against known tags...
+           git-ls-remote --tags .
+       ) |
+       while read base junk
+       do
+           # See if we have it as a tree...
+           git-cat-file tree "$base" >/dev/null 2>&1 || continue
+
+           rm -fr "$dotest"/patch-merge-* &&
+           mkdir "$dotest/patch-merge-tmp-dir" || break
+           (
+               cd "$dotest/patch-merge-tmp-dir" &&
+               GIT_INDEX_FILE=../patch-merge-tmp-index &&
+               GIT_OBJECT_DIRECTORY="$O_OBJECT" &&
+               export GIT_INDEX_FILE GIT_OBJECT_DIRECTORY &&
+               git-read-tree "$base" &&
+               git-apply --index &&
+               mv ../patch-merge-tmp-index ../patch-merge-index &&
+               echo "$base" >../patch-merge-base
+           ) <"$dotest/patch"  2>/dev/null && break
+       done
+    fi
+
+    test -f "$dotest/patch-merge-index" &&
+    his_tree=$(GIT_INDEX_FILE="$dotest/patch-merge-index" git-write-tree) &&
+    orig_tree=$(cat "$dotest/patch-merge-base") &&
+    rm -fr "$dotest"/patch-merge-* || exit 1
+
+    echo Falling back to patching base and 3-way merge...
+
+    # This is not so wrong.  Depending on which base we picked,
+    # orig_tree may be wildly different from ours, but his_tree
+    # has the same set of wildly different changes in parts the
+    # patch did not touch, so resolve ends up cancelling them,
+    # saying that we reverted all those changes.
+
+    git-merge-resolve $orig_tree -- HEAD $his_tree || {
+           echo Failed to merge in the changes.
+           exit 1
+    }
+}
+
+prec=4
+dotest=.dotest sign= utf8= keep= skip= interactive=
+
+while case "$#" in 0) break;; esac
+do
+       case "$1" in
+       -d=*|--d=*|--do=*|--dot=*|--dote=*|--dotes=*|--dotest=*)
+       dotest=`expr "$1" : '-[^=]*=\(.*\)'`; shift ;;
+       -d|--d|--do|--dot|--dote|--dotes|--dotest)
+       case "$#" in 1) usage ;; esac; shift
+       dotest="$1"; shift;;
+
+       -i|--i|--in|--int|--inte|--inter|--intera|--interac|--interact|\
+       --interacti|--interactiv|--interactive)
+       interactive=t; shift ;;
+
+       -3|--3|--3w|--3wa|--3way)
+       threeway=t; shift ;;
+       -s|--s|--si|--sig|--sign|--signo|--signof|--signoff)
+       sign=t; shift ;;
+       -u|--u|--ut|--utf|--utf8)
+       utf8=t; shift ;;
+       -k|--k|--ke|--kee|--keep)
+       keep=t; shift ;;
+
+       --sk|--ski|--skip)
+       skip=t; shift ;;
+
+       --)
+       shift; break ;;
+       -*)
+       usage ;;
+       *)
+       break ;;
+       esac
+done
+
+if test -d "$dotest" &&
+   last=$(cat "$dotest/last") &&
+   next=$(cat "$dotest/next") &&
+   test $# != 0 &&
+   test "$next" -gt "$last"
+then
+   rm -fr "$dotest"
+fi
+
+if test -d "$dotest"
+then
+       test ",$#," = ",0," ||
+       die "previous dotest directory $dotest still exists but mbox given."
+       resume=yes
+else
+       # Make sure we are not given --skip
+       test ",$skip," = ,, ||
+       die "we are not resuming."
+
+       # Start afresh.
+       mkdir -p "$dotest" || exit
+
+       # cat does the right thing for us, including '-' to mean
+       # standard input.
+       cat "$@" |
+       git-mailsplit -d$prec "$dotest/" >"$dotest/last" || {
+               rm -fr "$dotest"
+               exit 1
+       }
+
+       echo "$sign" >"$dotest/sign"
+       echo "$utf8" >"$dotest/utf8"
+       echo "$keep" >"$dotest/keep"
+       echo 1 >"$dotest/next"
+fi
+
+if test "$(cat "$dotest/utf8")" = t
+then
+       utf8=-u
+fi
+if test "$(cat "$dotest/keep")" = t
+then
+       keep=-k
+fi
+if test "$(cat "$dotest/sign")" = t
+then
+       SIGNOFF=`git-var GIT_COMMITTER_IDENT | sed -e '
+                       s/>.*/>/
+                       s/^/Signed-off-by: /'
+               `
+else
+       SIGNOFF=
+fi
+
+last=`cat "$dotest/last"`
+this=`cat "$dotest/next"`
+if test "$skip" = t
+then
+       this=`expr "$this" + 1`
+fi
+
+if test "$this" -gt "$last"
+then
+       echo Nothing to do.
+       rm -fr "$dotest"
+       exit
+fi
+
+while test "$this" -le "$last"
+do
+       msgnum=`printf "%0${prec}d" $this`
+       next=`expr "$this" + 1`
+       test -f "$dotest/$msgnum" || {
+               go_next
+               continue
+       }
+       case "$resume" in
+       '')
+               git-mailinfo $keep $utf8 "$dotest/msg" "$dotest/patch" \
+                       <"$dotest/$msgnum" >"$dotest/info" ||
+                       stop_here $this
+               git-stripspace < "$dotest/msg" > "$dotest/msg-clean"
+               ;;
+       esac
+       resume=
+
+       GIT_AUTHOR_NAME="$(sed -n '/^Author/ s/Author: //p' "$dotest/info")"
+       GIT_AUTHOR_EMAIL="$(sed -n '/^Email/ s/Email: //p' "$dotest/info")"
+       GIT_AUTHOR_DATE="$(sed -n '/^Date/ s/Date: //p' "$dotest/info")"
+       SUBJECT="$(sed -n '/^Subject/ s/Subject: //p' "$dotest/info")"
+       export GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL GIT_AUTHOR_DATE
+
+       case "$keep_subject" in -k)  SUBJECT="[PATCH] $SUBJECT" ;; esac
+       if test '' != "$SIGNOFF"
+       then
+               LAST_SIGNED_OFF_BY=`
+                       sed -ne '/^Signed-off-by: /p' "$dotest/msg-clean" |
+                       tail -n 1
+               `
+               ADD_SIGNOFF=$(test "$LAST_SIGNED_OFF_BY" = "$SIGNOFF" || {
+                   test '' = "$LAST_SIGNED_OFF_BY" && echo
+                   echo "$SIGNOFF"
+               })
+       else
+               ADD_SIGNOFF=
+       fi
+       {
+               echo "$SUBJECT"
+               if test -s "$dotest/msg-clean"
+               then
+                       echo
+                       cat "$dotest/msg-clean"
+               fi
+               if test '' != "$ADD_SIGNOFF"
+               then
+                       echo "$ADD_SIGNOFF"
+               fi
+       } >"$dotest/final-commit"
+
+       if test "$interactive" = t
+       then
+           test -t 0 ||
+           die "cannot be interactive without stdin connected to a terminal."
+           action=again
+           while test "$action" = again
+           do
+               echo "Commit Body is:"
+               echo "--------------------------"
+               cat "$dotest/final-commit"
+               echo "--------------------------"
+               echo -n "Apply? [y]es/[n]o/[e]dit/[v]iew patch/[a]ccept all "
+               read reply
+               case "$reply" in
+               [yY]*) action=yes ;;
+               [aA]*) action=yes interactive= ;;
+               [nN]*) action=skip ;;
+               [eE]*) "${VISUAL:-${EDITOR:-vi}}" "$dotest/final-commit"
+                      action=again ;;
+               [vV]*) action=again
+                      LESS=-S ${PAGER:-less} "$dotest/patch" ;;
+               *)     action=again ;;
+               esac
+           done
+       else
+           action=yes
+       fi
+
+       if test $action = skip
+       then
+               go_next
+               continue
+       fi
+
+       if test -x "$GIT_DIR"/hooks/applypatch-msg
+       then
+               "$GIT_DIR"/hooks/applypatch-msg "$dotest/final-commit" ||
+               stop_here $this
+       fi
+
+       echo
+       echo "Applying '$SUBJECT'"
+       echo
+
+       git-apply --index "$dotest/patch"; apply_status=$?
+       if test $apply_status = 1 && test "$threeway" = t
+       then
+               if (fall_back_3way)
+               then
+                   # Applying the patch to an earlier tree and merging the
+                   # result may have produced the same tree as ours.
+                   changed="$(git-diff-index --cached --name-only -z HEAD)"
+                   if test '' = "$changed"
+                   then
+                           echo No changes -- Patch already applied.
+                           go_next
+                           continue
+                   fi
+                   # clear apply_status -- we have successfully merged.
+                   apply_status=0
+               fi
+       fi
+       if test $apply_status != 0
+       then
+               echo Patch failed at $msgnum.
+               stop_here $this
+       fi
+
+       if test -x "$GIT_DIR"/hooks/pre-applypatch
+       then
+               "$GIT_DIR"/hooks/pre-applypatch || stop_here $this
+       fi
+
+       tree=$(git-write-tree) &&
+       echo Wrote tree $tree &&
+       parent=$(git-rev-parse --verify HEAD) &&
+       commit=$(git-commit-tree $tree -p $parent <"$dotest/final-commit") &&
+       echo Committed: $commit &&
+       git-update-ref HEAD $commit $parent ||
+       stop_here $this
+
+       if test -x "$GIT_DIR"/hooks/post-applypatch
+       then
+               "$GIT_DIR"/hooks/post-applypatch
+       fi
+
+       go_next
+done
+
+rm -fr "$dotest"
index e2bfd0287057b5eb712018574bef1790993ca743..6de6932879e9f1298f69e2826f13e1ee231ecea0 100755 (executable)
@@ -9,19 +9,19 @@
 ## You give it a mbox-format collection of emails, and it will try to
 ## apply them to the kernel using "applypatch"
 ##
-## applymbox [-u] [-k] [-q] (-c .dotest/msg-number | mail_archive) [Signoff_file]"
-##
 ## The patch application may fail in the middle.  In which case:
 ## (1) look at .dotest/patch and fix it up to apply
 ## (2) re-run applymbox with -c .dotest/msg-number for the current one.
 ## Pay a special attention to the commit log message if you do this and
 ## use a Signoff_file, because applypatch wants to append the sign-off
 ## message to msg-clean every time it is run.
+##
+## git-am is supposed to be the newer and better tool for this job.
 
 . git-sh-setup || die "Not a git archive"
 
 usage () {
-    echo >&2 "applymbox [-u] [-k] [-q] (-c .dotest/<num> | mbox) [signoff]"
+    echo >&2 "applymbox [-u] [-k] [-q] [-m] (-c .dotest/<num> | mbox) [signoff]"
     exit 1
 }
 
@@ -33,6 +33,7 @@ do
        -k)     keep_subject=-k ;;
        -q)     query_apply=t ;;
        -c)     continue="$2"; resume=f; shift ;;
+       -m)     fallback_3way=t ;;
        -*)     usage ;;
        *)      break ;;
        esac
@@ -43,7 +44,8 @@ case "$continue" in
 '')
        rm -rf .dotest
        mkdir .dotest
-       git-mailsplit "$1" .dotest || exit 1
+       num_msgs=$(git-mailsplit "$1" .dotest) || exit 1
+       echo "$num_msgs patch(es) to process."
        shift
 esac
 
@@ -56,6 +58,9 @@ fi
 case "$query_apply" in
 t)     touch .dotest/.query_apply
 esac
+case "$fall_back_3way" in
+t)     : >.dotest/.3way
+esac
 case "$keep_subject" in
 -k)    : >.dotest/.keep_subject
 esac
@@ -80,7 +85,11 @@ do
     do
        git-applypatch .dotest/msg-clean .dotest/patch .dotest/info "$signoff"
        case "$?" in
-       0 | 2 )
+       0)
+               # Remove the cleanly applied one to reduce clutter.
+               rm -f .dotest/$i
+               ;;
+       2)
                # 2 is a special exit code from applypatch to indicate that
                # the patch wasn't applied, but continue anyway 
                ;;
index 9f5a45bb2bbb330207ea4410a76f5521bdc7b850..66fd19ae2df2e1f44b709a20802342c88d9d2cf2 100755 (executable)
@@ -22,6 +22,8 @@ query_apply=.dotest/.query_apply
 ## if this file exists.
 keep_subject=.dotest/.keep_subject
 
+## We do not attempt the 3-way merge fallback unless this file exists.
+fall_back_3way=.dotest/.3way
 
 MSGFILE=$1
 PATCHFILE=$2
@@ -29,10 +31,10 @@ INFO=$3
 SIGNOFF=$4
 EDIT=${VISUAL:-${EDITOR:-vi}}
 
-export GIT_AUTHOR_NAME="$(sed -n '/^Author/ s/Author: //p' .dotest/info)"
-export GIT_AUTHOR_EMAIL="$(sed -n '/^Email/ s/Email: //p' .dotest/info)"
-export GIT_AUTHOR_DATE="$(sed -n '/^Date/ s/Date: //p' .dotest/info)"
-export SUBJECT="$(sed -n '/^Subject/ s/Subject: //p' .dotest/info)"
+export GIT_AUTHOR_NAME="$(sed -n '/^Author/ s/Author: //p' "$INFO")"
+export GIT_AUTHOR_EMAIL="$(sed -n '/^Email/ s/Email: //p' "$INFO")"
+export GIT_AUTHOR_DATE="$(sed -n '/^Date/ s/Date: //p' "$INFO")"
+export SUBJECT="$(sed -n '/^Subject/ s/Subject: //p' "$INFO")"
 
 if test '' != "$SIGNOFF"
 then
@@ -54,8 +56,10 @@ then
                        sed -ne '/^Signed-off-by: /p' "$MSGFILE" |
                        tail -n 1
                `
-               test "$LAST_SIGNED_OFF_BY" = "$SIGNOFF" ||
-               echo "$SIGNOFF" >>"$MSGFILE"
+               test "$LAST_SIGNED_OFF_BY" = "$SIGNOFF" || {
+                   test '' = "$LAST_SIGNED_OFF_BY" && echo
+                   echo "$SIGNOFF"
+               } >>"$MSGFILE"
        fi
 fi
 
@@ -99,7 +103,81 @@ echo
 echo Applying "'$SUBJECT'"
 echo
 
-git-apply --index "$PATCHFILE" || exit 1
+git-apply --index "$PATCHFILE" || {
+
+       # git-apply exits with status 1 when the patch does not apply,
+       # but it die()s with other failures, most notably upon corrupt
+       # patch.  In the latter case, there is no point to try applying
+       # it to another tree and do 3-way merge.
+       test $? = 1 || exit 1
+
+       test -f "$fall_back_3way" || exit 1
+
+       # Here if we know which revision the patch applies to,
+       # we create a temporary working tree and index, apply the
+       # patch, and attempt 3-way merge with the resulting tree.
+
+       O_OBJECT=`cd "$GIT_OBJECT_DIRECTORY" && pwd`
+       rm -fr .patch-merge-*
+
+       (
+               N=10
+
+               # if the patch records the base tree...
+               sed -ne '
+                       /^diff /q
+                       /^applies-to: \([0-9a-f]*\)$/{
+                               s//\1/p
+                               q
+                       }
+               ' "$PATCHFILE"
+
+               # or hoping the patch is against our recent commits...
+               git-rev-list --max-count=$N HEAD
+
+               # or hoping the patch is against known tags...
+               git-ls-remote --tags .
+       ) |
+       while read base junk
+       do
+               # Try it if we have it as a tree.
+               git-cat-file tree "$base" >/dev/null 2>&1 || continue
+
+               rm -fr .patch-merge-tmp-* &&
+               mkdir .patch-merge-tmp-dir || break
+               (
+                       cd .patch-merge-tmp-dir &&
+                       GIT_INDEX_FILE=../.patch-merge-tmp-index &&
+                       GIT_OBJECT_DIRECTORY="$O_OBJECT" &&
+                       export GIT_INDEX_FILE GIT_OBJECT_DIRECTORY &&
+                       git-read-tree "$base" &&
+                       git-apply --index &&
+                       mv ../.patch-merge-tmp-index ../.patch-merge-index &&
+                       echo "$base" >../.patch-merge-base
+               ) <"$PATCHFILE"  2>/dev/null && break
+       done
+
+       test -f .patch-merge-index &&
+       his_tree=$(GIT_INDEX_FILE=.patch-merge-index git-write-tree) &&
+       orig_tree=$(cat .patch-merge-base) &&
+       rm -fr .patch-merge-* || exit 1
+
+       echo Falling back to patching base and 3-way merge using $orig_tree...
+
+       # This is not so wrong.  Depending on which base we picked,
+       # orig_tree may be wildly different from ours, but his_tree
+       # has the same set of wildly different changes in parts the
+       # patch did not touch, so resolve ends up cancelling them,
+       # saying that we reverted all those changes.
+
+       if git-merge-resolve $orig_tree -- HEAD $his_tree
+       then
+               echo Done.
+       else
+               echo Failed to merge in the changes.
+               exit 1
+       fi
+}
 
 if test -x "$GIT_DIR"/hooks/pre-applypatch
 then
index 2c053a33c3c78f27150cdd397ad28641787abb3a..cb33fdc7e20272a77d97d1b68c31d9b93d3cf4d4 100755 (executable)
@@ -1,6 +1,10 @@
 #!/bin/sh
 . git-sh-setup || die "Not a git archive"
 
+usage () {
+    die "usage: git checkout [-f] [-b <new_branch>] [<branch>] [<paths>...]"
+}
+
 old=$(git-rev-parse HEAD)
 new=
 force=
@@ -23,32 +27,84 @@ while [ "$#" != "0" ]; do
        "-f")
                force=1
                ;;
+       --)
+               break
+               ;;
+       -*)
+               usage
+               ;;
        *)
-               rev=$(git-rev-parse --verify "$arg^0" 2>/dev/null) ||
-                       die "I don't know any '$arg'."
-               if [ -z "$rev" ]; then
-                       echo "unknown flag $arg"
-                       exit 1
-               fi
-               if [ "$new" ]; then
-                       echo "Multiple revisions?"
-                       exit 1
-               fi
-               new="$rev"
-               if [ -f "$GIT_DIR/refs/heads/$arg" ]; then
-                       branch="$arg"
+               if rev=$(git-rev-parse --verify "$arg^0" 2>/dev/null)
+               then
+                       if [ -z "$rev" ]; then
+                               echo "unknown flag $arg"
+                               exit 1
+                       fi
+                       new="$rev"
+                       if [ -f "$GIT_DIR/refs/heads/$arg" ]; then
+                               branch="$arg"
+                       fi
+               elif rev=$(git-rev-parse --verify "$arg^{tree}" 2>/dev/null)
+               then
+                       # checking out selected paths from a tree-ish.
+                       new="$rev"
+                       branch=
+               else
+                       new=
+                       branch=
+                       set x "$arg" "$@"
+                       shift
                fi
+               break
                ;;
     esac
 done
-[ -z "$new" ] && new=$old
 
+# The behaviour of the command with and without explicit path
+# parameters is quite different.
+#
+# Without paths, we are checking out everything in the work tree,
+# possibly switching branches.  This is the traditional behaviour.
 #
+# With paths, we are _never_ switching branch, but checking out
+# the named paths from either index (when no rev is given),
+# or the named tree-ish (when rev is given).
+
+if test "$#" -ge 1
+then
+       if test '' != "$newbranch$force"
+       then
+               die "updating paths and switching branches or forcing are incompatible."
+       fi
+       if test '' != "$new"
+       then
+               # from a specific tree-ish; note that this is for
+               # rescuing paths and is never meant to remove what
+               # is not in the named tree-ish.
+               git-ls-tree -r "$new" "$@" |
+               sed -ne 's/^\([0-7]*\) blob \(.*\)$/\1 \2/p' |
+               git-update-index --index-info || exit $?
+       fi
+       git-checkout-index -f -u -- "$@"
+       exit $?
+else
+       # Make sure we did not fall back on $arg^{tree} codepath
+       # since we are not checking out from an arbitrary tree-ish,
+       # but switching branches.
+       if test '' != "$new"
+       then
+               git-rev-parse --verify "$new^{commit}" >/dev/null 2>&1 ||
+               die "Cannot switch branch to a non-commit."
+       fi
+fi
+
+[ -z "$new" ] && new=$old
+
 # If we don't have an old branch that we're switching to,
 # and we don't have a new branch name for the target we
 # are switching to, then we'd better just be checking out
 # what we already had
-#
+
 [ -z "$branch$newbranch" ] &&
        [ "$new" != "$old" ] &&
        die "git checkout: you need to specify a new branch name"
index 591fcdceb1a3d47ce311bda0b3a81b62e99f47fc..10651d87d087b6e8d656799d00384669e376fca3 100755 (executable)
@@ -6,7 +6,7 @@
 . git-sh-setup || die "Not a git archive"
 
 usage () {
-       die 'git commit [-a] [-v | --no-verify]  [-m <message>] [-F <logfile>] [(-C|-c) <commit>] [<path>...]'
+       die 'git commit [-a] [-s] [-v | --no-verify]  [-m <message> | -F <logfile> | (-C|-c) <commit>] [-e] [<path>...]'
 }
 
 all= logfile= use_commit= no_edit= log_given= log_message= verify=t signoff=
@@ -99,7 +99,7 @@ t,*)
 ,0)
        ;;
 *)
-       git-diff-files --name-only -z "$@" |
+       git-diff-files --name-only -z -- "$@" |
        git-update-index --remove -z --stdin
        ;;
 esac || exit 1
@@ -129,7 +129,7 @@ then
 elif test "$use_commit" != ""
 then
        git-cat-file commit "$use_commit" | sed -e '1,/^$/d'
-fi | git-stripspace >.editmsg
+fi | git-stripspace >"$GIT_DIR"/COMMIT_EDITMSG
 
 case "$signoff" in
 t)
@@ -139,7 +139,7 @@ t)
                        s/>.*/>/
                        s/^/Signed-off-by: /
                '
-       } >>.editmsg
+       } >>"$GIT_DIR"/COMMIT_EDITMSG
        ;;
 esac
 
@@ -153,7 +153,7 @@ if [ -f "$GIT_DIR/MERGE_HEAD" ]; then
        echo "# $GIT_DIR/MERGE_HEAD"
        echo "# and try again"
        echo "#"
-fi >>.editmsg
+fi >>"$GIT_DIR"/COMMIT_EDITMSG
 
 PARENTS="-p HEAD"
 if GIT_DIR="$GIT_DIR" git-rev-parse --verify HEAD >/dev/null 2>&1
@@ -197,16 +197,16 @@ else
        fi
        PARENTS=""
 fi
-git-status >>.editmsg
+git-status >>"$GIT_DIR"/COMMIT_EDITMSG
 if [ "$?" != "0" -a ! -f "$GIT_DIR/MERGE_HEAD" ]
 then
-       rm -f .editmsg
+       rm -f "$GIT_DIR/COMMIT_EDITMSG"
        git-status
        exit 1
 fi
 case "$no_edit" in
 '')
-       ${VISUAL:-${EDITOR:-vi}} .editmsg
+       ${VISUAL:-${EDITOR:-vi}} "$GIT_DIR/COMMIT_EDITMSG"
        ;;
 esac
 
@@ -214,16 +214,20 @@ case "$verify" in
 t)
        if test -x "$GIT_DIR"/hooks/commit-msg
        then
-               "$GIT_DIR"/hooks/commit-msg .editmsg || exit
+               "$GIT_DIR"/hooks/commit-msg "$GIT_DIR"/COMMIT_EDITMSG || exit
        fi
 esac
 
-grep -v '^#' < .editmsg | git-stripspace > .cmitmsg
-grep -v -i '^Signed-off-by' .cmitmsg >.cmitchk
-if test -s .cmitchk
+grep -v '^#' < "$GIT_DIR"/COMMIT_EDITMSG |
+git-stripspace > "$GIT_DIR"/COMMIT_MSG
+
+if cnt=`grep -v -i '^Signed-off-by' "$GIT_DIR"/COMMIT_MSG |
+       git-stripspace |
+       wc -l` &&
+   test 0 -lt $cnt
 then
        tree=$(git-write-tree) &&
-       commit=$(cat .cmitmsg | git-commit-tree $tree $PARENTS) &&
+       commit=$(cat "$GIT_DIR"/COMMIT_MSG | git-commit-tree $tree $PARENTS) &&
        git-update-ref HEAD $commit $current &&
        rm -f -- "$GIT_DIR/MERGE_HEAD"
 else
@@ -231,7 +235,7 @@ else
        false
 fi
 ret="$?"
-rm -f .cmitmsg .editmsg .cmitchk
+rm -f "$GIT_DIR/COMMIT_MSG" "$GIT_DIR/COMMIT_EDITMSG"
 
 if test -x "$GIT_DIR"/hooks/post-commit && test "$ret" = 0
 then
index 87630694427a0b9a95a0514120f8a352a44a6f0c..5240dd2c296482a7ae5486e7688ef8ca944197e0 100644 (file)
@@ -9,7 +9,7 @@ URL:            http://kernel.org/pub/software/scm/git/
 Source:        http://kernel.org/pub/software/scm/git/%{name}-%{version}.tar.gz
 BuildRequires: zlib-devel, openssl-devel, curl-devel  %{!?_without_docs:, xmlto, asciidoc > 6.0.3}
 BuildRoot:     %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n)
-Requires:      rsync, rcs, curl, less, openssh-clients, python >= 2.3, tk >= 8.4
+Requires:      zlib >= 1.2, rsync, rcs, curl, less, openssh-clients, python >= 2.3, tk >= 8.4
 
 %description
 This is a stupid (but extremely fast) directory content manager.  It
index 74ee4f371f749cb5ae9fdb7e789d341f60997e62..843d2fd9f2ef53ccd1600dd9b89ec70fbc639146 100755 (executable)
@@ -2,7 +2,7 @@
 
 . git-sh-setup
 
-echo $(find "$GIT_DIR/objects"/?? -type f -print | wc -l) objects, \
+echo $(find "$GIT_DIR/objects"/?? -type f -print 2>/dev/null | wc -l) objects, \
 $({
     echo 0
     # "no-such" is to help Darwin folks by not using xargs -r.
index f00f7596b75753c23a8b9dd6092b1a7d8b72835e..bbb83fb71cab2d177e98a1c648974bb2bca4607a 100755 (executable)
@@ -570,6 +570,7 @@ my $commit = sub {
        unless($pid) {
                $pr->writer();
                $pw->reader();
+               open(OUT,">&STDOUT");
                dup2($pw->fileno(),0);
                dup2($pr->fileno(),1);
                $pr->close();
@@ -587,10 +588,9 @@ my $commit = sub {
                                if ( -e "$git_dir/refs/heads/$mparent") {
                                        $mparent = get_headref($mparent, $git_dir);
                                        push @par, '-p', $mparent;
-                                       # printing here breaks import # 
-                                       # # print "Merge parent branch: $mparent\n" if $opt_v;
+                                       print OUT "Merge parent branch: $mparent\n" if $opt_v;
                                }
-                       } 
+                       }
                }
 
                exec("env",
index 84a152af206166e88701b27acdc7d2b033e90bab..b3ec84be698311b30e8c7c793873e6caa41daf71 100755 (executable)
@@ -28,16 +28,16 @@ case "$rev" in
 ?*' '^?*)
        begin=$(expr "$rev" : '.*^.\([0-9a-f]*\).*') &&
        end=$(expr "$rev" : '.\([0-9a-f]*\). .*') || exit
-       cmd="git-diff-tree $flags $begin $end $files"
+       cmd="git-diff-tree $flags $begin $end -- $files"
        ;;
 ?*' '?*)
-       cmd="git-diff-tree $flags $rev $files"
+       cmd="git-diff-tree $flags $rev -- $files"
        ;;
 ?*' ')
-       cmd="git-diff-index $flags $rev $files"
+       cmd="git-diff-index $flags $rev -- $files"
        ;;
 '')
-       cmd="git-diff-files $flags $files"
+       cmd="git-diff-files $flags -- $files"
        ;;
 *)
        die "I don't understand $*"
index 360fecdd4ea6d59d52274fa397fd6af895e0f1f1..31e5f4c7225df3d279b78f67ebd0f7a8c87ede40 100755 (executable)
@@ -5,6 +5,10 @@
 _x40='[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]'
 _x40="$_x40$_x40$_x40$_x40$_x40$_x40$_x40$_x40"
 
+LF='
+'
+IFS="$LF"
+
 tags=
 append=
 force=
@@ -110,7 +114,12 @@ fast_forward_local () {
        # is no way to guarantee "fast-forward" anyway.
        if test -f "$GIT_DIR/$1"
        then
-               echo >&2 "* $1: updating with $3"
+               if now_=$(cat "$GIT_DIR/$1") && test "$now_" = "$2"
+               then
+                       echo >&2 "* $1: same as $3"
+               else
+                       echo >&2 "* $1: updating with $3"
+               fi
        else
                echo >&2 "* $1: storing $3"
        fi
@@ -178,7 +187,7 @@ then
        if test "$#" -gt 1
        then
                # remote URL plus explicit refspecs; we need to merge them.
-               reflist="$reflist $taglist"
+               reflist="$reflist$LF$taglist"
        else
                # No explicit refspecs; fetch tags only.
                reflist=$taglist
@@ -187,7 +196,7 @@ fi
 
 for ref in $reflist
 do
-    refs="$refs $ref"
+    refs="$refs$LF$ref"
 
     # These are relative path from $GIT_DIR, typically starting at refs/
     # but may be HEAD
@@ -208,7 +217,7 @@ do
     remote_name=$(expr "$ref" : '\([^:]*\):')
     local_name=$(expr "$ref" : '[^:]*:\(.*\)')
 
-    rref="$rref $remote_name"
+    rref="$rref$LF$remote_name"
 
     # There are transports that can fetch only one head at a time...
     case "$remote" in
@@ -216,7 +225,12 @@ do
        if [ -n "$GIT_SSL_NO_VERIFY" ]; then
            curl_extra_args="-k"
        fi
-       head=$(curl -nsf $curl_extra_args "$remote/$remote_name") &&
+       remote_name_quoted=$(perl -e '
+           my $u = $ARGV[0];
+           $u =~ s{([^-a-zA-Z0-9/.])}{sprintf"%%%02x",ord($1)}eg;
+           print "$u";
+       ' "$remote_name")
+       head=$(curl -nsf $curl_extra_args "$remote/$remote_name_quoted") &&
        expr "$head" : "$_x40\$" >/dev/null ||
                die "Failed to fetch $remote_name from $remote"
        echo >&2 Fetching "$remote_name from $remote" using http
@@ -266,6 +280,7 @@ case "$remote" in
 http://* | https://* | rsync://* )
     ;; # we are already done.
 *)
+    IFS="      $LF"
     (
        git-fetch-pack "$remote" $rref || echo failed "$remote"
     ) |
diff --git a/git-findtags.perl b/git-findtags.perl
new file mode 100755 (executable)
index 0000000..745affe
--- /dev/null
@@ -0,0 +1,94 @@
+#!/usr/bin/perl -w
+#
+# Copyright (c) 2005 Martin Langhoff
+#
+# Walk the tags and find if they match a commit
+# expects a SHA1 of a commit. Option -t enables 
+# searching trees too.
+#
+
+use strict;
+use File::Basename;
+use File::Find;
+use Getopt::Std;
+
+my $git_dir = $ENV{GIT_DIR} || '.git';
+$git_dir =~ s|/$||; # chomp trailing slash
+
+# options
+our $opt_t;
+getopts("t") || usage();
+
+my @tagfiles   = `find $git_dir/refs/tags -follow -type f`; # haystack
+my $target = shift @ARGV;                     # needle
+unless ($target) {
+    usage();
+}
+
+# drive the processing from the find hook
+# slower, safer (?) than the find utility
+find( { wanted   => \&process,
+       no_chdir => 1,
+       follow   => 1,
+      }, "$git_dir/refs/tags");
+
+
+sub process {
+    my ($dev,$ino,$mode,$nlink,$uid,$gid);
+
+    # process only regular files
+    unless ((($dev,$ino,$mode,$nlink,$uid,$gid) = lstat($_)) && -f _) {
+       return 1; # ignored anyway
+    }
+
+    my $tagfile = $_;
+    chomp $tagfile;
+    my $tagname = substr($tagfile, length($git_dir.'/refs/tags/'));
+
+    my $tagid = quickread($tagfile);
+    chomp $tagid;
+
+    # is it just a soft tag?
+    if ($tagid eq $target) {
+       print "$tagname\n";
+       return 1; # done with this tag
+    }
+
+    # grab the first 2 lines (the whole tag could be large)
+    my $tagobj = `git-cat-file tag $tagid | head -n2 `;
+    if ($tagobj =~  m/^type commit$/m) { # only deal with commits
+
+       if ($tagobj =~ m/^object $target$/m) { # match on the commit
+           print "$tagname\n";
+
+       } elsif ( $opt_t &&                      # follow the commit
+                $tagobj =~ m/^object (\S+)$/m) { # and try to match trees
+           my $commitid = $1;
+           my $commitobj = `git-cat-file commit $commitid | head -n1`;
+           chomp $commitobj;
+           $commitobj =~ m/^tree (\S+)$/;
+           my $treeid = $1;
+           if ($target eq $treeid) {
+               print "$tagname\n";
+           }
+       }
+    }
+}
+
+sub quickread {
+    my $file = shift;
+    local $/; # undef: slurp mode
+    open FILE, "<$file"
+       or die "Cannot open $file : $!";
+    my $content = <FILE>;
+    close FILE;
+    return $content;
+}
+
+sub usage {
+       print STDERR <<END;
+Usage: ${\basename $0}     # find tags for a commit or tree
+       [ -t ] <commit-or-tree-sha1>
+END
+       exit(1);
+}
index 2844799535a4c63f61f6c94cf7a66c88d0052656..0207ab18a3876249a928e7539d8f594a4f6921f1 100755 (executable)
@@ -6,7 +6,9 @@
 . git-sh-setup || die "Not a git archive."
 
 usage () {
-    echo >&2 "usage: $0"' [-n] [-o dir] [--keep-subject] [--mbox] [--check] [--signoff] [-<diff options>...] upstream [ our-head ]
+    echo >&2 "usage: $0"' [-n] [-o dir | --stdout] [--keep-subject] [--mbox]
+    [--check] [--signoff] [-<diff options>...]
+    ( from..to ... | upstream [ our-head ] )
 
 Prepare each commit with its patch since our-head forked from upstream,
 one file per patch, for e-mail submission.  Each output file is
@@ -49,6 +51,8 @@ do
     numbered=t ;;
     -s|--s|--si|--sig|--sign|--signo|--signof|--signoff)
     signoff=t ;;
+    --st|--std|--stdo|--stdou|--stdout)
+    stdout=t mbox=t date=t author=t ;;
     -o=*|--o=*|--ou=*|--out=*|--outp=*|--outpu=*|--output=*|--output-=*|\
     --output-d=*|--output-di=*|--output-dir=*|--output-dire=*|\
     --output-direc=*|--output-direct=*|--output-directo=*|\
@@ -73,25 +77,70 @@ tt)
        die '--keep-subject and --numbered are incompatible.' ;;
 esac
 
-rev1= rev2=
-case "$#" in
-2)
-    rev1="$1" rev2="$2" ;;
-1)
-    case "$1" in
-    *..*)
-       rev1=`expr "$1" : '\(.*\)\.\.'`
-       rev2=`expr "$1" : '.*\.\.\(.*\)'`
+tmp=.tmp-series$$
+trap 'rm -f $tmp-*' 0 1 2 3 15
+
+series=$tmp-series
+commsg=$tmp-commsg
+filelist=$tmp-files
+
+# Backward compatible argument parsing hack.
+#
+# Historically, we supported:
+# 1. "rev1"            is equivalent to "rev1..HEAD"
+# 2. "rev1..rev2"
+# 3. "rev1" "rev2      is equivalent to "rev1..rev2"
+#
+# We want to take a sequence of "rev1..rev2" in general.
+
+case "$#,$1" in
+1,?*..?*)
+       # single "rev1..rev2"
        ;;
-    *)
-        rev1="$1"
-       rev2="HEAD"
+1,*)
+       # single rev1
+       set x "$1..HEAD"
+       shift
+       ;;
+2,?*..?*)
+       # not traditional "rev1" "rev2"
+       ;;
+2,*)
+       set x "$1..$2"
+       shift
        ;;
-    esac ;;
-*)
-    usage ;;
 esac
 
+# Now we have what we want in $@
+for revpair
+do
+       case "$revpair" in
+       ?*..?*)
+               rev1=`expr "$revpair" : '\(.*\)\.\.'`
+               rev2=`expr "$revpair" : '.*\.\.\(.*\)'`
+               ;;
+       *)
+               usage
+               ;;
+       esac
+       git-rev-parse --verify "$rev1^0" >/dev/null 2>&1 ||
+               die "Not a valid rev $rev1 ($revpair)"
+       git-rev-parse --verify "$rev2^0" >/dev/null 2>&1 ||
+               die "Not a valid rev $rev2 ($revpair)"
+       git-cherry -v "$rev1" "$rev2" |
+       while read sign rev comment
+       do
+               case "$sign" in
+               '-')
+                       echo >&2 "Merged already: $comment"
+                       ;;
+               *)
+                       echo $rev
+                       ;;
+               esac
+       done
+done >$series
+
 me=`git-var GIT_AUTHOR_IDENT | sed -e 's/>.*/>/'`
 
 case "$outdir" in
@@ -100,13 +149,6 @@ case "$outdir" in
 esac
 test -d "$outdir" || mkdir -p "$outdir" || exit
 
-tmp=.tmp-series$$
-trap 'rm -f $tmp-*' 0 1 2 3 15
-
-series=$tmp-series
-commsg=$tmp-commsg
-filelist=$tmp-files
-
 titleScript='
        /./d
        /^$/n
@@ -128,38 +170,7 @@ whosepatchScript='
        q
 }'
 
-git-cherry -v "$rev1" "$rev2" |
-while read sign rev comment
-do
-       case "$sign" in
-       '-')
-               echo >&2 "Merged already: $comment"
-               ;;
-       *)
-               echo $rev
-               ;;
-       esac
-done >$series
-
-total=`wc -l <$series | tr -dc "[0-9]"`
-i=1
-while read commit
-do
-    git-cat-file commit "$commit" | git-stripspace >$commsg
-    title=`sed -ne "$titleScript" <$commsg`
-    case "$numbered" in
-    '') num= ;;
-    *)
-       case $total in
-       1) num= ;;
-       *) num=' '`printf "%d/%d" $i $total` ;;
-       esac
-    esac
-
-    file=`printf '%04d-%stxt' $i "$title"`
-    i=`expr "$i" + 1`
-    echo "* $file"
-    {
+process_one () {
        mailScript='
        /./d
        /^$/n'
@@ -178,6 +189,7 @@ do
            echo 'From nobody Mon Sep 17 00:00:00 2001' ;# UNIX "From" line
            ;;
        esac
+
        eval "$(sed -ne "$whosepatchScript" $commsg)"
        test "$author,$au" = ",$me" || {
                mailScript="$mailScript"'
@@ -196,7 +208,9 @@ Date: '"$ad"
        n
        b body'
 
-       sed -ne "$mailScript" <$commsg
+       (cat $commsg ; echo; echo) |
+       sed -ne "$mailScript" |
+       git-stripspace
 
        test "$signoff" = "t" && {
                offsigner=`git-var GIT_COMMITTER_IDENT | sed -e 's/>.*/>/'`
@@ -212,21 +226,49 @@ Date: '"$ad"
        echo
        git-diff-tree -p $diff_opts "$commit" | git-apply --stat --summary
        echo
+       git-cat-file commit "$commit^" | sed -e 's/^tree /applies-to: /' -e q
        git-diff-tree -p $diff_opts "$commit"
+       echo "---"
+       echo "@@GIT_VERSION@@"
 
        case "$mbox" in
        t)
                echo
                ;;
        esac
-    } >"$outdir$file"
-    case "$check" in
-    t)
-       # This is slightly modified from Andrew Morton's Perfect Patch.
-       # Lines you introduce should not have trailing whitespace.
-       # Also check for an indentation that has SP before a TAB.
-        grep -n '^+\([         ]*      .*\|.*[         ]\)$' "$outdir$file"
-
-       : do not exit with non-zero because we saw no problem in the last one.
+}
+
+total=`wc -l <$series | tr -dc "[0-9]"`
+i=1
+while read commit
+do
+    git-cat-file commit "$commit" | git-stripspace >$commsg
+    title=`sed -ne "$titleScript" <$commsg`
+    case "$numbered" in
+    '') num= ;;
+    *)
+       case $total in
+       1) num= ;;
+       *) num=' '`printf "%d/%d" $i $total` ;;
+       esac
     esac
+
+    file=`printf '%04d-%stxt' $i "$title"`
+    if test '' = "$stdout"
+    then
+           echo "* $file"
+           process_one >"$outdir$file"
+           if test t = "$check"
+           then
+               # This is slightly modified from Andrew Morton's Perfect Patch.
+               # Lines you introduce should not have trailing whitespace.
+               # Also check for an indentation that has SP before a TAB.
+               grep -n '^+\([  ]*      .*\|.*[         ]\)$' "$outdir$file"
+               :
+           fi
+    else
+           echo >&2 "* $file"
+           process_one
+    fi
+    i=`expr "$i" + 1`
 done <$series
index bfbd5a4d5a1bb35f9f52e3a495d1830b76078a7c..f0f0b07f6f8c85219104303d65dabbba80fd3098 100755 (executable)
@@ -1,6 +1,6 @@
 #!/bin/sh
 #
-. git-sh-setup || die "Not a git archive"
+. git-sh-setup
 
 usage () {
     echo >&2 "usage: $0 [--heads] [--tags] <repository> <refs>..."
index b80a860357a4c402c01aeeb7d7974b8d6eafe23e..626d85493a64d798dedbdc52b2b0f68d56d447fd 100755 (executable)
@@ -4,7 +4,7 @@ import sys, math, random, os, re, signal, tempfile, stat, errno, traceback
 from heapq import heappush, heappop
 from sets import Set
 
-sys.path.append('@@GIT_PYTHON_PATH@@')
+sys.path.append('''@@GIT_PYTHON_PATH@@''')
 from gitMergeCommon import *
 
 originalIndexFile = os.environ.get('GIT_INDEX_FILE',
index 3465041bc4c9b2512623ee6c06317ecb7689e528..6ad96ebfbb06697a0ad0875274022d9103b24d0a 100755 (executable)
@@ -25,7 +25,7 @@ dropsave() {
 
 savestate() {
        # Stash away any local modifications.
-       git-diff-index -r -z --name-only $head |
+       git-diff-index -z --name-only $head |
        cpio -0 -o >"$GIT_DIR/MERGE_SAVE"
 }
 
@@ -38,10 +38,20 @@ restorestate() {
        fi
 }
 
-summary() {
+finish () {
+       test '' = "$2" || echo "$2"
+       case "$merge_msg" in
+       '')
+               echo "No merge message -- not updating HEAD"
+               ;;
+       *)
+               git-update-ref HEAD "$1" "$head" || exit 1
+               ;;
+       esac
+
        case "$no_summary" in
        '')
-               git-diff-tree -p -M $head "$1" |
+               git-diff-tree -p -M "$head" "$1" |
                git-apply --stat --summary
                ;;
        esac
@@ -108,7 +118,7 @@ case "$#,$common" in
 1,"$1")
        # If head can reach all the merge then we are up to date.
        # but first the most common case of merging one remote
-       echo "Already up-to-date. Yeeah!"
+       echo "Already up-to-date."
        dropsave
        exit 0
        ;;
@@ -116,10 +126,9 @@ case "$#,$common" in
        # Again the most common case of merging one remote.
        echo "Updating from $head to $1."
        git-update-index --refresh 2>/dev/null
-       git-read-tree -u -m $head "$1" &&
        new_head=$(git-rev-parse --verify "$1^0") &&
-       git-update-ref HEAD "$new_head" "$head" || exit 1
-       summary "$1"
+       git-read-tree -u -m $head "$new_head" &&
+       finish "$new_head" "Fast forward"
        dropsave
        exit 0
        ;;
@@ -140,8 +149,7 @@ case "$#,$common" in
                echo "$merge_msg" |
                git-commit-tree $result_tree -p HEAD -p "$1"
            ) || exit
-           git-update-ref HEAD $result_commit $head
-           summary $result_commit
+           finish "$result_commit" "In-index merge"
            dropsave
            exit 0
        fi
@@ -237,9 +245,7 @@ then
         parents="$parents -p $remote"
     done
     result_commit=$(echo "$merge_msg" | git-commit-tree $result_tree $parents) || exit
-    echo "Committed merge $result_commit, made by $wt_strategy."
-    git-update-ref HEAD $result_commit $head
-    summary $result_commit
+    finish "$result_commit" "Merge $result_commit, made by $wt_strategy."
     dropsave
     exit 0
 fi
diff --git a/git-mv.perl b/git-mv.perl
new file mode 100755 (executable)
index 0000000..17e35b0
--- /dev/null
@@ -0,0 +1,195 @@
+#!/usr/bin/perl
+#
+# Copyright 2005, Ryan Anderson <ryan@michonline.com>
+#                 Josef Weidendorfer <Josef.Weidendorfer@gmx.de>
+#
+# This file is licensed under the GPL v2, or a later version
+# at the discretion of Linus Torvalds.
+
+
+use warnings;
+use strict;
+use Getopt::Std;
+
+sub usage() {
+       print <<EOT;
+$0 [-f] [-n] <source> <dest>
+$0 [-f] [-k] [-n] <source> ... <dest directory>
+
+In the first form, source must exist and be either a file,
+symlink or directory, dest must not exist. It renames source to dest.
+In the second form, the last argument has to be an existing
+directory; the given sources will be moved into this directory.
+
+Updates the git cache to reflect the change.
+Use "git commit" to make the change permanently.
+
+Options:
+  -f   Force renaming/moving, even if target exists
+  -k   Continue on error by skipping
+       not-existing or not revision-controlled source
+  -n   Do nothing; show what would happen
+EOT
+       exit(1);
+}
+
+# Sanity checks:
+my $GIT_DIR = $ENV{'GIT_DIR'} || ".git";
+
+unless ( -d $GIT_DIR && -d $GIT_DIR . "/objects" && 
+       -d $GIT_DIR . "/objects/" && -d $GIT_DIR . "/refs") {
+    print "Git repository not found.";
+    usage();
+}
+
+
+our ($opt_n, $opt_f, $opt_h, $opt_k, $opt_v);
+getopts("hnfkv") || usage;
+usage() if $opt_h;
+@ARGV >= 1 or usage;
+
+my (@srcArgs, @dstArgs, @srcs, @dsts);
+my ($src, $dst, $base, $dstDir);
+
+my $argCount = scalar @ARGV;
+if (-d $ARGV[$argCount-1]) {
+       $dstDir = $ARGV[$argCount-1];
+       @srcArgs = @ARGV[0..$argCount-2];
+       
+       foreach $src (@srcArgs) {
+               $base = $src;
+               $base =~ s/^.*\///;
+               $dst = "$dstDir/". $base;
+               push @dstArgs, $dst;
+       }
+}
+else {
+    if ($argCount != 2) {
+       print "Error: moving to directory '"
+           . $ARGV[$argCount-1]
+           . "' not possible; not exisiting\n";
+       usage;
+    }
+    @srcArgs = ($ARGV[0]);
+    @dstArgs = ($ARGV[1]);
+    $dstDir = "";
+}
+
+my (@allfiles,@srcfiles,@dstfiles);
+my $safesrc;
+my (%overwritten, %srcForDst);
+
+$/ = "\0";
+open(F,"-|","git-ls-files","-z")
+        or die "Failed to open pipe from git-ls-files: " . $!;
+
+@allfiles = map { chomp; $_; } <F>;
+close(F);
+
+
+my ($i, $bad);
+while(scalar @srcArgs > 0) {
+    $src = shift @srcArgs;
+    $dst = shift @dstArgs;
+    $bad = "";
+
+    if ($opt_v) {
+       print "Checking rename of '$src' to '$dst'\n";
+    }
+
+    unless (-f $src || -l $src || -d $src) {
+       $bad = "bad source '$src'";
+    }
+
+    $overwritten{$dst} = 0;
+    if (($bad eq "") && -e $dst) {
+       $bad = "destination '$dst' already exists";
+       if (-f $dst && $opt_f) {
+           print "Warning: $bad; will overwrite!\n";
+           $bad = "";
+           $overwritten{$dst} = 1;
+       }
+    }
+    
+    if (($bad eq "") && ($src eq $dstDir)) {
+       $bad = "can not move directory '$src' into itself";
+    }
+
+    if ($bad eq "") {
+       $safesrc = quotemeta($src);
+       @srcfiles = grep /^$safesrc(\/|$)/, @allfiles;
+        if (scalar @srcfiles == 0) {
+           $bad = "'$src' not under version control";
+       }
+    }
+
+    if ($bad eq "") {
+       if (defined $srcForDst{$dst}) {
+           $bad = "can not move '$src' to '$dst'; already target of ";
+           $bad .= "'".$srcForDst{$dst}."'";
+       }
+       else {
+           $srcForDst{$dst} = $src;
+       }
+    }
+
+    if ($bad ne "") {
+       if ($opt_k) {
+           print "Warning: $bad; skipping\n";
+           next;
+       }
+       print "Error: $bad\n";
+       usage();
+    }
+    push @srcs, $src;
+    push @dsts, $dst;
+}
+
+# Final pass: rename/move
+my (@deletedfiles,@addedfiles,@changedfiles);
+while(scalar @srcs > 0) {
+    $src = shift @srcs;
+    $dst = shift @dsts;
+
+    if ($opt_n || $opt_v) { print "Renaming $src to $dst\n"; }
+    if (!$opt_n) {
+       rename($src,$dst)
+           or die "rename failed: $!";
+    }
+
+    $safesrc = quotemeta($src);
+    @srcfiles = grep /^$safesrc(\/|$)/, @allfiles;
+    @dstfiles = @srcfiles;
+    s/^$safesrc(\/|$)/$dst$1/ for @dstfiles;
+
+    push @deletedfiles, @srcfiles;
+    if (scalar @srcfiles == 1) {
+       if ($overwritten{$dst} ==1) {
+           push @changedfiles, $dst;
+       } else {
+           push @addedfiles, $dst;
+       }
+    }
+    else {
+       push @addedfiles, @dstfiles;
+    }
+}
+
+if ($opt_n) {
+       print "Changed  : ". join(", ", @changedfiles) ."\n";
+       print "Adding   : ". join(", ", @addedfiles) ."\n";
+       print "Deleting : ". join(", ", @deletedfiles) ."\n";
+       exit(1);
+}
+       
+my $rc;
+if (scalar @changedfiles >0) {
+       $rc = system("git-update-index","--",@changedfiles);
+       die "git-update-index failed to update changed files with code $?\n" if $rc;
+}
+if (scalar @addedfiles >0) {
+       $rc = system("git-update-index","--add","--",@addedfiles);
+       die "git-update-index failed to add new names with code $?\n" if $rc;
+}
+$rc = system("git-update-index","--remove","--",@deletedfiles);
+die "git-update-index failed to remove old names with code $?\n" if $rc;
index 32f10855b6e94ad891417a476bafd3092135bec7..aea7b0e5497fb0727533ea750503193fe308374a 100755 (executable)
@@ -1,6 +1,6 @@
 #!/bin/sh
 
-. git-sh-setup || die "Not a git archive"
+. git-sh-setup
 
 get_data_source () {
        case "$1" in
index 9657dbf2711e40bfe035a22ec211811c88c89e8e..ef31bd2a6824104ba401bffbe41ecd50a81780e9 100755 (executable)
@@ -15,6 +15,7 @@ do
     shift;
 done
 
+sync
 git-fsck-objects --full --cache --unreachable "$@" |
 sed -ne '/unreachable /{
     s/unreachable [^ ][^ ]* //
@@ -22,6 +23,7 @@ sed -ne '/unreachable /{
 }' | {
        cd "$GIT_OBJECT_DIRECTORY" || exit
        xargs $echo rm -f
+       rmdir 2>/dev/null [0-9a-f][0-9a-f]
 }
 
 git-prune-packed $dryrun
index 00d715059c4b751bef9adaf3debbe56ae1cf19ac..5aa6531945c9e3494e34abb08d81e9ad03864521 100755 (executable)
@@ -1,6 +1,11 @@
 #!/bin/sh
 . git-sh-setup || die "Not a git archive"
 
+usage () {
+    die "Usage: git push [--all] [--force] <repository> [<refspec>]"
+}
+
+
 # Parse out parameters and then stop at remote, so that we can
 # translate it using .git/branches information
 has_all=
@@ -18,7 +23,7 @@ do
        --exec=*)
                has_exec="$1" ;;
        -*)
-               die "Unknown parameter $1" ;;
+                usage ;;
         *)
                set x "$@"
                shift
@@ -28,7 +33,8 @@ do
 done
 case "$#" in
 0)
-       die "Where would you want to push today?" ;;
+       echo "Where would you want to push today?"
+        usage ;;
 esac
 
 . git-parse-remote
@@ -40,8 +46,10 @@ esac
 shift
 
 case "$remote" in
-http://* | https://* | git://* | rsync://* )
-       die "Cannot push to $remote" ;;
+http://* | https://* | git://*)
+       die "Cannot use READ-ONLY transport to push to $remote" ;;
+rsync://*)
+        die "Pushing with rsync transport is deprecated" ;;
 esac
 
 set x "$remote" "$@"; shift
index a28c8c83bb98e81635ce5cb629921e13eca1e3c2..3b1127b1b20280cc4c6c657168dba963f31fa067 100755 (executable)
@@ -15,7 +15,7 @@ sub usage($);
 my $GIT_DIR = $ENV{'GIT_DIR'} || ".git";
 
 unless ( -d $GIT_DIR && -d $GIT_DIR . "/objects" && 
-       -d $GIT_DIR . "/objects/00" && -d $GIT_DIR . "/refs") {
+       -d $GIT_DIR . "/objects/" && -d $GIT_DIR . "/refs") {
        usage("Git repository not found.");
 }
 
index b395d0ef34758f2e3e3129a5131832e932ab7875..d341966efba783b294792c9a13915982f70a1f73 100755 (executable)
@@ -5,13 +5,14 @@
 
 . git-sh-setup || die "Not a git archive"
        
-no_update_info= all_into_one= remove_redundant=
+no_update_info= all_into_one= remove_redundant= local=
 while case "$#" in 0) break ;; esac
 do
        case "$1" in
        -n)     no_update_info=t ;;
        -a)     all_into_one=t ;;
        -d)     remove_redandant=t ;;
+       -l)     local=t ;;
        *)      break ;;
        esac
        shift
@@ -37,6 +38,9 @@ case ",$all_into_one," in
            find . -type f \( -name '*.pack' -o -name '*.idx' \) -print`
        ;;
 esac
+if [ "$local" ]; then
+       pack_objects="$pack_objects --local"
+fi
 name=$(git-rev-list --objects $rev_list $(git-rev-parse $rev_parse) |
        git-pack-objects --non-empty $pack_objects .tmp-pack) ||
        exit 1
@@ -58,6 +62,7 @@ then
        # all-into-one is used.
        if test "$all_into_one" != '' && test "$existing" != ''
        then
+               sync
                ( cd "$PACKDIR" &&
                  for e in $existing
                  do
index f9995cadf563a7b3b2c1dcdeea3bf862feac2e4b..2086d26d343c59d36df79d874d65f58d6d7d8b10 100755 (executable)
@@ -1,6 +1,10 @@
 #!/bin/sh
 . git-sh-setup || die "Not a git archive"
 
+usage () {
+       die 'Usage: git reset [--mixed | --soft | --hard]  [<commit-ish>]'
+}
+
 tmp=/var/tmp/reset.$$
 trap 'rm -f $tmp-*' 0 1 2 3 15
 
@@ -10,6 +14,8 @@ case "$1" in
        reset_type="$1"
        shift
        ;;
+-*)
+        usage ;;
 esac
 
 rev=$(git-rev-parse --verify --default HEAD "$@") || exit
index a0172686a90c58a454656f9e6d49d6c694aec2fc..dbb98842bf6327210c604ae65a128cdfc785b32c 100755 (executable)
@@ -22,4 +22,4 @@ refs/*)       : ;;
 *)     false ;;
 esac &&
 [ -d "$GIT_DIR/refs" ] &&
-[ -d "$GIT_OBJECT_DIRECTORY/00" ]
+[ -d "$GIT_OBJECT_DIRECTORY/" ]
index 8f0984be02c56b23edf4c705fa8a75b95c66edcc..0b14f833ee97a0e5b589098914e52c49b0be50d0 100755 (executable)
@@ -2,55 +2,13 @@
 
 use strict;
 
-#
-# Even with git, we don't always have name translations.
-# So have an email->real name table to translate the
-# (hopefully few) missing names
-#
-my %mailmap = (
-       'R.Marek@sh.cvut.cz' => 'Rudolf Marek',
-       'Ralf.Wildenhues@gmx.de' => 'Ralf Wildenhues',
-       'aherrman@de.ibm.com' => 'Andreas Herrmann',
-       'akpm@osdl.org' => 'Andrew Morton',
-       'andrew.vasquez@qlogic.com' => 'Andrew Vasquez',
-       'aquynh@gmail.com' => 'Nguyen Anh Quynh',
-       'axboe@suse.de' => 'Jens Axboe',
-       'blaisorblade@yahoo.it' => 'Paolo \'Blaisorblade\' Giarrusso',
-       'bunk@stusta.de' => 'Adrian Bunk',
-       'domen@coderock.org' => 'Domen Puncer',
-       'dougg@torque.net' => 'Douglas Gilbert',
-       'dwmw2@shinybook.infradead.org' => 'David Woodhouse',
-       'ecashin@coraid.com' => 'Ed L Cashin',
-       'felix@derklecks.de' => 'Felix Moeller',
-       'fzago@systemfabricworks.com' => 'Frank Zago',
-       'gregkh@suse.de' => 'Greg Kroah-Hartman',
-       'hch@lst.de' => 'Christoph Hellwig',
-       'htejun@gmail.com' => 'Tejun Heo',
-       'jejb@mulgrave.(none)' => 'James Bottomley',
-       'jejb@titanic.il.steeleye.com' => 'James Bottomley',
-       'jgarzik@pretzel.yyz.us' => 'Jeff Garzik',
-       'johnpol@2ka.mipt.ru' => 'Evgeniy Polyakov',
-       'kay.sievers@vrfy.org' => 'Kay Sievers',
-       'minyard@acm.org' => 'Corey Minyard',
-       'mshah@teja.com' => 'Mitesh shah',
-       'pj@ludd.ltu.se' => 'Peter A Jonsson',
-       'rmps@joel.ist.utl.pt' => 'Rui Saraiva',
-       'santtu.hyrkko@gmail.com' => 'Santtu Hyrkkö',
-       'simon@thekelleys.org.uk' => 'Simon Kelley',
-       'ssant@in.ibm.com' => 'Sachin P Sant',
-       'terra@gnome.org' => 'Morten Welinder',
-       'tony.luck@intel.com' => 'Tony Luck',
-       'welinder@anemone.rentec.com' => 'Morten Welinder',
-       'welinder@darter.rentec.com' => 'Morten Welinder',
-       'welinder@troll.com' => 'Morten Welinder',
-);
-
+my (%mailmap);
+my (%email);
 my (%map);
 my $pstate = 1;
 my $n_records = 0;
 my $n_output = 0;
 
-
 sub shortlog_entry($$) {
        my ($name, $desc) = @_;
        my $key = $name;
@@ -108,41 +66,35 @@ sub changelog_input {
                if ($pstate == 1) {
                        my ($email);
 
-                       next unless /^[Aa]uthor:? (.*)<(.*)>.*$/;
-       
+                       next unless /^[Aa]uthor:?\s*(.*?)\s*<(.*)>/;
+
                        $n_records++;
-       
+
                        $author = $1;
                        $email = $2;
                        $desc = undef;
 
-                       # trim trailing whitespace.
-                       # why doesn't chomp work?
-                       while ($author && ($author =~ /\s$/)) {
-                               chop $author;
-                       }
-       
                        # cset author fixups
                        if (exists $mailmap{$email}) {
                                $author = $mailmap{$email};
                        } elsif (exists $mailmap{$author}) {
                                $author = $mailmap{$author};
-                       } elsif ((!$author) || ($author eq "")) {
+                       } elsif (!$author) {
                                $author = $email;
                        }
-       
+                       $email{$author}{$email}++;
                        $pstate++;
                }
-       
+
                # skip to blank line
                elsif ($pstate == 2) {
                        next unless /^\s*$/;
                        $pstate++;
                }
-       
+
                # skip to non-blank line
                elsif ($pstate == 3) {
-                       next unless /^\s*(\S.*)$/;
+                       next unless /^\s*?(.*)/;
 
                        # skip lines that are obviously not
                        # a 1-line cset description
@@ -150,9 +102,9 @@ sub changelog_input {
 
                        chomp;
                        $desc = $1;
-       
+
                        &shortlog_entry($author, $desc);
-       
+
                        $pstate = 1;
                }
        
@@ -162,16 +114,87 @@ sub changelog_input {
        }
 }
 
+sub read_mailmap {
+       my ($fh, $mailmap) = @_;
+       while (<$fh>) {
+               chomp;
+               if (/^([^#].*?)\s*<(.*)>/) {
+                       $mailmap->{$2} = $1;
+               }
+       }
+}
+
+sub setup_mailmap {
+       read_mailmap(\*DATA, \%mailmap);
+       if (-f '.mailmap') {
+               my $fh = undef;
+               open $fh, '<', '.mailmap';
+               read_mailmap($fh, \%mailmap);
+               close $fh;
+       }
+}
+
 sub finalize {
        #print "\n$n_records records parsed.\n";
 
        if ($n_records != $n_output) {
                die "parse error: input records != output records\n";
        }
+       if (0) {
+               for my $author (sort keys %email) {
+                       my $e = $email{$author};
+                       for my $email (sort keys %$e) {
+                               print STDERR "$author <$email>\n";
+                       }
+               }
+       }
 }
 
+&setup_mailmap;
 &changelog_input;
 &shortlog_output;
 &finalize;
 exit(0);
 
+
+__DATA__
+#
+# Even with git, we don't always have name translations.
+# So have an email->real name table to translate the
+# (hopefully few) missing names
+#
+Adrian Bunk <bunk@stusta.de>
+Andreas Herrmann <aherrman@de.ibm.com>
+Andrew Morton <akpm@osdl.org>
+Andrew Vasquez <andrew.vasquez@qlogic.com>
+Christoph Hellwig <hch@lst.de>
+Corey Minyard <minyard@acm.org>
+David Woodhouse <dwmw2@shinybook.infradead.org>
+Domen Puncer <domen@coderock.org>
+Douglas Gilbert <dougg@torque.net>
+Ed L Cashin <ecashin@coraid.com>
+Evgeniy Polyakov <johnpol@2ka.mipt.ru>
+Felix Moeller <felix@derklecks.de>
+Frank Zago <fzago@systemfabricworks.com>
+Greg Kroah-Hartman <gregkh@suse.de>
+James Bottomley <jejb@mulgrave.(none)>
+James Bottomley <jejb@titanic.il.steeleye.com>
+Jeff Garzik <jgarzik@pretzel.yyz.us>
+Jens Axboe <axboe@suse.de>
+Kay Sievers <kay.sievers@vrfy.org>
+Mitesh shah <mshah@teja.com>
+Morten Welinder <terra@gnome.org>
+Morten Welinder <welinder@anemone.rentec.com>
+Morten Welinder <welinder@darter.rentec.com>
+Morten Welinder <welinder@troll.com>
+Nguyen Anh Quynh <aquynh@gmail.com>
+Paolo 'Blaisorblade' Giarrusso <blaisorblade@yahoo.it>
+Peter A Jonsson <pj@ludd.ltu.se>
+Ralf Wildenhues <Ralf.Wildenhues@gmx.de>
+Rudolf Marek <R.Marek@sh.cvut.cz>
+Rui Saraiva <rmps@joel.ist.utl.pt>
+Sachin P Sant <ssant@in.ibm.com>
+Santtu Hyrkk\e,Av\e(B <santtu.hyrkko@gmail.com>
+Simon Kelley <simon@thekelleys.org.uk>
+Tejun Heo <htejun@gmail.com>
+Tony Luck <tony.luck@intel.com>
index fbdd377539eea2c4521a2df2d37ff06b80567664..62a24a9b03a259fa94f61b3cb060c2b99f55e40e 100755 (executable)
@@ -11,7 +11,7 @@ report () {
 #
 "
   trailer=""
-  while read oldmode mode oldsha sha status name newname
+  while read status name newname
   do
     echo -n "$header"
     header=""
@@ -41,16 +41,11 @@ git-update-index -q --unmerged --refresh || exit
 
 if GIT_DIR="$GIT_DIR" git-rev-parse --verify HEAD >/dev/null 2>&1
 then
-       git-diff-index -M --cached HEAD |
+       git-diff-index -M --cached --name-status HEAD |
        sed -e '
-               s/^:// 
-               h
-               s/^[^   ]*//
+               s/\\/\\\\/g
                s/ /\\ /g
-               x
-               s/      .*$//
-               G
-               s/\n/ /' |
+       ' |
        report "Updated but not checked in" "will commit"
 
        committable="$?"
@@ -60,40 +55,48 @@ else
 #'
        git-ls-files |
        sed -e '
+               s/\\/\\\\/g
                s/ /\\ /g
-               s/^/o o o o A /' |
+               s/^/A /
+       ' |
        report "Updated but not checked in" "will commit"
 
        committable="$?"
 fi
 
-git-diff-files |
+git-diff-files  --name-status |
 sed -e '
-       s/^:// 
-       h
-       s/^[^   ]*//
+       s/\\/\\\\/g
        s/ /\\ /g
-       x
-       s/      .*$//
-       G
-       s/\n/ /' |
+' |
 report "Changed but not updated" "use git-update-index to mark for commit"
 
-if grep -v '^#' "$GIT_DIR/info/exclude" >/dev/null 2>&1
+
+if test -f "$GIT_DIR/info/exclude"
 then
-       git-ls-files --others \
-           --exclude-from="$GIT_DIR/info/exclude" \
-           --exclude-per-directory=.gitignore |
-       sed -e '
-       1i\
-#\
-# Ignored files:\
-#   (use "git add" to add to commit)\
-#
-       s/^/#   /
-       $a\
-#'
-fi
+    git-ls-files -z --others \
+       --exclude-from="$GIT_DIR/info/exclude" \
+        --exclude-per-directory=.gitignore
+else
+    git-ls-files -z --others \
+        --exclude-per-directory=.gitignore
+fi |
+perl -e '$/ = "\0";
+       my $shown = 0;
+       while (<>) {
+               chomp;
+               s|\\|\\\\|g;
+               s|\t|\\t|g;
+               s|\n|\\n|g;
+               s/^/#   /;
+               if (!$shown) {
+                       print "#\n# Untracked files:\n";
+                       print "#   (use \"git add\" to add to commit)\n#\n";
+                       $shown = 1;
+               }
+               print "$_\n";
+       }
+'
 
 case "$committable" in
 0)
diff --git a/git-svnimport.perl b/git-svnimport.perl
new file mode 100755 (executable)
index 0000000..45b6a19
--- /dev/null
@@ -0,0 +1,717 @@
+#!/usr/bin/perl -w
+
+# This tool is copyright (c) 2005, Matthias Urlichs.
+# It is released under the Gnu Public License, version 2.
+#
+# The basic idea is to pull and analyze SVN changes.
+#
+# Checking out the files is done by a single long-running SVN connection.
+#
+# The head revision is on branch "origin" by default.
+# You can change that with the '-o' option.
+
+require 5.008; # for shell-safe open("-|",LIST)
+use strict;
+use warnings;
+use Getopt::Std;
+use File::Spec;
+use File::Temp qw(tempfile);
+use File::Path qw(mkpath);
+use File::Basename qw(basename dirname);
+use Time::Local;
+use IO::Pipe;
+use POSIX qw(strftime dup2);
+use IPC::Open2;
+use SVN::Core;
+use SVN::Ra;
+
+die "Need CVN:Core 1.2.1 or better" if $SVN::Core::VERSION lt "1.2.1";
+
+$SIG{'PIPE'}="IGNORE";
+$ENV{'TZ'}="UTC";
+
+our($opt_h,$opt_o,$opt_v,$opt_u,$opt_C,$opt_i,$opt_m,$opt_M,$opt_t,$opt_T,$opt_b,$opt_s,$opt_l,$opt_d,$opt_D);
+
+sub usage() {
+       print STDERR <<END;
+Usage: ${\basename $0}     # fetch/update GIT from CVS
+       [-o branch-for-HEAD] [-h] [-v] [-l max_num_changes]
+       [-C GIT_repository] [-t tagname] [-T trunkname] [-b branchname]
+       [-d|-D] [-i] [-u] [-s start_chg] [-m] [-M regex] [SVN_URL]
+END
+       exit(1);
+}
+
+getopts("b:C:dDhil:mM:o:s:t:T:uv") or usage();
+usage if $opt_h;
+
+my $tag_name = $opt_t || "tags";
+my $trunk_name = $opt_T || "trunk";
+my $branch_name = $opt_b || "branches";
+
+@ARGV == 1 or @ARGV == 2 or usage();
+
+$opt_o ||= "origin";
+$opt_s ||= 1;
+$opt_l = 100 unless defined $opt_l;
+my $git_tree = $opt_C;
+$git_tree ||= ".";
+
+my $svn_url = $ARGV[0];
+my $svn_dir = $ARGV[1];
+
+our @mergerx = ();
+if ($opt_m) {
+       @mergerx = ( qr/\W(?:from|of|merge|merging|merged) (\w+)/i );
+}
+if ($opt_M) {
+       push (@mergerx, qr/$opt_M/);
+}
+
+select(STDERR); $|=1; select(STDOUT);
+
+
+package SVNconn;
+# Basic SVN connection.
+# We're only interested in connecting and downloading, so ...
+
+use File::Spec;
+use File::Temp qw(tempfile);
+use POSIX qw(strftime dup2);
+
+sub new {
+       my($what,$repo) = @_;
+       $what=ref($what) if ref($what);
+
+       my $self = {};
+       $self->{'buffer'} = "";
+       bless($self,$what);
+
+       $repo =~ s#/+$##;
+       $self->{'fullrep'} = $repo;
+       $self->conn();
+
+       return $self;
+}
+
+sub conn {
+       my $self = shift;
+       my $repo = $self->{'fullrep'};
+       my $s = SVN::Ra->new($repo);
+
+       die "SVN connection to $repo: $!\n" unless defined $s;
+       $self->{'svn'} = $s;
+       $self->{'repo'} = $repo;
+       $self->{'maxrev'} = $s->get_latest_revnum();
+}
+
+sub file {
+       my($self,$path,$rev) = @_;
+
+       my ($fh, $name) = tempfile('gitsvn.XXXXXX',
+                   DIR => File::Spec->tmpdir(), UNLINK => 1);
+
+       print "... $rev $path ...\n" if $opt_v;
+       eval { $self->{'svn'}->get_file($path,$rev,$fh); };
+       if($@) {
+               return undef if $@ =~ /Attempted to get checksum/;
+               die $@;
+       }
+       close ($fh);
+
+       return $name;
+}
+
+package main;
+use URI;
+
+my $svn = $svn_url;
+$svn .= "/$svn_dir" if defined $svn_dir;
+$svn = SVNconn->new($svn);
+
+my $lwp_ua;
+if($opt_d or $opt_D) {
+       $svn_url = URI->new($svn_url)->canonical;
+       if($opt_D) {
+               $svn_dir =~ s#/*$#/#;
+       } else {
+               $svn_dir = "";
+       }
+       if ($svn_url->scheme eq "http") {
+               use LWP::UserAgent;
+               $lwp_ua = LWP::UserAgent->new(keep_alive => 1, requests_redirectable => []);
+       } else {
+               print STDERR "Warning: not HTTP; turning off direct file access\n";
+               $opt_d=0;
+       }
+}
+
+sub pdate($) {
+       my($d) = @_;
+       $d =~ m#(\d\d\d\d)-(\d\d)-(\d\d)T(\d\d):(\d\d):(\d\d)#
+               or die "Unparseable date: $d\n";
+       my $y=$1; $y-=1900 if $y>1900;
+       return timegm($6||0,$5,$4,$3,$2-1,$y);
+}
+
+sub getwd() {
+       my $pwd = `pwd`;
+       chomp $pwd;
+       return $pwd;
+}
+
+
+sub get_headref($$) {
+    my $name    = shift;
+    my $git_dir = shift;
+    my $sha;
+
+    if (open(C,"$git_dir/refs/heads/$name")) {
+       chomp($sha = <C>);
+       close(C);
+       length($sha) == 40
+           or die "Cannot get head id for $name ($sha): $!\n";
+    }
+    return $sha;
+}
+
+
+-d $git_tree
+       or mkdir($git_tree,0777)
+       or die "Could not create $git_tree: $!";
+chdir($git_tree);
+
+my $orig_branch = "";
+my $forward_master = 0;
+my %branches;
+
+my $git_dir = $ENV{"GIT_DIR"} || ".git";
+$git_dir = getwd()."/".$git_dir unless $git_dir =~ m#^/#;
+$ENV{"GIT_DIR"} = $git_dir;
+my $orig_git_index;
+$orig_git_index = $ENV{GIT_INDEX_FILE} if exists $ENV{GIT_INDEX_FILE};
+my ($git_ih, $git_index) = tempfile('gitXXXXXX', SUFFIX => '.idx',
+                                   DIR => File::Spec->tmpdir());
+close ($git_ih);
+$ENV{GIT_INDEX_FILE} = $git_index;
+my $maxnum = 0;
+my $last_rev = "";
+my $last_branch;
+my $current_rev = $opt_s-1;
+unless(-d $git_dir) {
+       system("git-init-db");
+       die "Cannot init the GIT db at $git_tree: $?\n" if $?;
+       system("git-read-tree");
+       die "Cannot init an empty tree: $?\n" if $?;
+
+       $last_branch = $opt_o;
+       $orig_branch = "";
+} else {
+       -f "$git_dir/refs/heads/$opt_o"
+               or die "Branch '$opt_o' does not exist.\n".
+                      "Either use the correct '-o branch' option,\n".
+                      "or import to a new repository.\n";
+
+       -f "$git_dir/svn2git"
+               or die "'$git_dir/svn2git' does not exist.\n".
+                      "You need that file for incremental imports.\n";
+       $last_branch = basename(readlink("$git_dir/HEAD"));
+       unless($last_branch) {
+               warn "Cannot read the last branch name: $! -- assuming 'master'\n";
+               $last_branch = "master";
+       }
+       $orig_branch = $last_branch;
+       $last_rev = get_headref($orig_branch, $git_dir);
+       if (-f "$git_dir/SVN2GIT_HEAD") {
+               die <<EOM;
+SVN2GIT_HEAD exists.
+Make sure your working directory corresponds to HEAD and remove SVN2GIT_HEAD.
+You may need to run
+
+    git-read-tree -m -u SVN2GIT_HEAD HEAD
+EOM
+       }
+       system('cp', "$git_dir/HEAD", "$git_dir/SVN2GIT_HEAD");
+
+       $forward_master =
+           $opt_o ne 'master' && -f "$git_dir/refs/heads/master" &&
+           system('cmp', '-s', "$git_dir/refs/heads/master",
+                               "$git_dir/refs/heads/$opt_o") == 0;
+
+       # populate index
+       system('git-read-tree', $last_rev);
+       die "read-tree failed: $?\n" if $?;
+
+       # Get the last import timestamps
+       open my $B,"<", "$git_dir/svn2git";
+       while(<$B>) {
+               chomp;
+               my($num,$branch,$ref) = split;
+               $branches{$branch}{$num} = $ref;
+               $branches{$branch}{"LAST"} = $ref;
+               $current_rev = $num if $current_rev < $num;
+       }
+       close($B);
+}
+-d $git_dir
+       or die "Could not create git subdir ($git_dir).\n";
+
+open BRANCHES,">>", "$git_dir/svn2git";
+
+sub get_file($$$) {
+       my($rev,$branch,$path) = @_;
+
+       # revert split_path(), below
+       my $svnpath;
+       $path = "" if $path eq "/"; # this should not happen, but ...
+       if($branch eq "/") {
+               $svnpath = "$trunk_name/$path";
+       } elsif($branch =~ m#^/#) {
+               $svnpath = "$tag_name$branch/$path";
+       } else {
+               $svnpath = "$branch_name/$branch/$path";
+       }
+
+       # now get it
+       my $name;
+       if($opt_d) {
+               my($req,$res);
+
+               # /svn/!svn/bc/2/django/trunk/django-docs/build.py
+               my $url=$svn_url->clone();
+               $url->path($url->path."/!svn/bc/$rev/$svn_dir$svnpath");
+               print "... $path...\n" if $opt_v;
+               $req = HTTP::Request->new(GET => $url);
+               $res = $lwp_ua->request($req);
+               if ($res->is_success) {
+                       my $fh;
+                       ($fh, $name) = tempfile('gitsvn.XXXXXX',
+                       DIR => File::Spec->tmpdir(), UNLINK => 1);
+                       print $fh $res->content;
+                       close($fh) or die "Could not write $name: $!\n";
+               } else {
+                       return undef if $res->code == 301; # directory?
+                       die $res->status_line." at $url\n";
+               }
+       } else {
+               $name = $svn->file("/$svnpath",$rev);
+               return undef unless defined $name;
+       }
+
+       open my $F, '-|', "git-hash-object", "-w", $name
+               or die "Cannot create object: $!\n";
+       my $sha = <$F>;
+       chomp $sha;
+       close $F;
+       unlink $name;
+       my $mode = "0644"; # SV does not seem to store any file modes
+       return [$mode, $sha, $path];
+}
+
+sub split_path($$) {
+       my($rev,$path) = @_;
+       my $branch;
+
+       if($path =~ s#^/\Q$tag_name\E/([^/]+)/?##) {
+               $branch = "/$1";
+       } elsif($path =~ s#^/\Q$trunk_name\E/?##) {
+               $branch = "/";
+       } elsif($path =~ s#^/\Q$branch_name\E/([^/]+)/?##) {
+               $branch = $1;
+       } else {
+               print STDERR "$rev: Unrecognized path: $path\n";
+               return ()
+       }
+       $path = "/" if $path eq "";
+       return ($branch,$path);
+}
+
+sub copy_subdir($$$$$$) {
+       # Somebody copied a whole subdirectory.
+       # We need to find the index entries from the old version which the
+       # SVN log entry points to, and add them to the new place.
+
+       my($newrev,$newbranch,$path,$oldpath,$rev,$new) = @_;
+       my($branch,$srcpath) = split_path($rev,$oldpath);
+
+       my $gitrev = $branches{$branch}{$rev};
+       unless($gitrev) {
+               print STDERR "$newrev:$newbranch: could not find $oldpath \@ $rev\n";
+               return;
+       }
+       print "$newrev:$newbranch:$path: copying from $branch:$srcpath @ $rev\n" if $opt_v;
+       $srcpath =~ s#/*$#/#;
+       open my $f,"-|","git-ls-tree","-r","-z",$gitrev,$srcpath;
+       local $/ = "\0";
+       while(<$f>) {
+               chomp;
+               my($m,$p) = split(/\t/,$_,2);
+               my($mode,$type,$sha1) = split(/ /,$m);
+               next if $type ne "blob";
+               $p = substr($p,length($srcpath)-1);
+               print "... found $path$p ...\n" if $opt_v;
+               push(@$new,[$mode,$sha1,$path.$p]);
+       }
+       close($f) or
+               print STDERR "$newrev:$newbranch: could not list files in $oldpath \@ $rev\n";
+}
+
+sub commit {
+       my($branch, $changed_paths, $revision, $author, $date, $message) = @_;
+       my($author_name,$author_email,$dest);
+       my(@old,@new);
+
+       if (not defined $author) {
+               $author_name = $author_email = "unknown";
+       } elsif ($author =~ /^(.*?)\s+<(.*)>$/) {
+               ($author_name, $author_email) = ($1, $2);
+       } else {
+               $author =~ s/^<(.*)>$/$1/;
+               $author_name = $author_email = $author;
+       }
+       $date = pdate($date);
+
+       my $tag;
+       my $parent;
+       if($branch eq "/") { # trunk
+               $parent = $opt_o;
+       } elsif($branch =~ m#^/(.+)#) { # tag
+               $tag = 1;
+               $parent = $1;
+       } else { # "normal" branch
+               # nothing to do
+               $parent = $branch;
+       }
+       $dest = $parent;
+
+       my $prev = $changed_paths->{"/"};
+       if($prev and $prev->[0] eq "A") {
+               delete $changed_paths->{"/"};
+               my $oldpath = $prev->[1];
+               my $rev;
+               if(defined $oldpath) {
+                       my $p;
+                       ($parent,$p) = split_path($revision,$oldpath);
+                       if($parent eq "/") {
+                               $parent = $opt_o;
+                       } else {
+                               $parent =~ s#^/##; # if it's a tag
+                       }
+               } else {
+                       $parent = undef;
+               }
+       }
+
+       my $rev;
+       if($revision > $opt_s and defined $parent) {
+               open(H,"git-rev-parse --verify $parent |");
+               $rev = <H>;
+               close(H) or do {
+                       print STDERR "$revision: cannot find commit '$parent'!\n";
+                       return;
+               };
+               chop $rev;
+               if(length($rev) != 40) {
+                       print STDERR "$revision: cannot find commit '$parent'!\n";
+                       return;
+               }
+               $rev = $branches{($parent eq $opt_o) ? "/" : $parent}{"LAST"};
+               if($revision != $opt_s and not $rev) {
+                       print STDERR "$revision: do not know ancestor for '$parent'!\n";
+                       return;
+               }
+       } else {
+               $rev = undef;
+       }
+
+#      if($prev and $prev->[0] eq "A") {
+#              if(not $tag) {
+#                      unless(open(H,"> $git_dir/refs/heads/$branch")) {
+#                              print STDERR "$revision: Could not create branch $branch: $!\n";
+#                              $state=11;
+#                              next;
+#                      }
+#                      print H "$rev\n"
+#                              or die "Could not write branch $branch: $!";
+#                      close(H)
+#                              or die "Could not write branch $branch: $!";
+#              }
+#      }
+       if(not defined $rev) {
+               unlink($git_index);
+       } elsif ($rev ne $last_rev) {
+               print "Switching from $last_rev to $rev ($branch)\n" if $opt_v;
+               system("git-read-tree", $rev);
+               die "read-tree failed for $rev: $?\n" if $?;
+               $last_rev = $rev;
+       }
+
+       my $cid;
+       if($tag and not %$changed_paths) {
+               $cid = $rev;
+       } else {
+               my @paths = sort keys %$changed_paths;
+               foreach my $path(@paths) {
+                       my $action = $changed_paths->{$path};
+
+                       if ($action->[0] eq "A") {
+                               my $f = get_file($revision,$branch,$path);
+                               if($f) {
+                                       push(@new,$f) if $f;
+                               } elsif($action->[1]) {
+                                       copy_subdir($revision,$branch,$path,$action->[1],$action->[2],\@new);
+                               } else {
+                                       my $opath = $action->[3];
+                                       print STDERR "$revision: $branch: could not fetch '$opath'\n";
+                               }
+                       } elsif ($action->[0] eq "D") {
+                               push(@old,$path);
+                       } elsif ($action->[0] eq "M") {
+                               my $f = get_file($revision,$branch,$path);
+                               push(@new,$f) if $f;
+                       } elsif ($action->[0] eq "R") {
+                               # refer to a file/tree in an earlier commit
+                               push(@old,$path); # remove any old stuff
+
+                               # ... and add any new stuff
+                               my($b,$srcpath) = split_path($revision,$action->[1]);
+                               $srcpath =~ s#/*$#/#;
+                               open my $F,"-|","git-ls-tree","-r","-z", $branches{$b}{$action->[2]}, $srcpath;
+                               local $/ = "\0";
+                               while(<$F>) {
+                                       chomp;
+                                       my($m,$p) = split(/\t/,$_,2);
+                                       my($mode,$type,$sha1) = split(/ /,$m);
+                                       next if $type ne "blob";
+                                       $p = substr($p,length($srcpath)-1);
+                                       push(@new,[$mode,$sha1,$path.$p]);
+                               }
+                               close($F);
+                       } else {
+                               die "$revision: unknown action '".$action->[0]."' for $path\n";
+                       }
+               }
+
+               if(@old) {
+                       open my $F, "-|", "git-ls-files", "-z", @old or die $!;
+                       @old = ();
+                       local $/ = "\0";
+                       while(<$F>) {
+                               chomp;
+                               push(@old,$_);
+                       }
+                       close($F);
+
+                       while(@old) {
+                               my @o2;
+                               if(@old > 55) {
+                                       @o2 = splice(@old,0,50);
+                               } else {
+                                       @o2 = @old;
+                                       @old = ();
+                               }
+                               system("git-update-index","--force-remove","--",@o2);
+                               die "Cannot remove files: $?\n" if $?;
+                       }
+               }
+               while(@new) {
+                       my @n2;
+                       if(@new > 12) {
+                               @n2 = splice(@new,0,10);
+                       } else {
+                               @n2 = @new;
+                               @new = ();
+                       }
+                       system("git-update-index","--add",
+                               (map { ('--cacheinfo', @$_) } @n2));
+                       die "Cannot add files: $?\n" if $?;
+               }
+
+               my $pid = open(C,"-|");
+               die "Cannot fork: $!" unless defined $pid;
+               unless($pid) {
+                       exec("git-write-tree");
+                       die "Cannot exec git-write-tree: $!\n";
+               }
+               chomp(my $tree = <C>);
+               length($tree) == 40
+                       or die "Cannot get tree id ($tree): $!\n";
+               close(C)
+                       or die "Error running git-write-tree: $?\n";
+               print "Tree ID $tree\n" if $opt_v;
+
+               my $pr = IO::Pipe->new() or die "Cannot open pipe: $!\n";
+               my $pw = IO::Pipe->new() or die "Cannot open pipe: $!\n";
+               $pid = fork();
+               die "Fork: $!\n" unless defined $pid;
+               unless($pid) {
+                       $pr->writer();
+                       $pw->reader();
+                       open(OUT,">&STDOUT");
+                       dup2($pw->fileno(),0);
+                       dup2($pr->fileno(),1);
+                       $pr->close();
+                       $pw->close();
+
+                       my @par = ();
+                       @par = ("-p",$rev) if defined $rev;
+
+                       # loose detection of merges
+                       # based on the commit msg
+                       foreach my $rx (@mergerx) {
+                               if ($message =~ $rx) {
+                                       my $mparent = $1;
+                                       if ($mparent eq 'HEAD') { $mparent = $opt_o };
+                                       if ( -e "$git_dir/refs/heads/$mparent") {
+                                               $mparent = get_headref($mparent, $git_dir);
+                                               push @par, '-p', $mparent;
+                                               print OUT "Merge parent branch: $mparent\n" if $opt_v;
+                                       }
+                               }
+                       }
+
+                       exec("env",
+                               "GIT_AUTHOR_NAME=$author_name",
+                               "GIT_AUTHOR_EMAIL=$author_email",
+                               "GIT_AUTHOR_DATE=".strftime("+0000 %Y-%m-%d %H:%M:%S",gmtime($date)),
+                               "GIT_COMMITTER_NAME=$author_name",
+                               "GIT_COMMITTER_EMAIL=$author_email",
+                               "GIT_COMMITTER_DATE=".strftime("+0000 %Y-%m-%d %H:%M:%S",gmtime($date)),
+                               "git-commit-tree", $tree,@par);
+                       die "Cannot exec git-commit-tree: $!\n";
+               }
+               $pw->writer();
+               $pr->reader();
+
+               $message =~ s/[\s\n]+\z//;
+
+               print $pw "$message\n"
+                       or die "Error writing to git-commit-tree: $!\n";
+               $pw->close();
+
+               print "Committed change $revision:$branch ".strftime("%Y-%m-%d %H:%M:%S",gmtime($date)).")\n" if $opt_v;
+               chomp($cid = <$pr>);
+               length($cid) == 40
+                       or die "Cannot get commit id ($cid): $!\n";
+               print "Commit ID $cid\n" if $opt_v;
+               $pr->close();
+
+               waitpid($pid,0);
+               die "Error running git-commit-tree: $?\n" if $?;
+       }
+
+       if(not defined $dest) {
+               print "... no known parent\n" if $opt_v;
+       } elsif(not $tag) {
+               print "Writing to refs/heads/$dest\n" if $opt_v;
+               open(C,">$git_dir/refs/heads/$dest") and
+               print C ("$cid\n") and
+               close(C)
+                       or die "Cannot write branch $dest for update: $!\n";
+       }
+
+       if($tag) {
+               my($in, $out) = ('','');
+               $last_rev = "-" if %$changed_paths;
+               # the tag was 'complex', i.e. did not refer to a "real" revision
+
+               $dest =~ tr/_/\./ if $opt_u;
+
+               my $pid = open2($in, $out, 'git-mktag');
+               print $out ("object $cid\n".
+                   "type commit\n".
+                   "tag $dest\n".
+                   "tagger $author_name <$author_email>\n") and
+               close($out)
+                   or die "Cannot create tag object $dest: $!\n";
+
+               my $tagobj = <$in>;
+               chomp $tagobj;
+
+               if ( !close($in) or waitpid($pid, 0) != $pid or
+                               $? != 0 or $tagobj !~ /^[0123456789abcdef]{40}$/ ) {
+                       die "Cannot create tag object $dest: $!\n";
+               }
+
+               open(C,">$git_dir/refs/tags/$dest") and
+               print C ("$tagobj\n") and
+               close(C)
+                       or die "Cannot create tag $branch: $!\n";
+
+               print "Created tag '$dest' on '$branch'\n" if $opt_v;
+       }
+       $branches{$branch}{"LAST"} = $cid;
+       $branches{$branch}{$revision} = $cid;
+       $last_rev = $cid;
+       print BRANCHES "$revision $branch $cid\n";
+       print "DONE: $revision $dest $cid\n" if $opt_v;
+}
+
+my ($changed_paths, $revision, $author, $date, $message, $pool) = @_;
+sub _commit_all {
+       ($changed_paths, $revision, $author, $date, $message, $pool) = @_;
+       my %p;
+       while(my($path,$action) = each %$changed_paths) {
+               $p{$path} = [ $action->action,$action->copyfrom_path, $action->copyfrom_rev, $path ];
+       }
+       $changed_paths = \%p;
+}
+
+sub commit_all {
+       my %done;
+       my @col;
+       my $pref;
+       my $branch;
+
+       while(my($path,$action) = each %$changed_paths) {
+               ($branch,$path) = split_path($revision,$path);
+               next if not defined $branch;
+               $done{$branch}{$path} = $action;
+       }
+       while(($branch,$changed_paths) = each %done) {
+               commit($branch, $changed_paths, $revision, $author, $date, $message);
+       }
+}
+
+while(++$current_rev <= $svn->{'maxrev'}) {
+       $svn->{'svn'}->get_log("/",$current_rev,$current_rev,$current_rev,1,1,\&_commit_all,"");
+       commit_all();
+       if($opt_l and not --$opt_l) {
+               print STDERR "Stopping, because there is a memory leak (in the SVN library).\n";
+               print STDERR "Please repeat this command; it will continue safely\n";
+               last;
+       }
+}
+
+
+unlink($git_index);
+
+if (defined $orig_git_index) {
+       $ENV{GIT_INDEX_FILE} = $orig_git_index;
+} else {
+       delete $ENV{GIT_INDEX_FILE};
+}
+
+# Now switch back to the branch we were in before all of this happened
+if($orig_branch) {
+       print "DONE\n" if $opt_v and (not defined $opt_l or $opt_l > 0);
+       system("cp","$git_dir/refs/heads/$opt_o","$git_dir/refs/heads/master")
+               if $forward_master;
+       unless ($opt_i) {
+               system('git-read-tree', '-m', '-u', 'SVN2GIT_HEAD', 'HEAD');
+               die "read-tree failed: $?\n" if $?;
+       }
+} else {
+       $orig_branch = "master";
+       print "DONE; creating $orig_branch branch\n" if $opt_v and (not defined $opt_l or $opt_l > 0);
+       system("cp","$git_dir/refs/heads/$opt_o","$git_dir/refs/heads/master")
+               unless -f "$git_dir/refs/heads/master";
+       unlink("$git_dir/HEAD");
+       symlink("refs/heads/$orig_branch","$git_dir/HEAD");
+       unless ($opt_i) {
+               system('git checkout');
+               die "checkout failed: $?\n" if $?;
+       }
+}
+unlink("$git_dir/SVN2GIT_HEAD");
+close(BRANCHES);
index 11b0492ba83d23155e460d0a805eedd7bf556de1..faa766799df6d276b6fcdca79d4f05b05ec2626b 100755 (executable)
@@ -4,7 +4,7 @@
 . git-sh-setup || die "Not a git archive"
 
 usage () {
-    echo >&2 "Usage: git-tag [-a | -s] [-f] [-m "tag message"] tagname [head]"
+    echo >&2 "Usage: git-tag [-a | -s | -u <key-id>] [-f] [-m <msg>] <tagname> [<head>]"
     exit 1
 }
 
@@ -12,6 +12,7 @@ annotate=
 signed=
 force=
 message=
+username=
 while case "$#" in 0) break ;; esac
 do
     case "$1" in
@@ -30,6 +31,12 @@ do
        shift
        message="$1"
        ;;
+    -u)
+       annotate=1
+       signed=1
+       shift
+       username="$1"
+       ;;
     -*)
         usage
        ;;
@@ -52,6 +59,7 @@ git-check-ref-format "tags/$name" ||
 object=$(git-rev-parse --verify --default HEAD "$@") || exit 1
 type=$(git-cat-file -t $object) || exit 1
 tagger=$(git-var GIT_COMMITTER_IDENT) || exit 1
+: ${username:=$(expr "$tagger" : '\(.*>\)')}
 
 trap 'rm -f .tmp-tag* .tagmsg .editmsg' 0
 
@@ -67,13 +75,15 @@ if [ "$annotate" ]; then
 
     grep -v '^#' < .editmsg | git-stripspace > .tagmsg
 
-    [ -s .tagmsg ] || exit
+    [ -s .tagmsg ] || {
+       echo >&2 "No tag message?"
+       exit 1
+    }
 
     ( echo -e "object $object\ntype $type\ntag $name\ntagger $tagger\n"; cat .tagmsg ) > .tmp-tag
     rm -f .tmp-tag.asc .tagmsg
     if [ "$signed" ]; then
-       me=$(expr "$tagger" : '\(.*>\)') &&
-       gpg -bsa -u "$me" .tmp-tag &&
+       gpg -bsa -u "$username" .tmp-tag &&
        cat .tmp-tag.asc >>.tmp-tag ||
        die "failed to sign the tag with GPG."
     fi
diff --git a/git.sh b/git.sh
index 1c73ca1c20be9861cce22ea4ca99f60d9e3d9173..94940aea28a45294126b9065e09017ae302e7a51 100755 (executable)
--- a/git.sh
+++ b/git.sh
@@ -11,7 +11,18 @@ case "$#" in
                echo "git version @@GIT_VERSION@@"
                exit 0 ;;
        esac
-       test -x "$path/git-$cmd" && exec "$path/git-$cmd" "$@" ;;
+       
+       test -x "$path/git-$cmd" && exec "$path/git-$cmd" "$@"
+       
+       case '@@X@@' in
+           '')
+               ;;
+           *)
+               test -x "$path/git-$cmd@@X@@" &&
+               exec "$path/git-$cmd@@X@@" "$@"
+               ;;
+       esac
+       ;;
 esac
 
 echo "Usage: git COMMAND [OPTIONS] [TARGET]"
diff --git a/gitk b/gitk
index f1ea4e1e432010f9a049fe91b305d09f44589280..a9d37d9c73e5aae166fc748160df9a5a3ebbad4a 100755 (executable)
--- a/gitk
+++ b/gitk
@@ -2806,7 +2806,7 @@ proc gettreediffs {ids} {
     set treediff {}
     set id [lindex $ids 0]
     set p [lindex $ids 1]
-    if [catch {set gdtf [open "|git-diff-tree -r $p $id" r]}] return
+    if [catch {set gdtf [open "|git-diff-tree -r $id" r]}] return
     fconfigure $gdtf -blocking 0
     fileevent $gdtf readable [list gettreediffline $gdtf $ids]
 }
@@ -2842,7 +2842,7 @@ proc getblobdiffs {ids} {
     set id [lindex $ids 0]
     set p [lindex $ids 1]
     set env(GIT_DIFF_OPTS) $diffopts
-    set cmd [list | git-diff-tree -r -p -C $p $id]
+    set cmd [list | git-diff-tree -r -p -C $id]
     if {[catch {set bdf [open $cmd r]} err]} {
        puts "error getting diffs: $err"
        return
index 71a8c60b56914ca579db3da1d2bef6795acc7bf9..a1b03cd9c842e9daa8f743939d4382faac0c534f 100644 (file)
@@ -6,6 +6,11 @@
 #include <curl/curl.h>
 #include <curl/easy.h>
 
+#if LIBCURL_VERSION_NUM >= 0x070908
+#define USE_CURL_MULTI
+#define DEFAULT_MAX_REQUESTS 5
+#endif
+
 #if LIBCURL_VERSION_NUM < 0x070704
 #define curl_global_cleanup() do { /* nothing */ } while(0)
 #endif
 #define curl_global_init(a) do { /* nothing */ } while(0)
 #endif
 
+#if LIBCURL_VERSION_NUM < 0x070c04
+#define NO_CURL_EASY_DUPHANDLE
+#endif
+
 #define PREV_BUF_SIZE 4096
 #define RANGE_HEADER_SIZE 30
 
-static CURL *curl;
+static int got_alternates = 0;
+static int active_requests = 0;
+static int data_received;
+
+#ifdef USE_CURL_MULTI
+static int max_requests = -1;
+static CURLM *curlm;
+#endif
+#ifndef NO_CURL_EASY_DUPHANDLE
+static CURL *curl_default;
+#endif
+static struct curl_slist *pragma_header;
 static struct curl_slist *no_pragma_header;
 static struct curl_slist *no_range_header;
 static char curl_errorstr[CURL_ERROR_SIZE];
 
-static char *initial_base;
-
 struct alt_base
 {
        char *base;
@@ -33,17 +51,59 @@ struct alt_base
 
 static struct alt_base *alt = NULL;
 
-static SHA_CTX c;
-static z_stream stream;
+enum transfer_state {
+       WAITING,
+       ABORTED,
+       ACTIVE,
+       COMPLETE,
+};
+
+struct transfer_request
+{
+       unsigned char sha1[20];
+       struct alt_base *repo;
+       char *url;
+       char filename[PATH_MAX];
+       char tmpfile[PATH_MAX];
+       int local;
+       enum transfer_state state;
+       CURLcode curl_result;
+       char errorstr[CURL_ERROR_SIZE];
+       long http_code;
+       unsigned char real_sha1[20];
+       SHA_CTX c;
+       z_stream stream;
+       int zret;
+       int rename;
+       struct active_request_slot *slot;
+       struct transfer_request *next;
+};
 
-static int local;
-static int zret;
+struct active_request_slot
+{
+       CURL *curl;
+       FILE *local;
+       int in_use;
+       int done;
+       CURLcode curl_result;
+       long http_code;
+       struct active_request_slot *next;
+};
 
-static int curl_ssl_verify;
-static char *ssl_cert;
-static char *ssl_key;
-static char *ssl_capath;
-static char *ssl_cainfo;
+static struct transfer_request *request_queue_head = NULL;
+static struct active_request_slot *active_queue_head = NULL;
+
+static int curl_ssl_verify = -1;
+static char *ssl_cert = NULL;
+#if LIBCURL_VERSION_NUM >= 0x070902
+static char *ssl_key = NULL;
+#endif
+#if LIBCURL_VERSION_NUM >= 0x070908
+static char *ssl_capath = NULL;
+#endif
+static char *ssl_cainfo = NULL;
+static long curl_low_speed_limit = -1;
+static long curl_low_speed_time = -1;
 
 struct buffer
 {
@@ -52,6 +112,71 @@ struct buffer
         void *buffer;
 };
 
+static int http_options(const char *var, const char *value)
+{
+       if (!strcmp("http.sslverify", var)) {
+               if (curl_ssl_verify == -1) {
+                       curl_ssl_verify = git_config_bool(var, value);
+               }
+               return 0;
+       }
+
+       if (!strcmp("http.sslcert", var)) {
+               if (ssl_cert == NULL) {
+                       ssl_cert = xmalloc(strlen(value)+1);
+                       strcpy(ssl_cert, value);
+               }
+               return 0;
+       }
+#if LIBCURL_VERSION_NUM >= 0x070902
+       if (!strcmp("http.sslkey", var)) {
+               if (ssl_key == NULL) {
+                       ssl_key = xmalloc(strlen(value)+1);
+                       strcpy(ssl_key, value);
+               }
+               return 0;
+       }
+#endif
+#if LIBCURL_VERSION_NUM >= 0x070908
+       if (!strcmp("http.sslcapath", var)) {
+               if (ssl_capath == NULL) {
+                       ssl_capath = xmalloc(strlen(value)+1);
+                       strcpy(ssl_capath, value);
+               }
+               return 0;
+       }
+#endif
+       if (!strcmp("http.sslcainfo", var)) {
+               if (ssl_cainfo == NULL) {
+                       ssl_cainfo = xmalloc(strlen(value)+1);
+                       strcpy(ssl_cainfo, value);
+               }
+               return 0;
+       }
+
+#ifdef USE_CURL_MULTI  
+       if (!strcmp("http.maxrequests", var)) {
+               if (max_requests == -1)
+                       max_requests = git_config_int(var, value);
+               return 0;
+       }
+#endif
+
+       if (!strcmp("http.lowspeedlimit", var)) {
+               if (curl_low_speed_limit == -1)
+                       curl_low_speed_limit = (long)git_config_int(var, value);
+               return 0;
+       }
+       if (!strcmp("http.lowspeedtime", var)) {
+               if (curl_low_speed_time == -1)
+                       curl_low_speed_time = (long)git_config_int(var, value);
+               return 0;
+       }
+
+       /* Fall back on the default ones */
+       return git_default_config(var, value);
+}
+
 static size_t fwrite_buffer(void *ptr, size_t eltsize, size_t nmemb,
                             struct buffer *buffer)
 {
@@ -60,81 +185,521 @@ static size_t fwrite_buffer(void *ptr, size_t eltsize, size_t nmemb,
                 size = buffer->size - buffer->posn;
         memcpy(buffer->buffer + buffer->posn, ptr, size);
         buffer->posn += size;
+       data_received++;
         return size;
 }
 
+static size_t fwrite_buffer_dynamic(const void *ptr, size_t eltsize,
+                                   size_t nmemb, struct buffer *buffer)
+{
+       size_t size = eltsize * nmemb;
+       if (size > buffer->size - buffer->posn) {
+               buffer->size = buffer->size * 3 / 2;
+               if (buffer->size < buffer->posn + size)
+                       buffer->size = buffer->posn + size;
+               buffer->buffer = xrealloc(buffer->buffer, buffer->size);
+       }
+       memcpy(buffer->buffer + buffer->posn, ptr, size);
+       buffer->posn += size;
+       data_received++;
+       return size;
+}
+
 static size_t fwrite_sha1_file(void *ptr, size_t eltsize, size_t nmemb,
                               void *data)
 {
        unsigned char expn[4096];
        size_t size = eltsize * nmemb;
        int posn = 0;
+       struct transfer_request *request = (struct transfer_request *)data;
        do {
-               ssize_t retval = write(local, ptr + posn, size - posn);
+               ssize_t retval = write(request->local,
+                                      ptr + posn, size - posn);
                if (retval < 0)
                        return posn;
                posn += retval;
        } while (posn < size);
 
-       stream.avail_in = size;
-       stream.next_in = ptr;
+       request->stream.avail_in = size;
+       request->stream.next_in = ptr;
        do {
-               stream.next_out = expn;
-               stream.avail_out = sizeof(expn);
-               zret = inflate(&stream, Z_SYNC_FLUSH);
-               SHA1_Update(&c, expn, sizeof(expn) - stream.avail_out);
-       } while (stream.avail_in && zret == Z_OK);
+               request->stream.next_out = expn;
+               request->stream.avail_out = sizeof(expn);
+               request->zret = inflate(&request->stream, Z_SYNC_FLUSH);
+               SHA1_Update(&request->c, expn,
+                           sizeof(expn) - request->stream.avail_out);
+       } while (request->stream.avail_in && request->zret == Z_OK);
+       data_received++;
        return size;
 }
 
-void prefetch(unsigned char *sha1)
+#ifdef USE_CURL_MULTI
+static void process_curl_messages(void);
+static void process_request_queue(void);
+#endif
+static int fetch_alternates(char *base);
+
+static CURL* get_curl_handle(void)
 {
+       CURL* result = curl_easy_init();
+
+       curl_easy_setopt(result, CURLOPT_SSL_VERIFYPEER, curl_ssl_verify);
+#if LIBCURL_VERSION_NUM >= 0x070907
+       curl_easy_setopt(result, CURLOPT_NETRC, CURL_NETRC_OPTIONAL);
+#endif
+
+       if (ssl_cert != NULL)
+               curl_easy_setopt(result, CURLOPT_SSLCERT, ssl_cert);
+#if LIBCURL_VERSION_NUM >= 0x070902
+       if (ssl_key != NULL)
+               curl_easy_setopt(result, CURLOPT_SSLKEY, ssl_key);
+#endif
+#if LIBCURL_VERSION_NUM >= 0x070908
+       if (ssl_capath != NULL)
+               curl_easy_setopt(result, CURLOPT_CAPATH, ssl_capath);
+#endif
+       if (ssl_cainfo != NULL)
+               curl_easy_setopt(result, CURLOPT_CAINFO, ssl_cainfo);
+       curl_easy_setopt(result, CURLOPT_FAILONERROR, 1);
+
+       if (curl_low_speed_limit > 0 && curl_low_speed_time > 0) {
+               curl_easy_setopt(result, CURLOPT_LOW_SPEED_LIMIT,
+                                curl_low_speed_limit);
+               curl_easy_setopt(result, CURLOPT_LOW_SPEED_TIME,
+                                curl_low_speed_time);
+       }
+
+       return result;
 }
 
-int relink_or_rename(char *old, char *new) {
-       int ret;
+static struct active_request_slot *get_active_slot(void)
+{
+       struct active_request_slot *slot = active_queue_head;
+       struct active_request_slot *newslot;
 
-       ret = link(old, new);
-       if (ret < 0) {
-               /* Same Coda hack as in write_sha1_file(sha1_file.c) */
-               ret = errno;
-               if (ret == EXDEV && !rename(old, new))
-                       return 0;
+#ifdef USE_CURL_MULTI
+       int num_transfers;
+
+       /* Wait for a slot to open up if the queue is full */
+       while (active_requests >= max_requests) {
+               curl_multi_perform(curlm, &num_transfers);
+               if (num_transfers < active_requests) {
+                       process_curl_messages();
+               }
+       }
+#endif
+
+       while (slot != NULL && slot->in_use) {
+               slot = slot->next;
        }
-       unlink(old);
-       if (ret) {
-               if (ret != EEXIST)
-                       return ret;
+       if (slot == NULL) {
+               newslot = xmalloc(sizeof(*newslot));
+               newslot->curl = NULL;
+               newslot->in_use = 0;
+               newslot->next = NULL;
+
+               slot = active_queue_head;
+               if (slot == NULL) {
+                       active_queue_head = newslot;
+               } else {
+                       while (slot->next != NULL) {
+                               slot = slot->next;
+                       }
+                       slot->next = newslot;
+               }
+               slot = newslot;
        }
 
-       return 0;
+       if (slot->curl == NULL) {
+#ifdef NO_CURL_EASY_DUPHANDLE
+               slot->curl = get_curl_handle();
+#else
+               slot->curl = curl_easy_duphandle(curl_default);
+#endif
+       }
+
+       active_requests++;
+       slot->in_use = 1;
+       slot->done = 0;
+       slot->local = NULL;
+       curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, pragma_header);
+       curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, no_range_header);
+       curl_easy_setopt(slot->curl, CURLOPT_ERRORBUFFER, curl_errorstr);
+
+       return slot;
 }
 
-static int got_alternates = 0;
+static int start_active_slot(struct active_request_slot *slot)
+{
+#ifdef USE_CURL_MULTI
+       CURLMcode curlm_result = curl_multi_add_handle(curlm, slot->curl);
+
+       if (curlm_result != CURLM_OK &&
+           curlm_result != CURLM_CALL_MULTI_PERFORM) {
+               active_requests--;
+               slot->in_use = 0;
+               return 0;
+       }
+#endif
+       return 1;
+}
+
+static void run_active_slot(struct active_request_slot *slot)
+{
+#ifdef USE_CURL_MULTI
+       int num_transfers;
+       long last_pos = 0;
+       long current_pos;
+       fd_set readfds;
+       fd_set writefds;
+       fd_set excfds;
+       int max_fd;
+       struct timeval select_timeout;
+       CURLMcode curlm_result;
+
+       while (!slot->done) {
+               data_received = 0;
+               do {
+                       curlm_result = curl_multi_perform(curlm,
+                                                         &num_transfers);
+               } while (curlm_result == CURLM_CALL_MULTI_PERFORM);
+               if (num_transfers < active_requests) {
+                       process_curl_messages();
+                       process_request_queue();
+               }
+
+               if (!data_received && slot->local != NULL) {
+                       current_pos = ftell(slot->local);
+                       if (current_pos > last_pos)
+                               data_received++;
+                       last_pos = current_pos;
+               }
+
+               if (!slot->done && !data_received) {
+                       max_fd = 0;
+                       FD_ZERO(&readfds);
+                       FD_ZERO(&writefds);
+                       FD_ZERO(&excfds);
+                       select_timeout.tv_sec = 0;
+                       select_timeout.tv_usec = 50000;
+                       select(max_fd, &readfds, &writefds,
+                              &excfds, &select_timeout);
+               }
+       }
+#else
+       slot->curl_result = curl_easy_perform(slot->curl);
+       active_requests--;
+#endif
+}
+
+static void start_request(struct transfer_request *request)
+{
+       char *hex = sha1_to_hex(request->sha1);
+       char prevfile[PATH_MAX];
+       char *url;
+       char *posn;
+       int prevlocal;
+       unsigned char prev_buf[PREV_BUF_SIZE];
+       ssize_t prev_read = 0;
+       long prev_posn = 0;
+       char range[RANGE_HEADER_SIZE];
+       struct curl_slist *range_header = NULL;
+       struct active_request_slot *slot;
+
+       snprintf(prevfile, sizeof(prevfile), "%s.prev", request->filename);
+       unlink(prevfile);
+       rename(request->tmpfile, prevfile);
+       unlink(request->tmpfile);
+
+       request->local = open(request->tmpfile,
+                             O_WRONLY | O_CREAT | O_EXCL, 0666);
+       /* This could have failed due to the "lazy directory creation";
+        * try to mkdir the last path component.
+        */
+       if (request->local < 0 && errno == ENOENT) {
+               char *dir = strrchr(request->tmpfile, '/');
+               if (dir) {
+                       *dir = 0;
+                       mkdir(request->tmpfile, 0777);
+                       *dir = '/';
+               }
+               request->local = open(request->tmpfile,
+                                     O_WRONLY | O_CREAT | O_EXCL, 0666);
+       }
+
+       if (request->local < 0) {
+               request->state = ABORTED;
+               error("Couldn't create temporary file %s for %s: %s\n",
+                     request->tmpfile, request->filename, strerror(errno));
+               return;
+       }
+
+       memset(&request->stream, 0, sizeof(request->stream));
+
+       inflateInit(&request->stream);
+
+       SHA1_Init(&request->c);
+
+       url = xmalloc(strlen(request->repo->base) + 50);
+       request->url = xmalloc(strlen(request->repo->base) + 50);
+       strcpy(url, request->repo->base);
+       posn = url + strlen(request->repo->base);
+       strcpy(posn, "objects/");
+       posn += 8;
+       memcpy(posn, hex, 2);
+       posn += 2;
+       *(posn++) = '/';
+       strcpy(posn, hex + 2);
+       strcpy(request->url, url);
+
+       /* If a previous temp file is present, process what was already
+          fetched. */
+       prevlocal = open(prevfile, O_RDONLY);
+       if (prevlocal != -1) {
+               do {
+                       prev_read = read(prevlocal, prev_buf, PREV_BUF_SIZE);
+                       if (prev_read>0) {
+                               if (fwrite_sha1_file(prev_buf,
+                                                    1,
+                                                    prev_read,
+                                                    request) == prev_read) {
+                                       prev_posn += prev_read;
+                               } else {
+                                       prev_read = -1;
+                               }
+                       }
+               } while (prev_read > 0);
+               close(prevlocal);
+       }
+       unlink(prevfile);
+
+       /* Reset inflate/SHA1 if there was an error reading the previous temp
+          file; also rewind to the beginning of the local file. */
+       if (prev_read == -1) {
+               memset(&request->stream, 0, sizeof(request->stream));
+               inflateInit(&request->stream);
+               SHA1_Init(&request->c);
+               if (prev_posn>0) {
+                       prev_posn = 0;
+                       lseek(request->local, SEEK_SET, 0);
+                       ftruncate(request->local, 0);
+               }
+       }
+
+       slot = get_active_slot();
+       curl_easy_setopt(slot->curl, CURLOPT_FILE, request);
+       curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_sha1_file);
+       curl_easy_setopt(slot->curl, CURLOPT_ERRORBUFFER, request->errorstr);
+       curl_easy_setopt(slot->curl, CURLOPT_URL, url);
+       curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, no_pragma_header);
+
+       /* If we have successfully processed data from a previous fetch
+          attempt, only fetch the data we don't already have. */
+       if (prev_posn>0) {
+               if (get_verbosely)
+                       fprintf(stderr,
+                               "Resuming fetch of object %s at byte %ld\n",
+                               hex, prev_posn);
+               sprintf(range, "Range: bytes=%ld-", prev_posn);
+               range_header = curl_slist_append(range_header, range);
+               curl_easy_setopt(slot->curl,
+                                CURLOPT_HTTPHEADER, range_header);
+       }
+
+       /* Try to get the request started, abort the request on error */
+       if (!start_active_slot(slot)) {
+               request->state = ABORTED;
+               close(request->local);
+               free(request->url);
+               return;
+       }
+       
+       request->slot = slot;
+       request->state = ACTIVE;
+}
+
+static void finish_request(struct transfer_request *request)
+{
+       fchmod(request->local, 0444);
+       close(request->local);
+
+       if (request->http_code == 416) {
+               fprintf(stderr, "Warning: requested range invalid; we may already have all the data.\n");
+       } else if (request->curl_result != CURLE_OK) {
+               return;
+       }
+
+       inflateEnd(&request->stream);
+       SHA1_Final(request->real_sha1, &request->c);
+       if (request->zret != Z_STREAM_END) {
+               unlink(request->tmpfile);
+               return;
+       }
+       if (memcmp(request->sha1, request->real_sha1, 20)) {
+               unlink(request->tmpfile);
+               return;
+       }
+       request->rename =
+               move_temp_to_file(request->tmpfile, request->filename);
+
+       if (request->rename == 0)
+               pull_say("got %s\n", sha1_to_hex(request->sha1));
+}
+
+static void release_request(struct transfer_request *request)
+{
+       struct transfer_request *entry = request_queue_head;
+
+       if (request == request_queue_head) {
+               request_queue_head = request->next;
+       } else {
+               while (entry->next != NULL && entry->next != request)
+                       entry = entry->next;
+               if (entry->next == request)
+                       entry->next = entry->next->next;
+       }
+
+       free(request->url);
+       free(request);
+}
+
+#ifdef USE_CURL_MULTI
+void process_curl_messages(void)
+{
+       int num_messages;
+       struct active_request_slot *slot;
+       struct transfer_request *request = NULL;
+       CURLMsg *curl_message = curl_multi_info_read(curlm, &num_messages);
+
+       while (curl_message != NULL) {
+               if (curl_message->msg == CURLMSG_DONE) {
+                       slot = active_queue_head;
+                       while (slot != NULL &&
+                              slot->curl != curl_message->easy_handle)
+                               slot = slot->next;
+                       if (slot != NULL) {
+                               curl_multi_remove_handle(curlm, slot->curl);
+                               active_requests--;
+                               slot->done = 1;
+                               slot->in_use = 0;
+                               slot->curl_result = curl_message->data.result;
+                               curl_easy_getinfo(slot->curl,
+                                                 CURLINFO_HTTP_CODE,
+                                                 &slot->http_code);
+                               request = request_queue_head;
+                               while (request != NULL &&
+                                      request->slot != slot)
+                                       request = request->next;
+                       } else {
+                               fprintf(stderr, "Received DONE message for unknown request!\n");
+                       }
+                       if (request != NULL) {
+                               request->curl_result =
+                                       curl_message->data.result;
+                               request->http_code = slot->http_code;
+                               request->slot = NULL;
+                               request->state = COMPLETE;
+
+                               /* Use alternates if necessary */
+                               if (request->http_code == 404) {
+                                       fetch_alternates(alt->base);
+                                       if (request->repo->next != NULL) {
+                                               request->repo =
+                                                       request->repo->next;
+                                               start_request(request);
+                                       }
+                               } else {
+                                       finish_request(request);
+                               }
+                       }
+               } else {
+                       fprintf(stderr, "Unknown CURL message received: %d\n",
+                               (int)curl_message->msg);
+               }
+               curl_message = curl_multi_info_read(curlm, &num_messages);
+       }
+}
+
+void process_request_queue(void)
+{
+       struct transfer_request *request = request_queue_head;
+       struct active_request_slot *slot = active_queue_head;
+       int num_transfers;
+
+       while (active_requests < max_requests && request != NULL) {
+               if (request->state == WAITING) {
+                       if (has_sha1_file(request->sha1))
+                               release_request(request);
+                       else
+                               start_request(request);
+                       curl_multi_perform(curlm, &num_transfers);
+               }
+               request = request->next;
+       }
+
+       while (slot != NULL) {
+               if (!slot->in_use && slot->curl != NULL) {
+                       curl_easy_cleanup(slot->curl);
+                       slot->curl = NULL;
+               }
+               slot = slot->next;
+       }                               
+}
+#endif
+
+void prefetch(unsigned char *sha1)
+{
+       struct transfer_request *newreq;
+       struct transfer_request *tail;
+       char *filename = sha1_file_name(sha1);
+
+       newreq = xmalloc(sizeof(*newreq));
+       memcpy(newreq->sha1, sha1, 20);
+       newreq->repo = alt;
+       newreq->url = NULL;
+       newreq->local = -1;
+       newreq->state = WAITING;
+       snprintf(newreq->filename, sizeof(newreq->filename), "%s", filename);
+       snprintf(newreq->tmpfile, sizeof(newreq->tmpfile),
+                "%s.temp", filename);
+       newreq->next = NULL;
+
+       if (request_queue_head == NULL) {
+               request_queue_head = newreq;
+       } else {
+               tail = request_queue_head;
+               while (tail->next != NULL) {
+                       tail = tail->next;
+               }
+               tail->next = newreq;
+       }
+#ifdef USE_CURL_MULTI
+       process_request_queue();
+       process_curl_messages();
+#endif
+}
 
 static int fetch_index(struct alt_base *repo, unsigned char *sha1)
 {
+       char *hex = sha1_to_hex(sha1);
        char *filename;
        char *url;
        char tmpfile[PATH_MAX];
-       int ret;
        long prev_posn = 0;
        char range[RANGE_HEADER_SIZE];
        struct curl_slist *range_header = NULL;
-       CURLcode curl_result;
 
        FILE *indexfile;
+       struct active_request_slot *slot;
 
        if (has_pack_index(sha1))
                return 0;
 
        if (get_verbosely)
-               fprintf(stderr, "Getting index for pack %s\n",
-                       sha1_to_hex(sha1));
+               fprintf(stderr, "Getting index for pack %s\n", hex);
        
        url = xmalloc(strlen(repo->base) + 64);
-       sprintf(url, "%s/objects/pack/pack-%s.idx",
-               repo->base, sha1_to_hex(sha1));
+       sprintf(url, "%s/objects/pack/pack-%s.idx", repo->base, hex);
        
        filename = sha1_pack_index_name(sha1);
        snprintf(tmpfile, sizeof(tmpfile), "%s.temp", filename);
@@ -143,12 +708,13 @@ static int fetch_index(struct alt_base *repo, unsigned char *sha1)
                return error("Unable to open local file %s for pack index",
                             filename);
 
-       curl_easy_setopt(curl, CURLOPT_FILE, indexfile);
-       curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, fwrite);
-       curl_easy_setopt(curl, CURLOPT_URL, url);
-       curl_easy_setopt(curl, CURLOPT_HTTPHEADER, no_pragma_header);
-       curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, curl_errorstr);
-       
+       slot = get_active_slot();
+       curl_easy_setopt(slot->curl, CURLOPT_FILE, indexfile);
+       curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite);
+       curl_easy_setopt(slot->curl, CURLOPT_URL, url);
+       curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, no_pragma_header);
+       slot->local = indexfile;
+
        /* If there is data present from a previous transfer attempt,
           resume where it left off */
        prev_posn = ftell(indexfile);
@@ -156,30 +722,26 @@ static int fetch_index(struct alt_base *repo, unsigned char *sha1)
                if (get_verbosely)
                        fprintf(stderr,
                                "Resuming fetch of index for pack %s at byte %ld\n",
-                               sha1_to_hex(sha1), prev_posn);
+                               hex, prev_posn);
                sprintf(range, "Range: bytes=%ld-", prev_posn);
                range_header = curl_slist_append(range_header, range);
-               curl_easy_setopt(curl, CURLOPT_HTTPHEADER, range_header);
+               curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, range_header);
        }
 
-       /* Clear out the Range: header after performing the request, so
-          other curl requests don't inherit inappropriate header data */
-       curl_result = curl_easy_perform(curl);
-       curl_easy_setopt(curl, CURLOPT_HTTPHEADER, no_range_header);
-       if (curl_result != 0) {
-               fclose(indexfile);
-               return error("Unable to get pack index %s\n%s", url,
-                            curl_errorstr);
+       if (start_active_slot(slot)) {
+               run_active_slot(slot);
+               if (slot->curl_result != CURLE_OK) {
+                       fclose(indexfile);
+                       return error("Unable to get pack index %s\n%s", url,
+                                    curl_errorstr);
+               }
+       } else {
+               return error("Unable to start request");
        }
 
        fclose(indexfile);
 
-       ret = relink_or_rename(tmpfile, filename);
-       if (ret)
-               return error("unable to write index filename %s: %s",
-                            filename, strerror(ret));
-
-       return 0;
+       return move_temp_to_file(tmpfile, filename);
 }
 
 static int setup_index(struct alt_base *repo, unsigned char *sha1)
@@ -205,10 +767,16 @@ static int fetch_alternates(char *base)
        char *data;
        int i = 0;
        int http_specific = 1;
+       struct alt_base *tail = alt;
+       static const char null_byte = '\0';
+
+       struct active_request_slot *slot;
+
        if (got_alternates)
                return 0;
+
        data = xmalloc(4096);
-       buffer.size = 4095;
+       buffer.size = 4096;
        buffer.posn = 0;
        buffer.buffer = data;
 
@@ -218,25 +786,41 @@ static int fetch_alternates(char *base)
        url = xmalloc(strlen(base) + 31);
        sprintf(url, "%s/objects/info/http-alternates", base);
 
-       curl_easy_setopt(curl, CURLOPT_FILE, &buffer);
-       curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, fwrite_buffer);
-       curl_easy_setopt(curl, CURLOPT_URL, url);
-
-       if (curl_easy_perform(curl) || !buffer.posn) {
-               http_specific = 0;
-
-               sprintf(url, "%s/objects/info/alternates", base);
-               
-               curl_easy_setopt(curl, CURLOPT_FILE, &buffer);
-               curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, fwrite_buffer);
-               curl_easy_setopt(curl, CURLOPT_URL, url);
-               
-               if (curl_easy_perform(curl)) {
-                       return 0;
+       slot = get_active_slot();
+       curl_easy_setopt(slot->curl, CURLOPT_FILE, &buffer);
+       curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION,
+                        fwrite_buffer_dynamic);
+       curl_easy_setopt(slot->curl, CURLOPT_URL, url);
+       if (start_active_slot(slot)) {
+               run_active_slot(slot);
+               if (slot->curl_result != CURLE_OK || !buffer.posn) {
+                       http_specific = 0;
+
+                       sprintf(url, "%s/objects/info/alternates", base);
+
+                       slot = get_active_slot();
+                       curl_easy_setopt(slot->curl, CURLOPT_FILE, &buffer);
+                       curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION,
+                                        fwrite_buffer_dynamic);
+                       curl_easy_setopt(slot->curl, CURLOPT_URL, url);
+                       if (start_active_slot(slot)) {
+                               run_active_slot(slot);
+                               if (slot->curl_result != CURLE_OK) {
+                                       free(buffer.buffer);
+                                       if (slot->http_code == 404)
+                                               got_alternates = 1;
+                                       return 0;
+                               }
+                       }
                }
+       } else {
+               free(buffer.buffer);
+               return 0;
        }
 
-       data[buffer.posn] = '\0';
+       fwrite_buffer_dynamic(&null_byte, 1, 1, &buffer);
+       buffer.posn--;
+       data = buffer.buffer;
 
        while (i < buffer.posn) {
                int posn = i;
@@ -283,18 +867,21 @@ static int fetch_alternates(char *base)
                                        fprintf(stderr, 
                                                "Also look at %s\n", target);
                                newalt = xmalloc(sizeof(*newalt));
-                               newalt->next = alt;
+                               newalt->next = NULL;
                                newalt->base = target;
                                newalt->got_indices = 0;
                                newalt->packs = NULL;
-                               alt = newalt;
+                               while (tail->next != NULL)
+                                       tail = tail->next;
+                               tail->next = newalt;
                                ret++;
                        }
                }
                i = posn + 1;
        }
+
        got_alternates = 1;
-       
+       free(buffer.buffer);
        return ret;
 }
 
@@ -306,6 +893,8 @@ static int fetch_indices(struct alt_base *repo)
        char *data;
        int i = 0;
 
+       struct active_request_slot *slot;
+
        if (repo->got_indices)
                return 0;
 
@@ -320,15 +909,24 @@ static int fetch_indices(struct alt_base *repo)
        url = xmalloc(strlen(repo->base) + 21);
        sprintf(url, "%s/objects/info/packs", repo->base);
 
-       curl_easy_setopt(curl, CURLOPT_FILE, &buffer);
-       curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, fwrite_buffer);
-       curl_easy_setopt(curl, CURLOPT_URL, url);
-       curl_easy_setopt(curl, CURLOPT_HTTPHEADER, NULL);
-       curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, curl_errorstr);
-       
-       if (curl_easy_perform(curl))
-               return error("%s", curl_errorstr);
+       slot = get_active_slot();
+       curl_easy_setopt(slot->curl, CURLOPT_FILE, &buffer);
+       curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION,
+                        fwrite_buffer_dynamic);
+       curl_easy_setopt(slot->curl, CURLOPT_URL, url);
+       curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, NULL);
+       if (start_active_slot(slot)) {
+               run_active_slot(slot);
+               if (slot->curl_result != CURLE_OK) {
+                       free(buffer.buffer);
+                       return error("%s", curl_errorstr);
+               }
+       } else {
+               free(buffer.buffer);
+               return error("Unable to start request");
+       }
 
+       data = buffer.buffer;
        while (i < buffer.posn) {
                switch (data[i]) {
                case 'P':
@@ -348,6 +946,7 @@ static int fetch_indices(struct alt_base *repo)
                i++;
        }
 
+       free(buffer.buffer);
        repo->got_indices = 1;
        return 0;
 }
@@ -364,7 +963,8 @@ static int fetch_pack(struct alt_base *repo, unsigned char *sha1)
        long prev_posn = 0;
        char range[RANGE_HEADER_SIZE];
        struct curl_slist *range_header = NULL;
-       CURLcode curl_result;
+
+       struct active_request_slot *slot;
 
        if (fetch_indices(repo))
                return -1;
@@ -390,11 +990,12 @@ static int fetch_pack(struct alt_base *repo, unsigned char *sha1)
                return error("Unable to open local file %s for pack",
                             filename);
 
-       curl_easy_setopt(curl, CURLOPT_FILE, packfile);
-       curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, fwrite);
-       curl_easy_setopt(curl, CURLOPT_URL, url);
-       curl_easy_setopt(curl, CURLOPT_HTTPHEADER, no_pragma_header);
-       curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, curl_errorstr);
+       slot = get_active_slot();
+       curl_easy_setopt(slot->curl, CURLOPT_FILE, packfile);
+       curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite);
+       curl_easy_setopt(slot->curl, CURLOPT_URL, url);
+       curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, no_pragma_header);
+       slot->local = packfile;
 
        /* If there is data present from a previous transfer attempt,
           resume where it left off */
@@ -406,25 +1007,25 @@ static int fetch_pack(struct alt_base *repo, unsigned char *sha1)
                                sha1_to_hex(target->sha1), prev_posn);
                sprintf(range, "Range: bytes=%ld-", prev_posn);
                range_header = curl_slist_append(range_header, range);
-               curl_easy_setopt(curl, CURLOPT_HTTPHEADER, range_header);
+               curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, range_header);
        }
 
-       /* Clear out the Range: header after performing the request, so
-          other curl requests don't inherit inappropriate header data */
-       curl_result = curl_easy_perform(curl);
-       curl_easy_setopt(curl, CURLOPT_HTTPHEADER, no_range_header);
-       if (curl_result != 0) {
-               fclose(packfile);
-               return error("Unable to get pack file %s\n%s", url,
-                            curl_errorstr);
+       if (start_active_slot(slot)) {
+               run_active_slot(slot);
+               if (slot->curl_result != CURLE_OK) {
+                       fclose(packfile);
+                       return error("Unable to get pack file %s\n%s", url,
+                                    curl_errorstr);
+               }
+       } else {
+               return error("Unable to start request");
        }
 
        fclose(packfile);
 
-       ret = relink_or_rename(tmpfile, filename);
+       ret = move_temp_to_file(tmpfile, filename);
        if (ret)
-               return error("unable to write pack filename %s: %s",
-                            filename, strerror(ret));
+               return ret;
 
        lst = &repo->packs;
        while (*lst != target)
@@ -441,185 +1042,179 @@ static int fetch_pack(struct alt_base *repo, unsigned char *sha1)
 static int fetch_object(struct alt_base *repo, unsigned char *sha1)
 {
        char *hex = sha1_to_hex(sha1);
-       char *filename = sha1_file_name(sha1);
-       unsigned char real_sha1[20];
-       char tmpfile[PATH_MAX];
-       char prevfile[PATH_MAX];
        int ret;
-       char *url;
-       char *posn;
-       int prevlocal;
-       unsigned char prev_buf[PREV_BUF_SIZE];
-       ssize_t prev_read = 0;
-       long prev_posn = 0;
-       char range[RANGE_HEADER_SIZE];
-       struct curl_slist *range_header = NULL;
-       CURLcode curl_result;
-
-       snprintf(tmpfile, sizeof(tmpfile), "%s.temp", filename);
-       snprintf(prevfile, sizeof(prevfile), "%s.prev", filename);
-
-       if (unlink(prevfile) && (errno != ENOENT))
-               return error("Failed to unlink %s (%s)",
-                            prevfile, strerror(errno));
-       if (rename(tmpfile, prevfile) && (errno != ENOENT))
-               return error("Failed to rename %s to %s (%s)",
-                            tmpfile, prevfile, strerror(errno));
-
-       local = open(tmpfile, O_WRONLY | O_CREAT | O_EXCL, 0666);
-
-       /* Note: if another instance starts now, it will turn our new
-          tmpfile into its prevfile. */
+       struct transfer_request *request = request_queue_head;
 
-       if (local < 0)
-               return error("Couldn't create temporary file %s for %s: %s\n",
-                            tmpfile, filename, strerror(errno));
-
-       memset(&stream, 0, sizeof(stream));
-
-       inflateInit(&stream);
-
-       SHA1_Init(&c);
-
-       curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1);
-       curl_easy_setopt(curl, CURLOPT_FILE, NULL);
-       curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, fwrite_sha1_file);
-       curl_easy_setopt(curl, CURLOPT_HTTPHEADER, no_pragma_header);
-       curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, curl_errorstr);
-
-       url = xmalloc(strlen(repo->base) + 50);
-       strcpy(url, repo->base);
-       posn = url + strlen(repo->base);
-       strcpy(posn, "objects/");
-       posn += 8;
-       memcpy(posn, hex, 2);
-       posn += 2;
-       *(posn++) = '/';
-       strcpy(posn, hex + 2);
+       while (request != NULL && memcmp(request->sha1, sha1, 20))
+               request = request->next;
+       if (request == NULL)
+               return error("Couldn't find request for %s in the queue", hex);
 
-       curl_easy_setopt(curl, CURLOPT_URL, url);
+       if (has_sha1_file(request->sha1)) {
+               release_request(request);
+               return 0;
+       }
 
-       /* If a previous temp file is present, process what was already
-          fetched. */
-       prevlocal = open(prevfile, O_RDONLY);
-       if (prevlocal != -1) {
-               do {
-                       prev_read = read(prevlocal, prev_buf, PREV_BUF_SIZE);
-                       if (prev_read>0) {
-                               if (fwrite_sha1_file(prev_buf,
-                                                    1,
-                                                    prev_read,
-                                                    NULL) == prev_read) {
-                                       prev_posn += prev_read;
-                               } else {
-                                       prev_read = -1;
-                               }
-                       }
-               } while (prev_read > 0);
-               close(prevlocal);
+#ifdef USE_CURL_MULTI
+       while (request->state == WAITING) {
+               int num_transfers;
+               curl_multi_perform(curlm, &num_transfers);
+               if (num_transfers < active_requests) {
+                       process_curl_messages();
+                       process_request_queue();
+               }
        }
-       unlink(prevfile);
+#else
+       start_request(request);
+#endif
 
-       /* Reset inflate/SHA1 if there was an error reading the previous temp
-          file; also rewind to the beginning of the local file. */
-       if (prev_read == -1) {
-               memset(&stream, 0, sizeof(stream));
-               inflateInit(&stream);
-               SHA1_Init(&c);
-               if (prev_posn>0) {
-                       prev_posn = 0;
-                       lseek(local, SEEK_SET, 0);
-                       ftruncate(local, 0);
+       while (request->state == ACTIVE) {
+               run_active_slot(request->slot);
+#ifndef USE_CURL_MULTI
+               request->curl_result = request->slot->curl_result;
+               request->http_code = request->slot->http_code;
+               request->slot = NULL;
+
+               /* Use alternates if necessary */
+               if (request->http_code == 404) {
+                       fetch_alternates(alt->base);
+                       if (request->repo->next != NULL) {
+                               request->repo = request->repo->next;
+                               start_request(request);
+                       }
+               } else {
+                       finish_request(request);
+                       request->state = COMPLETE;
                }
+#endif
        }
 
-       /* If we have successfully processed data from a previous fetch
-          attempt, only fetch the data we don't already have. */
-       if (prev_posn>0) {
-               if (get_verbosely)
-                       fprintf(stderr,
-                               "Resuming fetch of object %s at byte %ld\n",
-                               hex, prev_posn);
-               sprintf(range, "Range: bytes=%ld-", prev_posn);
-               range_header = curl_slist_append(range_header, range);
-               curl_easy_setopt(curl, CURLOPT_HTTPHEADER, range_header);
+       if (request->state == ABORTED) {
+               release_request(request);
+               return error("Request for %s aborted", hex);
        }
 
-       /* Clear out the Range: header after performing the request, so
-          other curl requests don't inherit inappropriate header data */
-       curl_result = curl_easy_perform(curl);
-       curl_easy_setopt(curl, CURLOPT_HTTPHEADER, no_range_header);
-       if (curl_result != 0) {
-               return error("%s", curl_errorstr);
+       if (request->curl_result != CURLE_OK && request->http_code != 416) {
+               if (request->http_code == 404)
+                       ret = -1; /* Be silent, it is probably in a pack. */
+               else
+                       ret = error("%s (curl_result = %d, http_code = %ld, sha1 = %s)",
+                                   request->errorstr, request->curl_result,
+                                   request->http_code, hex);
+               release_request(request);
+               return ret;
        }
 
-       fchmod(local, 0444);
-       close(local);
-       inflateEnd(&stream);
-       SHA1_Final(real_sha1, &c);
-       if (zret != Z_STREAM_END) {
-               unlink(tmpfile);
-               return error("File %s (%s) corrupt\n", hex, url);
+       if (request->zret != Z_STREAM_END) {
+               ret = error("File %s (%s) corrupt\n", hex, request->url);
+               release_request(request);
+               return ret;
        }
-       if (memcmp(sha1, real_sha1, 20)) {
-               unlink(tmpfile);
+
+       if (memcmp(request->sha1, request->real_sha1, 20)) {
+               release_request(request);
                return error("File %s has bad hash\n", hex);
        }
-       ret = relink_or_rename(tmpfile, filename);
-       if (ret)
-               return error("unable to write sha1 filename %s: %s",
-                            filename, strerror(ret));
 
-       pull_say("got %s\n", hex);
+       if (request->rename < 0) {
+               ret = error("unable to write sha1 filename %s: %s",
+                           request->filename,
+                           strerror(request->rename));
+               release_request(request);
+               return ret;
+       }
+
+       release_request(request);
        return 0;
 }
 
 int fetch(unsigned char *sha1)
 {
        struct alt_base *altbase = alt;
+
+       if (!fetch_object(altbase, sha1))
+               return 0;
        while (altbase) {
-               if (!fetch_object(altbase, sha1))
-                       return 0;
                if (!fetch_pack(altbase, sha1))
                        return 0;
-               if (fetch_alternates(altbase->base) > 0) {
-                       altbase = alt;
-                       continue;
-               }
+               fetch_alternates(alt->base);
                altbase = altbase->next;
        }
        return error("Unable to find %s under %s\n", sha1_to_hex(sha1), 
-                    initial_base);
+                    alt->base);
+}
+
+static inline int needs_quote(int ch)
+{
+       switch (ch) {
+       case '/': case '-': case '.':
+       case 'A'...'Z': case 'a'...'z': case '0'...'9':
+               return 0;
+       default:
+               return 1;
+       }
+}
+
+static inline int hex(int v)
+{
+       if (v < 10) return '0' + v;
+       else return 'A' + v - 10;
+}
+
+static char *quote_ref_url(const char *base, const char *ref)
+{
+       const char *cp;
+       char *dp, *qref;
+       int len, baselen, ch;
+
+       baselen = strlen(base);
+       len = baselen + 6; /* "refs/" + NUL */
+       for (cp = ref; (ch = *cp) != 0; cp++, len++)
+               if (needs_quote(ch))
+                       len += 2; /* extra two hex plus replacement % */
+       qref = xmalloc(len);
+       memcpy(qref, base, baselen);
+       memcpy(qref + baselen, "refs/", 5);
+       for (cp = ref, dp = qref + baselen + 5; (ch = *cp) != 0; cp++) {
+               if (needs_quote(ch)) {
+                       *dp++ = '%';
+                       *dp++ = hex((ch >> 4) & 0xF);
+                       *dp++ = hex(ch & 0xF);
+               }
+               else
+                       *dp++ = ch;
+       }
+       *dp = 0;
+
+       return qref;
 }
 
 int fetch_ref(char *ref, unsigned char *sha1)
 {
-        char *url, *posn;
+        char *url;
         char hex[42];
         struct buffer buffer;
-       char *base = initial_base;
+       char *base = alt->base;
+       struct active_request_slot *slot;
         buffer.size = 41;
         buffer.posn = 0;
         buffer.buffer = hex;
         hex[41] = '\0';
         
-        curl_easy_setopt(curl, CURLOPT_FILE, &buffer);
-        curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, fwrite_buffer);
-       curl_easy_setopt(curl, CURLOPT_HTTPHEADER, NULL);
-       curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, curl_errorstr);
-
-        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%s",
-                            url, ref, curl_errorstr);
+       url = quote_ref_url(base, ref);
+       slot = get_active_slot();
+       curl_easy_setopt(slot->curl, CURLOPT_FILE, &buffer);
+       curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_buffer);
+       curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, NULL);
+       curl_easy_setopt(slot->curl, CURLOPT_URL, url);
+       if (start_active_slot(slot)) {
+               run_active_slot(slot);
+               if (slot->curl_result != CURLE_OK)
+                       return error("Couldn't get %s for %s\n%s",
+                                    url, ref, curl_errorstr);
+       } else {
+               return error("Unable to start request");
+       }
 
         hex[40] = '\0';
         get_sha1_hex(hex, sha1);
@@ -631,6 +1226,11 @@ int main(int argc, char **argv)
        char *commit_id;
        char *url;
        int arg = 1;
+       struct active_request_slot *slot;
+       char *low_speed_limit;
+       char *low_speed_time;
+       char *wait_url;
+       int rc = 0;
 
        while (arg < argc && argv[arg][0] == '-') {
                if (argv[arg][1] == 't') {
@@ -660,44 +1260,90 @@ int main(int argc, char **argv)
 
        curl_global_init(CURL_GLOBAL_ALL);
 
-       curl = curl_easy_init();
-       no_pragma_header = curl_slist_append(no_pragma_header, "Pragma:");
-       no_range_header = curl_slist_append(no_range_header, "Range:");
+#ifdef USE_CURL_MULTI
+       {
+               char *http_max_requests = getenv("GIT_HTTP_MAX_REQUESTS");
+               if (http_max_requests != NULL)
+                       max_requests = atoi(http_max_requests);
+       }
 
-       curl_ssl_verify = getenv("GIT_SSL_NO_VERIFY") ? 0 : 1;
-       curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, curl_ssl_verify);
-#if LIBCURL_VERSION_NUM >= 0x070907
-       curl_easy_setopt(curl, CURLOPT_NETRC, CURL_NETRC_OPTIONAL);
+       curlm = curl_multi_init();
+       if (curlm == NULL) {
+               fprintf(stderr, "Error creating curl multi handle.\n");
+               return 1;
+       }
 #endif
 
-       if ((ssl_cert = getenv("GIT_SSL_CERT")) != NULL) {
-               curl_easy_setopt(curl, CURLOPT_SSLCERT, ssl_cert);
-       }
+       if (getenv("GIT_SSL_NO_VERIFY"))
+               curl_ssl_verify = 0;
+
+       ssl_cert = getenv("GIT_SSL_CERT");
 #if LIBCURL_VERSION_NUM >= 0x070902
-       if ((ssl_key = getenv("GIT_SSL_KEY")) != NULL) {
-               curl_easy_setopt(curl, CURLOPT_SSLKEY, ssl_key);
-       }
+       ssl_key = getenv("GIT_SSL_KEY");
 #endif
 #if LIBCURL_VERSION_NUM >= 0x070908
-       if ((ssl_capath = getenv("GIT_SSL_CAPATH")) != NULL) {
-               curl_easy_setopt(curl, CURLOPT_CAPATH, ssl_capath);
-       }
+       ssl_capath = getenv("GIT_SSL_CAPATH");
+#endif
+       ssl_cainfo = getenv("GIT_SSL_CAINFO");
+
+       low_speed_limit = getenv("GIT_HTTP_LOW_SPEED_LIMIT");
+       if (low_speed_limit != NULL)
+               curl_low_speed_limit = strtol(low_speed_limit, NULL, 10);
+       low_speed_time = getenv("GIT_HTTP_LOW_SPEED_TIME");
+       if (low_speed_time != NULL)
+               curl_low_speed_time = strtol(low_speed_time, NULL, 10);
+
+       git_config(http_options);
+
+       if (curl_ssl_verify == -1)
+               curl_ssl_verify = 1;
+
+#ifdef USE_CURL_MULTI
+       if (max_requests < 1)
+               max_requests = DEFAULT_MAX_REQUESTS;
+#endif
+
+       pragma_header = curl_slist_append(pragma_header, "Pragma: no-cache");
+       no_pragma_header = curl_slist_append(no_pragma_header, "Pragma:");
+       no_range_header = curl_slist_append(no_range_header, "Range:");
+
+#ifndef NO_CURL_EASY_DUPHANDLE
+       curl_default = get_curl_handle();
 #endif
-       if ((ssl_cainfo = getenv("GIT_SSL_CAINFO")) != NULL) {
-               curl_easy_setopt(curl, CURLOPT_CAINFO, ssl_cainfo);
-       }
 
        alt = xmalloc(sizeof(*alt));
        alt->base = url;
        alt->got_indices = 0;
        alt->packs = NULL;
        alt->next = NULL;
-       initial_base = url;
 
        if (pull(commit_id))
-               return 1;
+               rc = 1;
 
+       curl_slist_free_all(pragma_header);
        curl_slist_free_all(no_pragma_header);
+       curl_slist_free_all(no_range_header);
+#ifndef NO_CURL_EASY_DUPHANDLE
+       curl_easy_cleanup(curl_default);
+#endif
+       slot = active_queue_head;
+       while (slot != NULL) {
+               if (slot->in_use) {
+                       if (get_verbosely) {
+                               curl_easy_getinfo(slot->curl,
+                                                 CURLINFO_EFFECTIVE_URL,
+                                                 &wait_url);
+                               fprintf(stderr, "Waiting for %s\n", wait_url);
+                       }
+                       run_active_slot(slot);
+               }
+               if (slot->curl != NULL)
+                       curl_easy_cleanup(slot->curl);
+               slot = slot->next;
+       }
+#ifdef USE_CURL_MULTI
+       curl_multi_cleanup(curlm);
+#endif
        curl_global_cleanup();
-       return 0;
+       return rc;
 }
diff --git a/ident.c b/ident.c
index 562f5f18160166506e99aa4f6b5b417cbdc847d8..bc89e1d04c63563c051005754a50247f22256974 100644 (file)
--- a/ident.c
+++ b/ident.c
@@ -8,12 +8,9 @@
 #include "cache.h"
 
 #include <pwd.h>
-#include <time.h>
-#include <ctype.h>
+#include <netdb.h>
 
-static char real_email[1000];
-static char real_name[1000];
-static char real_date[50];
+static char git_default_date[50];
 
 static void copy_gecos(struct passwd *w, char *name, int sz)
 {
@@ -58,22 +55,29 @@ int setup_ident(void)
                die("You don't exist. Go away!");
 
        /* Get the name ("gecos") */
-       copy_gecos(pw, real_name, sizeof(real_name));
+       copy_gecos(pw, git_default_name, sizeof(git_default_name));
 
        /* Make up a fake email address (name + '@' + hostname [+ '.' + domainname]) */
        len = strlen(pw->pw_name);
-       if (len > sizeof(real_email)/2)
+       if (len > sizeof(git_default_email)/2)
                die("Your sysadmin must hate you!");
-       memcpy(real_email, pw->pw_name, len);
-       real_email[len++] = '@';
-       gethostname(real_email + len, sizeof(real_email) - len);
-       if (!strchr(real_email+len, '.')) {
-               len = strlen(real_email);
-               real_email[len++] = '.';
-               getdomainname(real_email+len, sizeof(real_email)-len);
+       memcpy(git_default_email, pw->pw_name, len);
+       git_default_email[len++] = '@';
+       gethostname(git_default_email + len, sizeof(git_default_email) - len);
+       if (!strchr(git_default_email+len, '.')) {
+               struct hostent *he = gethostbyname(git_default_email + len);
+               char *domainname;
+
+               len = strlen(git_default_email);
+               git_default_email[len++] = '.';
+               if (he && (domainname = strchr(he->h_name, '.')))
+                       strncpy(git_default_email + len, domainname + 1, sizeof(git_default_email) - len);
+               else
+                       strncpy(git_default_email + len, "(none)", sizeof(git_default_email) - len);
+               git_default_email[sizeof(git_default_email) - 1] = 0;
        }
        /* And set the default date */
-       datestamp(real_date, sizeof(real_date));
+       datestamp(git_default_date, sizeof(git_default_date));
        return 0;
 }
 
@@ -159,10 +163,10 @@ char *get_ident(const char *name, const char *email, const char *date_str)
        int i;
 
        if (!name)
-               name = real_name;
+               name = git_default_name;
        if (!email)
-               email = real_email;
-       strcpy(date, real_date);
+               email = git_default_email;
+       strcpy(date, git_default_date);
        if (date_str)
                parse_date(date_str, date, sizeof(date));
 
diff --git a/index-pack.c b/index-pack.c
new file mode 100644 (file)
index 0000000..785fe71
--- /dev/null
@@ -0,0 +1,462 @@
+#include "cache.h"
+#include "delta.h"
+#include "pack.h"
+#include "csum-file.h"
+
+static const char index_pack_usage[] =
+"git-index-pack [-o index-file] pack-file";
+
+struct object_entry
+{
+       unsigned long offset;
+       enum object_type type;
+       enum object_type real_type;
+       unsigned char sha1[20];
+};
+
+struct delta_entry
+{
+       struct object_entry *obj;
+       unsigned char base_sha1[20];
+};
+
+static const char *pack_name;
+static unsigned char *pack_base;
+static unsigned long pack_size;
+static struct object_entry *objects;
+static struct delta_entry *deltas;
+static int nr_objects;
+static int nr_deltas;
+
+static void open_pack_file(void)
+{
+       int fd;
+       struct stat st;
+
+       fd = open(pack_name, O_RDONLY);
+       if (fd < 0)
+               die("cannot open packfile '%s': %s", pack_name,
+                   strerror(errno));
+       if (fstat(fd, &st)) {
+               int err = errno;
+               close(fd);
+               die("cannot fstat packfile '%s': %s", pack_name,
+                   strerror(err));
+       }
+       pack_size = st.st_size;
+       pack_base = mmap(NULL, pack_size, PROT_READ, MAP_PRIVATE, fd, 0);
+       if (pack_base == MAP_FAILED) {
+               int err = errno;
+               close(fd);
+               die("cannot mmap packfile '%s': %s", pack_name,
+                   strerror(err));
+       }
+       close(fd);
+}
+
+static void parse_pack_header(void)
+{
+       const struct pack_header *hdr;
+       unsigned char sha1[20];
+       SHA_CTX ctx;
+
+       /* Ensure there are enough bytes for the header and final SHA1 */
+       if (pack_size < sizeof(struct pack_header) + 20)
+               die("packfile '%s' is too small", pack_name);
+
+       /* Header consistency check */
+       hdr = (void *)pack_base;
+       if (hdr->hdr_signature != htonl(PACK_SIGNATURE))
+               die("packfile '%s' signature mismatch", pack_name);
+       if (hdr->hdr_version != htonl(PACK_VERSION))
+               die("packfile '%s' version %d different from ours %d",
+                   pack_name, ntohl(hdr->hdr_version), PACK_VERSION);
+
+       nr_objects = ntohl(hdr->hdr_entries);
+
+       /* Check packfile integrity */
+       SHA1_Init(&ctx);
+       SHA1_Update(&ctx, pack_base, pack_size - 20);
+       SHA1_Final(sha1, &ctx);
+       if (memcmp(sha1, pack_base + pack_size - 20, 20))
+               die("packfile '%s' SHA1 mismatch", pack_name);
+}
+
+static void bad_object(unsigned long offset, const char *format,
+                      ...) NORETURN __attribute__((format (printf, 2, 3)));
+
+static void bad_object(unsigned long offset, const char *format, ...)
+{
+       va_list params;
+       char buf[1024];
+
+       va_start(params, format);
+       vsnprintf(buf, sizeof(buf), format, params);
+       va_end(params);
+       die("packfile '%s': bad object at offset %lu: %s",
+           pack_name, offset, buf);
+}
+
+static void *unpack_entry_data(unsigned long offset,
+                              unsigned long *current_pos, unsigned long size)
+{
+       unsigned long pack_limit = pack_size - 20;
+       unsigned long pos = *current_pos;
+       z_stream stream;
+       void *buf = xmalloc(size);
+
+       memset(&stream, 0, sizeof(stream));
+       stream.next_out = buf;
+       stream.avail_out = size;
+       stream.next_in = pack_base + pos;
+       stream.avail_in = pack_limit - pos;
+       inflateInit(&stream);
+
+       for (;;) {
+               int ret = inflate(&stream, 0);
+               if (ret == Z_STREAM_END)
+                       break;
+               if (ret != Z_OK)
+                       bad_object(offset, "inflate returned %d", ret);
+       }
+       inflateEnd(&stream);
+       if (stream.total_out != size)
+               bad_object(offset, "size mismatch (expected %lu, got %lu)",
+                          size, stream.total_out);
+       *current_pos = pack_limit - stream.avail_in;
+       return buf;
+}
+
+static void *unpack_raw_entry(unsigned long offset,
+                             enum object_type *obj_type,
+                             unsigned long *obj_size,
+                             unsigned char *delta_base,
+                             unsigned long *next_obj_offset)
+{
+       unsigned long pack_limit = pack_size - 20;
+       unsigned long pos = offset;
+       unsigned char c;
+       unsigned long size;
+       unsigned shift;
+       enum object_type type;
+       void *data;
+
+       c = pack_base[pos++];
+       type = (c >> 4) & 7;
+       size = (c & 15);
+       shift = 4;
+       while (c & 0x80) {
+               if (pos >= pack_limit)
+                       bad_object(offset, "object extends past end of pack");
+               c = pack_base[pos++];
+               size += (c & 0x7fUL) << shift;
+               shift += 7;
+       }
+
+       switch (type) {
+       case OBJ_DELTA:
+               if (pos + 20 >= pack_limit)
+                       bad_object(offset, "object extends past end of pack");
+               memcpy(delta_base, pack_base + pos, 20);
+               pos += 20;
+               /* fallthru */
+       case OBJ_COMMIT:
+       case OBJ_TREE:
+       case OBJ_BLOB:
+       case OBJ_TAG:
+               data = unpack_entry_data(offset, &pos, size);
+               break;
+       default:
+               bad_object(offset, "bad object type %d", type);
+       }
+
+       *obj_type = type;
+       *obj_size = size;
+       *next_obj_offset = pos;
+       return data;
+}
+
+static int find_delta(const unsigned char *base_sha1)
+{
+       int first = 0, last = nr_deltas;
+
+        while (first < last) {
+                int next = (first + last) / 2;
+                struct delta_entry *delta = &deltas[next];
+                int cmp;
+
+                cmp = memcmp(base_sha1, delta->base_sha1, 20);
+                if (!cmp)
+                        return next;
+                if (cmp < 0) {
+                        last = next;
+                        continue;
+                }
+                first = next+1;
+        }
+        return -first-1;
+}
+
+static int find_deltas_based_on_sha1(const unsigned char *base_sha1,
+                                    int *first_index, int *last_index)
+{
+       int first = find_delta(base_sha1);
+       int last = first;
+       int end = nr_deltas - 1;
+
+       if (first < 0)
+               return -1;
+       while (first > 0 && !memcmp(deltas[first-1].base_sha1, base_sha1, 20))
+               --first;
+       while (last < end && !memcmp(deltas[last+1].base_sha1, base_sha1, 20))
+               ++last;
+       *first_index = first;
+       *last_index = last;
+       return 0;
+}
+
+static void sha1_object(const void *data, unsigned long size,
+                       enum object_type type, unsigned char *sha1)
+{
+       SHA_CTX ctx;
+       char header[50];
+       int header_size;
+       const char *type_str;
+
+       switch (type) {
+       case OBJ_COMMIT: type_str = "commit"; break;
+       case OBJ_TREE:   type_str = "tree"; break;
+       case OBJ_BLOB:   type_str = "blob"; break;
+       case OBJ_TAG:    type_str = "tag"; break;
+       default:
+               die("bad type %d", type);
+       }
+
+       header_size = sprintf(header, "%s %lu", type_str, size) + 1;
+
+       SHA1_Init(&ctx);
+       SHA1_Update(&ctx, header, header_size);
+       SHA1_Update(&ctx, data, size);
+       SHA1_Final(sha1, &ctx);
+}
+
+static void resolve_delta(struct delta_entry *delta, void *base_data,
+                         unsigned long base_size, enum object_type type)
+{
+       struct object_entry *obj = delta->obj;
+       void *delta_data;
+       unsigned long delta_size;
+       void *result;
+       unsigned long result_size;
+       enum object_type delta_type;
+       unsigned char base_sha1[20];
+       unsigned long next_obj_offset;
+       int j, first, last;
+
+       obj->real_type = type;
+       delta_data = unpack_raw_entry(obj->offset, &delta_type,
+                                     &delta_size, base_sha1,
+                                     &next_obj_offset);
+       result = patch_delta(base_data, base_size, delta_data, delta_size,
+                            &result_size);
+       free(delta_data);
+       if (!result)
+               bad_object(obj->offset, "failed to apply delta");
+       sha1_object(result, result_size, type, obj->sha1);
+       if (!find_deltas_based_on_sha1(obj->sha1, &first, &last)) {
+               for (j = first; j <= last; j++)
+                       resolve_delta(&deltas[j], result, result_size, type);
+       }
+       free(result);
+}
+
+static int compare_delta_entry(const void *a, const void *b)
+{
+       const struct delta_entry *delta_a = a;
+       const struct delta_entry *delta_b = b;
+       return memcmp(delta_a->base_sha1, delta_b->base_sha1, 20);
+}
+
+static void parse_pack_objects(void)
+{
+       int i;
+       unsigned long offset = sizeof(struct pack_header);
+       unsigned char base_sha1[20];
+       void *data;
+       unsigned long data_size;
+
+       /*
+        * First pass:
+        * - find locations of all objects;
+        * - calculate SHA1 of all non-delta objects;
+        * - remember base SHA1 for all deltas.
+        */
+       for (i = 0; i < nr_objects; i++) {
+               struct object_entry *obj = &objects[i];
+               obj->offset = offset;
+               data = unpack_raw_entry(offset, &obj->type, &data_size,
+                                       base_sha1, &offset);
+               obj->real_type = obj->type;
+               if (obj->type == OBJ_DELTA) {
+                       struct delta_entry *delta = &deltas[nr_deltas++];
+                       delta->obj = obj;
+                       memcpy(delta->base_sha1, base_sha1, 20);
+               } else
+                       sha1_object(data, data_size, obj->type, obj->sha1);
+               free(data);
+       }
+       if (offset != pack_size - 20)
+               die("packfile '%s' has junk at the end", pack_name);
+
+       /* Sort deltas by base SHA1 for fast searching */
+       qsort(deltas, nr_deltas, sizeof(struct delta_entry),
+             compare_delta_entry);
+
+       /*
+        * Second pass:
+        * - for all non-delta objects, look if it is used as a base for
+        *   deltas;
+        * - if used as a base, uncompress the object and apply all deltas,
+        *   recursively checking if the resulting object is used as a base
+        *   for some more deltas.
+        */
+       for (i = 0; i < nr_objects; i++) {
+               struct object_entry *obj = &objects[i];
+               int j, first, last;
+
+               if (obj->type == OBJ_DELTA)
+                       continue;
+               if (find_deltas_based_on_sha1(obj->sha1, &first, &last))
+                       continue;
+               data = unpack_raw_entry(obj->offset, &obj->type, &data_size,
+                                       base_sha1, &offset);
+               for (j = first; j <= last; j++)
+                       resolve_delta(&deltas[j], data, data_size, obj->type);
+               free(data);
+       }
+
+       /* Check for unresolved deltas */
+       for (i = 0; i < nr_deltas; i++) {
+               if (deltas[i].obj->real_type == OBJ_DELTA)
+                       die("packfile '%s' has unresolved deltas",  pack_name);
+       }
+}
+
+static int sha1_compare(const void *_a, const void *_b)
+{
+       struct object_entry *a = *(struct object_entry **)_a;
+       struct object_entry *b = *(struct object_entry **)_b;
+       return memcmp(a->sha1, b->sha1, 20);
+}
+
+static void write_index_file(const char *index_name, unsigned char *sha1)
+{
+       struct sha1file *f;
+       struct object_entry **sorted_by_sha =
+               xcalloc(nr_objects, sizeof(struct object_entry *));
+       struct object_entry **list = sorted_by_sha;
+       struct object_entry **last = sorted_by_sha + nr_objects;
+       unsigned int array[256];
+       int i;
+       SHA_CTX ctx;
+
+       for (i = 0; i < nr_objects; ++i)
+               sorted_by_sha[i] = &objects[i];
+       qsort(sorted_by_sha, nr_objects, sizeof(sorted_by_sha[0]),
+             sha1_compare);
+
+       unlink(index_name);
+       f = sha1create("%s", index_name);
+
+       /*
+        * Write the first-level table (the list is sorted,
+        * but we use a 256-entry lookup to be able to avoid
+        * having to do eight extra binary search iterations).
+        */
+       for (i = 0; i < 256; i++) {
+               struct object_entry **next = list;
+               while (next < last) {
+                       struct object_entry *obj = *next;
+                       if (obj->sha1[0] != i)
+                               break;
+                       next++;
+               }
+               array[i] = htonl(next - sorted_by_sha);
+               list = next;
+       }
+       sha1write(f, array, 256 * sizeof(int));
+
+       /* recompute the SHA1 hash of sorted object names.
+        * currently pack-objects does not do this, but that
+        * can be fixed.
+        */
+       SHA1_Init(&ctx);
+       /*
+        * Write the actual SHA1 entries..
+        */
+       list = sorted_by_sha;
+       for (i = 0; i < nr_objects; i++) {
+               struct object_entry *obj = *list++;
+               unsigned int offset = htonl(obj->offset);
+               sha1write(f, &offset, 4);
+               sha1write(f, obj->sha1, 20);
+               SHA1_Update(&ctx, obj->sha1, 20);
+       }
+       sha1write(f, pack_base + pack_size - 20, 20);
+       sha1close(f, NULL, 1);
+       free(sorted_by_sha);
+       SHA1_Final(sha1, &ctx);
+}
+
+int main(int argc, char **argv)
+{
+       int i;
+       char *index_name = NULL;
+       char *index_name_buf = NULL;
+       unsigned char sha1[20];
+
+       for (i = 1; i < argc; i++) {
+               const char *arg = argv[i];
+
+               if (*arg == '-') {
+                       if (!strcmp(arg, "-o")) {
+                               if (index_name || (i+1) >= argc)
+                                       usage(index_pack_usage);
+                               index_name = argv[++i];
+                       } else
+                               usage(index_pack_usage);
+                       continue;
+               }
+
+               if (pack_name)
+                       usage(index_pack_usage);
+               pack_name = arg;
+       }
+
+       if (!pack_name)
+               usage(index_pack_usage);
+       if (!index_name) {
+               int len = strlen(pack_name);
+               if (len < 5 || strcmp(pack_name + len - 5, ".pack"))
+                       die("packfile name '%s' does not end with '.pack'",
+                           pack_name);
+               index_name_buf = xmalloc(len - 1);
+               memcpy(index_name_buf, pack_name, len - 5);
+               strcpy(index_name_buf + len - 5, ".idx");
+               index_name = index_name_buf;
+       }
+
+       open_pack_file();
+       parse_pack_header();
+       objects = xcalloc(nr_objects, sizeof(struct object_entry));
+       deltas = xcalloc(nr_objects, sizeof(struct delta_entry));
+       parse_pack_objects();
+       free(deltas);
+       write_index_file(index_name, sha1);
+       free(objects);
+       free(index_name_buf);
+
+       printf("%s\n", sha1_to_hex(sha1));
+
+       return 0;
+}
index aabc09f4e1f714f5b4c1adba388fc47b41e16b0b..ca6fa4d4200faaf208e5c6c5af5f009cc4432a09 100644 (file)
--- a/init-db.c
+++ b/init-db.c
@@ -196,6 +196,43 @@ static void create_default_files(const char *git_dir,
        }
        path[len] = 0;
        copy_templates(path, len, template_path);
+
+       /*
+        * Find out if we can trust the executable bit.
+        */
+       safe_create_dir(path);
+       strcpy(path + len, "config");
+       if (access(path, R_OK) < 0) {
+               static const char contents[] =
+                       "#\n"
+                       "# This is the config file\n"
+                       "#\n"
+                       "\n"
+                       "; core variables\n"
+                       "[core]\n"
+                       "       ; Don't trust file modes\n"
+                       "       filemode = false\n"
+                       "\n";
+               FILE *config = fopen(path, "w");
+               struct stat st;
+
+               if (!config)
+                       die("Can not write to %s?", path);
+
+               fwrite(contents, sizeof(contents)-1, 1, config);
+
+               fclose(config);
+
+               if (!lstat(path, &st)) {
+                       struct stat st2;
+                       if (!chmod(path, st.st_mode ^ S_IXUSR) &&
+                                       !lstat(path, &st2) &&
+                                       st.st_mode != st2.st_mode)
+                               unlink(path);
+                       else
+                               fprintf(stderr, "Ignoring file modes\n");
+               }
+       }
 }
 
 static const char init_db_usage[] =
@@ -244,10 +281,6 @@ int main(int argc, char **argv)
        memcpy(path, sha1_dir, len);
 
        safe_create_dir(sha1_dir);
-       for (i = 0; i < 256; i++) {
-               sprintf(path+len, "/%02x", i);
-               safe_create_dir(path);
-       }
        strcpy(path+len, "/pack");
        safe_create_dir(path);
        strcpy(path+len, "/info");
index a57386ca6a632c2e0a43348ed8b147692746a44f..0931109143e0ce1c2f03f654b3ab67965220011a 100644 (file)
@@ -52,9 +52,10 @@ static int setup_indices(void)
        return 0;
 }
 
-static int copy_file(const char *source, const char *dest, const char *hex,
+static int copy_file(const char *source, char *dest, const char *hex,
                     int warn_if_not_exists)
 {
+       safe_create_leading_directories(dest);
        if (use_link) {
                if (!link(source, dest)) {
                        pull_say("link %s\n", hex);
@@ -82,31 +83,23 @@ static int copy_file(const char *source, const char *dest, const char *hex,
                }
        }
        if (use_filecopy) {
-               int ifd, ofd, status;
-               struct stat st;
-               void *map;
+               int ifd, ofd, status = 0;
+
                ifd = open(source, O_RDONLY);
-               if (ifd < 0 || fstat(ifd, &st) < 0) {
-                       int err = errno;
-                       if (ifd >= 0)
-                               close(ifd);
-                       if (!warn_if_not_exists && err == ENOENT)
+               if (ifd < 0) {
+                       if (!warn_if_not_exists && errno == ENOENT)
                                return -1;
                        fprintf(stderr, "cannot open %s\n", source);
                        return -1;
                }
-               map = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, ifd, 0);
-               close(ifd);
-               if (map == MAP_FAILED) {
-                       fprintf(stderr, "cannot mmap %s\n", source);
+               ofd = open(dest, O_WRONLY | O_CREAT | O_EXCL, 0666);
+               if (ofd < 0) {
+                       fprintf(stderr, "cannot open %s\n", dest);
+                       close(ifd);
                        return -1;
                }
-               ofd = open(dest, O_WRONLY | O_CREAT | O_EXCL, 0666);
-               status = ((ofd < 0) ||
-                         (write(ofd, map, st.st_size) != st.st_size));
-               munmap(map, st.st_size);
-               if (ofd >= 0)
-                       close(ofd);
+               status = copy_fd(ifd, ofd);
+               close(ofd);
                if (status)
                        fprintf(stderr, "cannot write %s\n", dest);
                else
@@ -150,7 +143,7 @@ static int fetch_file(const unsigned char *sha1)
        static int object_name_start = -1;
        static char filename[PATH_MAX];
        char *hex = sha1_to_hex(sha1);
-       const char *dest_filename = sha1_file_name(sha1);
+       char *dest_filename = sha1_file_name(sha1);
 
        if (object_name_start < 0) {
                strcpy(filename, path); /* e.g. git.git */
@@ -166,7 +159,10 @@ static int fetch_file(const unsigned char *sha1)
 
 int fetch(unsigned char *sha1)
 {
-       return fetch_file(sha1) && fetch_pack(sha1);
+       if (has_sha1_file(sha1))
+               return 0;
+       else
+               return fetch_file(sha1) && fetch_pack(sha1);
 }
 
 int fetch_ref(char *ref, unsigned char *sha1)
index f47114a168f3a7479cbe147f953e5f56af3d2dfc..3085b2fc8c7e8920ecca8e9c0657d6124b0fbfda 100644 (file)
@@ -9,6 +9,7 @@
 #include <fnmatch.h>
 
 #include "cache.h"
+#include "quote.h"
 
 static int show_deleted = 0;
 static int show_cached = 0;
@@ -342,7 +343,9 @@ static void show_dir_entry(const char *tag, struct nond_on_fs *ent)
        if (pathspec && !match(pathspec, ent->name, len))
                return;
 
-       printf("%s%s%c", tag, ent->name + offset, line_terminator);
+       fputs(tag, stdout);
+       write_name_quoted("", ent->name + offset, line_terminator, stdout);
+       putchar(line_terminator);
 }
 
 static void show_killed_files(void)
@@ -405,15 +408,20 @@ static void show_ce_entry(const char *tag, struct cache_entry *ce)
        if (pathspec && !match(pathspec, ce->name, len))
                return;
 
-       if (!show_stage)
-               printf("%s%s%c", tag, ce->name + offset, line_terminator);
-       else
-               printf("%s%06o %s %d\t%s%c",
+       if (!show_stage) {
+               fputs(tag, stdout);
+               write_name_quoted("", ce->name + offset, line_terminator, stdout);
+               putchar(line_terminator);
+       }
+       else {
+               printf("%s%06o %s %d\t",
                       tag,
                       ntohl(ce->ce_mode),
                       sha1_to_hex(ce->sha1),
-                      ce_stage(ce),
-                      ce->name + offset, line_terminator); 
+                      ce_stage(ce));
+               write_name_quoted("", ce->name + offset, line_terminator, stdout);
+               putchar(line_terminator);
+       }
 }
 
 static void show_files(void)
index dd642e0bf059cdf969d370fc1155fb189a0c89ce..d9f15e349cb833401eea38d21fb050b10f9678d4 100644 (file)
--- a/ls-tree.c
+++ b/ls-tree.c
@@ -6,6 +6,7 @@
 #include "cache.h"
 #include "blob.h"
 #include "tree.h"
+#include "quote.h"
 
 static int line_termination = '\n';
 #define LS_RECURSIVE 1
@@ -156,8 +157,9 @@ static int show_entry(struct tree_entry_list *e, int level, char *pathbuf)
        int err = 0; 
 
        if (e != &root_entry) {
-               printf("%06o %s %s      %s%s", e->mode, entry_type(e),
-                      entry_hex(e), pathbuf, e->name);
+               printf("%06o %s %s      ",
+                      e->mode, entry_type(e), entry_hex(e));
+               write_name_quoted(pathbuf, e->name, line_termination, stdout);
                putchar(line_termination);
        }
 
index 7afea1aaca6af966b6532ba66f7411d897a2874f..189f4ed724cba345eeb80cd41a9b7ae67068a037 100644 (file)
@@ -9,30 +9,13 @@
 #include <fcntl.h>
 #include <sys/types.h>
 #include <sys/stat.h>
-#include <sys/mman.h>
 #include <string.h>
 #include <stdio.h>
-#include <ctype.h>
 #include <assert.h>
+#include "cache.h"
 
-static int usage(void)
-{
-       fprintf(stderr, "mailsplit <mbox> <directory>\n");
-       exit(1);
-}
-
-static int linelen(const char *map, unsigned long size)
-{
-       int len = 0, c;
-
-       do {
-               c = *map;
-               map++;
-               size--;
-               len++;
-       } while (size && c != '\n');
-       return len;
-}
+static const char git_mailsplit_usage[] =
+"git-mailsplit [-d<prec>] [<mbox>] <directory>";
 
 static int is_from_line(const char *line, int len)
 {
@@ -65,81 +48,110 @@ static int is_from_line(const char *line, int len)
        return 1;
 }
 
-static int parse_email(const void *map, unsigned long size)
+/* Could be as small as 64, enough to hold a Unix "From " line. */
+static char buf[4096];
+
+/* Called with the first line (potentially partial)
+ * already in buf[] -- normally that should begin with
+ * the Unix "From " line.  Write it into the specified
+ * file.
+ */
+static int split_one(FILE *mbox, const char *name)
 {
-       unsigned long offset;
+       FILE *output = NULL;
+       int len = strlen(buf);
+       int fd;
+       int status = 0;
 
-       if (size < 6 || memcmp("From ", map, 5))
+       if (!is_from_line(buf, len))
                goto corrupt;
 
-       /* Make sure we don't trigger on this first line */
-       map++; size--; offset=1;
+       fd = open(name, O_WRONLY | O_CREAT | O_EXCL, 0666);
+       if (fd < 0)
+               die("cannot open output file %s", name);
+       output = fdopen(fd, "w");
 
-       /*
-        * Search for a line beginning with "From ", and 
-        * having something that looks like a date format.
+       /* Copy it out, while searching for a line that begins with
+        * "From " and having something that looks like a date format.
         */
-       do {
-               int len = linelen(map, size);
-               if (is_from_line(map, len))
-                       return offset;
-               map += len;
-               size -= len;
-               offset += len;
-       } while (size);
-       return offset;
-
-corrupt:
+       for (;;) {
+               int is_partial = (buf[len-1] != '\n');
+
+               if (fputs(buf, output) == EOF)
+                       die("cannot write output");
+
+               if (fgets(buf, sizeof(buf), mbox) == NULL) {
+                       if (feof(mbox)) {
+                               status = 1;
+                               break;
+                       }
+                       die("cannot read mbox");
+               }
+               len = strlen(buf);
+               if (!is_partial && is_from_line(buf, len))
+                       break; /* done with one message */
+       }
+       fclose(output);
+       return status;
+
+ corrupt:
+       if (output)
+               fclose(output);
+       unlink(name);
        fprintf(stderr, "corrupt mailbox\n");
        exit(1);
 }
 
-int main(int argc, char **argv)
+int main(int argc, const char **argv)
 {
-       int fd, nr;
-       struct stat st;
-       unsigned long size;
-       void *map;
-
-       if (argc != 3)
-               usage();
-       fd = open(argv[1], O_RDONLY);
-       if (fd < 0) {
-               perror(argv[1]);
-               exit(1);
-       }
-       if (chdir(argv[2]) < 0)
-               usage();
-       if (fstat(fd, &st) < 0) {
-               perror("stat");
-               exit(1);
+       int i, nr, nr_prec = 4;
+       FILE *mbox = NULL;
+
+       for (i = 1; i < argc; i++) {
+               const char *arg = argv[i];
+
+               if (arg[0] != '-')
+                       break;
+               /* do flags here */
+               if (!strncmp(arg, "-d", 2)) {
+                       nr_prec = strtol(arg + 2, NULL, 10);
+                       if (nr_prec < 3 || 10 <= nr_prec)
+                               usage(git_mailsplit_usage);
+                       continue;
+               }
        }
-       size = st.st_size;
-       map = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0);
-       if (map == MAP_FAILED) {
-               perror("mmap");
-               close(fd);
-               exit(1);
+
+       /* Either one remaining arg (dir), or two (mbox and dir) */
+       switch (argc - i) {
+       case 1:
+               mbox = stdin;
+               break;
+       case 2:
+               if ((mbox = fopen(argv[i], "r")) == NULL)
+                       die("cannot open mbox %s for reading", argv[i]);
+               break;
+       default:
+               usage(git_mailsplit_usage);
        }
-       close(fd);
+       if (chdir(argv[argc - 1]) < 0)
+               usage(git_mailsplit_usage);
+
        nr = 0;
-       do {
+       if (fgets(buf, sizeof(buf), mbox) == NULL)
+               die("cannot read mbox");
+
+       for (;;) {
                char name[10];
-               unsigned long len = parse_email(map, size);
-               assert(len <= size);
-               sprintf(name, "%04d", ++nr);
-               fd = open(name, O_WRONLY | O_CREAT | O_EXCL, 0666);
-               if (fd < 0) {
-                       perror(name);
-                       exit(1);
-               }
-               if (write(fd, map, len) != len) {
-                       perror("write");
+
+               sprintf(name, "%0*d", nr_prec, ++nr);
+               switch (split_one(mbox, name)) {
+               case 0:
+                       break;
+               case 1:
+                       printf("%d\n", nr);
+                       return 0;
+               default:
                        exit(1);
                }
-               close(fd);
-               map += len;
-               size -= len;
-       } while (size > 0);
-       return 0;
+       }
 }
diff --git a/name-rev.c b/name-rev.c
new file mode 100644 (file)
index 0000000..21fecdf
--- /dev/null
@@ -0,0 +1,246 @@
+#include <stdlib.h>
+#include "cache.h"
+#include "commit.h"
+#include "tag.h"
+#include "refs.h"
+
+static const char name_rev_usage[] =
+       "git-name-rev [--tags] ( --all | --stdin | commitish [commitish...] )\n";
+
+typedef struct rev_name {
+       const char *tip_name;
+       int merge_traversals;
+       int generation;
+} rev_name;
+
+static long cutoff = LONG_MAX;
+
+static void name_rev(struct commit *commit,
+               const char *tip_name, int merge_traversals, int generation,
+               int deref)
+{
+       struct rev_name *name = (struct rev_name *)commit->object.util;
+       struct commit_list *parents;
+       int parent_number = 0;
+
+       if (!commit->object.parsed)
+               parse_commit(commit);
+
+       if (commit->date < cutoff)
+               return;
+
+       if (deref) {
+               char *new_name = xmalloc(strlen(tip_name)+3);
+               strcpy(new_name, tip_name);
+               strcat(new_name, "^0");
+               tip_name = new_name;
+
+               if (generation)
+                       die("generation: %d, but deref?", generation);
+       }
+
+       if (name == NULL) {
+               name = xmalloc(sizeof(rev_name));
+               commit->object.util = name;
+               goto copy_data;
+       } else if (name->merge_traversals > merge_traversals ||
+                       (name->merge_traversals == merge_traversals &&
+                        name->generation > generation)) {
+copy_data:
+               name->tip_name = tip_name;
+               name->merge_traversals = merge_traversals;
+               name->generation = generation;
+       } else
+               return;
+
+       for (parents = commit->parents;
+                       parents;
+                       parents = parents->next, parent_number++) {
+               if (parent_number > 0) {
+                       char *new_name = xmalloc(strlen(tip_name)+8);
+
+                       if (generation > 0)
+                               sprintf(new_name, "%s~%d^%d", tip_name,
+                                               generation, parent_number);
+                       else
+                               sprintf(new_name, "%s^%d", tip_name, parent_number);
+
+                       name_rev(parents->item, new_name,
+                               merge_traversals + 1 , 0, 0);
+               } else {
+                       name_rev(parents->item, tip_name, merge_traversals,
+                               generation + 1, 0);
+               }
+       }
+}
+
+static int tags_only = 0;
+
+static int name_ref(const char *path, const unsigned char *sha1)
+{
+       struct object *o = parse_object(sha1);
+       int deref = 0;
+
+       if (tags_only && strncmp(path, "refs/tags/", 10))
+               return 0;
+
+       while (o && o->type == tag_type) {
+               struct tag *t = (struct tag *) o;
+               if (!t->tagged)
+                       break; /* broken repository */
+               o = parse_object(t->tagged->sha1);
+               deref = 1;
+       }
+       if (o && o->type == commit_type) {
+               struct commit *commit = (struct commit *)o;
+               const char *p;
+
+               while ((p = strchr(path, '/')))
+                       path = p+1;
+
+               name_rev(commit, strdup(path), 0, 0, deref);
+       }
+       return 0;
+}
+
+/* returns a static buffer */
+static const char* get_rev_name(struct object *o)
+{
+       static char buffer[1024];
+       struct rev_name *n = (struct rev_name *)o->util;
+       if (!n)
+               return "undefined";
+
+       if (!n->generation)
+               return n->tip_name;
+
+       snprintf(buffer, sizeof(buffer), "%s~%d", n->tip_name, n->generation);
+
+       return buffer;
+}
+       
+int main(int argc, char **argv)
+{
+       struct object_list *revs = NULL;
+       struct object_list **walker = &revs;
+       int as_is = 0, all = 0, transform_stdin = 0;
+
+       setup_git_directory();
+
+       if (argc < 2)
+               usage(name_rev_usage);
+
+       for (--argc, ++argv; argc; --argc, ++argv) {
+               unsigned char sha1[20];
+               struct object *o;
+               struct commit *commit;
+
+               if (!as_is && (*argv)[0] == '-') {
+                       if (!strcmp(*argv, "--")) {
+                               as_is = 1;
+                               continue;
+                       } else if (!strcmp(*argv, "--tags")) {
+                               tags_only = 1;
+                               continue;
+                       } else if (!strcmp(*argv, "--all")) {
+                               if (argc > 1)
+                                       die("Specify either a list, or --all, not both!");
+                               all = 1;
+                               cutoff = 0;
+                               continue;
+                       } else if (!strcmp(*argv, "--stdin")) {
+                               if (argc > 1)
+                                       die("Specify either a list, or --stdin, not both!");
+                               transform_stdin = 1;
+                               cutoff = 0;
+                               continue;
+                       }
+                       usage(name_rev_usage);
+               }
+
+               if (get_sha1(*argv, sha1)) {
+                       fprintf(stderr, "Could not get sha1 for %s. Skipping.\n",
+                                       *argv);
+                       continue;
+               }
+
+               o = deref_tag(parse_object(sha1));
+               if (!o || o->type != commit_type) {
+                       fprintf(stderr, "Could not get commit for %s. Skipping.\n",
+                                       *argv);
+                       continue;
+               }
+
+               commit = (struct commit *)o;
+
+               if (cutoff > commit->date)
+                       cutoff = commit->date;
+
+               object_list_append((struct object *)commit, walker);
+               (*walker)->name = *argv;
+               walker = &((*walker)->next);
+       }
+
+       for_each_ref(name_ref);
+
+       if (transform_stdin) {
+               char buffer[2048];
+               char *p, *p_start;
+
+               while (!feof(stdin)) {
+                       int forty = 0;
+                       p = fgets(buffer, sizeof(buffer), stdin);
+                       if (!p)
+                               break;
+
+                       for (p_start = p; *p; p++) {
+#define ishex(x) (isdigit((x)) || ((x) >= 'a' && (x) <= 'f'))
+                               if (!ishex(*p))
+                                       forty = 0;
+                               else if (++forty == 40 &&
+                                               !ishex(*(p+1))) {
+                                       unsigned char sha1[40];
+                                       const char *name = "undefined";
+                                       char c = *(p+1);
+
+                                       forty = 0;
+
+                                       *(p+1) = 0;
+                                       if (!get_sha1(p - 39, sha1)) {
+                                               struct object *o =
+                                                       lookup_object(sha1);
+                                               if (o)
+                                                       name = get_rev_name(o);
+                                       }
+                                       *(p+1) = c;
+
+                                       if (!strcmp(name, "undefined"))
+                                               continue;
+
+                                       fwrite(p_start, p - p_start, 1, stdout);
+                                       fputc('(', stdout);
+                                       fputs(name, stdout);
+                                       fputc(')', stdout);
+                                       p_start = p + 1;
+                               }
+                       }
+
+                       /* flush */
+                       if (p_start != p)
+                               fwrite(p_start, p - p_start, 1, stdout);
+               }
+       } else if (all) {
+               extern struct object **objs;
+               extern int nr_objs;
+               int i;
+
+               for (i = 0; i < nr_objs; i++)
+                       printf("%s %s\n", sha1_to_hex(objs[i]->sha1),
+                                       get_rev_name(objs[i]));
+       } else
+               for ( ; revs; revs = revs->next)
+                       printf("%s %s\n", revs->name, get_rev_name(revs->item));
+
+       return 0;
+}
+
index 3d622787cc554eb2936995441e4591ec1986bded..4e941e7392077dca9b511282be13e2b559589e5b 100644 (file)
@@ -1,11 +1,10 @@
-#include <ctype.h>
 #include "cache.h"
 #include "object.h"
 #include "delta.h"
 #include "pack.h"
 #include "csum-file.h"
 
-static const char pack_usage[] = "git-pack-objects [--incremental] [--window=N] [--depth=N] {--stdout | base-name} < object-list";
+static const char pack_usage[] = "git-pack-objects [--local] [--incremental] [--window=N] [--depth=N] {--stdout | base-name} < object-list";
 
 struct object_entry {
        unsigned char sha1[20];
@@ -20,6 +19,7 @@ struct object_entry {
 
 static unsigned char object_list_sha1[20];
 static int non_empty = 0;
+static int local = 0;
 static int incremental = 0;
 static struct object_entry **sorted_by_sha, **sorted_by_type;
 static struct object_entry *objects = NULL;
@@ -195,8 +195,20 @@ static int add_object_entry(unsigned char *sha1, unsigned int hash)
        unsigned int idx = nr_objects;
        struct object_entry *entry;
 
-       if (incremental && has_sha1_pack(sha1))
-               return 0;
+       if (incremental || local) {
+               struct packed_git *p;
+
+               for (p = packed_git; p; p = p->next) {
+                       struct pack_entry e;
+
+                       if (find_pack_entry_one(sha1, &e, p)) {
+                               if (incremental)
+                                       return 0;
+                               if (local && !p->pack_local)
+                                       return 0;
+                       }
+               }
+       }
 
        if (idx >= nr_alloc) {
                unsigned int needed = (idx + 1024) * 3 / 2;
@@ -388,11 +400,77 @@ static void find_deltas(struct object_entry **list, int window, int depth)
        free(array);
 }
 
+static void prepare_pack(int window, int depth)
+{
+       get_object_details();
+
+       fprintf(stderr, "Packing %d objects\n", nr_objects);
+
+       sorted_by_type = create_sorted_list(type_size_sort);
+       if (window && depth)
+               find_deltas(sorted_by_type, window+1, depth);
+       write_pack_file();
+}
+
+static int reuse_cached_pack(unsigned char *sha1, int pack_to_stdout)
+{
+       static const char cache[] = "pack-cache/pack-%s.%s";
+       char *cached_pack, *cached_idx;
+       int ifd, ofd, ifd_ix = -1;
+
+       cached_pack = git_path(cache, sha1_to_hex(sha1), "pack");
+       ifd = open(cached_pack, O_RDONLY);
+       if (ifd < 0)
+               return 0;
+
+       if (!pack_to_stdout) {
+               cached_idx = git_path(cache, sha1_to_hex(sha1), "idx");
+               ifd_ix = open(cached_idx, O_RDONLY);
+               if (ifd_ix < 0) {
+                       close(ifd);
+                       return 0;
+               }
+       }
+
+       fprintf(stderr, "Reusing %d objects pack %s\n", nr_objects,
+               sha1_to_hex(sha1));
+
+       if (pack_to_stdout) {
+               if (copy_fd(ifd, 1))
+                       exit(1);
+               close(ifd);
+       }
+       else {
+               char name[PATH_MAX];
+               snprintf(name, sizeof(name),
+                        "%s-%s.%s", base_name, sha1_to_hex(sha1), "pack");
+               ofd = open(name, O_CREAT | O_EXCL | O_WRONLY, 0666);
+               if (ofd < 0)
+                       die("unable to open %s (%s)", name, strerror(errno));
+               if (copy_fd(ifd, ofd))
+                       exit(1);
+               close(ifd);
+
+               snprintf(name, sizeof(name),
+                        "%s-%s.%s", base_name, sha1_to_hex(sha1), "idx");
+               ofd = open(name, O_CREAT | O_EXCL | O_WRONLY, 0666);
+               if (ofd < 0)
+                       die("unable to open %s (%s)", name, strerror(errno));
+               if (copy_fd(ifd_ix, ofd))
+                       exit(1);
+               close(ifd_ix);
+               puts(sha1_to_hex(sha1));
+       }
+
+       return 1;
+}
+
 int main(int argc, char **argv)
 {
        SHA_CTX ctx;
        char line[PATH_MAX + 20];
        int window = 10, depth = 10, pack_to_stdout = 0;
+       struct object_entry **list;
        int i;
 
        for (i = 1; i < argc; i++) {
@@ -403,6 +481,10 @@ int main(int argc, char **argv)
                                non_empty = 1;
                                continue;
                        }
+                       if (!strcmp("--local", arg)) {
+                               local = 1;
+                               continue;
+                       }
                        if (!strcmp("--incremental", arg)) {
                                incremental = 1;
                                continue;
@@ -435,7 +517,7 @@ int main(int argc, char **argv)
        if (pack_to_stdout != !base_name)
                usage(pack_usage);
 
-       SHA1_Init(&ctx);
+       prepare_packed_git();
        while (fgets(line, sizeof(line), stdin) != NULL) {
                unsigned int hash;
                char *p;
@@ -451,25 +533,28 @@ int main(int argc, char **argv)
                                continue;
                        hash = hash * 11 + c;
                }
-               if (add_object_entry(sha1, hash))
-                       SHA1_Update(&ctx, sha1, 20);
+               add_object_entry(sha1, hash);
        }
-       SHA1_Final(object_list_sha1, &ctx);
        if (non_empty && !nr_objects)
                return 0;
-       get_object_details();
-
-       fprintf(stderr, "Packing %d objects\n", nr_objects);
 
        sorted_by_sha = create_sorted_list(sha1_sort);
-       sorted_by_type = create_sorted_list(type_size_sort);
-       if (window && depth)
-               find_deltas(sorted_by_type, window+1, depth);
+       SHA1_Init(&ctx);
+       list = sorted_by_sha;
+       for (i = 0; i < nr_objects; i++) {
+               struct object_entry *entry = *list++;
+               SHA1_Update(&ctx, entry->sha1, 20);
+       }
+       SHA1_Final(object_list_sha1, &ctx);
 
-       write_pack_file();
-       if (!pack_to_stdout) {
-               write_index_file();
-               puts(sha1_to_hex(object_list_sha1));
+       if (reuse_cached_pack(object_list_sha1, pack_to_stdout))
+               ;
+       else {
+               prepare_pack(window, depth);
+               if (!pack_to_stdout) {
+                       write_index_file();
+                       puts(sha1_to_hex(object_list_sha1));
+               }
        }
        return 0;
 }
index 960e7cedf9d55f8a8fa9dba38a2b69d5f622212d..edbc4aa3e82974168f2d4c21085bdd43b774d55e 100644 (file)
@@ -1,4 +1,3 @@
-#include <ctype.h>
 #include "cache.h"
 
 static void flush_current_id(int patchlen, unsigned char *id, SHA_CTX *c)
index 5306e8e5ef411bfa7e6bcf4ba0639e640592818b..26123f7f6bb8bf9cc7fc27905fe2b67d66646f05 100644 (file)
@@ -26,6 +26,8 @@ static void prune_dir(int i, DIR *dir, char *pathname, int len)
                else if (unlink(pathname) < 0)
                        error("unable to unlink %s", pathname);
        }
+       pathname[len] = 0;
+       rmdir(pathname);
 }
 
 static void prune_packed_objects(void)
@@ -46,7 +48,7 @@ static void prune_packed_objects(void)
                sprintf(pathname + len, "%02x/", i);
                d = opendir(pathname);
                if (!d)
-                       die("unable to open %s", pathname);
+                       continue;
                prune_dir(i, d, pathname, len + 3);
                closedir(d);
        }
@@ -69,6 +71,7 @@ int main(int argc, char **argv)
                /* Handle arguments here .. */
                usage(prune_packed_usage);
        }
+       sync();
        prune_packed_objects();
        return 0;
 }
diff --git a/quote.c b/quote.c
index 92e07f070ff54be3f5d0ca02582a7c9e1fd1fa53..e662a7da71e39c1ffc3d1a4bf485fda17df5e907 100644 (file)
--- a/quote.c
+++ b/quote.c
@@ -2,43 +2,95 @@
 #include "quote.h"
 
 /* Help to copy the thing properly quoted for the shell safety.
- * any single quote is replaced with '\'', and the caller is
- * expected to enclose the result within a single quote pair.
+ * any single quote is replaced with '\'', any exclamation point
+ * is replaced with '\!', and the whole thing is enclosed in a
  *
  * E.g.
  *  original     sq_quote     result
  *  name     ==> name      ==> 'name'
  *  a b      ==> a b       ==> 'a b'
  *  a'b      ==> a'\''b    ==> 'a'\''b'
+ *  a!b      ==> a'\!'b    ==> 'a'\!'b'
  */
-char *sq_quote(const char *src)
+#undef EMIT
+#define EMIT(x) ( (++len < n) && (*bp++ = (x)) )
+
+static inline int need_bs_quote(char c)
 {
-       static char *buf = NULL;
-       int cnt, c;
-       const char *cp;
-       char *bp;
+       return (c == '\'' || c == '!');
+}
 
-       /* count bytes needed to store the quoted string. */
-       for (cnt = 3, cp = src; *cp; cnt++, cp++)
-               if (*cp == '\'')
-                       cnt += 3;
+size_t sq_quote_buf(char *dst, size_t n, const char *src)
+{
+       char c;
+       char *bp = dst;
+       size_t len = 0;
 
-       buf = xmalloc(cnt);
-       bp = buf;
-       *bp++ = '\'';
+       EMIT('\'');
        while ((c = *src++)) {
-               if (c != '\'')
-                       *bp++ = c;
-               else {
-                       bp = strcpy(bp, "'\\''");
-                       bp += 4;
+               if (need_bs_quote(c)) {
+                       EMIT('\'');
+                       EMIT('\\');
+                       EMIT(c);
+                       EMIT('\'');
+               } else {
+                       EMIT(c);
                }
        }
-       *bp++ = '\'';
-       *bp = 0;
+       EMIT('\'');
+
+       if ( n )
+               *bp = 0;
+
+       return len;
+}
+
+char *sq_quote(const char *src)
+{
+       char *buf;
+       size_t cnt;
+
+       cnt = sq_quote_buf(NULL, 0, src) + 1;
+       buf = xmalloc(cnt);
+       sq_quote_buf(buf, cnt, src);
+
        return buf;
 }
 
+char *sq_dequote(char *arg)
+{
+       char *dst = arg;
+       char *src = arg;
+       char c;
+
+       if (*src != '\'')
+               return NULL;
+       for (;;) {
+               c = *++src;
+               if (!c)
+                       return NULL;
+               if (c != '\'') {
+                       *dst++ = c;
+                       continue;
+               }
+               /* We stepped out of sq */
+               switch (*++src) {
+               case '\0':
+                       *dst = 0;
+                       return arg;
+               case '\\':
+                       c = *++src;
+                       if (need_bs_quote(c) && *++src == '\'') {
+                               *dst++ = c;
+                               continue;
+                       }
+               /* Fallthrough */
+               default:
+                       return NULL;
+               }
+       }
+}
+
 /*
  * C-style name quoting.
  *
diff --git a/quote.h b/quote.h
index ea227bb7d44b3a37990ce0d00ab4eea34a30065a..2486e6e68c2d7fa7bc2f78c18df323cc9adcc894 100644 (file)
--- a/quote.h
+++ b/quote.h
@@ -1,11 +1,13 @@
 #ifndef QUOTE_H
 #define QUOTE_H
 
+#include <stddef.h>
 #include <stdio.h>
 
 /* Help to copy the thing properly quoted for the shell safety.
- * any single quote is replaced with '\'', and the whole thing
- * is enclosed in a single quote pair.
+ * any single quote is replaced with '\'', any exclamation point
+ * is replaced with '\!', and the whole thing is enclosed in a
+ * single quote pair.
  *
  * For example, if you are passing the result to system() as an
  * argument:
  *
  * Note that the above examples leak memory!  Remember to free result from
  * sq_quote() in a real application.
+ *
+ * sq_quote_buf() writes to an existing buffer of specified size; it
+ * will return the number of characters that would have been written
+ * excluding the final null regardless of the buffer size.
  */
 
 extern char *sq_quote(const char *src);
+extern size_t sq_quote_buf(char *dst, size_t n, const char *src);
+
+/* This unwraps what sq_quote() produces in place, but returns
+ * NULL if the input does not look like what sq_quote would have
+ * produced.
+ */
+extern char *sq_dequote(char *);
 
 extern int quote_c_style(const char *name, char *outbuf, FILE *outfp,
                         int nodq);
@@ -30,4 +43,5 @@ extern char *unquote_c_style(const char *quoted, const char **endp);
 
 extern void write_name_quoted(const char *prefix, const char *name,
                              int quote, FILE *out);
+
 #endif
index d2aebdd6bc6dbf7ae40f3a7600d76765f31b9a15..693273620370e0500bc9ea2adba98a88cafc39cd 100644 (file)
@@ -35,8 +35,11 @@ int ce_match_stat(struct cache_entry *ce, struct stat *st)
        switch (ntohl(ce->ce_mode) & S_IFMT) {
        case S_IFREG:
                changed |= !S_ISREG(st->st_mode) ? TYPE_CHANGED : 0;
-               /* We consider only the owner x bit to be relevant for "mode changes" */
-               if (0100 & (ntohl(ce->ce_mode) ^ st->st_mode))
+               /* We consider only the owner x bit to be relevant for
+                * "mode changes"
+                */
+               if (trust_executable_bit &&
+                   (0100 & (ntohl(ce->ce_mode) ^ st->st_mode)))
                        changed |= MODE_CHANGED;
                break;
        case S_IFLNK:
@@ -392,7 +395,7 @@ int add_cache_entry(struct cache_entry *ce, int option)
        int skip_df_check = option & ADD_CACHE_SKIP_DFCHECK;
        pos = cache_name_pos(ce->name, ntohs(ce->ce_flags));
 
-       /* existing match? Just replace it */
+       /* existing match? Just replace it. */
        if (pos >= 0) {
                active_cache_changed = 1;
                active_cache[pos] = ce;
@@ -415,7 +418,8 @@ int add_cache_entry(struct cache_entry *ce, int option)
        if (!ok_to_add)
                return -1;
 
-       if (!skip_df_check && check_file_directory_conflict(ce, pos, ok_to_replace)) {
+       if (!skip_df_check &&
+           check_file_directory_conflict(ce, pos, ok_to_replace)) {
                if (!ok_to_replace)
                        return -1;
                pos = cache_name_pos(ce->name, ntohs(ce->ce_flags));
index 5fdf58d240e68f2c4b332dbdd02be357641127f8..6a456ae61100f8e45f97394d7ea23be4faa63fc4 100644 (file)
@@ -237,6 +237,35 @@ static void reject_merge(struct cache_entry *ce)
            ce->name);
 }
 
+/* Unlink the last component and attempt to remove leading
+ * directories, in case this unlink is the removal of the
+ * last entry in the directory -- empty directories are removed.
+ */
+static void unlink_entry(char *name)
+{
+       char *cp, *prev;
+
+       if (unlink(name))
+               return;
+       prev = NULL;
+       while (1) {
+               int status;
+               cp = strrchr(name, '/');
+               if (prev)
+                       *prev = '/';
+               if (!cp)
+                       break;
+
+               *cp = 0;
+               status = rmdir(name);
+               if (status) {
+                       *cp = '/';
+                       break;
+               }
+               prev = cp;
+       }
+}
+
 static void check_updates(struct cache_entry **src, int nr)
 {
        static struct checkout state = {
@@ -250,7 +279,7 @@ static void check_updates(struct cache_entry **src, int nr)
                struct cache_entry *ce = *src++;
                if (!ce->ce_mode) {
                        if (update)
-                               unlink(ce->name);
+                               unlink_entry(ce->name);
                        continue;
                }
                if (ce->ce_flags & mask) {
diff --git a/refs.c b/refs.c
index d7f8dfddf4979c0dda2b888435e6c343b29409ad..a52b038eefdf22fce20d1a81180c97def0613cdb 100644 (file)
--- a/refs.c
+++ b/refs.c
@@ -2,7 +2,6 @@
 #include "cache.h"
 
 #include <errno.h>
-#include <ctype.h>
 
 /* We allow "recursive" symbolic refs. Only within reason, though */
 #define MAXDEPTH 5
@@ -117,14 +116,17 @@ const char *resolve_ref(const char *path, unsigned char *sha1, int reading)
 
 int create_symref(const char *git_HEAD, const char *refs_heads_master)
 {
-#if USE_SYMLINK_HEAD
-       unlink(git_HEAD);
-       return symlink(refs_heads_master, git_HEAD);
-#else
        const char *lockpath;
        char ref[1000];
        int fd, len, written;
 
+#if USE_SYMLINK_HEAD
+       unlink(git_HEAD);
+       if (!symlink(refs_heads_master, git_HEAD))
+               return 0;
+       fprintf(stderr, "no symlink - falling back to symbolic ref\n");
+#endif
+
        len = snprintf(ref, sizeof(ref), "ref: %s\n", refs_heads_master);
        if (sizeof(ref) <= len) {
                error("refname too long: %s", refs_heads_master);
@@ -145,7 +147,6 @@ int create_symref(const char *git_HEAD, const char *refs_heads_master)
                return -3;
        }
        return 0;
-#endif
 }
 
 int read_ref(const char *filename, unsigned char *sha1)
index 3a32e405a38faebe0d4b7ec3b743fe3e15cfd3cf..6e6ffde39600f048f5c365dd478256feba321db9 100644 (file)
@@ -5,27 +5,36 @@
 #include "tree.h"
 #include "blob.h"
 #include "epoch.h"
+#include "diff.h"
 
 #define SEEN           (1u << 0)
 #define INTERESTING    (1u << 1)
 #define COUNTED                (1u << 2)
 #define SHOWN          (1u << 3)
+#define TREECHANGE     (1u << 4)
 
 static const char rev_list_usage[] =
-       "git-rev-list [OPTION] commit-id <commit-id>\n"
-                     "  --max-count=nr\n"
-                     "  --max-age=epoch\n"
-                     "  --min-age=epoch\n"
-                     "  --parents\n"
-                     "  --bisect\n"
-                     "  --objects\n"
-                     "  --unpacked\n"
-                     "  --header\n"
-                     "  --pretty\n"
-                     "  --no-merges\n"
-                     "  --merge-order [ --show-breaks ]\n"
-                     "  --topo-order";
-
+"git-rev-list [OPTION] <commit-id>... [ -- paths... ]\n"
+"  limiting output:\n"
+"    --max-count=nr\n"
+"    --max-age=epoch\n"
+"    --min-age=epoch\n"
+"    --sparse\n"
+"    --no-merges\n"
+"    --all\n"
+"  ordering output:\n"
+"    --merge-order [ --show-breaks ]\n"
+"    --topo-order\n"
+"  formatting output:\n"
+"    --parents\n"
+"    --objects\n"
+"    --unpacked\n"
+"    --header | --pretty\n"
+"  special purpose:\n"
+"    --bisect"
+;
+
+static int dense = 1;
 static int unpacked = 0;
 static int bisect_list = 0;
 static int tag_objects = 0;
@@ -44,6 +53,7 @@ static int show_breaks = 0;
 static int stop_traversal = 0;
 static int topo_order = 0;
 static int no_merges = 0;
+static const char **paths = NULL;
 
 static void show_commit(struct commit *commit)
 {
@@ -77,6 +87,31 @@ static void show_commit(struct commit *commit)
        fflush(stdout);
 }
 
+static int rewrite_one(struct commit **pp)
+{
+       for (;;) {
+               struct commit *p = *pp;
+               if (p->object.flags & (TREECHANGE | UNINTERESTING))
+                       return 0;
+               if (!p->parents)
+                       return -1;
+               *pp = p->parents->item;
+       }
+}
+
+static void rewrite_parents(struct commit *commit)
+{
+       struct commit_list **pp = &commit->parents;
+       while (*pp) {
+               struct commit_list *parent = *pp;
+               if (rewrite_one(&parent->item) < 0) {
+                       *pp = parent->next;
+                       continue;
+               }
+               pp = &parent->next;
+       }
+}
+
 static int filter_commit(struct commit * commit)
 {
        if (stop_traversal && (commit->object.flags & BOUNDARY))
@@ -93,6 +128,11 @@ static int filter_commit(struct commit * commit)
                return STOP;
        if (no_merges && (commit->parents && commit->parents->next))
                return CONTINUE;
+       if (paths && dense) {
+               if (!(commit->object.flags & TREECHANGE))
+                       return CONTINUE;
+               rewrite_parents(commit);
+       }
        return DO;
 }
 
@@ -377,18 +417,184 @@ static void mark_edges_uninteresting(struct commit_list *list)
        }
 }
 
+static int is_different = 0;
+
+static void file_add_remove(struct diff_options *options,
+                   int addremove, unsigned mode,
+                   const unsigned char *sha1,
+                   const char *base, const char *path)
+{
+       is_different = 1;
+}
+
+static void file_change(struct diff_options *options,
+                unsigned old_mode, unsigned new_mode,
+                const unsigned char *old_sha1,
+                const unsigned char *new_sha1,
+                const char *base, const char *path)
+{
+       is_different = 1;
+}
+
+static struct diff_options diff_opt = {
+       .recursive = 1,
+       .add_remove = file_add_remove,
+       .change = file_change,
+};
+
+static int same_tree(struct tree *t1, struct tree *t2)
+{
+       is_different = 0;
+       if (diff_tree_sha1(t1->object.sha1, t2->object.sha1, "", &diff_opt) < 0)
+               return 0;
+       return !is_different;
+}
+
+static int same_tree_as_empty(struct tree *t1)
+{
+       int retval;
+       void *tree;
+       struct tree_desc empty, real;
+
+       if (!t1)
+               return 0;
+
+       tree = read_object_with_reference(t1->object.sha1, "tree", &real.size, NULL);
+       if (!tree)
+               return 0;
+       real.buf = tree;
+
+       empty.buf = "";
+       empty.size = 0;
+
+       is_different = 0;
+       retval = diff_tree(&empty, &real, "", &diff_opt);
+       free(tree);
+
+       return retval >= 0 && !is_different;
+}
+
+static struct commit *try_to_simplify_merge(struct commit *commit, struct commit_list *parent)
+{
+       if (!commit->tree)
+               return NULL;
+
+       while (parent) {
+               struct commit *p = parent->item;
+               parent = parent->next;
+               parse_commit(p);
+               if (!p->tree)
+                       continue;
+               if (same_tree(commit->tree, p->tree))
+                       return p;
+       }
+       return NULL;
+}
+
+static void add_parents_to_list(struct commit *commit, struct commit_list **list)
+{
+       struct commit_list *parent = commit->parents;
+
+       /*
+        * If the commit is uninteresting, don't try to
+        * prune parents - we want the maximal uninteresting
+        * set.
+        *
+        * Normally we haven't parsed the parent
+        * yet, so we won't have a parent of a parent
+        * here. However, it may turn out that we've
+        * reached this commit some other way (where it
+        * wasn't uninteresting), in which case we need
+        * to mark its parents recursively too..
+        */
+       if (commit->object.flags & UNINTERESTING) {
+               while (parent) {
+                       struct commit *p = parent->item;
+                       parent = parent->next;
+                       parse_commit(p);
+                       p->object.flags |= UNINTERESTING;
+                       if (p->parents)
+                               mark_parents_uninteresting(p);
+                       if (p->object.flags & SEEN)
+                               continue;
+                       p->object.flags |= SEEN;
+                       insert_by_date(p, list);
+               }
+               return;
+       }
+
+       /*
+        * Ok, the commit wasn't uninteresting. If it
+        * is a merge, try to find the parent that has
+        * no differences in the path set if one exists.
+        */
+       if (paths && parent && parent->next) {
+               struct commit *preferred;
+
+               preferred = try_to_simplify_merge(commit, parent);
+               if (preferred) {
+                       parent->item = preferred;
+                       parent->next = NULL;
+               }
+       }
+
+       while (parent) {
+               struct commit *p = parent->item;
+
+               parent = parent->next;
+
+               parse_commit(p);
+               if (p->object.flags & SEEN)
+                       continue;
+               p->object.flags |= SEEN;
+               insert_by_date(p, list);
+       }
+}
+
+static void compress_list(struct commit_list *list)
+{
+       while (list) {
+               struct commit *commit = list->item;
+               struct commit_list *parent = commit->parents;
+               list = list->next;
+
+               if (!parent) {
+                       if (!same_tree_as_empty(commit->tree))
+                               commit->object.flags |= TREECHANGE;
+                       continue;
+               }
+
+               /*
+                * Exactly one parent? Check if it leaves the tree
+                * unchanged
+                */
+               if (!parent->next) {
+                       struct tree *t1 = commit->tree;
+                       struct tree *t2 = parent->item->tree;
+                       if (!t1 || !t2 || same_tree(t1, t2))
+                               continue;
+               }
+               commit->object.flags |= TREECHANGE;
+       }
+}
+
 static struct commit_list *limit_list(struct commit_list *list)
 {
        struct commit_list *newlist = NULL;
        struct commit_list **p = &newlist;
        while (list) {
-               struct commit *commit = pop_most_recent_commit(&list, SEEN);
+               struct commit_list *entry = list;
+               struct commit *commit = list->item;
                struct object *obj = &commit->object;
 
+               list = list->next;
+               free(entry);
+
                if (max_age != -1 && (commit->date < max_age))
                        obj->flags |= UNINTERESTING;
                if (unpacked && has_sha1_pack(obj->sha1))
                        obj->flags |= UNINTERESTING;
+               add_parents_to_list(commit, &list);
                if (obj->flags & UNINTERESTING) {
                        mark_parents_uninteresting(commit);
                        if (everybody_uninteresting(list))
@@ -401,6 +607,8 @@ static struct commit_list *limit_list(struct commit_list *list)
        }
        if (tree_objects)
                mark_edges_uninteresting(newlist);
+       if (paths && dense)
+               compress_list(newlist);
        if (bisect_list)
                newlist = find_bisection(newlist);
        return newlist;
@@ -411,13 +619,10 @@ static void add_pending_object(struct object *obj, const char *name)
        add_object(obj, &pending_objects, name);
 }
 
-static struct commit *get_commit_reference(const char *name, unsigned int flags)
+static struct commit *get_commit_reference(const char *name, const unsigned char *sha1, unsigned int flags)
 {
-       unsigned char sha1[20];
        struct object *object;
 
-       if (get_sha1(name, sha1))
-               usage(rev_list_usage);
        object = parse_object(sha1);
        if (!object)
                die("bad object %s", name);
@@ -495,7 +700,7 @@ static struct commit_list **global_lst;
 
 static int include_one_commit(const char *path, const unsigned char *sha1)
 {
-       struct commit *com = get_commit_reference(path, 0);
+       struct commit *com = get_commit_reference(path, sha1, 0);
        handle_one_commit(com, global_lst);
        return 0;
 }
@@ -507,17 +712,18 @@ static void handle_all(struct commit_list **lst)
        global_lst = NULL;
 }
 
-int main(int argc, char **argv)
+int main(int argc, const char **argv)
 {
+       const char *prefix = setup_git_directory();
        struct commit_list *list = NULL;
        int i, limited = 0;
 
-       setup_git_directory();
        for (i = 1 ; i < argc; i++) {
                int flags;
-               char *arg = argv[i];
+               const char *arg = argv[i];
                char *dotdot;
                struct commit *commit;
+               unsigned char sha1[20];
 
                if (!strncmp(arg, "--max-count=", 12)) {
                        max_count = atoi(arg + 12);
@@ -587,6 +793,18 @@ int main(int argc, char **argv)
                        limited = 1;
                        continue;
                }
+               if (!strcmp(arg, "--dense")) {
+                       dense = 1;
+                       continue;
+               }
+               if (!strcmp(arg, "--sparse")) {
+                       dense = 0;
+                       continue;
+               }
+               if (!strcmp(arg, "--")) {
+                       i++;
+                       break;
+               }
 
                if (show_breaks && !merge_order)
                        usage(rev_list_usage);
@@ -594,15 +812,19 @@ int main(int argc, char **argv)
                flags = 0;
                dotdot = strstr(arg, "..");
                if (dotdot) {
+                       unsigned char from_sha1[20];
                        char *next = dotdot + 2;
-                       struct commit *exclude = NULL;
-                       struct commit *include = NULL;
                        *dotdot = 0;
                        if (!*next)
                                next = "HEAD";
-                       exclude = get_commit_reference(arg, UNINTERESTING);
-                       include = get_commit_reference(next, 0);
-                       if (exclude && include) {
+                       if (!get_sha1(arg, from_sha1) && !get_sha1(next, sha1)) {
+                               struct commit *exclude;
+                               struct commit *include;
+                               
+                               exclude = get_commit_reference(arg, from_sha1, UNINTERESTING);
+                               include = get_commit_reference(next, sha1, 0);
+                               if (!exclude || !include)
+                                       die("Invalid revision range %s..%s", arg, next);
                                limited = 1;
                                handle_one_commit(exclude, &list);
                                handle_one_commit(include, &list);
@@ -615,10 +837,21 @@ int main(int argc, char **argv)
                        arg++;
                        limited = 1;
                }
-               commit = get_commit_reference(arg, flags);
+               if (get_sha1(arg, sha1) < 0)
+                       break;
+               commit = get_commit_reference(arg, sha1, flags);
                handle_one_commit(commit, &list);
        }
 
+       if (!list)
+               usage(rev_list_usage);
+
+       paths = get_pathspec(prefix, argv + i);
+       if (paths) {
+               limited = 1;
+               diff_tree_setup_paths(paths);
+       }
+
        save_commit_buffer = verbose_header;
        track_object_refs = 0;
 
index 41b9dae429d2b423f57af4f4e091accfd9697022..5a989825113b122d130448726c8eb97ffeb21ad1 100644 (file)
@@ -34,6 +34,7 @@ static int is_rev_argument(const char *arg)
        static const char *rev_args[] = {
                "--all",
                "--bisect",
+               "--dense",
                "--header",
                "--max-age=",
                "--max-count=",
@@ -44,6 +45,7 @@ static int is_rev_argument(const char *arg)
                "--parents",
                "--pretty",
                "--show-breaks",
+               "--sparse",
                "--topo-order",
                "--unpacked",
                NULL
@@ -151,6 +153,13 @@ static void show_datestring(const char *flag, const char *datestr)
        show(buffer);
 }
 
+static void show_file(const char *arg)
+{
+       show_default();
+       if ((filter & (DO_NONFLAGS|DO_NOREV)) == (DO_NONFLAGS|DO_NOREV))
+               show(arg);
+}
+
 int main(int argc, char **argv)
 {
        int i, as_is = 0, verify = 0;
@@ -162,12 +171,15 @@ int main(int argc, char **argv)
                char *dotdot;
        
                if (as_is) {
-                       show(arg);
+                       show_file(arg);
                        continue;
                }
                if (*arg == '-') {
                        if (!strcmp(arg, "--")) {
                                as_is = 1;
+                               /* Pass on the "--" if we show anything but files.. */
+                               if (filter & (DO_FLAGS | DO_REVS))
+                                       show_file(arg);
                                continue;
                        }
                        if (!strcmp(arg, "--default")) {
@@ -282,9 +294,8 @@ int main(int argc, char **argv)
                }
                if (verify)
                        die("Needed a single revision");
-               if ((filter & (DO_NONFLAGS|DO_NOREV)) ==
-                   (DO_NONFLAGS|DO_NOREV))
-                       show(arg);
+               as_is = 1;
+               show_file(arg);
        }
        show_default();
        if (verify && revs_count != 1)
diff --git a/rsh.c b/rsh.c
index 1fef6da513b8157331b1a71c1d4c8df4c438498e..d66526941fbe45f99e51babc2c55a63b4baa027c 100644 (file)
--- a/rsh.c
+++ b/rsh.c
@@ -1,52 +1,16 @@
-#include "rsh.h"
-
 #include <string.h>
 #include <sys/types.h>
 #include <sys/socket.h>
 
+#include "rsh.h"
+#include "quote.h"
 #include "cache.h"
 
 #define COMMAND_SIZE 4096
 
 /*
- * Write a shell-quoted version of a string into a buffer, and
- * return bytes that ought to be output excluding final null.
- */
-static int shell_quote(char *buf, int nmax, const char *str)
-{
-       char ch;
-       int nq;
-       int oc = 0;
-
-       while ( (ch = *str++) ) {
-               nq = 0;
-               if ( strchr(" !\"#$%&\'()*;<=>?[\\]^`{|}", ch) )
-                       nq = 1;
-
-               if ( nq ) {
-                       if ( nmax > 1 ) {
-                               *buf++ = '\\';
-                               nmax--;
-                       }
-                       oc++;
-               }
-
-               if ( nmax > 1 ) {
-                       *buf++ = ch;
-                       nmax--;
-               }
-               oc++;
-       }
-
-       if ( nmax )
-               *buf = '\0';
-
-       return oc;
-}
-                       
-/*
- * Append a string to a string buffer, with or without quoting.  Return true
- * if the buffer overflowed.
+ * Append a string to a string buffer, with or without shell quoting.
+ * Return true if the buffer overflowed.
  */
 static int add_to_string(char **ptrp, int *sizep, const char *str, int quote)
 {
@@ -56,7 +20,7 @@ static int add_to_string(char **ptrp, int *sizep, const char *str, int quote)
        int err = 0;
 
        if ( quote ) {
-               oc = shell_quote(p, size, str);
+               oc = sq_quote_buf(p, size, str);
        } else {
                oc = strlen(str);
                memcpy(p, str, (oc >= size) ? size-1 : oc);
@@ -109,8 +73,7 @@ int setup_connection(int *fd_in, int *fd_out, const char *remote_prog,
        posn = command;
        of = 0;
        of |= add_to_string(&posn, &sizen, "env ", 0);
-       of |= add_to_string(&posn, &sizen, GIT_DIR_ENVIRONMENT, 0);
-       of |= add_to_string(&posn, &sizen, "=", 0);
+       of |= add_to_string(&posn, &sizen, GIT_DIR_ENVIRONMENT "=", 0);
        of |= add_to_string(&posn, &sizen, path, 1);
        of |= add_to_string(&posn, &sizen, " ", 0);
        of |= add_to_string(&posn, &sizen, remote_prog, 1);
index a9e5607f2f5668411e5742243372bf570c2bda72..ba5359108deb4f010be366ef65974ec7db123098 100644 (file)
@@ -9,7 +9,14 @@ static FILE *info_ref_fp;
 
 static int add_info_ref(const char *path, const unsigned char *sha1)
 {
+       struct object *o = parse_object(sha1);
+
        fprintf(info_ref_fp, "%s        %s\n", sha1_to_hex(sha1), path);
+       if (o->type == tag_type) {
+               o = deref_tag(o);
+               fprintf(info_ref_fp, "%s        %s^{}\n",
+                       sha1_to_hex(o->sha1), path);
+       }
        return 0;
 }
 
@@ -59,6 +66,16 @@ static struct object *parse_object_cheap(const unsigned char *sha1)
                struct commit *commit = (struct commit *)o;
                free(commit->buffer);
                commit->buffer = NULL;
+       } else if (o->type == tree_type) {
+               struct tree *tree = (struct tree *)o;
+               struct tree_entry_list *e, *n;
+               for (e = tree->entries; e; e = n) {
+                       free(e->name);
+                       e->name = NULL;
+                       n = e->next;
+                       free(e);
+               }
+               tree->entries = NULL;
        }
        return o;
 }
index 895c1fab6fc00b8131e44851f33b79b1d2310b12..642f00d3d47c3d782c3dfe522d21819c1a67de30 100644 (file)
@@ -48,65 +48,6 @@ int get_sha1_hex(const char *hex, unsigned char *sha1)
        return 0;
 }
 
-static char *git_dir, *git_object_dir, *git_index_file, *git_refs_dir,
-       *git_graft_file;
-static void setup_git_env(void)
-{
-       git_dir = getenv(GIT_DIR_ENVIRONMENT);
-       if (!git_dir)
-               git_dir = DEFAULT_GIT_DIR_ENVIRONMENT;
-       git_object_dir = getenv(DB_ENVIRONMENT);
-       if (!git_object_dir) {
-               git_object_dir = xmalloc(strlen(git_dir) + 9);
-               sprintf(git_object_dir, "%s/objects", git_dir);
-       }
-       git_refs_dir = xmalloc(strlen(git_dir) + 6);
-       sprintf(git_refs_dir, "%s/refs", git_dir);
-       git_index_file = getenv(INDEX_ENVIRONMENT);
-       if (!git_index_file) {
-               git_index_file = xmalloc(strlen(git_dir) + 7);
-               sprintf(git_index_file, "%s/index", git_dir);
-       }
-       git_graft_file = getenv(GRAFT_ENVIRONMENT);
-       if (!git_graft_file)
-               git_graft_file = strdup(git_path("info/grafts"));
-}
-
-char *get_git_dir(void)
-{
-       if (!git_dir)
-               setup_git_env();
-       return git_dir;
-}
-
-char *get_object_directory(void)
-{
-       if (!git_object_dir)
-               setup_git_env();
-       return git_object_dir;
-}
-
-char *get_refs_directory(void)
-{
-       if (!git_refs_dir)
-               setup_git_env();
-       return git_refs_dir;
-}
-
-char *get_index_file(void)
-{
-       if (!git_index_file)
-               setup_git_env();
-       return git_index_file;
-}
-
-char *get_graft_file(void)
-{
-       if (!git_graft_file)
-               setup_git_env();
-       return git_graft_file;
-}
-
 int safe_create_leading_directories(char *path)
 {
        char *pos = path;
@@ -475,7 +416,7 @@ int use_packed_git(struct packed_git *p)
        return 0;
 }
 
-struct packed_git *add_packed_git(char *path, int path_len)
+struct packed_git *add_packed_git(char *path, int path_len, int local)
 {
        struct stat st;
        struct packed_git *p;
@@ -503,6 +444,7 @@ struct packed_git *add_packed_git(char *path, int path_len)
        p->pack_base = NULL;
        p->pack_last_used = 0;
        p->pack_use_cnt = 0;
+       p->pack_local = local;
        return p;
 }
 
@@ -543,7 +485,7 @@ void install_packed_git(struct packed_git *pack)
        packed_git = pack;
 }
 
-static void prepare_packed_git_one(char *objdir)
+static void prepare_packed_git_one(char *objdir, int local)
 {
        char path[PATH_MAX];
        int len;
@@ -565,7 +507,7 @@ static void prepare_packed_git_one(char *objdir)
 
                /* we have .idx.  Is it a file we can map? */
                strcpy(path + len, de->d_name);
-               p = add_packed_git(path, len + namelen);
+               p = add_packed_git(path, len + namelen, local);
                if (!p)
                        continue;
                p->next = packed_git;
@@ -581,11 +523,11 @@ void prepare_packed_git(void)
 
        if (run_once)
                return;
-       prepare_packed_git_one(get_object_directory());
+       prepare_packed_git_one(get_object_directory(), 1);
        prepare_alt_odb();
        for (alt = alt_odb_list; alt; alt = alt->next) {
                alt->name[0] = 0;
-               prepare_packed_git_one(alt->base);
+               prepare_packed_git_one(alt->base, 0);
        }
        run_once = 1;
 }
@@ -1248,6 +1190,77 @@ char *write_sha1_file_prepare(void *buf,
        return sha1_file_name(sha1);
 }
 
+/*
+ * Link the tempfile to the final place, possibly creating the
+ * last directory level as you do so.
+ *
+ * Returns the errno on failure, 0 on success.
+ */
+static int link_temp_to_file(const char *tmpfile, char *filename)
+{
+       int ret;
+
+       if (!link(tmpfile, filename))
+               return 0;
+
+       /*
+        * Try to mkdir the last path component if that failed
+        * with an ENOENT.
+        *
+        * Re-try the "link()" regardless of whether the mkdir
+        * succeeds, since a race might mean that somebody
+        * else succeeded.
+        */
+       ret = errno;
+       if (ret == ENOENT) {
+               char *dir = strrchr(filename, '/');
+               if (dir) {
+                       *dir = 0;
+                       mkdir(filename, 0777);
+                       *dir = '/';
+                       if (!link(tmpfile, filename))
+                               return 0;
+                       ret = errno;
+               }
+       }
+       return ret;
+}
+
+/*
+ * Move the just written object into its final resting place
+ */
+int move_temp_to_file(const char *tmpfile, char *filename)
+{
+       int ret = link_temp_to_file(tmpfile, filename);
+
+       /*
+        * Coda hack - coda doesn't like cross-directory links,
+        * so we fall back to a rename, which will mean that it
+        * won't be able to check collisions, but that's not a
+        * big deal.
+        *
+        * The same holds for FAT formatted media.
+        *
+        * When this succeeds, we just return 0. We have nothing
+        * left to unlink.
+        */
+       if (ret && ret != EEXIST) {
+               if (!rename(tmpfile, filename))
+                       return 0;
+               ret = errno;
+       }
+       unlink(tmpfile);
+       if (ret) {
+               if (ret != EEXIST) {
+                       fprintf(stderr, "unable to write sha1 filename %s: %s", filename, strerror(ret));
+                       return -1;
+               }
+               /* FIXME!!! Collision check here ? */
+       }
+
+       return 0;
+}
+
 int write_sha1_file(void *buf, unsigned long len, const char *type, unsigned char *returnsha1)
 {
        int size;
@@ -1257,7 +1270,7 @@ int write_sha1_file(void *buf, unsigned long len, const char *type, unsigned cha
        char *filename;
        static char tmpfile[PATH_MAX];
        unsigned char hdr[50];
-       int fd, hdrlen, ret;
+       int fd, hdrlen;
 
        /* Normally if we have it in the pack then we do not bother writing
         * it out into .git/objects/??/?{38} file.
@@ -1320,32 +1333,7 @@ int write_sha1_file(void *buf, unsigned long len, const char *type, unsigned cha
        close(fd);
        free(compressed);
 
-       ret = link(tmpfile, filename);
-       if (ret < 0) {
-               ret = errno;
-
-               /*
-                * Coda hack - coda doesn't like cross-directory links,
-                * so we fall back to a rename, which will mean that it
-                * won't be able to check collisions, but that's not a
-                * big deal.
-                *
-                * When this succeeds, we just return 0. We have nothing
-                * left to unlink.
-                */
-               if (ret == EXDEV && !rename(tmpfile, filename))
-                       return 0;
-       }
-       unlink(tmpfile);
-       if (ret) {
-               if (ret != EEXIST) {
-                       fprintf(stderr, "unable to write sha1 filename %s: %s", filename, strerror(ret));
-                       return -1;
-               }
-               /* FIXME!!! Collision check here ? */
-       }
-
-       return 0;
+       return move_temp_to_file(tmpfile, filename);
 }
 
 int write_sha1_to_fd(int fd, const unsigned char *sha1)
@@ -1420,8 +1408,7 @@ int write_sha1_to_fd(int fd, const unsigned char *sha1)
 int write_sha1_from_fd(const unsigned char *sha1, int fd, char *buffer,
                       size_t bufsize, size_t *bufposn)
 {
-       char *filename = sha1_file_name(sha1);
-
+       char tmpfile[PATH_MAX];
        int local;
        z_stream stream;
        unsigned char real_sha1[20];
@@ -1429,10 +1416,11 @@ int write_sha1_from_fd(const unsigned char *sha1, int fd, char *buffer,
        int ret;
        SHA_CTX c;
 
-       local = open(filename, O_WRONLY | O_CREAT | O_EXCL, 0666);
+       snprintf(tmpfile, sizeof(tmpfile), "%s/obj_XXXXXX", get_object_directory());
 
+       local = mkstemp(tmpfile);
        if (local < 0)
-               return error("Couldn't open %s\n", filename);
+               return error("Couldn't open %s for %s\n", tmpfile, sha1_to_hex(sha1));
 
        memset(&stream, 0, sizeof(stream));
 
@@ -1462,7 +1450,7 @@ int write_sha1_from_fd(const unsigned char *sha1, int fd, char *buffer,
                size = read(fd, buffer + *bufposn, bufsize - *bufposn);
                if (size <= 0) {
                        close(local);
-                       unlink(filename);
+                       unlink(tmpfile);
                        if (!size)
                                return error("Connection closed?");
                        perror("Reading from connection");
@@ -1475,15 +1463,15 @@ int write_sha1_from_fd(const unsigned char *sha1, int fd, char *buffer,
        close(local);
        SHA1_Final(real_sha1, &c);
        if (ret != Z_STREAM_END) {
-               unlink(filename);
+               unlink(tmpfile);
                return error("File %s corrupted", sha1_to_hex(sha1));
        }
        if (memcmp(sha1, real_sha1, 20)) {
-               unlink(filename);
+               unlink(tmpfile);
                return error("File %s has bad hash\n", sha1_to_hex(sha1));
        }
-       
-       return 0;
+
+       return move_temp_to_file(tmpfile, sha1_file_name(sha1));
 }
 
 int has_pack_index(const unsigned char *sha1)
@@ -1545,3 +1533,42 @@ int index_fd(unsigned char *sha1, int fd, struct stat *st, int write_object, con
                munmap(buf, size);
        return ret;
 }
+
+int index_path(unsigned char *sha1, const char *path, struct stat *st, int write_object)
+{
+       int fd;
+       char *target;
+
+       switch (st->st_mode & S_IFMT) {
+       case S_IFREG:
+               fd = open(path, O_RDONLY);
+               if (fd < 0)
+                       return error("open(\"%s\"): %s", path,
+                                    strerror(errno));
+               if (index_fd(sha1, fd, st, write_object, NULL) < 0)
+                       return error("%s: failed to insert into database",
+                                    path);
+               break;
+       case S_IFLNK:
+               target = xmalloc(st->st_size+1);
+               if (readlink(path, target, st->st_size+1) != st->st_size) {
+                       char *errstr = strerror(errno);
+                       free(target);
+                       return error("readlink(\"%s\"): %s", path,
+                                    errstr);
+               }
+               if (!write_object) {
+                       unsigned char hdr[50];
+                       int hdrlen;
+                       write_sha1_file_prepare(target, st->st_size, "blob",
+                                               sha1, hdr, &hdrlen);
+               } else if (write_sha1_file(target, st->st_size, "blob", sha1))
+                       return error("%s: failed to insert into database",
+                                    path);
+               free(target);
+               break;
+       default:
+               return error("%s: unsupported file type", path);
+       }
+       return 0;
+}
index d0896f8183d87df53d80c6e5e72ebdb5932c29a8..fe409fbce4978535bdf38773fd4e2924a68733b9 100644 (file)
@@ -122,6 +122,9 @@ static int find_short_packed_object(int len, const unsigned char *match, unsigne
        return found;
 }
 
+#define SHORT_NAME_NOT_FOUND (-1)
+#define SHORT_NAME_AMBIGUOUS (-2)
+
 static int find_unique_short_object(int len, char *canonical,
                                    unsigned char *res, unsigned char *sha1)
 {
@@ -131,23 +134,24 @@ static int find_unique_short_object(int len, char *canonical,
        has_unpacked = find_short_object_filename(len, canonical, unpacked_sha1);
        has_packed = find_short_packed_object(len, res, packed_sha1);
        if (!has_unpacked && !has_packed)
-               return -1;
+               return SHORT_NAME_NOT_FOUND;
        if (1 < has_unpacked || 1 < has_packed)
-               return error("short SHA1 %.*s is ambiguous.", len, canonical);
+               return SHORT_NAME_AMBIGUOUS;
        if (has_unpacked != has_packed) {
                memcpy(sha1, (has_packed ? packed_sha1 : unpacked_sha1), 20);
                return 0;
        }
        /* Both have unique ones -- do they match? */
        if (memcmp(packed_sha1, unpacked_sha1, 20))
-               return error("short SHA1 %.*s is ambiguous.", len, canonical);
+               return -2;
        memcpy(sha1, packed_sha1, 20);
        return 0;
 }
 
-static int get_short_sha1(const char *name, int len, unsigned char *sha1)
+static int get_short_sha1(const char *name, int len, unsigned char *sha1,
+                         int quietly)
 {
-       int i;
+       int i, status;
        char canonical[40];
        unsigned char res[20];
 
@@ -174,7 +178,52 @@ static int get_short_sha1(const char *name, int len, unsigned char *sha1)
                res[i >> 1] |= val;
        }
 
-       return find_unique_short_object(i, canonical, res, sha1);
+       status = find_unique_short_object(i, canonical, res, sha1);
+       if (!quietly && (status == SHORT_NAME_AMBIGUOUS))
+               return error("short SHA1 %.*s is ambiguous.", len, canonical);
+       return status;
+}
+
+const char *find_unique_abbrev(const unsigned char *sha1, int len)
+{
+       int status;
+       static char hex[41];
+       memcpy(hex, sha1_to_hex(sha1), 40);
+       while (len < 40) {
+               unsigned char sha1_ret[20];
+               status = get_short_sha1(hex, len, sha1_ret, 1);
+               if (!status) {
+                       hex[len] = 0;
+                       return hex;
+               }
+               if (status != SHORT_NAME_AMBIGUOUS)
+                       return NULL;
+               len++;
+       }
+       return NULL;
+}
+
+static int ambiguous_path(const char *path)
+{
+       int slash = 1;
+
+       for (;;) {
+               switch (*path++) {
+               case '\0':
+                       break;
+               case '/':
+                       if (slash)
+                               break;
+                       slash = 1;
+                       continue;
+               case '.':
+                       continue;
+               default:
+                       slash = 0;
+                       continue;
+               }
+               return slash;
+       }
 }
 
 static int get_sha1_basic(const char *str, int len, unsigned char *sha1)
@@ -191,6 +240,10 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1)
        if (len == 40 && !get_sha1_hex(str, sha1))
                return 0;
 
+       /* Accept only unambiguous ref paths. */
+       if (ambiguous_path(str))
+               return -1;
+
        for (p = prefix; *p; p++) {
                char *pathname = git_path("%s/%.*s", *p, len, str);
                if (!read_ref(pathname, sha1))
@@ -297,6 +350,8 @@ static int peel_onion(const char *name, int len, unsigned char *sha1)
                return -1;
        if (!type_string) {
                o = deref_tag(o);
+               if (!o || (!o->parsed && !parse_object(o->sha1)))
+                       return -1;
                memcpy(sha1, o->sha1, 20);
        }
        else {
@@ -306,7 +361,7 @@ static int peel_onion(const char *name, int len, unsigned char *sha1)
                 */
 
                while (1) {
-                       if (!o)
+                       if (!o || (!o->parsed && !parse_object(o->sha1)))
                                return -1;
                        if (o->type == type_string) {
                                memcpy(sha1, o->sha1, 20);
@@ -375,7 +430,7 @@ static int get_sha1_1(const char *name, int len, unsigned char *sha1)
        ret = get_sha1_basic(name, len, sha1);
        if (!ret)
                return 0;
-       return get_short_sha1(name, len, sha1);
+       return get_short_sha1(name, len, sha1, 0);
 }
 
 /*
diff --git a/shell.c b/shell.c
new file mode 100644 (file)
index 0000000..2c4789e
--- /dev/null
+++ b/shell.c
@@ -0,0 +1,59 @@
+#include "cache.h"
+#include "quote.h"
+
+static int do_generic_cmd(const char *me, char *arg)
+{
+       const char *my_argv[4];
+
+       arg = sq_dequote(arg);
+       if (!arg)
+               die("bad argument");
+
+       my_argv[0] = me;
+       my_argv[1] = arg;
+       my_argv[2] = NULL;
+
+       return execvp(me, (char**) my_argv);
+}
+
+static struct commands {
+       const char *name;
+       int (*exec)(const char *me, char *arg);
+} cmd_list[] = {
+       { "git-receive-pack", do_generic_cmd },
+       { "git-upload-pack", do_generic_cmd },
+       { NULL },
+};
+
+int main(int argc, char **argv)
+{
+       char *prog;
+       struct commands *cmd;
+
+       /* We want to see "-c cmd args", and nothing else */
+       if (argc != 3 || strcmp(argv[1], "-c"))
+               die("What do you think I am? A shell?");
+
+       prog = argv[2];
+       argv += 2;
+       argc -= 2;
+       for (cmd = cmd_list ; cmd->name ; cmd++) {
+               int len = strlen(cmd->name);
+               char *arg;
+               if (strncmp(cmd->name, prog, len))
+                       continue;
+               arg = NULL;
+               switch (prog[len]) {
+               case '\0':
+                       arg = NULL;
+                       break;
+               case ' ':
+                       arg = prog + len + 1;
+                       break;
+               default:
+                       continue;
+               }
+               exit(cmd->exec(cmd->name, arg));
+       }
+       die("unrecognized command '%s'", prog);
+}
index ed83645e16bb3364780b93dc3d24722161929b3d..70120005be00bb38feb6d56740a9340f2f353373 100644 (file)
@@ -133,25 +133,28 @@ static void name_commits(struct commit_list *list,
                        nth = 0;
                        while (parents) {
                                struct commit *p = parents->item;
-                               char newname[1000];
+                               char newname[1000], *en;
                                parents = parents->next;
                                nth++;
                                if (p->object.util)
                                        continue;
+                               en = newname;
                                switch (n->generation) {
                                case 0:
-                                       sprintf(newname, "%s^%d",
-                                               n->head_name, nth);
+                                       en += sprintf(en, "%s", n->head_name);
                                        break;
                                case 1:
-                                       sprintf(newname, "%s^^%d",
-                                               n->head_name, nth);
+                                       en += sprintf(en, "%s^", n->head_name);
                                        break;
                                default:
-                                       sprintf(newname, "%s~%d^%d",
-                                               n->head_name, n->generation,
-                                               nth);
+                                       en += sprintf(en, "%s~%d",
+                                               n->head_name, n->generation);
+                                       break;
                                }
+                               if (nth == 1)
+                                       en += sprintf(en, "^");
+                               else
+                                       en += sprintf(en, "^%d", nth);
                                name_commit(p, strdup(newname), 0);
                                i++;
                                name_first_parent_chain(p);
@@ -205,7 +208,7 @@ static void join_revs(struct commit_list **list_p,
        }
 }
 
-static void show_one_commit(struct commit *commit)
+static void show_one_commit(struct commit *commit, int no_name)
 {
        char pretty[128], *cp;
        struct commit_name *name = commit->object.util;
@@ -218,11 +221,21 @@ static void show_one_commit(struct commit *commit)
                cp = pretty + 8;
        else
                cp = pretty;
-       if (name && name->head_name) {
-               printf("[%s", name->head_name);
-               if (name->generation)
-                       printf("~%d", name->generation);
-               printf("] ");
+
+       if (!no_name) {
+               if (name && name->head_name) {
+                       printf("[%s", name->head_name);
+                       if (name->generation) {
+                               if (name->generation == 1)
+                                       printf("^");
+                               else
+                                       printf("~%d", name->generation);
+                       }
+                       printf("] ");
+               }
+               else
+                       printf("[%s] ",
+                              find_unique_abbrev(commit->object.sha1, 7));
        }
        puts(cp);
 }
@@ -354,7 +367,8 @@ int main(int ac, char **av)
        unsigned char head_sha1[20];
        int merge_base = 0;
        int independent = 0;
-       char **label;
+       int no_name = 0;
+       int sha1_name = 0;
 
        setup_git_directory();
 
@@ -370,6 +384,10 @@ int main(int ac, char **av)
                        extra = 1;
                else if (!strcmp(arg, "--list"))
                        extra = -1;
+               else if (!strcmp(arg, "--no-name"))
+                       no_name = 1;
+               else if (!strcmp(arg, "--sha1-name"))
+                       sha1_name = 1;
                else if (!strncmp(arg, "--more=", 7))
                        extra = atoi(arg + 7);
                else if (!strcmp(arg, "--merge-base"))
@@ -465,7 +483,8 @@ int main(int ac, char **av)
                                printf("%c [%s] ",
                                       is_head ? '*' : '!', ref_name[i]);
                        }
-                       show_one_commit(rev[i]);
+                       /* header lines never need name */
+                       show_one_commit(rev[i], 1);
                }
                if (0 <= extra) {
                        for (i = 0; i < num_rev; i++)
@@ -480,7 +499,8 @@ int main(int ac, char **av)
        sort_in_topological_order(&seen);
 
        /* Give names to commits */
-       name_commits(seen, rev, ref_name, num_rev);
+       if (!sha1_name && !no_name)
+               name_commits(seen, rev, ref_name, num_rev);
 
        all_mask = ((1u << (REV_SHIFT + num_rev)) - 1);
        all_revs = all_mask & ~((1u << REV_SHIFT) - 1);
@@ -490,7 +510,6 @@ int main(int ac, char **av)
                struct commit *commit = pop_one_commit(&seen);
                int this_flag = commit->object.flags;
                int is_merge_point = (this_flag & all_revs) == all_revs;
-               static char *obvious[] = { "" };
 
                if (is_merge_point)
                        shown_merge_point = 1;
@@ -501,9 +520,7 @@ int main(int ac, char **av)
                                        ? '+' : ' ');
                        putchar(' ');
                }
-               show_one_commit(commit);
-               if (num_rev == 1)
-                       label = obvious;
+               show_one_commit(commit, no_name);
                if (shown_merge_point && is_merge_point)
                        if (--extra < 0)
                                break;
index e71da7782ecce282a2c0098cc1d10d2171a5a954..5c76afff83087ee4d6324699f7ba376c18201716 100644 (file)
@@ -7,10 +7,16 @@
 SHELL_PATH ?= $(SHELL)
 TAR ?= $(TAR)
 
+# Shell quote;
+# Result of this needs to be placed inside ''
+shq = $(subst ','\'',$(1))
+# This has surrounding ''
+shellquote = '$(call shq,$(1))'
+
 T = $(wildcard t[0-9][0-9][0-9][0-9]-*.sh)
 
 all:
-       @$(foreach t,$T,echo "*** $t ***"; $(SHELL_PATH) $t $(GIT_TEST_OPTS) || exit; )
+       @$(foreach t,$T,echo "*** $t ***"; $(call shellquote,$(SHELL_PATH)) $t $(GIT_TEST_OPTS) || exit; )
        @rm -fr trash
 
 clean:
index a912f435aa67d7b2cde09f0b8c9855442c6c2377..745a1b03116a58f89cc9ac533f948fa91c795518 100755 (executable)
@@ -29,7 +29,13 @@ compare_diff_raw_z () {
 compare_diff_patch () {
     # When heuristics are improved, the score numbers would change.
     # Ignore them while comparing.
-    sed -e '/^[dis]*imilarity index [0-9]*%$/d' <"$1" >.tmp-1
-    sed -e '/^[dis]*imilarity index [0-9]*%$/d' <"$2" >.tmp-2
+    sed -e '
+       /^[dis]*imilarity index [0-9]*%$/d
+       /^index [0-9a-f]*\.\.[0-9a-f]/d
+    ' <"$1" >.tmp-1
+    sed -e '
+       /^[dis]*imilarity index [0-9]*%$/d
+       /^index [0-9a-f]*\.\.[0-9a-f]/d
+    ' <"$2" >.tmp-2
     diff -u .tmp-1 .tmp-2 && rm -f .tmp-1 .tmp-2
 }
index bd940bd09b3df6ff6cecae4fa8a9db739ef000e4..dff7d6916374d6f1c9302970cef7d0ab7e0c75ad 100755 (executable)
@@ -28,12 +28,12 @@ test_expect_success \
     '.git/objects should be empty after git-init-db in an empty repo.' \
     'cmp -s /dev/null should-be-empty' 
 
-# also it should have 258 subdirectories; 256 fan-out, pack, and info.
-# 259 is counting "objects" itself
+# also it should have 2 subdirectories; no fan-out anymore, pack, and info.
+# 3 is counting "objects" itself
 find .git/objects -type d -print >full-of-directories
 test_expect_success \
-    '.git/objects should have 258 subdirectories.' \
-    'test $(wc -l < full-of-directories) = 259'
+    '.git/objects should have 3 subdirectories.' \
+    'test $(wc -l < full-of-directories) = 3'
 
 ################################################################
 # Basics of the basics
diff --git a/t/t1200-tutorial.sh b/t/t1200-tutorial.sh
new file mode 100644 (file)
index 0000000..35db799
--- /dev/null
@@ -0,0 +1,160 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Johannes Schindelin
+#
+
+test_description='Test git-rev-parse with different parent options'
+
+. ./test-lib.sh
+
+echo "Hello World" > hello
+echo "Silly example" > example
+
+git-update-index --add hello example
+
+test_expect_success 'blob' "test blob = \"$(git-cat-file -t 557db03)\""
+
+test_expect_success 'blob 557db03' "test \"Hello World\" = \"$(git-cat-file blob 557db03)\""
+
+echo "It's a new day for git" >>hello
+cat > diff.expect << EOF
+diff --git a/hello b/hello
+index 557db03..263414f 100644
+--- a/hello
++++ b/hello
+@@ -1 +1,2 @@
+ Hello World
++It's a new day for git
+EOF
+git-diff-files -p > diff.output
+test_expect_success 'git-diff-files -p' 'cmp diff.expect diff.output'
+git diff > diff.output
+test_expect_success 'git diff' 'cmp diff.expect diff.output'
+
+tree=$(git-write-tree 2>/dev/null)
+
+test_expect_success 'tree' "test 8988da15d077d4829fc51d8544c097def6644dbb = $tree"
+
+output="$(echo "Initial commit" | git-commit-tree $(git-write-tree) 2>&1 > .git/refs/heads/master)"
+
+test_expect_success 'commit' "test 'Committing initial tree 8988da15d077d4829fc51d8544c097def6644dbb' = \"$output\""
+
+git-diff-index -p HEAD > diff.output
+test_expect_success 'git-diff-index -p HEAD' 'cmp diff.expect diff.output'
+
+git diff HEAD > diff.output
+test_expect_success 'git diff HEAD' 'cmp diff.expect diff.output'
+
+#rm hello
+#test_expect_success 'git-read-tree --reset HEAD' "git-read-tree --reset HEAD ; test \"hello: needs update\" = \"$(git-update-index --refresh)\""
+
+cat > whatchanged.expect << EOF
+diff-tree VARIABLE (from root)
+Author: VARIABLE
+Date:   VARIABLE
+
+    Initial commit
+
+diff --git a/example b/example
+new file mode 100644
+index 0000000..f24c74a
+--- /dev/null
++++ b/example
+@@ -0,0 +1 @@
++Silly example
+diff --git a/hello b/hello
+new file mode 100644
+index 0000000..557db03
+--- /dev/null
++++ b/hello
+@@ -0,0 +1 @@
++Hello World
+EOF
+
+git-whatchanged -p --root | \
+       sed -e "1s/^\(.\{10\}\).\{40\}/\1VARIABLE/" \
+               -e "2,3s/^\(.\{8\}\).*$/\1VARIABLE/" \
+> whatchanged.output
+test_expect_success 'git-whatchanged -p --root' 'cmp whatchanged.expect whatchanged.output'
+
+git tag my-first-tag
+test_expect_success 'git tag my-first-tag' 'cmp .git/refs/heads/master .git/refs/tags/my-first-tag'
+
+# TODO: test git-clone
+
+git checkout -b mybranch
+test_expect_success 'git checkout -b mybranch' 'cmp .git/refs/heads/master .git/refs/heads/mybranch'
+
+cat > branch.expect <<EOF
+  master
+* mybranch
+EOF
+
+git branch > branch.output
+test_expect_success 'git branch' 'cmp branch.expect branch.output'
+
+git checkout mybranch
+echo "Work, work, work" >>hello
+git commit -m 'Some work.' hello
+
+git checkout master
+
+echo "Play, play, play" >>hello
+echo "Lots of fun" >>example
+git commit -m 'Some fun.' hello example
+
+test_expect_failure 'git resolve now fails' 'git resolve HEAD mybranch "Merge work in mybranch"'
+
+cat > hello << EOF
+Hello World
+It's a new day for git
+Play, play, play
+Work, work, work
+EOF
+
+git commit -m 'Merged "mybranch" changes.' hello
+
+cat > show-branch.expect << EOF
+* [master] Merged "mybranch" changes.
+ ! [mybranch] Some work.
+--
++  [master] Merged "mybranch" changes.
+++ [mybranch] Some work.
+EOF
+
+git show-branch master mybranch > show-branch.output
+test_expect_success 'git show-branch' 'cmp show-branch.expect show-branch.output'
+
+git checkout mybranch
+
+cat > resolve.expect << EOF
+Updating from VARIABLE to VARIABLE.
+ example |    1 +
+ hello   |    1 +
+ 2 files changed, 2 insertions(+), 0 deletions(-)
+EOF
+
+git resolve HEAD master "Merge upstream changes." | \
+       sed -e "1s/[0-9a-f]\{40\}/VARIABLE/g" > resolve.output
+test_expect_success 'git resolve' 'cmp resolve.expect resolve.output'
+
+cat > show-branch2.expect << EOF
+! [master] Merged "mybranch" changes.
+ * [mybranch] Merged "mybranch" changes.
+--
+++ [master] Merged "mybranch" changes.
+EOF
+
+git show-branch master mybranch > show-branch2.output
+test_expect_success 'git show-branch' 'cmp show-branch2.expect show-branch2.output'
+
+# TODO: test git fetch
+
+# TODO: test git push
+
+test_expect_success 'git repack' 'git repack'
+test_expect_success 'git prune-packed' 'git prune-packed'
+test_expect_failure '-> only packed objects' 'find -type f .git/objects/[0-9a-f][0-9a-f]'
+
+test_done
+
diff --git a/t/t3300-funny-names.sh b/t/t3300-funny-names.sh
new file mode 100755 (executable)
index 0000000..897c378
--- /dev/null
@@ -0,0 +1,142 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+
+test_description='Pathnames with funny characters.
+
+This test tries pathnames with funny characters in the working
+tree, index, and tree objects.
+'
+
+# since FAT/NTFS does not allow tabs in filenames, skip this test
+test "$(uname -o 2>/dev/null)" = Cygwin && exit 0
+
+. ./test-lib.sh
+
+p0='no-funny'
+p1='tabs       and spaces'
+p2='just space'
+
+cat >"$p0" <<\EOF
+1. A quick brown fox jumps over the lazy cat, oops dog.
+2. A quick brown fox jumps over the lazy cat, oops dog.
+3. A quick brown fox jumps over the lazy cat, oops dog.
+EOF
+
+cat >"$p1" "$p0"
+echo 'Foo Bar Baz' >"$p2"
+
+echo 'just space
+no-funny' >expected
+test_expect_success 'git-ls-files no-funny' \
+       'git-update-index --add "$p0" "$p2" &&
+       git-ls-files >current &&
+       diff -u expected current'
+
+t0=`git-write-tree`
+echo "$t0" >t0
+
+echo 'just space
+no-funny
+"tabs\tand spaces"' >expected
+test_expect_success 'git-ls-files with-funny' \
+       'git-update-index --add "$p1" &&
+       git-ls-files >current &&
+       diff -u expected current'
+
+echo 'just space
+no-funny
+tabs   and spaces' >expected
+test_expect_success 'git-ls-files -z with-funny' \
+       'git-ls-files -z | tr \\0 \\012 >current &&
+       diff -u expected current'
+
+t1=`git-write-tree`
+echo "$t1" >t1
+
+echo 'just space
+no-funny
+"tabs\tand spaces"' >expected
+test_expect_success 'git-ls-tree with funny' \
+       'git-ls-tree -r $t1 | sed -e "s/^[^     ]*      //" >current &&
+        diff -u expected current'
+
+echo 'A        "tabs\tand spaces"' >expected
+test_expect_success 'git-diff-index with-funny' \
+       'git-diff-index --name-status $t0 >current &&
+       diff -u expected current'
+
+test_expect_success 'git-diff-tree with-funny' \
+       'git-diff-tree --name-status $t0 $t1 >current &&
+       diff -u expected current'
+
+echo 'A
+tabs   and spaces' >expected
+test_expect_success 'git-diff-index -z with-funny' \
+       'git-diff-index -z --name-status $t0 | tr \\0 \\012 >current &&
+       diff -u expected current'
+
+test_expect_success 'git-diff-tree -z with-funny' \
+       'git-diff-tree -z --name-status $t0 $t1 | tr \\0 \\012 >current &&
+       diff -u expected current'
+
+echo 'CNUM     no-funny        "tabs\tand spaces"' >expected
+test_expect_success 'git-diff-tree -C with-funny' \
+       'git-diff-tree -C --find-copies-harder --name-status \
+               $t0 $t1 | sed -e 's/^C[0-9]*/CNUM/' >current &&
+       diff -u expected current'
+
+echo 'RNUM     no-funny        "tabs\tand spaces"' >expected
+test_expect_success 'git-diff-tree delete with-funny' \
+       'git-update-index --force-remove "$p0" &&
+       git-diff-index -M --name-status \
+               $t0 | sed -e 's/^R[0-9]*/RNUM/' >current &&
+       diff -u expected current'
+
+echo 'diff --git a/no-funny "b/tabs\tand spaces"
+similarity index NUM%
+rename from no-funny
+rename to "tabs\tand spaces"' >expected
+
+test_expect_success 'git-diff-tree delete with-funny' \
+       'git-diff-index -M -p $t0 |
+        sed -e "s/index [0-9]*%/index NUM%/" >current &&
+        diff -u expected current'
+
+chmod +x "$p1"
+echo 'diff --git a/no-funny "b/tabs\tand spaces"
+old mode 100644
+new mode 100755
+similarity index NUM%
+rename from no-funny
+rename to "tabs\tand spaces"' >expected
+
+test_expect_success 'git-diff-tree delete with-funny' \
+       'git-diff-index -M -p $t0 |
+        sed -e "s/index [0-9]*%/index NUM%/" >current &&
+        diff -u expected current'
+
+echo >expected ' "tabs\tand spaces"
+ 1 files changed, 0 insertions(+), 0 deletions(-)'
+test_expect_success 'git-diff-tree rename with-funny applied' \
+       'git-diff-index -M -p $t0 |
+        git-apply --stat | sed -e "s/|.*//" -e "s/ *\$//" >current &&
+        diff -u expected current'
+
+echo >expected ' no-funny
+ "tabs\tand spaces"
+ 2 files changed, 3 insertions(+), 3 deletions(-)'
+
+test_expect_success 'git-diff-tree delete with-funny applied' \
+       'git-diff-index -p $t0 |
+        git-apply --stat | sed -e "s/|.*//" -e "s/ *\$//" >current &&
+        diff -u expected current'
+
+test_expect_success 'git-apply non-git diff' \
+       'git-diff-index -p $t0 |
+        sed -ne "/^[-+@]/p" |
+        git-apply --stat | sed -e "s/|.*//" -e "s/ *\$//" >current &&
+        diff -u expected current'
+
+test_done
index f3b6330a9b4af0e68d9e402ab7d82e600c939ccc..beb6d8f4877c157bdf9e3efa2c914e74da863cd5 100755 (executable)
@@ -7,6 +7,7 @@ test_description='Test built-in diff output engine.
 
 '
 . ./test-lib.sh
+. ../diff-lib.sh
 
 echo >path0 'Line 1
 Line 2
@@ -48,6 +49,6 @@ EOF
 
 test_expect_success \
     'validate git-diff-files -p output.' \
-    'cmp -s current expected'
+    'compare_diff_patch current expected'
 
 test_done
index be474856824f8e6659151590ed5acff38187e97d..2e3c20d6b9468bf413e97d422e7dbe13ac4238cd 100755 (executable)
@@ -7,6 +7,7 @@ test_description='Test rename detection in diff engine.
 
 '
 . ./test-lib.sh
+. ../diff-lib.sh
 
 echo >path0 'Line 1
 Line 2
@@ -61,6 +62,6 @@ EOF
 
 test_expect_success \
     'validate the output.' \
-    'diff -I "similarity.*" >/dev/null current expected'
+    'compare_diff_patch current expected'
 
 test_done
index f59614ae255b33f450a784200716c9fd63b0a054..a23aaa0a9471c68b233480cf34c7115d1f40e154 100755 (executable)
@@ -10,6 +10,7 @@ copy of symbolic links, but should not produce rename/copy followed
 by an edit for them.
 '
 . ./test-lib.sh
+. ../diff-lib.sh
 
 test_expect_success \
     'prepare reference tree' \
@@ -61,6 +62,6 @@ EOF
 
 test_expect_success \
     'validate diff output' \
-    'diff -u current expected'
+    'compare_diff_patch current expected'
 
 test_done
index 5dffb8e1e535b47269821b24816dd9465388f9a8..4db1bb11425797b5b105fcb7b92f443616d58355 100755 (executable)
@@ -41,8 +41,8 @@ test_expect_success \
      find a -type l | xargs git-update-index --add &&
      treeid=`git-write-tree` &&
      echo $treeid >treeid &&
-     TZ=GMT GIT_COMMITTER_DATE="2005-05-27 22:00:00" \
-     git-commit-tree $treeid </dev/null >.git/HEAD'
+     git-update-ref HEAD $(TZ=GMT GIT_COMMITTER_DATE="2005-05-27 22:00:00" \
+     git-commit-tree $treeid </dev/null)'
 
 test_expect_success \
     'git-tar-tree' \
@@ -59,7 +59,7 @@ test_expect_success \
 test_expect_success \
     'git-get-tar-commit-id' \
     'git-get-tar-commit-id <b.tar >b.commitid &&
-     diff .git/HEAD b.commitid'
+     diff .git/$(git-symbolic-ref HEAD) b.commitid'
 
 test_expect_success \
     'extract tar archive' \
index bb62336f267086cc85bf0c6d5133560c581f5973..5b50536b54ff84b6a6e51c210be39773c6879443 100755 (executable)
@@ -49,7 +49,7 @@ test_expect_success \
      git-unpack-objects <test-1-${packname_1}.pack"
 
 unset GIT_OBJECT_DIRECTORY
-cd $TRASH/.git2
+cd "$TRASH/.git2"
 
 test_expect_success \
     'check unpack without delta' \
@@ -61,7 +61,7 @@ test_expect_success \
             return 1
         }
      done'
-cd $TRASH
+cd "$TRASH"
 
 test_expect_success \
     'pack with delta' \
@@ -80,7 +80,7 @@ test_expect_success \
      git-unpack-objects <test-2-${packname_2}.pack'
 
 unset GIT_OBJECT_DIRECTORY
-cd $TRASH/.git2
+cd "$TRASH/.git2"
 test_expect_success \
     'check unpack with delta' \
     '(cd ../.git && find objects -type f -print) |
@@ -91,7 +91,7 @@ test_expect_success \
             return 1
         }
      done'
-cd $TRASH
+cd "$TRASH"
 
 rm -fr .git2
 mkdir .git2
@@ -165,4 +165,22 @@ test_expect_success \
 
      :'
 
+test_expect_success \
+    'build pack index for an existing pack' \
+    'cp test-1-${packname_1}.pack test-3.pack &&
+     git-index-pack -o tmp.idx test-3.pack &&
+     cmp tmp.idx test-1-${packname_1}.idx &&
+
+     git-index-pack test-3.pack &&
+     cmp test-3.idx test-1-${packname_1}.idx &&
+
+     cp test-2-${packname_2}.pack test-3.pack &&
+     git-index-pack -o tmp.idx test-2-${packname_2}.pack &&
+     cmp tmp.idx test-2-${packname_2}.idx &&
+
+     git-index-pack test-3.pack &&
+     cmp test-3.idx test-2-${packname_2}.idx &&
+
+     :'
+
 test_done
diff --git a/t/t5500-fetch-pack.sh b/t/t5500-fetch-pack.sh
new file mode 100644 (file)
index 0000000..0781bd2
--- /dev/null
@@ -0,0 +1,136 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Johannes Schindelin
+#
+
+test_description='Testing multi_ack pack fetching
+
+'
+. ./test-lib.sh
+
+# Test fetch-pack/upload-pack pair.
+
+# Some convenience functions
+
+function show_count () {
+       commit_count=$(($commit_count+1))
+       printf "      %d\r" $commit_count
+}
+
+function add () {
+       local name=$1
+       local text="$@"
+       local branch=${name:0:1}
+       local parents=""
+
+       shift
+       while test $1; do
+               parents="$parents -p $1"
+               shift
+       done
+
+       echo "$text" > test.txt
+       git-update-index --add test.txt
+       tree=$(git-write-tree)
+       # make sure timestamps are in correct order
+       sec=$(($sec+1))
+       commit=$(echo "$text" | GIT_AUTHOR_DATE=$sec \
+               git-commit-tree $tree $parents 2>>log2.txt)
+       export $name=$commit
+       echo $commit > .git/refs/heads/$branch
+       eval ${branch}TIP=$commit
+}
+
+function count_objects () {
+       ls .git/objects/??/* 2>>log2.txt | wc -l | tr -d " "
+}
+
+function test_expect_object_count () {
+       local message=$1
+       local count=$2
+
+       output="$(count_objects)"
+       test_expect_success \
+               "new object count $message" \
+               "test $count = $output"
+}
+
+function test_repack () {
+       local rep=$1
+
+       test_expect_success "repack && prune-packed in $rep" \
+               '(git-repack && git-prune-packed)2>>log.txt'
+}
+
+function pull_to_client () {
+       local number=$1
+       local heads=$2
+       local count=$3
+       local no_strict_count_check=$4
+
+       cd client
+       test_expect_success "$number pull" \
+               "git-fetch-pack -v .. $heads > log.txt 2>&1"
+       case "$heads" in *A*) echo $ATIP > .git/refs/heads/A;; esac
+       case "$heads" in *B*) echo $BTIP > .git/refs/heads/B;; esac
+       git-symbolic-ref HEAD refs/heads/${heads:0:1}
+       test_expect_success "fsck" 'git-fsck-objects --full > fsck.txt 2>&1'
+       test_expect_object_count "after $number pull" $count
+       pack_count=$(grep Packing log.txt|tr -dc "0-9")
+       test -z "$pack_count" && pack_count=0
+       if [ -z "$no_strict_count_check" ]; then
+               test_expect_success "minimal count" "test $count = $pack_count"
+       else
+               test $count != $pack_count && \
+                       echo "WARNING: $pack_count objects transmitted, only $count of which were needed"
+       fi
+       cd ..
+}
+
+# Here begins the actual testing
+
+# A1 - ... - A20 - A21
+#    \
+#      B1  -   B2 - .. - B70
+
+# client pulls A20, B1. Then tracks only B. Then pulls A.
+
+(
+       mkdir client &&
+       cd client &&
+       git-init-db 2>> log2.txt
+)
+
+add A1
+
+prev=1; cur=2; while [ $cur -le 10 ]; do
+       add A$cur $(eval echo \$A$prev)
+       prev=$cur
+       cur=$(($cur+1))
+done
+
+add B1 $A1
+
+echo $ATIP > .git/refs/heads/A
+echo $BTIP > .git/refs/heads/B
+git-symbolic-ref HEAD refs/heads/B
+
+pull_to_client 1st "B A" $((11*3))
+
+(cd client; test_repack client)
+
+add A11 $A10
+
+prev=1; cur=2; while [ $cur -le 65 ]; do
+       add B$cur $(eval echo \$B$prev)
+       prev=$cur
+       cur=$(($cur+1))
+done
+
+pull_to_client 2nd "B" $((64*3))
+
+(cd client; test_repack client)
+
+pull_to_client 3rd "A" $((1*3)) # old fails
+
+test_done
diff --git a/t/t5501-old-fetch-and-upload.sh b/t/t5501-old-fetch-and-upload.sh
new file mode 100755 (executable)
index 0000000..ada5130
--- /dev/null
@@ -0,0 +1,43 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Johannes Schindelin
+#
+
+# Test that the current fetch-pack/upload-pack plays nicely with
+# an old counterpart
+
+cd $(dirname $0) || exit 1
+
+tmp=$(mktemp /tmp/tmp-XXXXXXXX)
+
+retval=0
+
+if [ -z "$1" ]; then
+       list="fetch upload"
+else
+       list="$@"
+fi
+
+for i in $list; do
+       case "$i" in
+       fetch) pgm="old-git-fetch-pack"; replace="$pgm";;
+       upload) pgm="old-git-upload-pack"; replace="git-fetch-pack --exec=$pgm";;
+       both) pgm="old-git-upload-pack"; replace="old-git-fetch-pack --exec=$pgm";;
+       esac
+
+       if which $pgm 2>/dev/null; then
+               echo "Testing with $pgm"
+               sed -e "s/git-fetch-pack/$replace/g" \
+                       -e "s/# old fails/warn/" < t5500-fetch-pack.sh > $tmp
+
+               sh $tmp || retval=$?
+               rm $tmp
+
+               test $retval != 0 && exit $retval
+       else
+               echo "Skipping test for $i, since I cannot find $pgm"
+       fi
+done
+
+exit 0
+
index 010124238257f7bc0ae8eb75c5d71a79c7fdef7d..8ec9ebb98abf00d5b433e11b56efbd31bbce2c40 100755 (executable)
@@ -108,7 +108,7 @@ save_tag h2 unique_commit g4 tree -p g2
 save_tag g3 unique_commit g5 tree -p g2
 save_tag g4 unique_commit g6 tree -p g3 -p h2
 
-tag l5 > .git/HEAD
+git-update-ref HEAD $(tag l5)
 
 test_expect_success 'rev-list has correct number of entries' 'git-rev-list HEAD | wc -l | tr -s " "' <<EOF
 19
index 42fcbc60caa3c369591d96c9d4d2913dbc2de397..693de9b32dea04140d58ba98d0db07ac0e724a6b 100755 (executable)
@@ -58,7 +58,7 @@ on_committer_date "1971-08-16 00:00:15" save_tag a4 unique_commit a4 tree -p a3
 on_committer_date "1971-08-16 00:00:16" save_tag l3 unique_commit l3 tree -p a4
 on_committer_date "1971-08-16 00:00:17" save_tag l4 unique_commit l4 tree -p l3
 on_committer_date "1971-08-16 00:00:18" save_tag l5 unique_commit l5 tree -p l4
-tag l5 > .git/HEAD
+git-update-ref HEAD $(tag l5)
 
 
 #     E
index 88d14ee1a3419f06c45a30e42a79934c4284c465..3c4c44c24d0781a1f876e7d4d016bf95f070e232 100755 (executable)
@@ -77,7 +77,7 @@ save_tag h2 unique_commit g4 tree -p g2
 save_tag g3 unique_commit g5 tree -p g2
 save_tag g4 unique_commit g6 tree -p g3 -p h2
 
-tag l5 > .git/HEAD
+git-update-ref HEAD $(tag l5)
 
 test_expect_success 'rev-list has correct number of entries' 'git-rev-list HEAD | wc -l | tr -s " "' <<EOF
 19
index 3f8a6fe4145e673e77fd6e18548a5622a5c0400e..a8f239df8fc45575ca543208c2bcad771d3f4d7e 100755 (executable)
@@ -164,4 +164,8 @@ test=trash
 rm -fr "$test"
 mkdir "$test"
 cd "$test"
-git-init-db 2>/dev/null || error "cannot run git-init-db"
+git-init-db --template=../../templates/blt/ 2>/dev/null ||
+error "cannot run git-init-db"
+
+mv .git/hooks .git/hooks-disabled
+
index ca680c5b9c59ed5327f8ba447c1525253c8d2222..6759ecbf98f8a90d96b4918c130babdd87889f69 100644 (file)
@@ -1 +1,2 @@
 blt
+boilerplates.made
index c23aee866d3eca13abd5586ee06bac6e2ab87dc7..07e928e56dd00678fdbe092b65bd16f7b7edfcc7 100644 (file)
@@ -6,6 +6,12 @@ prefix ?= $(HOME)
 template_dir ?= $(prefix)/share/git-core/templates/
 # DESTDIR=
 
+# Shell quote;
+# Result of this needs to be placed inside ''
+shq = $(subst ','\'',$(1))
+# This has surrounding ''
+shellquote = '$(call shq,$(1))'
+
 all: boilerplates.made custom
        find blt
 
@@ -38,6 +44,6 @@ clean:
        rm -rf blt boilerplates.made
 
 install: all
-       $(INSTALL) -d -m755 $(DESTDIR)$(template_dir)
+       $(INSTALL) -d -m755 $(call shellquote,$(DESTDIR)$(template_dir))
        (cd blt && $(TAR) cf - .) | \
-       (cd $(DESTDIR)$(template_dir) && $(TAR) xf -)
+       (cd $(call shellquote,$(DESTDIR)$(template_dir)) && $(TAR) xf -)
diff --git a/tree-diff.c b/tree-diff.c
new file mode 100644 (file)
index 0000000..0ef06a9
--- /dev/null
@@ -0,0 +1,270 @@
+/*
+ * Helper functions for tree diff generation
+ */
+#include "cache.h"
+#include "diff.h"
+
+// What paths are we interested in?
+static int nr_paths = 0;
+static const char **paths = NULL;
+static int *pathlens = NULL;
+
+static void update_tree_entry(struct tree_desc *desc)
+{
+       void *buf = desc->buf;
+       unsigned long size = desc->size;
+       int len = strlen(buf) + 1 + 20;
+
+       if (size < len)
+               die("corrupt tree file");
+       desc->buf = buf + len;
+       desc->size = size - len;
+}
+
+static const unsigned char *extract(struct tree_desc *desc, const char **pathp, unsigned int *modep)
+{
+       void *tree = desc->buf;
+       unsigned long size = desc->size;
+       int len = strlen(tree)+1;
+       const unsigned char *sha1 = tree + len;
+       const char *path = strchr(tree, ' ');
+       unsigned int mode;
+
+       if (!path || size < len + 20 || sscanf(tree, "%o", &mode) != 1)
+               die("corrupt tree file");
+       *pathp = path+1;
+       *modep = DIFF_FILE_CANON_MODE(mode);
+       return sha1;
+}
+
+static char *malloc_base(const char *base, const char *path, int pathlen)
+{
+       int baselen = strlen(base);
+       char *newbase = xmalloc(baselen + pathlen + 2);
+       memcpy(newbase, base, baselen);
+       memcpy(newbase + baselen, path, pathlen);
+       memcpy(newbase + baselen + pathlen, "/", 2);
+       return newbase;
+}
+
+static int show_entry(struct diff_options *opt, const char *prefix, struct tree_desc *desc, const char *base);
+
+static int compare_tree_entry(struct tree_desc *t1, struct tree_desc *t2, const char *base, struct diff_options *opt)
+{
+       unsigned mode1, mode2;
+       const char *path1, *path2;
+       const unsigned char *sha1, *sha2;
+       int cmp, pathlen1, pathlen2;
+
+       sha1 = extract(t1, &path1, &mode1);
+       sha2 = extract(t2, &path2, &mode2);
+
+       pathlen1 = strlen(path1);
+       pathlen2 = strlen(path2);
+       cmp = base_name_compare(path1, pathlen1, mode1, path2, pathlen2, mode2);
+       if (cmp < 0) {
+               show_entry(opt, "-", t1, base);
+               return -1;
+       }
+       if (cmp > 0) {
+               show_entry(opt, "+", t2, base);
+               return 1;
+       }
+       if (!opt->find_copies_harder &&
+           !memcmp(sha1, sha2, 20) && mode1 == mode2)
+               return 0;
+
+       /*
+        * If the filemode has changed to/from a directory from/to a regular
+        * file, we need to consider it a remove and an add.
+        */
+       if (S_ISDIR(mode1) != S_ISDIR(mode2)) {
+               show_entry(opt, "-", t1, base);
+               show_entry(opt, "+", t2, base);
+               return 0;
+       }
+
+       if (opt->recursive && S_ISDIR(mode1)) {
+               int retval;
+               char *newbase = malloc_base(base, path1, pathlen1);
+               if (opt->tree_in_recursive)
+                       opt->change(opt, mode1, mode2,
+                                   sha1, sha2, base, path1);
+               retval = diff_tree_sha1(sha1, sha2, newbase, opt);
+               free(newbase);
+               return retval;
+       }
+
+       opt->change(opt, mode1, mode2, sha1, sha2, base, path1);
+       return 0;
+}
+
+static int interesting(struct tree_desc *desc, const char *base)
+{
+       const char *path;
+       unsigned mode;
+       int i;
+       int baselen, pathlen;
+
+       if (!nr_paths)
+               return 1;
+
+       (void)extract(desc, &path, &mode);
+
+       pathlen = strlen(path);
+       baselen = strlen(base);
+
+       for (i=0; i < nr_paths; i++) {
+               const char *match = paths[i];
+               int matchlen = pathlens[i];
+
+               if (baselen >= matchlen) {
+                       /* If it doesn't match, move along... */
+                       if (strncmp(base, match, matchlen))
+                               continue;
+
+                       /* The base is a subdirectory of a path which was specified. */
+                       return 1;
+               }
+
+               /* Does the base match? */
+               if (strncmp(base, match, baselen))
+                       continue;
+
+               match += baselen;
+               matchlen -= baselen;
+
+               if (pathlen > matchlen)
+                       continue;
+
+               if (matchlen > pathlen) {
+                       if (match[pathlen] != '/')
+                               continue;
+                       if (!S_ISDIR(mode))
+                               continue;
+               }
+
+               if (strncmp(path, match, pathlen))
+                       continue;
+
+               return 1;
+       }
+       return 0; /* No matches */
+}
+
+/* A whole sub-tree went away or appeared */
+static void show_tree(struct diff_options *opt, const char *prefix, struct tree_desc *desc, const char *base)
+{
+       while (desc->size) {
+               if (interesting(desc, base))
+                       show_entry(opt, prefix, desc, base);
+               update_tree_entry(desc);
+       }
+}
+
+/* A file entry went away or appeared */
+static int show_entry(struct diff_options *opt, const char *prefix, struct tree_desc *desc, const char *base)
+{
+       unsigned mode;
+       const char *path;
+       const unsigned char *sha1 = extract(desc, &path, &mode);
+
+       if (opt->recursive && S_ISDIR(mode)) {
+               char type[20];
+               char *newbase = malloc_base(base, path, strlen(path));
+               struct tree_desc inner;
+               void *tree;
+
+               tree = read_sha1_file(sha1, type, &inner.size);
+               if (!tree || strcmp(type, "tree"))
+                       die("corrupt tree sha %s", sha1_to_hex(sha1));
+
+               inner.buf = tree;
+               show_tree(opt, prefix, &inner, newbase);
+
+               free(tree);
+               free(newbase);
+               return 0;
+       }
+
+       opt->add_remove(opt, prefix[0], mode, sha1, base, path);
+       return 0;
+}
+
+int diff_tree(struct tree_desc *t1, struct tree_desc *t2, const char *base, struct diff_options *opt)
+{
+       while (t1->size | t2->size) {
+               if (nr_paths && t1->size && !interesting(t1, base)) {
+                       update_tree_entry(t1);
+                       continue;
+               }
+               if (nr_paths && t2->size && !interesting(t2, base)) {
+                       update_tree_entry(t2);
+                       continue;
+               }
+               if (!t1->size) {
+                       show_entry(opt, "+", t2, base);
+                       update_tree_entry(t2);
+                       continue;
+               }
+               if (!t2->size) {
+                       show_entry(opt, "-", t1, base);
+                       update_tree_entry(t1);
+                       continue;
+               }
+               switch (compare_tree_entry(t1, t2, base, opt)) {
+               case -1:
+                       update_tree_entry(t1);
+                       continue;
+               case 0:
+                       update_tree_entry(t1);
+                       /* Fallthrough */
+               case 1:
+                       update_tree_entry(t2);
+                       continue;
+               }
+               die("git-diff-tree: internal error");
+       }
+       return 0;
+}
+
+int diff_tree_sha1(const unsigned char *old, const unsigned char *new, const char *base, struct diff_options *opt)
+{
+       void *tree1, *tree2;
+       struct tree_desc t1, t2;
+       int retval;
+
+       tree1 = read_object_with_reference(old, "tree", &t1.size, NULL);
+       if (!tree1)
+               die("unable to read source tree (%s)", sha1_to_hex(old));
+       tree2 = read_object_with_reference(new, "tree", &t2.size, NULL);
+       if (!tree2)
+               die("unable to read destination tree (%s)", sha1_to_hex(new));
+       t1.buf = tree1;
+       t2.buf = tree2;
+       retval = diff_tree(&t1, &t2, base, opt);
+       free(tree1);
+       free(tree2);
+       return retval;
+}
+
+static int count_paths(const char **paths)
+{
+       int i = 0;
+       while (*paths++)
+               i++;
+       return i;
+}
+
+void diff_tree_setup_paths(const char **p)
+{
+       if (p) {
+               int i;
+
+               paths = p;
+               nr_paths = count_paths(paths);
+               pathlens = xmalloc(nr_paths * sizeof(int));
+               for (i=0; i<nr_paths; i++)
+                       pathlens[i] = strlen(paths[i]);
+       }
+}
index b825a11d2f6d8a53f5e42e4f157ba036a13edb59..5bbc3de2898caddfd019fac77c6a1daf922250cf 100644 (file)
@@ -5,6 +5,7 @@
  */
 #include "cache.h"
 #include "strbuf.h"
+#include "quote.h"
 
 /*
  * Default to not allowing changes to the list of files. The
  * like "git-update-index *" and suddenly having all the object
  * files be revision controlled.
  */
-static int allow_add = 0, allow_remove = 0, allow_replace = 0, allow_unmerged = 0, not_new = 0, quiet = 0, info_only = 0;
+static int allow_add;
+static int allow_remove;
+static int allow_replace;
+static int allow_unmerged; /* --refresh needing merge is not error */
+static int not_new; /* --refresh not having working tree files is not error */
+static int quiet; /* --refresh needing update is not error */
+static int info_only;
 static int force_remove;
+static int verbose;
 
 /* Three functions to allow overloaded pointer return; see linux/err.h */
 static inline void *ERR_PTR(long error)
@@ -32,13 +40,24 @@ static inline long IS_ERR(const void *ptr)
        return (unsigned long)ptr > (unsigned long)-1000L;
 }
 
+static void report(const char *fmt, ...)
+{
+       va_list vp;
+
+       if (!verbose)
+               return;
+
+       va_start(vp, fmt);
+       vprintf(fmt, vp);
+       putchar('\n');
+       va_end(vp);
+}
+
 static int add_file_to_cache(const char *path)
 {
        int size, namelen, option, status;
        struct cache_entry *ce;
        struct stat st;
-       int fd;
-       char *target;
 
        status = lstat(path, &st);
        if (status < 0 || S_ISDIR(st.st_mode)) {
@@ -69,42 +88,27 @@ static int add_file_to_cache(const char *path)
                        return error("lstat(\"%s\"): %s", path,
                                     strerror(errno));
        }
+
        namelen = strlen(path);
        size = cache_entry_size(namelen);
        ce = xmalloc(size);
        memset(ce, 0, size);
        memcpy(ce->name, path, namelen);
        fill_stat_cache_info(ce, &st);
+
        ce->ce_mode = create_ce_mode(st.st_mode);
-       ce->ce_flags = htons(namelen);
-       switch (st.st_mode & S_IFMT) {
-       case S_IFREG:
-               fd = open(path, O_RDONLY);
-               if (fd < 0)
-                       return error("open(\"%s\"): %s", path, strerror(errno));
-               if (index_fd(ce->sha1, fd, &st, !info_only, NULL) < 0)
-                       return error("%s: failed to insert into database", path);
-               break;
-       case S_IFLNK:
-               target = xmalloc(st.st_size+1);
-               if (readlink(path, target, st.st_size+1) != st.st_size) {
-                       char *errstr = strerror(errno);
-                       free(target);
-                       return error("readlink(\"%s\"): %s", path,
-                                    errstr);
-               }
-               if (info_only) {
-                       unsigned char hdr[50];
-                       int hdrlen;
-                       write_sha1_file_prepare(target, st.st_size, "blob",
-                                               ce->sha1, hdr, &hdrlen);
-               } else if (write_sha1_file(target, st.st_size, "blob", ce->sha1))
-                       return error("%s: failed to insert into database", path);
-               free(target);
-               break;
-       default:
-               return error("%s: unsupported file type", path);
+       if (!trust_executable_bit) {
+               /* If there is an existing entry, pick the mode bits
+                * from it.
+                */
+               int pos = cache_name_pos(path, namelen);
+               if (0 <= pos)
+                       ce->ce_mode = active_cache[pos]->ce_mode;
        }
+       ce->ce_flags = htons(namelen);
+
+       if (index_path(ce->sha1, path, &st, !info_only))
+               return -1;
        option = allow_add ? ADD_CACHE_OK_TO_ADD : 0;
        option |= allow_replace ? ADD_CACHE_OK_TO_REPLACE : 0;
        if (add_cache_entry(ce, option))
@@ -277,11 +281,39 @@ static int add_cacheinfo(const char *arg1, const char *arg2, const char *arg3)
        ce->ce_mode = create_ce_mode(mode);
        option = allow_add ? ADD_CACHE_OK_TO_ADD : 0;
        option |= allow_replace ? ADD_CACHE_OK_TO_REPLACE : 0;
-       return add_cache_entry(ce, option);
+       if (add_cache_entry(ce, option))
+               return error("%s: cannot add to the index - missing --add option?",
+                            arg3);
+       report("add '%s'", arg3);
+       return 0;
 }
 
-static struct cache_file cache_file;
+static int chmod_path(int flip, const char *path)
+{
+       int pos;
+       struct cache_entry *ce;
+       unsigned int mode;
+
+       pos = cache_name_pos(path, strlen(path));
+       if (pos < 0)
+               return -1;
+       ce = active_cache[pos];
+       mode = ntohl(ce->ce_mode);
+       if (!S_ISREG(mode))
+               return -1;
+       switch (flip) {
+       case '+':
+               ce->ce_mode |= htonl(0111); break;
+       case '-':
+               ce->ce_mode &= htonl(~0111); break;
+       default:
+               return -1;
+       }
+       active_cache_changed = 1;
+       return 0;
+}
 
+static struct cache_file cache_file;
 
 static void update_one(const char *path, const char *prefix, int prefix_length)
 {
@@ -293,12 +325,76 @@ static void update_one(const char *path, const char *prefix, int prefix_length)
        if (force_remove) {
                if (remove_file_from_cache(p))
                        die("git-update-index: unable to remove %s", path);
+               report("remove '%s'", path);
                return;
        }
        if (add_file_to_cache(p))
                die("Unable to process file %s", path);
+       report("add '%s'", path);
+}
+
+static void read_index_info(int line_termination)
+{
+       struct strbuf buf;
+       strbuf_init(&buf);
+       while (1) {
+               char *ptr;
+               char *path_name;
+               unsigned char sha1[20];
+               unsigned int mode;
+
+               read_line(&buf, stdin, line_termination);
+               if (buf.eof)
+                       break;
+
+               mode = strtoul(buf.buf, &ptr, 8);
+               if (ptr == buf.buf || *ptr != ' ' ||
+                   get_sha1_hex(ptr + 1, sha1) ||
+                   ptr[41] != '\t')
+                       goto bad_line;
+
+               ptr += 42;
+
+               if (line_termination && ptr[0] == '"')
+                       path_name = unquote_c_style(ptr, NULL);
+               else
+                       path_name = ptr;
+
+               if (!verify_path(path_name)) {
+                       fprintf(stderr, "Ignoring path %s\n", path_name);
+                       if (path_name != ptr)
+                               free(path_name);
+                       continue;
+               }
+
+               if (!mode) {
+                       /* mode == 0 means there is no such path -- remove */
+                       if (remove_file_from_cache(path_name))
+                               die("git-update-index: unable to remove %s",
+                                   ptr);
+               }
+               else {
+                       /* mode ' ' sha1 '\t' name
+                        * ptr[-1] points at tab,
+                        * ptr[-41] is at the beginning of sha1
+                        */
+                       ptr[-42] = ptr[-1] = 0;
+                       if (add_cacheinfo(buf.buf, ptr-41, path_name))
+                               die("git-update-index: unable to update %s",
+                                   path_name);
+               }
+               if (path_name != ptr)
+                       free(path_name);
+               continue;
+
+       bad_line:
+               die("malformed index info %s", buf.buf);
+       }
 }
 
+static const char update_index_usage[] =
+"git-update-index [-q] [--add] [--replace] [--remove] [--unmerged] [--refresh] [--cacheinfo] [--chmod=(+|-)x] [--info-only] [--force-remove] [--stdin] [--index-info] [--ignore-missing] [-z] [--verbose] [--] <file>...";
+
 int main(int argc, const char **argv)
 {
        int i, newfd, entries, has_errors = 0, line_termination = '\n';
@@ -307,6 +403,8 @@ int main(int argc, const char **argv)
        const char *prefix = setup_git_directory();
        int prefix_length = prefix ? strlen(prefix) : 0;
 
+       git_config(git_default_config);
+
        newfd = hold_index_file_for_update(&cache_file, get_index_file());
        if (newfd < 0)
                die("unable to create new cachefile");
@@ -355,6 +453,14 @@ int main(int argc, const char **argv)
                                i += 3;
                                continue;
                        }
+                       if (!strcmp(path, "--chmod=-x") ||
+                           !strcmp(path, "--chmod=+x")) {
+                               if (argc <= i+1)
+                                       die("git-update-index: %s <path>", path);
+                               if (chmod_path(path[8], argv[++i]))
+                                       die("git-update-index: %s cannot chmod %s", path, argv[i]);
+                               continue;
+                       }
                        if (!strcmp(path, "--info-only")) {
                                info_only = 1;
                                continue;
@@ -373,10 +479,21 @@ int main(int argc, const char **argv)
                                read_from_stdin = 1;
                                break;
                        }
+                       if (!strcmp(path, "--index-info")) {
+                               allow_add = allow_replace = allow_remove = 1;
+                               read_index_info(line_termination);
+                               continue;
+                       }
                        if (!strcmp(path, "--ignore-missing")) {
                                not_new = 1;
                                continue;
                        }
+                       if (!strcmp(path, "--verbose")) {
+                               verbose = 1;
+                               continue;
+                       }
+                       if (!strcmp(path, "-h") || !strcmp(path, "--help"))
+                               usage(update_index_usage);
                        die("unknown option %s", path);
                }
                update_one(path, prefix, prefix_length);
index 4a1704c1a538ffde8eed46d4efb253b2d9b31b7f..65dc3d6385756a11aac257a3ed6a9dfcb68cce53 100644 (file)
@@ -1,6 +1,5 @@
 #include "cache.h"
 #include "refs.h"
-#include <ctype.h>
 
 static const char git_update_ref_usage[] = "git-update-ref <refname> <value> [<oldval>]";
 
index 80a5d0925a751fc3f2002db68dc019ecc01fffd7..c5eff21363245e1febe03aa8d4309f8cbb0cd7fa 100644 (file)
@@ -1,12 +1,18 @@
 #include "cache.h"
 #include "refs.h"
 #include "pkt-line.h"
+#include "tag.h"
+#include "object.h"
+#include "commit.h"
 
 static const char upload_pack_usage[] = "git-upload-pack [--strict] [--timeout=nn] <dir>";
 
-#define MAX_HAS (16)
-#define MAX_NEEDS (256)
-static int nr_has = 0, nr_needs = 0;
+#define THEY_HAVE (1U << 0)
+#define OUR_REF (1U << 1)
+#define WANTED (1U << 2)
+#define MAX_HAS 256
+#define MAX_NEEDS 256
+static int nr_has = 0, nr_needs = 0, multi_ack = 0, nr_our_refs = 0;
 static unsigned char has_sha1[MAX_HAS][20];
 static unsigned char needs_sha1[MAX_NEEDS][20];
 static unsigned int timeout = 0;
@@ -27,6 +33,7 @@ static void create_pack_file(void)
 {
        int fd[2];
        pid_t pid;
+       int create_full_pack = (nr_our_refs == nr_needs && !nr_has);
 
        if (pipe(fd) < 0)
                die("git-upload-pack: unable to create pipe");
@@ -41,8 +48,8 @@ static void create_pack_file(void)
                char *buf;
                char **p;
 
-               if (MAX_NEEDS <= nr_needs)
-                       args = nr_has + 10;
+               if (create_full_pack)
+                       args = 10;
                else
                        args = nr_has + nr_needs + 5;
                argv = xmalloc(args * sizeof(char *));
@@ -55,7 +62,7 @@ static void create_pack_file(void)
                close(fd[1]);
                *p++ = "git-rev-list";
                *p++ = "--objects";
-               if (MAX_NEEDS <= nr_needs)
+               if (create_full_pack || MAX_NEEDS <= nr_needs)
                        *p++ = "--all";
                else {
                        for (i = 0; i < nr_needs; i++) {
@@ -64,12 +71,13 @@ static void create_pack_file(void)
                                buf += 41;
                        }
                }
-               for (i = 0; i < nr_has; i++) {
-                       *p++ = buf;
-                       *buf++ = '^';
-                       memcpy(buf, sha1_to_hex(has_sha1[i]), 41);
-                       buf += 41;
-               }
+               if (!create_full_pack)
+                       for (i = 0; i < nr_has; i++) {
+                               *p++ = buf;
+                               *buf++ = '^';
+                               memcpy(buf, sha1_to_hex(has_sha1[i]), 41);
+                               buf += 41;
+                       }
                *p++ = NULL;
                execvp("git-rev-list", argv);
                die("git-upload-pack: unable to exec git-rev-list");
@@ -83,15 +91,27 @@ static void create_pack_file(void)
 
 static int got_sha1(char *hex, unsigned char *sha1)
 {
-       int nr;
        if (get_sha1_hex(hex, sha1))
                die("git-upload-pack: expected SHA1 object, got '%s'", hex);
        if (!has_sha1_file(sha1))
                return 0;
-       nr = nr_has;
-       if (nr < MAX_HAS) {
-               memcpy(has_sha1[nr], sha1, 20);
-               nr_has = nr+1;
+       if (nr_has < MAX_HAS) {
+               struct object *o = lookup_object(sha1);
+               if (!(o && o->parsed))
+                       o = parse_object(sha1);
+               if (!o)
+                       die("oops (%s)", sha1_to_hex(sha1));
+               if (o->type == commit_type) {
+                       struct commit_list *parents;
+                       if (o->flags & THEY_HAVE)
+                               return 0;
+                       o->flags |= THEY_HAVE;
+                       for (parents = ((struct commit*)o)->parents;
+                            parents;
+                            parents = parents->next)
+                               parents->item->object.flags |= THEY_HAVE;
+               }
+               memcpy(has_sha1[nr_has++], sha1, 20);
        }
        return 1;
 }
@@ -99,47 +119,47 @@ static int got_sha1(char *hex, unsigned char *sha1)
 static int get_common_commits(void)
 {
        static char line[1000];
-       unsigned char sha1[20];
+       unsigned char sha1[20], last_sha1[20];
        int len;
 
+       track_object_refs = 0;
+       save_commit_buffer = 0;
+
        for(;;) {
                len = packet_read_line(0, line, sizeof(line));
                reset_timeout();
 
                if (!len) {
-                       packet_write(1, "NAK\n");
+                       if (nr_has == 0 || multi_ack)
+                               packet_write(1, "NAK\n");
                        continue;
                }
                len = strip(line, len);
                if (!strncmp(line, "have ", 5)) {
-                       if (got_sha1(line+5, sha1)) {
-                               packet_write(1, "ACK %s\n", sha1_to_hex(sha1));
-                               break;
+                       if (got_sha1(line+5, sha1) &&
+                                       (multi_ack || nr_has == 1)) {
+                               if (nr_has >= MAX_HAS)
+                                       multi_ack = 0;
+                               packet_write(1, "ACK %s%s\n",
+                                       sha1_to_hex(sha1),
+                                       multi_ack ?  " continue" : "");
+                               if (multi_ack)
+                                       memcpy(last_sha1, sha1, 20);
                        }
                        continue;
                }
                if (!strcmp(line, "done")) {
+                       if (nr_has > 0) {
+                               if (multi_ack)
+                                       packet_write(1, "ACK %s\n",
+                                                       sha1_to_hex(last_sha1));
+                               return 0;
+                       }
                        packet_write(1, "NAK\n");
                        return -1;
                }
                die("git-upload-pack: expected SHA1 list, got '%s'", line);
        }
-
-       for (;;) {
-               len = packet_read_line(0, line, sizeof(line));
-               reset_timeout();
-               if (!len)
-                       continue;
-               len = strip(line, len);
-               if (!strncmp(line, "have ", 5)) {
-                       got_sha1(line+5, sha1);
-                       continue;
-               }
-               if (!strcmp(line, "done"))
-                       break;
-               die("git-upload-pack: expected SHA1 list, got '%s'", line);
-       }
-       return 0;
 }
 
 static int receive_needs(void)
@@ -149,6 +169,7 @@ static int receive_needs(void)
 
        needs = 0;
        for (;;) {
+               struct object *o;
                unsigned char dummy[20], *sha1_buf;
                len = packet_read_line(0, line, sizeof(line));
                reset_timeout();
@@ -168,13 +189,46 @@ static int receive_needs(void)
                if (strncmp("want ", line, 5) || get_sha1_hex(line+5, sha1_buf))
                        die("git-upload-pack: protocol error, "
                            "expected to get sha, not '%s'", line);
-               needs++;
+               if (strstr(line+45, "multi_ack"))
+                       multi_ack = 1;
+
+               /* We have sent all our refs already, and the other end
+                * should have chosen out of them; otherwise they are
+                * asking for nonsense.
+                *
+                * Hmph.  We may later want to allow "want" line that
+                * asks for something like "master~10" (symbolic)...
+                * would it make sense?  I don't know.
+                */
+               o = lookup_object(sha1_buf);
+               if (!o || !(o->flags & OUR_REF))
+                       die("git-upload-pack: not our ref %s", line+5);
+               if (!(o->flags & WANTED)) {
+                       o->flags |= WANTED;
+                       needs++;
+               }
        }
 }
 
 static int send_ref(const char *refname, const unsigned char *sha1)
 {
-       packet_write(1, "%s %s\n", sha1_to_hex(sha1), refname);
+       static char *capabilities = "multi_ack";
+       struct object *o = parse_object(sha1);
+
+       if (capabilities)
+               packet_write(1, "%s %s%c%s\n", sha1_to_hex(sha1), refname,
+                       0, capabilities);
+       else
+               packet_write(1, "%s %s\n", sha1_to_hex(sha1), refname);
+       capabilities = NULL;
+       if (!(o->flags & OUR_REF)) {
+               o->flags |= OUR_REF;
+               nr_our_refs++;
+       }
+       if (o->type == tag_type) {
+               o = deref_tag(o);
+               packet_write(1, "%s %s^{}\n", sha1_to_hex(o->sha1), refname);
+       }
        return 0;
 }
 
diff --git a/var.c b/var.c
index 3f13126cb8b88593fa81dda875e8e4080a27ae03..51cf86a5843acc3b6bc3d8c4be9fec0fdd0a0df5 100644 (file)
--- a/var.c
+++ b/var.c
@@ -42,6 +42,15 @@ static const char *read_var(const char *var)
        return val;
 }
 
+static int show_config(const char *var, const char *value)
+{
+       if (value)
+               printf("%s=%s\n", var, value);
+       else
+               printf("%s\n", var);
+       return git_default_config(var, value);
+}
+
 int main(int argc, char **argv)
 {
        const char *val;
@@ -52,9 +61,11 @@ int main(int argc, char **argv)
        val = NULL;
 
        if (strcmp(argv[1], "-l") == 0) {
+               git_config(show_config);
                list_vars();
                return 0;
        }
+       git_config(git_default_config);
        val = read_var(argv[1]);
        if (!val)
                usage(var_usage);
index 80b60a6b7cdfb2f45bb34331dbc372fcbabe8c5a..c99db9dd79315dff4ac19c79b35275cd02397e60 100644 (file)
@@ -15,12 +15,12 @@ static int verify_one_pack(char *arg, int verbose)
                        len--;
                }
                /* Should name foo.idx now */
-               if ((g = add_packed_git(arg, len)))
+               if ((g = add_packed_git(arg, len, 1)))
                        break;
                /* No?  did you name just foo? */
                strcpy(arg + len, ".idx");
                len += 4;
-               if ((g = add_packed_git(arg, len)))
+               if ((g = add_packed_git(arg, len, 1)))
                        break;
                return error("packfile %s not found.", arg);
        }