Merge branch 'maint'
authorJunio C Hamano <junkio@cox.net>
Fri, 7 Apr 2006 23:51:55 +0000 (16:51 -0700)
committerJunio C Hamano <junkio@cox.net>
Fri, 7 Apr 2006 23:51:55 +0000 (16:51 -0700)
* maint:
  count-delta: match get_delta_hdr_size() changes.
  check patch_delta bounds more carefully

253 files changed:
.gitignore
Documentation/Makefile
Documentation/SubmittingPatches
Documentation/asciidoc.conf
Documentation/diff-options.txt
Documentation/git-add.txt
Documentation/git-am.txt
Documentation/git-apply.txt
Documentation/git-applypatch.txt
Documentation/git-archimport.txt
Documentation/git-branch.txt
Documentation/git-check-ref-format.txt
Documentation/git-checkout-index.txt
Documentation/git-checkout.txt
Documentation/git-cherry-pick.txt
Documentation/git-cherry.txt
Documentation/git-clean.txt [new file with mode: 0644]
Documentation/git-clone-pack.txt
Documentation/git-clone.txt
Documentation/git-commit.txt
Documentation/git-count-objects.txt
Documentation/git-cvsimport.txt
Documentation/git-cvsserver.txt [new file with mode: 0644]
Documentation/git-daemon.txt
Documentation/git-describe.txt
Documentation/git-diff-stages.txt
Documentation/git-diff.txt
Documentation/git-fetch-pack.txt
Documentation/git-fetch.txt
Documentation/git-format-patch.txt
Documentation/git-fsck-objects.txt
Documentation/git-get-tar-commit-id.txt
Documentation/git-grep.txt
Documentation/git-hash-object.txt
Documentation/git-http-push.txt
Documentation/git-imap-send.txt [new file with mode: 0644]
Documentation/git-init-db.txt
Documentation/git-lost-found.txt
Documentation/git-ls-files.txt
Documentation/git-ls-remote.txt
Documentation/git-ls-tree.txt
Documentation/git-mailinfo.txt
Documentation/git-mailsplit.txt
Documentation/git-mv.txt
Documentation/git-name-rev.txt
Documentation/git-pack-objects.txt
Documentation/git-pack-redundant.txt
Documentation/git-patch-id.txt
Documentation/git-peek-remote.txt
Documentation/git-prune-packed.txt
Documentation/git-pull.txt
Documentation/git-push.txt
Documentation/git-read-tree.txt
Documentation/git-rebase.txt
Documentation/git-relink.txt
Documentation/git-repack.txt
Documentation/git-repo-config.txt
Documentation/git-request-pull.txt
Documentation/git-reset.txt
Documentation/git-rev-list.txt
Documentation/git-rev-parse.txt
Documentation/git-revert.txt
Documentation/git-rm.txt [new file with mode: 0644]
Documentation/git-send-email.txt
Documentation/git-send-pack.txt
Documentation/git-sh-setup.txt
Documentation/git-shell.txt
Documentation/git-shortlog.txt
Documentation/git-show-branch.txt
Documentation/git-show.txt
Documentation/git-status.txt
Documentation/git-stripspace.txt
Documentation/git-svnimport.txt
Documentation/git-tag.txt
Documentation/git-tools.txt [new file with mode: 0644]
Documentation/git-unpack-objects.txt
Documentation/git-update-index.txt
Documentation/git-update-ref.txt
Documentation/git-upload-pack.txt
Documentation/git-var.txt
Documentation/git-verify-pack.txt
Documentation/git-verify-tag.txt
Documentation/git-whatchanged.txt
Documentation/git.txt
Documentation/hooks.txt
Documentation/repository-layout.txt
Documentation/technical/pack-format.txt [new file with mode: 0644]
Documentation/technical/pack-heuristics.txt [new file with mode: 0644]
Documentation/tutorial.txt
GIT-VERSION-GEN
INSTALL
Makefile
apply.c
blame.c [new file with mode: 0644]
blob.c
cache.h
cat-file.c
checkout-index.c
combine-diff.c
commit-tree.c
commit.c
commit.h
config.c
contrib/README [new file with mode: 0644]
contrib/emacs/.gitignore [new file with mode: 0644]
contrib/emacs/Makefile [new file with mode: 0644]
contrib/emacs/git.el [new file with mode: 0644]
contrib/emacs/vc-git.el [new file with mode: 0644]
contrib/git-svn/.gitignore [new file with mode: 0644]
contrib/git-svn/Makefile [new file with mode: 0644]
contrib/git-svn/git-svn.perl [new file with mode: 0755]
contrib/git-svn/git-svn.txt [new file with mode: 0644]
contrib/git-svn/t/t0000-contrib-git-svn.sh [new file with mode: 0644]
contrib/gitview/gitview [new file with mode: 0755]
contrib/gitview/gitview.txt [new file with mode: 0644]
convert-objects.c
count-delta.c [deleted file]
count-delta.h [deleted file]
date.c
diff-delta.c
diff-files.c
diff-index.c
diff-tree.c
diff.c
diff.h
diffcore-break.c
diffcore-delta.c [new file with mode: 0644]
diffcore-pickaxe.c
diffcore-rename.c
diffcore.h
entry.c
environment.c
epoch.c [deleted file]
epoch.h [deleted file]
exec_cmd.c
exec_cmd.h
fetch-pack.c
fsck-objects.c
generate-cmdlist.sh [new file with mode: 0755]
git-add.sh
git-am.sh
git-annotate.perl [new file with mode: 0755]
git-archimport.perl
git-bisect.sh
git-branch.sh
git-clean.sh [new file with mode: 0755]
git-clone.sh
git-commit.sh
git-compat-util.h
git-cvsimport.perl
git-cvsserver.perl [new file with mode: 0755]
git-diff.sh
git-fetch.sh
git-fmt-merge-msg.perl
git-format-patch.sh
git-ls-remote.sh
git-merge.sh
git-mv.perl
git-parse-remote.sh
git-pull.sh
git-push.sh
git-rebase.sh
git-repack.sh
git-rerere.perl
git-resolve.sh
git-revert.sh
git-rm.sh [new file with mode: 0755]
git-send-email.perl
git-svnimport.perl
git-tag.sh
git-verify-tag.sh
git.c
gitk
hash-object.c
http-fetch.c
http-push.c
http.c
http.h
imap-send.c [new file with mode: 0644]
index-pack.c
ls-files.c
ls-tree.c
mailinfo.c
merge-base.c
merge-tree.c [new file with mode: 0644]
mktag.c
mktree.c [new file with mode: 0644]
name-rev.c
object.c
pack-check.c
pack-objects.c
pack-redundant.c
pager.c [new file with mode: 0644]
read-cache.c
read-tree.c
receive-pack.c
refs.c
repo-config.c
rev-list.c
rev-parse.c
revision.c [new file with mode: 0644]
revision.h [new file with mode: 0644]
run-command.c
run-command.h
send-pack.c
sha1_file.c
sha1_name.c
shell.c
show-branch.c
t/.gitignore [new file with mode: 0644]
t/Makefile
t/annotate-tests.sh [new file with mode: 0644]
t/t0000-basic.sh
t/t1200-tutorial.sh
t/t1300-repo-config.sh
t/t2004-checkout-cache-temp.sh [new file with mode: 0755]
t/t3020-ls-files-error-unmatch.sh [new file with mode: 0755]
t/t3600-rm.sh [new file with mode: 0755]
t/t5000-tar-tree.sh
t/t5600-clone-fail-cleanup.sh [new file with mode: 0755]
t/t6001-rev-list-merge-order.sh [deleted file]
t/t6021-merge-criss-cross.sh
t/t6022-merge-rename.sh
t/t7001-mv.sh
t/t8001-annotate.sh [new file with mode: 0755]
t/t8002-blame.sh [new file with mode: 0755]
t/test-lib.sh
tag.c
tar-tree.c
tar.h [new file with mode: 0644]
templates/hooks--pre-rebase [new file with mode: 0644]
tree-diff.c
tree-walk.c [new file with mode: 0644]
tree-walk.h [new file with mode: 0644]
tree.c
unpack-file.c
unpack-objects.c
update-index.c
update-ref.c
upload-pack.c
write-tree.c
xdiff/xdiff.h [new file with mode: 0644]
xdiff/xdiffi.c [new file with mode: 0644]
xdiff/xdiffi.h [new file with mode: 0644]
xdiff/xemit.c [new file with mode: 0644]
xdiff/xemit.h [new file with mode: 0644]
xdiff/xinclude.h [new file with mode: 0644]
xdiff/xmacros.h [new file with mode: 0644]
xdiff/xprepare.c [new file with mode: 0644]
xdiff/xprepare.h [new file with mode: 0644]
xdiff/xtypes.h [new file with mode: 0644]
xdiff/xutils.c [new file with mode: 0644]
xdiff/xutils.h [new file with mode: 0644]

index d7e8d2a..b5959d6 100644 (file)
@@ -2,6 +2,7 @@ GIT-VERSION-FILE
 git
 git-add
 git-am
+git-annotate
 git-apply
 git-applymbox
 git-applypatch
@@ -14,6 +15,7 @@ git-checkout
 git-checkout-index
 git-cherry
 git-cherry-pick
+git-clean
 git-clone
 git-clone-pack
 git-commit
@@ -22,6 +24,7 @@ git-convert-objects
 git-count-objects
 git-cvsexportcommit
 git-cvsimport
+git-cvsserver
 git-daemon
 git-diff
 git-diff-files
@@ -40,6 +43,7 @@ git-grep
 git-hash-object
 git-http-fetch
 git-http-push
+git-imap-send
 git-index-pack
 git-init-db
 git-local-fetch
@@ -53,6 +57,7 @@ git-mailsplit
 git-merge
 git-merge-base
 git-merge-index
+git-merge-tree
 git-merge-octopus
 git-merge-one-file
 git-merge-ours
@@ -60,6 +65,7 @@ git-merge-recursive
 git-merge-resolve
 git-merge-stupid
 git-mktag
+git-mktree
 git-name-rev
 git-mv
 git-pack-redundant
@@ -84,6 +90,7 @@ git-resolve
 git-rev-list
 git-rev-parse
 git-revert
+git-rm
 git-send-email
 git-send-pack
 git-sh-setup
@@ -116,12 +123,13 @@ git-write-tree
 git-core-*/?*
 test-date
 test-delta
+common-cmds.h
 *.tar.gz
 *.dsc
 *.deb
 git-core.spec
 *.exe
-libgit.a
-*.o
+*.[ao]
 *.py[co]
 config.mak
+git-blame
index a3bca86..f4cbf7e 100644 (file)
@@ -1,4 +1,7 @@
-MAN1_TXT=$(wildcard git-*.txt) gitk.txt
+MAN1_TXT= \
+       $(filter-out $(addsuffix .txt, $(ARTICLES) $(SP_ARTICLES)), \
+               $(wildcard git-*.txt)) \
+       gitk.txt
 MAN7_TXT=git.txt
 
 DOC_HTML=$(patsubst %.txt,%.html,$(MAN1_TXT) $(MAN7_TXT))
@@ -11,6 +14,7 @@ ARTICLES += howto-index
 ARTICLES += repository-layout
 ARTICLES += hooks
 ARTICLES += everyday
+ARTICLES += git-tools
 # with their own formatting rules.
 SP_ARTICLES = glossary howto/revert-branch-rebase
 
index 9ccb8f7..318b04f 100644 (file)
@@ -4,8 +4,8 @@ it for the core GIT to make sure people understand what they are
 doing when they write "Signed-off-by" line.
 
 But the patch submission requirements are a lot more relaxed
-here, because the core GIT is thousand times smaller ;-).  So
-here is only the relevant bits.
+here on the technical/contents front, because the core GIT is
+thousand times smaller ;-).  So here is only the relevant bits.
 
 
 (1) Make separate commits for logically separate changes.
@@ -18,13 +18,19 @@ repository.  It is a good discipline.
 
 Describe the technical detail of the change(s).
 
-If your description starts to get long, that's a sign that you
+If your description starts to get too long, that's a sign that you
 probably need to split up your commit to finer grained pieces.
 
+Oh, another thing.  I am picky about whitespaces.  Make sure your
+changes do not trigger errors with the sample pre-commit hook shipped
+in templates/hooks--pre-commit.
 
-(2) Generate your patch using git/cogito out of your commits.
 
-git diff tools generate unidiff which is the preferred format.
+(2) Generate your patch using git tools out of your commits.
+
+git based diff tools (git, Cogito, and StGIT included) generate
+unidiff which is the preferred format.
+
 You do not have to be afraid to use -M option to "git diff" or
 "git format-patch", if your patch involves file renames.  The
 receiving end can handle them just fine.
@@ -33,20 +39,22 @@ Please make sure your patch does not include any extra files
 which do not belong in a patch submission.  Make sure to review
 your patch after generating it, to ensure accuracy.  Before
 sending out, please make sure it cleanly applies to the "master"
-branch head.
+branch head.  If you are preparing a work based on "next" branch,
+that is fine, but please mark it as such.
 
 
 (3) Sending your patches.
 
-People on the git mailing list needs to be able to read and
+People on the git mailing list need to be able to read and
 comment on the changes you are submitting.  It is important for
 a developer to be able to "quote" your changes, using standard
 e-mail tools, so that they may comment on specific portions of
-your code.  For this reason, all patches should be submitting
-e-mail "inline".  WARNING: Be wary of your MUAs word-wrap
-corrupting your patch.  Do not cut-n-paste your patch.
+your code.  For this reason, all patches should be submited
+"inline".  WARNING: Be wary of your MUAs word-wrap
+corrupting your patch.  Do not cut-n-paste your patch; you can
+lose tabs that way if you are not careful.
 
-It is common convention to prefix your subject line with
+It is common convention to prefix your subject line with
 [PATCH].  This lets people easily distinguish patches from other
 e-mail discussions.
 
index fa0877d..7ce7151 100644 (file)
@@ -18,6 +18,16 @@ ifdef::backend-docbook[]
 {0#</citerefentry>}
 endif::backend-docbook[]
 
+ifdef::backend-docbook[]
+# "unbreak" docbook-xsl v1.68 for manpages. v1.69 works with or without this.
+[listingblock]
+<example><title>{title}</title>
+<literallayout>
+|
+</literallayout>
+{title#}</example>
+endif::backend-docbook[]
+
 ifdef::backend-xhtml11[]
 [gitlink-inlinemacro]
 <a href="{target}.html">{target}{0?({0})}</a>
index 2a0275e..ec6811c 100644 (file)
        changeset, not just the files that contain the change
        in <string>.
 
+--pickaxe-regex::
+       Make the <string> not a plain string but an extended POSIX
+       regex to match.
+
 -O<orderfile>::
        Output the patch in the order specified in the
        <orderfile>, which has one shell glob pattern per line.
index 7e29383..ae24547 100644 (file)
@@ -3,7 +3,7 @@ git-add(1)
 
 NAME
 ----
-git-add - Add files to the index file.
+git-add - Add files to the index file
 
 SYNOPSIS
 --------
@@ -65,6 +65,9 @@ git-add git-*.sh::
        (i.e. you are listing the files explicitly), it does not
        add `subdir/git-foo.sh` to the index.
 
+See Also
+--------
+gitlink:git-rm[1]
 
 Author
 ------
index 02cabc9..910457d 100644 (file)
@@ -9,7 +9,8 @@ git-am - Apply a series of patches in a mailbox
 SYNOPSIS
 --------
 [verse]
-'git-am' [--signoff] [--dotest=<dir>] [--utf8] [--binary] [--3way] <mbox>...
+'git-am' [--signoff] [--dotest=<dir>] [--utf8] [--binary] [--3way]
+         [--interactive] [--whitespace=<option>] <mbox>...
 'git-am' [--skip | --resolved]
 
 DESCRIPTION
@@ -46,6 +47,10 @@ OPTIONS
        Skip the current patch.  This is only meaningful when
        restarting an aborted patch.
 
+--whitespace=<option>::
+       This flag is passed to the `git-apply` program that applies
+       the patch.
+
 --interactive::
        Run interactively, just like git-applymbox.
 
@@ -80,7 +85,7 @@ names.
 
 SEE ALSO
 --------
-gitlink:git-applymbox[1], gitlink:git-applypatch[1].
+gitlink:git-applymbox[1], gitlink:git-applypatch[1], gitlink:git-apply[1].
 
 
 Author
index 75076b6..1c64a1a 100644 (file)
@@ -11,6 +11,7 @@ SYNOPSIS
 [verse]
 'git-apply' [--stat] [--numstat] [--summary] [--check] [--index] [--apply]
          [--no-add] [--index-info] [--allow-binary-replacement] [-z] [-pNUM]
+         [--whitespace=<nowarn|warn|error|error-all|strip>]
          [<patch>...]
 
 DESCRIPTION
@@ -97,6 +98,35 @@ OPTIONS
        result.  This allows binary files to be patched in a
        very limited way.
 
+--whitespace=<option>::
+       When applying a patch, detect a new or modified line
+       that ends with trailing whitespaces (this includes a
+       line that solely consists of whitespaces).  By default,
+       the command outputs warning messages and applies the
+       patch.
+       When `git-apply` is used for statistics and not applying a
+       patch, it defaults to `nowarn`.
+       You can use different `<option>` to control this
+       behaviour:
++
+* `nowarn` turns off the trailing whitespace warning.
+* `warn` outputs warnings for a few such errors, but applies the
+  patch (default).
+* `error` outputs warnings for a few such errors, and refuses
+  to apply the patch.
+* `error-all` is similar to `error` but shows all errors.
+* `strip` outputs warnings for a few such errors, strips out the
+  trailing whitespaces and applies the patch.
+
+
+Configuration
+-------------
+
+apply.whitespace::
+       When no `--whitespace` flag is given from the command
+       line, this configuration item is used as the default.
+
+
 Author
 ------
 Written by Linus Torvalds <torvalds@osdl.org>
index 5b9037d..2b1ff14 100644 (file)
@@ -3,7 +3,7 @@ git-applypatch(1)
 
 NAME
 ----
-git-applypatch - Apply one patch extracted from an e-mail.
+git-applypatch - Apply one patch extracted from an e-mail
 
 
 SYNOPSIS
index 023d3ae..5a13187 100644 (file)
@@ -9,7 +9,7 @@ git-archimport - Import an Arch repository into git
 SYNOPSIS
 --------
 [verse]
-`git-archimport` [-h] [-v] [-o] [-a] [-f] [-T] [-D depth] [-t tempdir]
+'git-archimport' [-h] [-v] [-o] [-a] [-f] [-T] [-D depth] [-t tempdir]
                <archive/branch> [ <archive/branch> ]
 
 DESCRIPTION
index b1bc827..71ecd85 100644 (file)
@@ -3,20 +3,24 @@ git-branch(1)
 
 NAME
 ----
-git-branch - Create a new branch, or remove an old one.
+git-branch - Create a new branch, or remove an old one
 
 SYNOPSIS
 --------
-'git-branch' [(-d | -D) <branchname>] | [[-f] <branchname> [<start-point>]]
+[verse]
+'git-branch' [[-f] <branchname> [<start-point>]]
+'git-branch' (-d | -D) <branchname>
 
 DESCRIPTION
 -----------
 If no argument is provided, show available branches and mark current
 branch with star. Otherwise, create a new branch of name <branchname>.
-
 If a starting point is also specified, that will be where the branch is
 created, otherwise it will be created at the current HEAD.
 
+With a `-d` or `-D` option, `<branchname>` will be deleted.
+
+
 OPTIONS
 -------
 -d::
@@ -39,7 +43,7 @@ OPTIONS
 Examples
 ~~~~~~~~
 
-Start development off of a know tag::
+Start development off of a known tag::
 +
 ------------
 $ git clone git://git.kernel.org/pub/scm/.../linux-2.6 my2.6
index f7f84c6..7dc1bdb 100644 (file)
@@ -3,7 +3,7 @@ git-check-ref-format(1)
 
 NAME
 ----
-git-check-ref-format - Make sure ref name is well formed.
+git-check-ref-format - Make sure ref name is well formed
 
 SYNOPSIS
 --------
index 2a1e526..09bd6a5 100644 (file)
@@ -10,7 +10,10 @@ SYNOPSIS
 --------
 [verse]
 'git-checkout-index' [-u] [-q] [-a] [-f] [-n] [--prefix=<string>]
-                  [--stage=<number>] [--] <file>...
+                  [--stage=<number>|all]
+                  [--temp]
+                  [-z] [--stdin]
+                  [--] [<file>]\*
 
 DESCRIPTION
 -----------
@@ -41,9 +44,24 @@ OPTIONS
        When creating files, prepend <string> (usually a directory
        including a trailing /)
 
---stage=<number>::
+--stage=<number>|all::
        Instead of checking out unmerged entries, copy out the
        files from named stage.  <number> must be between 1 and 3.
+       Note: --stage=all automatically implies --temp.
+
+--temp::
+       Instead of copying the files to the working directory
+       write the content to temporary files.  The temporary name
+       associations will be written to stdout.
+
+--stdin::
+       Instead of taking list of paths from the command line,
+       read list of paths from the standard input.  Paths are
+       separated by LF (i.e. one path per line) by default.
+
+-z::
+       Only meaningful with `--stdin`; paths are separated with
+       NUL character instead of LF.
 
 --::
        Do not interpret any more arguments as options.
@@ -64,13 +82,58 @@ $ find . -name '*.h' -print0 | xargs -0 git-checkout-index -f --
 
 which will force all existing `*.h` files to be replaced with their
 cached copies. If an empty command line implied "all", then this would
-force-refresh everything in the index, which was not the point.
+force-refresh everything in the index, which was not the point.  But
+since git-checkout-index accepts --stdin it would be faster to use:
+
+----------------
+$ find . -name '*.h' -print0 | git-checkout-index -f -z --stdin
+----------------
 
 The `--` is just a good idea when you know the rest will be filenames;
 it will prevent problems with a filename of, for example,  `-a`.
 Using `--` is probably a good policy in scripts.
 
 
+Using --temp or --stage=all
+---------------------------
+When `--temp` is used (or implied by `--stage=all`)
+`git-checkout-index` will create a temporary file for each index
+entry being checked out.  The index will not be updated with stat
+information.  These options can be useful if the caller needs all
+stages of all unmerged entries so that the unmerged files can be
+processed by an external merge tool.
+
+A listing will be written to stdout providing the association of
+temporary file names to tracked path names.  The listing format
+has two variations:
+
+    . tempname TAB path RS
++
+The first format is what gets used when `--stage` is omitted or
+is not `--stage=all`. The field tempname is the temporary file
+name holding the file content and path is the tracked path name in
+the index.  Only the requested entries are output.
+
+    . stage1temp SP stage2temp SP stage3tmp TAB path RS
++
+The second format is what gets used when `--stage=all`.  The three
+stage temporary fields (stage1temp, stage2temp, stage3temp) list the
+name of the temporary file if there is a stage entry in the index
+or `.` if there is no stage entry.  Paths which only have a stage 0
+entry will always be omitted from the output.
+
+In both formats RS (the record separator) is newline by default
+but will be the null byte if -z was passed on the command line.
+The temporary file names are always safe strings; they will never
+contain directory separators or whitespace characters.  The path
+field is always relative to the current directory and the temporary
+file names are always relative to the top level directory.
+
+If the object being copied out to a temporary file is a symbolic
+link the content of the link will be written to a normal file.  It is
+up to the end-user or the Porcelain to make use of this information.
+
+
 EXAMPLES
 --------
 To update and refresh only the files already checked out::
index df9a618..985bb2f 100644 (file)
@@ -3,19 +3,22 @@ git-checkout(1)
 
 NAME
 ----
-git-checkout - Checkout and switch to a branch.
+git-checkout - Checkout and switch to a branch
 
 SYNOPSIS
 --------
-'git-checkout' [-f] [-b <new_branch>] [-m] [<branch>] [<paths>...]
+[verse]
+'git-checkout' [-f] [-b <new_branch>] [-m] [<branch>]
+'git-checkout' [-m] [<branch>] <paths>...
 
 DESCRIPTION
 -----------
 
-When <paths> are not given, this command switches branches, by
+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>.
+specified, <new_branch>.  Using -b will cause <new_branch> to
+be created.
 
 When <paths> are given, this command does *not* switch
 branches.  It updates the named paths in the working tree from
@@ -29,17 +32,17 @@ given paths before updating the working tree.
 OPTIONS
 -------
 -f::
-       Force an re-read of everything.
+       Force a re-read of everything.
 
 -b::
        Create a new branch and start it at <branch>.
 
 -m::
-       If you have local modifications to a file that is
-       different between the current branch and the branch you
-       are switching to, the command refuses to switch
-       branches, to preserve your modifications in context.
-       With this option, a three-way merge between the current
+       If you have local modifications to one or more files that
+       are different between the current branch and the branch to
+       which you are switching, the command refuses to switch
+       branches in order to preserve your modifications in context.
+       However, with this option, a three-way merge between the current
        branch, your working tree contents, and the new branch
        is done, and you will be on the new branch.
 +
@@ -82,7 +85,7 @@ $ git checkout -- hello.c
 ------------
 
 . After working in a wrong branch, switching to the correct
-branch you would want to is done with:
+branch would be done using:
 +
 ------------
 $ git checkout mytopic
index 4f323fa..bfa950c 100644 (file)
@@ -3,7 +3,7 @@ git-cherry-pick(1)
 
 NAME
 ----
-git-cherry-pick - Apply the change introduced by an existing commit.
+git-cherry-pick - Apply the change introduced by an existing commit
 
 SYNOPSIS
 --------
index af87966..9a5e371 100644 (file)
@@ -3,7 +3,7 @@ git-cherry(1)
 
 NAME
 ----
-git-cherry - Find commits not merged upstream.
+git-cherry - Find commits not merged upstream
 
 SYNOPSIS
 --------
diff --git a/Documentation/git-clean.txt b/Documentation/git-clean.txt
new file mode 100644 (file)
index 0000000..36890c5
--- /dev/null
@@ -0,0 +1,50 @@
+git-clean(1)
+============
+
+NAME
+----
+git-clean - Remove untracked files from the working tree
+
+SYNOPSIS
+--------
+[verse]
+'git-clean' [-d] [-n] [-q] [-x | -X]
+
+DESCRIPTION
+-----------
+Removes files unknown to git.  This allows to clean the working tree
+from files that are not under version control.  If the '-x' option is
+specified, ignored files are also removed, allowing to remove all
+build products.
+
+OPTIONS
+-------
+-d::
+       Remove untracked directories in addition to untracked files.
+
+-n::
+       Don't actually remove anything, just show what would be done.
+
+-q::
+       Be quiet, only report errors, but not the files that are
+       successfully removed.
+
+-x::
+       Don't use the ignore rules.  This allows removing all untracked
+       files, including build products.  This can be used (possibly in
+       conjunction with gitlink:git-reset[1]) to create a pristine
+       working directory to test a clean build.
+
+-X::
+       Remove only files ignored by git.  This may be useful to rebuild
+       everything from scratch, but keep manually created files.
+
+
+Author
+------
+Written by Pavel Roskin <proski@gnu.org>
+
+
+GIT
+---
+Part of the gitlink:git[7] suite
index 39906fc..09f43ee 100644 (file)
@@ -3,7 +3,7 @@ git-clone-pack(1)
 
 NAME
 ----
-git-clone-pack - Clones a repository by receiving packed objects.
+git-clone-pack - Clones a repository by receiving packed objects
 
 
 SYNOPSIS
index 684e4bd..9ac54c2 100644 (file)
@@ -3,7 +3,7 @@ git-clone(1)
 
 NAME
 ----
-git-clone - Clones a repository.
+git-clone - Clones a repository
 
 
 SYNOPSIS
index 5b1b4d3..0a7365b 100644 (file)
@@ -9,7 +9,8 @@ SYNOPSIS
 --------
 [verse]
 'git-commit' [-a] [-s] [-v] [(-c | -C) <commit> | -F <file> | -m <msg>]
-          [-e] [--author <author>] [--] [[-i | -o ]<file>...]
+          [--no-verify] [--amend] [-e] [--author <author>]
+          [--] [[-i | -o ]<file>...]
 
 DESCRIPTION
 -----------
@@ -18,6 +19,10 @@ Updates the index file for given paths, or all modified files if
 VISUAL and EDITOR environment variables to edit the commit log
 message.
 
+Several environment variable are used during commits.  They are
+documented in gitlink:git-commit-tree[1].
+
+
 This command can run `commit-msg`, `pre-commit`, and
 `post-commit` hooks.  See link:hooks.html[hooks] for more
 information.
@@ -67,6 +72,28 @@ OPTIONS
        commit log message unmodified.  This option lets you
        further edit the message taken from these sources.
 
+--amend::
+
+       Used to amend the tip of the current branch. Prepare the tree
+       object you would want to replace the latest commit as usual
+       (this includes the usual -i/-o and explicit paths), and the
+       commit log editor is seeded with the commit message from the
+       tip of the current branch. The commit you create replaces the
+       current tip -- if it was a merge, it will have the parents of
+       the current tip as parents -- so the current top commit is
+       discarded.
++
+--
+It is a rough equivalent for:
+------
+       $ git reset --soft HEAD^
+       $ ... do something else to come up with the right tree ...
+       $ git commit -c ORIG_HEAD
+
+------
+but can be used to amend a merge commit.
+--
+
 -i|--include::
        Instead of committing only the files specified on the
        command line, update them in the index file and then
@@ -85,27 +112,12 @@ OPTIONS
 <file>...::
        Files to be committed.  The meaning of these is
        different between `--include` and `--only`.  Without
-       either, it defaults `--include` semantics.
+       either, it defaults `--only` semantics.
 
 If you make a commit and then found a mistake immediately after
 that, you can recover from it with gitlink:git-reset[1].
 
 
-WARNING
--------
-
-The 1.2.0 and its maintenance series 1.2.X will keep the
-traditional `--include` semantics as the default when neither
-`--only` nor `--include` is specified and `paths...` are given.
-This *will* change during the development towards 1.3.0 in the
-'master' branch of `git.git` repository.  If you are using this
-command in your scripts, and you depend on the traditional
-`--include` semantics, please update them to explicitly ask for
-`--include` semantics.  Also if you are used to making partial
-commit using `--include` semantics, please train your fingers to
-say `git commit --include paths...` (or `git commit -i paths...`).
-
-
 Discussion
 ----------
 
@@ -121,7 +133,7 @@ even the command is invoked from a subdirectory.
 That is, update the specified paths to the index and then commit
 the whole tree.
 
-`git commit --only paths...` largely bypasses the index file and
+`git commit paths...` largely bypasses the index file and
 commits only the changes made to the specified paths.  It has
 however several safety valves to prevent confusion.
 
index 36888d9..47216f4 100644 (file)
@@ -3,7 +3,7 @@ git-count-objects(1)
 
 NAME
 ----
-git-count-objects - Reports on unpacked objects.
+git-count-objects - Reports on unpacked objects
 
 SYNOPSIS
 --------
index dfe86ce..b0c6d7c 100644 (file)
@@ -22,6 +22,12 @@ repository, or incrementally import into an existing one.
 Splitting the CVS log into patch sets is done by 'cvsps'.
 At least version 2.1 is required.
 
+You should *never* do any work of your own on the branches that are
+created by git-cvsimport. The initial import will create and populate a
+"master" branch from the CVS repository's main branch which you're free
+to work with; after that, you need to 'git merge' incremental imports, or
+any CVS branches, yourself.
+
 OPTIONS
 -------
 -d <CVSROOT>::
@@ -93,21 +99,24 @@ If you need to pass multiple options, separate them with a comma.
        CVS by default uses the unix username when writing its
        commit logs. Using this option and an author-conv-file
        in this format
-
++
+---------
        exon=Andreas Ericsson <ae@op5.se>
        spawn=Simon Pawn <spawn@frog-pond.org>
 
-       git-cvsimport will make it appear as those authors had
-       their GIT_AUTHOR_NAME and GIT_AUTHOR_EMAIL set properly
-       all along.
-
-       For convenience, this data is saved to $GIT_DIR/cvs-authors
-       each time the -A option is provided and read from that same
-       file each time git-cvsimport is run.
-
-       It is not recommended to use this feature if you intend to
-       export changes back to CVS again later with
-       git-link[1]::git-cvsexportcommit.
+---------
++
+git-cvsimport will make it appear as those authors had
+their GIT_AUTHOR_NAME and GIT_AUTHOR_EMAIL set properly
+all along.
++
+For convenience, this data is saved to $GIT_DIR/cvs-authors
+each time the -A option is provided and read from that same
+file each time git-cvsimport is run.
++
+It is not recommended to use this feature if you intend to
+export changes back to CVS again later with
+git-link[1]::git-cvsexportcommit.
 
 OUTPUT
 ------
diff --git a/Documentation/git-cvsserver.txt b/Documentation/git-cvsserver.txt
new file mode 100644 (file)
index 0000000..4dc13c3
--- /dev/null
@@ -0,0 +1,148 @@
+git-cvsserver(1)
+================
+
+NAME
+----
+git-cvsserver - A CVS server emulator for git
+
+SYNOPSIS
+--------
+[verse]
+export CVS_SERVER=git-cvsserver
+'cvs' -d :ext:user@server/path/repo.git co <HEAD_name>
+
+DESCRIPTION
+-----------
+
+This application is a CVS emulation layer for git.
+
+It is highly functional. However, not all methods are implemented,
+and for those methods that are implemented,
+not all switches are implemented.
+
+Testing has been done using both the CLI CVS client, and the Eclipse CVS
+plugin. Most functionality works fine with both of these clients.
+
+LIMITATIONS
+-----------
+
+Currently cvsserver works over SSH connections for read/write clients, and
+over pserver for anonymous CVS access.
+
+CVS clients cannot tag, branch or perform GIT merges.
+
+INSTALLATION
+------------
+
+1. If you are going to offer anonymous CVS access via pserver, add a line in
+   /etc/inetd.conf like
+
+   cvspserver stream tcp nowait nobody git-cvsserver pserver
+
+   Note: In some cases, you need to pass the 'pserver' argument twice for
+   git-cvsserver to see it. So the line would look like
+
+   cvspserver stream tcp nowait nobody git-cvsserver pserver pserver
+
+   No special setup is needed for SSH access, other than having GIT tools
+   in the PATH. If you have clients that do not accept the CVS_SERVER
+   env variable, you can rename git-cvsserver to cvs.
+
+2. For each repo that you want accessible from CVS you need to edit config in
+   the repo and add the following section.
+
+   [gitcvs]
+        enabled=1
+        # optional for debugging
+        logfile=/path/to/logfile
+
+   Note: you need to ensure each user that is going to invoke git-cvsserver has
+   write access to the log file and to the git repository. When offering anon
+   access via pserver, this means that the nobody user should have write access
+   to at least the sqlite database at the root of the repository.
+
+3. On the client machine you need to set the following variables.
+   CVSROOT should be set as per normal, but the directory should point at the
+   appropriate git repo. For example:
+
+   For SSH access, CVS_SERVER should be set to git-cvsserver
+
+   Example:
+
+     export CVSROOT=:ext:user@server:/var/git/project.git
+     export CVS_SERVER=git-cvsserver
+
+4. For SSH clients that will make commits, make sure their .bashrc file
+   sets the GIT_AUTHOR and GIT_COMMITTER variables.
+
+5. Clients should now be able to check out the project. Use the CVS 'module'
+   name to indicate what GIT 'head' you want to check out. Example:
+
+     cvs co -d project-master master
+
+Eclipse CVS Client Notes
+------------------------
+
+To get a checkout with the Eclipse CVS client:
+
+1. Select "Create a new project -> From CVS checkout"
+2. Create a new location. See the notes below for details on how to choose the
+   right protocol.
+3. Browse the 'modules' available. It will give you a list of the heads in
+   the repository. You will not be able to browse the tree from there. Only
+   the heads.
+4. Pick 'HEAD' when it asks what branch/tag to check out. Untick the
+   "launch commit wizard" to avoid committing the .project file.
+
+Protocol notes: If you are using anonymous acces via pserver, just select that.
+Those using SSH access should choose the 'ext' protocol, and configure 'ext'
+access on the Preferences->Team->CVS->ExtConnection pane. Set CVS_SERVER to
+'git-cvsserver'. Not that password support is not good when using 'ext',
+you will definitely want to have SSH keys setup.
+
+Alternatively, you can just use the non-standard extssh protocol that Eclipse
+offer. In that case CVS_SERVER is ignored, and you will have to replace
+the cvs utility on the server with git-cvsserver or manipulate your .bashrc
+so that calling 'cvs' effectively calls git-cvsserver.
+
+Clients known to work
+---------------------
+
+CVS 1.12.9 on Debian
+CVS 1.11.17 on MacOSX (from Fink package)
+Eclipse 3.0, 3.1.2 on MacOSX (see Eclipse CVS Client Notes)
+TortoiseCVS
+
+Operations supported
+--------------------
+
+All the operations required for normal use are supported, including
+checkout, diff, status, update, log, add, remove, commit.
+Legacy monitoring operations are not supported (edit, watch and related).
+Exports and tagging (tags and branches) are not supported at this stage.
+
+The server will set the -k mode to binary when relevant. In proper GIT
+tradition, the contents of the files are always respected.
+No keyword expansion or newline munging is supported.
+
+Dependencies
+------------
+
+git-cvsserver depends on DBD::SQLite.
+
+Copyright and Authors
+---------------------
+
+This program is copyright The Open University UK - 2006.
+
+Authors: Martyn Smith    <martyn@catalyst.net.nz>
+         Martin Langhoff <martin@catalyst.net.nz>
+         with ideas and patches from participants of the git-list <git@vger.kernel.org>.
+
+Documentation
+--------------
+Documentation by Martyn Smith <martyn@catalyst.net.nz> and Martin Langhoff <martin@catalyst.net.nz> Matthias Urlichs <smurf@smurf.noris.de>.
+
+GIT
+---
+Part of the gitlink:git[7] suite
index 2cc6075..924a676 100644 (file)
@@ -3,7 +3,7 @@ git-daemon(1)
 
 NAME
 ----
-git-daemon - A really simple server for git repositories.
+git-daemon - A really simple server for git repositories
 
 SYNOPSIS
 --------
index 0efe82a..7a253ea 100644 (file)
@@ -3,7 +3,7 @@ git-describe(1)
 
 NAME
 ----
-git-describe - Show the most recent tag that is reachable from a commit.
+git-describe - Show the most recent tag that is reachable from a commit
 
 
 SYNOPSIS
index 28c60fc..3273918 100644 (file)
@@ -3,7 +3,7 @@ git-diff-stages(1)
 
 NAME
 ----
-git-diff-stages - Compares content and mode of blobs between stages in an unmerged index file.
+git-diff-stages - Compares content and mode of blobs between stages in an unmerged index file
 
 
 SYNOPSIS
index ca41634..890931c 100644 (file)
@@ -3,7 +3,7 @@ git-diff(1)
 
 NAME
 ----
-git-diff - Show changes between commits, commit and working tree, etc.
+git-diff - Show changes between commits, commit and working tree, etc
 
 
 SYNOPSIS
index b507e9b..bff9aa6 100644 (file)
@@ -3,12 +3,12 @@ git-fetch-pack(1)
 
 NAME
 ----
-git-fetch-pack - Receive missing objects from another repository.
+git-fetch-pack - Receive missing objects from another repository
 
 
 SYNOPSIS
 --------
-git-fetch-pack [-q] [-k] [--exec=<git-upload-pack>] [<host>:]<directory> [<refs>...]
+'git-fetch-pack' [-q] [-k] [--exec=<git-upload-pack>] [<host>:]<directory> [<refs>...]
 
 DESCRIPTION
 -----------
index a67dc34..a9e86fd 100644 (file)
@@ -3,7 +3,7 @@ git-fetch(1)
 
 NAME
 ----
-git-fetch - Download objects and a head from another repository.
+git-fetch - Download objects and a head from another repository
 
 
 SYNOPSIS
index 9ac0636..7cc7faf 100644 (file)
@@ -3,13 +3,13 @@ git-format-patch(1)
 
 NAME
 ----
-git-format-patch - Prepare patches for e-mail submission.
+git-format-patch - Prepare patches for e-mail submission
 
 
 SYNOPSIS
 --------
 [verse]
-'git-format-patch' [-n | -k] [-o <dir> | --stdout] [-s] [-c]
+'git-format-patch' [-n | -k] [-o <dir> | --stdout] [--attach] [-s] [-c]
                 [--diff-options] <his> [<mine>]
 
 DESCRIPTION
@@ -60,6 +60,18 @@ OPTIONS
        standard output, instead of saving them into a file per
        patch and implies --mbox.
 
+--attach::
+       Create attachments instead of inlining patches.
+
+
+CONFIGURATION
+-------------
+You can specify extra mail header lines to be added to each
+message in the repository configuration as follows:
+
+[format]
+        headers = "Organization: git-foo\n"
+
 
 EXAMPLES
 --------
index 387b435..93ce9dc 100644 (file)
@@ -10,7 +10,7 @@ SYNOPSIS
 --------
 [verse]
 'git-fsck-objects' [--tags] [--root] [--unreachable] [--cache]
-                [--standalone | --full] [--strict] [<object>*]
+                [--full] [--strict] [<object>*]
 
 DESCRIPTION
 -----------
@@ -38,21 +38,14 @@ index file and all SHA1 references in .git/refs/* as heads.
        Consider any object recorded in the index also as a head node for
        an unreachability trace.
 
---standalone::
-       Limit checks to the contents of GIT_OBJECT_DIRECTORY
-       ($GIT_DIR/objects), making sure that it is consistent and
-       complete without referring to objects found in alternate
-       object pools listed in GIT_ALTERNATE_OBJECT_DIRECTORIES,
-       nor packed git archives found in $GIT_DIR/objects/pack;
-       cannot be used with --full.
-
 --full::
        Check not just objects in GIT_OBJECT_DIRECTORY
        ($GIT_DIR/objects), but also the ones found in alternate
-       object pools listed in GIT_ALTERNATE_OBJECT_DIRECTORIES,
+       object pools listed in GIT_ALTERNATE_OBJECT_DIRECTORIES
+       or $GIT_DIR/objects/info/alternates,
        and in packed git archives found in $GIT_DIR/objects/pack
        and corresponding pack subdirectories in alternate
-       object pools; cannot be used with --standalone.
+       object pools.
 
 --strict::
        Enable more strict checking, namely to catch a file mode
index 30b1fbf..48805b6 100644 (file)
@@ -3,7 +3,7 @@ git-get-tar-commit-id(1)
 
 NAME
 ----
-git-get-tar-commit-id - Extract commit ID from an archive created using git-tar-tree.
+git-get-tar-commit-id - Extract commit ID from an archive created using git-tar-tree
 
 
 SYNOPSIS
index bf4b592..d55456a 100644 (file)
@@ -3,7 +3,7 @@ git-grep(1)
 
 NAME
 ----
-git-grep - print lines matching a pattern
+git-grep - Print lines matching a pattern
 
 
 SYNOPSIS
@@ -24,13 +24,13 @@ OPTIONS
 
 <option>...::
        Either an option to pass to `grep` or `git-ls-files`.
-
-       The following are the specific `git-ls-files` options
-       that may be given: `-o`, `--cached`, `--deleted`, `--others`,
-       `--killed`, `--ignored`, `--modified`, `--exclude=*`,
-       `--exclude-from=*`, and `--exclude-per-directory=*`.
-
-       All other options will be passed to `grep`.
++
+The following are the specific `git-ls-files` options
+that may be given: `-o`, `--cached`, `--deleted`, `--others`,
+`--killed`, `--ignored`, `--modified`, `--exclude=\*`,
+`--exclude-from=\*`, and `--exclude-per-directory=\*`.
++
+All other options will be passed to `grep`.
 
 <pattern>::
        The pattern to look for.  The first non option is taken
index 0924931..04e8d00 100644 (file)
@@ -3,7 +3,7 @@ git-hash-object(1)
 
 NAME
 ----
-git-hash-object - Computes object ID and optionally creates a blob from a file.
+git-hash-object - Computes object ID and optionally creates a blob from a file
 
 
 SYNOPSIS
index c7066d6..7e1f894 100644 (file)
@@ -3,7 +3,7 @@ git-http-push(1)
 
 NAME
 ----
-git-http-push - Push missing objects using HTTP/DAV.
+git-http-push - Push missing objects using HTTP/DAV
 
 
 SYNOPSIS
diff --git a/Documentation/git-imap-send.txt b/Documentation/git-imap-send.txt
new file mode 100644 (file)
index 0000000..cfc0d88
--- /dev/null
@@ -0,0 +1,60 @@
+git-imap-send(1)
+================
+
+NAME
+----
+git-imap-send - Dump a mailbox from stdin into an imap folder
+
+
+SYNOPSIS
+--------
+'git-imap-send'
+
+
+DESCRIPTION
+-----------
+This command uploads a mailbox generated with git-format-patch
+into an imap drafts folder.  This allows patches to be sent as
+other email is sent with mail clients that cannot read mailbox
+files directly.
+
+Typical usage is something like:
+
+git-format-patch --signoff --stdout --attach origin | git-imap-send
+
+
+CONFIGURATION
+-------------
+
+git-imap-send requires the following values in the repository
+configuration file (shown with examples):
+
+[imap]
+    Folder = "INBOX.Drafts"
+
+[imap]
+    Tunnel = "ssh -q user@server.com /usr/bin/imapd ./Maildir 2> /dev/null"
+
+[imap]
+    Host = imap.server.com
+    User = bob
+    Password = pwd
+    Port = 143
+
+
+BUGS
+----
+Doesn't handle lines starting with "From " in the message body.
+
+
+Author
+------
+Derived from isync 1.0.1 by Mike McCormack.
+
+Documentation
+--------------
+Documentation by Mike McCormack
+
+GIT
+---
+Part of the gitlink:git[7] suite
index ea4d849..aeb1115 100644 (file)
@@ -14,7 +14,8 @@ SYNOPSIS
 OPTIONS
 -------
 --template=<template_directory>::
-       Provide the directory in from which templates will be used.
+       Provide the directory from which templates will be used.
+       The default template directory is `/usr/share/git-core/templates`.
 
 --shared::
        Specify that the git repository is to be shared amongst several users.
@@ -22,9 +23,17 @@ OPTIONS
 
 DESCRIPTION
 -----------
-This simply creates an empty git repository - basically a `.git` directory
-and `.git/object/??/`, `.git/refs/heads` and `.git/refs/tags` directories,
-and links `.git/HEAD` symbolically to `.git/refs/heads/master`.
+This command creates an empty git repository - basically a `.git` directory
+with subdirectories for `objects`, `refs/heads`, `refs/tags`, and
+templated files.
+An initial `HEAD` file that references the HEAD of the master branch
+is also created.
+
+If `--template=<template_directory>` is specified, `<template_directory>`
+is used as the source of the template files rather than the default.
+The template files include some directory structure, some suggested
+"exclude patterns", and copies of non-executing "hook" files.  The
+suggested patterns and hook files are all modifiable and extensible.
 
 If the `$GIT_DIR` environment variable is set then it specifies a path
 to use instead of `./.git` for the base of the repository.
@@ -38,7 +47,6 @@ repository. When specifying `--shared` the config variable "core.sharedRepositor
 is set to 'true' so that directories under `$GIT_DIR` are made group writable
 (and g+sx, since the git group may be not the primary group of all users).
 
-
 Running `git-init-db` in an existing repository is safe. It will not overwrite
 things that are already there. The primary reason for rerunning `git-init-db`
 is to pick up newly added templates.
index 03156f2..f52a9d7 100644 (file)
@@ -3,7 +3,7 @@ git-lost-found(1)
 
 NAME
 ----
-git-lost-found - Recover lost refs that luckily have not yet been pruned.
+git-lost-found - Recover lost refs that luckily have not yet been pruned
 
 SYNOPSIS
 --------
index fe53412..796d049 100644 (file)
@@ -8,13 +8,15 @@ git-ls-files - Information about files in the index/working directory
 
 SYNOPSIS
 --------
-'git-ls-files' [-z] [-t]
+[verse]
+'git-ls-files' [-z] [-t] [-v]
                (--[cached|deleted|others|ignored|stage|unmerged|killed|modified])\*
                (-[c|d|o|i|s|u|k|m])\*
                [-x <pattern>|--exclude=<pattern>]
                [-X <file>|--exclude-from=<file>]
-               [--exclude-per-directory=<file>] 
-               [--full-name] [--] [<file>]\*
+               [--exclude-per-directory=<file>]
+               [--error-unmatch]
+               [--full-name] [--abbrev] [--] [<file>]\*
 
 DESCRIPTION
 -----------
@@ -50,6 +52,9 @@ OPTIONS
        If a whole directory is classified as "other", show just its
        name (with a trailing slash) and not its whole contents.
 
+--no-empty-directory::
+       Do not list empty directories. Has no effect without --directory.
+
 -u|--unmerged::
        Show unmerged files in the output (forces --stage)
 
@@ -72,6 +77,10 @@ OPTIONS
        read additional exclude patterns that apply only to the
        directory and its subdirectories in <file>.
 
+--error-unmatch::
+       If any <file> does not appear in the index, treat this as an
+       error (return 1).
+
 -t::
        Identify the file status with the following tags (followed by
        a space) at the start of each line:
@@ -82,12 +91,21 @@ OPTIONS
        K::     to be killed
        ?::     other
 
+-v::
+       Similar to `-t`, but use lowercase letters for files
+       that are marked as 'always matching index'.
+
 --full-name::
        When run from a subdirectory, the command usually
        outputs paths relative to the current directory.  This
        option forces paths to be output relative to the project
        top directory.
 
+--abbrev[=<n>]::
+       Instead of showing the full 40-byte hexadecimal object
+       lines, show only handful hexdigits prefix.
+       Non default number of digits can be specified with --abbrev=<n>.
+
 --::
        Do not interpret any more arguments as options.
 
index 66fe60f..ae4c1a2 100644 (file)
@@ -3,7 +3,7 @@ git-ls-remote(1)
 
 NAME
 ----
-git-ls-remote - Look at references other repository has.
+git-ls-remote - Look at references other repository has
 
 
 SYNOPSIS
index b92a8b2..018c401 100644 (file)
@@ -3,12 +3,14 @@ git-ls-tree(1)
 
 NAME
 ----
-git-ls-tree - Lists the contents of a tree object.
+git-ls-tree - Lists the contents of a tree object
 
 
 SYNOPSIS
 --------
-'git-ls-tree' [-d] [-r] [-t] [-z] [--name-only] [--name-status] <tree-ish> [paths...]
+'git-ls-tree' [-d] [-r] [-t] [-z]
+       [--name-only] [--name-status] [--full-name] [--abbrev=[<n>]]
+       <tree-ish> [paths...]
 
 DESCRIPTION
 -----------
@@ -40,6 +42,11 @@ OPTIONS
 --name-status::
        List only filenames (instead of the "long" output), one per line.
 
+--abbrev[=<n>]::
+       Instead of showing the full 40-byte hexadecimal object
+       lines, show only handful hexdigits prefix.
+       Non default number of digits can be specified with --abbrev=<n>.
+
 paths::
        When paths are given, show them (note that this isn't really raw
        pathnames, but rather a list of patterns to match).  Otherwise
index 8890754..ea0a065 100644 (file)
@@ -3,7 +3,7 @@ git-mailinfo(1)
 
 NAME
 ----
-git-mailinfo - Extracts patch from a single e-mail message.
+git-mailinfo - Extracts patch from a single e-mail message
 
 
 SYNOPSIS
index e0703e9..209e36b 100644 (file)
@@ -3,7 +3,7 @@ git-mailsplit(1)
 
 NAME
 ----
-git-mailsplit - Totally braindamaged mbox splitter program.
+git-mailsplit - Totally braindamaged mbox splitter program
 
 SYNOPSIS
 --------
index d242b39..207c43a 100644 (file)
@@ -3,7 +3,7 @@ git-mv(1)
 
 NAME
 ----
-git-mv - Script used to move or rename a file, directory or symlink.
+git-mv - Move or rename a file, directory or symlink
 
 
 SYNOPSIS
index e37b0b8..6870708 100644 (file)
@@ -3,7 +3,7 @@ git-name-rev(1)
 
 NAME
 ----
-git-name-rev - Find symbolic names for given revs.
+git-name-rev - Find symbolic names for given revs
 
 
 SYNOPSIS
index 4cb2e83..4991f88 100644 (file)
@@ -3,7 +3,7 @@ git-pack-objects(1)
 
 NAME
 ----
-git-pack-objects - Create a packed archive of objects.
+git-pack-objects - Create a packed archive of objects
 
 
 SYNOPSIS
@@ -101,7 +101,7 @@ Documentation
 -------------
 Documentation by Junio C Hamano
 
-See-Also
+See Also
 --------
 gitlink:git-repack[1]
 gitlink:git-prune-packed[1]
index 9fe86ae..8fb0659 100644 (file)
@@ -3,12 +3,12 @@ git-pack-redundant(1)
 
 NAME
 ----
-git-pack-redundant - Program used to find redundant pack files.
+git-pack-redundant - Program used to find redundant pack files
 
 
 SYNOPSIS
 --------
-'git-pack-redundant [ --verbose ] [ --alt-odb ] < --all | .pack filename ... >'
+'git-pack-redundant' [ --verbose ] [ --alt-odb ] < --all | .pack filename ... >
 
 DESCRIPTION
 -----------
@@ -46,7 +46,7 @@ Documentation
 --------------
 Documentation by Lukas Sandström <lukass@etek.chalmers.se>
 
-See-Also
+See Also
 --------
 gitlink:git-pack-objects[1]
 gitlink:git-repack[1]
index c8bd197..723b8cc 100644 (file)
@@ -3,7 +3,7 @@ git-patch-id(1)
 
 NAME
 ----
-git-patch-id - Generate a patch ID.
+git-patch-id - Generate a patch ID
 
 SYNOPSIS
 --------
index 915d3f8..a00060c 100644 (file)
@@ -3,7 +3,7 @@ git-peek-remote(1)
 
 NAME
 ----
-git-peek-remote - Lists the references in a remote repository.
+git-peek-remote - Lists the references in a remote repository
 
 
 SYNOPSIS
index 37c53a9..2348826 100644 (file)
@@ -40,7 +40,7 @@ Documentation
 --------------
 Documentation by Ryan Anderson <ryan@michonline.com>
 
-See-Also
+See Also
 --------
 gitlink:git-pack-objects[1]
 gitlink:git-repack[1]
index 20175f4..51577fc 100644 (file)
@@ -3,7 +3,7 @@ git-pull(1)
 
 NAME
 ----
-git-pull - Pull and merge from another repository.
+git-pull - Pull and merge from another repository
 
 
 SYNOPSIS
index 6f4a48a..d5b5ca1 100644 (file)
@@ -3,7 +3,7 @@ git-push(1)
 
 NAME
 ----
-git-push - Update remote refs along with associated objects.
+git-push - Update remote refs along with associated objects
 
 
 SYNOPSIS
index 6fbd6d9..844cfda 100644 (file)
@@ -8,7 +8,7 @@ git-read-tree - Reads tree information into the index
 
 SYNOPSIS
 --------
-'git-read-tree' (<tree-ish> | [[-m | --reset] [-u | -i]] <tree-ish1> [<tree-ish2> [<tree-ish3>]])
+'git-read-tree' (<tree-ish> | [[-m [--aggressive]| --reset] [-u | -i]] <tree-ish1> [<tree-ish2> [<tree-ish3>]])
 
 
 DESCRIPTION
@@ -50,6 +50,19 @@ OPTIONS
        trees that are not directly related to the current
        working tree status into a temporary index file.
 
+--aggressive::
+       Usually a three-way merge by `git-read-tree` resolves
+       the merge for really trivial cases and leaves other
+       cases unresolved in the index, so that Porcelains can
+       implement different merge policies.  This flag makes the
+       command to resolve a few more cases internally:
++
+* when one side removes a path and the other side leaves the path
+  unmodified.  The resolution is to remove that path.
+* when both sides remove a path.  The resolution is to remove that path.
+* when both sides adds a path identically.  The resolution
+  is to add that path.
+
 <tree-ish#>::
        The id of the tree object(s) to be read/merged.
 
index 16c158f..4a7e67a 100644 (file)
@@ -3,22 +3,74 @@ git-rebase(1)
 
 NAME
 ----
-git-rebase - Rebase local commits to new upstream head.
+git-rebase - Rebase local commits to new upstream head
 
 SYNOPSIS
 --------
-'git-rebase' <upstream> [<head>]
+'git-rebase' [--onto <newbase>] <upstream> [<branch>]
 
 DESCRIPTION
 -----------
-Rebases local commits to the new head of the upstream tree.
+git-rebase applies to <upstream> (or optionally to <newbase>) commits
+from <branch> that do not appear in <upstream>. When <branch> is not
+specified it defaults to the current branch (HEAD).
+
+When git-rebase is complete, <branch> will be updated to point to the
+newly created line of commit objects, so the previous line will not be
+accessible unless there are other references to it already.
+
+Assume the following history exists and the current branch is "topic":
+
+          A---B---C topic
+         /
+    D---E---F---G master
+
+From this point, the result of either of the following commands:
+
+    git-rebase master
+    git-rebase master topic
+
+would be:
+
+                  A'--B'--C' topic
+                 /
+    D---E---F---G master
+
+While, starting from the same point, the result of either of the following
+commands:
+
+    git-rebase --onto master~1 master
+    git-rebase --onto master~1 master topic
+
+would be:
+
+              A'--B'--C' topic
+             /
+    D---E---F---G master
+
+In case of conflict, git-rebase will stop at the first problematic commit
+and leave conflict markers in the tree.  After resolving the conflict manually
+and updating the index with the desired resolution, you can continue the
+rebasing process with
+
+    git am --resolved --3way
+
+Alternatively, you can undo the git-rebase with
+
+    git reset --hard ORIG_HEAD
+    rm -r .dotest
 
 OPTIONS
 -------
+<newbase>::
+       Starting point at which to create the new commits. If the
+       --onto option is not specified, the starting point is
+       <upstream>.
+
 <upstream>::
        Upstream branch to compare against.
 
-<head>::
+<branch>::
        Working branch; defaults to HEAD.
 
 Author
index 6240535..aca6012 100644 (file)
@@ -3,7 +3,7 @@ git-relink(1)
 
 NAME
 ----
-git-relink - Hardlink common objects in local repositories.
+git-relink - Hardlink common objects in local repositories
 
 SYNOPSIS
 --------
index 6c0f792..d2f9a44 100644 (file)
@@ -63,7 +63,7 @@ Documentation
 --------------
 Documentation by Ryan Anderson <ryan@michonline.com>
 
-See-Also
+See Also
 --------
 gitlink:git-pack-objects[1]
 gitlink:git-prune-packed[1]
index 33fcde4..26759a8 100644 (file)
@@ -3,11 +3,12 @@ git-repo-config(1)
 
 NAME
 ----
-git-repo-config - Get and set options in .git/config.
+git-repo-config - Get and set options in .git/config
 
 
 SYNOPSIS
 --------
+[verse]
 'git-repo-config' [type] name [value [value_regex]]
 'git-repo-config' [type] --replace-all name [value [value_regex]]
 'git-repo-config' [type] --get name [value_regex]
index 2463ec9..478a5fd 100644 (file)
@@ -3,7 +3,7 @@ git-request-pull(1)
 
 NAME
 ----
-git-request-pull - Generates a summary of pending changes.
+git-request-pull - Generates a summary of pending changes
 
 SYNOPSIS
 --------
index b4e737e..b7b9798 100644 (file)
@@ -3,7 +3,7 @@ git-reset(1)
 
 NAME
 ----
-git-reset - Reset current HEAD to the specified state.
+git-reset - Reset current HEAD to the specified state
 
 SYNOPSIS
 --------
index 1c6146c..8255ae1 100644 (file)
@@ -16,9 +16,9 @@ SYNOPSIS
             [ \--no-merges ]
             [ \--remove-empty ]
             [ \--all ]
-            [ [ \--merge-order [ \--show-breaks ] ] | [ \--topo-order ] ]
+            [ \--topo-order ]
             [ \--parents ]
-            [ \--objects [ \--unpacked ] ]
+            [ [\--objects | \--objects-edge] [ \--unpacked ] ]
             [ \--pretty | \--header ]
             [ \--bisect ]
             <commit>... [ \-- <paths>... ]
@@ -53,6 +53,14 @@ OPTIONS
        which I need to download if I have the commit object 'bar', but
        not 'foo'".
 
+--objects-edge::
+       Similar to `--objects`, but also print the IDs of
+       excluded commits refixed with a `-` character.  This is
+       used by `git-pack-objects` to build 'thin' pack, which
+       records objects in deltified form based on objects
+       contained in these excluded commits to reduce network
+       traffic.
+
 --unpacked::
        Only useful with `--objects`; print the object IDs that
        are not in packs.
@@ -94,57 +102,10 @@ OPTIONS
        topological order (i.e. descendant commits are shown
        before their parents).
 
---merge-order::
-       When specified the commit history is decomposed into a unique
-       sequence of minimal, non-linear epochs and maximal, linear epochs.
-       Non-linear epochs are then linearised by sorting them into merge
-       order, which is described below.
-+
-Maximal, linear epochs correspond to periods of sequential development.
-Minimal, non-linear epochs correspond to periods of divergent development
-followed by a converging merge. The theory of epochs is described in more
-detail at
-link:http://blackcubes.dyndns.org/epoch/[http://blackcubes.dyndns.org/epoch/].
-+
-The merge order for a non-linear epoch is defined as a linearisation for which
-the following invariants are true:
-+
-    1. if a commit P is reachable from commit N, commit P sorts after commit N
-       in the linearised list.
-    2. if Pi and Pj are any two parents of a merge M (with i < j), then any
-       commit N, such that N is reachable from Pj but not reachable from Pi,
-       sorts before all commits reachable from Pi.
-+
-Invariant 1 states that later commits appear before earlier commits they are
-derived from.
-+
-Invariant 2 states that commits unique to "later" parents in a merge, appear
-before all commits from "earlier" parents of a merge.
-
---show-breaks::
-       Each item of the list is output with a 2-character prefix consisting
-       of one of: (|), (^), (=) followed by a space.
-+
-Commits marked with (=) represent the boundaries of minimal, non-linear epochs
-and correspond either to the start of a period of divergent development or to
-the end of such a period.
-+
-Commits marked with (|) are direct parents of commits immediately preceding
-the marked commit in the list.
-+
-Commits marked with (^) are not parents of the immediately preceding commit.
-These "breaks" represent necessary discontinuities implied by trying to
-represent an arbitrary DAG in a linear form.
-+
-`--show-breaks` is only valid if `--merge-order` is also specified.
-
-
 Author
 ------
 Written by Linus Torvalds <torvalds@osdl.org>
 
-Original *--merge-order* logic by Jon Seymour <jon.seymour@gmail.com>
-
 Documentation
 --------------
 Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>.
index 1662e06..8b95df0 100644 (file)
@@ -3,7 +3,7 @@ git-rev-parse(1)
 
 NAME
 ----
-git-rev-parse - Pick out and massage parameters.
+git-rev-parse - Pick out and massage parameters
 
 
 SYNOPSIS
@@ -80,7 +80,7 @@ OPTIONS
 --git-dir::
        Show `$GIT_DIR` if defined else show the path to the .git directory.
 
---short, short=number::
+--short, --short=number::
        Instead of outputting the full SHA1 values of object names try to
        abbriviate them to a shorter unique name. When no length is specified
        7 is used. The minimum length is 4.
index e27c680..71f7815 100644 (file)
@@ -3,7 +3,7 @@ git-revert(1)
 
 NAME
 ----
-git-revert - Revert an existing commit.
+git-revert - Revert an existing commit
 
 SYNOPSIS
 --------
diff --git a/Documentation/git-rm.txt b/Documentation/git-rm.txt
new file mode 100644 (file)
index 0000000..c9c3088
--- /dev/null
@@ -0,0 +1,92 @@
+git-rm(1)
+=========
+
+NAME
+----
+git-rm - Remove files from the working tree and from the index
+
+SYNOPSIS
+--------
+'git-rm' [-f] [-n] [-v] [--] <file>...
+
+DESCRIPTION
+-----------
+A convenience wrapper for git-update-index --remove. For those coming
+from cvs, git-rm provides an operation similar to "cvs rm" or "cvs
+remove".
+
+
+OPTIONS
+-------
+<file>...::
+       Files to remove from the index and optionally, from the
+       working tree as well.
+
+-f::
+       Remove files from the working tree as well as from the index.
+
+-n::
+        Don't actually remove the file(s), just show if they exist in
+        the index.
+
+-v::
+        Be verbose.
+
+--::
+       This option can be used to separate command-line options from
+       the list of files, (useful when filenames might be mistaken
+       for command-line options).
+
+
+DISCUSSION
+----------
+
+The list of <file> given to the command is fed to `git-ls-files`
+command to list files that are registered in the index and
+are not ignored/excluded by `$GIT_DIR/info/exclude` file or
+`.gitignore` file in each directory.  This means two things:
+
+. You can put the name of a directory on the command line, and the
+  command will remove all files in it and its subdirectories (the
+  directories themselves are never removed from the working tree);
+
+. Giving the name of a file that is not in the index does not
+  remove that file.
+
+
+EXAMPLES
+--------
+git-rm Documentation/\\*.txt::
+
+       Removes all `\*.txt` files from the index that are under the
+       `Documentation` directory and any of its subdirectories. The
+       files are not removed from the working tree.
++
+Note that the asterisk `\*` is quoted from the shell in this
+example; this lets the command include the files from
+subdirectories of `Documentation/` directory.
+
+git-rm -f git-*.sh::
+
+       Remove all git-*.sh scripts that are in the index. The files
+       are removed from the index, and (because of the -f option),
+       from the working tree as well. Because this example lets the
+       shell expand the asterisk (i.e. you are listing the files
+       explicitly), it does not remove `subdir/git-foo.sh`.
+
+See Also
+--------
+gitlink:git-add[1]
+
+Author
+------
+Written by Linus Torvalds <torvalds@osdl.org>
+
+Documentation
+--------------
+Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
index 00537d8..8c58685 100644 (file)
@@ -24,6 +24,9 @@ OPTIONS
 -------
 The options available are:
 
+--cc::
+       Specify a starting "Cc:" value for each email.
+
 --chain-reply-to, --no-chain-reply-to::
        If this is set, each email will be sent as a reply to the previous
        email sent.  If disabled with "--no-chain-reply-to", all emails after
@@ -48,6 +51,9 @@ The options available are:
        Only necessary if --compose is also set.  If --compose
        is not set, this will be prompted for.
 
+--no-signed-off-by-cc::
+       Do not add emails foudn in Signed-off-by: lines to the cc list.
+
 --quiet::
        Make git-send-email less verbose.  One line per email should be
        all that is output.
@@ -61,6 +67,10 @@ The options available are:
        Only necessary if --compose is also set.  If --compose
        is not set, this will be prompted for.
 
+--suppress-from::
+       Do not add the From: address to the cc: list, if it shows up in a From:
+       line.
+
 --to::
        Specify the primary recipient of the emails generated.
        Generally, this will be the upstream maintainer of the
index 577f06a..08e0705 100644 (file)
@@ -3,7 +3,7 @@ git-send-pack(1)
 
 NAME
 ----
-git-send-pack - Push missing objects packed.
+git-send-pack - Push missing objects packed
 
 
 SYNOPSIS
index 6ef59ac..6742c9b 100644 (file)
@@ -3,7 +3,7 @@ git-sh-setup(1)
 
 NAME
 ----
-git-sh-setup - Common git shell script setup code.
+git-sh-setup - Common git shell script setup code
 
 SYNOPSIS
 --------
index 3f4d804..cc4266d 100644 (file)
@@ -8,7 +8,7 @@ git-shell - Restricted login shell for GIT over SSH only
 
 SYNOPSIS
 --------
-'git-shell -c <command> <argument>'
+'git-shell' -c <command> <argument>
 
 DESCRIPTION
 -----------
index 65ca77f..54fb922 100644 (file)
@@ -3,12 +3,12 @@ git-shortlog(1)
 
 NAME
 ----
-git-shortlog - Summarize 'git log' output.
+git-shortlog - Summarize 'git log' output
 
 
 SYNOPSIS
 --------
-'git-log --pretty=short | git shortlog'
+git-log --pretty=short | 'git-shortlog'
 
 DESCRIPTION
 -----------
index 7b1a9c9..f115b45 100644 (file)
@@ -3,14 +3,14 @@ git-show-branch(1)
 
 NAME
 ----
-git-show-branch - Show branches and their commits.
+git-show-branch - Show branches and their commits
 
 SYNOPSIS
 --------
 [verse]
-git-show-branch [--all] [--heads] [--tags] [--topo-order] [--current]
-       [--more=<n> | --list | --independent | --merge-base]
-       [--no-name | --sha1-name] [<rev> | <glob>]...
+'git-show-branch' [--all] [--heads] [--tags] [--topo-order] [--current]
+               [--more=<n> | --list | --independent | --merge-base]
+               [--no-name | --sha1-name] [<rev> | <glob>]...
 
 DESCRIPTION
 -----------
@@ -141,7 +141,7 @@ it, having the following in the configuration file may help:
 
 ------------
 
-With this,`git show-branch` without extra parameters would show
+With this, `git show-branch` without extra parameters would show
 only the primary branches.  In addition, if you happen to be on
 your topic branch, it is shown as well.
 
index 9c359a4..2b4df3f 100644 (file)
@@ -3,7 +3,7 @@ git-show(1)
 
 NAME
 ----
-git-show - Show one commit with difference it introduces.
+git-show - Show one commit with difference it introduces
 
 
 SYNOPSIS
index 753fc08..e446f48 100644 (file)
@@ -3,7 +3,7 @@ git-status(1)
 
 NAME
 ----
-git-status - Show working tree status.
+git-status - Show working tree status
 
 
 SYNOPSIS
index 528a1b6..3a03dd0 100644 (file)
@@ -3,7 +3,7 @@ git-stripspace(1)
 
 NAME
 ----
-git-stripspace - Filter out empty lines.
+git-stripspace - Filter out empty lines
 
 
 SYNOPSIS
index 63e28b8..b1b87c2 100644 (file)
@@ -9,11 +9,13 @@ git-svnimport - Import a SVN repository into git
 
 SYNOPSIS
 --------
+[verse]
 'git-svnimport' [ -o <branch-for-HEAD> ] [ -h ] [ -v ] [ -d | -D ]
-                       [ -C <GIT_repository> ] [ -i ] [ -u ] [-l limit_rev]
-                       [ -b branch_subdir ] [ -T trunk_subdir ] [ -t tag_subdir ]
-                       [ -s start_chg ] [ -m ] [ -M regex ]
-                       <SVN_repository_URL> [ <path> ]
+               [ -C <GIT_repository> ] [ -i ] [ -u ] [-l limit_rev]
+               [ -b branch_subdir ] [ -T trunk_subdir ] [ -t tag_subdir ]
+               [ -s start_chg ] [ -m ] [ -r ] [ -M regex ]
+               [ -I <ignorefile_name> ] [ -A <author_file> ]
+               <SVN_repository_URL> [ <path> ]
 
 
 DESCRIPTION
@@ -61,6 +63,34 @@ When importing incrementally, you might need to edit the .git/svn2git file.
        the git repository. Use this option if you want to import into a
        different branch.
 
+-r::
+       Prepend 'rX: ' to commit messages, where X is the imported
+       subversion revision.
+
+-I <ignorefile_name>::
+       Import the svn:ignore directory property to files with this
+       name in each directory. (The Subversion and GIT ignore
+       syntaxes are similar enough that using the Subversion patterns
+       directly with "-I .gitignore" will almost always just work.)
+
+-A <author_file>::
+       Read a file with lines on the form
++
+------
+       username = User's Full Name <email@addr.es>
+
+------
++
+and use "User's Full Name <email@addr.es>" as the GIT
+author and committer for Subversion commits made by
+"username". If encountering a commit made by a user not in the
+list, abort.
++
+For convenience, this data is saved to $GIT_DIR/svn-authors
+each time the -A option is provided, and read from that same
+file each time git-svnimport is run with an existing GIT
+repository without -A.
+
 -m::
        Attempt to detect merges based on the commit message. This option
        will enable default regexes that try to capture the name source
index e8892bb..45476c2 100644 (file)
@@ -3,16 +3,18 @@ git-tag(1)
 
 NAME
 ----
-git-tag -  Create a tag object signed with GPG
+git-tag - Create a tag object signed with GPG
 
 
 SYNOPSIS
 --------
+[verse]
 'git-tag' [-a | -s | -u <key-id>] [-f | -d] [-m <msg>] <name> [<head>]
+'git-tag' -l [<pattern>]
 
 DESCRIPTION
 -----------
-Adds a 'tag' reference in .git/refs/tags/
+Adds a 'tag' reference in `.git/refs/tags/`
 
 Unless `-f` is given, the tag must not yet exist in
 `.git/refs/tags/` directory.
@@ -32,6 +34,9 @@ GnuPG key for signing.
 
 `-d <tag>` deletes the tag.
 
+`-l <pattern>` lists tags that match the given pattern (or all
+if no pattern is given).
+
 OPTIONS
 -------
 -a::
@@ -49,6 +54,9 @@ OPTIONS
 -d::
        Delete an existing tag with the given name
 
+-l <pattern>::
+       List tags that match the given pattern (or all if no pattern is given).
+
 -m <msg>::
        Use the given tag message (instead of prompting)
 
diff --git a/Documentation/git-tools.txt b/Documentation/git-tools.txt
new file mode 100644 (file)
index 0000000..00e57a6
--- /dev/null
@@ -0,0 +1,97 @@
+A short git tools survey
+========================
+
+
+Introduction
+------------
+
+Apart from git contrib/ area there are some others third-party tools
+you may want to look.
+
+This document presents a brief summary of each tool and the corresponding
+link.
+
+
+Alternative/Augmentative Procelains
+-----------------------------------
+
+   - *Cogito* (http://www.kernel.org/pub/software/scm/cogito/)
+
+   Cogito is a version control system layered on top of the git tree history
+   storage system. It aims at seamless user interface and ease of use,
+   providing generally smoother user experience than the "raw" Core GIT
+   itself and indeed many other version control systems.
+
+
+   - *pg* (http://www.spearce.org/category/projects/scm/pg/)
+
+   pg is a shell script wrapper around GIT to help the user manage a set of
+   patches to files. pg is somewhat like quilt or StGIT, but it does have a
+   slightly different feature set.
+
+
+   - *StGit* (http://www.procode.org/stgit/)
+
+   Stacked GIT provides a quilt-like patch management functionality in the
+    GIT environment. You can easily manage your patches in the scope of GIT
+   until they get merged upstream.
+
+
+History Viewers
+---------------
+
+   - *gitk* (shipped with git-core)
+
+   gitk is a simple TK GUI for browsing history of GIT repositories easily.
+
+
+   - *gitview*  (contrib/)
+
+   gitview is a GTK based repository browser for git
+
+
+   - *gitweb* (ftp://ftp.kernel.org/pub/software/scm/gitweb/)
+
+   GITweb provides full-fledged web interface for GIT repositories.
+
+
+   - *qgit* (http://digilander.libero.it/mcostalba/)
+
+   QGit is a git/StGIT GUI viewer built on Qt/C++. QGit could be used
+   to browse history and directory tree, view annotated files, commit
+   changes cherry picking single files or applying patches.
+   Currently it is the fastest and most feature rich among the git
+   viewers and commit tools.
+
+
+
+Foreign SCM interface
+---------------------
+
+   - *git-svn* (contrib/)
+
+   git-svn is a simple conduit for changesets between a single Subversion
+   branch and git.
+
+
+   - *quilt2git / git2quilt* (http://home-tj.org/wiki/index.php/Misc)
+
+   These utilities convert patch series in a quilt repository and commit
+   series in git back and forth.
+
+
+Others
+------
+
+   - *(h)gct* (http://www.cyd.liu.se/users/~freku045/gct/)
+
+   Commit Tool or (h)gct is a GUI enabled commit tool for git and
+   Mercurial (hg). It allows the user to view diffs, select which files
+   to committed (or ignored / reverted) write commit messages and
+   perform the commit itself.
+
+   - *git.el* (contrib/)
+
+   This is an Emacs interface for git. The user interface is modeled on
+   pcl-cvs. It has been developed on Emacs 21 and will probably need some
+   tweaking to work on XEmacs.
index 31ea34d..1828062 100644 (file)
@@ -3,7 +3,7 @@ git-unpack-objects(1)
 
 NAME
 ----
-git-unpack-objects - Unpack objects from a packed archive.
+git-unpack-objects - Unpack objects from a packed archive
 
 
 SYNOPSIS
index c74311d..0a1b0ad 100644 (file)
@@ -8,11 +8,14 @@ git-update-index - Modifies the index or directory cache
 
 SYNOPSIS
 --------
+[verse]
 'git-update-index'
             [--add] [--remove | --force-remove] [--replace] 
             [--refresh [-q] [--unmerged] [--ignore-missing]]
             [--cacheinfo <mode> <object> <file>]\*
             [--chmod=(+|-)x]
+            [--assume-unchanged | --no-assume-unchanged]
+            [--really-refresh]
             [--info-only] [--index-info]
             [-z] [--stdin]
             [--verbose]
@@ -65,6 +68,18 @@ OPTIONS
 --chmod=(+|-)x::
         Set the execute permissions on the updated files.        
 
+--assume-unchanged, --no-assume-unchanged::
+       When these flags are specified, the object name recorded
+       for the paths are not updated.  Instead, these options
+       sets and unsets the "assume unchanged" bit for the
+       paths.  When the "assume unchanged" bit is on, git stops
+       checking the working tree files for possible
+       modifications, so you need to manually unset the bit to
+       tell git when you change the working tree file. This is
+       sometimes helpful when working with a big project on a
+       filesystem that has very slow lstat(2) system call
+       (e.g. cifs).
+
 --info-only::
        Do not create objects in the object database for all
        <file> arguments that follow this flag; just insert
@@ -193,6 +208,37 @@ $ git ls-files -s
 ------------
 
 
+Using "assume unchanged" bit
+----------------------------
+
+Many operations in git depend on your filesystem to have an
+efficient `lstat(2)` implementation, so that `st_mtime`
+information for working tree files can be cheaply checked to see
+if the file contents have changed from the version recorded in
+the index file.  Unfortunately, some filesystems have
+inefficient `lstat(2)`.  If your filesystem is one of them, you
+can set "assume unchanged" bit to paths you have not changed to
+cause git not to do this check.  Note that setting this bit on a
+path does not mean git will check the contents of the file to
+see if it has changed -- it makes git to omit any checking and
+assume it has *not* changed.  When you make changes to working
+tree files, you have to explicitly tell git about it by dropping
+"assume unchanged" bit, either before or after you modify them.
+
+In order to set "assume unchanged" bit, use `--assume-unchanged`
+option.  To unset, use `--no-assume-unchanged`.
+
+The command looks at `core.ignorestat` configuration variable.  When
+this is true, paths updated with `git-update-index paths...` and
+paths updated with other git commands that update both index and
+working tree (e.g. `git-apply --index`, `git-checkout-index -u`,
+and `git-read-tree -u`) are automatically marked as "assume
+unchanged".  Note that "assume unchanged" bit is *not* set if
+`git-update-index --refresh` finds the working tree file matches
+the index (use `git-update-index --really-refresh` if you want
+to mark them as "assume unchanged").
+
+
 Examples
 --------
 To update and refresh only the files already checked out:
@@ -201,6 +247,35 @@ To update and refresh only the files already checked out:
 $ git-checkout-index -n -f -a && git-update-index --ignore-missing --refresh
 ----------------
 
+On an inefficient filesystem with `core.ignorestat` set:
+
+------------
+$ git update-index --really-refresh <1>
+$ git update-index --no-assume-unchanged foo.c <2>
+$ git diff --name-only <3>
+$ edit foo.c
+$ git diff --name-only <4>
+M foo.c
+$ git update-index foo.c <5>
+$ git diff --name-only <6>
+$ edit foo.c
+$ git diff --name-only <7>
+$ git update-index --no-assume-unchanged foo.c <8>
+$ git diff --name-only <9>
+M foo.c
+
+<1> forces lstat(2) to set "assume unchanged" bits for paths
+    that match index.
+<2> mark the path to be edited.
+<3> this does lstat(2) and finds index matches the path.
+<4> this does lstat(2) and finds index does not match the path.
+<5> registering the new version to index sets "assume unchanged" bit.
+<6> and it is assumed unchanged.
+<7> even after you edit it.
+<8> you can tell about the change after the fact.
+<9> now it checks with lstat(2) and finds it has been changed.
+------------
+
 
 Configuration
 -------------
@@ -213,6 +288,9 @@ in the index and the file mode on the filesystem if they differ only on
 executable bit.   On such an unfortunate filesystem, you may
 need to use `git-update-index --chmod=`.
 
+The command looks at `core.ignorestat` configuration variable.  See
+'Using "assume unchanged" bit' section above.
+
 
 See Also
 --------
index 69715aa..475237f 100644 (file)
@@ -7,7 +7,7 @@ git-update-ref - update the object name stored in a ref safely
 
 SYNOPSIS
 --------
-`git-update-ref` <ref> <newvalue> [<oldvalue>]
+'git-update-ref' <ref> <newvalue> [<oldvalue>]
 
 DESCRIPTION
 -----------
index 3d8f8ef..4795e98 100644 (file)
@@ -3,7 +3,7 @@ git-upload-pack(1)
 
 NAME
 ----
-git-upload-pack - Send missing objects packed.
+git-upload-pack - Send missing objects packed
 
 
 SYNOPSIS
index c22d34f..90cb157 100644 (file)
@@ -8,7 +8,7 @@ git-var - Print the git users identity
 
 SYNOPSIS
 --------
-git-var [ -l | <variable> ]
+'git-var' [ -l | <variable> ]
 
 DESCRIPTION
 -----------
index d032280..4962d69 100644 (file)
@@ -3,7 +3,7 @@ git-verify-pack(1)
 
 NAME
 ----
-git-verify-pack - Validate packed git archive files.
+git-verify-pack - Validate packed git archive files
 
 
 SYNOPSIS
index b8a73c4..0f9bdb5 100644 (file)
@@ -3,7 +3,7 @@ git-verify-tag(1)
 
 NAME
 ----
-git-verify-tag - Check the GPG signature of tag.
+git-verify-tag - Check the GPG signature of tag
 
 SYNOPSIS
 --------
index 6c150b0..641cb7e 100644 (file)
@@ -3,7 +3,7 @@ git-whatchanged(1)
 
 NAME
 ----
-git-whatchanged - Show logs with difference each commit introduces.
+git-whatchanged - Show logs with difference each commit introduces
 
 
 SYNOPSIS
@@ -47,9 +47,9 @@ OPTIONS
        By default, differences for merge commits are not shown.
        With this flag, show differences to that commit from all
        of its parents.
-
-       However, it is not very useful in general, although it
-       *is* useful on a file-by-file basis.
++
+However, it is not very useful in general, although it
+*is* useful on a file-by-file basis.
 
 Examples
 --------
index 2d0ca9d..06b2e53 100644 (file)
@@ -12,77 +12,65 @@ SYNOPSIS
 
 DESCRIPTION
 -----------
-'git' is both a program and a directory content tracker system.
-The program 'git' is just a wrapper to reach the core git programs
-(or a potty if you like, as it's not exactly porcelain but still
-brings your stuff to the plumbing).
+Git is a fast, scalable, distributed revision control system with an
+unusually rich command set that provides both high-level operations
+and full access to internals.
+
+See this link:tutorial.html[tutorial] to get started, then see
+link:everyday.html[Everyday Git] for a useful minimum set of commands, and
+"man git-commandname" for documentation of each command.  CVS users may
+also want to read link:cvs-migration.html[CVS migration].
 
 OPTIONS
 -------
 --version::
-       prints the git suite version that the 'git' program came from.
+       Prints the git suite version that the 'git' program came from.
 
 --help::
-       prints the synopsis and a list of available commands.
-       If a git command is named this option will bring up the
-       man-page for that command.
+       Prints the synopsis and a list of the most commonly used
+       commands.  If a git command is named this option will bring up
+       the man-page for that command. If the option '--all' or '-a' is
+       given then all available commands are printed.
 
 --exec-path::
-       path to wherever your core git programs are installed.
+       Path to wherever your core git programs are installed.
        This can also be controlled by setting the GIT_EXEC_PATH
        environment variable. If no path is given 'git' will print
        the current setting and then exit.
 
 
-NOT LEARNING CORE GIT COMMANDS
-------------------------------
-
-This manual is intended to give complete background information
-and internal workings of git, which may be too much for most
-people.  The <<Discussion>> section below contains much useful
-definition and clarification - read that first.
-
-If you are interested in using git to manage (version control)
-projects, use link:tutorial.html[The Tutorial] to get you started,
-and then link:everyday.html[Everyday GIT] as a guide to the
-minimum set of commands you need to know for day-to-day work.
-Most likely, that will get you started, and you can go a long
-way without knowing the low level details too much.
-
-The link:core-tutorial.html[Core tutorial] document covers how things
-internally work.
-
-If you are migrating from CVS, link:cvs-migration.html[cvs
-migration] document may be helpful after you finish the
-tutorial.
+FURTHER DOCUMENTATION
+---------------------
 
-After you get the general feel from the tutorial and this
-overview page, you may want to take a look at the
-link:howto-index.html[howto] documents.
+See the references above to get started using git.  The following is
+probably more detail than necessary for a first-time user.
 
+The <<Discussion,Discussion>> section below and the
+link:core-tutorial.html[Core tutorial] both provide introductions to the
+underlying git architecture.
 
-CORE GIT COMMANDS
------------------
+See also the link:howto-index.html[howto] documents for some useful
+examples.
 
-If you are writing your own Porcelain, you need to be familiar
-with most of the low level commands --- I suggest starting from
-gitlink:git-update-index[1] and gitlink:git-read-tree[1].
+GIT COMMANDS
+------------
 
+We divide git into high level ("porcelain") commands and low level
+("plumbing") commands.
 
-Commands Overview
------------------
-The git commands can helpfully be split into those that manipulate
-the repository, the index and the files in the working tree, those that
-interrogate and compare them, and those that moves objects and
-references between repositories.
+Low-level commands (plumbing)
+-----------------------------
 
-In addition, git itself comes with a spartan set of porcelain
-commands.  They are usable but are not meant to compete with real
-Porcelains.
+Although git includes its
+own porcelain layer, its low-level commands are sufficient to support
+development of alternative porcelains.  Developers of such porcelains
+might start by reading about gitlink:git-update-index[1] and
+gitlink:git-read-tree[1].
 
-There are also some ancillary programs that can be viewed as useful
-aids for using the core commands but which are unlikely to be used by
-SCMs layered over git.
+We divide the low-level commands into commands that manipulate objects (in
+the repository, index, and working tree), commands that interrogate and
+compare objects, and commands that move objects and references between
+repositories.
 
 Manipulation commands
 ~~~~~~~~~~~~~~~~~~~~~
@@ -247,8 +235,14 @@ gitlink:git-upload-pack[1]::
        what are asked for.
 
 
-Porcelain-ish Commands
-----------------------
+High-level commands (porcelain)
+-------------------------------
+
+We separate the porcelain commands into the main commands and some
+ancillary user utilities.
+
+Main porcelain commands
+~~~~~~~~~~~~~~~~~~~~~~~
 
 gitlink:git-add[1]::
        Add paths to the index.
@@ -328,6 +322,9 @@ gitlink:git-revert[1]::
 gitlink:git-shortlog[1]::
        Summarizes 'git log' output.
 
+gitlink:git-show[1]::
+       Show one commit log and its diff.
+
 gitlink:git-show-branch[1]::
        Show branches and their commits.
 
@@ -342,7 +339,7 @@ gitlink:git-whatchanged[1]::
 
 
 Ancillary Commands
-------------------
+~~~~~~~~~~~~~~~~~~
 Manipulators:
 
 gitlink:git-applypatch[1]::
@@ -517,16 +514,14 @@ HEAD::
        a valid head 'name'
        (i.e. the contents of `$GIT_DIR/refs/heads/<head>`).
 
-<snap>::
-       a valid snapshot 'name'
-       (i.e. the contents of `$GIT_DIR/refs/snap/<snap>`).
-
 
 File/Directory Structure
 ------------------------
 
 Please see link:repository-layout.html[repository layout] document.
 
+Read link:hooks.html[hooks] for more details about each hook.
+
 Higher level SCMs may provide and manage additional information in the
 `$GIT_DIR`.
 
index 4ad1920..3824a95 100644 (file)
@@ -97,16 +97,31 @@ send out a commit notification e-mail.
 update
 ------
 
-This hook is invoked by `git-receive-pack`, which is invoked
-when a `git push` is done against the repository.  It takes
-three parameters, name of the ref being updated, old object name
-stored in the ref, and the new objectname to be stored in the
-ref.  Exiting with non-zero status from this hook prevents
-`git-receive-pack` from updating the ref.
-
-This can be used to prevent 'forced' update on certain refs by
+This hook is invoked by `git-receive-pack` on the remote repository,
+which is happens when a `git push` is done on a local repository.
+Just before updating the ref on the remote repository, the update hook
+is invoked.  It's exit status determins the success or failure of
+the ref update.
+
+The hook executes once for each ref to be updated, and takes
+three parameters:
+    - the name of the ref being updated,
+    - the old object name stored in the ref,
+    - and the new objectname to be stored in the ref.
+
+A zero exit from the update hook allows the ref to be updated.
+Exiting with a non-zero status prevents `git-receive-pack`
+from updating the ref.
+
+This hook can be used to prevent 'forced' update on certain refs by
 making sure that the object name is a commit object that is a
 descendant of the commit object named by the old object name.
+That is, to enforce a "fast forward only" policy.
+
+It could also be used to log the old..new status.  However, it
+does not know the entire set of branches, so it would end up
+firing one e-mail per ref when used naively, though.
+
 Another use suggested on the mailing list is to use this hook to
 implement access control which is finer grained than the one
 based on filesystem group.
@@ -115,20 +130,30 @@ The standard output of this hook is sent to /dev/null; if you
 want to report something to the git-send-pack on the other end,
 you can redirect your output to your stderr.
 
+
 post-update
 -----------
 
-This hook is invoked by `git-receive-pack`, which is invoked
-when a `git push` is done against the repository.  It takes
-variable number of parameters; each of which is the name of ref
-that was actually updated.
+This hook is invoked by `git-receive-pack` on the remote repository,
+which is happens when a `git push` is done on a local repository.
+It executes on the remote repository once after all the refs have
+been updated.
+
+It takes a variable number of parameters, each of which is the
+name of ref that was actually updated.
 
 This hook is meant primarily for notification, and cannot affect
 the outcome of `git-receive-pack`.
 
+The post-update hook can tell what are the heads that were pushed,
+but it does not know what their original and updated values are,
+so it is a poor place to do log old..new.
+
 The default post-update hook, when enabled, runs
 `git-update-server-info` to keep the information used by dumb
-transport up-to-date.
+transports (eg, http) up-to-date.  If you are publishing
+a git repository that is accessible via http, you should
+probably enable this hook.
 
 The standard output of this hook is sent to /dev/null; if you
 want to report something to the git-send-pack on the other end,
index 1f19bf8..98fbe7d 100644 (file)
@@ -89,6 +89,8 @@ hooks::
        commands.  A handful of sample hooks are installed when
        `git init-db` is run, but all of them are disabled by
        default.  To enable, they need to be made executable.
+       Read link:hooks.html[hooks] for more details about
+       each hook.
 
 index::
        The current index file for the repository.  It is
diff --git a/Documentation/technical/pack-format.txt b/Documentation/technical/pack-format.txt
new file mode 100644 (file)
index 0000000..ed2decc
--- /dev/null
@@ -0,0 +1,111 @@
+GIT pack format
+===============
+
+= pack-*.pack file has the following format:
+
+   - The header appears at the beginning and consists of the following:
+
+     4-byte signature
+     4-byte version number (network byte order)
+     4-byte number of objects contained in the pack (network byte order)
+
+     Observation: we cannot have more than 4G versions ;-) and
+     more than 4G objects in a pack.
+
+   - The header is followed by number of object entries, each of
+     which looks like this:
+
+     (undeltified representation)
+     n-byte type and length (4-bit type, (n-1)*7+4-bit length)
+     compressed data
+
+     (deltified representation)
+     n-byte type and length (4-bit type, (n-1)*7+4-bit length)
+     20-byte base object name
+     compressed delta data
+
+     Observation: length of each object is encoded in a variable
+     length format and is not constrained to 32-bit or anything.
+
+  - The trailer records 20-byte SHA1 checksum of all of the above.
+
+= pack-*.idx file has the following format:
+
+  - The header consists of 256 4-byte network byte order
+    integers.  N-th entry of this table records the number of
+    objects in the corresponding pack, the first byte of whose
+    object name are smaller than N.  This is called the
+    'first-level fan-out' table.
+
+    Observation: we would need to extend this to an array of
+    8-byte integers to go beyond 4G objects per pack, but it is
+    not strictly necessary.
+
+  - The header is followed by sorted 28-byte entries, one entry
+    per object in the pack.  Each entry is:
+
+    4-byte network byte order integer, recording where the
+    object is stored in the packfile as the offset from the
+    beginning.
+
+    20-byte object name.
+
+    Observation: we would definitely need to extend this to
+    8-byte integer plus 20-byte object name to handle a packfile
+    that is larger than 4GB.
+
+  - The file is concluded with a trailer:
+
+    A copy of the 20-byte SHA1 checksum at the end of
+    corresponding packfile.
+
+    20-byte SHA1-checksum of all of the above.
+
+Pack Idx file:
+
+       idx
+           +--------------------------------+
+           | fanout[0] = 2                  |-.
+           +--------------------------------+ |
+           | fanout[1]                      | |
+           +--------------------------------+ |
+           | fanout[2]                      | |
+           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
+           | fanout[255]                    | |
+           +--------------------------------+ |
+main       | offset                         | |
+index      | object name 00XXXXXXXXXXXXXXXX | |
+table      +--------------------------------+ | 
+           | offset                         | |
+           | object name 00XXXXXXXXXXXXXXXX | |
+           +--------------------------------+ |
+         .-| offset                         |<+
+         | | object name 01XXXXXXXXXXXXXXXX |
+         | +--------------------------------+
+         | | offset                         |
+         | | object name 01XXXXXXXXXXXXXXXX |
+         | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+         | | offset                         |
+         | | object name FFXXXXXXXXXXXXXXXX |
+         | +--------------------------------+
+trailer          | | packfile checksum              |
+         | +--------------------------------+
+         | | idxfile checksum               |
+         | +--------------------------------+
+          .-------.      
+                  |
+Pack file entry: <+
+
+     packed object header:
+       1-byte type (upper 4-bit)
+              size0 (lower 4-bit) 
+        n-byte sizeN (as long as MSB is set, each 7-bit)
+               size0..sizeN form 4+7+7+..+7 bit integer, size0
+               is the most significant part.
+     packed object data:
+        If it is not DELTA, then deflated bytes (the size above
+               is the size before compression).
+       If it is DELTA, then
+         20-byte base object name SHA1 (the size above is the
+               size of the delta data that follows).
+          delta data, deflated.
diff --git a/Documentation/technical/pack-heuristics.txt b/Documentation/technical/pack-heuristics.txt
new file mode 100644 (file)
index 0000000..eaab3ee
--- /dev/null
@@ -0,0 +1,466 @@
+        Concerning Git's Packing Heuristics
+        ===================================
+
+        Oh, here's a really stupid question:
+
+                  Where do I go
+               to learn the details
+           of git's packing heuristics?
+
+Be careful what you ask!
+
+Followers of the git, please open the git IRC Log and turn to
+February 10, 2006.
+
+It's a rare occasion, and we are joined by the King Git Himself,
+Linus Torvalds (linus).  Nathaniel Smith, (njs`), has the floor
+and seeks enlightenment.  Others are present, but silent.
+
+Let's listen in!
+
+    <njs`> Oh, here's a really stupid question -- where do I go to
+        learn the details of git's packing heuristics?  google avails
+        me not, reading the source didn't help a lot, and wading
+        through the whole mailing list seems less efficient than any
+        of that.
+
+It is a bold start!  A plea for help combined with a simultaneous
+tri-part attack on some of the tried and true mainstays in the quest
+for enlightenment.  Brash accusations of google being useless. Hubris!
+Maligning the source.  Heresy!  Disdain for the mailing list archives.
+Woe.
+
+    <pasky> yes, the packing-related delta stuff is somewhat
+        mysterious even for me ;)
+
+Ah!  Modesty after all.
+
+    <linus> njs, I don't think the docs exist. That's something where
+        I don't think anybody else than me even really got involved.
+        Most of the rest of git others have been busy with (especially
+        Junio), but packing nobody touched after I did it.
+
+It's cryptic, yet vague.  Linus in style for sure.  Wise men
+interpret this as an apology.  A few argue it is merely a
+statement of fact.
+
+    <njs`> I guess the next step is "read the source again", but I
+        have to build up a certain level of gumption first :-)
+
+Indeed!  On both points.
+
+    <linus> The packing heuristic is actually really really simple.
+
+Bait...
+
+    <linus> But strange.
+
+And switch.  That ought to do it!
+
+    <linus> Remember: git really doesn't follow files. So what it does is
+        - generate a list of all objects
+        - sort the list according to magic heuristics
+        - walk the list, using a sliding window, seeing if an object
+          can be diffed against another object in the window
+        - write out the list in recency order
+
+The traditional understatement:
+
+    <njs`> I suspect that what I'm missing is the precise definition of
+        the word "magic"
+
+The traditional insight:
+
+    <pasky> yes
+
+And Bable-like confusion flowed.
+
+    <njs`> oh, hmm, and I'm not sure what this sliding window means either
+
+    <pasky> iirc, it appeared to me to be just the sha1 of the object
+        when reading the code casually ...
+
+        ... which simply doesn't sound as a very good heuristics, though ;)
+
+    <njs`> .....and recency order.  okay, I think it's clear I didn't
+       even realize how much I wasn't realizing :-)
+
+Ah, grasshopper!  And thus the enlightenment begins anew.
+
+    <linus> The "magic" is actually in theory totally arbitrary.
+        ANY order will give you a working pack, but no, it's not
+        ordered by SHA1.
+
+        Before talking about the ordering for the sliding delta
+        window, let's talk about the recency order. That's more
+        important in one way.
+
+    <njs`> Right, but if all you want is a working way to pack things
+        together, you could just use cat and save yourself some
+        trouble...
+
+Waaait for it....
+
+    <linus> The recency ordering (which is basically: put objects
+        _physically_ into the pack in the order that they are
+        "reachable" from the head) is important.
+
+    <njs`> okay
+
+    <linus> It's important because that's the thing that gives packs
+        good locality. It keeps the objects close to the head (whether
+        they are old or new, but they are _reachable_ from the head)
+        at the head of the pack. So packs actually have absolutely
+        _wonderful_ IO patterns.
+
+Read that again, because it is important.
+
+    <linus> But recency ordering is totally useless for deciding how
+        to actually generate the deltas, so the delta ordering is
+        something else.
+
+        The delta ordering is (wait for it):
+        - first sort by the "basename" of the object, as defined by
+          the name the object was _first_ reached through when
+          generating the object list
+        - within the same basename, sort by size of the object
+        - but always sort different types separately (commits first).
+
+        That's not exactly it, but it's very close.
+
+    <njs`> The "_first_ reached" thing is not too important, just you
+        need some way to break ties since the same objects may be
+        reachable many ways, yes?
+
+And as if to clarify:
+
+    <linus> The point is that it's all really just any random
+        heuristic, and the ordering is totally unimportant for
+        correctness, but it helps a lot if the heuristic gives
+        "clumping" for things that are likely to delta well against
+        each other.
+
+It is an important point, so secretly, I did my own research and have
+included my results below.  To be fair, it has changed some over time.
+And through the magic of Revisionistic History, I draw upon this entry
+from The Git IRC Logs on my father's birthday, March 1:
+
+    <gitster> The quote from the above linus should be rewritten a
+        bit (wait for it):
+        - first sort by type.  Different objects never delta with
+         each other.
+        - then sort by filename/dirname.  hash of the basename
+          occupies the top BITS_PER_INT-DIR_BITS bits, and bottom
+          DIR_BITS are for the hash of leading path elements.
+        - then if we are doing "thin" pack, the objects we are _not_
+          going to pack but we know about are sorted earlier than
+          other objects.
+        - and finally sort by size, larger to smaller.
+
+In one swell-foop, clarification and obscurification!  Nonetheless,
+authoritative.  Cryptic, yet concise.  It even solicits notions of
+quotes from The Source Code.  Clearly, more study is needed.
+
+    <gitster> That's the sort order.  What this means is:
+        - we do not delta different object types.
+       - we prefer to delta the objects with the same full path, but
+          allow files with the same name from different directories.
+       - we always prefer to delta against objects we are not going
+          to send, if there are some.
+       - we prefer to delta against larger objects, so that we have
+          lots of removals.
+
+        The penultimate rule is for "thin" packs.  It is used when
+        the other side is known to have such objects.
+
+There it is again. "Thin" packs.  I'm thinking to myself, "What
+is a 'thin' pack?"  So I ask:
+
+    <jdl> What is a "thin" pack?
+
+    <gitster> Use of --objects-edge to rev-list as the upstream of
+        pack-objects.  The pack transfer protocol negotiates that.
+
+Woo hoo!  Cleared that _right_ up!
+
+    <gitster> There are two directions - push and fetch.
+
+There!  Did you see it?  It is not '"push" and "pull"'!  How often the
+confusion has started here.  So casually mentioned, too!
+
+    <gitster> For push, git-send-pack invokes git-receive-pack on the
+        other end.  The receive-pack says "I have up to these commits".
+        send-pack looks at them, and computes what are missing from
+        the other end.  So "thin" could be the default there.
+
+        In the other direction, fetch, git-fetch-pack and
+        git-clone-pack invokes git-upload-pack on the other end
+       (via ssh or by talking to the daemon).
+
+       There are two cases: fetch-pack with -k and clone-pack is one,
+        fetch-pack without -k is the other.  clone-pack and fetch-pack
+        with -k will keep the downloaded packfile without expanded, so
+        we do not use thin pack transfer.  Otherwise, the generated
+        pack will have delta without base object in the same pack.
+
+        But fetch-pack without -k will explode the received pack into
+        individual objects, so we automatically ask upload-pack to
+        give us a thin pack if upload-pack supports it.
+
+OK then.
+
+Uh.
+
+Let's return to the previous conversation still in progress.
+
+    <njs`> and "basename" means something like "the tail of end of
+        path of file objects and dir objects, as per basename(3), and
+        we just declare all commit and tag objects to have the same
+        basename" or something?
+
+Luckily, that too is a point that gitster clarified for us!
+
+If I might add, the trick is to make files that _might_ be similar be
+located close to each other in the hash buckets based on their file
+names.  It used to be that "foo/Makefile", "bar/baz/quux/Makefile" and
+"Makefile" all landed in the same bucket due to their common basename,
+"Makefile". However, now they land in "close" buckets.
+
+The algorithm allows not just for the _same_ bucket, but for _close_
+buckets to be considered delta candidates.  The rationale is
+essentially that files, like Makefiles, often have very similar
+content no matter what directory they live in.
+
+    <linus> I played around with different delta algorithms, and with
+        making the "delta window" bigger, but having too big of a
+        sliding window makes it very expensive to generate the pack:
+        you need to compare every object with a _ton_ of other objects.
+
+        There are a number of other trivial heuristics too, which
+        basically boil down to "don't bother even trying to delta this
+        pair" if we can tell before-hand that the delta isn't worth it
+        (due to size differences, where we can take a previous delta
+        result into account to decide that "ok, no point in trying
+        that one, it will be worse").
+
+        End result: packing is actually very size efficient. It's
+        somewhat CPU-wasteful, but on the other hand, since you're
+        really only supposed to do it maybe once a month (and you can
+        do it during the night), nobody really seems to care.
+
+Nice Engineering Touch, there.  Find when it doesn't matter, and
+proclaim it a non-issue.  Good style too!
+
+    <njs`> So, just to repeat to see if I'm following, we start by
+        getting a list of the objects we want to pack, we sort it by
+        this heuristic (basically lexicographically on the tuple
+        (type, basename, size)).
+
+        Then we walk through this list, and calculate a delta of
+        each object against the last n (tunable paramater) objects,
+        and pick the smallest of these deltas.
+
+Vastly simplified, but the essence is there!
+
+    <linus> Correct.
+
+    <njs`> And then once we have picked a delta or fulltext to
+        represent each object, we re-sort by recency, and write them
+        out in that order.
+
+    <linus> Yup. Some other small details:
+
+And of course there is the "Other Shoe" Factor too.
+
+    <linus> - We limit the delta depth to another magic value (right
+        now both the window and delta depth magic values are just "10")
+
+    <njs`> Hrm, my intuition is that you'd end up with really _bad_ IO
+        patterns, because the things you want are near by, but to
+        actually reconstruct them you may have to jump all over in
+        random ways.
+
+    <linus> - When we write out a delta, and we haven't yet written
+        out the object it is a delta against, we write out the base
+        object first.  And no, when we reconstruct them, we actually
+        get nice IO patterns, because:
+        - larger objects tend to be "more recent" (Linus' law: files grow)
+        - we actively try to generate deltas from a larger object to a
+          smaller one
+        - this means that the top-of-tree very seldom has deltas
+          (ie deltas in _practice_ are "backwards deltas")
+
+Again, we should reread that whole paragraph.  Not just because
+Linus has slipped Linus's Law in there on us, but because it is
+important.  Let's make sure we clarify some of the points here:
+
+    <njs`> So the point is just that in practice, delta order and
+        recency order match each other quite well.
+
+    <linus> Yes. There's another nice side to this (and yes, it was
+       designed that way ;):
+        - the reason we generate deltas against the larger object is
+         actually a big space saver too!
+
+    <njs`> Hmm, but your last comment (if "we haven't yet written out
+        the object it is a delta against, we write out the base object
+        first"), seems like it would make these facts mostly
+        irrelevant because even if in practice you would not have to
+        wander around much, in fact you just brute-force say that in
+        the cases where you might have to wander, don't do that :-)
+
+    <linus> Yes and no. Notice the rule: we only write out the base
+        object first if the delta against it was more recent.  That
+        means that you can actually have deltas that refer to a base
+        object that is _not_ close to the delta object, but that only
+        happens when the delta is needed to generate an _old_ object.
+
+    <linus> See?
+
+Yeah, no.  I missed that on the first two or three readings myself.
+
+    <linus> This keeps the front of the pack dense. The front of the
+        pack never contains data that isn't relevant to a "recent"
+        object.  The size optimization comes from our use of xdelta
+        (but is true for many other delta algorithms): removing data
+        is cheaper (in size) than adding data.
+
+        When you remove data, you only need to say "copy bytes n--m".
+       In contrast, in a delta that _adds_ data, you have to say "add
+        these bytes: 'actual data goes here'"
+
+    *** njs` has quit: Read error: 104 (Connection reset by peer)
+
+    <linus> Uhhuh. I hope I didn't blow njs` mind.
+
+    *** njs` has joined channel #git
+
+    <pasky> :)
+
+The silent observers are amused.  Of course.
+
+And as if njs` was expected to be omniscient:
+
+    <linus> njs - did you miss anything?
+
+OK, I'll spell it out.  That's Geek Humor.  If njs` was not actually
+connected for a little bit there, how would he know if missed anything
+while he was disconnected?  He's a benevolent dictator with a sense of
+humor!  Well noted!
+
+    <njs`> Stupid router.  Or gremlins, or whatever.
+
+It's a cheap shot at Cisco.  Take 'em when you can.
+
+    <njs`> Yes and no. Notice the rule: we only write out the base
+        object first if the delta against it was more recent.
+
+        I'm getting lost in all these orders, let me re-read :-)
+       So the write-out order is from most recent to least recent?
+        (Conceivably it could be the opposite way too, I'm not sure if
+        we've said) though my connection back at home is logging, so I
+        can just read what you said there :-)
+
+And for those of you paying attention, the Omniscient Trick has just
+been detailed!
+
+    <linus> Yes, we always write out most recent first
+
+For the other record:
+
+    <pasky> njs`: http://pastebin.com/547965
+
+The 'net never forgets, so that should be good until the end of time.
+
+    <njs`> And, yeah, I got the part about deeper-in-history stuff
+        having worse IO characteristics, one sort of doesn't care.
+
+    <linus> With the caveat that if the "most recent" needs an older
+        object to delta against (hey, shrinking sometimes does
+        happen), we write out the old object with the delta.
+
+    <njs`> (if only it happened more...)
+
+    <linus> Anyway, the pack-file could easily be denser still, but
+        because it's used both for streaming (the git protocol) and
+        for on-disk, it has a few pessimizations.
+
+Actually, it is a made-up word. But it is a made-up word being
+used as setup for a later optimization, which is a real word:
+
+    <linus> In particular, while the pack-file is then compressed,
+        it's compressed just one object at a time, so the actual
+        compression factor is less than it could be in theory. But it
+        means that it's all nice random-access with a simple index to
+        do "object name->location in packfile" translation.
+
+    <njs`> I'm assuming the real win for delta-ing large->small is
+        more homogenous statistics for gzip to run over?
+
+        (You have to put the bytes in one place or another, but
+        putting them in a larger blob wins on compression)
+
+        Actually, what is the compression strategy -- each delta
+        individually gzipped, the whole file gzipped, somewhere in
+        between, no compression at all, ....?
+
+        Right.
+
+Reality IRC sets in.  For example:
+
+    <pasky> I'll read the rest in the morning, I really have to go
+        sleep or there's no hope whatsoever for me at the today's
+        exam... g'nite all.
+
+Heh.
+
+    <linus> pasky: g'nite
+
+    <njs`> pasky: 'luck
+
+    <linus> Right: large->small matters exactly because of compression
+        behaviour. If it was non-compressed, it probably wouldn't make
+        any difference.
+
+    <njs`> yeah
+
+    <linus> Anyway: I'm not even trying to claim that the pack-files
+        are perfect, but they do tend to have a nice balance of
+        density vs ease-of use.
+
+Gasp!  OK, saved.  That's a fair Engineering trade off.  Close call!
+In fact, Linus reflects on some Basic Engineering Fundamentals,
+design options, etc.
+
+    <linus> More importantly, they allow git to still _conceptually_
+        never deal with deltas at all, and be a "whole object" store.
+
+        Which has some problems (we discussed bad huge-file
+        behaviour on the git lists the other day), but it does mean
+        that the basic git concepts are really really simple and
+        straightforward.
+
+        It's all been quite stable.
+
+        Which I think is very much a result of having very simple
+        basic ideas, so that there's never any confusion about what's
+        going on.
+
+        Bugs happen, but they are "simple" bugs. And bugs that
+        actually get some object store detail wrong are almost always
+        so obious that they never go anywhere.
+
+    <njs`> Yeah.
+
+Nuff said.
+
+    <linus> Anyway.  I'm off for bed. It's not 6AM here, but I've got
+        three kids, and have to get up early in the morning to send
+        them off. I need my beauty sleep.
+
+    <njs`> :-)
+
+    <njs`> appreciate the infodump, I really was failing to find the
+        details on git packs :-)
+
+And now you know the rest of the story.
index 66680d7..fa79b01 100644 (file)
@@ -309,7 +309,7 @@ git diff HEAD^^ HEAD^
 -------------------------------------
 
 shows the difference between that previous state and the state two
-commits ago.  Also, HEAD~5 can be used as a shorthand for HEAD^^^^^,
+commits ago.  Also, HEAD~5 can be used as a shorthand for HEAD{caret}{caret}{caret}{caret}{caret},
 and more generally HEAD~n can refer to the nth previous commit.
 Commits representing merges have more than one parent, and you can
 specify which parent to follow in that case; see
index 1056b7c..d2200ac 100755 (executable)
@@ -1,14 +1,17 @@
 #!/bin/sh
 
 GVF=GIT-VERSION-FILE
-DEF_VER=v1.2.GIT
+DEF_VER=v1.3-rc2.GIT
 
 # First try git-describe, then see if there is a version file
 # (included in release tarballs), then default
 if VN=$(git-describe --abbrev=4 HEAD 2>/dev/null); then
        VN=$(echo "$VN" | sed -e 's/-/./g');
-else
+elif test -f version
+then
        VN=$(cat version) || VN="$DEF_VER"
+else
+       VN="$DEF_VER"
 fi
 
 VN=$(expr "$VN" : v*'\(.*\)')
diff --git a/INSTALL b/INSTALL
index 433449f..63af8ec 100644 (file)
--- a/INSTALL
+++ b/INSTALL
@@ -40,9 +40,7 @@ Issues of note:
 
          If you don't have openssl, you can use one of the SHA1 libraries
          that come with git (git includes the one from Mozilla, and has
-         its own PowerPC-optimized one too - see the Makefile), and you
-         can avoid the bignum support by excising git-rev-list support
-         for "--merge-order" (by hand).
+         its own PowerPC and ARM optimized ones too - see the Makefile).
 
        - "libcurl" and "curl" executable.  git-http-fetch and
          git-fetch use them.  If you do not use http
index 648469e..3367b8c 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -6,8 +6,8 @@ all:
 # on non-x86 architectures (e.g. PowerPC), while the OpenSSL version (default
 # choice) has very fast version optimized for i586.
 #
-# Define NO_OPENSSL environment variable if you do not have OpenSSL. You will
-# miss out git-rev-list --merge-order. This also implies MOZILLA_SHA1.
+# Define NO_OPENSSL environment variable if you do not have OpenSSL.
+# This also implies MOZILLA_SHA1.
 #
 # Define NO_CURL if you do not have curl installed.  git-http-pull and
 # git-http-push are not built, and you cannot use http:// and https://
@@ -53,6 +53,13 @@ all:
 # Define NO_SOCKADDR_STORAGE if your platform does not have struct
 # sockaddr_storage.
 #
+# Define NO_ICONV if your libc does not properly support iconv.
+#
+# Define NO_ACCURATE_DIFF if your diff program at least sometimes misses
+# a missing newline at the end of the file.
+#
+# Define NO_PYTHON if you want to loose all benefits of the recursive merge.
+#
 # Define COLLISION_CHECK below if you believe that SHA1's
 # 1461501637330902918203684832716283019655932542976 hashes do not give you
 # sufficient guarantee that no collisions between objects will ever happen.
@@ -70,6 +77,12 @@ GIT-VERSION-FILE: .FORCE-GIT-VERSION-FILE
        @$(SHELL_PATH) ./GIT-VERSION-GEN
 -include GIT-VERSION-FILE
 
+uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not')
+uname_M := $(shell sh -c 'uname -m 2>/dev/null || echo not')
+uname_O := $(shell sh -c 'uname -o 2>/dev/null || echo not')
+uname_R := $(shell sh -c 'uname -r 2>/dev/null || echo not')
+uname_P := $(shell sh -c 'uname -p 2>/dev/null || echo not')
+
 # CFLAGS and LDFLAGS are for the users to override from the command line.
 
 CFLAGS = -g -O2 -Wall
@@ -80,7 +93,7 @@ STRIP ?= strip
 
 prefix = $(HOME)
 bindir = $(prefix)/bin
-gitexecdir = $(prefix)/bin
+gitexecdir = $(bindir)
 template_dir = $(prefix)/share/git-core/templates/
 GIT_PYTHON_DIR = $(prefix)/share/git-core/python
 # DESTDIR=
@@ -101,13 +114,13 @@ SPARSE_FLAGS = -D__BIG_ENDIAN__ -D__powerpc__
 
 SCRIPT_SH = \
        git-add.sh git-bisect.sh git-branch.sh git-checkout.sh \
-       git-cherry.sh git-clone.sh git-commit.sh \
+       git-cherry.sh git-clean.sh git-clone.sh git-commit.sh \
        git-count-objects.sh git-diff.sh git-fetch.sh \
        git-format-patch.sh git-log.sh git-ls-remote.sh \
        git-merge-one-file.sh git-parse-remote.sh \
        git-prune.sh git-pull.sh git-push.sh git-rebase.sh \
        git-repack.sh git-request-pull.sh git-reset.sh \
-       git-resolve.sh git-revert.sh git-sh-setup.sh \
+       git-resolve.sh git-revert.sh git-rm.sh git-sh-setup.sh \
        git-tag.sh git-verify-tag.sh git-whatchanged.sh \
        git-applymbox.sh git-applypatch.sh git-am.sh \
        git-merge.sh git-merge-stupid.sh git-merge-octopus.sh \
@@ -117,6 +130,7 @@ SCRIPT_SH = \
 SCRIPT_PERL = \
        git-archimport.perl git-cvsimport.perl git-relink.perl \
        git-shortlog.perl git-fmt-merge-msg.perl git-rerere.perl \
+       git-annotate.perl git-cvsserver.perl \
        git-svnimport.perl git-mv.perl git-cvsexportcommit.perl
 
 SCRIPT_PYTHON = \
@@ -127,9 +141,9 @@ SCRIPTS = $(patsubst %.sh,%,$(SCRIPT_SH)) \
          $(patsubst %.py,%,$(SCRIPT_PYTHON)) \
          git-cherry-pick git-show git-status
 
-# The ones that do not have to link with lcrypto nor lz.
+# The ones that do not have to link with lcrypto, lz nor xdiff.
 SIMPLE_PROGRAMS = \
-       git-get-tar-commit-id$X git-mailinfo$X git-mailsplit$X \
+       git-get-tar-commit-id$X git-mailsplit$X \
        git-stripspace$X git-daemon$X
 
 # ... and all the rest that could be moved out of bindir to gitexecdir
@@ -139,9 +153,9 @@ PROGRAMS = \
        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-hash-object$X git-index-pack$X git-init-db$X git-local-fetch$X \
+       git-ls-files$X git-ls-tree$X git-mailinfo$X git-merge-base$X \
+       git-merge-index$X git-mktag$X git-mktree$X git-pack-objects$X git-patch-id$X \
        git-peek-remote$X git-prune-packed$X git-read-tree$X \
        git-receive-pack$X git-rev-list$X git-rev-parse$X \
        git-send-pack$X git-show-branch$X git-shell$X \
@@ -151,7 +165,7 @@ PROGRAMS = \
        git-upload-pack$X git-verify-pack$X git-write-tree$X \
        git-update-ref$X git-symbolic-ref$X git-check-ref-format$X \
        git-name-rev$X git-pack-redundant$X git-repo-config$X git-var$X \
-       git-describe$X
+       git-describe$X git-merge-tree$X git-blame$X git-imap-send$X
 
 # what 'all' will build and 'install' will install, in gitexecdir
 ALL_PROGRAMS = $(PROGRAMS) $(SIMPLE_PROGRAMS) $(SCRIPTS)
@@ -174,34 +188,31 @@ PYMODULES = \
        gitMergeCommon.py
 
 LIB_FILE=libgit.a
+XDIFF_LIB=xdiff/lib.a
 
 LIB_H = \
-       blob.h cache.h commit.h count-delta.h csum-file.h delta.h \
-       diff.h epoch.h object.h pack.h pkt-line.h quote.h refs.h \
-       run-command.h strbuf.h tag.h tree.h git-compat-util.h
+       blob.h cache.h commit.h csum-file.h delta.h \
+       diff.h object.h pack.h pkt-line.h quote.h refs.h \
+       run-command.h strbuf.h tag.h tree.h git-compat-util.h revision.h \
+       tree-walk.h
 
 DIFF_OBJS = \
        diff.o diffcore-break.o diffcore-order.o diffcore-pathspec.o \
-       diffcore-pickaxe.o diffcore-rename.o tree-diff.o combine-diff.o
+       diffcore-pickaxe.o diffcore-rename.o tree-diff.o combine-diff.o \
+       diffcore-delta.o
 
 LIB_OBJS = \
-       blob.o commit.o connect.o count-delta.o csum-file.o \
+       blob.o commit.o connect.o csum-file.o \
        date.o diff-delta.o entry.o exec_cmd.o ident.o index.o \
        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 config.o environment.o ctype.o copy.o \
-       fetch-clone.o \
+       fetch-clone.o revision.o pager.o tree-walk.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))'
+GITLIBS = $(LIB_FILE) $(XDIFF_LIB)
+LIBS = $(GITLIBS) -lz
 
 #
 # Platform specific tweaks
@@ -210,28 +221,28 @@ shellquote = '$(call shq,$(1))'
 # We choose to avoid "if .. else if .. else .. endif endif"
 # because maintaining the nesting to match is a pain.  If
 # we had "elif" things would have been much nicer...
-uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not')
-uname_M := $(shell sh -c 'uname -m 2>/dev/null || echo not')
-uname_O := $(shell sh -c 'uname -o 2>/dev/null || echo not')
-uname_R := $(shell sh -c 'uname -r 2>/dev/null || echo not')
 
 ifeq ($(uname_S),Darwin)
        NEEDS_SSL_WITH_CRYPTO = YesPlease
        NEEDS_LIBICONV = YesPlease
        ## fink
-       ALL_CFLAGS += -I/sw/include
-       ALL_LDFLAGS += -L/sw/lib
+       ifeq ($(shell test -d /sw/lib && echo y),y)
+               ALL_CFLAGS += -I/sw/include
+               ALL_LDFLAGS += -L/sw/lib
+       endif
        ## darwinports
-       ALL_CFLAGS += -I/opt/local/include
-       ALL_LDFLAGS += -L/opt/local/lib
+       ifeq ($(shell test -d /opt/local/lib && echo y),y)
+               ALL_CFLAGS += -I/opt/local/include
+               ALL_LDFLAGS += -L/opt/local/lib
+       endif
 endif
 ifeq ($(uname_S),SunOS)
        NEEDS_SOCKET = YesPlease
        NEEDS_NSL = YesPlease
-       NEEDS_LIBICONV = YesPlease
        SHELL_PATH = /bin/bash
        NO_STRCASESTR = YesPlease
        ifeq ($(uname_R),5.8)
+               NEEDS_LIBICONV = YesPlease
                NO_UNSETENV = YesPlease
                NO_SETENV = YesPlease
        endif
@@ -271,6 +282,16 @@ ifeq ($(uname_S),AIX)
        NO_STRCASESTR=YesPlease
        NEEDS_LIBICONV=YesPlease
 endif
+ifeq ($(uname_S),IRIX64)
+       NO_IPV6=YesPlease
+       NO_SETENV=YesPlease
+       NO_STRCASESTR=YesPlease
+       NO_SOCKADDR_STORAGE=YesPlease
+       SHELL_PATH=/usr/gnu/bin/bash
+       ALL_CFLAGS += -DPATH_MAX=1024
+       # for now, build 32-bit version
+       ALL_LDFLAGS += -L/usr/lib32
+endif
 ifneq (,$(findstring arm,$(uname_M)))
        ARM_SHA1 = YesPlease
 endif
@@ -280,8 +301,10 @@ endif
 ifdef WITH_OWN_SUBPROCESS_PY
        PYMODULES += compat/subprocess.py
 else
-       ifneq ($(shell $(PYTHON_PATH) -c 'import subprocess;print"OK"' 2>/dev/null),OK)
-               PYMODULES += compat/subprocess.py
+       ifeq ($(NO_PYTHON),)
+               ifneq ($(shell $(PYTHON_PATH) -c 'import subprocess;print"OK"' 2>/dev/null),OK)
+                       PYMODULES += compat/subprocess.py
+               endif
        endif
 endif
 
@@ -308,7 +331,6 @@ ifndef NO_CURL
 endif
 
 ifndef NO_OPENSSL
-       LIB_OBJS += epoch.o
        OPENSSL_LIBSSL = -lssl
        ifdef OPENSSLDIR
                # Again this may be problematic -- gcc does not always want -R.
@@ -380,6 +402,10 @@ else
 endif
 endif
 
+ifdef NO_ICONV
+       ALL_CFLAGS += -DNO_ICONV
+endif
+
 ifdef PPC_SHA1
        SHA1_HEADER = "ppc/sha1.h"
        LIB_OBJS += ppc/sha1.o ppc/sha1ppc.o
@@ -397,8 +423,25 @@ else
 endif
 endif
 endif
+ifdef NO_ACCURATE_DIFF
+       ALL_CFLAGS += -DNO_ACCURATE_DIFF
+endif
+
+# Shell quote (do not use $(call) to accomodate ancient setups);
+
+SHA1_HEADER_SQ = $(subst ','\'',$(SHA1_HEADER))
 
-ALL_CFLAGS += -DSHA1_HEADER=$(call shellquote,$(SHA1_HEADER)) $(COMPAT_CFLAGS)
+DESTDIR_SQ = $(subst ','\'',$(DESTDIR))
+bindir_SQ = $(subst ','\'',$(bindir))
+gitexecdir_SQ = $(subst ','\'',$(gitexecdir))
+template_dir_SQ = $(subst ','\'',$(template_dir))
+
+SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH))
+PERL_PATH_SQ = $(subst ','\'',$(PERL_PATH))
+PYTHON_PATH_SQ = $(subst ','\'',$(PYTHON_PATH))
+GIT_PYTHON_DIR_SQ = $(subst ','\'',$(GIT_PYTHON_DIR))
+
+ALL_CFLAGS += -DSHA1_HEADER='$(SHA1_HEADER_SQ)' $(COMPAT_CFLAGS)
 LIB_OBJS += $(COMPAT_OBJS)
 export prefix TAR INSTALL DESTDIR SHELL_PATH template_dir
 ### Build rules
@@ -411,29 +454,34 @@ all:
 strip: $(PROGRAMS) git$X
        $(STRIP) $(STRIP_OPTS) $(PROGRAMS) git$X
 
-git$X: git.c $(LIB_FILE)
+git$X: git.c common-cmds.h $(GITLIBS)
        $(CC) -DGIT_VERSION='"$(GIT_VERSION)"' \
-               $(CFLAGS) $(COMPAT_CFLAGS) -o $@ $(filter %.c,$^) $(LIB_FILE)
+               $(ALL_CFLAGS) -o $@ $(filter %.c,$^) \
+               $(ALL_LDFLAGS) $(LIBS)
+
+common-cmds.h: Documentation/git-*.txt
+       ./generate-cmdlist.sh > $@
 
 $(patsubst %.sh,%,$(SCRIPT_SH)) : % : %.sh
        rm -f $@
-       sed -e '1s|#!.*/sh|#!$(call shq,$(SHELL_PATH))|' \
+       sed -e '1s|#!.*/sh|#!$(SHELL_PATH_SQ)|' \
            -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \
            -e 's/@@NO_CURL@@/$(NO_CURL)/g' \
+           -e 's/@@NO_PYTHON@@/$(NO_PYTHON)/g' \
            $@.sh >$@
        chmod +x $@
 
 $(patsubst %.perl,%,$(SCRIPT_PERL)) : % : %.perl
        rm -f $@
-       sed -e '1s|#!.*perl|#!$(call shq,$(PERL_PATH))|' \
+       sed -e '1s|#!.*perl|#!$(PERL_PATH_SQ)|' \
            -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \
            $@.perl >$@
        chmod +x $@
 
 $(patsubst %.py,%,$(SCRIPT_PYTHON)) : % : %.py
        rm -f $@
-       sed -e '1s|#!.*python|#!$(call shq,$(PYTHON_PATH))|' \
-           -e 's|@@GIT_PYTHON_PATH@@|$(call shq,$(GIT_PYTHON_DIR))|g' \
+       sed -e '1s|#!.*python|#!$(PYTHON_PATH_SQ)|' \
+           -e 's|@@GIT_PYTHON_PATH@@|$(GIT_PYTHON_DIR_SQ)|g' \
            -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \
            $@.py >$@
        chmod +x $@
@@ -459,46 +507,80 @@ git$X git.spec \
 %.o: %.S
        $(CC) -o $*.o -c $(ALL_CFLAGS) $<
 
-exec_cmd.o: ALL_CFLAGS += -DGIT_EXEC_PATH=\"$(gitexecdir)\"
+exec_cmd.o: exec_cmd.c
+       $(CC) -o $*.o -c $(ALL_CFLAGS) '-DGIT_EXEC_PATH="$(gitexecdir_SQ)"' $<
 
-git-%$X: %.o $(LIB_FILE)
+http.o: http.c
+       $(CC) -o $*.o -c $(ALL_CFLAGS) -DGIT_USER_AGENT='"git/$(GIT_VERSION)"' $<
+
+git-%$X: %.o $(GITLIBS)
        $(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(LIBS)
 
-git-mailinfo$X : SIMPLE_LIB += $(LIB_4_ICONV)
 $(SIMPLE_PROGRAMS) : $(LIB_FILE)
 $(SIMPLE_PROGRAMS) : git-%$X : %.o
        $(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \
                $(LIB_FILE) $(SIMPLE_LIB)
 
-git-http-fetch$X: fetch.o http.o
-git-http-push$X: http.o
+git-mailinfo$X: mailinfo.o $(LIB_FILE)
+       $(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \
+               $(LIB_FILE) $(SIMPLE_LIB) $(LIB_4_ICONV)
+
 git-local-fetch$X: fetch.o
 git-ssh-fetch$X: rsh.o fetch.o
 git-ssh-upload$X: rsh.o
 git-ssh-pull$X: rsh.o fetch.o
 git-ssh-push$X: rsh.o
 
-git-http-fetch$X: LIBS += $(CURL_LIBCURL)
-git-http-push$X: LIBS += $(CURL_LIBCURL) $(EXPAT_LIBEXPAT)
-git-rev-list$X: LIBS += $(OPENSSL_LIBSSL)
+git-imap-send$X: imap-send.o $(LIB_FILE)
+
+git-http-fetch$X: fetch.o http.o http-fetch.o $(LIB_FILE)
+       $(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \
+               $(LIBS) $(CURL_LIBCURL)
+
+git-http-push$X: revision.o http.o http-push.o $(LIB_FILE)
+       $(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \
+               $(LIBS) $(CURL_LIBCURL) $(EXPAT_LIBEXPAT)
+
+git-rev-list$X: rev-list.o $(LIB_FILE)
+       $(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \
+               $(LIBS) $(OPENSSL_LIBSSL)
 
 init-db.o: init-db.c
        $(CC) -c $(ALL_CFLAGS) \
-               -DDEFAULT_GIT_TEMPLATE_DIR=$(call shellquote,"$(template_dir)") $*.c
+               -DDEFAULT_GIT_TEMPLATE_DIR='"$(template_dir_SQ)"' $*.c
 
 $(LIB_OBJS): $(LIB_H)
-$(patsubst git-%$X,%.o,$(PROGRAMS)): $(LIB_H)
+$(patsubst git-%$X,%.o,$(PROGRAMS)): $(GITLIBS)
 $(DIFF_OBJS): diffcore.h
 
 $(LIB_FILE): $(LIB_OBJS)
        $(AR) rcs $@ $(LIB_OBJS)
 
+XDIFF_OBJS=xdiff/xdiffi.o xdiff/xprepare.o xdiff/xutils.o xdiff/xemit.o
+
+$(XDIFF_LIB): $(XDIFF_OBJS)
+       $(AR) rcs $@ $(XDIFF_OBJS)
+
+
 doc:
        $(MAKE) -C Documentation all
 
+TAGS:
+       rm -f TAGS
+       find . -name '*.[hcS]' -print | xargs etags -a
+
+tags:
+       rm -f tags
+       find . -name '*.[hcS]' -print | xargs ctags -a
 
 ### Testing rules
 
+# GNU make supports exporting all variables by "export" without parameters.
+# However, the environment gets quite big, and some programs have problems
+# with that.
+
+export NO_PYTHON
+
 test: all
        $(MAKE) -C t/ all
 
@@ -506,7 +588,7 @@ test-date$X: test-date.c date.o ctype.o
        $(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) test-date.c date.o ctype.o
 
 test-delta$X: test-delta.c diff-delta.o patch-delta.o
-       $(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $^
+       $(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $^ -lz
 
 check:
        for i in *.c; do sparse $(ALL_CFLAGS) $(SPARSE_FLAGS) $$i || exit; done
@@ -516,13 +598,13 @@ check:
 ### Installation rules
 
 install: all
-       $(INSTALL) -d -m755 $(call shellquote,$(DESTDIR)$(bindir))
-       $(INSTALL) -d -m755 $(call shellquote,$(DESTDIR)$(gitexecdir))
-       $(INSTALL) $(ALL_PROGRAMS) $(call shellquote,$(DESTDIR)$(gitexecdir))
-       $(INSTALL) git$X gitk $(call shellquote,$(DESTDIR)$(bindir))
+       $(INSTALL) -d -m755 '$(DESTDIR_SQ)$(bindir_SQ)'
+       $(INSTALL) -d -m755 '$(DESTDIR_SQ)$(gitexecdir_SQ)'
+       $(INSTALL) $(ALL_PROGRAMS) '$(DESTDIR_SQ)$(gitexecdir_SQ)'
+       $(INSTALL) git$X gitk '$(DESTDIR_SQ)$(bindir_SQ)'
        $(MAKE) -C templates install
-       $(INSTALL) -d -m755 $(call shellquote,$(DESTDIR)$(GIT_PYTHON_DIR))
-       $(INSTALL) $(PYMODULES) $(call shellquote,$(DESTDIR)$(GIT_PYTHON_DIR))
+       $(INSTALL) -d -m755 '$(DESTDIR_SQ)$(GIT_PYTHON_DIR_SQ)'
+       $(INSTALL) $(PYMODULES) '$(DESTDIR_SQ)$(GIT_PYTHON_DIR_SQ)'
 
 install-doc:
        $(MAKE) -C Documentation install
@@ -552,9 +634,10 @@ rpm: dist
 ### Cleaning rules
 
 clean:
-       rm -f *.o mozilla-sha1/*.o arm/*.o ppc/*.o compat/*.o $(LIB_FILE)
+       rm -f *.o mozilla-sha1/*.o arm/*.o ppc/*.o compat/*.o xdiff/*.o \
+               $(LIB_FILE) $(XDIFF_LIB)
        rm -f $(ALL_PROGRAMS) git$X
-       rm -f *.spec *.pyc *.pyo */*.pyc */*.pyo
+       rm -f *.spec *.pyc *.pyo */*.pyc */*.pyo common-cmds.h TAGS tags
        rm -rf $(GIT_TARNAME)
        rm -f $(GIT_TARNAME).tar.gz git-core_$(GIT_VERSION)-*.tar.gz
        $(MAKE) -C Documentation/ clean
@@ -563,5 +646,5 @@ clean:
        rm -f GIT-VERSION-FILE
 
 .PHONY: all install clean strip
-.PHONY: .FORCE-GIT-VERSION-FILE
+.PHONY: .FORCE-GIT-VERSION-FILE TAGS tags
 
diff --git a/apply.c b/apply.c
index 453482a..33b4271 100644 (file)
--- a/apply.c
+++ b/apply.c
@@ -9,6 +9,7 @@
 #include <fnmatch.h>
 #include "cache.h"
 #include "quote.h"
+#include "blob.h"
 
 //  --check turns on checking that the working tree matches the
 //    files that are being modified, but doesn't apply the patch
@@ -32,7 +33,7 @@ static int no_add = 0;
 static int show_index_info = 0;
 static int line_termination = '\n';
 static const char apply_usage[] =
-"git-apply [--stat] [--numstat] [--summary] [--check] [--index] [--apply] [--no-add] [--index-info] [--allow-binary-replacement] [-z] [-pNUM] <patch>...";
+"git-apply [--stat] [--numstat] [--summary] [--check] [--index] [--apply] [--no-add] [--index-info] [--allow-binary-replacement] [-z] [-pNUM] [--whitespace=<nowarn|warn|error|error-all|strip>] <patch>...";
 
 static enum whitespace_eol {
        nowarn_whitespace,
@@ -651,7 +652,7 @@ static int parse_git_header(char *line, int len, unsigned int size, struct patch
                len = linelen(line, size);
                if (!len || line[len-1] != '\n')
                        break;
-               for (i = 0; i < sizeof(optable) / sizeof(optable[0]); i++) {
+               for (i = 0; i < ARRAY_SIZE(optable); i++) {
                        const struct opentry *p = optable + i;
                        int oplen = strlen(p->str);
                        if (len < oplen || memcmp(p->str, line, oplen))
@@ -693,7 +694,7 @@ static int parse_range(const char *line, int len, int offset, const char *expect
        line += digits;
        len -= digits;
 
-       *p2 = *p1;
+       *p2 = 1;
        if (*line == ',') {
                digits = parse_num(line+1, p2);
                if (!digits)
@@ -834,7 +835,7 @@ static int parse_fragment(char *line, unsigned long size, struct patch *patch, s
                        patch->new_name = NULL;
        }
 
-       if (patch->is_new != !oldlines)
+       if (patch->is_new && oldlines)
                return error("new file depends on old contents");
        if (patch->is_delete != !newlines) {
                if (newlines)
@@ -901,6 +902,8 @@ static int parse_fragment(char *line, unsigned long size, struct patch *patch, s
                        break;
                }
        }
+       if (oldlines || newlines)
+               return -1;
        /* If a fragment ends with an incomplete line, we failed to include
         * it in the above loop because we hit oldlines == newlines == 0
         * before seeing it.
@@ -922,8 +925,7 @@ static int parse_single_patch(char *line, unsigned long size, struct patch *patc
                struct fragment *fragment;
                int len;
 
-               fragment = xmalloc(sizeof(*fragment));
-               memset(fragment, 0, sizeof(*fragment));
+               fragment = xcalloc(1, sizeof(*fragment));
                len = parse_fragment(line, size, patch, fragment);
                if (len <= 0)
                        die("corrupt patch at line %d", linenr);
@@ -1232,6 +1234,14 @@ static int apply_one_fragment(struct buffer_desc *desc, struct fragment *frag)
                size -= len;
        }
 
+#ifdef NO_ACCURATE_DIFF
+       if (oldsize > 0 && old[oldsize - 1] == '\n' &&
+                       newsize > 0 && new[newsize - 1] == '\n') {
+               oldsize--;
+               newsize--;
+       }
+#endif
+                       
        offset = find_offset(buf, desc->size, old, oldsize, frag->newpos);
        if (offset >= 0) {
                int diff = newsize - oldsize;
@@ -1286,7 +1296,7 @@ static int apply_fragments(struct buffer_desc *desc, struct patch *patch)
                         * applies to.
                         */
                        write_sha1_file_prepare(desc->buffer, desc->size,
-                                               "blob", sha1, hdr, &hdrlen);
+                                               blob_type, sha1, hdr, &hdrlen);
                        if (strcmp(sha1_to_hex(sha1), patch->old_sha1_prefix))
                                return error("the patch applies to '%s' (%s), "
                                             "which does not match the "
@@ -1394,12 +1404,13 @@ static int check_patch(struct patch *patch)
                                costate.not_new = 0;
                                costate.refresh_cache = 1;
                                if (checkout_entry(active_cache[pos],
-                                                  &costate) ||
+                                                  &costate,
+                                                  NULL) ||
                                    lstat(old_name, &st))
                                        return -1;
                        }
 
-                       changed = ce_match_stat(active_cache[pos], &st);
+                       changed = ce_match_stat(active_cache[pos], &st, 1);
                        if (changed)
                                return error("%s: does not match index",
                                             old_name);
@@ -1640,15 +1651,14 @@ static void add_index_file(const char *path, unsigned mode, void *buf, unsigned
        if (!write_index)
                return;
 
-       ce = xmalloc(ce_size);
-       memset(ce, 0, ce_size);
+       ce = xcalloc(1, ce_size);
        memcpy(ce->name, path, namelen);
        ce->ce_mode = create_ce_mode(mode);
        ce->ce_flags = htons(namelen);
        if (lstat(path, &st) < 0)
                die("unable to stat newly created file %s", path);
        fill_stat_cache_info(ce, &st);
-       if (write_sha1_file(buf, size, "blob", ce->sha1) < 0)
+       if (write_sha1_file(buf, size, blob_type, ce->sha1) < 0)
                die("unable to create backing store for newly created file %s", path);
        if (add_cache_entry(ce, ADD_CACHE_OK_TO_ADD) < 0)
                die("unable to add cache entry for %s", path);
@@ -1797,8 +1807,7 @@ static int apply_patch(int fd, const char *filename)
                struct patch *patch;
                int nr;
 
-               patch = xmalloc(sizeof(*patch));
-               memset(patch, 0, sizeof(*patch));
+               patch = xcalloc(1, sizeof(*patch));
                nr = parse_chunk(buffer + offset, size, patch);
                if (nr < 0)
                        break;
diff --git a/blame.c b/blame.c
new file mode 100644 (file)
index 0000000..9bb34e6
--- /dev/null
+++ b/blame.c
@@ -0,0 +1,893 @@
+/*
+ * Copyright (C) 2006, Fredrik Kuivinen <freku045@student.liu.se>
+ */
+
+#include <assert.h>
+#include <time.h>
+#include <sys/time.h>
+#include <math.h>
+
+#include "cache.h"
+#include "refs.h"
+#include "tag.h"
+#include "commit.h"
+#include "tree.h"
+#include "blob.h"
+#include "diff.h"
+#include "diffcore.h"
+#include "revision.h"
+
+#define DEBUG 0
+
+static const char blame_usage[] = "[-c] [-l] [--] file [commit]\n"
+       "  -c, --compability Use the same output mode as git-annotate (Default: off)\n"
+       "  -l, --long        Show long commit SHA1 (Default: off)\n"
+       "  -h, --help        This message";
+
+static struct commit **blame_lines;
+static int num_blame_lines;
+static char* blame_contents;
+static int blame_len;
+
+struct util_info {
+       int *line_map;
+       unsigned char sha1[20]; /* blob sha, not commit! */
+       char *buf;
+       unsigned long size;
+       int num_lines;
+       const char* pathname;
+
+       void* topo_data;
+};
+
+struct chunk {
+       int off1, len1; // ---
+       int off2, len2; // +++
+};
+
+struct patch {
+       struct chunk *chunks;
+       int num;
+};
+
+static void get_blob(struct commit *commit);
+
+/* Only used for statistics */
+static int num_get_patch = 0;
+static int num_commits = 0;
+static int patch_time = 0;
+
+#define TEMPFILE_PATH_LEN 60
+static struct patch *get_patch(struct commit *commit, struct commit *other)
+{
+       struct patch *ret;
+       struct util_info *info_c = (struct util_info *)commit->object.util;
+       struct util_info *info_o = (struct util_info *)other->object.util;
+       char tmp_path1[TEMPFILE_PATH_LEN], tmp_path2[TEMPFILE_PATH_LEN];
+       char diff_cmd[TEMPFILE_PATH_LEN*2 + 20];
+       struct timeval tv_start, tv_end;
+       int fd;
+       FILE *fin;
+       char buf[1024];
+
+       ret = xmalloc(sizeof(struct patch));
+       ret->chunks = NULL;
+       ret->num = 0;
+
+       get_blob(commit);
+       get_blob(other);
+
+       gettimeofday(&tv_start, NULL);
+
+       fd = git_mkstemp(tmp_path1, TEMPFILE_PATH_LEN, "git-blame-XXXXXX");
+       if (fd < 0)
+               die("unable to create temp-file: %s", strerror(errno));
+
+       if (xwrite(fd, info_c->buf, info_c->size) != info_c->size)
+               die("write failed: %s", strerror(errno));
+       close(fd);
+
+       fd = git_mkstemp(tmp_path2, TEMPFILE_PATH_LEN, "git-blame-XXXXXX");
+       if (fd < 0)
+               die("unable to create temp-file: %s", strerror(errno));
+
+       if (xwrite(fd, info_o->buf, info_o->size) != info_o->size)
+               die("write failed: %s", strerror(errno));
+       close(fd);
+
+       sprintf(diff_cmd, "diff -U 0 %s %s", tmp_path1, tmp_path2);
+       fin = popen(diff_cmd, "r");
+       if (!fin)
+               die("popen failed: %s", strerror(errno));
+
+       while (fgets(buf, sizeof(buf), fin)) {
+               struct chunk *chunk;
+               char *start, *sp;
+
+               if (buf[0] != '@' || buf[1] != '@')
+                       continue;
+
+               if (DEBUG)
+                       printf("chunk line: %s", buf);
+               ret->num++;
+               ret->chunks = xrealloc(ret->chunks,
+                                      sizeof(struct chunk) * ret->num);
+               chunk = &ret->chunks[ret->num - 1];
+
+               assert(!strncmp(buf, "@@ -", 4));
+
+               start = buf + 4;
+               sp = index(start, ' ');
+               *sp = '\0';
+               if (index(start, ',')) {
+                       int ret =
+                           sscanf(start, "%d,%d", &chunk->off1, &chunk->len1);
+                       assert(ret == 2);
+               } else {
+                       int ret = sscanf(start, "%d", &chunk->off1);
+                       assert(ret == 1);
+                       chunk->len1 = 1;
+               }
+               *sp = ' ';
+
+               start = sp + 1;
+               sp = index(start, ' ');
+               *sp = '\0';
+               if (index(start, ',')) {
+                       int ret =
+                           sscanf(start, "%d,%d", &chunk->off2, &chunk->len2);
+                       assert(ret == 2);
+               } else {
+                       int ret = sscanf(start, "%d", &chunk->off2);
+                       assert(ret == 1);
+                       chunk->len2 = 1;
+               }
+               *sp = ' ';
+
+               if (chunk->len1 == 0)
+                       chunk->off1++;
+               if (chunk->len2 == 0)
+                       chunk->off2++;
+
+               if (chunk->off1 > 0)
+                       chunk->off1--;
+               if (chunk->off2 > 0)
+                       chunk->off2--;
+
+               assert(chunk->off1 >= 0);
+               assert(chunk->off2 >= 0);
+       }
+       pclose(fin);
+       unlink(tmp_path1);
+       unlink(tmp_path2);
+
+       gettimeofday(&tv_end, NULL);
+       patch_time += 1000000 * (tv_end.tv_sec - tv_start.tv_sec) +
+               tv_end.tv_usec - tv_start.tv_usec;
+
+       num_get_patch++;
+       return ret;
+}
+
+static void free_patch(struct patch *p)
+{
+       free(p->chunks);
+       free(p);
+}
+
+static int get_blob_sha1_internal(unsigned char *sha1, const char *base,
+                                 int baselen, const char *pathname,
+                                 unsigned mode, int stage);
+
+static unsigned char blob_sha1[20];
+static const char* blame_file;
+static int get_blob_sha1(struct tree *t, const char *pathname,
+                        unsigned char *sha1)
+{
+       int i;
+       const char *pathspec[2];
+       blame_file = pathname;
+       pathspec[0] = pathname;
+       pathspec[1] = NULL;
+       memset(blob_sha1, 0, sizeof(blob_sha1));
+       read_tree_recursive(t, "", 0, 0, pathspec, get_blob_sha1_internal);
+
+       for (i = 0; i < 20; i++) {
+               if (blob_sha1[i] != 0)
+                       break;
+       }
+
+       if (i == 20)
+               return -1;
+
+       memcpy(sha1, blob_sha1, 20);
+       return 0;
+}
+
+static int get_blob_sha1_internal(unsigned char *sha1, const char *base,
+                                 int baselen, const char *pathname,
+                                 unsigned mode, int stage)
+{
+       if (S_ISDIR(mode))
+               return READ_TREE_RECURSIVE;
+
+       if (strncmp(blame_file, base, baselen) ||
+           strcmp(blame_file + baselen, pathname))
+               return -1;
+
+       memcpy(blob_sha1, sha1, 20);
+       return -1;
+}
+
+static void get_blob(struct commit *commit)
+{
+       struct util_info *info = commit->object.util;
+       char type[20];
+
+       if (info->buf)
+               return;
+
+       info->buf = read_sha1_file(info->sha1, type, &info->size);
+
+       assert(!strcmp(type, blob_type));
+}
+
+/* For debugging only */
+static void print_patch(struct patch *p)
+{
+       int i;
+       printf("Num chunks: %d\n", p->num);
+       for (i = 0; i < p->num; i++) {
+               printf("%d,%d %d,%d\n", p->chunks[i].off1, p->chunks[i].len1,
+                      p->chunks[i].off2, p->chunks[i].len2);
+       }
+}
+
+#if DEBUG
+/* For debugging only */
+static void print_map(struct commit *cmit, struct commit *other)
+{
+       struct util_info *util = cmit->object.util;
+       struct util_info *util2 = other->object.util;
+
+       int i;
+       int max =
+           util->num_lines >
+           util2->num_lines ? util->num_lines : util2->num_lines;
+       int num;
+
+       for (i = 0; i < max; i++) {
+               printf("i: %d ", i);
+               num = -1;
+
+               if (i < util->num_lines) {
+                       num = util->line_map[i];
+                       printf("%d\t", num);
+               } else
+                       printf("\t");
+
+               if (i < util2->num_lines) {
+                       int num2 = util2->line_map[i];
+                       printf("%d\t", num2);
+                       if (num != -1 && num2 != num)
+                               printf("---");
+               } else
+                       printf("\t");
+
+               printf("\n");
+       }
+}
+#endif
+
+// p is a patch from commit to other.
+static void fill_line_map(struct commit *commit, struct commit *other,
+                         struct patch *p)
+{
+       struct util_info *util = commit->object.util;
+       struct util_info *util2 = other->object.util;
+       int *map = util->line_map;
+       int *map2 = util2->line_map;
+       int cur_chunk = 0;
+       int i1, i2;
+
+       if (p->num && DEBUG)
+               print_patch(p);
+
+       if (DEBUG)
+               printf("num lines 1: %d num lines 2: %d\n", util->num_lines,
+                      util2->num_lines);
+
+       for (i1 = 0, i2 = 0; i1 < util->num_lines; i1++, i2++) {
+               struct chunk *chunk = NULL;
+               if (cur_chunk < p->num)
+                       chunk = &p->chunks[cur_chunk];
+
+               if (chunk && chunk->off1 == i1) {
+                       if (DEBUG && i2 != chunk->off2)
+                               printf("i2: %d off2: %d\n", i2, chunk->off2);
+
+                       assert(i2 == chunk->off2);
+
+                       i1--;
+                       i2--;
+                       if (chunk->len1 > 0)
+                               i1 += chunk->len1;
+
+                       if (chunk->len2 > 0)
+                               i2 += chunk->len2;
+
+                       cur_chunk++;
+               } else {
+                       if (i2 >= util2->num_lines)
+                               break;
+
+                       if (map[i1] != map2[i2] && map[i1] != -1) {
+                               if (DEBUG)
+                                       printf("map: i1: %d %d %p i2: %d %d %p\n",
+                                              i1, map[i1],
+                                              i1 != -1 ? blame_lines[map[i1]] : NULL,
+                                              i2, map2[i2],
+                                              i2 != -1 ? blame_lines[map2[i2]] : NULL);
+                               if (map2[i2] != -1 &&
+                                   blame_lines[map[i1]] &&
+                                   !blame_lines[map2[i2]])
+                                       map[i1] = map2[i2];
+                       }
+
+                       if (map[i1] == -1 && map2[i2] != -1)
+                               map[i1] = map2[i2];
+               }
+
+               if (DEBUG > 1)
+                       printf("l1: %d l2: %d i1: %d i2: %d\n",
+                              map[i1], map2[i2], i1, i2);
+       }
+}
+
+static int map_line(struct commit *commit, int line)
+{
+       struct util_info *info = commit->object.util;
+       assert(line >= 0 && line < info->num_lines);
+       return info->line_map[line];
+}
+
+static struct util_info* get_util(struct commit *commit)
+{
+       struct util_info *util = commit->object.util;
+
+       if (util)
+               return util;
+
+       util = xmalloc(sizeof(struct util_info));
+       util->buf = NULL;
+       util->size = 0;
+       util->line_map = NULL;
+       util->num_lines = -1;
+       util->pathname = NULL;
+       commit->object.util = util;
+       return util;
+}
+
+static int fill_util_info(struct commit *commit)
+{
+       struct util_info *util = commit->object.util;
+
+       assert(util);
+       assert(util->pathname);
+
+       if (get_blob_sha1(commit->tree, util->pathname, util->sha1))
+               return 1;
+       else
+               return 0;
+}
+
+static void alloc_line_map(struct commit *commit)
+{
+       struct util_info *util = commit->object.util;
+       int i;
+
+       if (util->line_map)
+               return;
+
+       get_blob(commit);
+
+       util->num_lines = 0;
+       for (i = 0; i < util->size; i++) {
+               if (util->buf[i] == '\n')
+                       util->num_lines++;
+       }
+       if(util->buf[util->size - 1] != '\n')
+               util->num_lines++;
+
+       util->line_map = xmalloc(sizeof(int) * util->num_lines);
+
+       for (i = 0; i < util->num_lines; i++)
+               util->line_map[i] = -1;
+}
+
+static void init_first_commit(struct commit* commit, const char* filename)
+{
+       struct util_info* util = commit->object.util;
+       int i;
+
+       util->pathname = filename;
+       if (fill_util_info(commit))
+               die("fill_util_info failed");
+
+       alloc_line_map(commit);
+
+       util = commit->object.util;
+
+       for (i = 0; i < util->num_lines; i++)
+               util->line_map[i] = i;
+}
+
+
+static void process_commits(struct rev_info *rev, const char *path,
+                           struct commit** initial)
+{
+       int i;
+       struct util_info* util;
+       int lines_left;
+       int *blame_p;
+       int *new_lines;
+       int new_lines_len;
+
+       struct commit* commit = get_revision(rev);
+       assert(commit);
+       init_first_commit(commit, path);
+
+       util = commit->object.util;
+       num_blame_lines = util->num_lines;
+       blame_lines = xmalloc(sizeof(struct commit *) * num_blame_lines);
+       blame_contents = util->buf;
+       blame_len = util->size;
+
+       for (i = 0; i < num_blame_lines; i++)
+               blame_lines[i] = NULL;
+
+       lines_left = num_blame_lines;
+       blame_p = xmalloc(sizeof(int) * num_blame_lines);
+       new_lines = xmalloc(sizeof(int) * num_blame_lines);
+       do {
+               struct commit_list *parents;
+               int num_parents;
+               struct util_info *util;
+
+               if (DEBUG)
+                       printf("\nProcessing commit: %d %s\n", num_commits,
+                              sha1_to_hex(commit->object.sha1));
+
+               if (lines_left == 0)
+                       return;
+
+               num_commits++;
+               memset(blame_p, 0, sizeof(int) * num_blame_lines);
+               new_lines_len = 0;
+               num_parents = 0;
+               for (parents = commit->parents;
+                    parents != NULL; parents = parents->next)
+                       num_parents++;
+
+               if(num_parents == 0)
+                       *initial = commit;
+
+               if (fill_util_info(commit))
+                       continue;
+
+               alloc_line_map(commit);
+               util = commit->object.util;
+
+               for (parents = commit->parents;
+                    parents != NULL; parents = parents->next) {
+                       struct commit *parent = parents->item;
+                       struct patch *patch;
+
+                       if (parse_commit(parent) < 0)
+                               die("parse_commit error");
+
+                       if (DEBUG)
+                               printf("parent: %s\n",
+                                      sha1_to_hex(parent->object.sha1));
+
+                       if (fill_util_info(parent)) {
+                               num_parents--;
+                               continue;
+                       }
+
+                       patch = get_patch(parent, commit);
+                        alloc_line_map(parent);
+                        fill_line_map(parent, commit, patch);
+
+                        for (i = 0; i < patch->num; i++) {
+                            int l;
+                            for (l = 0; l < patch->chunks[i].len2; l++) {
+                                int mapped_line =
+                                    map_line(commit, patch->chunks[i].off2 + l);
+                                if (mapped_line != -1) {
+                                    blame_p[mapped_line]++;
+                                    if (blame_p[mapped_line] == num_parents)
+                                        new_lines[new_lines_len++] = mapped_line;
+                                }
+                            }
+                       }
+                        free_patch(patch);
+               }
+
+               if (DEBUG)
+                       printf("parents: %d\n", num_parents);
+
+               for (i = 0; i < new_lines_len; i++) {
+                       int mapped_line = new_lines[i];
+                       if (blame_lines[mapped_line] == NULL) {
+                               blame_lines[mapped_line] = commit;
+                               lines_left--;
+                               if (DEBUG)
+                                       printf("blame: mapped: %d i: %d\n",
+                                              mapped_line, i);
+                       }
+               }
+       } while ((commit = get_revision(rev)) != NULL);
+}
+
+
+static int compare_tree_path(struct rev_info* revs,
+                            struct commit* c1, struct commit* c2)
+{
+       const char* paths[2];
+       struct util_info* util = c2->object.util;
+       paths[0] = util->pathname;
+       paths[1] = NULL;
+
+       diff_tree_setup_paths(get_pathspec(revs->prefix, paths));
+       return rev_compare_tree(c1->tree, c2->tree);
+}
+
+
+static int same_tree_as_empty_path(struct rev_info *revs, struct tree* t1,
+                                  const char* path)
+{
+       const char* paths[2];
+       paths[0] = path;
+       paths[1] = NULL;
+
+       diff_tree_setup_paths(get_pathspec(revs->prefix, paths));
+       return rev_same_tree_as_empty(t1);
+}
+
+static const char* find_rename(struct commit* commit, struct commit* parent)
+{
+       struct util_info* cutil = commit->object.util;
+       struct diff_options diff_opts;
+       const char *paths[1];
+       int i;
+
+       if (DEBUG) {
+               printf("find_rename commit: %s ",
+                      sha1_to_hex(commit->object.sha1));
+               puts(sha1_to_hex(parent->object.sha1));
+       }
+
+       diff_setup(&diff_opts);
+       diff_opts.recursive = 1;
+       diff_opts.detect_rename = DIFF_DETECT_RENAME;
+       paths[0] = NULL;
+       diff_tree_setup_paths(paths);
+       if (diff_setup_done(&diff_opts) < 0)
+               die("diff_setup_done failed");
+
+       diff_tree_sha1(commit->tree->object.sha1, parent->tree->object.sha1,
+                      "", &diff_opts);
+       diffcore_std(&diff_opts);
+
+       for (i = 0; i < diff_queued_diff.nr; i++) {
+               struct diff_filepair *p = diff_queued_diff.queue[i];
+
+               if (p->status == 'R' && !strcmp(p->one->path, cutil->pathname)) {
+                       if (DEBUG)
+                               printf("rename %s -> %s\n", p->one->path, p->two->path);
+                       return p->two->path;
+               }
+       }
+
+       return 0;
+}
+
+static void simplify_commit(struct rev_info *revs, struct commit *commit)
+{
+       struct commit_list **pp, *parent;
+
+       if (!commit->tree)
+               return;
+
+       if (!commit->parents) {
+               struct util_info* util = commit->object.util;
+               if (!same_tree_as_empty_path(revs, commit->tree,
+                                            util->pathname))
+                       commit->object.flags |= TREECHANGE;
+               return;
+       }
+
+       pp = &commit->parents;
+       while ((parent = *pp) != NULL) {
+               struct commit *p = parent->item;
+
+               if (p->object.flags & UNINTERESTING) {
+                       pp = &parent->next;
+                       continue;
+               }
+
+               parse_commit(p);
+               switch (compare_tree_path(revs, p, commit)) {
+               case REV_TREE_SAME:
+                       parent->next = NULL;
+                       commit->parents = parent;
+                       get_util(p)->pathname = get_util(commit)->pathname;
+                       return;
+
+               case REV_TREE_NEW:
+               {
+
+                       struct util_info* util = commit->object.util;
+                       if (revs->remove_empty_trees &&
+                           same_tree_as_empty_path(revs, p->tree,
+                                                   util->pathname)) {
+                               const char* new_name = find_rename(commit, p);
+                               if (new_name) {
+                                       struct util_info* putil = get_util(p);
+                                       if (!putil->pathname)
+                                               putil->pathname = strdup(new_name);
+                               } else {
+                                       *pp = parent->next;
+                                       continue;
+                               }
+                       }
+               }
+
+               /* fallthrough */
+               case REV_TREE_DIFFERENT:
+                       pp = &parent->next;
+                       if (!get_util(p)->pathname)
+                               get_util(p)->pathname =
+                                       get_util(commit)->pathname;
+                       continue;
+               }
+               die("bad tree compare for commit %s",
+                   sha1_to_hex(commit->object.sha1));
+       }
+       commit->object.flags |= TREECHANGE;
+}
+
+
+struct commit_info
+{
+       char* author;
+       char* author_mail;
+       unsigned long author_time;
+       char* author_tz;
+};
+
+static void get_commit_info(struct commit* commit, struct commit_info* ret)
+{
+       int len;
+       char* tmp;
+       static char author_buf[1024];
+
+       tmp = strstr(commit->buffer, "\nauthor ") + 8;
+       len = index(tmp, '\n') - tmp;
+       ret->author = author_buf;
+       memcpy(ret->author, tmp, len);
+
+       tmp = ret->author;
+       tmp += len;
+       *tmp = 0;
+       while(*tmp != ' ')
+               tmp--;
+       ret->author_tz = tmp+1;
+
+       *tmp = 0;
+       while(*tmp != ' ')
+               tmp--;
+       ret->author_time = strtoul(tmp, NULL, 10);
+
+       *tmp = 0;
+       while(*tmp != ' ')
+               tmp--;
+       ret->author_mail = tmp + 1;
+
+       *tmp = 0;
+}
+
+static const char* format_time(unsigned long time, const char* tz_str)
+{
+       static char time_buf[128];
+       time_t t = time;
+       int minutes, tz;
+       struct tm *tm;
+
+       tz = atoi(tz_str);
+       minutes = tz < 0 ? -tz : tz;
+       minutes = (minutes / 100)*60 + (minutes % 100);
+       minutes = tz < 0 ? -minutes : minutes;
+       t = time + minutes * 60;
+       tm = gmtime(&t);
+
+       strftime(time_buf, sizeof(time_buf), "%Y-%m-%d %H:%M:%S ", tm);
+       strcat(time_buf, tz_str);
+       return time_buf;
+}
+
+static void topo_setter(struct commit* c, void* data)
+{
+       struct util_info* util = c->object.util;
+       util->topo_data = data;
+}
+
+static void* topo_getter(struct commit* c)
+{
+       struct util_info* util = c->object.util;
+       return util->topo_data;
+}
+
+int main(int argc, const char **argv)
+{
+       int i;
+       struct commit *initial = NULL;
+       unsigned char sha1[20];
+
+       const char *filename = NULL, *commit = NULL;
+       char filename_buf[256];
+       int sha1_len = 8;
+       int compability = 0;
+       int options = 1;
+       struct commit* start_commit;
+
+       const char* args[10];
+       struct rev_info rev;
+
+       struct commit_info ci;
+       const char *buf;
+       int max_digits;
+       int longest_file, longest_author;
+       int found_rename;
+
+       const char* prefix = setup_git_directory();
+       git_config(git_default_config);
+
+       for(i = 1; i < argc; i++) {
+               if(options) {
+                       if(!strcmp(argv[i], "-h") ||
+                          !strcmp(argv[i], "--help"))
+                               usage(blame_usage);
+                       else if(!strcmp(argv[i], "-l") ||
+                               !strcmp(argv[i], "--long")) {
+                               sha1_len = 40;
+                               continue;
+                       } else if(!strcmp(argv[i], "-c") ||
+                                 !strcmp(argv[i], "--compability")) {
+                               compability = 1;
+                               continue;
+                       } else if(!strcmp(argv[i], "--")) {
+                               options = 0;
+                               continue;
+                       } else if(argv[i][0] == '-')
+                               usage(blame_usage);
+                       else
+                               options = 0;
+               }
+
+               if(!options) {
+                       if(!filename)
+                               filename = argv[i];
+                       else if(!commit)
+                               commit = argv[i];
+                       else
+                               usage(blame_usage);
+               }
+       }
+
+       if(!filename)
+               usage(blame_usage);
+       if(!commit)
+               commit = "HEAD";
+
+       if(prefix)
+               sprintf(filename_buf, "%s%s", prefix, filename);
+       else
+               strcpy(filename_buf, filename);
+       filename = filename_buf;
+
+       if (get_sha1(commit, sha1))
+               die("get_sha1 failed, commit '%s' not found", commit);
+       start_commit = lookup_commit_reference(sha1);
+       get_util(start_commit)->pathname = filename;
+       if (fill_util_info(start_commit)) {
+               printf("%s not found in %s\n", filename, commit);
+               return 1;
+       }
+
+
+       init_revisions(&rev);
+       rev.remove_empty_trees = 1;
+       rev.topo_order = 1;
+       rev.prune_fn = simplify_commit;
+       rev.topo_setter = topo_setter;
+       rev.topo_getter = topo_getter;
+       rev.parents = 1;
+       rev.limited = 1;
+
+       commit_list_insert(start_commit, &rev.commits);
+
+       args[0] = filename;
+       args[1] = NULL;
+       diff_tree_setup_paths(args);
+       prepare_revision_walk(&rev);
+       process_commits(&rev, filename, &initial);
+
+       buf = blame_contents;
+       for (max_digits = 1, i = 10; i <= num_blame_lines + 1; max_digits++)
+               i *= 10;
+
+       longest_file = 0;
+       longest_author = 0;
+       found_rename = 0;
+       for (i = 0; i < num_blame_lines; i++) {
+               struct commit *c = blame_lines[i];
+               struct util_info* u;
+               if (!c)
+                       c = initial;
+               u = c->object.util;
+
+               if (!found_rename && strcmp(filename, u->pathname))
+                       found_rename = 1;
+               if (longest_file < strlen(u->pathname))
+                       longest_file = strlen(u->pathname);
+               get_commit_info(c, &ci);
+               if (longest_author < strlen(ci.author))
+                       longest_author = strlen(ci.author);
+       }
+
+       for (i = 0; i < num_blame_lines; i++) {
+               struct commit *c = blame_lines[i];
+               struct util_info* u;
+
+               if (!c)
+                       c = initial;
+
+               u = c->object.util;
+               get_commit_info(c, &ci);
+               fwrite(sha1_to_hex(c->object.sha1), sha1_len, 1, stdout);
+               if(compability) {
+                       printf("\t(%10s\t%10s\t%d)", ci.author,
+                              format_time(ci.author_time, ci.author_tz), i+1);
+               } else {
+                       if (found_rename)
+                               printf(" %-*.*s", longest_file, longest_file,
+                                      u->pathname);
+                       printf(" (%-*.*s %10s %*d) ",
+                              longest_author, longest_author, ci.author,
+                              format_time(ci.author_time, ci.author_tz),
+                              max_digits, i+1);
+               }
+
+               if(i == num_blame_lines - 1) {
+                       fwrite(buf, blame_len - (buf - blame_contents),
+                              1, stdout);
+                       if(blame_contents[blame_len-1] != '\n')
+                               putc('\n', stdout);
+               } else {
+                       char* next_buf = index(buf, '\n') + 1;
+                       fwrite(buf, next_buf - buf, 1, stdout);
+                       buf = next_buf;
+               }
+       }
+
+       if (DEBUG) {
+               printf("num get patch: %d\n", num_get_patch);
+               printf("num commits: %d\n", num_commits);
+               printf("patch time: %f\n", patch_time / 1000000.0);
+               printf("initial: %s\n", sha1_to_hex(initial->object.sha1));
+       }
+
+       return 0;
+}
diff --git a/blob.c b/blob.c
index 84ec121..c1fdd86 100644 (file)
--- a/blob.c
+++ b/blob.c
@@ -8,8 +8,7 @@ struct blob *lookup_blob(const unsigned char *sha1)
 {
        struct object *obj = lookup_object(sha1);
        if (!obj) {
-               struct blob *ret = xmalloc(sizeof(struct blob));
-               memset(ret, 0, sizeof(struct blob));
+               struct blob *ret = xcalloc(1, sizeof(struct blob));
                created_object(sha1, &ret->object);
                ret->object.type = blob_type;
                return ret;
diff --git a/cache.h b/cache.h
index f686e72..69801b0 100644 (file)
--- a/cache.h
+++ b/cache.h
@@ -10,7 +10,7 @@
 #define deflateBound(c,s)  ((s) + (((s) + 7) >> 3) + (((s) + 63) >> 6) + 11)
 #endif
 
-#if defined(DT_UNKNOWN) && !NO_D_TYPE_IN_DIRENT
+#if defined(DT_UNKNOWN) && !defined(NO_D_TYPE_IN_DIRENT)
 #define DTYPE(de)      ((de)->d_type)
 #else
 #undef DT_UNKNOWN
@@ -91,6 +91,7 @@ struct cache_entry {
 #define CE_NAMEMASK  (0x0fff)
 #define CE_STAGEMASK (0x3000)
 #define CE_UPDATE    (0x4000)
+#define CE_VALID     (0x8000)
 #define CE_STAGESHIFT 12
 
 #define create_ce_flags(len, stage) htons((len) | ((stage) << CE_STAGESHIFT))
@@ -105,6 +106,9 @@ static inline unsigned int create_ce_mode(unsigned int mode)
                return htonl(S_IFLNK);
        return htonl(S_IFREG | ce_permissions(mode));
 }
+#define canon_mode(mode) \
+       (S_ISREG(mode) ? (S_IFREG | ce_permissions(mode)) : \
+       S_ISLNK(mode) ? S_IFLNK : S_IFDIR)
 
 #define cache_entry_size(len) ((offsetof(struct cache_entry,name) + (len) + 8) & ~7)
 
@@ -144,8 +148,8 @@ extern int add_cache_entry(struct cache_entry *ce, int option);
 extern int remove_cache_entry_at(int pos);
 extern int remove_file_from_cache(const char *path);
 extern int ce_same_name(struct cache_entry *a, struct cache_entry *b);
-extern int ce_match_stat(struct cache_entry *ce, struct stat *st);
-extern int ce_modified(struct cache_entry *ce, struct stat *st);
+extern int ce_match_stat(struct cache_entry *ce, struct stat *st, int);
+extern int ce_modified(struct cache_entry *ce, struct stat *st, int);
 extern int ce_path_match(const struct cache_entry *ce, const char **pathspec);
 extern int index_fd(unsigned char *sha1, int fd, struct stat *st, int write_object, const char *type);
 extern int index_pipe(unsigned char *sha1, int fd, const char *type, int write_object);
@@ -162,7 +166,9 @@ extern void rollback_index_file(struct cache_file *);
 
 /* Environment bits from configuration mechanism */
 extern int trust_executable_bit;
+extern int assume_unchanged;
 extern int only_use_symrefs;
+extern int warn_ambiguous_refs;
 extern int diff_rename_limit_default;
 extern int shared_repository;
 extern const char *apply_default_whitespace;
@@ -260,7 +266,7 @@ struct checkout {
                 refresh_cache:1;
 };
 
-extern int checkout_entry(struct cache_entry *ce, struct checkout *state);
+extern int checkout_entry(struct cache_entry *ce, struct checkout *state, char *topath);
 
 extern struct alternate_object_database {
        struct alternate_object_database *next;
@@ -324,7 +330,7 @@ extern int num_packed_objects(const struct packed_git *p);
 extern int nth_packed_object_sha1(const struct packed_git *, int, unsigned char*);
 extern int find_pack_entry_one(const unsigned char *, struct pack_entry *, struct packed_git *);
 extern void *unpack_entry_gently(struct pack_entry *, char *, unsigned long *);
-extern void packed_object_info_detail(struct pack_entry *, char *, unsigned long *, unsigned long *, int *, unsigned char *);
+extern void packed_object_info_detail(struct pack_entry *, char *, unsigned long *, unsigned long *, unsigned int *, unsigned char *);
 
 /* Dumb servers support */
 extern int update_server_info(int);
@@ -352,4 +358,7 @@ extern int copy_fd(int ifd, int ofd);
 extern int receive_unpack_pack(int fd[2], const char *me, int quiet);
 extern int receive_keep_pack(int fd[2], const char *me, int quiet);
 
+/* pager.c */
+extern void setup_pager(void);
+
 #endif /* CACHE_H */
index 96d66b4..628f6ca 100644 (file)
@@ -4,6 +4,94 @@
  * Copyright (C) Linus Torvalds, 2005
  */
 #include "cache.h"
+#include "exec_cmd.h"
+#include "tag.h"
+#include "tree.h"
+
+static void flush_buffer(const char *buf, unsigned long size)
+{
+       while (size > 0) {
+               long ret = xwrite(1, buf, size);
+               if (ret < 0) {
+                       /* Ignore epipe */
+                       if (errno == EPIPE)
+                               break;
+                       die("git-cat-file: %s", strerror(errno));
+               } else if (!ret) {
+                       die("git-cat-file: disk full?");
+               }
+               size -= ret;
+               buf += ret;
+       }
+}
+
+static int pprint_tag(const unsigned char *sha1, const char *buf, unsigned long size)
+{
+       /* the parser in tag.c is useless here. */
+       const char *endp = buf + size;
+       const char *cp = buf;
+
+       while (cp < endp) {
+               char c = *cp++;
+               if (c != '\n')
+                       continue;
+               if (7 <= endp - cp && !memcmp("tagger ", cp, 7)) {
+                       const char *tagger = cp;
+
+                       /* Found the tagger line.  Copy out the contents
+                        * of the buffer so far.
+                        */
+                       flush_buffer(buf, cp - buf);
+
+                       /*
+                        * Do something intelligent, like pretty-printing
+                        * the date.
+                        */
+                       while (cp < endp) {
+                               if (*cp++ == '\n') {
+                                       /* tagger to cp is a line
+                                        * that has ident and time.
+                                        */
+                                       const char *sp = tagger;
+                                       char *ep;
+                                       unsigned long date;
+                                       long tz;
+                                       while (sp < cp && *sp != '>')
+                                               sp++;
+                                       if (sp == cp) {
+                                               /* give up */
+                                               flush_buffer(tagger,
+                                                            cp - tagger);
+                                               break;
+                                       }
+                                       while (sp < cp &&
+                                              !('0' <= *sp && *sp <= '9'))
+                                               sp++;
+                                       flush_buffer(tagger, sp - tagger);
+                                       date = strtoul(sp, &ep, 10);
+                                       tz = strtol(ep, NULL, 10);
+                                       sp = show_date(date, tz);
+                                       flush_buffer(sp, strlen(sp));
+                                       xwrite(1, "\n", 1);
+                                       break;
+                               }
+                       }
+                       break;
+               }
+               if (cp < endp && *cp == '\n')
+                       /* end of header */
+                       break;
+       }
+       /* At this point, we have copied out the header up to the end of
+        * the tagger line and cp points at one past \n.  It could be the
+        * next header line after the tagger line, or it could be another
+        * \n that marks the end of the headers.  We need to copy out the
+        * remainder as is.
+        */
+       if (cp < endp)
+               flush_buffer(cp, endp - cp);
+       return 0;
+}
 
 int main(int argc, char **argv)
 {
@@ -14,8 +102,9 @@ int main(int argc, char **argv)
        int opt;
 
        setup_git_directory();
+       git_config(git_default_config);
        if (argc != 3 || get_sha1(argv[2], sha1))
-               usage("git-cat-file [-t|-s|-e|<type>] <sha1>");
+               usage("git-cat-file [-t|-s|-e|-p|<type>] <sha1>");
 
        opt = 0;
        if ( argv[1][0] == '-' ) {
@@ -43,6 +132,23 @@ int main(int argc, char **argv)
        case 'e':
                return !has_sha1_file(sha1);
 
+       case 'p':
+               if (get_sha1(argv[2], sha1) ||
+                   sha1_object_info(sha1, type, NULL))
+                       die("Not a valid object name %s", argv[2]);
+
+               /* custom pretty-print here */
+               if (!strcmp(type, tree_type))
+                       return execl_git_cmd("ls-tree", argv[2], NULL);
+
+               buf = read_sha1_file(sha1, type, &size);
+               if (!buf)
+                       die("Cannot read object %s", argv[2]);
+               if (!strcmp(type, tag_type))
+                       return pprint_tag(sha1, buf, size);
+
+               /* otherwise just spit out the data */
+               break;
        case 0:
                buf = read_object_with_reference(sha1, argv[1], &size, NULL);
                break;
@@ -54,18 +160,6 @@ int main(int argc, char **argv)
        if (!buf)
                die("git-cat-file %s: bad file", argv[2]);
 
-       while (size > 0) {
-               long ret = xwrite(1, buf, size);
-               if (ret < 0) {
-                       /* Ignore epipe */
-                       if (errno == EPIPE)
-                               break;
-                       die("git-cat-file: %s", strerror(errno));
-               } else if (!ret) {
-                       die("git-cat-file: disk full?");
-               }
-               size -= ret;
-               buf += ret;
-       }
+       flush_buffer(buf, size);
        return 0;
 }
index 53dd8cb..dd6a2d8 100644 (file)
  *
  *     find . -name '*.h' -print0 | xargs -0 git-checkout-index -f --
  *
+ * or:
+ *
+ *     find . -name '*.h' -print0 | git-checkout-index -f -z --stdin
+ *
  * which will force all existing *.h files to be replaced with
  * their cached copies. If an empty command line implied "all",
  * then this would force-refresh everything in the cache, which
  * but get used to it in scripting!).
  */
 #include "cache.h"
+#include "strbuf.h"
+#include "quote.h"
 
+#define CHECKOUT_ALL 4
 static const char *prefix;
 static int prefix_length;
+static int line_termination = '\n';
 static int checkout_stage; /* default to checkout stage0 */
+static int to_tempfile;
+static char topath[4][MAXPATHLEN+1];
 
 static struct checkout state = {
        .base_dir = "",
@@ -47,11 +57,39 @@ static struct checkout state = {
        .refresh_cache = 0,
 };
 
+static void write_tempfile_record (const char *name)
+{
+       int i;
+
+       if (CHECKOUT_ALL == checkout_stage) {
+               for (i = 1; i < 4; i++) {
+                       if (i > 1)
+                               putchar(' ');
+                       if (topath[i][0])
+                               fputs(topath[i], stdout);
+                       else
+                               putchar('.');
+               }
+       } else
+               fputs(topath[checkout_stage], stdout);
+
+       putchar('\t');
+       write_name_quoted("", 0, name + prefix_length,
+               line_termination, stdout);
+       putchar(line_termination);
+
+       for (i = 0; i < 4; i++) {
+               topath[i][0] = 0;
+       }
+}
+
 static int checkout_file(const char *name)
 {
        int namelen = strlen(name);
        int pos = cache_name_pos(name, namelen);
        int has_same_name = 0;
+       int did_checkout = 0;
+       int errs = 0;
 
        if (pos < 0)
                pos = -pos - 1;
@@ -62,9 +100,20 @@ static int checkout_file(const char *name)
                    memcmp(ce->name, name, namelen))
                        break;
                has_same_name = 1;
-               if (checkout_stage == ce_stage(ce))
-                       return checkout_entry(ce, &state);
                pos++;
+               if (ce_stage(ce) != checkout_stage
+                   && (CHECKOUT_ALL != checkout_stage || !ce_stage(ce)))
+                       continue;
+               did_checkout = 1;
+               if (checkout_entry(ce, &state,
+                   to_tempfile ? topath[ce_stage(ce)] : NULL) < 0)
+                       errs++;
+       }
+
+       if (did_checkout) {
+               if (to_tempfile)
+                       write_tempfile_record(name);
+               return errs > 0 ? -1 : 0;
        }
 
        if (!state.quiet) {
@@ -84,18 +133,29 @@ static int checkout_file(const char *name)
 static int checkout_all(void)
 {
        int i, errs = 0;
+       struct cache_entry* last_ce = NULL;
 
        for (i = 0; i < active_nr ; i++) {
                struct cache_entry *ce = active_cache[i];
-               if (ce_stage(ce) != checkout_stage)
+               if (ce_stage(ce) != checkout_stage
+                   && (CHECKOUT_ALL != checkout_stage || !ce_stage(ce)))
                        continue;
                if (prefix && *prefix &&
                    (ce_namelen(ce) <= prefix_length ||
                     memcmp(prefix, ce->name, prefix_length)))
                        continue;
-               if (checkout_entry(ce, &state) < 0)
+               if (last_ce && to_tempfile) {
+                       if (ce_namelen(last_ce) != ce_namelen(ce)
+                           || memcmp(last_ce->name, ce->name, ce_namelen(ce)))
+                               write_tempfile_record(last_ce->name);
+               }
+               if (checkout_entry(ce, &state,
+                   to_tempfile ? topath[ce_stage(ce)] : NULL) < 0)
                        errs++;
+               last_ce = ce;
        }
+       if (last_ce && to_tempfile)
+               write_tempfile_record(last_ce->name);
        if (errs)
                /* we have already done our error reporting.
                 * exit with the same code as die().
@@ -105,7 +165,7 @@ static int checkout_all(void)
 }
 
 static const char checkout_cache_usage[] =
-"git-checkout-index [-u] [-q] [-a] [-f] [-n] [--stage=[123]] [--prefix=<string>] [--] <file>...";
+"git-checkout-index [-u] [-q] [-a] [-f] [-n] [--stage=[123]|all] [--prefix=<string>] [--temp] [--] <file>...";
 
 static struct cache_file cache_file;
 
@@ -114,8 +174,10 @@ int main(int argc, char **argv)
        int i;
        int newfd = -1;
        int all = 0;
+       int read_from_stdin = 0;
 
        prefix = setup_git_directory();
+       git_config(git_default_config);
        prefix_length = prefix ? strlen(prefix) : 0;
 
        if (read_cache() < 0) {
@@ -155,17 +217,37 @@ int main(int argc, char **argv)
                                die("cannot open index.lock file.");
                        continue;
                }
+               if (!strcmp(arg, "-z")) {
+                       line_termination = 0;
+                       continue;
+               }
+               if (!strcmp(arg, "--stdin")) {
+                       if (i != argc - 1)
+                               die("--stdin must be at the end");
+                       read_from_stdin = 1;
+                       i++; /* do not consider arg as a file name */
+                       break;
+               }
+               if (!strcmp(arg, "--temp")) {
+                       to_tempfile = 1;
+                       continue;
+               }
                if (!strncmp(arg, "--prefix=", 9)) {
                        state.base_dir = arg+9;
                        state.base_dir_len = strlen(state.base_dir);
                        continue;
                }
                if (!strncmp(arg, "--stage=", 8)) {
-                       int ch = arg[8];
-                       if ('1' <= ch && ch <= '3')
-                               checkout_stage = arg[8] - '0';
-                       else
-                               die("stage should be between 1 and 3");
+                       if (!strcmp(arg + 8, "all")) {
+                               to_tempfile = 1;
+                               checkout_stage = CHECKOUT_ALL;
+                       } else {
+                               int ch = arg[8];
+                               if ('1' <= ch && ch <= '3')
+                                       checkout_stage = arg[8] - '0';
+                               else
+                                       die("stage should be between 1 and 3 or all");
+                       }
                        continue;
                }
                if (arg[0] == '-')
@@ -173,7 +255,7 @@ int main(int argc, char **argv)
                break;
        }
 
-       if (state.base_dir_len) {
+       if (state.base_dir_len || to_tempfile) {
                /* when --prefix is specified we do not
                 * want to update cache.
                 */
@@ -190,9 +272,31 @@ int main(int argc, char **argv)
 
                if (all)
                        die("git-checkout-index: don't mix '--all' and explicit filenames");
+               if (read_from_stdin)
+                       die("git-checkout-index: don't mix '--stdin' and explicit filenames");
                checkout_file(prefix_path(prefix, prefix_length, arg));
        }
 
+       if (read_from_stdin) {
+               struct strbuf buf;
+               if (all)
+                       die("git-checkout-index: don't mix '--all' and '--stdin'");
+               strbuf_init(&buf);
+               while (1) {
+                       char *path_name;
+                       read_line(&buf, stdin, line_termination);
+                       if (buf.eof)
+                               break;
+                       if (line_termination && buf.buf[0] == '"')
+                               path_name = unquote_c_style(buf.buf, NULL);
+                       else
+                               path_name = buf.buf;
+                       checkout_file(prefix_path(prefix, prefix_length, path_name));
+                       if (path_name != buf.buf)
+                               free(path_name);
+               }
+       }
+
        if (all)
                checkout_all();
 
index a23894d..7693884 100644 (file)
@@ -1,5 +1,6 @@
 #include "cache.h"
 #include "commit.h"
+#include "blob.h"
 #include "diff.h"
 #include "diffcore.h"
 #include "quote.h"
@@ -104,7 +105,7 @@ static char *grab_blob(const unsigned char *sha1, unsigned long *size)
                return xcalloc(1, 1);
        }
        blob = read_sha1_file(sha1, type, size);
-       if (strcmp(type, "blob"))
+       if (strcmp(type, blob_type))
                die("object '%s' is not a blob!", sha1_to_hex(sha1));
        return blob;
 }
@@ -649,7 +650,7 @@ static int show_patch_diff(struct combine_diff_path *elem, int num_parent,
                        int len = st.st_size;
                        int cnt = 0;
 
-                       elem->mode = DIFF_FILE_CANON_MODE(st.st_mode);
+                       elem->mode = canon_mode(st.st_mode);
                        size = len;
                        result = xmalloc(len + 1);
                        while (cnt < len) {
index 88871b0..2d86518 100644 (file)
@@ -4,6 +4,8 @@
  * Copyright (C) Linus Torvalds, 2005
  */
 #include "cache.h"
+#include "commit.h"
+#include "tree.h"
 
 #define BLOCKING (1ul << 14)
 
@@ -93,13 +95,13 @@ int main(int argc, char **argv)
        if (argc < 2 || get_sha1_hex(argv[1], tree_sha1) < 0)
                usage(commit_tree_usage);
 
-       check_valid(tree_sha1, "tree");
+       check_valid(tree_sha1, tree_type);
        for (i = 2; i < argc; i += 2) {
                char *a, *b;
                a = argv[i]; b = argv[i+1];
                if (!b || strcmp(a, "-p") || get_sha1(b, parent_sha1[parents]))
                        usage(commit_tree_usage);
-               check_valid(parent_sha1[parents], "commit");
+               check_valid(parent_sha1[parents], commit_type);
                if (new_parent(parents))
                        parents++;
        }
@@ -125,7 +127,10 @@ int main(int argc, char **argv)
        while (fgets(comment, sizeof(comment), stdin) != NULL)
                add_buffer(&buffer, &size, "%s", comment);
 
-       write_sha1_file(buffer, size, "commit", commit_sha1);
-       printf("%s\n", sha1_to_hex(commit_sha1));
-       return 0;
+       if (!write_sha1_file(buffer, size, commit_type, commit_sha1)) {
+               printf("%s\n", sha1_to_hex(commit_sha1));
+               return 0;
+       }
+       else
+               return 1;
 }
index 512b5d7..d4976fb 100644 (file)
--- a/commit.c
+++ b/commit.c
@@ -73,8 +73,7 @@ struct commit *lookup_commit(const unsigned char *sha1)
 {
        struct object *obj = lookup_object(sha1);
        if (!obj) {
-               struct commit *ret = xmalloc(sizeof(struct commit));
-               memset(ret, 0, sizeof(struct commit));
+               struct commit *ret = xcalloc(1, sizeof(struct commit));
                created_object(sha1, &ret->object);
                ret->object.type = commit_type;
                return ret;
@@ -569,10 +568,28 @@ int count_parents(struct commit * commit)
         return count;
 }
 
+void topo_sort_default_setter(struct commit *c, void *data)
+{
+       c->object.util = data;
+}
+
+void *topo_sort_default_getter(struct commit *c)
+{
+       return c->object.util;
+}
+
 /*
  * Performs an in-place topological sort on the list supplied.
  */
-void sort_in_topological_order(struct commit_list ** list)
+void sort_in_topological_order(struct commit_list ** list, int lifo)
+{
+       sort_in_topological_order_fn(list, lifo, topo_sort_default_setter,
+                                    topo_sort_default_getter);
+}
+
+void sort_in_topological_order_fn(struct commit_list ** list, int lifo,
+                                 topo_sort_set_fn_t setter,
+                                 topo_sort_get_fn_t getter)
 {
        struct commit_list * next = *list;
        struct commit_list * work = NULL, **insert;
@@ -596,7 +613,7 @@ void sort_in_topological_order(struct commit_list ** list)
        next=*list;
        while (next) {
                next_nodes->list_item = next;
-               next->item->object.util = next_nodes;
+               setter(next->item, next_nodes);
                next_nodes++;
                next = next->next;
        }
@@ -606,8 +623,8 @@ void sort_in_topological_order(struct commit_list ** list)
                struct commit_list * parents = next->item->parents;
                while (parents) {
                        struct commit * parent=parents->item;
-                       struct sort_node * pn = (struct sort_node *)parent->object.util;
-                       
+                       struct sort_node * pn = (struct sort_node *) getter(parent);
+
                        if (pn)
                                pn->indegree++;
                        parents=parents->next;
@@ -624,32 +641,39 @@ void sort_in_topological_order(struct commit_list ** list)
        next=*list;
        insert = &work;
        while (next) {
-               struct sort_node * node = (struct sort_node *)next->item->object.util;
+               struct sort_node * node = (struct sort_node *) getter(next->item);
 
                if (node->indegree == 0) {
                        insert = &commit_list_insert(next->item, insert)->next;
                }
                next=next->next;
        }
+
        /* process the list in topological order */
+       if (!lifo)
+               sort_by_date(&work);
        while (work) {
                struct commit * work_item = pop_commit(&work);
-               struct sort_node * work_node = (struct sort_node *)work_item->object.util;
+               struct sort_node * work_node = (struct sort_node *) getter(work_item);
                struct commit_list * parents = work_item->parents;
 
                while (parents) {
                        struct commit * parent=parents->item;
-                       struct sort_node * pn = (struct sort_node *)parent->object.util;
-                       
+                       struct sort_node * pn = (struct sort_node *) getter(parent);
+
                        if (pn) {
-                               /* 
+                               /*
                                 * parents are only enqueued for emission 
                                  * when all their children have been emitted thereby
                                  * guaranteeing topological order.
                                  */
                                pn->indegree--;
-                               if (!pn->indegree) 
-                                       commit_list_insert(parent, &work);
+                               if (!pn->indegree) {
+                                       if (!lifo)
+                                               insert_by_date(parent, &work);
+                                       else
+                                               commit_list_insert(parent, &work);
+                               }
                        }
                        parents=parents->next;
                }
@@ -660,7 +684,7 @@ void sort_in_topological_order(struct commit_list ** list)
                *pptr = work_node->list_item;
                pptr = &(*pptr)->next;
                *pptr = NULL;
-               work_item->object.util = NULL;
+               setter(work_item, NULL);
        }
        free(nodes);
 }
index 986b22d..98682b2 100644 (file)
--- a/commit.h
+++ b/commit.h
@@ -65,13 +65,29 @@ int count_parents(struct commit * commit);
 /*
  * Performs an in-place topological sort of list supplied.
  *
- * Pre-conditions:
+ * Pre-conditions for sort_in_topological_order:
  *   all commits in input list and all parents of those
  *   commits must have object.util == NULL
- *        
- * Post-conditions: 
+ *
+ * Pre-conditions for sort_in_topological_order_fn:
+ *   all commits in input list and all parents of those
+ *   commits must have getter(commit) == NULL
+ *
+ * Post-conditions:
  *   invariant of resulting list is:
  *      a reachable from b => ord(b) < ord(a)
+ *   in addition, when lifo == 0, commits on parallel tracks are
+ *   sorted in the dates order.
  */
-void sort_in_topological_order(struct commit_list ** list);
+
+typedef void (*topo_sort_set_fn_t)(struct commit*, void *data);
+typedef void* (*topo_sort_get_fn_t)(struct commit*);
+
+void topo_sort_default_setter(struct commit *c, void *data);
+void *topo_sort_default_getter(struct commit *c);
+
+void sort_in_topological_order(struct commit_list ** list, int lifo);
+void sort_in_topological_order_fn(struct commit_list ** list, int lifo,
+                                 topo_sort_set_fn_t setter,
+                                 topo_sort_get_fn_t getter);
 #endif /* COMMIT_H */
index 8355224..95ec349 100644 (file)
--- a/config.c
+++ b/config.c
@@ -222,11 +222,21 @@ int git_default_config(const char *var, const char *value)
                return 0;
        }
 
+       if (!strcmp(var, "core.ignorestat")) {
+               assume_unchanged = git_config_bool(var, value);
+               return 0;
+       }
+
        if (!strcmp(var, "core.symrefsonly")) {
                only_use_symrefs = git_config_bool(var, value);
                return 0;
        }
 
+       if (!strcmp(var, "core.warnambiguousrefs")) {
+               warn_ambiguous_refs = git_config_bool(var, value);
+               return 0;
+       }
+
        if (!strcmp(var, "user.name")) {
                strncpy(git_default_name, value, sizeof(git_default_name));
                return 0;
diff --git a/contrib/README b/contrib/README
new file mode 100644 (file)
index 0000000..e1c0a01
--- /dev/null
@@ -0,0 +1,44 @@
+Contributed Software
+
+Although these pieces are available as part of the official git
+source tree, they are in somewhat different status.  The
+intention is to keep interesting tools around git here, maybe
+even experimental ones, to give users an easier access to them,
+and to give tools wider exposure, so that they can be improved
+faster.
+
+I am not expecting to touch these myself that much.  As far as
+my day-to-day operation is concerned, these subdirectories are
+owned by their respective primary authors.  I am willing to help
+if users of these components and the contrib/ subtree "owners"
+have technical/design issues to resolve, but the initiative to
+fix and/or enhance things _must_ be on the side of the subtree
+owners.  IOW, I won't be actively looking for bugs and rooms for
+enhancements in them as the git maintainer -- I may only do so
+just as one of the users when I want to scratch my own itch.  If
+you have patches to things in contrib/ area, the patch should be
+first sent to the primary author, and then the primary author
+should ack and forward it to me (git pull request is nicer).
+This is the same way as how I have been treating gitk, and to a
+lesser degree various foreign SCM interfaces, so you know the
+drill.
+
+I expect that things that start their life in the contrib/ area
+to graduate out of contrib/ once they mature, either by becoming
+projects on their own, or moving to the toplevel directory.  On
+the other hand, I expect I'll be proposing removal of disused
+and inactive ones from time to time.
+
+If you have new things to add to this area, please first propose
+it on the git mailing list, and after a list discussion proves
+there are some general interests (it does not have to be a
+list-wide consensus for a tool targeted to a relatively narrow
+audience -- for example I do not work with projects whose
+upstream is svn, so I have no use for git-svn myself, but it is
+of general interest for people who need to interoperate with SVN
+repositories in a way git-svn works better than git-svnimport),
+submit a patch to create a subdirectory of contrib/ and put your
+stuff there.
+
+-jc
+
diff --git a/contrib/emacs/.gitignore b/contrib/emacs/.gitignore
new file mode 100644 (file)
index 0000000..c531d98
--- /dev/null
@@ -0,0 +1 @@
+*.elc
diff --git a/contrib/emacs/Makefile b/contrib/emacs/Makefile
new file mode 100644 (file)
index 0000000..d3619db
--- /dev/null
@@ -0,0 +1,20 @@
+## Build and install stuff
+
+EMACS = emacs
+
+ELC = git.elc vc-git.elc
+INSTALL = install
+INSTALL_ELC = $(INSTALL) -m 644
+prefix = $(HOME)
+emacsdir = $(prefix)/share/emacs/site-lisp
+
+all: $(ELC)
+
+install: all
+       $(INSTALL) -d $(emacsdir)
+       $(INSTALL_ELC) $(ELC) $(emacsdir)
+
+%.elc: %.el
+       $(EMACS) --batch --eval '(byte-compile-file "$<")'
+
+clean:; rm -f $(ELC)
diff --git a/contrib/emacs/git.el b/contrib/emacs/git.el
new file mode 100644 (file)
index 0000000..ebd00ef
--- /dev/null
@@ -0,0 +1,1012 @@
+;;; git.el --- A user interface for git
+
+;; Copyright (C) 2005, 2006 Alexandre Julliard <julliard@winehq.org>
+
+;; Version: 1.0
+
+;; This program is free software; you can redistribute it and/or
+;; modify it under the terms of the GNU General Public License as
+;; published by the Free Software Foundation; either version 2 of
+;; the License, or (at your option) any later version.
+;;
+;; This program is distributed in the hope that it will be
+;; useful, but WITHOUT ANY WARRANTY; without even the implied
+;; warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+;; PURPOSE.  See the GNU General Public License for more details.
+;;
+;; You should have received a copy of the GNU General Public
+;; License along with this program; if not, write to the Free
+;; Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+;; MA 02111-1307 USA
+
+;;; Commentary:
+
+;; This file contains an interface for the git version control
+;; system. It provides easy access to the most frequently used git
+;; commands. The user interface is as far as possible identical to
+;; that of the PCL-CVS mode.
+;;
+;; To install: put this file on the load-path and place the following
+;; in your .emacs file:
+;;
+;;    (require 'git)
+;;
+;; To start: `M-x git-status'
+;;
+;; TODO
+;;  - portability to XEmacs
+;;  - better handling of subprocess errors
+;;  - hook into file save (after-save-hook)
+;;  - diff against other branch
+;;  - renaming files from the status buffer
+;;  - creating tags
+;;  - fetch/pull
+;;  - switching branches
+;;  - revlist browser
+;;  - git-show-branch browser
+;;  - menus
+;;
+
+(eval-when-compile (require 'cl))
+(require 'ewoc)
+
+
+;;;; Customizations
+;;;; ------------------------------------------------------------
+
+(defgroup git nil
+  "Git user interface")
+
+(defcustom git-committer-name nil
+  "User name to use for commits.
+The default is to fall back to the repository config, then to `add-log-full-name' and then to `user-full-name'."
+  :group 'git
+  :type '(choice (const :tag "Default" nil)
+                 (string :tag "Name")))
+
+(defcustom git-committer-email nil
+  "Email address to use for commits.
+The default is to fall back to the git repository config, then to `add-log-mailing-address' and then to `user-mail-address'."
+  :group 'git
+  :type '(choice (const :tag "Default" nil)
+                 (string :tag "Email")))
+
+(defcustom git-commits-coding-system 'utf-8
+  "Default coding system for the log message of git commits."
+  :group 'git
+  :type 'coding-system)
+
+(defcustom git-append-signed-off-by nil
+  "Whether to append a Signed-off-by line to the commit message before editing."
+  :group 'git
+  :type 'boolean)
+
+(defcustom git-per-dir-ignore-file ".gitignore"
+  "Name of the per-directory ignore file."
+  :group 'git
+  :type 'string)
+
+(defface git-status-face
+  '((((class color) (background light)) (:foreground "purple")))
+  "Git mode face used to highlight added and modified files."
+  :group 'git)
+
+(defface git-unmerged-face
+  '((((class color) (background light)) (:foreground "red" :bold t)))
+  "Git mode face used to highlight unmerged files."
+  :group 'git)
+
+(defface git-unknown-face
+  '((((class color) (background light)) (:foreground "goldenrod" :bold t)))
+  "Git mode face used to highlight unknown files."
+  :group 'git)
+
+(defface git-uptodate-face
+  '((((class color) (background light)) (:foreground "grey60")))
+  "Git mode face used to highlight up-to-date files."
+  :group 'git)
+
+(defface git-ignored-face
+  '((((class color) (background light)) (:foreground "grey60")))
+  "Git mode face used to highlight ignored files."
+  :group 'git)
+
+(defface git-mark-face
+  '((((class color) (background light)) (:foreground "red" :bold t)))
+  "Git mode face used for the file marks."
+  :group 'git)
+
+(defface git-header-face
+  '((((class color) (background light)) (:foreground "blue")))
+  "Git mode face used for commit headers."
+  :group 'git)
+
+(defface git-separator-face
+  '((((class color) (background light)) (:foreground "brown")))
+  "Git mode face used for commit separator."
+  :group 'git)
+
+(defface git-permission-face
+  '((((class color) (background light)) (:foreground "green" :bold t)))
+  "Git mode face used for permission changes."
+  :group 'git)
+
+
+;;;; Utilities
+;;;; ------------------------------------------------------------
+
+(defconst git-log-msg-separator "--- log message follows this line ---")
+
+(defun git-get-env-strings (env)
+  "Build a list of NAME=VALUE strings from a list of environment strings."
+  (mapcar (lambda (entry) (concat (car entry) "=" (cdr entry))) env))
+
+(defun git-call-process-env (buffer env &rest args)
+  "Wrapper for call-process that sets environment strings."
+  (if env
+      (apply #'call-process "env" nil buffer nil
+             (append (git-get-env-strings env) (list "git") args))
+    (apply #'call-process "git" nil buffer nil args)))
+
+(defun git-call-process-env-string (env &rest args)
+  "Wrapper for call-process that sets environment strings, and returns the process output as a string."
+  (with-temp-buffer
+    (and (eq 0 (apply #' git-call-process-env t env args))
+         (buffer-string))))
+
+(defun git-run-process-region (buffer start end program args)
+  "Run a git process with a buffer region as input."
+  (let ((output-buffer (current-buffer))
+        (dir default-directory))
+    (with-current-buffer buffer
+      (cd dir)
+      (apply #'call-process-region start end program
+             nil (list output-buffer nil) nil args))))
+
+(defun git-run-command-buffer (buffer-name &rest args)
+  "Run a git command, sending the output to a buffer named BUFFER-NAME."
+  (let ((dir default-directory)
+        (buffer (get-buffer-create buffer-name)))
+    (message "Running git %s..." (car args))
+    (with-current-buffer buffer
+      (let ((default-directory dir)
+            (buffer-read-only nil))
+        (erase-buffer)
+        (apply #'git-call-process-env buffer nil args)))
+    (message "Running git %s...done" (car args))
+    buffer))
+
+(defun git-run-command (buffer env &rest args)
+  (message "Running git %s..." (car args))
+  (apply #'git-call-process-env buffer env args)
+  (message "Running git %s...done" (car args)))
+
+(defun git-run-command-region (buffer start end env &rest args)
+  "Run a git command with specified buffer region as input."
+  (message "Running git %s..." (car args))
+  (unless (eq 0 (if env
+                    (git-run-process-region
+                     buffer start end "env"
+                     (append (git-get-env-strings env) (list "git") args))
+                  (git-run-process-region
+                   buffer start end "git" args)))
+    (error "Failed to run \"git %s\":\n%s" (mapconcat (lambda (x) x) args " ") (buffer-string)))
+  (message "Running git %s...done" (car args)))
+
+(defun git-get-string-sha1 (string)
+  "Read a SHA1 from the specified string."
+  (and string
+       (string-match "[0-9a-f]\\{40\\}" string)
+       (match-string 0 string)))
+
+(defun git-get-committer-name ()
+  "Return the name to use as GIT_COMMITTER_NAME."
+  ; copied from log-edit
+  (or git-committer-name
+      (git-repo-config "user.name")
+      (and (boundp 'add-log-full-name) add-log-full-name)
+      (and (fboundp 'user-full-name) (user-full-name))
+      (and (boundp 'user-full-name) user-full-name)))
+
+(defun git-get-committer-email ()
+  "Return the email address to use as GIT_COMMITTER_EMAIL."
+  ; copied from log-edit
+  (or git-committer-email
+      (git-repo-config "user.email")
+      (and (boundp 'add-log-mailing-address) add-log-mailing-address)
+      (and (fboundp 'user-mail-address) (user-mail-address))
+      (and (boundp 'user-mail-address) user-mail-address)))
+
+(defun git-escape-file-name (name)
+  "Escape a file name if necessary."
+  (if (string-match "[\n\t\"\\]" name)
+      (concat "\""
+              (mapconcat (lambda (c)
+                   (case c
+                     (?\n "\\n")
+                     (?\t "\\t")
+                     (?\\ "\\\\")
+                     (?\" "\\\"")
+                     (t (char-to-string c))))
+                 name "")
+              "\"")
+    name))
+
+(defun git-get-top-dir (dir)
+  "Retrieve the top-level directory of a git tree."
+  (let ((cdup (with-output-to-string
+                (with-current-buffer standard-output
+                  (cd dir)
+                  (unless (eq 0 (call-process "git" nil t nil "rev-parse" "--show-cdup"))
+                    (error "cannot find top-level git tree for %s." dir))))))
+    (expand-file-name (concat (file-name-as-directory dir)
+                              (car (split-string cdup "\n"))))))
+
+;stolen from pcl-cvs
+(defun git-append-to-ignore (file)
+  "Add a file name to the ignore file in its directory."
+  (let* ((fullname (expand-file-name file))
+         (dir (file-name-directory fullname))
+         (name (file-name-nondirectory fullname))
+         (ignore-name (expand-file-name git-per-dir-ignore-file dir))
+         (created (not (file-exists-p ignore-name))))
+  (save-window-excursion
+    (set-buffer (find-file-noselect ignore-name))
+    (goto-char (point-max))
+    (unless (zerop (current-column)) (insert "\n"))
+    (insert name "\n")
+    (sort-lines nil (point-min) (point-max))
+    (save-buffer))
+  (when created
+    (git-run-command nil nil "update-index" "--info-only" "--add" "--" (file-relative-name ignore-name)))
+  (git-add-status-file (if created 'added 'modified) (file-relative-name ignore-name))))
+
+
+;;;; Wrappers for basic git commands
+;;;; ------------------------------------------------------------
+
+(defun git-rev-parse (rev)
+  "Parse a revision name and return its SHA1."
+  (git-get-string-sha1
+   (git-call-process-env-string nil "rev-parse" rev)))
+
+(defun git-repo-config (key)
+  "Retrieve the value associated to KEY in the git repository config file."
+  (let ((str (git-call-process-env-string nil "repo-config" key)))
+    (and str (car (split-string str "\n")))))
+
+(defun git-symbolic-ref (ref)
+  "Wrapper for the git-symbolic-ref command."
+  (let ((str (git-call-process-env-string nil "symbolic-ref" ref)))
+    (and str (car (split-string str "\n")))))
+
+(defun git-update-ref (ref val &optional oldval)
+  "Update a reference by calling git-update-ref."
+  (apply #'git-call-process-env nil nil "update-ref" ref val (if oldval (list oldval))))
+
+(defun git-read-tree (tree &optional index-file)
+  "Read a tree into the index file."
+  (apply #'git-call-process-env nil
+         (if index-file `(("GIT_INDEX_FILE" . ,index-file)) nil)
+         "read-tree" (if tree (list tree))))
+
+(defun git-write-tree (&optional index-file)
+  "Call git-write-tree and return the resulting tree SHA1 as a string."
+  (git-get-string-sha1
+   (git-call-process-env-string (and index-file `(("GIT_INDEX_FILE" . ,index-file))) "write-tree")))
+
+(defun git-commit-tree (buffer tree head)
+  "Call git-commit-tree with buffer as input and return the resulting commit SHA1."
+  (let ((author-name (git-get-committer-name))
+        (author-email (git-get-committer-email))
+        author-date log-start log-end args)
+    (when head
+      (push "-p" args)
+      (push head args))
+    (with-current-buffer buffer
+      (goto-char (point-min))
+      (if
+          (setq log-start (re-search-forward (concat "^" (regexp-quote git-log-msg-separator) "\n") nil t))
+          (save-restriction
+            (narrow-to-region (point-min) log-start)
+            (goto-char (point-min))
+            (when (re-search-forward "^Author: +\\(.*?\\) *<\\(.*\\)> *$" nil t)
+              (setq author-name (match-string 1)
+                    author-email (match-string 2)))
+            (goto-char (point-min))
+            (when (re-search-forward "^Date: +\\(.*\\)$" nil t)
+              (setq author-date (match-string 1)))
+            (goto-char (point-min))
+            (while (re-search-forward "^Parent: +\\([0-9a-f]+\\)" nil t)
+              (unless (string-equal head (match-string 1))
+                (push "-p" args)
+                (push (match-string 1) args))))
+        (setq log-start (point-min)))
+      (setq log-end (point-max)))
+    (git-get-string-sha1
+     (with-output-to-string
+       (with-current-buffer standard-output
+         (let ((coding-system-for-write git-commits-coding-system)
+               (env `(("GIT_AUTHOR_NAME" . ,author-name)
+                      ("GIT_AUTHOR_EMAIL" . ,author-email)
+                      ("GIT_COMMITTER_NAME" . ,(git-get-committer-name))
+                      ("GIT_COMMITTER_EMAIL" . ,(git-get-committer-email)))))
+           (when author-date (push `("GIT_AUTHOR_DATE" . ,author-date) env))
+           (apply #'git-run-command-region
+                  buffer log-start log-end env
+                  "commit-tree" tree (nreverse args))))))))
+
+(defun git-empty-db-p ()
+  "Check if the git db is empty (no commit done yet)."
+  (not (eq 0 (call-process "git" nil nil nil "rev-parse" "--verify" "HEAD"))))
+
+(defun git-get-merge-heads ()
+  "Retrieve the merge heads from the MERGE_HEAD file if present."
+  (let (heads)
+    (when (file-readable-p ".git/MERGE_HEAD")
+      (with-temp-buffer
+        (insert-file-contents ".git/MERGE_HEAD" nil nil nil t)
+        (goto-char (point-min))
+        (while (re-search-forward "[0-9a-f]\\{40\\}" nil t)
+          (push (match-string 0) heads))))
+    (nreverse heads)))
+
+;;;; File info structure
+;;;; ------------------------------------------------------------
+
+; fileinfo structure stolen from pcl-cvs
+(defstruct (git-fileinfo
+            (:copier nil)
+            (:constructor git-create-fileinfo (state name &optional old-perm new-perm rename-state orig-name marked))
+            (:conc-name git-fileinfo->))
+  marked              ;; t/nil
+  state               ;; current state
+  name                ;; file name
+  old-perm new-perm   ;; permission flags
+  rename-state        ;; rename or copy state
+  orig-name           ;; original name for renames or copies
+  needs-refresh)      ;; whether file needs to be refreshed
+
+(defvar git-status nil)
+
+(defun git-clear-status (status)
+  "Remove everything from the status list."
+  (ewoc-filter status (lambda (info) nil)))
+
+(defun git-set-files-state (files state)
+  "Set the state of a list of files."
+  (dolist (info files)
+    (unless (eq (git-fileinfo->state info) state)
+      (setf (git-fileinfo->state info) state)
+      (setf (git-fileinfo->rename-state info) nil)
+      (setf (git-fileinfo->orig-name info) nil)
+      (setf (git-fileinfo->needs-refresh info) t))))
+
+(defun git-state-code (code)
+  "Convert from a string to a added/deleted/modified state."
+  (case (string-to-char code)
+    (?M 'modified)
+    (?? 'unknown)
+    (?A 'added)
+    (?D 'deleted)
+    (?U 'unmerged)
+    (t nil)))
+
+(defun git-status-code-as-string (code)
+  "Format a git status code as string."
+  (case code
+    ('modified (propertize "Modified" 'face 'git-status-face))
+    ('unknown  (propertize "Unknown " 'face 'git-unknown-face))
+    ('added    (propertize "Added   " 'face 'git-status-face))
+    ('deleted  (propertize "Deleted " 'face 'git-status-face))
+    ('unmerged (propertize "Unmerged" 'face 'git-unmerged-face))
+    ('uptodate (propertize "Uptodate" 'face 'git-uptodate-face))
+    ('ignored  (propertize "Ignored " 'face 'git-ignored-face))
+    (t "?       ")))
+
+(defun git-rename-as-string (info)
+  "Return a string describing the copy or rename associated with INFO, or an empty string if none."
+  (let ((state (git-fileinfo->rename-state info)))
+    (if state
+        (propertize
+         (concat "   ("
+                 (if (eq state 'copy) "copied from "
+                   (if (eq (git-fileinfo->state info) 'added) "renamed to "
+                     "renamed from "))
+                 (git-escape-file-name (git-fileinfo->orig-name info))
+                 ")") 'face 'git-status-face)
+      "")))
+
+(defun git-permissions-as-string (old-perm new-perm)
+  "Format a permission change as string."
+  (propertize
+   (if (or (not old-perm)
+           (not new-perm)
+           (eq 0 (logand ?\111 (logxor old-perm new-perm))))
+       "  "
+     (if (eq 0 (logand ?\111 old-perm)) "+x" "-x"))
+  'face 'git-permission-face))
+
+(defun git-fileinfo-prettyprint (info)
+  "Pretty-printer for the git-fileinfo structure."
+  (insert (format "   %s %s %s  %s%s"
+                  (if (git-fileinfo->marked info) (propertize "*" 'face 'git-mark-face) " ")
+                  (git-status-code-as-string (git-fileinfo->state info))
+                  (git-permissions-as-string (git-fileinfo->old-perm info) (git-fileinfo->new-perm info))
+                  (git-escape-file-name (git-fileinfo->name info))
+                  (git-rename-as-string info))))
+
+(defun git-parse-status (status)
+  "Parse the output of git-diff-index in the current buffer."
+  (goto-char (point-min))
+  (while (re-search-forward
+          ":\\([0-7]\\{6\\}\\) \\([0-7]\\{6\\}\\) [0-9a-f]\\{40\\} [0-9a-f]\\{40\\} \\(\\([ADMU]\\)\0\\([^\0]+\\)\\|\\([CR]\\)[0-9]*\0\\([^\0]+\\)\0\\([^\0]+\\)\\)\0"
+          nil t 1)
+    (let ((old-perm (string-to-number (match-string 1) 8))
+          (new-perm (string-to-number (match-string 2) 8))
+          (state (or (match-string 4) (match-string 6)))
+          (name (or (match-string 5) (match-string 7)))
+          (new-name (match-string 8)))
+      (if new-name  ; copy or rename
+          (if (eq ?C (string-to-char state))
+              (ewoc-enter-last status (git-create-fileinfo 'added new-name old-perm new-perm 'copy name))
+            (ewoc-enter-last status (git-create-fileinfo 'deleted name 0 0 'rename new-name))
+            (ewoc-enter-last status (git-create-fileinfo 'added new-name old-perm new-perm 'rename name)))
+        (ewoc-enter-last status (git-create-fileinfo (git-state-code state) name old-perm new-perm))))))
+
+(defun git-find-status-file (status file)
+  "Find a given file in the status ewoc and return its node."
+  (let ((node (ewoc-nth status 0)))
+    (while (and node (not (string= file (git-fileinfo->name (ewoc-data node)))))
+      (setq node (ewoc-next status node)))
+    node))
+
+(defun git-parse-ls-files (status default-state &optional skip-existing)
+  "Parse the output of git-ls-files in the current buffer."
+  (goto-char (point-min))
+  (let (infolist)
+    (while (re-search-forward "\\([HMRCK?]\\) \\([^\0]*\\)\0" nil t 1)
+      (let ((state (match-string 1))
+            (name (match-string 2)))
+        (unless (and skip-existing (git-find-status-file status name))
+          (push (git-create-fileinfo (or (git-state-code state) default-state) name) infolist))))
+    (dolist (info (nreverse infolist))
+      (ewoc-enter-last status info))))
+
+(defun git-parse-ls-unmerged (status)
+  "Parse the output of git-ls-files -u in the current buffer."
+  (goto-char (point-min))
+  (let (files)
+    (while (re-search-forward "[0-7]\\{6\\} [0-9a-f]\\{40\\} [123]\t\\([^\0]+\\)\0" nil t)
+      (let ((node (git-find-status-file status (match-string 1))))
+        (when node (push (ewoc-data node) files))))
+    (git-set-files-state files 'unmerged)))
+
+(defun git-add-status-file (state name)
+  "Add a new file to the status list (if not existing already) and return its node."
+  (unless git-status (error "Not in git-status buffer."))
+  (or (git-find-status-file git-status name)
+      (ewoc-enter-last git-status (git-create-fileinfo state name))))
+
+(defun git-marked-files ()
+  "Return a list of all marked files, or if none a list containing just the file at cursor position."
+  (unless git-status (error "Not in git-status buffer."))
+  (or (ewoc-collect git-status (lambda (info) (git-fileinfo->marked info)))
+      (list (ewoc-data (ewoc-locate git-status)))))
+
+(defun git-marked-files-state (&rest states)
+  "Return marked files that are in the specified states."
+  (let ((files (git-marked-files))
+        result)
+    (dolist (info files)
+      (when (memq (git-fileinfo->state info) states)
+        (push info result)))
+    result))
+
+(defun git-refresh-files ()
+  "Refresh all files that need it and clear the needs-refresh flag."
+  (unless git-status (error "Not in git-status buffer."))
+  (ewoc-map
+   (lambda (info)
+     (let ((refresh (git-fileinfo->needs-refresh info)))
+       (setf (git-fileinfo->needs-refresh info) nil)
+       refresh))
+   git-status)
+  ; move back to goal column
+  (when goal-column (move-to-column goal-column)))
+
+(defun git-refresh-ewoc-hf (status)
+  "Refresh the ewoc header and footer."
+  (let ((branch (git-symbolic-ref "HEAD"))
+        (head (if (git-empty-db-p) "Nothing committed yet"
+                (substring (git-rev-parse "HEAD") 0 10)))
+        (merge-heads (git-get-merge-heads)))
+    (ewoc-set-hf status
+                 (format "Directory:  %s\nBranch:     %s\nHead:       %s%s\n"
+                         default-directory
+                         (if (string-match "^refs/heads/" branch)
+                             (substring branch (match-end 0))
+                           branch)
+                         head
+                         (if merge-heads
+                             (concat "\nMerging:    "
+                                     (mapconcat (lambda (str) (substring str 0 10)) merge-heads " "))
+                           ""))
+                 (if (ewoc-nth status 0) "" "    No changes."))))
+
+(defun git-get-filenames (files)
+  (mapcar (lambda (info) (git-fileinfo->name info)) files))
+
+(defun git-update-index (index-file files)
+  "Run git-update-index on a list of files."
+  (let ((env (and index-file `(("GIT_INDEX_FILE" . ,index-file))))
+        added deleted modified)
+    (dolist (info files)
+      (case (git-fileinfo->state info)
+        ('added (push info added))
+        ('deleted (push info deleted))
+        ('modified (push info modified))))
+    (when added
+      (apply #'git-run-command nil env "update-index" "--add" "--" (git-get-filenames added)))
+    (when deleted
+      (apply #'git-run-command nil env "update-index" "--remove" "--" (git-get-filenames deleted)))
+    (when modified
+      (apply #'git-run-command nil env "update-index" "--" (git-get-filenames modified)))))
+
+(defun git-do-commit ()
+  "Perform the actual commit using the current buffer as log message."
+  (interactive)
+  (let ((buffer (current-buffer))
+        (index-file (make-temp-file "gitidx")))
+    (with-current-buffer log-edit-parent-buffer
+      (if (git-marked-files-state 'unmerged)
+          (message "You cannot commit unmerged files, resolve them first.")
+        (unwind-protect
+            (let ((files (git-marked-files-state 'added 'deleted 'modified))
+                  head head-tree)
+              (unless (git-empty-db-p)
+                (setq head (git-rev-parse "HEAD")
+                      head-tree (git-rev-parse "HEAD^{tree}")))
+              (if files
+                  (progn
+                    (git-read-tree head-tree index-file)
+                    (git-update-index nil files)         ;update both the default index
+                    (git-update-index index-file files)  ;and the temporary one
+                    (let ((tree (git-write-tree index-file)))
+                      (if (or (not (string-equal tree head-tree))
+                              (yes-or-no-p "The tree was not modified, do you really want to perform an empty commit? "))
+                          (let ((commit (git-commit-tree buffer tree head)))
+                            (git-update-ref "HEAD" commit head)
+                            (condition-case nil (delete-file ".git/MERGE_HEAD") (error nil))
+                            (with-current-buffer buffer (erase-buffer))
+                            (git-set-files-state files 'uptodate)
+                            (git-refresh-files)
+                            (git-refresh-ewoc-hf git-status)
+                            (message "Committed %s." commit))
+                        (message "Commit aborted."))))
+                (message "No files to commit.")))
+          (delete-file index-file))))))
+
+
+;;;; Interactive functions
+;;;; ------------------------------------------------------------
+
+(defun git-mark-file ()
+  "Mark the file that the cursor is on and move to the next one."
+  (interactive)
+  (unless git-status (error "Not in git-status buffer."))
+  (let* ((pos (ewoc-locate git-status))
+         (info (ewoc-data pos)))
+    (setf (git-fileinfo->marked info) t)
+    (ewoc-invalidate git-status pos)
+    (ewoc-goto-next git-status 1)))
+
+(defun git-unmark-file ()
+  "Unmark the file that the cursor is on and move to the next one."
+  (interactive)
+  (unless git-status (error "Not in git-status buffer."))
+  (let* ((pos (ewoc-locate git-status))
+         (info (ewoc-data pos)))
+    (setf (git-fileinfo->marked info) nil)
+    (ewoc-invalidate git-status pos)
+    (ewoc-goto-next git-status 1)))
+
+(defun git-unmark-file-up ()
+  "Unmark the file that the cursor is on and move to the previous one."
+  (interactive)
+  (unless git-status (error "Not in git-status buffer."))
+  (let* ((pos (ewoc-locate git-status))
+         (info (ewoc-data pos)))
+    (setf (git-fileinfo->marked info) nil)
+    (ewoc-invalidate git-status pos)
+    (ewoc-goto-prev git-status 1)))
+
+(defun git-mark-all ()
+  "Mark all files."
+  (interactive)
+  (unless git-status (error "Not in git-status buffer."))
+  (ewoc-map (lambda (info) (setf (git-fileinfo->marked info) t) t) git-status)
+  ; move back to goal column after invalidate
+  (when goal-column (move-to-column goal-column)))
+
+(defun git-unmark-all ()
+  "Unmark all files."
+  (interactive)
+  (unless git-status (error "Not in git-status buffer."))
+  (ewoc-map (lambda (info) (setf (git-fileinfo->marked info) nil) t) git-status)
+  ; move back to goal column after invalidate
+  (when goal-column (move-to-column goal-column)))
+
+(defun git-toggle-all-marks ()
+  "Toggle all file marks."
+  (interactive)
+  (unless git-status (error "Not in git-status buffer."))
+  (ewoc-map (lambda (info) (setf (git-fileinfo->marked info) (not (git-fileinfo->marked info))) t) git-status)
+  ; move back to goal column after invalidate
+  (when goal-column (move-to-column goal-column)))
+
+(defun git-next-file (&optional n)
+  "Move the selection down N files."
+  (interactive "p")
+  (unless git-status (error "Not in git-status buffer."))
+  (ewoc-goto-next git-status n))
+
+(defun git-prev-file (&optional n)
+  "Move the selection up N files."
+  (interactive "p")
+  (unless git-status (error "Not in git-status buffer."))
+  (ewoc-goto-prev git-status n))
+
+(defun git-add-file ()
+  "Add marked file(s) to the index cache."
+  (interactive)
+  (let ((files (git-marked-files-state 'unknown)))
+    (unless files
+      (push (ewoc-data
+             (git-add-status-file 'added (file-relative-name
+                                          (read-file-name "File to add: " nil nil t))))
+            files))
+    (apply #'git-run-command nil nil "update-index" "--info-only" "--add" "--" (git-get-filenames files))
+    (git-set-files-state files 'added)
+    (git-refresh-files)))
+
+(defun git-ignore-file ()
+  "Add marked file(s) to the ignore list."
+  (interactive)
+  (let ((files (git-marked-files-state 'unknown)))
+    (unless files
+      (push (ewoc-data
+             (git-add-status-file 'unknown (file-relative-name
+                                            (read-file-name "File to ignore: " nil nil t))))
+            files))
+    (dolist (info files) (git-append-to-ignore (git-fileinfo->name info)))
+    (git-set-files-state files 'ignored)
+    (git-refresh-files)))
+
+(defun git-remove-file ()
+  "Remove the marked file(s)."
+  (interactive)
+  (let ((files (git-marked-files-state 'added 'modified 'unknown 'uptodate)))
+    (unless files
+      (push (ewoc-data
+             (git-add-status-file 'unknown (file-relative-name
+                                            (read-file-name "File to remove: " nil nil t))))
+            files))
+    (if (yes-or-no-p
+         (format "Remove %d file%s? " (length files) (if (> (length files) 1) "s" "")))
+        (progn
+          (dolist (info files)
+            (let ((name (git-fileinfo->name info)))
+              (when (file-exists-p name) (delete-file name))))
+          (apply #'git-run-command nil nil "update-index" "--info-only" "--remove" "--" (git-get-filenames files))
+          ; remove unknown files from the list, set the others to deleted
+          (ewoc-filter git-status
+                       (lambda (info files)
+                         (not (and (memq info files) (eq (git-fileinfo->state info) 'unknown))))
+                       files)
+          (git-set-files-state files 'deleted)
+          (git-refresh-files)
+          (unless (ewoc-nth git-status 0)  ; refresh header if list is empty
+            (git-refresh-ewoc-hf git-status)))
+      (message "Aborting"))))
+
+(defun git-revert-file ()
+  "Revert changes to the marked file(s)."
+  (interactive)
+  (let ((files (git-marked-files))
+        added modified)
+    (when (and files
+               (yes-or-no-p
+                (format "Revert %d file%s? " (length files) (if (> (length files) 1) "s" ""))))
+      (dolist (info files)
+        (case (git-fileinfo->state info)
+          ('added (push info added))
+          ('deleted (push info modified))
+          ('unmerged (push info modified))
+          ('modified (push info modified))))
+      (when added
+          (apply #'git-run-command nil nil "update-index" "--force-remove" "--" (git-get-filenames added))
+          (git-set-files-state added 'unknown))
+      (when modified
+          (apply #'git-run-command nil nil "checkout" "HEAD" (git-get-filenames modified))
+          (git-set-files-state modified 'uptodate))
+      (git-refresh-files))))
+
+(defun git-resolve-file ()
+  "Resolve conflicts in marked file(s)."
+  (interactive)
+  (let ((files (git-marked-files-state 'unmerged)))
+    (when files
+      (apply #'git-run-command nil nil "update-index" "--info-only" "--" (git-get-filenames files))
+      (git-set-files-state files 'modified)
+      (git-refresh-files))))
+
+(defun git-remove-handled ()
+  "Remove handled files from the status list."
+  (interactive)
+  (ewoc-filter git-status
+               (lambda (info)
+                 (not (or (eq (git-fileinfo->state info) 'ignored)
+                          (eq (git-fileinfo->state info) 'uptodate)))))
+  (unless (ewoc-nth git-status 0)  ; refresh header if list is empty
+    (git-refresh-ewoc-hf git-status)))
+
+(defun git-setup-diff-buffer (buffer)
+  "Setup a buffer for displaying a diff."
+  (with-current-buffer buffer
+    (diff-mode)
+    (goto-char (point-min))
+    (setq buffer-read-only t))
+  (display-buffer buffer)
+  (shrink-window-if-larger-than-buffer))
+
+(defun git-diff-file ()
+  "Diff the marked file(s) against HEAD."
+  (interactive)
+  (let ((files (git-marked-files)))
+    (git-setup-diff-buffer
+     (apply #'git-run-command-buffer "*git-diff*" "diff-index" "-p" "-M" "HEAD" "--" (git-get-filenames files)))))
+
+(defun git-diff-file-merge-head (arg)
+  "Diff the marked file(s) against the first merge head (or the nth one with a numeric prefix)."
+  (interactive "p")
+  (let ((files (git-marked-files))
+        (merge-heads (git-get-merge-heads)))
+    (unless merge-heads (error "No merge in progress"))
+    (git-setup-diff-buffer
+     (apply #'git-run-command-buffer "*git-diff*" "diff-index" "-p" "-M"
+            (or (nth (1- arg) merge-heads) "HEAD") "--" (git-get-filenames files)))))
+
+(defun git-diff-unmerged-file (stage)
+  "Diff the marked unmerged file(s) against the specified stage."
+  (let ((files (git-marked-files)))
+    (git-setup-diff-buffer
+     (apply #'git-run-command-buffer "*git-diff*" "diff-files" "-p" stage "--" (git-get-filenames files)))))
+
+(defun git-diff-file-base ()
+  "Diff the marked unmerged file(s) against the common base file."
+  (interactive)
+  (git-diff-unmerged-file "-1"))
+
+(defun git-diff-file-mine ()
+  "Diff the marked unmerged file(s) against my pre-merge version."
+  (interactive)
+  (git-diff-unmerged-file "-2"))
+
+(defun git-diff-file-other ()
+  "Diff the marked unmerged file(s) against the other's pre-merge version."
+  (interactive)
+  (git-diff-unmerged-file "-3"))
+
+(defun git-diff-file-combined ()
+  "Do a combined diff of the marked unmerged file(s)."
+  (interactive)
+  (git-diff-unmerged-file "-c"))
+
+(defun git-diff-file-idiff ()
+  "Perform an interactive diff on the current file."
+  (interactive)
+  (error "Interactive diffs not implemented yet."))
+
+(defun git-log-file ()
+  "Display a log of changes to the marked file(s)."
+  (interactive)
+  (let* ((files (git-marked-files))
+         (coding-system-for-read git-commits-coding-system)
+         (buffer (apply #'git-run-command-buffer "*git-log*" "rev-list" "--pretty" "HEAD" "--" (git-get-filenames files))))
+    (with-current-buffer buffer
+      ; (git-log-mode)  FIXME: implement log mode
+      (goto-char (point-min))
+      (setq buffer-read-only t))
+    (display-buffer buffer)))
+
+(defun git-log-edit-files ()
+  "Return a list of marked files for use in the log-edit buffer."
+  (with-current-buffer log-edit-parent-buffer
+    (git-get-filenames (git-marked-files-state 'added 'deleted 'modified))))
+
+(defun git-commit-file ()
+  "Commit the marked file(s), asking for a commit message."
+  (interactive)
+  (unless git-status (error "Not in git-status buffer."))
+  (let ((buffer (get-buffer-create "*git-commit*"))
+        (merge-heads (git-get-merge-heads))
+        (dir default-directory)
+        (sign-off git-append-signed-off-by))
+    (with-current-buffer buffer
+      (when (eq 0 (buffer-size))
+        (cd dir)
+        (erase-buffer)
+        (insert
+         (propertize
+          (format "Author: %s <%s>\n%s"
+                  (git-get-committer-name) (git-get-committer-email)
+                  (if merge-heads
+                      (format "Parent: %s\n%s\n"
+                              (git-rev-parse "HEAD")
+                              (mapconcat (lambda (str) (concat "Parent: " str)) merge-heads "\n"))
+                    ""))
+          'face 'git-header-face)
+         (propertize git-log-msg-separator 'face 'git-separator-face)
+         "\n")
+        (cond ((and merge-heads (file-readable-p ".git/MERGE_MSG"))
+               (insert-file-contents ".git/MERGE_MSG"))
+              (sign-off
+               (insert (format "\n\nSigned-off-by: %s <%s>\n"
+                               (git-get-committer-name) (git-get-committer-email)))))))
+    (let ((log-edit-font-lock-keywords
+           `(("^\\(Author:\\|Date:\\|Parent:\\|Signed-off-by:\\)\\(.*\\)"
+              (1 font-lock-keyword-face)
+              (2 font-lock-function-name-face))
+             (,(concat "^\\(" (regexp-quote git-log-msg-separator) "\\)$")
+              (1 font-lock-comment-face)))))
+      (log-edit #'git-do-commit nil #'git-log-edit-files buffer))))
+
+(defun git-find-file ()
+  "Visit the current file in its own buffer."
+  (interactive)
+  (unless git-status (error "Not in git-status buffer."))
+  (let ((info (ewoc-data (ewoc-locate git-status))))
+    (find-file (git-fileinfo->name info))
+    (when (eq 'unmerged (git-fileinfo->state info))
+      (smerge-mode))))
+
+(defun git-find-file-imerge ()
+  "Visit the current file in interactive merge mode."
+  (interactive)
+  (unless git-status (error "Not in git-status buffer."))
+  (let ((info (ewoc-data (ewoc-locate git-status))))
+    (find-file (git-fileinfo->name info))
+    (smerge-ediff)))
+
+(defun git-view-file ()
+  "View the current file in its own buffer."
+  (interactive)
+  (unless git-status (error "Not in git-status buffer."))
+  (let ((info (ewoc-data (ewoc-locate git-status))))
+    (view-file (git-fileinfo->name info))))
+
+(defun git-refresh-status ()
+  "Refresh the git status buffer."
+  (interactive)
+  (let* ((status git-status)
+         (pos (ewoc-locate status))
+         (cur-name (and pos (git-fileinfo->name (ewoc-data pos)))))
+    (unless status (error "Not in git-status buffer."))
+    (git-clear-status status)
+    (git-run-command nil nil "update-index" "--info-only" "--refresh")
+    (if (git-empty-db-p)
+        ; we need some special handling for an empty db
+        (with-temp-buffer
+          (git-run-command t nil "ls-files" "-z" "-t" "-c")
+          (git-parse-ls-files status 'added))
+      (with-temp-buffer
+        (git-run-command t nil "diff-index" "-z" "-M" "HEAD")
+        (git-parse-status status)))
+      (with-temp-buffer
+        (git-run-command t nil "ls-files" "-z" "-u")
+        (git-parse-ls-unmerged status))
+      (when (file-readable-p ".git/info/exclude")
+        (with-temp-buffer
+          (git-run-command t nil "ls-files" "-z" "-t" "-o"
+                           "--exclude-from=.git/info/exclude"
+                           (concat "--exclude-per-directory=" git-per-dir-ignore-file))
+          (git-parse-ls-files status 'unknown)))
+    (git-refresh-files)
+    (git-refresh-ewoc-hf status)
+    ; move point to the current file name if any
+    (let ((node (and cur-name (git-find-status-file status cur-name))))
+      (when node (ewoc-goto-node status node)))))
+
+(defun git-status-quit ()
+  "Quit git-status mode."
+  (interactive)
+  (bury-buffer))
+
+;;;; Major Mode
+;;;; ------------------------------------------------------------
+
+(defvar git-status-mode-hook nil
+  "Run after `git-status-mode' is setup.")
+
+(defvar git-status-mode-map nil
+  "Keymap for git major mode.")
+
+(defvar git-status nil
+  "List of all files managed by the git-status mode.")
+
+(unless git-status-mode-map
+  (let ((map (make-keymap))
+        (diff-map (make-sparse-keymap)))
+    (suppress-keymap map)
+    (define-key map " "   'git-next-file)
+    (define-key map "a"   'git-add-file)
+    (define-key map "c"   'git-commit-file)
+    (define-key map "d"    diff-map)
+    (define-key map "="   'git-diff-file)
+    (define-key map "f"   'git-find-file)
+    (define-key map "\r"  'git-find-file)
+    (define-key map "g"   'git-refresh-status)
+    (define-key map "i"   'git-ignore-file)
+    (define-key map "l"   'git-log-file)
+    (define-key map "m"   'git-mark-file)
+    (define-key map "M"   'git-mark-all)
+    (define-key map "n"   'git-next-file)
+    (define-key map "p"   'git-prev-file)
+    (define-key map "q"   'git-status-quit)
+    (define-key map "r"   'git-remove-file)
+    (define-key map "R"   'git-resolve-file)
+    (define-key map "T"   'git-toggle-all-marks)
+    (define-key map "u"   'git-unmark-file)
+    (define-key map "U"   'git-revert-file)
+    (define-key map "v"   'git-view-file)
+    (define-key map "x"   'git-remove-handled)
+    (define-key map "\C-?" 'git-unmark-file-up)
+    (define-key map "\M-\C-?" 'git-unmark-all)
+    ; the diff submap
+    (define-key diff-map "b" 'git-diff-file-base)
+    (define-key diff-map "c" 'git-diff-file-combined)
+    (define-key diff-map "=" 'git-diff-file)
+    (define-key diff-map "e" 'git-diff-file-idiff)
+    (define-key diff-map "E" 'git-find-file-imerge)
+    (define-key diff-map "h" 'git-diff-file-merge-head)
+    (define-key diff-map "m" 'git-diff-file-mine)
+    (define-key diff-map "o" 'git-diff-file-other)
+    (setq git-status-mode-map map)))
+
+;; git mode should only run in the *git status* buffer
+(put 'git-status-mode 'mode-class 'special)
+
+(defun git-status-mode ()
+  "Major mode for interacting with Git.
+Commands:
+\\{git-status-mode-map}"
+  (kill-all-local-variables)
+  (buffer-disable-undo)
+  (setq mode-name "git status"
+        major-mode 'git-status-mode
+        goal-column 17
+        buffer-read-only t)
+  (use-local-map git-status-mode-map)
+  (let ((buffer-read-only nil))
+    (erase-buffer)
+  (let ((status (ewoc-create 'git-fileinfo-prettyprint "" "")))
+    (set (make-local-variable 'git-status) status))
+  (set (make-local-variable 'list-buffers-directory) default-directory)
+  (run-hooks 'git-status-mode-hook)))
+
+(defun git-status (dir)
+  "Entry point into git-status mode."
+  (interactive "DSelect directory: ")
+  (setq dir (git-get-top-dir dir))
+  (if (file-directory-p (concat (file-name-as-directory dir) ".git"))
+      (let ((buffer (create-file-buffer (expand-file-name "*git-status*" dir))))
+        (switch-to-buffer buffer)
+        (cd dir)
+        (git-status-mode)
+        (git-refresh-status)
+        (goto-char (point-min)))
+    (message "%s is not a git working tree." dir)))
+
+(provide 'git)
+;;; git.el ends here
diff --git a/contrib/emacs/vc-git.el b/contrib/emacs/vc-git.el
new file mode 100644 (file)
index 0000000..2453cdc
--- /dev/null
@@ -0,0 +1,135 @@
+;;; vc-git.el --- VC backend for the git version control system
+
+;; Copyright (C) 2006 Alexandre Julliard
+
+;; This program is free software; you can redistribute it and/or
+;; modify it under the terms of the GNU General Public License as
+;; published by the Free Software Foundation; either version 2 of
+;; the License, or (at your option) any later version.
+;;
+;; This program is distributed in the hope that it will be
+;; useful, but WITHOUT ANY WARRANTY; without even the implied
+;; warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+;; PURPOSE.  See the GNU General Public License for more details.
+;;
+;; You should have received a copy of the GNU General Public
+;; License along with this program; if not, write to the Free
+;; Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+;; MA 02111-1307 USA
+
+;;; Commentary:
+
+;; This file contains a VC backend for the git version control
+;; system.
+;;
+;; To install: put this file on the load-path and add GIT to the list
+;; of supported backends in `vc-handled-backends'.
+;;
+;; TODO
+;;  - changelog generation
+;;  - working with revisions other than HEAD
+;;
+
+(defvar git-commits-coding-system 'utf-8
+  "Default coding system for git commits.")
+
+(defun vc-git--run-command-string (file &rest args)
+  "Run a git command on FILE and return its output as string."
+  (let* ((ok t)
+         (str (with-output-to-string
+                (with-current-buffer standard-output
+                  (unless (eq 0 (apply #'call-process "git" nil '(t nil) nil
+                                       (append args (list (file-relative-name file)))))
+                    (setq ok nil))))))
+    (and ok str)))
+
+(defun vc-git--run-command (file &rest args)
+  "Run a git command on FILE, discarding any output."
+  (let ((name (file-relative-name file)))
+    (eq 0 (apply #'call-process "git" nil (get-buffer "*Messages") nil (append args (list name))))))
+
+(defun vc-git-registered (file)
+  "Check whether FILE is registered with git."
+  (with-temp-buffer
+    (let* ((dir (file-name-directory file))
+           (name (file-relative-name file dir)))
+      (when dir (cd dir))
+      (and (eq 0 (call-process "git" nil '(t nil) nil "ls-files" "-c" "-z" "--" name))
+           (let ((str (buffer-string)))
+             (and (> (length str) (length name))
+                  (string= (substring str 0 (1+ (length name))) (concat name "\0"))))))))