summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore3
-rw-r--r--Documentation/Makefile6
-rw-r--r--Documentation/asciidoc.conf10
-rw-r--r--Documentation/git-add.txt5
-rw-r--r--Documentation/git-applypatch.txt2
-rw-r--r--Documentation/git-archimport.txt2
-rw-r--r--Documentation/git-branch.txt2
-rw-r--r--Documentation/git-check-ref-format.txt2
-rw-r--r--Documentation/git-checkout-index.txt69
-rw-r--r--Documentation/git-checkout.txt2
-rw-r--r--Documentation/git-cherry-pick.txt2
-rw-r--r--Documentation/git-cherry.txt2
-rw-r--r--Documentation/git-clone-pack.txt2
-rw-r--r--Documentation/git-clone.txt2
-rw-r--r--Documentation/git-count-objects.txt2
-rw-r--r--Documentation/git-cvsimport.txt6
-rw-r--r--Documentation/git-cvsserver.txt85
-rw-r--r--Documentation/git-daemon.txt2
-rw-r--r--Documentation/git-describe.txt2
-rw-r--r--Documentation/git-diff-stages.txt2
-rw-r--r--Documentation/git-diff.txt2
-rw-r--r--Documentation/git-fetch-pack.txt4
-rw-r--r--Documentation/git-fetch.txt2
-rw-r--r--Documentation/git-format-patch.txt16
-rw-r--r--Documentation/git-fsck-objects.txt15
-rw-r--r--Documentation/git-get-tar-commit-id.txt2
-rw-r--r--Documentation/git-grep.txt2
-rw-r--r--Documentation/git-hash-object.txt2
-rw-r--r--Documentation/git-http-push.txt2
-rw-r--r--Documentation/git-lost-found.txt2
-rw-r--r--Documentation/git-ls-remote.txt2
-rw-r--r--Documentation/git-ls-tree.txt2
-rw-r--r--Documentation/git-mailinfo.txt2
-rw-r--r--Documentation/git-mailsplit.txt2
-rw-r--r--Documentation/git-mv.txt2
-rw-r--r--Documentation/git-name-rev.txt2
-rw-r--r--Documentation/git-pack-objects.txt4
-rw-r--r--Documentation/git-pack-redundant.txt6
-rw-r--r--Documentation/git-patch-id.txt2
-rw-r--r--Documentation/git-peek-remote.txt2
-rw-r--r--Documentation/git-prune-packed.txt2
-rw-r--r--Documentation/git-pull.txt2
-rw-r--r--Documentation/git-push.txt2
-rw-r--r--Documentation/git-rebase.txt2
-rw-r--r--Documentation/git-relink.txt2
-rw-r--r--Documentation/git-repack.txt2
-rw-r--r--Documentation/git-repo-config.txt2
-rw-r--r--Documentation/git-request-pull.txt2
-rw-r--r--Documentation/git-reset.txt2
-rw-r--r--Documentation/git-rev-parse.txt2
-rw-r--r--Documentation/git-revert.txt2
-rw-r--r--Documentation/git-rm.txt5
-rw-r--r--Documentation/git-send-pack.txt2
-rw-r--r--Documentation/git-sh-setup.txt2
-rw-r--r--Documentation/git-shell.txt2
-rw-r--r--Documentation/git-shortlog.txt4
-rw-r--r--Documentation/git-show-branch.txt8
-rw-r--r--Documentation/git-show.txt2
-rw-r--r--Documentation/git-status.txt2
-rw-r--r--Documentation/git-stripspace.txt2
-rw-r--r--Documentation/git-tag.txt2
-rw-r--r--Documentation/git-unpack-objects.txt2
-rw-r--r--Documentation/git-update-ref.txt2
-rw-r--r--Documentation/git-upload-pack.txt2
-rw-r--r--Documentation/git-var.txt2
-rw-r--r--Documentation/git-verify-pack.txt2
-rw-r--r--Documentation/git-verify-tag.txt2
-rw-r--r--Documentation/git-whatchanged.txt2
-rw-r--r--Documentation/git.txt11
-rw-r--r--Documentation/tutorial.txt2
-rw-r--r--Makefile15
-rw-r--r--apply.c5
-rw-r--r--blame.c398
-rw-r--r--cache.h2
-rw-r--r--checkout-index.c125
-rw-r--r--commit.c36
-rw-r--r--commit.h20
-rw-r--r--contrib/emacs/.gitignore1
-rw-r--r--contrib/emacs/Makefile20
-rw-r--r--contrib/emacs/git.el121
-rw-r--r--contrib/emacs/vc-git.el135
-rwxr-xr-xcontrib/git-svn/git-svn.perl76
-rw-r--r--contrib/git-svn/git-svn.txt45
-rw-r--r--date.c4
-rw-r--r--diff-delta.c101
-rw-r--r--entry.c42
-rw-r--r--exec_cmd.c15
-rw-r--r--exec_cmd.h4
-rw-r--r--fsck-objects.c20
-rwxr-xr-xgenerate-cmdlist.sh48
-rwxr-xr-xgit-annotate.perl16
-rwxr-xr-xgit-commit.sh2
-rw-r--r--git-compat-util.h2
-rwxr-xr-xgit-cvsimport.perl27
-rwxr-xr-xgit-diff.sh4
-rwxr-xr-xgit-fetch.sh10
-rwxr-xr-xgit-fmt-merge-msg.perl10
-rwxr-xr-xgit-format-patch.sh56
-rwxr-xr-xgit-merge.sh2
-rwxr-xr-xgit-parse-remote.sh8
-rwxr-xr-xgit-repack.sh1
-rwxr-xr-xgit-resolve.sh2
-rw-r--r--git.c81
-rw-r--r--http-push.c1559
-rw-r--r--http.c8
-rw-r--r--http.h1
-rw-r--r--imap-send.c1359
-rw-r--r--pack-check.c20
-rw-r--r--pack-objects.c38
-rw-r--r--read-tree.c2
-rw-r--r--receive-pack.c10
-rw-r--r--refs.c6
-rw-r--r--repo-config.c5
-rw-r--r--rev-list.c6
-rw-r--r--revision.c174
-rw-r--r--revision.h18
-rw-r--r--run-command.c6
-rw-r--r--run-command.h4
-rw-r--r--send-pack.c4
-rw-r--r--shell.c2
-rw-r--r--t/.gitignore1
-rw-r--r--t/annotate-tests.sh121
-rwxr-xr-xt/t1200-tutorial.sh2
-rwxr-xr-xt/t1300-repo-config.sh8
-rwxr-xr-xt/t2004-checkout-cache-temp.sh212
-rwxr-xr-xt/t8001-annotate.sh85
-rwxr-xr-xt/t8002-blame.sh9
-rw-r--r--upload-pack.c6
128 files changed, 4542 insertions, 943 deletions
diff --git a/.gitignore b/.gitignore
index 5be239a4aa..b4355b9faf 100644
--- a/.gitignore
+++ b/.gitignore
@@ -42,6 +42,7 @@ git-grep
git-hash-object
git-http-fetch
git-http-push
+git-imap-send
git-index-pack
git-init-db
git-local-fetch
@@ -121,6 +122,7 @@ git-write-tree
git-core-*/?*
test-date
test-delta
+common-cmds.h
*.tar.gz
*.dsc
*.deb
@@ -130,3 +132,4 @@ libgit.a
*.o
*.py[co]
config.mak
+git-blame
diff --git a/Documentation/Makefile b/Documentation/Makefile
index a3bca86cb0..f4cbf7e159 100644
--- a/Documentation/Makefile
+++ b/Documentation/Makefile
@@ -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
diff --git a/Documentation/asciidoc.conf b/Documentation/asciidoc.conf
index fa0877d483..7ce71510de 100644
--- a/Documentation/asciidoc.conf
+++ b/Documentation/asciidoc.conf
@@ -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>
diff --git a/Documentation/git-add.txt b/Documentation/git-add.txt
index 7e293834d1..ae24547c8a 100644
--- a/Documentation/git-add.txt
+++ b/Documentation/git-add.txt
@@ -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
------
diff --git a/Documentation/git-applypatch.txt b/Documentation/git-applypatch.txt
index 5b9037de9f..2b1ff1454b 100644
--- a/Documentation/git-applypatch.txt
+++ b/Documentation/git-applypatch.txt
@@ -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
diff --git a/Documentation/git-archimport.txt b/Documentation/git-archimport.txt
index 023d3ae7b9..5a13187a87 100644
--- a/Documentation/git-archimport.txt
+++ b/Documentation/git-archimport.txt
@@ -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
diff --git a/Documentation/git-branch.txt b/Documentation/git-branch.txt
index b1bc8272eb..4cd0cb90ad 100644
--- a/Documentation/git-branch.txt
+++ b/Documentation/git-branch.txt
@@ -3,7 +3,7 @@ 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
--------
diff --git a/Documentation/git-check-ref-format.txt b/Documentation/git-check-ref-format.txt
index f7f84c644e..7dc1bdb6ef 100644
--- a/Documentation/git-check-ref-format.txt
+++ b/Documentation/git-check-ref-format.txt
@@ -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
--------
diff --git a/Documentation/git-checkout-index.txt b/Documentation/git-checkout-index.txt
index 2a1e526c6a..09bd6a5535 100644
--- a/Documentation/git-checkout-index.txt
+++ b/Documentation/git-checkout-index.txt
@@ -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::
diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt
index df9a618674..556e733c9b 100644
--- a/Documentation/git-checkout.txt
+++ b/Documentation/git-checkout.txt
@@ -3,7 +3,7 @@ git-checkout(1)
NAME
----
-git-checkout - Checkout and switch to a branch.
+git-checkout - Checkout and switch to a branch
SYNOPSIS
--------
diff --git a/Documentation/git-cherry-pick.txt b/Documentation/git-cherry-pick.txt
index 4f323fa42a..bfa950ca19 100644
--- a/Documentation/git-cherry-pick.txt
+++ b/Documentation/git-cherry-pick.txt
@@ -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
--------
diff --git a/Documentation/git-cherry.txt b/Documentation/git-cherry.txt
index af87966e51..9a5e37186f 100644
--- a/Documentation/git-cherry.txt
+++ b/Documentation/git-cherry.txt
@@ -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-clone-pack.txt b/Documentation/git-clone-pack.txt
index 39906fc450..09f43eefe4 100644
--- a/Documentation/git-clone-pack.txt
+++ b/Documentation/git-clone-pack.txt
@@ -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
diff --git a/Documentation/git-clone.txt b/Documentation/git-clone.txt
index 684e4bdf69..9ac54c282c 100644
--- a/Documentation/git-clone.txt
+++ b/Documentation/git-clone.txt
@@ -3,7 +3,7 @@ git-clone(1)
NAME
----
-git-clone - Clones a repository.
+git-clone - Clones a repository
SYNOPSIS
diff --git a/Documentation/git-count-objects.txt b/Documentation/git-count-objects.txt
index 36888d98bf..47216f488b 100644
--- a/Documentation/git-count-objects.txt
+++ b/Documentation/git-count-objects.txt
@@ -3,7 +3,7 @@ git-count-objects(1)
NAME
----
-git-count-objects - Reports on unpacked objects.
+git-count-objects - Reports on unpacked objects
SYNOPSIS
--------
diff --git a/Documentation/git-cvsimport.txt b/Documentation/git-cvsimport.txt
index dfe86ceea3..57027b448f 100644
--- a/Documentation/git-cvsimport.txt
+++ b/Documentation/git-cvsimport.txt
@@ -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>::
diff --git a/Documentation/git-cvsserver.txt b/Documentation/git-cvsserver.txt
index 19c9c51cff..4dc13c35db 100644
--- a/Documentation/git-cvsserver.txt
+++ b/Documentation/git-cvsserver.txt
@@ -5,14 +5,12 @@ 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
-----------
@@ -27,48 +25,85 @@ plugin. Most functionality works fine with both of these clients.
LIMITATIONS
-----------
-Currently gitcvs only works over ssh connections.
+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. Put server.pl somewhere useful on the same machine that is hosting your git repos
+
+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
- n.b. you need to ensure each user that is going to invoke server.pl has
- write access to the log file.
+ 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:
-5. On each 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.
- CVS_SERVER should be set to the server.pl script that has been put on the
- remote machine.
+ export CVSROOT=:ext:user@server:/var/git/project.git
+ export CVS_SERVER=git-cvsserver
-6. Clients should now be able to check out modules (where modules are the names
- of branches in git).
- $ cvs co -d mylocaldir master
+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. Create a new project from CVS checkout, giving it repository and module
-2. Context Menu->Team->Share Project...
-3. Enter the repository and module information again and click Finish
-4. The Synchronize view appears. Untick "launch commit wizard" to avoid
-committing the .project file, and select HEAD as the tag to synchronize to.
-Update all incoming changes.
-
-Note that most versions of Eclipse ignore CVS_SERVER (which you can set in
-the Preferences->Team->CVS->ExtConnection pane), so you may have to
-rename, alias or symlink git-cvsserver to 'cvs' on the server.
+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
---------------------
@@ -106,7 +141,7 @@ Authors: Martyn Smith <martyn@catalyst.net.nz>
Documentation
--------------
-Documentation by Martyn Smith <martyn@catalyst.net.nz> and Martin Langhoff <martin@catalyst.net.nz>Matthias Urlichs <smurf@smurf.noris.de>.
+Documentation by Martyn Smith <martyn@catalyst.net.nz> and Martin Langhoff <martin@catalyst.net.nz> Matthias Urlichs <smurf@smurf.noris.de>.
GIT
---
diff --git a/Documentation/git-daemon.txt b/Documentation/git-daemon.txt
index 2cc6075fb0..924a676a6a 100644
--- a/Documentation/git-daemon.txt
+++ b/Documentation/git-daemon.txt
@@ -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
--------
diff --git a/Documentation/git-describe.txt b/Documentation/git-describe.txt
index 0efe82ae1e..7a253eaf28 100644
--- a/Documentation/git-describe.txt
+++ b/Documentation/git-describe.txt
@@ -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
diff --git a/Documentation/git-diff-stages.txt b/Documentation/git-diff-stages.txt
index 28c60fc7e4..3273918627 100644
--- a/Documentation/git-diff-stages.txt
+++ b/Documentation/git-diff-stages.txt
@@ -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
diff --git a/Documentation/git-diff.txt b/Documentation/git-diff.txt
index ca41634022..890931c891 100644
--- a/Documentation/git-diff.txt
+++ b/Documentation/git-diff.txt
@@ -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
diff --git a/Documentation/git-fetch-pack.txt b/Documentation/git-fetch-pack.txt
index b507e9b648..bff9aa6939 100644
--- a/Documentation/git-fetch-pack.txt
+++ b/Documentation/git-fetch-pack.txt
@@ -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
-----------
diff --git a/Documentation/git-fetch.txt b/Documentation/git-fetch.txt
index a67dc340fd..a9e86fd26b 100644
--- a/Documentation/git-fetch.txt
+++ b/Documentation/git-fetch.txt
@@ -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
diff --git a/Documentation/git-format-patch.txt b/Documentation/git-format-patch.txt
index 9ac0636850..7cc7fafc1d 100644
--- a/Documentation/git-format-patch.txt
+++ b/Documentation/git-format-patch.txt
@@ -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
--------
diff --git a/Documentation/git-fsck-objects.txt b/Documentation/git-fsck-objects.txt
index 387b435484..93ce9dcc92 100644
--- a/Documentation/git-fsck-objects.txt
+++ b/Documentation/git-fsck-objects.txt
@@ -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
diff --git a/Documentation/git-get-tar-commit-id.txt b/Documentation/git-get-tar-commit-id.txt
index 30b1fbf6e7..48805b651c 100644
--- a/Documentation/git-get-tar-commit-id.txt
+++ b/Documentation/git-get-tar-commit-id.txt
@@ -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
diff --git a/Documentation/git-grep.txt b/Documentation/git-grep.txt
index bf4b592f48..fbd2394481 100644
--- a/Documentation/git-grep.txt
+++ b/Documentation/git-grep.txt
@@ -3,7 +3,7 @@ git-grep(1)
NAME
----
-git-grep - print lines matching a pattern
+git-grep - Print lines matching a pattern
SYNOPSIS
diff --git a/Documentation/git-hash-object.txt b/Documentation/git-hash-object.txt
index 0924931dc1..04e8d00436 100644
--- a/Documentation/git-hash-object.txt
+++ b/Documentation/git-hash-object.txt
@@ -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
diff --git a/Documentation/git-http-push.txt b/Documentation/git-http-push.txt
index c7066d66f3..7e1f894a92 100644
--- a/Documentation/git-http-push.txt
+++ b/Documentation/git-http-push.txt
@@ -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-lost-found.txt b/Documentation/git-lost-found.txt
index 03156f218b..f52a9d7f68 100644
--- a/Documentation/git-lost-found.txt
+++ b/Documentation/git-lost-found.txt
@@ -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
--------
diff --git a/Documentation/git-ls-remote.txt b/Documentation/git-ls-remote.txt
index 66fe60f998..ae4c1a250e 100644
--- a/Documentation/git-ls-remote.txt
+++ b/Documentation/git-ls-remote.txt
@@ -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
diff --git a/Documentation/git-ls-tree.txt b/Documentation/git-ls-tree.txt
index b92a8b2db1..5bf6d8b613 100644
--- a/Documentation/git-ls-tree.txt
+++ b/Documentation/git-ls-tree.txt
@@ -3,7 +3,7 @@ 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
diff --git a/Documentation/git-mailinfo.txt b/Documentation/git-mailinfo.txt
index 8890754740..ea0a06557f 100644
--- a/Documentation/git-mailinfo.txt
+++ b/Documentation/git-mailinfo.txt
@@ -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
diff --git a/Documentation/git-mailsplit.txt b/Documentation/git-mailsplit.txt
index e0703e9dfa..209e36bacb 100644
--- a/Documentation/git-mailsplit.txt
+++ b/Documentation/git-mailsplit.txt
@@ -3,7 +3,7 @@ git-mailsplit(1)
NAME
----
-git-mailsplit - Totally braindamaged mbox splitter program.
+git-mailsplit - Totally braindamaged mbox splitter program
SYNOPSIS
--------
diff --git a/Documentation/git-mv.txt b/Documentation/git-mv.txt
index d242b39654..207c43a631 100644
--- a/Documentation/git-mv.txt
+++ b/Documentation/git-mv.txt
@@ -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
diff --git a/Documentation/git-name-rev.txt b/Documentation/git-name-rev.txt
index e37b0b8f97..68707083be 100644
--- a/Documentation/git-name-rev.txt
+++ b/Documentation/git-name-rev.txt
@@ -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
diff --git a/Documentation/git-pack-objects.txt b/Documentation/git-pack-objects.txt
index 4cb2e83faa..4991f88c92 100644
--- a/Documentation/git-pack-objects.txt
+++ b/Documentation/git-pack-objects.txt
@@ -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]
diff --git a/Documentation/git-pack-redundant.txt b/Documentation/git-pack-redundant.txt
index 9fe86aef98..8fb0659438 100644
--- a/Documentation/git-pack-redundant.txt
+++ b/Documentation/git-pack-redundant.txt
@@ -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]
diff --git a/Documentation/git-patch-id.txt b/Documentation/git-patch-id.txt
index c8bd197779..723b8ccbf6 100644
--- a/Documentation/git-patch-id.txt
+++ b/Documentation/git-patch-id.txt
@@ -3,7 +3,7 @@ git-patch-id(1)
NAME
----
-git-patch-id - Generate a patch ID.
+git-patch-id - Generate a patch ID
SYNOPSIS
--------
diff --git a/Documentation/git-peek-remote.txt b/Documentation/git-peek-remote.txt
index 915d3f8a06..a00060c507 100644
--- a/Documentation/git-peek-remote.txt
+++ b/Documentation/git-peek-remote.txt
@@ -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
diff --git a/Documentation/git-prune-packed.txt b/Documentation/git-prune-packed.txt
index 37c53a91de..234882685d 100644
--- a/Documentation/git-prune-packed.txt
+++ b/Documentation/git-prune-packed.txt
@@ -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]
diff --git a/Documentation/git-pull.txt b/Documentation/git-pull.txt
index 20175f4b9a..51577fcbe6 100644
--- a/Documentation/git-pull.txt
+++ b/Documentation/git-pull.txt
@@ -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
diff --git a/Documentation/git-push.txt b/Documentation/git-push.txt
index 6f4a48a109..d5b5ca167c 100644
--- a/Documentation/git-push.txt
+++ b/Documentation/git-push.txt
@@ -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
diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt
index f037d1280e..4d5b546db1 100644
--- a/Documentation/git-rebase.txt
+++ b/Documentation/git-rebase.txt
@@ -3,7 +3,7 @@ git-rebase(1)
NAME
----
-git-rebase - Rebase local commits to new upstream head.
+git-rebase - Rebase local commits to new upstream head
SYNOPSIS
--------
diff --git a/Documentation/git-relink.txt b/Documentation/git-relink.txt
index 62405358fc..aca60120c8 100644
--- a/Documentation/git-relink.txt
+++ b/Documentation/git-relink.txt
@@ -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
--------
diff --git a/Documentation/git-repack.txt b/Documentation/git-repack.txt
index 6c0f792dad..d2f9a44382 100644
--- a/Documentation/git-repack.txt
+++ b/Documentation/git-repack.txt
@@ -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]
diff --git a/Documentation/git-repo-config.txt b/Documentation/git-repo-config.txt
index 00efde5f0f..26759a8071 100644
--- a/Documentation/git-repo-config.txt
+++ b/Documentation/git-repo-config.txt
@@ -3,7 +3,7 @@ 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
diff --git a/Documentation/git-request-pull.txt b/Documentation/git-request-pull.txt
index 2463ec91d5..478a5fd6b7 100644
--- a/Documentation/git-request-pull.txt
+++ b/Documentation/git-request-pull.txt
@@ -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
--------
diff --git a/Documentation/git-reset.txt b/Documentation/git-reset.txt
index b4e737e660..b7b9798bf9 100644
--- a/Documentation/git-reset.txt
+++ b/Documentation/git-reset.txt
@@ -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
--------
diff --git a/Documentation/git-rev-parse.txt b/Documentation/git-rev-parse.txt
index 29b578978a..8b95df0c6e 100644
--- a/Documentation/git-rev-parse.txt
+++ b/Documentation/git-rev-parse.txt
@@ -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
diff --git a/Documentation/git-revert.txt b/Documentation/git-revert.txt
index e27c6808b3..71f7815d65 100644
--- a/Documentation/git-revert.txt
+++ b/Documentation/git-revert.txt
@@ -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
index 401bfb2d9c..c9c3088424 100644
--- a/Documentation/git-rm.txt
+++ b/Documentation/git-rm.txt
@@ -3,7 +3,7 @@ git-rm(1)
NAME
----
-git-rm - Remove files from the working tree and from the index.
+git-rm - Remove files from the working tree and from the index
SYNOPSIS
--------
@@ -74,6 +74,9 @@ git-rm -f git-*.sh::
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
------
diff --git a/Documentation/git-send-pack.txt b/Documentation/git-send-pack.txt
index 577f06a214..08e0705303 100644
--- a/Documentation/git-send-pack.txt
+++ b/Documentation/git-send-pack.txt
@@ -3,7 +3,7 @@ git-send-pack(1)
NAME
----
-git-send-pack - Push missing objects packed.
+git-send-pack - Push missing objects packed
SYNOPSIS
diff --git a/Documentation/git-sh-setup.txt b/Documentation/git-sh-setup.txt
index 6ef59acf50..6742c9bfcf 100644
--- a/Documentation/git-sh-setup.txt
+++ b/Documentation/git-sh-setup.txt
@@ -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
--------
diff --git a/Documentation/git-shell.txt b/Documentation/git-shell.txt
index 3f4d804cca..cc4266d83b 100644
--- a/Documentation/git-shell.txt
+++ b/Documentation/git-shell.txt
@@ -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
-----------
diff --git a/Documentation/git-shortlog.txt b/Documentation/git-shortlog.txt
index 65ca77fbf6..54fb922ba9 100644
--- a/Documentation/git-shortlog.txt
+++ b/Documentation/git-shortlog.txt
@@ -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
-----------
diff --git a/Documentation/git-show-branch.txt b/Documentation/git-show-branch.txt
index 7b1a9c9875..d3b6e620a8 100644
--- a/Documentation/git-show-branch.txt
+++ b/Documentation/git-show-branch.txt
@@ -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
-----------
diff --git a/Documentation/git-show.txt b/Documentation/git-show.txt
index 9c359a448e..2b4df3f96f 100644
--- a/Documentation/git-show.txt
+++ b/Documentation/git-show.txt
@@ -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
diff --git a/Documentation/git-status.txt b/Documentation/git-status.txt
index 753fc0866d..e446f4812e 100644
--- a/Documentation/git-status.txt
+++ b/Documentation/git-status.txt
@@ -3,7 +3,7 @@ git-status(1)
NAME
----
-git-status - Show working tree status.
+git-status - Show working tree status
SYNOPSIS
diff --git a/Documentation/git-stripspace.txt b/Documentation/git-stripspace.txt
index 528a1b6ce3..3a03dd0410 100644
--- a/Documentation/git-stripspace.txt
+++ b/Documentation/git-stripspace.txt
@@ -3,7 +3,7 @@ git-stripspace(1)
NAME
----
-git-stripspace - Filter out empty lines.
+git-stripspace - Filter out empty lines
SYNOPSIS
diff --git a/Documentation/git-tag.txt b/Documentation/git-tag.txt
index e1c76c600d..45476c2e41 100644
--- a/Documentation/git-tag.txt
+++ b/Documentation/git-tag.txt
@@ -3,7 +3,7 @@ git-tag(1)
NAME
----
-git-tag - Create a tag object signed with GPG
+git-tag - Create a tag object signed with GPG
SYNOPSIS
diff --git a/Documentation/git-unpack-objects.txt b/Documentation/git-unpack-objects.txt
index 31ea34d229..18280628a1 100644
--- a/Documentation/git-unpack-objects.txt
+++ b/Documentation/git-unpack-objects.txt
@@ -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
diff --git a/Documentation/git-update-ref.txt b/Documentation/git-update-ref.txt
index 69715aa061..475237f19e 100644
--- a/Documentation/git-update-ref.txt
+++ b/Documentation/git-update-ref.txt
@@ -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
-----------
diff --git a/Documentation/git-upload-pack.txt b/Documentation/git-upload-pack.txt
index 3d8f8ef667..4795e98754 100644
--- a/Documentation/git-upload-pack.txt
+++ b/Documentation/git-upload-pack.txt
@@ -3,7 +3,7 @@ git-upload-pack(1)
NAME
----
-git-upload-pack - Send missing objects packed.
+git-upload-pack - Send missing objects packed
SYNOPSIS
diff --git a/Documentation/git-var.txt b/Documentation/git-var.txt
index c22d34f5fb..90cb157be5 100644
--- a/Documentation/git-var.txt
+++ b/Documentation/git-var.txt
@@ -8,7 +8,7 @@ git-var - Print the git users identity
SYNOPSIS
--------
-git-var [ -l | <variable> ]
+'git-var' [ -l | <variable> ]
DESCRIPTION
-----------
diff --git a/Documentation/git-verify-pack.txt b/Documentation/git-verify-pack.txt
index d032280e7c..4962d6975f 100644
--- a/Documentation/git-verify-pack.txt
+++ b/Documentation/git-verify-pack.txt
@@ -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
diff --git a/Documentation/git-verify-tag.txt b/Documentation/git-verify-tag.txt
index b8a73c47af..0f9bdb58dc 100644
--- a/Documentation/git-verify-tag.txt
+++ b/Documentation/git-verify-tag.txt
@@ -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
--------
diff --git a/Documentation/git-whatchanged.txt b/Documentation/git-whatchanged.txt
index 6c150b0264..f02f939baa 100644
--- a/Documentation/git-whatchanged.txt
+++ b/Documentation/git-whatchanged.txt
@@ -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
diff --git a/Documentation/git.txt b/Documentation/git.txt
index 2d0ca9d8ed..8610d36c49 100644
--- a/Documentation/git.txt
+++ b/Documentation/git.txt
@@ -20,15 +20,16 @@ brings your stuff to the plumbing).
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.
diff --git a/Documentation/tutorial.txt b/Documentation/tutorial.txt
index 66680d76bd..fa79b016c7 100644
--- a/Documentation/tutorial.txt
+++ b/Documentation/tutorial.txt
@@ -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
diff --git a/Makefile b/Makefile
index ab2890d638..0bdf03b7de 100644
--- a/Makefile
+++ b/Makefile
@@ -165,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-merge-tree$X git-blame$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)
@@ -452,10 +452,13 @@ all:
strip: $(PROGRAMS) git$X
$(STRIP) $(STRIP_OPTS) $(PROGRAMS) git$X
-git$X: git.c $(LIB_FILE)
+git$X: git.c common-cmds.h $(LIB_FILE)
$(CC) -DGIT_VERSION='"$(GIT_VERSION)"' \
$(ALL_CFLAGS) -o $@ $(filter %.c,$^) $(LIB_FILE) $(LIBS)
+common-cmds.h: Documentation/git-*.txt
+ ./generate-cmdlist.sh > $@
+
$(patsubst %.sh,%,$(SCRIPT_SH)) : % : %.sh
rm -f $@
sed -e '1s|#!.*/sh|#!$(SHELL_PATH_SQ)|' \
@@ -522,11 +525,13 @@ git-ssh-upload$X: rsh.o
git-ssh-pull$X: rsh.o fetch.o
git-ssh-push$X: rsh.o
+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: http.o http-push.o $(LIB_FILE)
+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)
@@ -564,7 +569,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
@@ -612,7 +617,7 @@ rpm: dist
clean:
rm -f *.o mozilla-sha1/*.o arm/*.o ppc/*.o compat/*.o $(LIB_FILE)
rm -f $(ALL_PROGRAMS) git$X
- rm -f *.spec *.pyc *.pyo */*.pyc */*.pyo
+ rm -f *.spec *.pyc *.pyo */*.pyc */*.pyo common-cmds.h
rm -rf $(GIT_TARNAME)
rm -f $(GIT_TARNAME).tar.gz git-core_$(GIT_VERSION)-*.tar.gz
$(MAKE) -C Documentation/ clean
diff --git a/apply.c b/apply.c
index c369966867..179b3bbd00 100644
--- a/apply.c
+++ b/apply.c
@@ -651,7 +651,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))
@@ -1402,7 +1402,8 @@ 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;
}
diff --git a/blame.c b/blame.c
index 7308c36d23..1fb507028b 100644
--- a/blame.c
+++ b/blame.c
@@ -5,6 +5,7 @@
#include <assert.h>
#include <time.h>
#include <sys/time.h>
+#include <math.h>
#include "cache.h"
#include "refs.h"
@@ -13,12 +14,20 @@
#include "tree.h"
#include "blob.h"
#include "diff.h"
+#include "diffcore.h"
#include "revision.h"
#define DEBUG 0
-struct commit **blame_lines;
-int num_blame_lines;
+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;
@@ -26,7 +35,9 @@ struct util_info {
char *buf;
unsigned long size;
int num_lines;
-// const char* path;
+ const char* pathname;
+
+ void* topo_data;
};
struct chunk {
@@ -84,7 +95,7 @@ static struct patch *get_patch(struct commit *commit, struct commit *other)
die("write failed: %s", strerror(errno));
close(fd);
- sprintf(diff_cmd, "diff -u0 %s %s", tmp_path1, tmp_path2);
+ 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));
@@ -226,6 +237,7 @@ static void print_patch(struct patch *p)
}
}
+#if DEBUG
/* For debugging only */
static void print_map(struct commit *cmit, struct commit *other)
{
@@ -259,6 +271,7 @@ static void print_map(struct commit *cmit, struct commit *other)
printf("\n");
}
}
+#endif
// p is a patch from commit to other.
static void fill_line_map(struct commit *commit, struct commit *other,
@@ -332,25 +345,34 @@ static int map_line(struct commit *commit, int line)
return info->line_map[line];
}
-static int fill_util_info(struct commit *commit, const char *path)
+static struct util_info* get_util(struct commit *commit)
{
- struct util_info *util;
- if (commit->object.util)
- return 0;
+ 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;
+}
- if (get_blob_sha1(commit->tree, path, util->sha1)) {
- free(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 {
- util->buf = NULL;
- util->size = 0;
- util->line_map = NULL;
- util->num_lines = -1;
- commit->object.util = util;
+ else
return 0;
- }
}
static void alloc_line_map(struct commit *commit)
@@ -379,18 +401,18 @@ static void alloc_line_map(struct commit *commit)
static void init_first_commit(struct commit* commit, const char* filename)
{
- struct util_info* util;
+ struct util_info* util = commit->object.util;
int i;
- if (fill_util_info(commit, filename))
+ util->pathname = filename;
+ if (fill_util_info(commit))
die("fill_util_info failed");
alloc_line_map(commit);
util = commit->object.util;
- num_blame_lines = util->num_lines;
- for (i = 0; i < num_blame_lines; i++)
+ for (i = 0; i < util->num_lines; i++)
util->line_map[i] = i;
}
@@ -412,6 +434,9 @@ static void process_commits(struct rev_info *rev, const char *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;
@@ -441,7 +466,7 @@ static void process_commits(struct rev_info *rev, const char *path,
if(num_parents == 0)
*initial = commit;
- if(fill_util_info(commit, path))
+ if (fill_util_info(commit))
continue;
alloc_line_map(commit);
@@ -459,7 +484,7 @@ static void process_commits(struct rev_info *rev, const char *path,
printf("parent: %s\n",
sha1_to_hex(parent->object.sha1));
- if(fill_util_info(parent, path)) {
+ if (fill_util_info(parent)) {
num_parents--;
continue;
}
@@ -499,56 +524,329 @@ static void process_commits(struct rev_info *rev, const char *path,
} 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;
- int num_args;
- const char* args[10];
- struct rev_info rev;
- setup_git_directory();
+ const char *filename = NULL, *commit = NULL;
+ char filename_buf[256];
+ int sha1_len = 8;
+ int compability = 0;
+ int options = 1;
+ struct commit* start_commit;
- if (argc != 3)
- die("Usage: blame commit-ish file");
+ const char* args[10];
+ struct rev_info rev;
+ struct commit_info ci;
+ const char *buf;
+ int max_digits;
- filename = argv[2];
+ const char* prefix = setup_git_directory();
- {
- struct commit* commit;
- if (get_sha1(argv[1], sha1))
- die("get_sha1 failed");
- commit = lookup_commit_reference(sha1);
+ 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 (fill_util_info(commit, filename)) {
- printf("%s not found in %s\n", filename, argv[1]);
- return 1;
+ if(!options) {
+ if(!filename)
+ filename = argv[i];
+ else if(!commit)
+ commit = argv[i];
+ else
+ usage(blame_usage);
}
}
- num_args = 0;
- args[num_args++] = NULL;
- args[num_args++] = "--topo-order";
- args[num_args++] = "--remove-empty";
- args[num_args++] = argv[1];
- args[num_args++] = "--";
- args[num_args++] = filename;
- args[num_args] = NULL;
+ 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;
+ }
+
- setup_revisions(num_args, args, &rev, "HEAD");
+ 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.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;
+
for (i = 0; i < num_blame_lines; i++) {
struct commit *c = blame_lines[i];
+ struct util_info* u;
+
if (!c)
c = initial;
- printf("%d %.8s\n", i, sha1_to_hex(c->object.sha1));
-// printf("%d %s\n", i, find_unique_abbrev(blame_lines[i]->object.sha1, 6));
+ 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
+ printf(" %s (%-15.15s %10s %*d) ", u->pathname,
+ 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) {
diff --git a/cache.h b/cache.h
index 8dc1de16e4..1f962809b0 100644
--- a/cache.h
+++ b/cache.h
@@ -262,7 +262,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;
diff --git a/checkout-index.c b/checkout-index.c
index 957b4a86b0..7b78715417 100644
--- a/checkout-index.c
+++ b/checkout-index.c
@@ -22,6 +22,10 @@
*
* 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
@@ -33,10 +37,16 @@
* 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 = 0;
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,6 +174,7 @@ 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);
@@ -156,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] == '-')
@@ -174,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.
*/
@@ -191,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();
diff --git a/commit.c b/commit.c
index 06d5439152..eb42d517a4 100644
--- a/commit.c
+++ b/commit.c
@@ -569,11 +569,29 @@ 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, 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;
struct commit_list ** pptr = list;
@@ -596,7 +614,7 @@ void sort_in_topological_order(struct commit_list ** list, int lifo)
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 +624,8 @@ void sort_in_topological_order(struct commit_list ** list, int lifo)
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,7 +642,7 @@ void sort_in_topological_order(struct commit_list ** list, int lifo)
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;
@@ -637,15 +655,15 @@ void sort_in_topological_order(struct commit_list ** list, int 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.
@@ -667,7 +685,7 @@ void sort_in_topological_order(struct commit_list ** list, int lifo)
*pptr = work_node->list_item;
pptr = &(*pptr)->next;
*pptr = NULL;
- work_item->object.util = NULL;
+ setter(work_item, NULL);
}
free(nodes);
}
diff --git a/commit.h b/commit.h
index 70a7c75e65..98682b232a 100644
--- a/commit.h
+++ b/commit.h
@@ -65,15 +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.
*/
+
+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 */
diff --git a/contrib/emacs/.gitignore b/contrib/emacs/.gitignore
new file mode 100644
index 0000000000..c531d9867f
--- /dev/null
+++ b/contrib/emacs/.gitignore
@@ -0,0 +1 @@
+*.elc
diff --git a/contrib/emacs/Makefile b/contrib/emacs/Makefile
new file mode 100644
index 0000000000..d3619db510
--- /dev/null
+++ b/contrib/emacs/Makefile
@@ -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
index 8f234772b0..5135e361be 100644
--- a/contrib/emacs/git.el
+++ b/contrib/emacs/git.el
@@ -39,13 +39,11 @@
;; - hook into file save (after-save-hook)
;; - diff against other branch
;; - renaming files from the status buffer
-;; - support for appending signed-off-by
;; - creating tags
;; - fetch/pull
;; - switching branches
;; - revlist browser
;; - git-show-branch browser
-;; - customize support
;; - menus
;;
@@ -53,65 +51,92 @@
(require 'ewoc)
-;;;; Faces
+;;;; 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 `add-log-full-name' and then `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 `add-log-mailing-address' and then `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.")
+ "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.")
+ "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.")
+ "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.")
+ "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.")
+ "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.")
+ "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.")
+ "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.")
+ "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.")
-
-(defvar git-committer-name nil
- "*User name to use for commits.
-If not set, fall back to `add-log-full-name' and then `user-full-name'.")
-
-(defvar git-committer-email nil
- "*Email address to use for commits.
-If not set, fall back to `add-log-mailing-address' and then `user-mail-address'.")
-
-(defvar git-commits-coding-system 'utf-8
- "Default coding system for git commits.")
-
-(defconst git-log-msg-separator "--- log message follows this line ---")
-
-(defconst git-per-dir-ignore-file ".gitignore"
- "Name of the per-directory ignore file.")
+ "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))
@@ -213,14 +238,19 @@ If not set, fall back to `add-log-mailing-address' and then `user-mail-address'.
"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)))
+ (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 (expand-file-name git-per-dir-ignore-file dir)))
+ (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))))
+ (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
@@ -272,7 +302,7 @@ If not set, fall back to `add-log-mailing-address' and then `user-mail-address'.
(with-current-buffer buffer
(goto-char (point-min))
(if
- (setq log-start (re-search-forward (concat "^" git-log-msg-separator "\n") nil t))
+ (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))
@@ -388,9 +418,9 @@ If not set, fall back to `add-log-mailing-address' and then `user-mail-address'.
(propertize
(if (or (not old-perm)
(not new-perm)
- (eq 0 (logand #O111 (logxor old-perm new-perm))))
+ (eq 0 (logand ?\111 (logxor old-perm new-perm))))
" "
- (if (eq 0 (logand #O111 old-perm)) "+x" "-x"))
+ (if (eq 0 (logand ?\111 old-perm)) "+x" "-x"))
'face 'git-permission-face))
(defun git-fileinfo-prettyprint (info)
@@ -787,7 +817,8 @@ If not set, fall back to `add-log-mailing-address' and then `user-mail-address'.
(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))
+ (dir default-directory)
+ (sign-off git-append-signed-off-by))
(with-current-buffer buffer
(when (eq 0 (buffer-size))
(cd dir)
@@ -804,9 +835,18 @@ If not set, fall back to `add-log-mailing-address' and then `user-mail-address'.
'face 'git-header-face)
(propertize git-log-msg-separator 'face 'git-separator-face)
"\n")
- (when (and merge-heads (file-readable-p ".git/MERGE_MSG"))
- (insert-file-contents ".git/MERGE_MSG"))))
- (log-edit #'git-do-commit nil #'git-log-edit-files buffer)))
+ (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."
@@ -891,7 +931,7 @@ If not set, fall back to `add-log-mailing-address' and then `user-mail-address'.
(define-key map "d" diff-map)
(define-key map "=" 'git-diff-file)
(define-key map "f" 'git-find-file)
- (define-key map [RET] '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)
@@ -937,6 +977,7 @@ Commands:
(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)
@@ -946,8 +987,8 @@ Commands:
(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)
- (git-status-mode)
(cd dir)
+ (git-status-mode)
(git-refresh-status)
(goto-char (point-min)))
(message "%s is not a git working tree." dir)))
diff --git a/contrib/emacs/vc-git.el b/contrib/emacs/vc-git.el
new file mode 100644
index 0000000000..2453cdcfae
--- /dev/null
+++ b/contrib/emacs/vc-git.el
@@ -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"))))))))
+
+(defun vc-git-state (file)
+ "git-specific version of `vc-state'."
+ (let ((diff (vc-git--run-command-string file "diff-index" "-z" "HEAD" "--")))
+ (if (and diff (string-match ":[0-7]\\{6\\} [0-7]\\{6\\} [0-9a-f]\\{40\\} [0-9a-f]\\{40\\} [ADMU]\0[^\0]+\0" diff))
+ 'edited
+ 'up-to-date)))
+
+(defun vc-git-workfile-version (file)
+ "git-specific version of `vc-workfile-version'."
+ (let ((str (with-output-to-string
+ (with-current-buffer standard-output
+ (call-process "git" nil '(t nil) nil "symbolic-ref" "HEAD")))))
+ (if (string-match "^\\(refs/heads/\\)?\\(.+\\)$" str)
+ (match-string 2 str)
+ str)))
+
+(defun vc-git-revert (file &optional contents-done)
+ "Revert FILE to the version stored in the git repository."
+ (if contents-done
+ (vc-git--run-command file "update-index" "--")
+ (vc-git--run-command file "checkout" "HEAD")))
+
+(defun vc-git-checkout-model (file)
+ 'implicit)
+
+(defun vc-git-workfile-unchanged-p (file)
+ (let ((sha1 (vc-git--run-command-string file "hash-object" "--"))
+ (head (vc-git--run-command-string file "ls-tree" "-z" "HEAD" "--")))
+ (and head
+ (string-match "[0-7]\\{6\\} blob \\([0-9a-f]\\{40\\}\\)\t[^\0]+\0" head)
+ (string= (car (split-string sha1 "\n")) (match-string 1 head)))))
+
+(defun vc-git-register (file &optional rev comment)
+ "Register FILE into the git version-control system."
+ (vc-git--run-command file "update-index" "--add" "--"))
+
+(defun vc-git-print-log (file)
+ (let ((name (file-relative-name file))
+ (coding-system-for-read git-commits-coding-system))
+ (vc-do-command nil 'async "git" name "rev-list" "--pretty" "HEAD" "--")))
+
+(defun vc-git-diff (file &optional rev1 rev2)
+ (let ((name (file-relative-name file)))
+ (if (and rev1 rev2)
+ (vc-do-command "*vc-diff*" 0 "git" name "diff-tree" "-p" rev1 rev2 "--")
+ (vc-do-command "*vc-diff*" 0 "git" name "diff-index" "-p" (or rev1 "HEAD") "--"))
+ ; git-diff-index doesn't set exit status like diff does
+ (if (vc-git-workfile-unchanged-p file) 0 1)))
+
+(defun vc-git-checkin (file rev comment)
+ (let ((coding-system-for-write git-commits-coding-system))
+ (vc-git--run-command file "commit" "-m" comment "--only" "--")))
+
+(defun vc-git-checkout (file &optional editable rev destfile)
+ (vc-git--run-command file "checkout" (or rev "HEAD")))
+
+(defun vc-git-annotate-command (file buf &optional rev)
+ ; FIXME: rev is ignored
+ (let ((name (file-relative-name file)))
+ (call-process "git" nil buf nil "annotate" name)))
+
+(defun vc-git-annotate-time ()
+ (and (re-search-forward "[0-9a-f]+\t(.*\t\\([0-9]+\\)-\\([0-9]+\\)-\\([0-9]+\\) \\([0-9]+\\):\\([0-9]+\\):\\([0-9]+\\) \\([-+0-9]+\\)\t[0-9]+)" nil t)
+ (vc-annotate-convert-time
+ (apply #'encode-time (mapcar (lambda (match) (string-to-number (match-string match))) '(6 5 4 3 2 1 7))))))
+
+;; Not really useful since we can't do anything with the revision yet
+;;(defun vc-annotate-extract-revision-at-line ()
+;; (save-excursion
+;; (move-beginning-of-line 1)
+;; (and (looking-at "[0-9a-f]+")
+;; (buffer-substring (match-beginning 0) (match-end 0)))))
+
+(provide 'vc-git)
diff --git a/contrib/git-svn/git-svn.perl b/contrib/git-svn/git-svn.perl
index 3c860e458c..cf233ef6ed 100755
--- a/contrib/git-svn/git-svn.perl
+++ b/contrib/git-svn/git-svn.perl
@@ -30,6 +30,7 @@ my $sha1_short = qr/[a-f\d]{4,40}/;
my ($_revision,$_stdin,$_no_ignore_ext,$_no_stop_copy,$_help,$_rmdir,$_edit,
$_find_copies_harder, $_l, $_version, $_upgrade, $_authors);
my (@_branch_from, %tree_map, %users);
+my $_svn_co_url_revs;
my %fc_opts = ( 'no-ignore-externals' => \$_no_ignore_ext,
'branch|b=s' => \@_branch_from,
@@ -77,7 +78,7 @@ usage(0) if $_help;
version() if $_version;
usage(1) unless defined $cmd;
load_authors() if $_authors;
-svn_check_ignore_externals();
+svn_compat_check();
$cmd{$cmd}->[0]->(@ARGV);
exit 0;
@@ -154,7 +155,7 @@ sub rebuild {
# if we merged or otherwise started elsewhere, this is
# how we break out of it
next if (defined $SVN_UUID && ($uuid ne $SVN_UUID));
- next if (defined $SVN_URL && ($url ne $SVN_URL));
+ next if (defined $SVN_URL && defined $url && ($url ne $SVN_URL));
print "r$rev = $c\n";
unless (defined $latest) {
@@ -162,7 +163,8 @@ sub rebuild {
croak "SVN repository location required: $url\n";
}
$SVN_URL ||= $url;
- $SVN_UUID ||= setup_git_svn();
+ $SVN_UUID ||= $uuid;
+ setup_git_svn();
$latest = $rev;
}
assert_revision_eq_or_unknown($rev, $c);
@@ -171,9 +173,7 @@ sub rebuild {
}
close $rev_list or croak $?;
if (!chdir $SVN_WC) {
- my @svn_co = ('svn','co',"-r$latest");
- push @svn_co, '--ignore-externals' unless $_no_ignore_ext;
- sys(@svn_co, $SVN_URL, $SVN_WC);
+ svn_cmd_checkout($SVN_URL, $latest, $SVN_WC);
chdir $SVN_WC or croak $!;
}
@@ -222,14 +222,14 @@ sub fetch {
my $base = shift @$svn_log or croak "No base revision!\n";
my $last_commit = undef;
unless (-d $SVN_WC) {
- my @svn_co = ('svn','co',"-r$base->{revision}");
- push @svn_co,'--ignore-externals' unless $_no_ignore_ext;
- sys(@svn_co, $SVN_URL, $SVN_WC);
+ svn_cmd_checkout($SVN_URL,$base->{revision},$SVN_WC);
chdir $SVN_WC or croak $!;
+ read_uuid();
$last_commit = git_commit($base, @parents);
assert_svn_wc_clean($base->{revision}, $last_commit);
} else {
chdir $SVN_WC or croak $!;
+ read_uuid();
$last_commit = file_to_s("$REV_DIR/$base->{revision}");
}
my @svn_up = qw(svn up);
@@ -275,7 +275,9 @@ sub commit {
fetch();
chdir $SVN_WC or croak $!;
- my $svn_current_rev = svn_info('.')->{'Last Changed Rev'};
+ my $info = svn_info('.');
+ read_uuid($info);
+ my $svn_current_rev = $info->{'Last Changed Rev'};
foreach my $c (@revs) {
my $mods = svn_checkout_tree($svn_current_rev, $c);
if (scalar @$mods == 0) {
@@ -314,6 +316,14 @@ sub show_ignore {
########################### utility functions #########################
+sub read_uuid {
+ return if $SVN_UUID;
+ my $info = shift || svn_info('.');
+ $SVN_UUID = $info->{'Repository UUID'} or
+ croak "Repository UUID unreadable\n";
+ s_to_file($SVN_UUID,"$GIT_DIR/$GIT_SVN/info/uuid");
+}
+
sub setup_git_svn {
defined $SVN_URL or croak "SVN repository location required\n";
unless (-d $GIT_DIR) {
@@ -323,14 +333,10 @@ sub setup_git_svn {
mkpath(["$GIT_DIR/$GIT_SVN/info"]);
mkpath([$REV_DIR]);
s_to_file($SVN_URL,"$GIT_DIR/$GIT_SVN/info/url");
- $SVN_UUID = svn_info($SVN_URL)->{'Repository UUID'} or
- croak "Repository UUID unreadable\n";
- s_to_file($SVN_UUID,"$GIT_DIR/$GIT_SVN/info/uuid");
open my $fd, '>>', "$GIT_DIR/$GIT_SVN/info/exclude" or croak $!;
print $fd '.svn',"\n";
close $fd or croak $!;
- return $SVN_UUID;
}
sub assert_svn_wc_clean {
@@ -860,7 +866,6 @@ sub git_commit {
my ($log_msg, @parents) = @_;
assert_revision_unknown($log_msg->{revision});
my $out_fh = IO::File->new_tmpfile or croak $!;
- $SVN_UUID ||= svn_info('.')->{'Repository UUID'};
map_tree_joins() if (@_branch_from && !%tree_map);
@@ -922,7 +927,16 @@ sub git_commit {
}
my @update_ref = ('git-update-ref',"refs/remotes/$GIT_SVN",$commit);
if (my $primary_parent = shift @exec_parents) {
- push @update_ref, $primary_parent;
+ $pid = fork;
+ defined $pid or croak $!;
+ if (!$pid) {
+ close STDERR;
+ close STDOUT;
+ exec 'git-rev-parse','--verify',
+ "refs/remotes/$GIT_SVN^0";
+ }
+ waitpid $pid, 0;
+ push @update_ref, $primary_parent unless $?;
}
sys(@update_ref);
sys('git-update-ref',"$GIT_SVN/revs/$log_msg->{revision}",$commit);
@@ -995,13 +1009,37 @@ sub safe_qx {
return wantarray ? @ret : join('',@ret);
}
-sub svn_check_ignore_externals {
- return if $_no_ignore_ext;
- unless (grep /ignore-externals/,(safe_qx(qw(svn co -h)))) {
+sub svn_compat_check {
+ my @co_help = safe_qx(qw(svn co -h));
+ unless (grep /ignore-externals/,@co_help) {
print STDERR "W: Installed svn version does not support ",
"--ignore-externals\n";
$_no_ignore_ext = 1;
}
+ if (grep /usage: checkout URL\[\@REV\]/,@co_help) {
+ $_svn_co_url_revs = 1;
+ }
+
+ # I really, really hope nobody hits this...
+ unless (grep /stop-on-copy/, (safe_qx(qw(svn log -h)))) {
+ print STDERR <<'';
+W: The installed svn version does not support the --stop-on-copy flag in
+ the log command.
+ Lets hope the directory you're tracking is not a branch or tag
+ and was never moved within the repository...
+
+ $_no_stop_copy = 1;
+ }
+}
+
+# *sigh*, new versions of svn won't honor -r<rev> without URL@<rev>,
+# (and they won't honor URL@<rev> without -r<rev>, too!)
+sub svn_cmd_checkout {
+ my ($url, $rev, $dir) = @_;
+ my @cmd = ('svn','co', "-r$rev");
+ push @cmd, '--ignore-externals' unless $_no_ignore_ext;
+ $url .= "\@$rev" if $_svn_co_url_revs;
+ sys(@cmd, $url, $dir);
}
sub check_upgrade_needed {
diff --git a/contrib/git-svn/git-svn.txt b/contrib/git-svn/git-svn.txt
index 8e9a971a85..7a6e0c4a4a 100644
--- a/contrib/git-svn/git-svn.txt
+++ b/contrib/git-svn/git-svn.txt
@@ -162,21 +162,8 @@ COMPATIBILITY OPTIONS
Otherwise, do not enable this flag unless you know what you're
doing.
---no-stop-on-copy::
- Only used with the 'fetch' command.
-
- By default, git-svn passes --stop-on-copy to avoid dealing with
- the copied/renamed branch directory problem entirely. A
- copied/renamed branch is the result of a <SVN_URL> being created
- in the past from a different source. These are problematic to
- deal with even when working purely with svn if you work inside
- subdirectories.
-
- Do not use this flag unless you know exactly what you're getting
- yourself into. You have been warned.
-
-Examples
-~~~~~~~~
+Basic Examples
+~~~~~~~~~~~~~~
Tracking and contributing to an Subversion managed-project:
@@ -234,6 +221,34 @@ This allows you to tie unfetched SVN revision 375 to your current HEAD::
git-svn fetch 375=$(git-rev-parse HEAD)
+Advanced Example: Tracking a Reorganized Repository
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+If you're tracking a directory that has moved, or otherwise been
+branched or tagged off of another directory in the repository and you
+care about the full history of the project, then you can read this
+section.
+
+This is how Yann Dirson tracked the trunk of the ufoai directory when
+the /trunk directory of his repository was moved to /ufoai/trunk and
+he needed to continue tracking /ufoai/trunk where /trunk left off.
+
+ # This log message shows when the repository was reorganized::
+ r166 | ydirson | 2006-03-02 01:36:55 +0100 (Thu, 02 Mar 2006) | 1 line
+ Changed paths:
+ D /trunk
+ A /ufoai/trunk (from /trunk:165)
+
+ # First we start tracking the old revisions::
+ GIT_SVN_ID=git-oldsvn git-svn init \
+ https://svn.sourceforge.net/svnroot/ufoai/trunk
+ GIT_SVN_ID=git-oldsvn git-svn fetch -r1:165
+
+ # And now, we continue tracking the new revisions::
+ GIT_SVN_ID=git-newsvn git-svn init \
+ https://svn.sourceforge.net/svnroot/ufoai/ufoai/trunk
+ GIT_SVN_ID=git-newsvn git-svn fetch \
+ 166=`git-rev-parse refs/remotes/git-oldsvn`
+
BUGS
----
If somebody commits a conflicting changeset to SVN at a bad moment
diff --git a/date.c b/date.c
index 416ea579a3..1c1917b4e8 100644
--- a/date.c
+++ b/date.c
@@ -123,8 +123,6 @@ static const struct {
{ "IDLE", +12, 0, }, /* International Date Line East */
};
-#define NR_TZ (sizeof(timezone_names) / sizeof(timezone_names[0]))
-
static int match_string(const char *date, const char *str)
{
int i = 0;
@@ -173,7 +171,7 @@ static int match_alpha(const char *date, struct tm *tm, int *offset)
}
}
- for (i = 0; i < NR_TZ; i++) {
+ for (i = 0; i < ARRAY_SIZE(timezone_names); i++) {
int match = match_string(date, timezone_names[i].name);
if (match >= 3) {
int off = timezone_names[i].offset;
diff --git a/diff-delta.c b/diff-delta.c
index 2ed5984b1c..aaee7be4d2 100644
--- a/diff-delta.c
+++ b/diff-delta.c
@@ -40,17 +40,18 @@ struct index {
static struct index ** delta_index(const unsigned char *buf,
unsigned long bufsize,
+ unsigned long trg_bufsize,
unsigned int *hash_shift)
{
- unsigned int hsize, hshift, entries, blksize, i;
+ unsigned int i, hsize, hshift, hlimit, entries, *hash_count;
const unsigned char *data;
struct index *entry, **hash;
void *mem;
/* determine index hash size */
- entries = (bufsize + BLK_SIZE - 1) / BLK_SIZE;
+ entries = bufsize / BLK_SIZE;
hsize = entries / 4;
- for (i = 4; (1 << i) < hsize && i < 16; i++);
+ for (i = 4; (1 << i) < hsize && i < 31; i++);
hsize = 1 << i;
hshift = 32 - i;
*hash_shift = hshift;
@@ -63,20 +64,62 @@ static struct index ** delta_index(const unsigned char *buf,
entry = mem + hsize * sizeof(*hash);
memset(hash, 0, hsize * sizeof(*hash));
- /* then populate it */
+ /* allocate an array to count hash entries */
+ hash_count = calloc(hsize, sizeof(*hash_count));
+ if (!hash_count) {
+ free(hash);
+ return NULL;
+ }
+
+ /* then populate the index */
data = buf + entries * BLK_SIZE - BLK_SIZE;
- blksize = bufsize - (data - buf);
while (data >= buf) {
- unsigned int val = adler32(0, data, blksize);
+ unsigned int val = adler32(0, data, BLK_SIZE);
i = HASH(val, hshift);
entry->ptr = data;
entry->val = val;
entry->next = hash[i];
hash[i] = entry++;
- blksize = BLK_SIZE;
+ hash_count[i]++;
data -= BLK_SIZE;
}
+ /*
+ * Determine a limit on the number of entries in the same hash
+ * bucket. This guard us against patological data sets causing
+ * really bad hash distribution with most entries in the same hash
+ * bucket that would bring us to O(m*n) computing costs (m and n
+ * corresponding to reference and target buffer sizes).
+ *
+ * The more the target buffer is large, the more it is important to
+ * have small entry lists for each hash buckets. With such a limit
+ * the cost is bounded to something more like O(m+n).
+ */
+ hlimit = (1 << 26) / trg_bufsize;
+ if (hlimit < 4*BLK_SIZE)
+ hlimit = 4*BLK_SIZE;
+
+ /*
+ * Now make sure none of the hash buckets has more entries than
+ * we're willing to test. Otherwise we cull the entry list
+ * uniformly to still preserve a good repartition across
+ * the reference buffer.
+ */
+ for (i = 0; i < hsize; i++) {
+ if (hash_count[i] < hlimit)
+ continue;
+ entry = hash[i];
+ do {
+ struct index *keep = entry;
+ int skip = hash_count[i] / hlimit / 2;
+ do {
+ entry = entry->next;
+ } while(--skip && entry);
+ keep->next = entry;
+ } while(entry);
+ }
+ free(hash_count);
+
return hash;
}
@@ -100,7 +143,7 @@ void *diff_delta(void *from_buf, unsigned long from_size,
if (!from_size || !to_size)
return NULL;
- hash = delta_index(from_buf, from_size, &hash_shift);
+ hash = delta_index(from_buf, from_size, to_size, &hash_shift);
if (!hash)
return NULL;
@@ -141,29 +184,27 @@ void *diff_delta(void *from_buf, unsigned long from_size,
while (data < top) {
unsigned int moff = 0, msize = 0;
- unsigned int blksize = MIN(top - data, BLK_SIZE);
- unsigned int val = adler32(0, data, blksize);
- i = HASH(val, hash_shift);
- for (entry = hash[i]; entry; entry = entry->next) {
- const unsigned char *ref = entry->ptr;
- const unsigned char *src = data;
- unsigned int ref_size = ref_top - ref;
- if (entry->val != val)
- continue;
- if (ref_size > top - src)
- ref_size = top - src;
- while (ref_size && *src++ == *ref) {
- ref++;
- ref_size--;
- }
- ref_size = ref - entry->ptr;
- if (ref_size > msize) {
- /* this is our best match so far */
- moff = entry->ptr - ref_data;
- msize = ref_size;
- if (msize >= 0x10000) {
- msize = 0x10000;
+ if (data + BLK_SIZE <= top) {
+ unsigned int val = adler32(0, data, BLK_SIZE);
+ i = HASH(val, hash_shift);
+ for (entry = hash[i]; entry; entry = entry->next) {
+ const unsigned char *ref = entry->ptr;
+ const unsigned char *src = data;
+ unsigned int ref_size = ref_top - ref;
+ if (entry->val != val)
+ continue;
+ if (ref_size > top - src)
+ ref_size = top - src;
+ if (ref_size > 0x10000)
+ ref_size = 0x10000;
+ if (ref_size <= msize)
break;
+ while (ref_size-- && *src++ == *ref)
+ ref++;
+ if (msize < ref - entry->ptr) {
+ /* this is our best match so far */
+ msize = ref - entry->ptr;
+ moff = entry->ptr - ref_data;
}
}
}
diff --git a/entry.c b/entry.c
index 8fb99bc83f..5d9aefd03f 100644
--- a/entry.c
+++ b/entry.c
@@ -63,7 +63,7 @@ static int create_file(const char *path, unsigned int mode)
return open(path, O_WRONLY | O_CREAT | O_EXCL, mode);
}
-static int write_entry(struct cache_entry *ce, const char *path, struct checkout *state)
+static int write_entry(struct cache_entry *ce, char *path, struct checkout *state, int to_tempfile)
{
int fd;
void *new;
@@ -80,7 +80,11 @@ static int write_entry(struct cache_entry *ce, const char *path, struct checkout
}
switch (ntohl(ce->ce_mode) & S_IFMT) {
case S_IFREG:
- fd = create_file(path, ntohl(ce->ce_mode));
+ if (to_tempfile) {
+ strcpy(path, ".merge_file_XXXXXX");
+ fd = mkstemp(path);
+ } else
+ fd = create_file(path, ntohl(ce->ce_mode));
if (fd < 0) {
free(new);
return error("git-checkout-index: unable to create file %s (%s)",
@@ -93,12 +97,27 @@ static int write_entry(struct cache_entry *ce, const char *path, struct checkout
return error("git-checkout-index: unable to write file %s", path);
break;
case S_IFLNK:
- if (symlink(new, path)) {
+ if (to_tempfile) {
+ strcpy(path, ".merge_link_XXXXXX");
+ fd = mkstemp(path);
+ if (fd < 0) {
+ free(new);
+ return error("git-checkout-index: unable to create "
+ "file %s (%s)", path, strerror(errno));
+ }
+ wrote = write(fd, new, size);
+ close(fd);
+ free(new);
+ if (wrote != size)
+ return error("git-checkout-index: unable to write file %s",
+ path);
+ } else {
+ wrote = symlink(new, path);
free(new);
- return error("git-checkout-index: unable to create "
- "symlink %s (%s)", path, strerror(errno));
+ if (wrote)
+ return error("git-checkout-index: unable to create "
+ "symlink %s (%s)", path, strerror(errno));
}
- free(new);
break;
default:
free(new);
@@ -113,12 +132,15 @@ static int write_entry(struct cache_entry *ce, const char *path, struct checkout
return 0;
}
-int checkout_entry(struct cache_entry *ce, struct checkout *state)
+int checkout_entry(struct cache_entry *ce, struct checkout *state, char *topath)
{
- struct stat st;
static char path[MAXPATHLEN+1];
+ struct stat st;
int len = state->base_dir_len;
+ if (topath)
+ return write_entry(ce, topath, state, 1);
+
memcpy(path, state->base_dir, len);
strcpy(path + len, ce->name);
@@ -144,10 +166,10 @@ int checkout_entry(struct cache_entry *ce, struct checkout *state)
return error("%s is a directory", path);
remove_subtree(path);
}
- } else if (state->not_new)
+ } else if (state->not_new)
return 0;
create_directories(path, state);
- return write_entry(ce, path, state);
+ return write_entry(ce, path, state, 0);
}
diff --git a/exec_cmd.c b/exec_cmd.c
index b5e59a9ae9..590e738969 100644
--- a/exec_cmd.c
+++ b/exec_cmd.c
@@ -29,17 +29,18 @@ const char *git_exec_path(void)
}
-int execv_git_cmd(char **argv)
+int execv_git_cmd(const char **argv)
{
char git_command[PATH_MAX + 1];
- char *tmp;
int len, err, i;
const char *paths[] = { current_exec_path,
getenv("GIT_EXEC_PATH"),
builtin_exec_path };
- for (i = 0; i < sizeof(paths)/sizeof(paths[0]); ++i) {
+ for (i = 0; i < ARRAY_SIZE(paths); ++i) {
const char *exec_dir = paths[i];
+ const char *tmp;
+
if (!exec_dir) continue;
if (*exec_dir != '/') {
@@ -82,7 +83,7 @@ int execv_git_cmd(char **argv)
argv[0] = git_command;
/* execve() can only ever return if it fails */
- execve(git_command, argv, environ);
+ execve(git_command, (char **)argv, environ);
err = errno;
@@ -93,11 +94,11 @@ int execv_git_cmd(char **argv)
}
-int execl_git_cmd(char *cmd,...)
+int execl_git_cmd(const char *cmd,...)
{
int argc;
- char *argv[MAX_ARGS + 1];
- char *arg;
+ const char *argv[MAX_ARGS + 1];
+ const char *arg;
va_list param;
va_start(param, cmd);
diff --git a/exec_cmd.h b/exec_cmd.h
index 5150ee29f7..989621ff4e 100644
--- a/exec_cmd.h
+++ b/exec_cmd.h
@@ -3,8 +3,8 @@
extern void git_set_exec_path(const char *exec_path);
extern const char* git_exec_path(void);
-extern int execv_git_cmd(char **argv); /* NULL terminated */
-extern int execl_git_cmd(char *cmd, ...);
+extern int execv_git_cmd(const char **argv); /* NULL terminated */
+extern int execl_git_cmd(const char *cmd, ...);
#endif /* __GIT_EXEC_CMD_H_ */
diff --git a/fsck-objects.c b/fsck-objects.c
index 4ddd67699c..59b25904cb 100644
--- a/fsck-objects.c
+++ b/fsck-objects.c
@@ -14,10 +14,9 @@
static int show_root = 0;
static int show_tags = 0;
static int show_unreachable = 0;
-static int standalone = 0;
static int check_full = 0;
static int check_strict = 0;
-static int keep_cache_objects = 0;
+static int keep_cache_objects = 0;
static unsigned char head_sha1[20];
#ifdef NO_D_INO_IN_DIRENT
@@ -68,7 +67,7 @@ static void check_connectivity(void)
continue;
if (!obj->parsed) {
- if (!standalone && has_sha1_file(obj->sha1))
+ if (has_sha1_file(obj->sha1))
; /* it is in pack */
else
printf("missing %s %s\n",
@@ -82,7 +81,7 @@ static void check_connectivity(void)
for (j = 0; j < refs->count; j++) {
struct object *ref = refs->ref[j];
if (ref->parsed ||
- (!standalone && has_sha1_file(ref->sha1)))
+ (has_sha1_file(ref->sha1)))
continue;
printf("broken link from %7s %s\n",
obj->type, sha1_to_hex(obj->sha1));
@@ -390,7 +389,7 @@ static int fsck_handle_ref(const char *refname, const unsigned char *sha1)
obj = lookup_object(sha1);
if (!obj) {
- if (!standalone && has_sha1_file(sha1)) {
+ if (has_sha1_file(sha1)) {
default_refs++;
return 0; /* it is in a pack */
}
@@ -464,10 +463,6 @@ int main(int argc, char **argv)
keep_cache_objects = 1;
continue;
}
- if (!strcmp(arg, "--standalone")) {
- standalone = 1;
- continue;
- }
if (!strcmp(arg, "--full")) {
check_full = 1;
continue;
@@ -477,14 +472,9 @@ int main(int argc, char **argv)
continue;
}
if (*arg == '-')
- usage("git-fsck-objects [--tags] [--root] [[--unreachable] [--cache] [--standalone | --full] [--strict] <head-sha1>*]");
+ usage("git-fsck-objects [--tags] [--root] [[--unreachable] [--cache] [--full] [--strict] <head-sha1>*]");
}
- if (standalone && check_full)
- die("Only one of --standalone or --full can be used.");
- if (standalone)
- putenv("GIT_ALTERNATE_OBJECT_DIRECTORIES=");
-
fsck_head_link();
fsck_object_dir(get_object_directory());
if (check_full) {
diff --git a/generate-cmdlist.sh b/generate-cmdlist.sh
new file mode 100755
index 0000000000..6ee85d5a53
--- /dev/null
+++ b/generate-cmdlist.sh
@@ -0,0 +1,48 @@
+#!/bin/sh
+
+echo "/* Automatically generated by $0 */
+struct cmdname_help
+{
+ char name[16];
+ char help[64];
+};
+
+struct cmdname_help common_cmds[] = {"
+
+sort <<\EOF |
+add
+apply
+bisect
+branch
+checkout
+cherry-pick
+clone
+commit
+diff
+fetch
+grep
+init-db
+log
+merge
+mv
+prune
+pull
+push
+rebase
+reset
+revert
+rm
+show
+show-branch
+status
+tag
+verify-tag
+whatchanged
+EOF
+while read cmd
+do
+ sed -n "/NAME/,/git-$cmd/H;
+ \$ {x; s/.*git-$cmd - \\(.*\\)/ {\"$cmd\", \"\1\"},/; p}" \
+ "Documentation/git-$cmd.txt"
+done
+echo "};"
diff --git a/git-annotate.perl b/git-annotate.perl
index d93ee19c7e..9df72a1662 100755
--- a/git-annotate.perl
+++ b/git-annotate.perl
@@ -20,7 +20,7 @@ sub usage() {
-r, --rename
Follow renames (Defaults on).
-S, --rev-file revs-file
- use revs from revs-file instead of calling git-rev-list
+ Use revs from revs-file instead of calling git-rev-list
-h, --help
This message.
';
@@ -99,7 +99,7 @@ while (my $bound = pop @stack) {
}
}
push @revqueue, $head;
-init_claim( defined $starting_rev ? $starting_rev : 'dirty');
+init_claim( defined $starting_rev ? $head : 'dirty');
unless (defined $starting_rev) {
my $diff = open_pipe("git","diff","-R", "HEAD", "--",$filename)
or die "Failed to call git diff to check for dirty state: $!";
@@ -345,6 +345,7 @@ sub git_cat_file {
return () unless defined $rev && defined $filename;
my $blob = git_ls_tree($rev, $filename);
+ die "Failed to find a blob for $filename in rev $rev\n" if !defined $blob;
my $catfile = open_pipe("git","cat-file", "blob", $blob)
or die "Failed to git-cat-file blob $blob (rev $rev, file $filename): " . $!;
@@ -367,12 +368,13 @@ sub git_ls_tree {
my ($mode, $type, $blob, $tfilename);
while(<$lstree>) {
+ chomp;
($mode, $type, $blob, $tfilename) = split(/\s+/, $_, 4);
last if ($tfilename eq $filename);
}
close($lstree);
- return $blob if $filename eq $filename;
+ return $blob if ($tfilename eq $filename);
die "git-ls-tree failed to find blob for $filename";
}
@@ -418,7 +420,13 @@ sub format_date {
return $_[0];
}
my ($timestamp, $timezone) = split(' ', $_[0]);
- return strftime("%Y-%m-%d %H:%M:%S " . $timezone, gmtime($timestamp));
+ my $minutes = abs($timezone);
+ $minutes = int($minutes / 100) * 60 + ($minutes % 100);
+ if ($timezone < 0) {
+ $minutes = -$minutes;
+ }
+ my $t = $timestamp + $minutes * 60;
+ return strftime("%Y-%m-%d %H:%M:%S " . $timezone, gmtime($t));
}
# Copied from git-send-email.perl - We need a Git.pm module..
diff --git a/git-commit.sh b/git-commit.sh
index d9ec1f14d9..330a434b18 100755
--- a/git-commit.sh
+++ b/git-commit.sh
@@ -609,7 +609,7 @@ fi
test -z "$only_include_assumed" || echo "$only_include_assumed"
run_status
} >>"$GIT_DIR"/COMMIT_EDITMSG
-if [ "$?" != "0" -a ! -f "$GIT_DIR/MERGE_HEAD" ]
+if [ "$?" != "0" -a ! -f "$GIT_DIR/MERGE_HEAD" -a -z "$amend" ]
then
rm -f "$GIT_DIR/COMMIT_EDITMSG"
run_status
diff --git a/git-compat-util.h b/git-compat-util.h
index f982b8e484..5d543d29f8 100644
--- a/git-compat-util.h
+++ b/git-compat-util.h
@@ -9,6 +9,8 @@
#endif
#endif
+#define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0]))
+
#include <unistd.h>
#include <stdio.h>
#include <sys/stat.h>
diff --git a/git-cvsimport.perl b/git-cvsimport.perl
index b46469ab32..02d1928ada 100755
--- a/git-cvsimport.perl
+++ b/git-cvsimport.perl
@@ -452,7 +452,6 @@ chdir($git_tree);
my $last_branch = "";
my $orig_branch = "";
-my $forward_master = 0;
my %branch_date;
my $git_dir = $ENV{"GIT_DIR"} || ".git";
@@ -488,21 +487,6 @@ unless(-d $git_dir) {
$last_branch = "master";
}
$orig_branch = $last_branch;
- if (-f "$git_dir/CVS2GIT_HEAD") {
- die <<EOM;
-CVS2GIT_HEAD exists.
-Make sure your working directory corresponds to HEAD and remove CVS2GIT_HEAD.
-You may need to run
-
- git read-tree -m -u CVS2GIT_HEAD HEAD
-EOM
- }
- system('cp', "$git_dir/HEAD", "$git_dir/CVS2GIT_HEAD");
-
- $forward_master =
- $opt_o ne 'master' && -f "$git_dir/refs/heads/master" &&
- system('cmp', '-s', "$git_dir/refs/heads/master",
- "$git_dir/refs/heads/$opt_o") == 0;
# populate index
system('git-read-tree', $last_branch);
@@ -889,17 +873,11 @@ if (defined $orig_git_index) {
# Now switch back to the branch we were in before all of this happened
if($orig_branch) {
- print "DONE\n" if $opt_v;
- system("cp","$git_dir/refs/heads/$opt_o","$git_dir/refs/heads/master")
- if $forward_master;
- unless ($opt_i) {
- system('git-read-tree', '-m', '-u', 'CVS2GIT_HEAD', 'HEAD');
- die "read-tree failed: $?\n" if $?;
- }
+ print "DONE; you may need to merge manually.\n" if $opt_v;
} else {
$orig_branch = "master";
print "DONE; creating $orig_branch branch\n" if $opt_v;
- system("cp","$git_dir/refs/heads/$opt_o","$git_dir/refs/heads/master")
+ system("git-update-ref", "refs/heads/master", "refs/heads/$opt_o")
unless -f "$git_dir/refs/heads/master";
system('git-update-ref', 'HEAD', "$orig_branch");
unless ($opt_i) {
@@ -907,4 +885,3 @@ if($orig_branch) {
die "checkout failed: $?\n" if $?;
}
}
-unlink("$git_dir/CVS2GIT_HEAD");
diff --git a/git-diff.sh b/git-diff.sh
index dc4d1b3cfd..dc0dd312bf 100755
--- a/git-diff.sh
+++ b/git-diff.sh
@@ -38,9 +38,9 @@ case " $flags " in
flags="$flags'$cc_or_p' " ;;
esac
-# If we do not have -B nor -C, default to -M.
+# If we do not have -B, -C, -r, nor -p, default to -M.
case " $flags " in
-*" '-"[BCM]* | *" '--find-copies-harder' "*)
+*" '-"[BCMrp]* | *" '--find-copies-harder' "*)
;; # something like -M50.
*)
flags="$flags'-M' " ;;
diff --git a/git-fetch.sh b/git-fetch.sh
index 0346d4a45c..c0eb96752e 100755
--- a/git-fetch.sh
+++ b/git-fetch.sh
@@ -94,6 +94,9 @@ append_fetch_head () {
# remote-nick is the URL given on the command line (or a shorthand)
# remote-name is the $GIT_DIR relative refs/ path we computed
# for this refspec.
+
+ # the $note_ variable will be fed to git-fmt-merge-msg for further
+ # processing.
case "$remote_name_" in
HEAD)
note_= ;;
@@ -103,6 +106,9 @@ append_fetch_head () {
refs/tags/*)
note_="$(expr "$remote_name_" : 'refs/tags/\(.*\)')"
note_="tag '$note_' of " ;;
+ refs/remotes/*)
+ note_="$(expr "$remote_name_" : 'refs/remotes/\(.*\)')"
+ note_="remote branch '$note_' of " ;;
*)
note_="$remote_name of " ;;
esac
@@ -147,10 +153,10 @@ fast_forward_local () {
else
echo >&2 "* $1: storing $3"
fi
- git-update-ref "$1" "$2"
+ git-update-ref "$1" "$2"
;;
- refs/heads/*)
+ refs/heads/* | refs/remotes/*)
# $1 is the ref being updated.
# $2 is the new value for the ref.
local=$(git-rev-parse --verify "$1^0" 2>/dev/null)
diff --git a/git-fmt-merge-msg.perl b/git-fmt-merge-msg.perl
index dae383f231..5986e5414a 100755
--- a/git-fmt-merge-msg.perl
+++ b/git-fmt-merge-msg.perl
@@ -47,7 +47,7 @@ sub current_branch {
sub shortlog {
my ($tip) = @_;
my @result;
- foreach ( qx{git-log --topo-order --pretty=oneline $tip ^HEAD} ) {
+ foreach ( qx{git-log --no-merges --topo-order --pretty=oneline $tip ^HEAD} ) {
s/^[0-9a-f]{40}\s+//;
push @result, $_;
}
@@ -75,6 +75,7 @@ while (<>) {
$src{$src} = {
BRANCH => [],
TAG => [],
+ R_BRANCH => [],
GENERIC => [],
# &1 == has HEAD.
# &2 == has others.
@@ -91,6 +92,11 @@ while (<>) {
push @{$src{$src}{TAG}}, $1;
$src{$src}{HEAD_STATUS} |= 2;
}
+ elsif (/^remote branch (.*)$/) {
+ $origin = $1;
+ push @{$src{$src}{R_BRANCH}}, $1;
+ $src{$src}{HEAD_STATUS} |= 2;
+ }
elsif (/^HEAD$/) {
$origin = $src;
$src{$src}{HEAD_STATUS} |= 1;
@@ -123,6 +129,8 @@ for my $src (@src) {
}
push @this, andjoin("branch ", "branches ",
$src{$src}{BRANCH});
+ push @this, andjoin("remote branch ", "remote branches ",
+ $src{$src}{R_BRANCH});
push @this, andjoin("tag ", "tags ",
$src{$src}{TAG});
push @this, andjoin("commit ", "commits ",
diff --git a/git-format-patch.sh b/git-format-patch.sh
index 2bd26395ec..2ebf7e8596 100755
--- a/git-format-patch.sh
+++ b/git-format-patch.sh
@@ -3,7 +3,7 @@
# Copyright (c) 2005 Junio C Hamano
#
-USAGE='[-n | -k] [-o <dir> | --stdout] [--signoff] [--check] [--diff-options] <his> [<mine>]'
+USAGE='[-n | -k] [-o <dir> | --stdout] [--signoff] [--check] [--diff-options] [--attach] <his> [<mine>]'
LONG_USAGE='Prepare each commit with its patch since <mine> head forked from
<his> head, one file per patch formatted to resemble UNIX mailbox
format, for e-mail submission or use with git-am.
@@ -18,7 +18,9 @@ is ignored if --stdout is specified.
When -n is specified, instead of "[PATCH] Subject", the first
line is formatted as "[PATCH N/M] Subject", unless you have only
-one patch.'
+one patch.
+
+When --attach is specified, patches are attached, not inlined.'
. git-sh-setup
@@ -40,6 +42,8 @@ do
-d|--d|--da|--dat|--date|\
-m|--m|--mb|--mbo|--mbox) # now noop
;;
+ --at|--att|--atta|--attac|--attach)
+ attach=t ;;
-k|--k|--ke|--kee|--keep|--keep-|--keep-s|--keep-su|--keep-sub|\
--keep-subj|--keep-subje|--keep-subjec|--keep-subject)
keep_subject=t ;;
@@ -149,6 +153,12 @@ do
done >$series
me=`git-var GIT_AUTHOR_IDENT | sed -e 's/>.*/>/'`
+headers=`git-repo-config --get format.headers`
+case "$attach" in
+"") ;;
+*)
+ mimemagic="050802040500080604070107"
+esac
case "$outdir" in
*/) ;;
@@ -173,7 +183,7 @@ titleScript='
process_one () {
perl -w -e '
-my ($keep_subject, $num, $signoff, $commsg) = @ARGV;
+my ($keep_subject, $num, $signoff, $headers, $mimemagic, $commsg) = @ARGV;
my ($signoff_pattern, $done_header, $done_subject, $done_separator, $signoff_seen,
$last_was_signoff);
@@ -224,7 +234,20 @@ while (<FH>) {
s/^\[PATCH[^]]*\]\s*//;
s/^/[PATCH$num] /;
}
+ if ($headers) {
+ print "$headers\n";
+ }
print "Subject: $_";
+ if ($mimemagic) {
+ print "MIME-Version: 1.0\n";
+ print "Content-Type: multipart/mixed;\n";
+ print " boundary=\"------------$mimemagic\"\n";
+ print "\n";
+ print "This is a multi-part message in MIME format.\n";
+ print "--------------$mimemagic\n";
+ print "Content-Type: text/plain; charset=UTF-8; format=fixed\n";
+ print "Content-Transfer-Encoding: 8bit\n";
+ }
$done_subject = 1;
next;
}
@@ -250,14 +273,33 @@ if (!$signoff_seen && $signoff ne "") {
}
print "\n---\n\n";
close FH or die "close $commsg pipe";
-' "$keep_subject" "$num" "$signoff" $commsg
+' "$keep_subject" "$num" "$signoff" "$headers" "$mimemagic" $commsg
git-diff-tree -p $diff_opts "$commit" | git-apply --stat --summary
echo
+ case "$mimemagic" in
+ '');;
+ *)
+ echo "--------------$mimemagic"
+ echo "Content-Type: text/x-patch;"
+ echo " name=\"$commit.diff\""
+ echo "Content-Transfer-Encoding: 8bit"
+ echo "Content-Disposition: inline;"
+ echo " filename=\"$commit.diff\""
+ echo
+ esac
git-diff-tree -p $diff_opts "$commit"
- echo "-- "
- echo "@@GIT_VERSION@@"
-
+ case "$mimemagic" in
+ '')
+ echo "-- "
+ echo "@@GIT_VERSION@@"
+ ;;
+ *)
+ echo
+ echo "--------------$mimemagic--"
+ echo
+ ;;
+ esac
echo
}
diff --git a/git-merge.sh b/git-merge.sh
index 7be9e81f1f..cc0952a97d 100755
--- a/git-merge.sh
+++ b/git-merge.sh
@@ -131,7 +131,7 @@ case "$#,$common,$no_commit" in
;;
1,"$head",*)
# Again the most common case of merging one remote.
- echo "Updating from $head to $1."
+ echo "Updating from $head to $1"
git-update-index --refresh 2>/dev/null
new_head=$(git-rev-parse --verify "$1^0") &&
git-read-tree -u -v -m $head "$new_head" &&
diff --git a/git-parse-remote.sh b/git-parse-remote.sh
index 5f158c613f..63f22818e6 100755
--- a/git-parse-remote.sh
+++ b/git-parse-remote.sh
@@ -86,14 +86,14 @@ canon_refs_list_for_fetch () {
local=$(expr "$ref" : '[^:]*:\(.*\)')
case "$remote" in
'') remote=HEAD ;;
- refs/heads/* | refs/tags/*) ;;
- heads/* | tags/* ) remote="refs/$remote" ;;
+ refs/heads/* | refs/tags/* | refs/remotes/*) ;;
+ heads/* | tags/* | remotes/* ) remote="refs/$remote" ;;
*) remote="refs/heads/$remote" ;;
esac
case "$local" in
'') local= ;;
- refs/heads/* | refs/tags/*) ;;
- heads/* | tags/* ) local="refs/$local" ;;
+ refs/heads/* | refs/tags/* | refs/remotes/*) ;;
+ heads/* | tags/* | remotes/* ) local="refs/$local" ;;
*) local="refs/heads/$local" ;;
esac
diff --git a/git-repack.sh b/git-repack.sh
index 3d6fec1c9a..bc901126bf 100755
--- a/git-repack.sh
+++ b/git-repack.sh
@@ -75,6 +75,7 @@ then
done
)
fi
+ git-prune-packed
fi
case "$no_update_info" in
diff --git a/git-resolve.sh b/git-resolve.sh
index b53ede8d87..1c7aaefa25 100755
--- a/git-resolve.sh
+++ b/git-resolve.sh
@@ -41,7 +41,7 @@ case "$common" in
exit 0
;;
"$head")
- echo "Updating from $head to $merge."
+ echo "Updating from $head to $merge"
git-read-tree -u -m $head $merge || exit 1
git-update-ref HEAD "$merge" "$head"
git-diff-tree -p $head $merge | git-apply --stat
diff --git a/git.c b/git.c
index a547dbd913..0b40e3060d 100644
--- a/git.c
+++ b/git.c
@@ -11,6 +11,7 @@
#include <sys/ioctl.h>
#include "git-compat-util.h"
#include "exec_cmd.h"
+#include "common-cmds.h"
#include "cache.h"
#include "commit.h"
@@ -171,11 +172,29 @@ static void list_commands(const char *exec_path, const char *pattern)
putchar('\n');
}
+static void list_common_cmds_help()
+{
+ int i, longest = 0;
+
+ for (i = 0; i < ARRAY_SIZE(common_cmds); i++) {
+ if (longest < strlen(common_cmds[i].name))
+ longest = strlen(common_cmds[i].name);
+ }
+
+ puts("The most commonly used git commands are:");
+ for (i = 0; i < ARRAY_SIZE(common_cmds); i++) {
+ printf(" %s", common_cmds[i].name);
+ mput_char(' ', longest - strlen(common_cmds[i].name) + 4);
+ puts(common_cmds[i].help);
+ }
+ puts("(use 'git help -a' to get a list of all installed git commands)");
+}
+
#ifdef __GNUC__
-static void cmd_usage(const char *exec_path, const char *fmt, ...)
- __attribute__((__format__(__printf__, 2, 3), __noreturn__));
+static void cmd_usage(int show_all, const char *exec_path, const char *fmt, ...)
+ __attribute__((__format__(__printf__, 3, 4), __noreturn__));
#endif
-static void cmd_usage(const char *exec_path, const char *fmt, ...)
+static void cmd_usage(int show_all, const char *exec_path, const char *fmt, ...)
{
if (fmt) {
va_list ap;
@@ -189,10 +208,13 @@ static void cmd_usage(const char *exec_path, const char *fmt, ...)
else
puts(git_usage);
- putchar('\n');
-
- if(exec_path)
- list_commands(exec_path, "git-*");
+ if (exec_path) {
+ putchar('\n');
+ if (show_all)
+ list_commands(exec_path, "git-*");
+ else
+ list_common_cmds_help();
+ }
exit(1);
}
@@ -216,42 +238,45 @@ static void prepend_to_path(const char *dir, int len)
setenv("PATH", path, 1);
}
-static void show_man_page(char *git_cmd)
+static void show_man_page(const char *git_cmd)
{
- char *page;
+ const char *page;
if (!strncmp(git_cmd, "git", 3))
page = git_cmd;
else {
int page_len = strlen(git_cmd) + 4;
-
- page = malloc(page_len + 1);
- strcpy(page, "git-");
- strcpy(page + 4, git_cmd);
- page[page_len] = 0;
+ char *p = malloc(page_len + 1);
+ strcpy(p, "git-");
+ strcpy(p + 4, git_cmd);
+ p[page_len] = 0;
+ page = p;
}
execlp("man", "man", page, NULL);
}
-static int cmd_version(int argc, char **argv, char **envp)
+static int cmd_version(int argc, const char **argv, char **envp)
{
printf("git version %s\n", GIT_VERSION);
return 0;
}
-static int cmd_help(int argc, char **argv, char **envp)
+static int cmd_help(int argc, const char **argv, char **envp)
{
- char *help_cmd = argv[1];
+ const char *help_cmd = argv[1];
if (!help_cmd)
- cmd_usage(git_exec_path(), NULL);
- show_man_page(help_cmd);
+ cmd_usage(0, git_exec_path(), NULL);
+ else if (!strcmp(help_cmd, "--all") || !strcmp(help_cmd, "-a"))
+ cmd_usage(1, git_exec_path(), NULL);
+ else
+ show_man_page(help_cmd);
return 0;
}
#define LOGSIZE (65536)
-static int cmd_log(int argc, char **argv, char **envp)
+static int cmd_log(int argc, const char **argv, char **envp)
{
struct rev_info rev;
struct commit *commit;
@@ -263,7 +288,7 @@ static int cmd_log(int argc, char **argv, char **envp)
argc = setup_revisions(argc, argv, &rev, "HEAD");
while (1 < argc) {
- char *arg = argv[1];
+ const char *arg = argv[1];
if (!strncmp(arg, "--pretty", 8)) {
commit_format = get_commit_format(arg + 8);
if (commit_format == CMIT_FMT_ONELINE)
@@ -323,14 +348,12 @@ static int cmd_log(int argc, char **argv, char **envp)
return 0;
}
-#define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0]))
-
-static void handle_internal_command(int argc, char **argv, char **envp)
+static void handle_internal_command(int argc, const char **argv, char **envp)
{
const char *cmd = argv[0];
static struct cmd_struct {
const char *cmd;
- int (*fn)(int, char **, char **);
+ int (*fn)(int, const char **, char **);
} commands[] = {
{ "version", cmd_version },
{ "help", cmd_help },
@@ -346,9 +369,9 @@ static void handle_internal_command(int argc, char **argv, char **envp)
}
}
-int main(int argc, char **argv, char **envp)
+int main(int argc, const char **argv, char **envp)
{
- char *cmd = argv[0];
+ const char *cmd = argv[0];
char *slash = strrchr(cmd, '/');
char git_command[PATH_MAX + 1];
const char *exec_path = NULL;
@@ -420,7 +443,7 @@ int main(int argc, char **argv, char **envp)
puts(git_exec_path());
exit(0);
}
- cmd_usage(NULL, NULL);
+ cmd_usage(0, NULL, NULL);
}
argv[0] = cmd;
@@ -443,7 +466,7 @@ int main(int argc, char **argv, char **envp)
execv_git_cmd(argv);
if (errno == ENOENT)
- cmd_usage(exec_path, "'%s' is not a git-command", cmd);
+ cmd_usage(0, exec_path, "'%s' is not a git-command", cmd);
fprintf(stderr, "Failed to run command '%s': %s\n",
git_command, strerror(errno));
diff --git a/http-push.c b/http-push.c
index fe925609b4..42b0d59e8c 100644
--- a/http-push.c
+++ b/http-push.c
@@ -5,11 +5,13 @@
#include "tag.h"
#include "blob.h"
#include "http.h"
+#include "refs.h"
+#include "revision.h"
#include <expat.h>
static const char http_push_usage[] =
-"git-http-push [--complete] [--force] [--verbose] <url> <ref> [<ref>...]\n";
+"git-http-push [--all] [--force] [--verbose] <remote> [<head>...]\n";
#ifndef XML_STATUS_OK
enum XML_Status {
@@ -20,6 +22,7 @@ enum XML_Status {
#define XML_STATUS_ERROR 0
#endif
+#define PREV_BUF_SIZE 4096
#define RANGE_HEADER_SIZE 30
/* DAV methods */
@@ -42,14 +45,25 @@ enum XML_Status {
#define DAV_ACTIVELOCK_OWNER ".prop.lockdiscovery.activelock.owner.href"
#define DAV_ACTIVELOCK_TIMEOUT ".prop.lockdiscovery.activelock.timeout"
#define DAV_ACTIVELOCK_TOKEN ".prop.lockdiscovery.activelock.locktoken.href"
+#define DAV_PROPFIND_RESP ".multistatus.response"
+#define DAV_PROPFIND_NAME ".multistatus.response.href"
+#define DAV_PROPFIND_COLLECTION ".multistatus.response.propstat.prop.resourcetype.collection"
/* DAV request body templates */
-#define PROPFIND_REQUEST "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n<D:propfind xmlns:D=\"DAV:\">\n<D:prop xmlns:R=\"%s\">\n<D:supportedlock/>\n</D:prop>\n</D:propfind>"
+#define PROPFIND_SUPPORTEDLOCK_REQUEST "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n<D:propfind xmlns:D=\"DAV:\">\n<D:prop xmlns:R=\"%s\">\n<D:supportedlock/>\n</D:prop>\n</D:propfind>"
+#define PROPFIND_ALL_REQUEST "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n<D:propfind xmlns:D=\"DAV:\">\n<D:allprop/>\n</D:propfind>"
#define LOCK_REQUEST "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n<D:lockinfo xmlns:D=\"DAV:\">\n<D:lockscope><D:exclusive/></D:lockscope>\n<D:locktype><D:write/></D:locktype>\n<D:owner>\n<D:href>mailto:%s</D:href>\n</D:owner>\n</D:lockinfo>"
#define LOCK_TIME 600
#define LOCK_REFRESH 30
+/* bits #0-4 in revision.h */
+
+#define LOCAL (1u << 5)
+#define REMOTE (1u << 6)
+#define FETCHING (1u << 7)
+#define PUSHING (1u << 8)
+
static int pushing = 0;
static int aborted = 0;
static char remote_dir_exists[256];
@@ -61,17 +75,25 @@ static int push_verbosely = 0;
static int push_all = 0;
static int force_all = 0;
+static struct object_list *objects = NULL;
+
struct repo
{
char *url;
+ int path_len;
+ int has_info_refs;
+ int can_update_info_refs;
+ int has_info_packs;
struct packed_git *packs;
+ struct remote_lock *locks;
};
static struct repo *remote = NULL;
enum transfer_state {
- NEED_CHECK,
- RUN_HEAD,
+ NEED_FETCH,
+ RUN_FETCH_LOOSE,
+ RUN_FETCH_PACKED,
NEED_PUSH,
RUN_MKCOL,
RUN_PUT,
@@ -82,14 +104,16 @@ enum transfer_state {
struct transfer_request
{
- unsigned char sha1[20];
+ struct object *obj;
char *url;
char *dest;
- struct active_lock *lock;
+ struct remote_lock *lock;
struct curl_slist *headers;
struct buffer buffer;
char filename[PATH_MAX];
char tmpfile[PATH_MAX];
+ int local_fileno;
+ FILE *local_stream;
enum transfer_state state;
CURLcode curl_result;
char errorstr[CURL_ERROR_SIZE];
@@ -99,6 +123,7 @@ struct transfer_request
z_stream stream;
int zret;
int rename;
+ void *userData;
struct active_request_slot *slot;
struct transfer_request *next;
};
@@ -114,7 +139,7 @@ struct xml_ctx
void *userData;
};
-struct active_lock
+struct remote_lock
{
char *url;
char *owner;
@@ -122,9 +147,30 @@ struct active_lock
time_t start_time;
long timeout;
int refreshing;
+ struct remote_lock *next;
+};
+
+/* Flags that control remote_ls processing */
+#define PROCESS_FILES (1u << 0)
+#define PROCESS_DIRS (1u << 1)
+#define RECURSIVE (1u << 2)
+
+/* Flags that remote_ls passes to callback functions */
+#define IS_DIR (1u << 0)
+
+struct remote_ls_ctx
+{
+ char *path;
+ void (*userFunc)(struct remote_ls_ctx *ls);
+ void *userData;
+ int flags;
+ char *dentry_name;
+ int dentry_flags;
+ struct remote_ls_ctx *parent;
};
static void finish_request(struct transfer_request *request);
+static void release_request(struct transfer_request *request);
static void process_response(void *callback_data)
{
@@ -134,42 +180,261 @@ static void process_response(void *callback_data)
finish_request(request);
}
-static void start_check(struct transfer_request *request)
+static size_t fwrite_sha1_file(void *ptr, size_t eltsize, size_t nmemb,
+ void *data)
{
- char *hex = sha1_to_hex(request->sha1);
- struct active_request_slot *slot;
+ unsigned char expn[4096];
+ size_t size = eltsize * nmemb;
+ int posn = 0;
+ struct transfer_request *request = (struct transfer_request *)data;
+ do {
+ ssize_t retval = write(request->local_fileno,
+ ptr + posn, size - posn);
+ if (retval < 0)
+ return posn;
+ posn += retval;
+ } while (posn < size);
+
+ request->stream.avail_in = size;
+ request->stream.next_in = ptr;
+ do {
+ request->stream.next_out = expn;
+ request->stream.avail_out = sizeof(expn);
+ request->zret = inflate(&request->stream, Z_SYNC_FLUSH);
+ SHA1_Update(&request->c, expn,
+ sizeof(expn) - request->stream.avail_out);
+ } while (request->stream.avail_in && request->zret == Z_OK);
+ data_received++;
+ return size;
+}
+
+static void start_fetch_loose(struct transfer_request *request)
+{
+ char *hex = sha1_to_hex(request->obj->sha1);
+ char *filename;
+ char prevfile[PATH_MAX];
+ char *url;
char *posn;
+ int prevlocal;
+ unsigned char prev_buf[PREV_BUF_SIZE];
+ ssize_t prev_read = 0;
+ long prev_posn = 0;
+ char range[RANGE_HEADER_SIZE];
+ struct curl_slist *range_header = NULL;
+ struct active_request_slot *slot;
- request->url = xmalloc(strlen(remote->url) + 55);
- strcpy(request->url, remote->url);
- posn = request->url + strlen(remote->url);
+ filename = sha1_file_name(request->obj->sha1);
+ snprintf(request->filename, sizeof(request->filename), "%s", filename);
+ snprintf(request->tmpfile, sizeof(request->tmpfile),
+ "%s.temp", filename);
+
+ snprintf(prevfile, sizeof(prevfile), "%s.prev", request->filename);
+ unlink(prevfile);
+ rename(request->tmpfile, prevfile);
+ unlink(request->tmpfile);
+
+ if (request->local_fileno != -1)
+ error("fd leakage in start: %d", request->local_fileno);
+ request->local_fileno = open(request->tmpfile,
+ O_WRONLY | O_CREAT | O_EXCL, 0666);
+ /* This could have failed due to the "lazy directory creation";
+ * try to mkdir the last path component.
+ */
+ if (request->local_fileno < 0 && errno == ENOENT) {
+ char *dir = strrchr(request->tmpfile, '/');
+ if (dir) {
+ *dir = 0;
+ mkdir(request->tmpfile, 0777);
+ *dir = '/';
+ }
+ request->local_fileno = open(request->tmpfile,
+ O_WRONLY | O_CREAT | O_EXCL, 0666);
+ }
+
+ if (request->local_fileno < 0) {
+ request->state = ABORTED;
+ error("Couldn't create temporary file %s for %s: %s",
+ request->tmpfile, request->filename, strerror(errno));
+ return;
+ }
+
+ memset(&request->stream, 0, sizeof(request->stream));
+
+ inflateInit(&request->stream);
+
+ SHA1_Init(&request->c);
+
+ url = xmalloc(strlen(remote->url) + 50);
+ request->url = xmalloc(strlen(remote->url) + 50);
+ strcpy(url, remote->url);
+ posn = url + strlen(remote->url);
strcpy(posn, "objects/");
posn += 8;
memcpy(posn, hex, 2);
posn += 2;
*(posn++) = '/';
strcpy(posn, hex + 2);
+ strcpy(request->url, url);
+
+ /* If a previous temp file is present, process what was already
+ fetched. */
+ prevlocal = open(prevfile, O_RDONLY);
+ if (prevlocal != -1) {
+ do {
+ prev_read = read(prevlocal, prev_buf, PREV_BUF_SIZE);
+ if (prev_read>0) {
+ if (fwrite_sha1_file(prev_buf,
+ 1,
+ prev_read,
+ request) == prev_read) {
+ prev_posn += prev_read;
+ } else {
+ prev_read = -1;
+ }
+ }
+ } while (prev_read > 0);
+ close(prevlocal);
+ }
+ unlink(prevfile);
+
+ /* Reset inflate/SHA1 if there was an error reading the previous temp
+ file; also rewind to the beginning of the local file. */
+ if (prev_read == -1) {
+ memset(&request->stream, 0, sizeof(request->stream));
+ inflateInit(&request->stream);
+ SHA1_Init(&request->c);
+ if (prev_posn>0) {
+ prev_posn = 0;
+ lseek(request->local_fileno, SEEK_SET, 0);
+ ftruncate(request->local_fileno, 0);
+ }
+ }
slot = get_active_slot();
slot->callback_func = process_response;
slot->callback_data = request;
+ request->slot = slot;
+
+ curl_easy_setopt(slot->curl, CURLOPT_FILE, request);
+ curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_sha1_file);
curl_easy_setopt(slot->curl, CURLOPT_ERRORBUFFER, request->errorstr);
- curl_easy_setopt(slot->curl, CURLOPT_URL, request->url);
- curl_easy_setopt(slot->curl, CURLOPT_NOBODY, 1);
+ curl_easy_setopt(slot->curl, CURLOPT_URL, url);
+ curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, no_pragma_header);
- if (start_active_slot(slot)) {
- request->slot = slot;
- request->state = RUN_HEAD;
- } else {
- request->state = ABORTED;
- free(request->url);
- request->url = NULL;
+ /* If we have successfully processed data from a previous fetch
+ attempt, only fetch the data we don't already have. */
+ if (prev_posn>0) {
+ if (push_verbosely)
+ fprintf(stderr,
+ "Resuming fetch of object %s at byte %ld\n",
+ hex, prev_posn);
+ sprintf(range, "Range: bytes=%ld-", prev_posn);
+ range_header = curl_slist_append(range_header, range);
+ curl_easy_setopt(slot->curl,
+ CURLOPT_HTTPHEADER, range_header);
+ }
+
+ /* Try to get the request started, abort the request on error */
+ request->state = RUN_FETCH_LOOSE;
+ if (!start_active_slot(slot)) {
+ fprintf(stderr, "Unable to start GET request\n");
+ remote->can_update_info_refs = 0;
+ release_request(request);
+ }
+}
+
+static void start_fetch_packed(struct transfer_request *request)
+{
+ char *url;
+ struct packed_git *target;
+ FILE *packfile;
+ char *filename;
+ long prev_posn = 0;
+ char range[RANGE_HEADER_SIZE];
+ struct curl_slist *range_header = NULL;
+
+ struct transfer_request *check_request = request_queue_head;
+ struct active_request_slot *slot;
+
+ target = find_sha1_pack(request->obj->sha1, remote->packs);
+ if (!target) {
+ fprintf(stderr, "Unable to fetch %s, will not be able to update server info refs\n", sha1_to_hex(request->obj->sha1));
+ remote->can_update_info_refs = 0;
+ release_request(request);
+ return;
+ }
+
+ fprintf(stderr, "Fetching pack %s\n", sha1_to_hex(target->sha1));
+ fprintf(stderr, " which contains %s\n", sha1_to_hex(request->obj->sha1));
+
+ filename = sha1_pack_name(target->sha1);
+ snprintf(request->filename, sizeof(request->filename), "%s", filename);
+ snprintf(request->tmpfile, sizeof(request->tmpfile),
+ "%s.temp", filename);
+
+ url = xmalloc(strlen(remote->url) + 64);
+ sprintf(url, "%sobjects/pack/pack-%s.pack",
+ remote->url, sha1_to_hex(target->sha1));
+
+ /* Make sure there isn't another open request for this pack */
+ while (check_request) {
+ if (check_request->state == RUN_FETCH_PACKED &&
+ !strcmp(check_request->url, url)) {
+ free(url);
+ release_request(request);
+ return;
+ }
+ check_request = check_request->next;
+ }
+
+ packfile = fopen(request->tmpfile, "a");
+ if (!packfile) {
+ fprintf(stderr, "Unable to open local file %s for pack",
+ filename);
+ remote->can_update_info_refs = 0;
+ free(url);
+ return;
+ }
+
+ slot = get_active_slot();
+ slot->callback_func = process_response;
+ slot->callback_data = request;
+ request->slot = slot;
+ request->local_stream = packfile;
+ request->userData = target;
+
+ request->url = url;
+ curl_easy_setopt(slot->curl, CURLOPT_FILE, packfile);
+ curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite);
+ curl_easy_setopt(slot->curl, CURLOPT_URL, url);
+ curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, no_pragma_header);
+ slot->local = packfile;
+
+ /* If there is data present from a previous transfer attempt,
+ resume where it left off */
+ prev_posn = ftell(packfile);
+ if (prev_posn>0) {
+ if (push_verbosely)
+ fprintf(stderr,
+ "Resuming fetch of pack %s at byte %ld\n",
+ sha1_to_hex(target->sha1), prev_posn);
+ sprintf(range, "Range: bytes=%ld-", prev_posn);
+ range_header = curl_slist_append(range_header, range);
+ curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, range_header);
+ }
+
+ /* Try to get the request started, abort the request on error */
+ request->state = RUN_FETCH_PACKED;
+ if (!start_active_slot(slot)) {
+ fprintf(stderr, "Unable to start GET request\n");
+ remote->can_update_info_refs = 0;
+ release_request(request);
}
}
static void start_mkcol(struct transfer_request *request)
{
- char *hex = sha1_to_hex(request->sha1);
+ char *hex = sha1_to_hex(request->obj->sha1);
struct active_request_slot *slot;
char *posn;
@@ -203,7 +468,7 @@ static void start_mkcol(struct transfer_request *request)
static void start_put(struct transfer_request *request)
{
- char *hex = sha1_to_hex(request->sha1);
+ char *hex = sha1_to_hex(request->obj->sha1);
struct active_request_slot *slot;
char *posn;
char type[20];
@@ -214,7 +479,7 @@ static void start_put(struct transfer_request *request)
ssize_t size;
z_stream stream;
- unpacked = read_sha1_file(request->sha1, type, &len);
+ unpacked = read_sha1_file(request->obj->sha1, type, &len);
hdrlen = sprintf(hdr, "%s %lu", type, len) + 1;
/* Set it up */
@@ -309,9 +574,10 @@ static void start_move(struct transfer_request *request)
}
}
-static int refresh_lock(struct active_lock *lock)
+static int refresh_lock(struct remote_lock *lock)
{
struct active_request_slot *slot;
+ struct slot_results results;
char *if_header;
char timeout_header[25];
struct curl_slist *dav_headers = NULL;
@@ -326,6 +592,7 @@ static int refresh_lock(struct active_lock *lock)
dav_headers = curl_slist_append(dav_headers, timeout_header);
slot = get_active_slot();
+ slot->results = &results;
curl_easy_setopt(slot->curl, CURLOPT_HTTPGET, 1);
curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_null);
curl_easy_setopt(slot->curl, CURLOPT_URL, lock->url);
@@ -334,8 +601,9 @@ static int refresh_lock(struct active_lock *lock)
if (start_active_slot(slot)) {
run_active_slot(slot);
- if (slot->curl_result != CURLE_OK) {
- fprintf(stderr, "Got HTTP error %ld\n", slot->http_code);
+ if (results.curl_result != CURLE_OK) {
+ fprintf(stderr, "LOCK HTTP error %ld\n",
+ results.http_code);
} else {
lock->start_time = time(NULL);
rc = 1;
@@ -349,24 +617,62 @@ static int refresh_lock(struct active_lock *lock)
return rc;
}
-static void finish_request(struct transfer_request *request)
+static void check_locks()
{
+ struct remote_lock *lock = remote->locks;
time_t current_time = time(NULL);
int time_remaining;
- request->curl_result = request->slot->curl_result;
+ while (lock) {
+ time_remaining = lock->start_time + lock->timeout -
+ current_time;
+ if (!lock->refreshing && time_remaining < LOCK_REFRESH) {
+ if (!refresh_lock(lock)) {
+ fprintf(stderr,
+ "Unable to refresh lock for %s\n",
+ lock->url);
+ aborted = 1;
+ return;
+ }
+ }
+ lock = lock->next;
+ }
+}
+
+static void release_request(struct transfer_request *request)
+{
+ struct transfer_request *entry = request_queue_head;
+
+ if (request == request_queue_head) {
+ request_queue_head = request->next;
+ } else {
+ while (entry->next != NULL && entry->next != request)
+ entry = entry->next;
+ if (entry->next == request)
+ entry->next = entry->next->next;
+ }
+
+ if (request->local_fileno != -1)
+ close(request->local_fileno);
+ if (request->local_stream)
+ fclose(request->local_stream);
+ if (request->url != NULL)
+ free(request->url);
+ free(request);
+}
+
+static void finish_request(struct transfer_request *request)
+{
+ struct stat st;
+ struct packed_git *target;
+ struct packed_git **lst;
+
+ request->curl_result = request->slot->curl_result;
request->http_code = request->slot->http_code;
request->slot = NULL;
- /* Refresh the lock if it is close to timing out */
- time_remaining = request->lock->start_time + request->lock->timeout
- - current_time;
- if (time_remaining < LOCK_REFRESH && !request->lock->refreshing) {
- if (!refresh_lock(request->lock)) {
- fprintf(stderr, "Unable to refresh remote lock\n");
- aborted = 1;
- }
- }
+ /* Keep locks active */
+ check_locks();
if (request->headers != NULL)
curl_slist_free_all(request->headers);
@@ -375,29 +681,16 @@ static void finish_request(struct transfer_request *request)
if (request->state != RUN_PUT) {
free(request->url);
request->url = NULL;
- }
-
- if (request->state == RUN_HEAD) {
- if (request->http_code == 404) {
- request->state = NEED_PUSH;
- } else if (request->curl_result == CURLE_OK) {
- remote_dir_exists[request->sha1[0]] = 1;
- request->state = COMPLETE;
- } else {
- fprintf(stderr, "HEAD %s failed, aborting (%d/%ld)\n",
- sha1_to_hex(request->sha1),
- request->curl_result, request->http_code);
- request->state = ABORTED;
- aborted = 1;
- }
- } else if (request->state == RUN_MKCOL) {
+ }
+
+ if (request->state == RUN_MKCOL) {
if (request->curl_result == CURLE_OK ||
request->http_code == 405) {
- remote_dir_exists[request->sha1[0]] = 1;
+ remote_dir_exists[request->obj->sha1[0]] = 1;
start_put(request);
} else {
fprintf(stderr, "MKCOL %s failed, aborting (%d/%ld)\n",
- sha1_to_hex(request->sha1),
+ sha1_to_hex(request->obj->sha1),
request->curl_result, request->http_code);
request->state = ABORTED;
aborted = 1;
@@ -407,7 +700,7 @@ static void finish_request(struct transfer_request *request)
start_move(request);
} else {
fprintf(stderr, "PUT %s failed, aborting (%d/%ld)\n",
- sha1_to_hex(request->sha1),
+ sha1_to_hex(request->obj->sha1),
request->curl_result, request->http_code);
request->state = ABORTED;
aborted = 1;
@@ -415,41 +708,84 @@ static void finish_request(struct transfer_request *request)
} else if (request->state == RUN_MOVE) {
if (request->curl_result == CURLE_OK) {
if (push_verbosely)
- fprintf(stderr,
- "sent %s\n",
- sha1_to_hex(request->sha1));
- request->state = COMPLETE;
+ fprintf(stderr, " sent %s\n",
+ sha1_to_hex(request->obj->sha1));
+ request->obj->flags |= REMOTE;
+ release_request(request);
} else {
fprintf(stderr, "MOVE %s failed, aborting (%d/%ld)\n",
- sha1_to_hex(request->sha1),
+ sha1_to_hex(request->obj->sha1),
request->curl_result, request->http_code);
request->state = ABORTED;
aborted = 1;
}
- }
-}
+ } else if (request->state == RUN_FETCH_LOOSE) {
+ fchmod(request->local_fileno, 0444);
+ close(request->local_fileno); request->local_fileno = -1;
+
+ if (request->curl_result != CURLE_OK &&
+ request->http_code != 416) {
+ if (stat(request->tmpfile, &st) == 0) {
+ if (st.st_size == 0)
+ unlink(request->tmpfile);
+ }
+ } else {
+ if (request->http_code == 416)
+ fprintf(stderr, "Warning: requested range invalid; we may already have all the data.\n");
+
+ inflateEnd(&request->stream);
+ SHA1_Final(request->real_sha1, &request->c);
+ if (request->zret != Z_STREAM_END) {
+ unlink(request->tmpfile);
+ } else if (memcmp(request->obj->sha1, request->real_sha1, 20)) {
+ unlink(request->tmpfile);
+ } else {
+ request->rename =
+ move_temp_to_file(
+ request->tmpfile,
+ request->filename);
+ if (request->rename == 0) {
+ request->obj->flags |= (LOCAL | REMOTE);
+ }
+ }
+ }
-static void release_request(struct transfer_request *request)
-{
- struct transfer_request *entry = request_queue_head;
+ /* Try fetching packed if necessary */
+ if (request->obj->flags & LOCAL)
+ release_request(request);
+ else
+ start_fetch_packed(request);
- if (request == request_queue_head) {
- request_queue_head = request->next;
- } else {
- while (entry->next != NULL && entry->next != request)
- entry = entry->next;
- if (entry->next == request)
- entry->next = entry->next->next;
+ } else if (request->state == RUN_FETCH_PACKED) {
+ if (request->curl_result != CURLE_OK) {
+ fprintf(stderr, "Unable to get pack file %s\n%s",
+ request->url, curl_errorstr);
+ remote->can_update_info_refs = 0;
+ } else {
+ fclose(request->local_stream);
+ request->local_stream = NULL;
+ if (!move_temp_to_file(request->tmpfile,
+ request->filename)) {
+ target = (struct packed_git *)request->userData;
+ lst = &remote->packs;
+ while (*lst != target)
+ lst = &((*lst)->next);
+ *lst = (*lst)->next;
+
+ if (!verify_pack(target, 0))
+ install_packed_git(target);
+ else
+ remote->can_update_info_refs = 0;
+ }
+ }
+ release_request(request);
}
-
- if (request->url != NULL)
- free(request->url);
- free(request);
}
void fill_active_slots(void)
{
struct transfer_request *request = request_queue_head;
+ struct transfer_request *next;
struct active_request_slot *slot = active_queue_head;
int num_transfers;
@@ -457,17 +793,18 @@ void fill_active_slots(void)
return;
while (active_requests < max_requests && request != NULL) {
- if (!pushing && request->state == NEED_CHECK) {
- start_check(request);
- curl_multi_perform(curlm, &num_transfers);
+ next = request->next;
+ if (request->state == NEED_FETCH) {
+ start_fetch_loose(request);
} else if (pushing && request->state == NEED_PUSH) {
- if (remote_dir_exists[request->sha1[0]])
+ if (remote_dir_exists[request->obj->sha1[0]] == 1) {
start_put(request);
- else
+ } else {
start_mkcol(request);
+ }
curl_multi_perform(curlm, &num_transfers);
}
- request = request->next;
+ request = next;
}
while (slot != NULL) {
@@ -476,34 +813,80 @@ void fill_active_slots(void)
slot->curl = NULL;
}
slot = slot->next;
- }
+ }
}
-static void add_request(unsigned char *sha1, struct active_lock *lock)
+static void get_remote_object_list(unsigned char parent);
+
+static void add_fetch_request(struct object *obj)
+{
+ struct transfer_request *request;
+
+ check_locks();
+
+ /*
+ * Don't fetch the object if it's known to exist locally
+ * or is already in the request queue
+ */
+ if (remote_dir_exists[obj->sha1[0]] == -1)
+ get_remote_object_list(obj->sha1[0]);
+ if (obj->flags & (LOCAL | FETCHING))
+ return;
+
+ obj->flags |= FETCHING;
+ request = xmalloc(sizeof(*request));
+ request->obj = obj;
+ request->url = NULL;
+ request->lock = NULL;
+ request->headers = NULL;
+ request->local_fileno = -1;
+ request->local_stream = NULL;
+ request->state = NEED_FETCH;
+ request->next = request_queue_head;
+ request_queue_head = request;
+
+ fill_active_slots();
+ step_active_slots();
+}
+
+static int add_send_request(struct object *obj, struct remote_lock *lock)
{
struct transfer_request *request = request_queue_head;
struct packed_git *target;
-
- while (request != NULL && memcmp(request->sha1, sha1, 20))
- request = request->next;
- if (request != NULL)
- return;
- target = find_sha1_pack(sha1, remote->packs);
- if (target)
- return;
+ /* Keep locks active */
+ check_locks();
+
+ /*
+ * Don't push the object if it's known to exist on the remote
+ * or is already in the request queue
+ */
+ if (remote_dir_exists[obj->sha1[0]] == -1)
+ get_remote_object_list(obj->sha1[0]);
+ if (obj->flags & (REMOTE | PUSHING))
+ return 0;
+ target = find_sha1_pack(obj->sha1, remote->packs);
+ if (target) {
+ obj->flags |= REMOTE;
+ return 0;
+ }
+ obj->flags |= PUSHING;
request = xmalloc(sizeof(*request));
- memcpy(request->sha1, sha1, 20);
+ request->obj = obj;
request->url = NULL;
request->lock = lock;
request->headers = NULL;
- request->state = NEED_CHECK;
+ request->local_fileno = -1;
+ request->local_stream = NULL;
+ request->state = NEED_PUSH;
request->next = request_queue_head;
request_queue_head = request;
fill_active_slots();
step_active_slots();
+
+ return 1;
}
static int fetch_index(unsigned char *sha1)
@@ -518,16 +901,18 @@ static int fetch_index(unsigned char *sha1)
FILE *indexfile;
struct active_request_slot *slot;
+ struct slot_results results;
/* Don't use the index if the pack isn't there */
- url = xmalloc(strlen(remote->url) + 65);
- sprintf(url, "%s/objects/pack/pack-%s.pack", remote->url, hex);
+ url = xmalloc(strlen(remote->url) + 64);
+ sprintf(url, "%sobjects/pack/pack-%s.pack", remote->url, hex);
slot = get_active_slot();
+ slot->results = &results;
curl_easy_setopt(slot->curl, CURLOPT_URL, url);
curl_easy_setopt(slot->curl, CURLOPT_NOBODY, 1);
if (start_active_slot(slot)) {
run_active_slot(slot);
- if (slot->curl_result != CURLE_OK) {
+ if (results.curl_result != CURLE_OK) {
free(url);
return error("Unable to verify pack %s is available",
hex);
@@ -541,9 +926,9 @@ static int fetch_index(unsigned char *sha1)
if (push_verbosely)
fprintf(stderr, "Getting index for pack %s\n", hex);
-
- sprintf(url, "%s/objects/pack/pack-%s.idx", remote->url, hex);
-
+
+ sprintf(url, "%sobjects/pack/pack-%s.idx", remote->url, hex);
+
filename = sha1_pack_index_name(sha1);
snprintf(tmpfile, sizeof(tmpfile), "%s.temp", filename);
indexfile = fopen(tmpfile, "a");
@@ -552,6 +937,7 @@ static int fetch_index(unsigned char *sha1)
filename);
slot = get_active_slot();
+ slot->results = &results;
curl_easy_setopt(slot->curl, CURLOPT_NOBODY, 0);
curl_easy_setopt(slot->curl, CURLOPT_HTTPGET, 1);
curl_easy_setopt(slot->curl, CURLOPT_FILE, indexfile);
@@ -575,7 +961,7 @@ static int fetch_index(unsigned char *sha1)
if (start_active_slot(slot)) {
run_active_slot(slot);
- if (slot->curl_result != CURLE_OK) {
+ if (results.curl_result != CURLE_OK) {
free(url);
fclose(indexfile);
return error("Unable to get pack index %s\n%s", url,
@@ -615,6 +1001,7 @@ static int fetch_indices(void)
int i = 0;
struct active_request_slot *slot;
+ struct slot_results results;
data = xmalloc(4096);
memset(data, 0, 4096);
@@ -624,21 +1011,22 @@ static int fetch_indices(void)
if (push_verbosely)
fprintf(stderr, "Getting pack list\n");
-
- url = xmalloc(strlen(remote->url) + 21);
- sprintf(url, "%s/objects/info/packs", remote->url);
+
+ url = xmalloc(strlen(remote->url) + 20);
+ sprintf(url, "%sobjects/info/packs", remote->url);
slot = get_active_slot();
+ slot->results = &results;
curl_easy_setopt(slot->curl, CURLOPT_FILE, &buffer);
curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_buffer);
curl_easy_setopt(slot->curl, CURLOPT_URL, url);
curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, NULL);
if (start_active_slot(slot)) {
run_active_slot(slot);
- if (slot->curl_result != CURLE_OK) {
+ if (results.curl_result != CURLE_OK) {
free(buffer.buffer);
free(url);
- if (slot->http_code == 404)
+ if (results.http_code == 404)
return 0;
else
return error("%s", curl_errorstr);
@@ -698,14 +1086,13 @@ static char *quote_ref_url(const char *base, const char *ref)
int len, baselen, ch;
baselen = strlen(base);
- len = baselen + 12; /* "refs/heads/" + NUL */
+ len = baselen + 1;
for (cp = ref; (ch = *cp) != 0; cp++, len++)
if (needs_quote(ch))
len += 2; /* extra two hex plus replacement % */
qref = xmalloc(len);
memcpy(qref, base, baselen);
- memcpy(qref + baselen, "refs/heads/", 11);
- for (cp = ref, dp = qref + baselen + 11; (ch = *cp) != 0; cp++) {
+ for (cp = ref, dp = qref + baselen; (ch = *cp) != 0; cp++) {
if (needs_quote(ch)) {
*dp++ = '%';
*dp++ = hex((ch >> 4) & 0xF);
@@ -726,20 +1113,22 @@ int fetch_ref(char *ref, unsigned char *sha1)
struct buffer buffer;
char *base = remote->url;
struct active_request_slot *slot;
+ struct slot_results results;
buffer.size = 41;
buffer.posn = 0;
buffer.buffer = hex;
hex[41] = '\0';
-
+
url = quote_ref_url(base, ref);
slot = get_active_slot();
+ slot->results = &results;
curl_easy_setopt(slot->curl, CURLOPT_FILE, &buffer);
curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_buffer);
curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, NULL);
curl_easy_setopt(slot->curl, CURLOPT_URL, url);
if (start_active_slot(slot)) {
run_active_slot(slot);
- if (slot->curl_result != CURLE_OK)
+ if (results.curl_result != CURLE_OK)
return error("Couldn't get %s for %s\n%s",
url, ref, curl_errorstr);
} else {
@@ -751,6 +1140,27 @@ int fetch_ref(char *ref, unsigned char *sha1)
return 0;
}
+static void one_remote_object(const char *hex)
+{
+ unsigned char sha1[20];
+ struct object *obj;
+
+ if (get_sha1_hex(hex, sha1) != 0)
+ return;
+
+ obj = lookup_object(sha1);
+ if (!obj)
+ obj = parse_object(sha1);
+
+ /* Ignore remote objects that don't exist locally */
+ if (!obj)
+ return;
+
+ obj->flags |= REMOTE;
+ if (!object_list_contains(objects, obj))
+ add_object(obj, &objects, NULL, "");
+}
+
static void handle_lockprop_ctx(struct xml_ctx *ctx, int tag_closed)
{
int *lock_flags = (int *)ctx->userData;
@@ -772,7 +1182,7 @@ static void handle_lockprop_ctx(struct xml_ctx *ctx, int tag_closed)
static void handle_new_lock_ctx(struct xml_ctx *ctx, int tag_closed)
{
- struct active_lock *lock = (struct active_lock *)ctx->userData;
+ struct remote_lock *lock = (struct remote_lock *)ctx->userData;
if (tag_closed && ctx->cdata) {
if (!strcmp(ctx->name, DAV_ACTIVELOCK_OWNER)) {
@@ -791,6 +1201,8 @@ static void handle_new_lock_ctx(struct xml_ctx *ctx, int tag_closed)
}
}
+static void one_remote_ref(char *refname);
+
static void
xml_start_tag(void *userData, const char *name, const char **atts)
{
@@ -848,9 +1260,10 @@ xml_cdata(void *userData, const XML_Char *s, int len)
strncpy(ctx->cdata, s, len);
}
-static struct active_lock *lock_remote(char *file, long timeout)
+static struct remote_lock *lock_remote(char *path, long timeout)
{
struct active_request_slot *slot;
+ struct slot_results results;
struct buffer out_buffer;
struct buffer in_buffer;
char *out_data;
@@ -858,28 +1271,29 @@ static struct active_lock *lock_remote(char *file, long timeout)
char *url;
char *ep;
char timeout_header[25];
- struct active_lock *new_lock = NULL;
+ struct remote_lock *lock = NULL;
XML_Parser parser = XML_ParserCreate(NULL);
enum XML_Status result;
struct curl_slist *dav_headers = NULL;
struct xml_ctx ctx;
- url = xmalloc(strlen(remote->url) + strlen(file) + 1);
- sprintf(url, "%s%s", remote->url, file);
+ url = xmalloc(strlen(remote->url) + strlen(path) + 1);
+ sprintf(url, "%s%s", remote->url, path);
/* Make sure leading directories exist for the remote ref */
ep = strchr(url + strlen(remote->url) + 11, '/');
while (ep) {
*ep = 0;
slot = get_active_slot();
+ slot->results = &results;
curl_easy_setopt(slot->curl, CURLOPT_HTTPGET, 1);
curl_easy_setopt(slot->curl, CURLOPT_URL, url);
curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, DAV_MKCOL);
curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_null);
if (start_active_slot(slot)) {
run_active_slot(slot);
- if (slot->curl_result != CURLE_OK &&
- slot->http_code != 405) {
+ if (results.curl_result != CURLE_OK &&
+ results.http_code != 405) {
fprintf(stderr,
"Unable to create branch path %s\n",
url);
@@ -887,7 +1301,7 @@ static struct active_lock *lock_remote(char *file, long timeout)
return NULL;
}
} else {
- fprintf(stderr, "Unable to start request\n");
+ fprintf(stderr, "Unable to start MKCOL request\n");
free(url);
return NULL;
}
@@ -911,6 +1325,7 @@ static struct active_lock *lock_remote(char *file, long timeout)
dav_headers = curl_slist_append(dav_headers, "Content-Type: text/xml");
slot = get_active_slot();
+ slot->results = &results;
curl_easy_setopt(slot->curl, CURLOPT_INFILE, &out_buffer);
curl_easy_setopt(slot->curl, CURLOPT_INFILESIZE, out_buffer.size);
curl_easy_setopt(slot->curl, CURLOPT_READFUNCTION, fread_buffer);
@@ -921,20 +1336,17 @@ static struct active_lock *lock_remote(char *file, long timeout)
curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, DAV_LOCK);
curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, dav_headers);
- new_lock = xcalloc(1, sizeof(*new_lock));
- new_lock->owner = NULL;
- new_lock->token = NULL;
- new_lock->timeout = -1;
- new_lock->refreshing = 0;
+ lock = xcalloc(1, sizeof(*lock));
+ lock->timeout = -1;
if (start_active_slot(slot)) {
run_active_slot(slot);
- if (slot->curl_result == CURLE_OK) {
+ if (results.curl_result == CURLE_OK) {
ctx.name = xcalloc(10, 1);
ctx.len = 0;
ctx.cdata = NULL;
ctx.userFunc = handle_new_lock_ctx;
- ctx.userData = new_lock;
+ ctx.userData = lock;
XML_SetUserData(parser, &ctx);
XML_SetElementHandler(parser, xml_start_tag,
xml_end_tag);
@@ -946,36 +1358,40 @@ static struct active_lock *lock_remote(char *file, long timeout)
fprintf(stderr, "XML error: %s\n",
XML_ErrorString(
XML_GetErrorCode(parser)));
- new_lock->timeout = -1;
+ lock->timeout = -1;
}
}
} else {
- fprintf(stderr, "Unable to start request\n");
+ fprintf(stderr, "Unable to start LOCK request\n");
}
curl_slist_free_all(dav_headers);
free(out_data);
free(in_data);
- if (new_lock->token == NULL || new_lock->timeout <= 0) {
- if (new_lock->token != NULL)
- free(new_lock->token);
- if (new_lock->owner != NULL)
- free(new_lock->owner);
+ if (lock->token == NULL || lock->timeout <= 0) {
+ if (lock->token != NULL)
+ free(lock->token);
+ if (lock->owner != NULL)
+ free(lock->owner);
free(url);
- free(new_lock);
- new_lock = NULL;
+ free(lock);
+ lock = NULL;
} else {
- new_lock->url = url;
- new_lock->start_time = time(NULL);
+ lock->url = url;
+ lock->start_time = time(NULL);
+ lock->next = remote->locks;
+ remote->locks = lock;
}
- return new_lock;
+ return lock;
}
-static int unlock_remote(struct active_lock *lock)
+static int unlock_remote(struct remote_lock *lock)
{
struct active_request_slot *slot;
+ struct slot_results results;
+ struct remote_lock *prev = remote->locks;
char *lock_token_header;
struct curl_slist *dav_headers = NULL;
int rc = 0;
@@ -986,6 +1402,7 @@ static int unlock_remote(struct active_lock *lock)
dav_headers = curl_slist_append(dav_headers, lock_token_header);
slot = get_active_slot();
+ slot->results = &results;
curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_null);
curl_easy_setopt(slot->curl, CURLOPT_URL, lock->url);
curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, DAV_UNLOCK);
@@ -993,18 +1410,27 @@ static int unlock_remote(struct active_lock *lock)
if (start_active_slot(slot)) {
run_active_slot(slot);
- if (slot->curl_result == CURLE_OK)
+ if (results.curl_result == CURLE_OK)
rc = 1;
else
- fprintf(stderr, "Got HTTP error %ld\n",
- slot->http_code);
+ fprintf(stderr, "UNLOCK HTTP error %ld\n",
+ results.http_code);
} else {
- fprintf(stderr, "Unable to start request\n");
+ fprintf(stderr, "Unable to start UNLOCK request\n");
}
curl_slist_free_all(dav_headers);
free(lock_token_header);
+ if (remote->locks == lock) {
+ remote->locks = lock->next;
+ } else {
+ while (prev && prev->next != lock)
+ prev = prev->next;
+ if (prev)
+ prev->next = prev->next->next;
+ }
+
if (lock->owner != NULL)
free(lock->owner);
free(lock->url);
@@ -1014,9 +1440,180 @@ static int unlock_remote(struct active_lock *lock)
return rc;
}
+static void remote_ls(const char *path, int flags,
+ void (*userFunc)(struct remote_ls_ctx *ls),
+ void *userData);
+
+static void process_ls_object(struct remote_ls_ctx *ls)
+{
+ unsigned int *parent = (unsigned int *)ls->userData;
+ char *path = ls->dentry_name;
+ char *obj_hex;
+
+ if (!strcmp(ls->path, ls->dentry_name) && (ls->flags & IS_DIR)) {
+ remote_dir_exists[*parent] = 1;
+ return;
+ }
+
+ if (strlen(path) != 49)
+ return;
+ path += 8;
+ obj_hex = xmalloc(strlen(path));
+ strncpy(obj_hex, path, 2);
+ strcpy(obj_hex + 2, path + 3);
+ one_remote_object(obj_hex);
+ free(obj_hex);
+}
+
+static void process_ls_ref(struct remote_ls_ctx *ls)
+{
+ if (!strcmp(ls->path, ls->dentry_name) && (ls->dentry_flags & IS_DIR)) {
+ fprintf(stderr, " %s\n", ls->dentry_name);
+ return;
+ }
+
+ if (!(ls->dentry_flags & IS_DIR))
+ one_remote_ref(ls->dentry_name);
+}
+
+static void handle_remote_ls_ctx(struct xml_ctx *ctx, int tag_closed)
+{
+ struct remote_ls_ctx *ls = (struct remote_ls_ctx *)ctx->userData;
+
+ if (tag_closed) {
+ if (!strcmp(ctx->name, DAV_PROPFIND_RESP) && ls->dentry_name) {
+ if (ls->dentry_flags & IS_DIR) {
+ if (ls->flags & PROCESS_DIRS) {
+ ls->userFunc(ls);
+ }
+ if (strcmp(ls->dentry_name, ls->path) &&
+ ls->flags & RECURSIVE) {
+ remote_ls(ls->dentry_name,
+ ls->flags,
+ ls->userFunc,
+ ls->userData);
+ }
+ } else if (ls->flags & PROCESS_FILES) {
+ ls->userFunc(ls);
+ }
+ } else if (!strcmp(ctx->name, DAV_PROPFIND_NAME) && ctx->cdata) {
+ ls->dentry_name = xmalloc(strlen(ctx->cdata) -
+ remote->path_len + 1);
+ strcpy(ls->dentry_name, ctx->cdata + remote->path_len);
+ } else if (!strcmp(ctx->name, DAV_PROPFIND_COLLECTION)) {
+ ls->dentry_flags |= IS_DIR;
+ }
+ } else if (!strcmp(ctx->name, DAV_PROPFIND_RESP)) {
+ if (ls->dentry_name) {
+ free(ls->dentry_name);
+ }
+ ls->dentry_name = NULL;
+ ls->dentry_flags = 0;
+ }
+}
+
+static void remote_ls(const char *path, int flags,
+ void (*userFunc)(struct remote_ls_ctx *ls),
+ void *userData)
+{
+ char *url = xmalloc(strlen(remote->url) + strlen(path) + 1);
+ struct active_request_slot *slot;
+ struct slot_results results;
+ struct buffer in_buffer;
+ struct buffer out_buffer;
+ char *in_data;
+ char *out_data;
+ XML_Parser parser = XML_ParserCreate(NULL);
+ enum XML_Status result;
+ struct curl_slist *dav_headers = NULL;
+ struct xml_ctx ctx;
+ struct remote_ls_ctx ls;
+
+ ls.flags = flags;
+ ls.path = strdup(path);
+ ls.dentry_name = NULL;
+ ls.dentry_flags = 0;
+ ls.userData = userData;
+ ls.userFunc = userFunc;
+
+ sprintf(url, "%s%s", remote->url, path);
+
+ out_buffer.size = strlen(PROPFIND_ALL_REQUEST);
+ out_data = xmalloc(out_buffer.size + 1);
+ snprintf(out_data, out_buffer.size + 1, PROPFIND_ALL_REQUEST);
+ out_buffer.posn = 0;
+ out_buffer.buffer = out_data;
+
+ in_buffer.size = 4096;
+ in_data = xmalloc(in_buffer.size);
+ in_buffer.posn = 0;
+ in_buffer.buffer = in_data;
+
+ dav_headers = curl_slist_append(dav_headers, "Depth: 1");
+ dav_headers = curl_slist_append(dav_headers, "Content-Type: text/xml");
+
+ slot = get_active_slot();
+ slot->results = &results;
+ curl_easy_setopt(slot->curl, CURLOPT_INFILE, &out_buffer);
+ curl_easy_setopt(slot->curl, CURLOPT_INFILESIZE, out_buffer.size);
+ curl_easy_setopt(slot->curl, CURLOPT_READFUNCTION, fread_buffer);
+ curl_easy_setopt(slot->curl, CURLOPT_FILE, &in_buffer);
+ curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_buffer);
+ curl_easy_setopt(slot->curl, CURLOPT_URL, url);
+ curl_easy_setopt(slot->curl, CURLOPT_UPLOAD, 1);
+ curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, DAV_PROPFIND);
+ curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, dav_headers);
+
+ if (start_active_slot(slot)) {
+ run_active_slot(slot);
+ if (results.curl_result == CURLE_OK) {
+ ctx.name = xcalloc(10, 1);
+ ctx.len = 0;
+ ctx.cdata = NULL;
+ ctx.userFunc = handle_remote_ls_ctx;
+ ctx.userData = &ls;
+ XML_SetUserData(parser, &ctx);
+ XML_SetElementHandler(parser, xml_start_tag,
+ xml_end_tag);
+ XML_SetCharacterDataHandler(parser, xml_cdata);
+ result = XML_Parse(parser, in_buffer.buffer,
+ in_buffer.posn, 1);
+ free(ctx.name);
+
+ if (result != XML_STATUS_OK) {
+ fprintf(stderr, "XML error: %s\n",
+ XML_ErrorString(
+ XML_GetErrorCode(parser)));
+ }
+ }
+ } else {
+ fprintf(stderr, "Unable to start PROPFIND request\n");
+ }
+
+ free(ls.path);
+ free(url);
+ free(out_data);
+ free(in_buffer.buffer);
+ curl_slist_free_all(dav_headers);
+}
+
+static void get_remote_object_list(unsigned char parent)
+{
+ char path[] = "objects/XX/";
+ static const char hex[] = "0123456789abcdef";
+ unsigned int val = parent;
+
+ path[8] = hex[val >> 4];
+ path[9] = hex[val & 0xf];
+ remote_dir_exists[val] = 0;
+ remote_ls(path, (PROCESS_FILES | PROCESS_DIRS),
+ process_ls_object, &val);
+}
+
static int locking_available(void)
{
struct active_request_slot *slot;
+ struct slot_results results;
struct buffer in_buffer;
struct buffer out_buffer;
char *in_data;
@@ -1027,9 +1624,12 @@ static int locking_available(void)
struct xml_ctx ctx;
int lock_flags = 0;
- out_buffer.size = strlen(PROPFIND_REQUEST) + strlen(remote->url) - 2;
+ out_buffer.size =
+ strlen(PROPFIND_SUPPORTEDLOCK_REQUEST) +
+ strlen(remote->url) - 2;
out_data = xmalloc(out_buffer.size + 1);
- snprintf(out_data, out_buffer.size + 1, PROPFIND_REQUEST, remote->url);
+ snprintf(out_data, out_buffer.size + 1,
+ PROPFIND_SUPPORTEDLOCK_REQUEST, remote->url);
out_buffer.posn = 0;
out_buffer.buffer = out_data;
@@ -1040,8 +1640,9 @@ static int locking_available(void)
dav_headers = curl_slist_append(dav_headers, "Depth: 0");
dav_headers = curl_slist_append(dav_headers, "Content-Type: text/xml");
-
+
slot = get_active_slot();
+ slot->results = &results;
curl_easy_setopt(slot->curl, CURLOPT_INFILE, &out_buffer);
curl_easy_setopt(slot->curl, CURLOPT_INFILESIZE, out_buffer.size);
curl_easy_setopt(slot->curl, CURLOPT_READFUNCTION, fread_buffer);
@@ -1054,7 +1655,7 @@ static int locking_available(void)
if (start_active_slot(slot)) {
run_active_slot(slot);
- if (slot->curl_result == CURLE_OK) {
+ if (results.curl_result == CURLE_OK) {
ctx.name = xcalloc(10, 1);
ctx.len = 0;
ctx.cdata = NULL;
@@ -1075,7 +1676,7 @@ static int locking_available(void)
}
}
} else {
- fprintf(stderr, "Unable to start request\n");
+ fprintf(stderr, "Unable to start PROPFIND request\n");
}
free(out_data);
@@ -1085,87 +1686,105 @@ static int locking_available(void)
return lock_flags;
}
-static int is_ancestor(unsigned char *sha1, struct commit *commit)
+static struct object_list **process_blob(struct blob *blob,
+ struct object_list **p,
+ struct name_path *path,
+ const char *name)
{
- struct commit_list *parents;
+ struct object *obj = &blob->object;
- if (parse_commit(commit))
- return 0;
- parents = commit->parents;
- for (; parents; parents = parents->next) {
- if (!memcmp(sha1, parents->item->object.sha1, 20)) {
- return 1;
- } else if (parents->item->object.type == commit_type) {
- if (is_ancestor(
- sha1,
- (struct commit *)&parents->item->object
- ))
- return 1;
- }
- }
- return 0;
+ obj->flags |= LOCAL;
+
+ if (obj->flags & (UNINTERESTING | SEEN))
+ return p;
+
+ obj->flags |= SEEN;
+ return add_object(obj, p, path, name);
}
-static void get_delta(unsigned char *sha1, struct object *obj,
- struct active_lock *lock)
+static struct object_list **process_tree(struct tree *tree,
+ struct object_list **p,
+ struct name_path *path,
+ const char *name)
{
- struct commit *commit;
- struct commit_list *parents;
- struct tree *tree;
+ struct object *obj = &tree->object;
struct tree_entry_list *entry;
+ struct name_path me;
+
+ obj->flags |= LOCAL;
+
+ if (obj->flags & (UNINTERESTING | SEEN))
+ return p;
+ if (parse_tree(tree) < 0)
+ die("bad tree object %s", sha1_to_hex(obj->sha1));
+
+ obj->flags |= SEEN;
+ p = add_object(obj, p, NULL, name);
+ me.up = path;
+ me.elem = name;
+ me.elem_len = strlen(name);
+ entry = tree->entries;
+ tree->entries = NULL;
+ while (entry) {
+ struct tree_entry_list *next = entry->next;
+ if (entry->directory)
+ p = process_tree(entry->item.tree, p, &me, entry->name);
+ else
+ p = process_blob(entry->item.blob, p, &me, entry->name);
+ free(entry);
+ entry = next;
+ }
+ return p;
+}
- if (sha1 && !memcmp(sha1, obj->sha1, 20))
- return;
+static int get_delta(struct rev_info *revs, struct remote_lock *lock)
+{
+ struct commit *commit;
+ struct object_list **p = &objects, *pending;
+ int count = 0;
+
+ while ((commit = get_revision(revs)) != NULL) {
+ p = process_tree(commit->tree, p, NULL, "");
+ commit->object.flags |= LOCAL;
+ if (!(commit->object.flags & UNINTERESTING))
+ count += add_send_request(&commit->object, lock);
+ }
- if (aborted)
- return;
+ for (pending = revs->pending_objects; pending; pending = pending->next) {
+ struct object *obj = pending->item;
+ const char *name = pending->name;
- if (obj->type == commit_type) {
- if (push_verbosely)
- fprintf(stderr, "walk %s\n", sha1_to_hex(obj->sha1));
- add_request(obj->sha1, lock);
- commit = (struct commit *)obj;
- if (parse_commit(commit)) {
- fprintf(stderr, "Error parsing commit %s\n",
- sha1_to_hex(obj->sha1));
- aborted = 1;
- return;
+ if (obj->flags & (UNINTERESTING | SEEN))
+ continue;
+ if (obj->type == tag_type) {
+ obj->flags |= SEEN;
+ p = add_object(obj, p, NULL, name);
+ continue;
}
- parents = commit->parents;
- for (; parents; parents = parents->next)
- if (sha1 == NULL ||
- memcmp(sha1, parents->item->object.sha1, 20))
- get_delta(sha1, &parents->item->object,
- lock);
- get_delta(sha1, &commit->tree->object, lock);
- } else if (obj->type == tree_type) {
- if (push_verbosely)
- fprintf(stderr, "walk %s\n", sha1_to_hex(obj->sha1));
- add_request(obj->sha1, lock);
- tree = (struct tree *)obj;
- if (parse_tree(tree)) {
- fprintf(stderr, "Error parsing tree %s\n",
- sha1_to_hex(obj->sha1));
- aborted = 1;
- return;
+ if (obj->type == tree_type) {
+ p = process_tree((struct tree *)obj, p, NULL, name);
+ continue;
}
- entry = tree->entries;
- tree->entries = NULL;
- while (entry) {
- struct tree_entry_list *next = entry->next;
- get_delta(sha1, entry->item.any, lock);
- free(entry->name);
- free(entry);
- entry = next;
+ if (obj->type == blob_type) {
+ p = process_blob((struct blob *)obj, p, NULL, name);
+ continue;
}
- } else if (obj->type == blob_type || obj->type == tag_type) {
- add_request(obj->sha1, lock);
+ die("unknown pending object %s (%s)", sha1_to_hex(obj->sha1), name);
+ }
+
+ while (objects) {
+ if (!(objects->item->flags & UNINTERESTING))
+ count += add_send_request(objects->item, lock);
+ objects = objects->next;
}
+
+ return count;
}
-static int update_remote(unsigned char *sha1, struct active_lock *lock)
+static int update_remote(unsigned char *sha1, struct remote_lock *lock)
{
struct active_request_slot *slot;
+ struct slot_results results;
char *out_data;
char *if_header;
struct buffer out_buffer;
@@ -1187,6 +1806,7 @@ static int update_remote(unsigned char *sha1, struct active_lock *lock)
out_buffer.buffer = out_data;
slot = get_active_slot();
+ slot->results = &results;
curl_easy_setopt(slot->curl, CURLOPT_INFILE, &out_buffer);
curl_easy_setopt(slot->curl, CURLOPT_INFILESIZE, out_buffer.size);
curl_easy_setopt(slot->curl, CURLOPT_READFUNCTION, fread_buffer);
@@ -1201,10 +1821,10 @@ static int update_remote(unsigned char *sha1, struct active_lock *lock)
run_active_slot(slot);
free(out_data);
free(if_header);
- if (slot->curl_result != CURLE_OK) {
+ if (results.curl_result != CURLE_OK) {
fprintf(stderr,
"PUT error: curl result=%d, HTTP code=%ld\n",
- slot->curl_result, slot->http_code);
+ results.curl_result, results.http_code);
/* We should attempt recovery? */
return 0;
}
@@ -1218,38 +1838,295 @@ static int update_remote(unsigned char *sha1, struct active_lock *lock)
return 1;
}
+static struct ref *local_refs, **local_tail;
+static struct ref *remote_refs, **remote_tail;
+
+static int one_local_ref(const char *refname, const unsigned char *sha1)
+{
+ struct ref *ref;
+ int len = strlen(refname) + 1;
+ ref = xcalloc(1, sizeof(*ref) + len);
+ memcpy(ref->new_sha1, sha1, 20);
+ memcpy(ref->name, refname, len);
+ *local_tail = ref;
+ local_tail = &ref->next;
+ return 0;
+}
+
+static void one_remote_ref(char *refname)
+{
+ struct ref *ref;
+ unsigned char remote_sha1[20];
+ struct object *obj;
+
+ if (fetch_ref(refname, remote_sha1) != 0) {
+ fprintf(stderr,
+ "Unable to fetch ref %s from %s\n",
+ refname, remote->url);
+ return;
+ }
+
+ /*
+ * Fetch a copy of the object if it doesn't exist locally - it
+ * may be required for updating server info later.
+ */
+ if (remote->can_update_info_refs && !has_sha1_file(remote_sha1)) {
+ obj = lookup_unknown_object(remote_sha1);
+ if (obj) {
+ fprintf(stderr, " fetch %s for %s\n",
+ sha1_to_hex(remote_sha1), refname);
+ add_fetch_request(obj);
+ }
+ }
+
+ int len = strlen(refname) + 1;
+ ref = xcalloc(1, sizeof(*ref) + len);
+ memcpy(ref->old_sha1, remote_sha1, 20);
+ memcpy(ref->name, refname, len);
+ *remote_tail = ref;
+ remote_tail = &ref->next;
+}
+
+static void get_local_heads(void)
+{
+ local_tail = &local_refs;
+ for_each_ref(one_local_ref);
+}
+
+static void get_dav_remote_heads(void)
+{
+ remote_tail = &remote_refs;
+ remote_ls("refs/", (PROCESS_FILES | PROCESS_DIRS | RECURSIVE), process_ls_ref, NULL);
+}
+
+static int is_zero_sha1(const unsigned char *sha1)
+{
+ int i;
+
+ for (i = 0; i < 20; i++) {
+ if (*sha1++)
+ return 0;
+ }
+ return 1;
+}
+
+static void unmark_and_free(struct commit_list *list, unsigned int mark)
+{
+ while (list) {
+ struct commit_list *temp = list;
+ temp->item->object.flags &= ~mark;
+ list = temp->next;
+ free(temp);
+ }
+}
+
+static int ref_newer(const unsigned char *new_sha1,
+ const unsigned char *old_sha1)
+{
+ struct object *o;
+ struct commit *old, *new;
+ struct commit_list *list, *used;
+ int found = 0;
+
+ /* Both new and old must be commit-ish and new is descendant of
+ * old. Otherwise we require --force.
+ */
+ o = deref_tag(parse_object(old_sha1), NULL, 0);
+ if (!o || o->type != commit_type)
+ return 0;
+ old = (struct commit *) o;
+
+ o = deref_tag(parse_object(new_sha1), NULL, 0);
+ if (!o || o->type != commit_type)
+ return 0;
+ new = (struct commit *) o;
+
+ if (parse_commit(new) < 0)
+ return 0;
+
+ used = list = NULL;
+ commit_list_insert(new, &list);
+ while (list) {
+ new = pop_most_recent_commit(&list, TMP_MARK);
+ commit_list_insert(new, &used);
+ if (new == old) {
+ found = 1;
+ break;
+ }
+ }
+ unmark_and_free(list, TMP_MARK);
+ unmark_and_free(used, TMP_MARK);
+ return found;
+}
+
+static void mark_edge_parents_uninteresting(struct commit *commit)
+{
+ struct commit_list *parents;
+
+ for (parents = commit->parents; parents; parents = parents->next) {
+ struct commit *parent = parents->item;
+ if (!(parent->object.flags & UNINTERESTING))
+ continue;
+ mark_tree_uninteresting(parent->tree);
+ }
+}
+
+static void mark_edges_uninteresting(struct commit_list *list)
+{
+ for ( ; list; list = list->next) {
+ struct commit *commit = list->item;
+
+ if (commit->object.flags & UNINTERESTING) {
+ mark_tree_uninteresting(commit->tree);
+ continue;
+ }
+ mark_edge_parents_uninteresting(commit);
+ }
+}
+
+static void add_remote_info_ref(struct remote_ls_ctx *ls)
+{
+ struct buffer *buf = (struct buffer *)ls->userData;
+ unsigned char remote_sha1[20];
+ struct object *o;
+ int len;
+ char *ref_info;
+
+ if (fetch_ref(ls->dentry_name, remote_sha1) != 0) {
+ fprintf(stderr,
+ "Unable to fetch ref %s from %s\n",
+ ls->dentry_name, remote->url);
+ aborted = 1;
+ return;
+ }
+
+ o = parse_object(remote_sha1);
+ if (!o) {
+ fprintf(stderr,
+ "Unable to parse object %s for remote ref %s\n",
+ sha1_to_hex(remote_sha1), ls->dentry_name);
+ aborted = 1;
+ return;
+ }
+
+ len = strlen(ls->dentry_name) + 42;
+ ref_info = xcalloc(len + 1, 1);
+ sprintf(ref_info, "%s %s\n",
+ sha1_to_hex(remote_sha1), ls->dentry_name);
+ fwrite_buffer(ref_info, 1, len, buf);
+ free(ref_info);
+
+ if (o->type == tag_type) {
+ o = deref_tag(o, ls->dentry_name, 0);
+ if (o) {
+ len = strlen(ls->dentry_name) + 45;
+ ref_info = xcalloc(len + 1, 1);
+ sprintf(ref_info, "%s %s^{}\n",
+ sha1_to_hex(o->sha1), ls->dentry_name);
+ fwrite_buffer(ref_info, 1, len, buf);
+ free(ref_info);
+ }
+ }
+}
+
+static void update_remote_info_refs(struct remote_lock *lock)
+{
+ struct buffer buffer;
+ struct active_request_slot *slot;
+ struct slot_results results;
+ char *if_header;
+ struct curl_slist *dav_headers = NULL;
+
+ buffer.buffer = xmalloc(4096);
+ memset(buffer.buffer, 0, 4096);
+ buffer.size = 4096;
+ buffer.posn = 0;
+ remote_ls("refs/", (PROCESS_FILES | RECURSIVE),
+ add_remote_info_ref, &buffer);
+ if (!aborted) {
+ if_header = xmalloc(strlen(lock->token) + 25);
+ sprintf(if_header, "If: (<opaquelocktoken:%s>)", lock->token);
+ dav_headers = curl_slist_append(dav_headers, if_header);
+
+ slot = get_active_slot();
+ slot->results = &results;
+ curl_easy_setopt(slot->curl, CURLOPT_INFILE, &buffer);
+ curl_easy_setopt(slot->curl, CURLOPT_INFILESIZE, buffer.posn);
+ curl_easy_setopt(slot->curl, CURLOPT_READFUNCTION, fread_buffer);
+ curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_null);
+ curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, DAV_PUT);
+ curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, dav_headers);
+ curl_easy_setopt(slot->curl, CURLOPT_UPLOAD, 1);
+ curl_easy_setopt(slot->curl, CURLOPT_PUT, 1);
+ curl_easy_setopt(slot->curl, CURLOPT_URL, lock->url);
+
+ buffer.posn = 0;
+
+ if (start_active_slot(slot)) {
+ run_active_slot(slot);
+ if (results.curl_result != CURLE_OK) {
+ fprintf(stderr,
+ "PUT error: curl result=%d, HTTP code=%ld\n",
+ results.curl_result, results.http_code);
+ }
+ }
+ free(if_header);
+ }
+ free(buffer.buffer);
+}
+
+static int remote_exists(const char *path)
+{
+ char *url = xmalloc(strlen(remote->url) + strlen(path) + 1);
+ struct active_request_slot *slot;
+ struct slot_results results;
+
+ sprintf(url, "%s%s", remote->url, path);
+
+ slot = get_active_slot();
+ slot->results = &results;
+ curl_easy_setopt(slot->curl, CURLOPT_URL, url);
+ curl_easy_setopt(slot->curl, CURLOPT_NOBODY, 1);
+
+ if (start_active_slot(slot)) {
+ run_active_slot(slot);
+ if (results.http_code == 404)
+ return 0;
+ else if (results.curl_result == CURLE_OK)
+ return 1;
+ else
+ fprintf(stderr, "HEAD HTTP error %ld\n", results.http_code);
+ } else {
+ fprintf(stderr, "Unable to start HEAD request\n");
+ }
+
+ return -1;
+}
+
int main(int argc, char **argv)
{
struct transfer_request *request;
struct transfer_request *next_request;
int nr_refspec = 0;
char **refspec = NULL;
- int do_remote_update;
- int new_branch;
- int force_this;
- char *local_ref;
- unsigned char local_sha1[20];
- struct object *local_object = NULL;
- char *remote_ref = NULL;
- unsigned char remote_sha1[20];
- struct active_lock *remote_lock;
- char *remote_path = NULL;
+ struct remote_lock *ref_lock = NULL;
+ struct remote_lock *info_ref_lock = NULL;
+ struct rev_info revs;
+ int objects_to_send;
int rc = 0;
int i;
setup_git_directory();
setup_ident();
- remote = xmalloc(sizeof(*remote));
- remote->url = NULL;
- remote->packs = NULL;
+ remote = xcalloc(sizeof(*remote), 1);
argv++;
for (i = 1; i < argc; i++, argv++) {
char *arg = *argv;
if (*arg == '-') {
- if (!strcmp(arg, "--complete")) {
+ if (!strcmp(arg, "--all")) {
push_all = 1;
continue;
}
@@ -1265,6 +2142,12 @@ int main(int argc, char **argv)
}
if (!remote->url) {
remote->url = arg;
+ char *path = strstr(arg, "//");
+ if (path) {
+ path = index(path+2, '/');
+ if (path)
+ remote->path_len = strlen(path);
+ }
continue;
}
refspec = argv;
@@ -1275,7 +2158,7 @@ int main(int argc, char **argv)
if (!remote->url)
usage(http_push_usage);
- memset(remote_dir_exists, 0, 256);
+ memset(remote_dir_exists, -1, 256);
http_init();
@@ -1293,121 +2176,155 @@ int main(int argc, char **argv)
goto cleanup;
}
- /* Process each refspec */
- for (i = 0; i < nr_refspec; i++) {
- char *ep;
- force_this = 0;
- do_remote_update = 0;
- new_branch = 0;
- local_ref = refspec[i];
- if (*local_ref == '+') {
- force_this = 1;
- local_ref++;
+ /* Check whether the remote has server info files */
+ remote->can_update_info_refs = 0;
+ remote->has_info_refs = remote_exists("info/refs");
+ remote->has_info_packs = remote_exists("objects/info/packs");
+ if (remote->has_info_refs) {
+ info_ref_lock = lock_remote("info/refs", LOCK_TIME);
+ if (info_ref_lock)
+ remote->can_update_info_refs = 1;
+ }
+ if (remote->has_info_packs)
+ fetch_indices();
+
+ /* Get a list of all local and remote heads to validate refspecs */
+ get_local_heads();
+ fprintf(stderr, "Fetching remote heads...\n");
+ get_dav_remote_heads();
+
+ /* match them up */
+ if (!remote_tail)
+ remote_tail = &remote_refs;
+ if (match_refs(local_refs, remote_refs, &remote_tail,
+ nr_refspec, refspec, push_all))
+ return -1;
+ if (!remote_refs) {
+ fprintf(stderr, "No refs in common and none specified; doing nothing.\n");
+ return 0;
+ }
+
+ int new_refs = 0;
+ struct ref *ref;
+ for (ref = remote_refs; ref; ref = ref->next) {
+ char old_hex[60], *new_hex;
+ if (!ref->peer_ref)
+ continue;
+ if (!memcmp(ref->old_sha1, ref->peer_ref->new_sha1, 20)) {
+ if (push_verbosely || 1)
+ fprintf(stderr, "'%s': up-to-date\n", ref->name);
+ continue;
}
- ep = strchr(local_ref, ':');
- if (ep) {
- remote_ref = ep + 1;
- *ep = 0;
+
+ if (!force_all &&
+ !is_zero_sha1(ref->old_sha1) &&
+ !ref->force) {
+ if (!has_sha1_file(ref->old_sha1) ||
+ !ref_newer(ref->peer_ref->new_sha1,
+ ref->old_sha1)) {
+ /* We do not have the remote ref, or
+ * we know that the remote ref is not
+ * an ancestor of what we are trying to
+ * push. Either way this can be losing
+ * commits at the remote end and likely
+ * we were not up to date to begin with.
+ */
+ error("remote '%s' is not a strict "
+ "subset of local ref '%s'. "
+ "maybe you are not up-to-date and "
+ "need to pull first?",
+ ref->name,
+ ref->peer_ref->name);
+ rc = -2;
+ continue;
+ }
}
- else
- remote_ref = local_ref;
+ memcpy(ref->new_sha1, ref->peer_ref->new_sha1, 20);
+ if (is_zero_sha1(ref->new_sha1)) {
+ error("cannot happen anymore");
+ rc = -3;
+ continue;
+ }
+ new_refs++;
+ strcpy(old_hex, sha1_to_hex(ref->old_sha1));
+ new_hex = sha1_to_hex(ref->new_sha1);
+
+ fprintf(stderr, "updating '%s'", ref->name);
+ if (strcmp(ref->name, ref->peer_ref->name))
+ fprintf(stderr, " using '%s'", ref->peer_ref->name);
+ fprintf(stderr, "\n from %s\n to %s\n", old_hex, new_hex);
+
/* Lock remote branch ref */
- if (remote_path)
- free(remote_path);
- remote_path = xmalloc(strlen(remote_ref) + 12);
- sprintf(remote_path, "refs/heads/%s", remote_ref);
- remote_lock = lock_remote(remote_path, LOCK_TIME);
- if (remote_lock == NULL) {
+ ref_lock = lock_remote(ref->name, LOCK_TIME);
+ if (ref_lock == NULL) {
fprintf(stderr, "Unable to lock remote branch %s\n",
- remote_ref);
+ ref->name);
rc = 1;
continue;
}
- /* Resolve local and remote refs */
- if (fetch_ref(remote_ref, remote_sha1) != 0) {
- fprintf(stderr,
- "Remote branch %s does not exist on %s\n",
- remote_ref, remote->url);
- new_branch = 1;
- }
- if (get_sha1(local_ref, local_sha1) != 0) {
- fprintf(stderr, "Error resolving local branch %s\n",
- local_ref);
- rc = 1;
- goto unlock;
+ /* Set up revision info for this refspec */
+ const char *commit_argv[4];
+ int commit_argc = 3;
+ char *new_sha1_hex = strdup(sha1_to_hex(ref->new_sha1));
+ char *old_sha1_hex = NULL;
+ commit_argv[1] = "--objects";
+ commit_argv[2] = new_sha1_hex;
+ if (!push_all && !is_zero_sha1(ref->old_sha1)) {
+ old_sha1_hex = xmalloc(42);
+ sprintf(old_sha1_hex, "^%s",
+ sha1_to_hex(ref->old_sha1));
+ commit_argv[3] = old_sha1_hex;
+ commit_argc++;
}
-
- /* Find relationship between local and remote */
- local_object = parse_object(local_sha1);
- if (!local_object) {
- fprintf(stderr, "Unable to parse local object %s\n",
- sha1_to_hex(local_sha1));
- rc = 1;
- goto unlock;
- } else if (new_branch) {
- do_remote_update = 1;
- } else {
- if (!memcmp(local_sha1, remote_sha1, 20)) {
- fprintf(stderr,
- "* %s: same as branch '%s' of %s\n",
- local_ref, remote_ref, remote->url);
- } else if (is_ancestor(remote_sha1,
- (struct commit *)local_object)) {
- fprintf(stderr,
- "Remote %s will fast-forward to local %s\n",
- remote_ref, local_ref);
- do_remote_update = 1;
- } else if (force_all || force_this) {
- fprintf(stderr,
- "* %s on %s does not fast forward to local branch '%s', overwriting\n",
- remote_ref, remote->url, local_ref);
- do_remote_update = 1;
- } else {
- fprintf(stderr,
- "* %s on %s does not fast forward to local branch '%s'\n",
- remote_ref, remote->url, local_ref);
- rc = 1;
- goto unlock;
- }
+ setup_revisions(commit_argc, commit_argv, &revs, NULL);
+ free(new_sha1_hex);
+ if (old_sha1_hex) {
+ free(old_sha1_hex);
+ commit_argv[1] = NULL;
}
- /* Generate and check list of required objects */
+ /* Generate a list of objects that need to be pushed */
pushing = 0;
- if (do_remote_update || push_all)
- fetch_indices();
- get_delta(push_all ? NULL : remote_sha1,
- local_object, remote_lock);
+ prepare_revision_walk(&revs);
+ mark_edges_uninteresting(revs.commits);
+ objects_to_send = get_delta(&revs, ref_lock);
finish_all_active_slots();
/* Push missing objects to remote, this would be a
convenient time to pack them first if appropriate. */
pushing = 1;
+ if (objects_to_send)
+ fprintf(stderr, " sending %d objects\n",
+ objects_to_send);
fill_active_slots();
finish_all_active_slots();
/* Update the remote branch if all went well */
- if (do_remote_update) {
- if (!aborted && update_remote(local_sha1,
- remote_lock)) {
- fprintf(stderr, "%s remote branch %s\n",
- new_branch ? "Created" : "Updated",
- remote_ref);
- } else {
- fprintf(stderr,
- "Unable to %s remote branch %s\n",
- new_branch ? "create" : "update",
- remote_ref);
- rc = 1;
- goto unlock;
- }
+ if (aborted || !update_remote(ref->new_sha1, ref_lock)) {
+ rc = 1;
+ goto unlock;
}
unlock:
- unlock_remote(remote_lock);
- free(remote_path);
+ if (!rc)
+ fprintf(stderr, " done\n");
+ unlock_remote(ref_lock);
+ check_locks();
+ }
+
+ /* Update remote server info if appropriate */
+ if (remote->has_info_refs && new_refs) {
+ if (info_ref_lock && remote->can_update_info_refs) {
+ fprintf(stderr, "Updating remote server info\n");
+ update_remote_info_refs(info_ref_lock);
+ } else {
+ fprintf(stderr, "Unable to update server info\n");
+ }
}
+ if (info_ref_lock)
+ unlock_remote(info_ref_lock);
cleanup:
free(remote);
diff --git a/http.c b/http.c
index 14a7669cd4..9604e3326b 100644
--- a/http.c
+++ b/http.c
@@ -339,6 +339,7 @@ struct active_request_slot *get_active_slot(void)
slot->in_use = 1;
slot->local = NULL;
slot->results = NULL;
+ slot->finished = NULL;
slot->callback_data = NULL;
slot->callback_func = NULL;
curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, pragma_header);
@@ -389,8 +390,10 @@ void run_active_slot(struct active_request_slot *slot)
fd_set excfds;
int max_fd;
struct timeval select_timeout;
+ int finished = 0;
- while (slot->in_use) {
+ slot->finished = &finished;
+ while (!finished) {
data_received = 0;
step_active_slots();
@@ -442,6 +445,9 @@ static void finish_active_slot(struct active_request_slot *slot)
closedown_active_slot(slot);
curl_easy_getinfo(slot->curl, CURLINFO_HTTP_CODE, &slot->http_code);
+ if (slot->finished != NULL)
+ (*slot->finished) = 1;
+
/* Store slot results so they can be read after the slot is reused */
if (slot->results != NULL) {
slot->results->curl_result = slot->curl_result;
diff --git a/http.h b/http.h
index 36fa154d2f..9ca16acec2 100644
--- a/http.h
+++ b/http.h
@@ -35,6 +35,7 @@ struct active_request_slot
int in_use;
CURLcode curl_result;
long http_code;
+ int *finished;
struct slot_results *results;
void *callback_data;
void (*callback_func)(void *data);
diff --git a/imap-send.c b/imap-send.c
new file mode 100644
index 0000000000..1b38b3af67
--- /dev/null
+++ b/imap-send.c
@@ -0,0 +1,1359 @@
+/*
+ * git-imap-send - drops patches into an imap Drafts folder
+ * derived from isync/mbsync - mailbox synchronizer
+ *
+ * Copyright (C) 2000-2002 Michael R. Elkins <me@mutt.org>
+ * Copyright (C) 2002-2004 Oswald Buddenhagen <ossi@users.sf.net>
+ * Copyright (C) 2004 Theodore Y. Ts'o <tytso@mit.edu>
+ * Copyright (C) 2006 Mike McCormack
+ *
+ * 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
+ */
+
+#include "cache.h"
+
+#include <assert.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <arpa/inet.h>
+#include <sys/socket.h>
+#include <netdb.h>
+
+typedef struct store_conf {
+ char *name;
+ const char *path; /* should this be here? its interpretation is driver-specific */
+ char *map_inbox;
+ char *trash;
+ unsigned max_size; /* off_t is overkill */
+ unsigned trash_remote_new:1, trash_only_new:1;
+} store_conf_t;
+
+typedef struct string_list {
+ struct string_list *next;
+ char string[1];
+} string_list_t;
+
+typedef struct channel_conf {
+ struct channel_conf *next;
+ char *name;
+ store_conf_t *master, *slave;
+ char *master_name, *slave_name;
+ char *sync_state;
+ string_list_t *patterns;
+ int mops, sops;
+ unsigned max_messages; /* for slave only */
+} channel_conf_t;
+
+typedef struct group_conf {
+ struct group_conf *next;
+ char *name;
+ string_list_t *channels;
+} group_conf_t;
+
+/* For message->status */
+#define M_RECENT (1<<0) /* unsyncable flag; maildir_* depend on this being 1<<0 */
+#define M_DEAD (1<<1) /* expunged */
+#define M_FLAGS (1<<2) /* flags fetched */
+
+typedef struct message {
+ struct message *next;
+ /* string_list_t *keywords; */
+ size_t size; /* zero implies "not fetched" */
+ int uid;
+ unsigned char flags, status;
+} message_t;
+
+typedef struct store {
+ store_conf_t *conf; /* foreign */
+
+ /* currently open mailbox */
+ const char *name; /* foreign! maybe preset? */
+ char *path; /* own */
+ message_t *msgs; /* own */
+ int uidvalidity;
+ unsigned char opts; /* maybe preset? */
+ /* note that the following do _not_ reflect stats from msgs, but mailbox totals */
+ int count; /* # of messages */
+ int recent; /* # of recent messages - don't trust this beyond the initial read */
+} store_t;
+
+typedef struct {
+ char *data;
+ int len;
+ unsigned char flags;
+ unsigned char crlf:1;
+} msg_data_t;
+
+#define DRV_OK 0
+#define DRV_MSG_BAD -1
+#define DRV_BOX_BAD -2
+#define DRV_STORE_BAD -3
+
+static int Verbose, Quiet;
+
+static void info( const char *, ... );
+static void warn( const char *, ... );
+
+static char *next_arg( char ** );
+
+static void free_generic_messages( message_t * );
+
+static int nfvasprintf( char **str, const char *fmt, va_list va );
+static int nfsnprintf( char *buf, int blen, const char *fmt, ... );
+
+
+static void arc4_init( void );
+static unsigned char arc4_getbyte( void );
+
+typedef struct imap_server_conf {
+ char *name;
+ char *tunnel;
+ char *host;
+ int port;
+ char *user;
+ char *pass;
+} imap_server_conf_t;
+
+typedef struct imap_store_conf {
+ store_conf_t gen;
+ imap_server_conf_t *server;
+ unsigned use_namespace:1;
+} imap_store_conf_t;
+
+#define NIL (void*)0x1
+#define LIST (void*)0x2
+
+typedef struct _list {
+ struct _list *next, *child;
+ char *val;
+ int len;
+} list_t;
+
+typedef struct {
+ int fd;
+} Socket_t;
+
+typedef struct {
+ Socket_t sock;
+ int bytes;
+ int offset;
+ char buf[1024];
+} buffer_t;
+
+struct imap_cmd;
+
+typedef struct imap {
+ int uidnext; /* from SELECT responses */
+ list_t *ns_personal, *ns_other, *ns_shared; /* NAMESPACE info */
+ unsigned caps, rcaps; /* CAPABILITY results */
+ /* command queue */
+ int nexttag, num_in_progress, literal_pending;
+ struct imap_cmd *in_progress, **in_progress_append;
+ buffer_t buf; /* this is BIG, so put it last */
+} imap_t;
+
+typedef struct imap_store {
+ store_t gen;
+ int uidvalidity;
+ imap_t *imap;
+ const char *prefix;
+ unsigned /*currentnc:1,*/ trashnc:1;
+} imap_store_t;
+
+struct imap_cmd_cb {
+ int (*cont)( imap_store_t *ctx, struct imap_cmd *cmd, const char *prompt );
+ void (*done)( imap_store_t *ctx, struct imap_cmd *cmd, int response);
+ void *ctx;
+ char *data;
+ int dlen;
+ int uid;
+ unsigned create:1, trycreate:1;
+};
+
+struct imap_cmd {
+ struct imap_cmd *next;
+ struct imap_cmd_cb cb;
+ char *cmd;
+ int tag;
+};
+
+#define CAP(cap) (imap->caps & (1 << (cap)))
+
+enum CAPABILITY {
+ NOLOGIN = 0,
+ UIDPLUS,
+ LITERALPLUS,
+ NAMESPACE,
+};
+
+static const char *cap_list[] = {
+ "LOGINDISABLED",
+ "UIDPLUS",
+ "LITERAL+",
+ "NAMESPACE",
+};
+
+#define RESP_OK 0
+#define RESP_NO 1
+#define RESP_BAD 2
+
+static int get_cmd_result( imap_store_t *ctx, struct imap_cmd *tcmd );
+
+
+static const char *Flags[] = {
+ "Draft",
+ "Flagged",
+ "Answered",
+ "Seen",
+ "Deleted",
+};
+
+static void
+socket_perror( const char *func, Socket_t *sock, int ret )
+{
+ if (ret < 0)
+ perror( func );
+ else
+ fprintf( stderr, "%s: unexpected EOF\n", func );
+}
+
+static int
+socket_read( Socket_t *sock, char *buf, int len )
+{
+ int n = read( sock->fd, buf, len );
+ if (n <= 0) {
+ socket_perror( "read", sock, n );
+ close( sock->fd );
+ sock->fd = -1;
+ }
+ return n;
+}
+
+static int
+socket_write( Socket_t *sock, char *buf, int len )
+{
+ int n = write( sock->fd, buf, len );
+ if (n != len) {
+ socket_perror( "write", sock, n );
+ close( sock->fd );
+ sock->fd = -1;
+ }
+ return n;
+}
+
+/* simple line buffering */
+static int
+buffer_gets( buffer_t * b, char **s )
+{
+ int n;
+ int start = b->offset;
+
+ *s = b->buf + start;
+
+ for (;;) {
+ /* make sure we have enough data to read the \r\n sequence */
+ if (b->offset + 1 >= b->bytes) {
+ if (start) {
+ /* shift down used bytes */
+ *s = b->buf;
+
+ assert( start <= b->bytes );
+ n = b->bytes - start;
+
+ if (n)
+ memcpy( b->buf, b->buf + start, n );
+ b->offset -= start;
+ b->bytes = n;
+ start = 0;
+ }
+
+ n = socket_read( &b->sock, b->buf + b->bytes,
+ sizeof(b->buf) - b->bytes );
+
+ if (n <= 0)
+ return -1;
+
+ b->bytes += n;
+ }
+
+ if (b->buf[b->offset] == '\r') {
+ assert( b->offset + 1 < b->bytes );
+ if (b->buf[b->offset + 1] == '\n') {
+ b->buf[b->offset] = 0; /* terminate the string */
+ b->offset += 2; /* next line */
+ if (Verbose)
+ puts( *s );
+ return 0;
+ }
+ }
+
+ b->offset++;
+ }
+ /* not reached */
+}
+
+static void
+info( const char *msg, ... )
+{
+ va_list va;
+
+ if (!Quiet) {
+ va_start( va, msg );
+ vprintf( msg, va );
+ va_end( va );
+ fflush( stdout );
+ }
+}
+
+static void
+warn( const char *msg, ... )
+{
+ va_list va;
+
+ if (Quiet < 2) {
+ va_start( va, msg );
+ vfprintf( stderr, msg, va );
+ va_end( va );
+ }
+}
+
+static char *
+next_arg( char **s )
+{
+ char *ret;
+
+ if (!s || !*s)
+ return 0;
+ while (isspace( (unsigned char) **s ))
+ (*s)++;
+ if (!**s) {
+ *s = 0;
+ return 0;
+ }
+ if (**s == '"') {
+ ++*s;
+ ret = *s;
+ *s = strchr( *s, '"' );
+ } else {
+ ret = *s;
+ while (**s && !isspace( (unsigned char) **s ))
+ (*s)++;
+ }
+ if (*s) {
+ if (**s)
+ *(*s)++ = 0;
+ if (!**s)
+ *s = 0;
+ }
+ return ret;
+}
+
+static void
+free_generic_messages( message_t *msgs )
+{
+ message_t *tmsg;
+
+ for (; msgs; msgs = tmsg) {
+ tmsg = msgs->next;
+ free( msgs );
+ }
+}
+
+static int
+vasprintf( char **strp, const char *fmt, va_list ap )
+{
+ int len;
+ char tmp[1024];
+
+ if ((len = vsnprintf( tmp, sizeof(tmp), fmt, ap )) < 0 || !(*strp = xmalloc( len + 1 )))
+ return -1;
+ if (len >= (int)sizeof(tmp))
+ vsprintf( *strp, fmt, ap );
+ else
+ memcpy( *strp, tmp, len + 1 );
+ return len;
+}
+
+static int
+nfsnprintf( char *buf, int blen, const char *fmt, ... )
+{
+ int ret;
+ va_list va;
+
+ va_start( va, fmt );
+ if (blen <= 0 || (unsigned)(ret = vsnprintf( buf, blen, fmt, va )) >= (unsigned)blen)
+ die( "Fatal: buffer too small. Please report a bug.\n");
+ va_end( va );
+ return ret;
+}
+
+static int
+nfvasprintf( char **str, const char *fmt, va_list va )
+{
+ int ret = vasprintf( str, fmt, va );
+ if (ret < 0)
+ die( "Fatal: Out of memory\n");
+ return ret;
+}
+
+static struct {
+ unsigned char i, j, s[256];
+} rs;
+
+static void
+arc4_init( void )
+{
+ int i, fd;
+ unsigned char j, si, dat[128];
+
+ if ((fd = open( "/dev/urandom", O_RDONLY )) < 0 && (fd = open( "/dev/random", O_RDONLY )) < 0) {
+ fprintf( stderr, "Fatal: no random number source available.\n" );
+ exit( 3 );
+ }
+ if (read( fd, dat, 128 ) != 128) {
+ fprintf( stderr, "Fatal: cannot read random number source.\n" );
+ exit( 3 );
+ }
+ close( fd );
+
+ for (i = 0; i < 256; i++)
+ rs.s[i] = i;
+ for (i = j = 0; i < 256; i++) {
+ si = rs.s[i];
+ j += si + dat[i & 127];
+ rs.s[i] = rs.s[j];
+ rs.s[j] = si;
+ }
+ rs.i = rs.j = 0;
+
+ for (i = 0; i < 256; i++)
+ arc4_getbyte();
+}
+
+static unsigned char
+arc4_getbyte( void )
+{
+ unsigned char si, sj;
+
+ rs.i++;
+ si = rs.s[rs.i];
+ rs.j += si;
+ sj = rs.s[rs.j];
+ rs.s[rs.i] = sj;
+ rs.s[rs.j] = si;
+ return rs.s[(si + sj) & 0xff];
+}
+
+static struct imap_cmd *
+v_issue_imap_cmd( imap_store_t *ctx, struct imap_cmd_cb *cb,
+ const char *fmt, va_list ap )
+{
+ imap_t *imap = ctx->imap;
+ struct imap_cmd *cmd;
+ int n, bufl;
+ char buf[1024];
+
+ cmd = xmalloc( sizeof(struct imap_cmd) );
+ nfvasprintf( &cmd->cmd, fmt, ap );
+ cmd->tag = ++imap->nexttag;
+
+ if (cb)
+ cmd->cb = *cb;
+ else
+ memset( &cmd->cb, 0, sizeof(cmd->cb) );
+
+ while (imap->literal_pending)
+ get_cmd_result( ctx, 0 );
+
+ bufl = nfsnprintf( buf, sizeof(buf), cmd->cb.data ? CAP(LITERALPLUS) ?
+ "%d %s{%d+}\r\n" : "%d %s{%d}\r\n" : "%d %s\r\n",
+ cmd->tag, cmd->cmd, cmd->cb.dlen );
+ if (Verbose) {
+ if (imap->num_in_progress)
+ printf( "(%d in progress) ", imap->num_in_progress );
+ if (memcmp( cmd->cmd, "LOGIN", 5 ))
+ printf( ">>> %s", buf );
+ else
+ printf( ">>> %d LOGIN <user> <pass>\n", cmd->tag );
+ }
+ if (socket_write( &imap->buf.sock, buf, bufl ) != bufl) {
+ free( cmd->cmd );
+ free( cmd );
+ if (cb && cb->data)
+ free( cb->data );
+ return NULL;
+ }
+ if (cmd->cb.data) {
+ if (CAP(LITERALPLUS)) {
+ n = socket_write( &imap->buf.sock, cmd->cb.data, cmd->cb.dlen );
+ free( cmd->cb.data );
+ if (n != cmd->cb.dlen ||
+ (n = socket_write( &imap->buf.sock, "\r\n", 2 )) != 2)
+ {
+ free( cmd->cmd );
+ free( cmd );
+ return NULL;
+ }
+ cmd->cb.data = 0;
+ } else
+ imap->literal_pending = 1;
+ } else if (cmd->cb.cont)
+ imap->literal_pending = 1;
+ cmd->next = 0;
+ *imap->in_progress_append = cmd;
+ imap->in_progress_append = &cmd->next;
+ imap->num_in_progress++;
+ return cmd;
+}
+
+static struct imap_cmd *
+issue_imap_cmd( imap_store_t *ctx, struct imap_cmd_cb *cb, const char *fmt, ... )
+{
+ struct imap_cmd *ret;
+ va_list ap;
+
+ va_start( ap, fmt );
+ ret = v_issue_imap_cmd( ctx, cb, fmt, ap );
+ va_end( ap );
+ return ret;
+}
+
+static int
+imap_exec( imap_store_t *ctx, struct imap_cmd_cb *cb, const char *fmt, ... )
+{
+ va_list ap;
+ struct imap_cmd *cmdp;
+
+ va_start( ap, fmt );
+ cmdp = v_issue_imap_cmd( ctx, cb, fmt, ap );
+ va_end( ap );
+ if (!cmdp)
+ return RESP_BAD;
+
+ return get_cmd_result( ctx, cmdp );
+}
+
+static int
+imap_exec_m( imap_store_t *ctx, struct imap_cmd_cb *cb, const char *fmt, ... )
+{
+ va_list ap;
+ struct imap_cmd *cmdp;
+
+ va_start( ap, fmt );
+ cmdp = v_issue_imap_cmd( ctx, cb, fmt, ap );
+ va_end( ap );
+ if (!cmdp)
+ return DRV_STORE_BAD;
+
+ switch (get_cmd_result( ctx, cmdp )) {
+ case RESP_BAD: return DRV_STORE_BAD;
+ case RESP_NO: return DRV_MSG_BAD;
+ default: return DRV_OK;
+ }
+}
+
+static int
+is_atom( list_t *list )
+{
+ return list && list->val && list->val != NIL && list->val != LIST;
+}
+
+static int
+is_list( list_t *list )
+{
+ return list && list->val == LIST;
+}
+
+static void
+free_list( list_t *list )
+{
+ list_t *tmp;
+
+ for (; list; list = tmp) {
+ tmp = list->next;
+ if (is_list( list ))
+ free_list( list->child );
+ else if (is_atom( list ))
+ free( list->val );
+ free( list );
+ }
+}
+
+static int
+parse_imap_list_l( imap_t *imap, char **sp, list_t **curp, int level )
+{
+ list_t *cur;
+ char *s = *sp, *p;
+ int n, bytes;
+
+ for (;;) {
+ while (isspace( (unsigned char)*s ))
+ s++;
+ if (level && *s == ')') {
+ s++;
+ break;
+ }
+ *curp = cur = xmalloc( sizeof(*cur) );
+ curp = &cur->next;
+ cur->val = 0; /* for clean bail */
+ if (*s == '(') {
+ /* sublist */
+ s++;
+ cur->val = LIST;
+ if (parse_imap_list_l( imap, &s, &cur->child, level + 1 ))
+ goto bail;
+ } else if (imap && *s == '{') {
+ /* literal */
+ bytes = cur->len = strtol( s + 1, &s, 10 );
+ if (*s != '}')
+ goto bail;
+
+ s = cur->val = xmalloc( cur->len );
+
+ /* dump whats left over in the input buffer */
+ n = imap->buf.bytes - imap->buf.offset;
+
+ if (n > bytes)
+ /* the entire message fit in the buffer */
+ n = bytes;
+
+ memcpy( s, imap->buf.buf + imap->buf.offset, n );
+ s += n;
+ bytes -= n;
+
+ /* mark that we used part of the buffer */
+ imap->buf.offset += n;
+
+ /* now read the rest of the message */
+ while (bytes > 0) {
+ if ((n = socket_read (&imap->buf.sock, s, bytes)) <= 0)
+ goto bail;
+ s += n;
+ bytes -= n;
+ }
+
+ if (buffer_gets( &imap->buf, &s ))
+ goto bail;
+ } else if (*s == '"') {
+ /* quoted string */
+ s++;
+ p = s;
+ for (; *s != '"'; s++)
+ if (!*s)
+ goto bail;
+ cur->len = s - p;
+ s++;
+ cur->val = xmalloc( cur->len + 1 );
+ memcpy( cur->val, p, cur->len );
+ cur->val[cur->len] = 0;
+ } else {
+ /* atom */
+ p = s;
+ for (; *s && !isspace( (unsigned char)*s ); s++)
+ if (level && *s == ')')
+ break;
+ cur->len = s - p;
+ if (cur->len == 3 && !memcmp ("NIL", p, 3))
+ cur->val = NIL;
+ else {
+ cur->val = xmalloc( cur->len + 1 );
+ memcpy( cur->val, p, cur->len );
+ cur->val[cur->len] = 0;
+ }
+ }
+
+ if (!level)
+ break;
+ if (!*s)
+ goto bail;
+ }
+ *sp = s;
+ *curp = 0;
+ return 0;
+
+ bail:
+ *curp = 0;
+ return -1;
+}
+
+static list_t *
+parse_imap_list( imap_t *imap, char **sp )
+{
+ list_t *head;
+
+ if (!parse_imap_list_l( imap, sp, &head, 0 ))
+ return head;
+ free_list( head );
+ return NULL;
+}
+
+static list_t *
+parse_list( char **sp )
+{
+ return parse_imap_list( 0, sp );
+}
+
+static void
+parse_capability( imap_t *imap, char *cmd )
+{
+ char *arg;
+ unsigned i;
+
+ imap->caps = 0x80000000;
+ while ((arg = next_arg( &cmd )))
+ for (i = 0; i < ARRAY_SIZE(cap_list); i++)
+ if (!strcmp( cap_list[i], arg ))
+ imap->caps |= 1 << i;
+ imap->rcaps = imap->caps;
+}
+
+static int
+parse_response_code( imap_store_t *ctx, struct imap_cmd_cb *cb, char *s )
+{
+ imap_t *imap = ctx->imap;
+ char *arg, *p;
+
+ if (*s != '[')
+ return RESP_OK; /* no response code */
+ s++;
+ if (!(p = strchr( s, ']' ))) {
+ fprintf( stderr, "IMAP error: malformed response code\n" );
+ return RESP_BAD;
+ }
+ *p++ = 0;
+ arg = next_arg( &s );
+ if (!strcmp( "UIDVALIDITY", arg )) {
+ if (!(arg = next_arg( &s )) || !(ctx->gen.uidvalidity = atoi( arg ))) {
+ fprintf( stderr, "IMAP error: malformed UIDVALIDITY status\n" );
+ return RESP_BAD;
+ }
+ } else if (!strcmp( "UIDNEXT", arg )) {
+ if (!(arg = next_arg( &s )) || !(imap->uidnext = atoi( arg ))) {
+ fprintf( stderr, "IMAP error: malformed NEXTUID status\n" );
+ return RESP_BAD;
+ }
+ } else if (!strcmp( "CAPABILITY", arg )) {
+ parse_capability( imap, s );
+ } else if (!strcmp( "ALERT", arg )) {
+ /* RFC2060 says that these messages MUST be displayed
+ * to the user
+ */
+ for (; isspace( (unsigned char)*p ); p++);
+ fprintf( stderr, "*** IMAP ALERT *** %s\n", p );
+ } else if (cb && cb->ctx && !strcmp( "APPENDUID", arg )) {
+ if (!(arg = next_arg( &s )) || !(ctx->gen.uidvalidity = atoi( arg )) ||
+ !(arg = next_arg( &s )) || !(*(int *)cb->ctx = atoi( arg )))
+ {
+ fprintf( stderr, "IMAP error: malformed APPENDUID status\n" );
+ return RESP_BAD;
+ }
+ }
+ return RESP_OK;
+}
+
+static int
+get_cmd_result( imap_store_t *ctx, struct imap_cmd *tcmd )
+{
+ imap_t *imap = ctx->imap;
+ struct imap_cmd *cmdp, **pcmdp, *ncmdp;
+ char *cmd, *arg, *arg1, *p;
+ int n, resp, resp2, tag;
+
+ for (;;) {
+ if (buffer_gets( &imap->buf, &cmd ))
+ return RESP_BAD;
+
+ arg = next_arg( &cmd );
+ if (*arg == '*') {
+ arg = next_arg( &cmd );
+ if (!arg) {
+ fprintf( stderr, "IMAP error: unable to parse untagged response\n" );
+ return RESP_BAD;
+ }
+
+ if (!strcmp( "NAMESPACE", arg )) {
+ imap->ns_personal = parse_list( &cmd );
+ imap->ns_other = parse_list( &cmd );
+ imap->ns_shared = parse_list( &cmd );
+ } else if (!strcmp( "OK", arg ) || !strcmp( "BAD", arg ) ||
+ !strcmp( "NO", arg ) || !strcmp( "BYE", arg )) {
+ if ((resp = parse_response_code( ctx, 0, cmd )) != RESP_OK)
+ return resp;
+ } else if (!strcmp( "CAPABILITY", arg ))
+ parse_capability( imap, cmd );
+ else if ((arg1 = next_arg( &cmd ))) {
+ if (!strcmp( "EXISTS", arg1 ))
+ ctx->gen.count = atoi( arg );
+ else if (!strcmp( "RECENT", arg1 ))
+ ctx->gen.recent = atoi( arg );
+ } else {
+ fprintf( stderr, "IMAP error: unable to parse untagged response\n" );
+ return RESP_BAD;
+ }
+ } else if (!imap->in_progress) {
+ fprintf( stderr, "IMAP error: unexpected reply: %s %s\n", arg, cmd ? cmd : "" );
+ return RESP_BAD;
+ } else if (*arg == '+') {
+ /* This can happen only with the last command underway, as
+ it enforces a round-trip. */
+ cmdp = (struct imap_cmd *)((char *)imap->in_progress_append -
+ offsetof(struct imap_cmd, next));
+ if (cmdp->cb.data) {
+ n = socket_write( &imap->buf.sock, cmdp->cb.data, cmdp->cb.dlen );
+ free( cmdp->cb.data );
+ cmdp->cb.data = 0;
+ if (n != (int)cmdp->cb.dlen)
+ return RESP_BAD;
+ } else if (cmdp->cb.cont) {
+ if (cmdp->cb.cont( ctx, cmdp, cmd ))
+ return RESP_BAD;
+ } else {
+ fprintf( stderr, "IMAP error: unexpected command continuation request\n" );
+ return RESP_BAD;
+ }
+ if (socket_write( &imap->buf.sock, "\r\n", 2 ) != 2)
+ return RESP_BAD;
+ if (!cmdp->cb.cont)
+ imap->literal_pending = 0;
+ if (!tcmd)
+ return DRV_OK;
+ } else {
+ tag = atoi( arg );
+ for (pcmdp = &imap->in_progress; (cmdp = *pcmdp); pcmdp = &cmdp->next)
+ if (cmdp->tag == tag)
+ goto gottag;
+ fprintf( stderr, "IMAP error: unexpected tag %s\n", arg );
+ return RESP_BAD;
+ gottag:
+ if (!(*pcmdp = cmdp->next))
+ imap->in_progress_append = pcmdp;
+ imap->num_in_progress--;
+ if (cmdp->cb.cont || cmdp->cb.data)
+ imap->literal_pending = 0;
+ arg = next_arg( &cmd );
+ if (!strcmp( "OK", arg ))
+ resp = DRV_OK;
+ else {
+ if (!strcmp( "NO", arg )) {
+ if (cmdp->cb.create && cmd && (cmdp->cb.trycreate || !memcmp( cmd, "[TRYCREATE]", 11 ))) { /* SELECT, APPEND or UID COPY */
+ p = strchr( cmdp->cmd, '"' );
+ if (!issue_imap_cmd( ctx, 0, "CREATE \"%.*s\"", strchr( p + 1, '"' ) - p + 1, p )) {
+ resp = RESP_BAD;
+ goto normal;
+ }
+ /* not waiting here violates the spec, but a server that does not
+ grok this nonetheless violates it too. */
+ cmdp->cb.create = 0;
+ if (!(ncmdp = issue_imap_cmd( ctx, &cmdp->cb, "%s", cmdp->cmd ))) {
+ resp = RESP_BAD;
+ goto normal;
+ }
+ free( cmdp->cmd );
+ free( cmdp );
+ if (!tcmd)
+ return 0; /* ignored */
+ if (cmdp == tcmd)
+ tcmd = ncmdp;
+ continue;
+ }
+ resp = RESP_NO;
+ } else /*if (!strcmp( "BAD", arg ))*/
+ resp = RESP_BAD;
+ fprintf( stderr, "IMAP command '%s' returned response (%s) - %s\n",
+ memcmp (cmdp->cmd, "LOGIN", 5) ?
+ cmdp->cmd : "LOGIN <user> <pass>",
+ arg, cmd ? cmd : "");
+ }
+ if ((resp2 = parse_response_code( ctx, &cmdp->cb, cmd )) > resp)
+ resp = resp2;
+ normal:
+ if (cmdp->cb.done)
+ cmdp->cb.done( ctx, cmdp, resp );
+ if (cmdp->cb.data)
+ free( cmdp->cb.data );
+ free( cmdp->cmd );
+ free( cmdp );
+ if (!tcmd || tcmd == cmdp)
+ return resp;
+ }
+ }
+ /* not reached */
+}
+
+static void
+imap_close_server( imap_store_t *ictx )
+{
+ imap_t *imap = ictx->imap;
+
+ if (imap->buf.sock.fd != -1) {
+ imap_exec( ictx, 0, "LOGOUT" );
+ close( imap->buf.sock.fd );
+ }
+ free_list( imap->ns_personal );
+ free_list( imap->ns_other );
+ free_list( imap->ns_shared );
+ free( imap );
+}
+
+static void
+imap_close_store( store_t *ctx )
+{
+ imap_close_server( (imap_store_t *)ctx );
+ free_generic_messages( ctx->msgs );
+ free( ctx );
+}
+
+static store_t *
+imap_open_store( imap_server_conf_t *srvc )
+{
+ imap_store_t *ctx;
+ imap_t *imap;
+ char *arg, *rsp;
+ struct hostent *he;
+ struct sockaddr_in addr;
+ int s, a[2], preauth;
+
+ ctx = xcalloc( sizeof(*ctx), 1 );
+
+ ctx->imap = imap = xcalloc( sizeof(*imap), 1 );
+ imap->buf.sock.fd = -1;
+ imap->in_progress_append = &imap->in_progress;
+
+ /* open connection to IMAP server */
+
+ if (srvc->tunnel) {
+ info( "Starting tunnel '%s'... ", srvc->tunnel );
+
+ if (socketpair( PF_UNIX, SOCK_STREAM, 0, a )) {
+ perror( "socketpair" );
+ exit( 1 );
+ }
+
+ if (fork() == 0) {
+ if (dup2( a[0], 0 ) == -1 || dup2( a[0], 1 ) == -1)
+ _exit( 127 );
+ close( a[0] );
+ close( a[1] );
+ execl( "/bin/sh", "sh", "-c", srvc->tunnel, NULL );
+ _exit( 127 );
+ }
+
+ close (a[0]);
+
+ imap->buf.sock.fd = a[1];
+
+ info( "ok\n" );
+ } else {
+ memset( &addr, 0, sizeof(addr) );
+ addr.sin_port = htons( srvc->port );
+ addr.sin_family = AF_INET;
+
+ info( "Resolving %s... ", srvc->host );
+ he = gethostbyname( srvc->host );
+ if (!he) {
+ perror( "gethostbyname" );
+ goto bail;
+ }
+ info( "ok\n" );
+
+ addr.sin_addr.s_addr = *((int *) he->h_addr_list[0]);
+
+ s = socket( PF_INET, SOCK_STREAM, 0 );
+
+ info( "Connecting to %s:%hu... ", inet_ntoa( addr.sin_addr ), ntohs( addr.sin_port ) );
+ if (connect( s, (struct sockaddr *)&addr, sizeof(addr) )) {
+ close( s );
+ perror( "connect" );
+ goto bail;
+ }
+ info( "ok\n" );
+
+ imap->buf.sock.fd = s;
+
+ }
+
+ /* read the greeting string */
+ if (buffer_gets( &imap->buf, &rsp )) {
+ fprintf( stderr, "IMAP error: no greeting response\n" );
+ goto bail;
+ }
+ arg = next_arg( &rsp );
+ if (!arg || *arg != '*' || (arg = next_arg( &rsp )) == NULL) {
+ fprintf( stderr, "IMAP error: invalid greeting response\n" );
+ goto bail;
+ }
+ preauth = 0;
+ if (!strcmp( "PREAUTH", arg ))
+ preauth = 1;
+ else if (strcmp( "OK", arg ) != 0) {
+ fprintf( stderr, "IMAP error: unknown greeting response\n" );
+ goto bail;
+ }
+ parse_response_code( ctx, 0, rsp );
+ if (!imap->caps && imap_exec( ctx, 0, "CAPABILITY" ) != RESP_OK)
+ goto bail;
+
+ if (!preauth) {
+
+ info ("Logging in...\n");
+ if (!srvc->user) {
+ fprintf( stderr, "Skipping server %s, no user\n", srvc->host );
+ goto bail;
+ }
+ if (!srvc->pass) {
+ char prompt[80];
+ sprintf( prompt, "Password (%s@%s): ", srvc->user, srvc->host );
+ arg = getpass( prompt );
+ if (!arg) {
+ perror( "getpass" );
+ exit( 1 );
+ }
+ if (!*arg) {
+ fprintf( stderr, "Skipping account %s@%s, no password\n", srvc->user, srvc->host );
+ goto bail;
+ }
+ /*
+ * getpass() returns a pointer to a static buffer. make a copy
+ * for long term storage.
+ */
+ srvc->pass = strdup( arg );
+ }
+ if (CAP(NOLOGIN)) {
+ fprintf( stderr, "Skipping account %s@%s, server forbids LOGIN\n", srvc->user, srvc->host );
+ goto bail;
+ }
+ warn( "*** IMAP Warning *** Password is being sent in the clear\n" );
+ if (imap_exec( ctx, 0, "LOGIN \"%s\" \"%s\"", srvc->user, srvc->pass ) != RESP_OK) {
+ fprintf( stderr, "IMAP error: LOGIN failed\n" );
+ goto bail;
+ }
+ } /* !preauth */
+
+ ctx->prefix = "";
+ ctx->trashnc = 1;
+ return (store_t *)ctx;
+
+ bail:
+ imap_close_store( &ctx->gen );
+ return 0;
+}
+
+static int
+imap_make_flags( int flags, char *buf )
+{
+ const char *s;
+ unsigned i, d;
+
+ for (i = d = 0; i < ARRAY_SIZE(Flags); i++)
+ if (flags & (1 << i)) {
+ buf[d++] = ' ';
+ buf[d++] = '\\';
+ for (s = Flags[i]; *s; s++)
+ buf[d++] = *s;
+ }
+ buf[0] = '(';
+ buf[d++] = ')';
+ return d;
+}
+
+#define TUIDL 8
+
+static int
+imap_store_msg( store_t *gctx, msg_data_t *data, int *uid )
+{
+ imap_store_t *ctx = (imap_store_t *)gctx;
+ imap_t *imap = ctx->imap;
+ struct imap_cmd_cb cb;
+ char *fmap, *buf;
+ const char *prefix, *box;
+ int ret, i, j, d, len, extra, nocr;
+ int start, sbreak = 0, ebreak = 0;
+ char flagstr[128], tuid[TUIDL * 2 + 1];
+
+ memset( &cb, 0, sizeof(cb) );
+
+ fmap = data->data;
+ len = data->len;
+ nocr = !data->crlf;
+ extra = 0, i = 0;
+ if (!CAP(UIDPLUS) && uid) {
+ nloop:
+ start = i;
+ while (i < len)
+ if (fmap[i++] == '\n') {
+ extra += nocr;
+ if (i - 2 + nocr == start) {
+ sbreak = ebreak = i - 2 + nocr;
+ goto mktid;
+ }
+ if (!memcmp( fmap + start, "X-TUID: ", 8 )) {
+ extra -= (ebreak = i) - (sbreak = start) + nocr;
+ goto mktid;
+ }
+ goto nloop;
+ }
+ /* invalid message */
+ free( fmap );
+ return DRV_MSG_BAD;
+ mktid:
+ for (j = 0; j < TUIDL; j++)
+ sprintf( tuid + j * 2, "%02x", arc4_getbyte() );
+ extra += 8 + TUIDL * 2 + 2;
+ }
+ if (nocr)
+ for (; i < len; i++)
+ if (fmap[i] == '\n')
+ extra++;
+
+ cb.dlen = len + extra;
+ buf = cb.data = xmalloc( cb.dlen );
+ i = 0;
+ if (!CAP(UIDPLUS) && uid) {
+ if (nocr) {
+ for (; i < sbreak; i++)
+ if (fmap[i] == '\n') {
+ *buf++ = '\r';
+ *buf++ = '\n';
+ } else
+ *buf++ = fmap[i];
+ } else {
+ memcpy( buf, fmap, sbreak );
+ buf += sbreak;
+ }
+ memcpy( buf, "X-TUID: ", 8 );
+ buf += 8;
+ memcpy( buf, tuid, TUIDL * 2 );
+ buf += TUIDL * 2;
+ *buf++ = '\r';
+ *buf++ = '\n';
+ i = ebreak;
+ }
+ if (nocr) {
+ for (; i < len; i++)
+ if (fmap[i] == '\n') {
+ *buf++ = '\r';
+ *buf++ = '\n';
+ } else
+ *buf++ = fmap[i];
+ } else
+ memcpy( buf, fmap + i, len - i );
+
+ free( fmap );
+
+ d = 0;
+ if (data->flags) {
+ d = imap_make_flags( data->flags, flagstr );
+ flagstr[d++] = ' ';
+ }
+ flagstr[d] = 0;
+
+ if (!uid) {
+ box = gctx->conf->trash;
+ prefix = ctx->prefix;
+ cb.create = 1;
+ if (ctx->trashnc)
+ imap->caps = imap->rcaps & ~(1 << LITERALPLUS);
+ } else {
+ box = gctx->name;
+ prefix = !strcmp( box, "INBOX" ) ? "" : ctx->prefix;
+ cb.create = 0;
+ }
+ cb.ctx = uid;
+ ret = imap_exec_m( ctx, &cb, "APPEND \"%s%s\" %s", prefix, box, flagstr );
+ imap->caps = imap->rcaps;
+ if (ret != DRV_OK)
+ return ret;
+ if (!uid)
+ ctx->trashnc = 0;
+ else
+ gctx->count++;
+
+ return DRV_OK;
+}
+
+#define CHUNKSIZE 0x1000
+
+static int
+read_message( FILE *f, msg_data_t *msg )
+{
+ int len, r;
+
+ memset( msg, 0, sizeof *msg );
+ len = CHUNKSIZE;
+ msg->data = xmalloc( len+1 );
+ msg->data[0] = 0;
+
+ while(!feof( f )) {
+ if (msg->len >= len) {
+ void *p;
+ len += CHUNKSIZE;
+ p = xrealloc(msg->data, len+1);
+ if (!p)
+ break;
+ }
+ r = fread( &msg->data[msg->len], 1, len - msg->len, f );
+ if (r <= 0)
+ break;
+ msg->len += r;
+ }
+ msg->data[msg->len] = 0;
+ return msg->len;
+}
+
+static int
+count_messages( msg_data_t *msg )
+{
+ int count = 0;
+ char *p = msg->data;
+
+ while (1) {
+ if (!strncmp( "From ", p, 5 )) {
+ count++;
+ p += 5;
+ }
+ p = strstr( p+5, "\nFrom ");
+ if (!p)
+ break;
+ p++;
+ }
+ return count;
+}
+
+static int
+split_msg( msg_data_t *all_msgs, msg_data_t *msg, int *ofs )
+{
+ char *p, *data;
+
+ memset( msg, 0, sizeof *msg );
+ if (*ofs >= all_msgs->len)
+ return 0;
+
+ data = &all_msgs->data[ *ofs ];
+ msg->len = all_msgs->len - *ofs;
+
+ if (msg->len < 5 || strncmp( data, "From ", 5 ))
+ return 0;
+
+ p = strstr( data, "\nFrom " );
+ if (p)
+ msg->len = &p[1] - data;
+
+ msg->data = xmalloc( msg->len + 1 );
+ if (!msg->data)
+ return 0;
+
+ memcpy( msg->data, data, msg->len );
+ msg->data[ msg->len ] = 0;
+
+ *ofs += msg->len;
+ return 1;
+}
+
+static imap_server_conf_t server =
+{
+ NULL, /* name */
+ NULL, /* tunnel */
+ NULL, /* host */
+ 0, /* port */
+ NULL, /* user */
+ NULL, /* pass */
+};
+
+static char *imap_folder;
+
+static int
+git_imap_config(const char *key, const char *val)
+{
+ char imap_key[] = "imap.";
+
+ if (strncmp( key, imap_key, sizeof imap_key - 1 ))
+ return 0;
+ key += sizeof imap_key - 1;
+
+ if (!strcmp( "folder", key )) {
+ imap_folder = strdup( val );
+ } else if (!strcmp( "host", key )) {
+ {
+ if (!strncmp( "imap:", val, 5 ))
+ val += 5;
+ if (!server.port)
+ server.port = 143;
+ }
+ if (!strncmp( "//", val, 2 ))
+ val += 2;
+ server.host = strdup( val );
+ }
+ else if (!strcmp( "user", key ))
+ server.user = strdup( val );
+ else if (!strcmp( "pass", key ))
+ server.pass = strdup( val );
+ else if (!strcmp( "port", key ))
+ server.port = git_config_int( key, val );
+ else if (!strcmp( "tunnel", key ))
+ server.tunnel = strdup( val );
+ return 0;
+}
+
+int
+main(int argc, char **argv)
+{
+ msg_data_t all_msgs, msg;
+ store_t *ctx = 0;
+ int uid = 0;
+ int ofs = 0;
+ int r;
+ int total, n = 0;
+
+ /* init the random number generator */
+ arc4_init();
+
+ git_config( git_imap_config );
+
+ if (!imap_folder) {
+ fprintf( stderr, "no imap store specified\n" );
+ return 1;
+ }
+
+ /* read the messages */
+ if (!read_message( stdin, &all_msgs )) {
+ fprintf(stderr,"nothing to send\n");
+ return 1;
+ }
+
+ /* write it to the imap server */
+ ctx = imap_open_store( &server );
+ if (!ctx) {
+ fprintf( stderr,"failed to open store\n");
+ return 1;
+ }
+
+ total = count_messages( &all_msgs );
+ fprintf( stderr, "sending %d message%s\n", total, (total!=1)?"s":"" );
+ ctx->name = imap_folder;
+ while (1) {
+ unsigned percent = n * 100 / total;
+ fprintf( stderr, "%4u%% (%d/%d) done\r", percent, n, total );
+ if (!split_msg( &all_msgs, &msg, &ofs ))
+ break;
+ r = imap_store_msg( ctx, &msg, &uid );
+ if (r != DRV_OK) break;
+ n++;
+ }
+ fprintf( stderr,"\n" );
+
+ imap_close_store( ctx );
+
+ return 0;
+}
diff --git a/pack-check.c b/pack-check.c
index eca32b6cab..84ed90d369 100644
--- a/pack-check.c
+++ b/pack-check.c
@@ -70,13 +70,17 @@ static int verify_packfile(struct packed_git *p)
}
+#define MAX_CHAIN 40
+
static void show_pack_info(struct packed_git *p)
{
struct pack_header *hdr;
int nr_objects, i;
+ unsigned int chain_histogram[MAX_CHAIN];
hdr = p->pack_base;
nr_objects = ntohl(hdr->hdr_entries);
+ memset(chain_histogram, 0, sizeof(chain_histogram));
for (i = 0; i < nr_objects; i++) {
unsigned char sha1[20], base_sha1[20];
@@ -97,11 +101,25 @@ static void show_pack_info(struct packed_git *p)
printf("%s ", sha1_to_hex(sha1));
if (!delta_chain_length)
printf("%-6s %lu %u\n", type, size, e.offset);
- else
+ else {
printf("%-6s %lu %u %u %s\n", type, size, e.offset,
delta_chain_length, sha1_to_hex(base_sha1));
+ if (delta_chain_length < MAX_CHAIN)
+ chain_histogram[delta_chain_length]++;
+ else
+ chain_histogram[0]++;
+ }
}
+ for (i = 0; i < MAX_CHAIN; i++) {
+ if (!chain_histogram[i])
+ continue;
+ printf("chain length %s %d: %d object%s\n",
+ i ? "=" : ">=",
+ i ? i : MAX_CHAIN,
+ chain_histogram[i],
+ 1 < chain_histogram[i] ? "s" : "");
+ }
}
int verify_pack(struct packed_git *p, int verbose)
diff --git a/pack-objects.c b/pack-objects.c
index 136a7f5aad..49357c6735 100644
--- a/pack-objects.c
+++ b/pack-objects.c
@@ -32,9 +32,6 @@ struct object_entry {
* be used as the base objectto delta huge
* objects against.
*/
- int based_on_preferred; /* current delta candidate is a preferred
- * one, or delta against a preferred one.
- */
};
/*
@@ -824,8 +821,6 @@ static int try_delta(struct unpacked *cur, struct unpacked *old, unsigned max_de
{
struct object_entry *cur_entry = cur->entry;
struct object_entry *old_entry = old->entry;
- int old_preferred = (old_entry->preferred_base ||
- old_entry->based_on_preferred);
unsigned long size, oldsize, delta_size, sizediff;
long max_size;
void *delta_buf;
@@ -867,27 +862,8 @@ static int try_delta(struct unpacked *cur, struct unpacked *old, unsigned max_de
* delete).
*/
max_size = size / 2 - 20;
- if (cur_entry->delta) {
- if (cur_entry->based_on_preferred) {
- if (old_preferred)
- max_size = cur_entry->delta_size-1;
- else
- /* trying with non-preferred one when we
- * already have a delta based on preferred
- * one is pointless.
- */
- return -1;
- }
- else if (!old_preferred)
- max_size = cur_entry->delta_size-1;
- else
- /* otherwise... even if delta with a
- * preferred one produces a bigger result than
- * what we currently have, which is based on a
- * non-preferred one, it is OK.
- */
- ;
- }
+ if (cur_entry->delta)
+ max_size = cur_entry->delta_size-1;
if (sizediff >= max_size)
return -1;
delta_buf = diff_delta(old->data, oldsize,
@@ -897,7 +873,6 @@ static int try_delta(struct unpacked *cur, struct unpacked *old, unsigned max_de
cur_entry->delta = old_entry;
cur_entry->delta_size = delta_size;
cur_entry->depth = old_entry->depth + 1;
- cur_entry->based_on_preferred = old_preferred;
free(delta_buf);
return 0;
}
@@ -966,6 +941,15 @@ static void find_deltas(struct object_entry **list, int window, int depth)
if (try_delta(n, m, depth) < 0)
break;
}
+#if 0
+ /* if we made n a delta, and if n is already at max
+ * depth, leaving it in the window is pointless. we
+ * should evict it first.
+ * ... in theory only; somehow this makes things worse.
+ */
+ if (entry->delta && depth <= entry->depth)
+ continue;
+#endif
idx++;
if (idx >= window)
idx = 0;
diff --git a/read-tree.c b/read-tree.c
index be29b3fe11..1c3b09beff 100644
--- a/read-tree.c
+++ b/read-tree.c
@@ -337,7 +337,7 @@ static void check_updates(struct cache_entry **src, int nr)
if (ce->ce_flags & mask) {
ce->ce_flags &= ~mask;
if (update)
- checkout_entry(ce, &state);
+ checkout_entry(ce, &state, NULL);
}
}
if (total) {
diff --git a/receive-pack.c b/receive-pack.c
index 2a3db16d68..93929b5371 100644
--- a/receive-pack.c
+++ b/receive-pack.c
@@ -6,7 +6,7 @@
static const char receive_pack_usage[] = "git-receive-pack <git-dir>";
-static char *unpacker[] = { "unpack-objects", NULL };
+static const char *unpacker[] = { "unpack-objects", NULL };
static int report_status = 0;
@@ -177,7 +177,7 @@ static void run_update_post_hook(struct command *cmd)
{
struct command *cmd_p;
int argc;
- char **argv;
+ const char **argv;
if (access(update_post_hook, X_OK) < 0)
return;
@@ -190,10 +190,12 @@ static void run_update_post_hook(struct command *cmd)
argv[0] = update_post_hook;
for (argc = 1, cmd_p = cmd; cmd_p; cmd_p = cmd_p->next) {
+ char *p;
if (cmd_p->error_string)
continue;
- argv[argc] = xmalloc(strlen(cmd_p->ref_name) + 1);
- strcpy(argv[argc], cmd_p->ref_name);
+ p = xmalloc(strlen(cmd_p->ref_name) + 1);
+ strcpy(p, cmd_p->ref_name);
+ argv[argc] = p;
argc++;
}
argv[argc] = NULL;
diff --git a/refs.c b/refs.c
index 982ebf8ae5..03398ccc53 100644
--- a/refs.c
+++ b/refs.c
@@ -152,12 +152,12 @@ static int do_for_each_ref(const char *base, int (*fn)(const char *path, const u
continue;
}
if (read_ref(git_path("%s", path), sha1) < 0) {
- fprintf(stderr, "%s points nowhere!", path);
+ error("%s points nowhere!", path);
continue;
}
if (!has_sha1_file(sha1)) {
- fprintf(stderr, "%s does not point to a valid "
- "commit object!", path);
+ error("%s does not point to a valid "
+ "commit object!", path);
continue;
}
retval = fn(path, sha1);
diff --git a/repo-config.c b/repo-config.c
index 9cf65193f9..c5ebb7668a 100644
--- a/repo-config.c
+++ b/repo-config.c
@@ -14,6 +14,9 @@ static enum { T_RAW, T_INT, T_BOOL } type = T_RAW;
static int show_config(const char* key_, const char* value_)
{
+ if (value_ == NULL)
+ value_ = "";
+
if (!strcmp(key_, key) &&
(regexp == NULL ||
(do_not_match ^
@@ -35,7 +38,7 @@ static int show_config(const char* key_, const char* value_)
sprintf(value, "%s", git_config_bool(key_, value_)
? "true" : "false");
} else {
- value = strdup(value_ ? value_ : "");
+ value = strdup(value_);
}
seen++;
}
diff --git a/rev-list.c b/rev-list.c
index 8e4d83efba..812d237f47 100644
--- a/rev-list.c
+++ b/rev-list.c
@@ -190,7 +190,7 @@ static int count_distance(struct commit_list *entry)
if (commit->object.flags & (UNINTERESTING | COUNTED))
break;
- if (!revs.paths || (commit->object.flags & TREECHANGE))
+ if (!revs.prune_fn || (commit->object.flags & TREECHANGE))
nr++;
commit->object.flags |= COUNTED;
p = commit->parents;
@@ -224,7 +224,7 @@ static struct commit_list *find_bisection(struct commit_list *list)
nr = 0;
p = list;
while (p) {
- if (!revs.paths || (p->item->object.flags & TREECHANGE))
+ if (!revs.prune_fn || (p->item->object.flags & TREECHANGE))
nr++;
p = p->next;
}
@@ -234,7 +234,7 @@ static struct commit_list *find_bisection(struct commit_list *list)
for (p = list; p; p = p->next) {
int distance;
- if (revs.paths && !(p->item->object.flags & TREECHANGE))
+ if (revs.prune_fn && !(p->item->object.flags & TREECHANGE))
continue;
distance = count_distance(p);
diff --git a/revision.c b/revision.c
index a3df810076..12cd0529a5 100644
--- a/revision.c
+++ b/revision.c
@@ -82,18 +82,20 @@ void mark_parents_uninteresting(struct commit *commit)
while (parents) {
struct commit *commit = parents->item;
- commit->object.flags |= UNINTERESTING;
-
- /*
- * Normally we haven't parsed the parent
- * yet, so we won't have a parent of a parent
- * here. However, it may turn out that we've
- * reached this commit some other way (where it
- * wasn't uninteresting), in which case we need
- * to mark its parents recursively too..
- */
- if (commit->parents)
- mark_parents_uninteresting(commit);
+ if (!(commit->object.flags & UNINTERESTING)) {
+ commit->object.flags |= UNINTERESTING;
+
+ /*
+ * Normally we haven't parsed the parent
+ * yet, so we won't have a parent of a parent
+ * here. However, it may turn out that we've
+ * reached this commit some other way (where it
+ * wasn't uninteresting), in which case we need
+ * to mark its parents recursively too..
+ */
+ if (commit->parents)
+ mark_parents_uninteresting(commit);
+ }
/*
* A missing commit is ok iff its parent is marked
@@ -197,31 +199,27 @@ static int everybody_uninteresting(struct commit_list *orig)
return 1;
}
-#define TREE_SAME 0
-#define TREE_NEW 1
-#define TREE_DIFFERENT 2
-static int tree_difference = TREE_SAME;
+static int tree_difference = REV_TREE_SAME;
static void file_add_remove(struct diff_options *options,
int addremove, unsigned mode,
const unsigned char *sha1,
const char *base, const char *path)
{
- int diff = TREE_DIFFERENT;
+ int diff = REV_TREE_DIFFERENT;
/*
- * Is it an add of a new file? It means that
- * the old tree didn't have it at all, so we
- * will turn "TREE_SAME" -> "TREE_NEW", but
- * leave any "TREE_DIFFERENT" alone (and if
- * it already was "TREE_NEW", we'll keep it
- * "TREE_NEW" of course).
+ * Is it an add of a new file? It means that the old tree
+ * didn't have it at all, so we will turn "REV_TREE_SAME" ->
+ * "REV_TREE_NEW", but leave any "REV_TREE_DIFFERENT" alone
+ * (and if it already was "REV_TREE_NEW", we'll keep it
+ * "REV_TREE_NEW" of course).
*/
if (addremove == '+') {
diff = tree_difference;
- if (diff != TREE_SAME)
+ if (diff != REV_TREE_SAME)
return;
- diff = TREE_NEW;
+ diff = REV_TREE_NEW;
}
tree_difference = diff;
}
@@ -232,7 +230,7 @@ static void file_change(struct diff_options *options,
const unsigned char *new_sha1,
const char *base, const char *path)
{
- tree_difference = TREE_DIFFERENT;
+ tree_difference = REV_TREE_DIFFERENT;
}
static struct diff_options diff_opt = {
@@ -241,19 +239,19 @@ static struct diff_options diff_opt = {
.change = file_change,
};
-static int compare_tree(struct tree *t1, struct tree *t2)
+int rev_compare_tree(struct tree *t1, struct tree *t2)
{
if (!t1)
- return TREE_NEW;
+ return REV_TREE_NEW;
if (!t2)
- return TREE_DIFFERENT;
- tree_difference = TREE_SAME;
+ return REV_TREE_DIFFERENT;
+ tree_difference = REV_TREE_SAME;
if (diff_tree_sha1(t1->object.sha1, t2->object.sha1, "", &diff_opt) < 0)
- return TREE_DIFFERENT;
+ return REV_TREE_DIFFERENT;
return tree_difference;
}
-static int same_tree_as_empty(struct tree *t1)
+int rev_same_tree_as_empty(struct tree *t1)
{
int retval;
void *tree;
@@ -280,12 +278,13 @@ static int same_tree_as_empty(struct tree *t1)
static void try_to_simplify_commit(struct rev_info *revs, struct commit *commit)
{
struct commit_list **pp, *parent;
+ int tree_changed = 0;
if (!commit->tree)
return;
if (!commit->parents) {
- if (!same_tree_as_empty(commit->tree))
+ if (!rev_same_tree_as_empty(commit->tree))
commit->object.flags |= TREECHANGE;
return;
}
@@ -294,31 +293,47 @@ static void try_to_simplify_commit(struct rev_info *revs, struct commit *commit)
while ((parent = *pp) != NULL) {
struct commit *p = parent->item;
- if (p->object.flags & UNINTERESTING) {
- pp = &parent->next;
- continue;
- }
-
parse_commit(p);
- switch (compare_tree(p->tree, commit->tree)) {
- case TREE_SAME:
+ switch (rev_compare_tree(p->tree, commit->tree)) {
+ case REV_TREE_SAME:
+ if (p->object.flags & UNINTERESTING) {
+ /* Even if a merge with an uninteresting
+ * side branch brought the entire change
+ * we are interested in, we do not want
+ * to lose the other branches of this
+ * merge, so we just keep going.
+ */
+ pp = &parent->next;
+ continue;
+ }
parent->next = NULL;
commit->parents = parent;
return;
- case TREE_NEW:
- if (revs->remove_empty_trees && same_tree_as_empty(p->tree)) {
- *pp = parent->next;
- continue;
+ case REV_TREE_NEW:
+ if (revs->remove_empty_trees &&
+ rev_same_tree_as_empty(p->tree)) {
+ /* We are adding all the specified
+ * paths from this parent, so the
+ * history beyond this parent is not
+ * interesting. Remove its parents
+ * (they are grandparents for us).
+ * IOW, we pretend this parent is a
+ * "root" commit.
+ */
+ parse_commit(p);
+ p->parents = NULL;
}
/* fallthrough */
- case TREE_DIFFERENT:
+ case REV_TREE_DIFFERENT:
+ tree_changed = 1;
pp = &parent->next;
continue;
}
die("bad tree compare for commit %s", sha1_to_hex(commit->object.sha1));
}
- commit->object.flags |= TREECHANGE;
+ if (tree_changed)
+ commit->object.flags |= TREECHANGE;
}
static void add_parents_to_list(struct rev_info *revs, struct commit *commit, struct commit_list **list)
@@ -358,8 +373,8 @@ static void add_parents_to_list(struct rev_info *revs, struct commit *commit, st
* simplify the commit history and find the parent
* that has no differences in the path set if one exists.
*/
- if (revs->paths)
- try_to_simplify_commit(revs, commit);
+ if (revs->prune_fn)
+ revs->prune_fn(revs, commit);
parent = commit->parents;
while (parent) {
@@ -381,9 +396,6 @@ static void limit_list(struct rev_info *revs)
struct commit_list *newlist = NULL;
struct commit_list **p = &newlist;
- if (revs->paths)
- diff_tree_setup_paths(revs->paths);
-
while (list) {
struct commit_list *entry = list;
struct commit *commit = list->item;
@@ -435,6 +447,23 @@ static void handle_all(struct rev_info *revs, unsigned flags)
for_each_ref(handle_one_ref);
}
+void init_revisions(struct rev_info *revs)
+{
+ memset(revs, 0, sizeof(*revs));
+ revs->lifo = 1;
+ revs->dense = 1;
+ revs->prefix = setup_git_directory();
+ revs->max_age = -1;
+ revs->min_age = -1;
+ revs->max_count = -1;
+
+ revs->prune_fn = NULL;
+ revs->prune_data = NULL;
+
+ revs->topo_setter = topo_sort_default_setter;
+ revs->topo_getter = topo_sort_default_getter;
+}
+
/*
* Parse revision information, filling in the "rev_info" structure,
* and removing the used arguments from the argument list.
@@ -448,13 +477,7 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch
const char **unrecognized = argv + 1;
int left = 1;
- memset(revs, 0, sizeof(*revs));
- revs->lifo = 1;
- revs->dense = 1;
- revs->prefix = setup_git_directory();
- revs->max_age = -1;
- revs->min_age = -1;
- revs->max_count = -1;
+ init_revisions(revs);
/* First, search for "--" */
seen_dashdash = 0;
@@ -464,7 +487,7 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch
continue;
argv[i] = NULL;
argc = i;
- revs->paths = get_pathspec(revs->prefix, argv + i + 1);
+ revs->prune_data = get_pathspec(revs->prefix, argv + i + 1);
seen_dashdash = 1;
break;
}
@@ -628,7 +651,7 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch
if (lstat(argv[j], &st) < 0)
die("'%s': %s", arg, strerror(errno));
}
- revs->paths = get_pathspec(revs->prefix, argv + i);
+ revs->prune_data = get_pathspec(revs->prefix, argv + i);
break;
}
commit = get_commit_reference(revs, arg, sha1, flags ^ local_flags);
@@ -642,8 +665,13 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch
commit = get_commit_reference(revs, def, sha1, 0);
add_one_commit(commit, revs);
}
- if (revs->paths)
+
+ if (revs->prune_data) {
+ diff_tree_setup_paths(revs->prune_data);
+ revs->prune_fn = try_to_simplify_commit;
revs->limited = 1;
+ }
+
return left;
}
@@ -653,7 +681,9 @@ void prepare_revision_walk(struct rev_info *revs)
if (revs->limited)
limit_list(revs);
if (revs->topo_order)
- sort_in_topological_order(&revs->commits, revs->lifo);
+ sort_in_topological_order_fn(&revs->commits, revs->lifo,
+ revs->topo_setter,
+ revs->topo_getter);
}
static int rewrite_one(struct commit **pp)
@@ -684,13 +714,11 @@ static void rewrite_parents(struct commit *commit)
struct commit *get_revision(struct rev_info *revs)
{
struct commit_list *list = revs->commits;
- struct commit *commit;
if (!list)
return NULL;
/* Check the max_count ... */
- commit = list->item;
switch (revs->max_count) {
case -1:
break;
@@ -701,22 +729,28 @@ struct commit *get_revision(struct rev_info *revs)
}
do {
- commit = pop_most_recent_commit(&revs->commits, SEEN);
+ struct commit *commit = revs->commits->item;
+
if (commit->object.flags & (UNINTERESTING|SHOWN))
- continue;
+ goto next;
if (revs->min_age != -1 && (commit->date > revs->min_age))
- continue;
+ goto next;
if (revs->max_age != -1 && (commit->date < revs->max_age))
return NULL;
if (revs->no_merges && commit->parents && commit->parents->next)
- continue;
- if (revs->paths && revs->dense) {
+ goto next;
+ if (revs->prune_fn && revs->dense) {
if (!(commit->object.flags & TREECHANGE))
- continue;
+ goto next;
rewrite_parents(commit);
}
+ /* More to go? */
+ if (revs->max_count)
+ pop_most_recent_commit(&revs->commits, SEEN);
commit->object.flags |= SHOWN;
return commit;
+next:
+ pop_most_recent_commit(&revs->commits, SEEN);
} while (revs->commits);
return NULL;
}
diff --git a/revision.h b/revision.h
index 31e8f61567..6c2becad13 100644
--- a/revision.h
+++ b/revision.h
@@ -7,6 +7,10 @@
#define SHOWN (1u<<3)
#define TMP_MARK (1u<<4) /* for isolated cases; clean after use */
+struct rev_info;
+
+typedef void (prune_fn_t)(struct rev_info *revs, struct commit *commit);
+
struct rev_info {
/* Starting list */
struct commit_list *commits;
@@ -14,7 +18,8 @@ struct rev_info {
/* Basic information */
const char *prefix;
- const char **paths;
+ void *prune_data;
+ prune_fn_t *prune_fn;
/* Traversal flags */
unsigned int dense:1,
@@ -33,9 +38,20 @@ struct rev_info {
int max_count;
unsigned long max_age;
unsigned long min_age;
+
+ topo_sort_set_fn_t topo_setter;
+ topo_sort_get_fn_t topo_getter;
};
+#define REV_TREE_SAME 0
+#define REV_TREE_NEW 1
+#define REV_TREE_DIFFERENT 2
+
/* revision.c */
+extern int rev_same_tree_as_empty(struct tree *t1);
+extern int rev_compare_tree(struct tree *t1, struct tree *t2);
+
+extern void init_revisions(struct rev_info *revs);
extern int setup_revisions(int argc, const char **argv, struct rev_info *revs, const char *def);
extern void prepare_revision_walk(struct rev_info *revs);
extern struct commit *get_revision(struct rev_info *revs);
diff --git a/run-command.c b/run-command.c
index b3d287e97e..ca67ee9333 100644
--- a/run-command.c
+++ b/run-command.c
@@ -3,7 +3,7 @@
#include <sys/wait.h>
#include "exec_cmd.h"
-int run_command_v_opt(int argc, char **argv, int flags)
+int run_command_v_opt(int argc, const char **argv, int flags)
{
pid_t pid = fork();
@@ -47,7 +47,7 @@ int run_command_v_opt(int argc, char **argv, int flags)
}
}
-int run_command_v(int argc, char **argv)
+int run_command_v(int argc, const char **argv)
{
return run_command_v_opt(argc, argv, 0);
}
@@ -55,7 +55,7 @@ int run_command_v(int argc, char **argv)
int run_command(const char *cmd, ...)
{
int argc;
- char *argv[MAX_RUN_COMMAND_ARGS];
+ const char *argv[MAX_RUN_COMMAND_ARGS];
const char *arg;
va_list param;
diff --git a/run-command.h b/run-command.h
index ef3ee053de..70b477a748 100644
--- a/run-command.h
+++ b/run-command.h
@@ -13,8 +13,8 @@ enum {
#define RUN_COMMAND_NO_STDIO 1
#define RUN_GIT_CMD 2 /*If this is to be git sub-command */
-int run_command_v_opt(int argc, char **argv, int opt);
-int run_command_v(int argc, char **argv);
+int run_command_v_opt(int argc, const char **argv, int opt);
+int run_command_v(int argc, const char **argv);
int run_command(const char *cmd, ...);
#endif
diff --git a/send-pack.c b/send-pack.c
index f558386143..c8ffc8d537 100644
--- a/send-pack.c
+++ b/send-pack.c
@@ -27,7 +27,7 @@ static int is_zero_sha1(const unsigned char *sha1)
static void exec_pack_objects(void)
{
- static char *args[] = {
+ static const char *args[] = {
"pack-objects",
"--stdout",
NULL
@@ -39,7 +39,7 @@ static void exec_pack_objects(void)
static void exec_rev_list(struct ref *refs)
{
struct ref *ref;
- static char *args[1000];
+ static const char *args[1000];
int i = 0, j;
args[i++] = "rev-list"; /* 0 */
diff --git a/shell.c b/shell.c
index fc0c73cde7..8c08cf0fb3 100644
--- a/shell.c
+++ b/shell.c
@@ -15,7 +15,7 @@ static int do_generic_cmd(const char *me, char *arg)
my_argv[1] = arg;
my_argv[2] = NULL;
- return execv_git_cmd((char**) my_argv);
+ return execv_git_cmd(my_argv);
}
static struct commands {
diff --git a/t/.gitignore b/t/.gitignore
new file mode 100644
index 0000000000..fad67c097b
--- /dev/null
+++ b/t/.gitignore
@@ -0,0 +1 @@
+trash
diff --git a/t/annotate-tests.sh b/t/annotate-tests.sh
new file mode 100644
index 0000000000..114938c3ff
--- /dev/null
+++ b/t/annotate-tests.sh
@@ -0,0 +1,121 @@
+# This file isn't used as a test script directly, instead it is
+# sourced from t8001-annotate.sh and t8001-blame.sh.
+
+check_count () {
+ head=
+ case "$1" in -h) head="$2"; shift; shift ;; esac
+ $PROG file $head | perl -e '
+ my %expect = (@ARGV);
+ my %count = ();
+ while (<STDIN>) {
+ if (/^[0-9a-f]+\t\(([^\t]+)\t/) {
+ my $author = $1;
+ for ($author) { s/^\s*//; s/\s*$//; }
+ if (exists $expect{$author}) {
+ $count{$author}++;
+ }
+ }
+ }
+ my $bad = 0;
+ while (my ($author, $count) = each %count) {
+ my $ok;
+ if ($expect{$author} != $count) {
+ $bad = 1;
+ $ok = "bad";
+ }
+ else {
+ $ok = "good";
+ }
+ print STDERR "Author $author (expected $expect{$author}, attributed $count) $ok\n";
+ }
+ exit($bad);
+ ' "$@"
+}
+
+test_expect_success \
+ 'prepare reference tree' \
+ 'echo "1A quick brown fox jumps over the" >file &&
+ echo "lazy dog" >>file &&
+ git add file
+ GIT_AUTHOR_NAME="A" git commit -a -m "Initial."'
+
+test_expect_success \
+ 'check all lines blamed on A' \
+ 'check_count A 2'
+
+test_expect_success \
+ 'Setup new lines blamed on B' \
+ 'echo "2A quick brown fox jumps over the" >>file &&
+ echo "lazy dog" >> file &&
+ GIT_AUTHOR_NAME="B" git commit -a -m "Second."'
+
+test_expect_success \
+ 'Two lines blamed on A, two on B' \
+ 'check_count A 2 B 2'
+
+test_expect_success \
+ 'merge-setup part 1' \
+ 'git checkout -b branch1 master &&
+ echo "3A slow green fox jumps into the" >> file &&
+ echo "well." >> file &&
+ GIT_AUTHOR_NAME="B1" git commit -a -m "Branch1-1"'
+
+test_expect_success \
+ 'Two lines blamed on A, two on B, two on B1' \
+ 'check_count A 2 B 2 B1 2'
+
+test_expect_success \
+ 'merge-setup part 2' \
+ 'git checkout -b branch2 master &&
+ sed -e "s/2A quick brown/4A quick brown lazy dog/" < file > file.new &&
+ mv file.new file &&
+ GIT_AUTHOR_NAME="B2" git commit -a -m "Branch2-1"'
+
+test_expect_success \
+ 'Two lines blamed on A, one on B, one on B2' \
+ 'check_count A 2 B 1 B2 1'
+
+test_expect_success \
+ 'merge-setup part 3' \
+ 'git pull . branch1'
+
+test_expect_success \
+ 'Two lines blamed on A, one on B, two on B1, one on B2' \
+ 'check_count A 2 B 1 B1 2 B2 1'
+
+test_expect_success \
+ 'Annotating an old revision works' \
+ 'check_count -h master A 2 B 2'
+
+test_expect_success \
+ 'Annotating an old revision works' \
+ 'check_count -h master^ A 2'
+
+test_expect_success \
+ 'merge-setup part 4' \
+ 'echo "evil merge." >>file &&
+ EDITOR=: VISUAL=: git commit -a --amend'
+
+test_expect_success \
+ 'Two lines blamed on A, one on B, two on B1, one on B2, one on A U Thor' \
+ 'check_count A 2 B 1 B1 2 B2 1 "A U Thor" 1'
+
+test_expect_success \
+ 'an incomplete line added' \
+ 'echo "incomplete" | tr -d "\\012" >>file &&
+ GIT_AUTHOR_NAME="C" git commit -a -m "Incomplete"'
+
+test_expect_success \
+ 'With incomplete lines.' \
+ 'check_count A 2 B 1 B1 2 B2 1 "A U Thor" 1 C 1'
+
+test_expect_success \
+ 'some edit' \
+ 'mv file file1 &&
+ sed -e 1d -e "5s/3A/99/" file1 >file &&
+ rm -f file1 &&
+ GIT_AUTHOR_NAME="D" git commit -a -m "edit"'
+
+test_expect_success \
+ 'some edit' \
+ 'check_count A 1 B 1 B1 1 B2 1 "A U Thor" 1 C 1 D 1'
diff --git a/t/t1200-tutorial.sh b/t/t1200-tutorial.sh
index c8a85f92dd..10024133e3 100755
--- a/t/t1200-tutorial.sh
+++ b/t/t1200-tutorial.sh
@@ -128,7 +128,7 @@ test_expect_success 'git show-branch' 'cmp show-branch.expect show-branch.output
git checkout mybranch
cat > resolve.expect << EOF
-Updating from VARIABLE to VARIABLE.
+Updating from VARIABLE to VARIABLE
example | 1 +
hello | 1 +
2 files changed, 2 insertions(+), 0 deletions(-)
diff --git a/t/t1300-repo-config.sh b/t/t1300-repo-config.sh
index 207dd3de64..ab4dd5c4ce 100755
--- a/t/t1300-repo-config.sh
+++ b/t/t1300-repo-config.sh
@@ -247,5 +247,13 @@ EOF
test_expect_success 'hierarchical section value' 'cmp .git/config expect'
+cat > .git/config << EOF
+[novalue]
+ variable
+EOF
+
+test_expect_success 'get variable with no value' \
+ 'git-repo-config --get novalue.variable ^$'
+
test_done
diff --git a/t/t2004-checkout-cache-temp.sh b/t/t2004-checkout-cache-temp.sh
new file mode 100755
index 0000000000..c100959cad
--- /dev/null
+++ b/t/t2004-checkout-cache-temp.sh
@@ -0,0 +1,212 @@
+#!/bin/sh
+#
+# Copyright (c) 2006 Shawn Pearce
+#
+
+test_description='git-checkout-index --temp test.
+
+With --temp flag, git-checkout-index writes to temporary merge files
+rather than the tracked path.'
+
+. ./test-lib.sh
+
+test_expect_success \
+'preparation' '
+mkdir asubdir &&
+echo tree1path0 >path0 &&
+echo tree1path1 >path1 &&
+echo tree1path3 >path3 &&
+echo tree1path4 >path4 &&
+echo tree1asubdir/path5 >asubdir/path5 &&
+git-update-index --add path0 path1 path3 path4 asubdir/path5 &&
+t1=$(git-write-tree) &&
+rm -f path* .merge_* out .git/index &&
+echo tree2path0 >path0 &&
+echo tree2path1 >path1 &&
+echo tree2path2 >path2 &&
+echo tree2path4 >path4 &&
+git-update-index --add path0 path1 path2 path4 &&
+t2=$(git-write-tree) &&
+rm -f path* .merge_* out .git/index &&
+echo tree2path0 >path0 &&
+echo tree3path1 >path1 &&
+echo tree3path2 >path2 &&
+echo tree3path3 >path3 &&
+git-update-index --add path0 path1 path2 path3 &&
+t3=$(git-write-tree)'
+
+test_expect_success \
+'checkout one stage 0 to temporary file' '
+rm -f path* .merge_* out .git/index &&
+git-read-tree $t1 &&
+git-checkout-index --temp -- path1 >out &&
+test $(wc -l <out) = 1 &&
+test $(cut "-d " -f2 out) = path1 &&
+p=$(cut "-d " -f1 out) &&
+test -f $p &&
+test $(cat $p) = tree1path1'
+
+test_expect_success \
+'checkout all stage 0 to temporary files' '
+rm -f path* .merge_* out .git/index &&
+git-read-tree $t1 &&
+git-checkout-index -a --temp >out &&
+test $(wc -l <out) = 5 &&
+for f in path0 path1 path3 path4 asubdir/path5
+do
+ test $(grep $f out | cut "-d " -f2) = $f &&
+ p=$(grep $f out | cut "-d " -f1) &&
+ test -f $p &&
+ test $(cat $p) = tree1$f
+done'
+
+test_expect_success \
+'prepare 3-way merge' '
+rm -f path* .merge_* out .git/index &&
+git-read-tree -m $t1 $t2 $t3'
+
+test_expect_success \
+'checkout one stage 2 to temporary file' '
+rm -f path* .merge_* out &&
+git-checkout-index --stage=2 --temp -- path1 >out &&
+test $(wc -l <out) = 1 &&
+test $(cut "-d " -f2 out) = path1 &&
+p=$(cut "-d " -f1 out) &&
+test -f $p &&
+test $(cat $p) = tree2path1'
+
+test_expect_success \
+'checkout all stage 2 to temporary files' '
+rm -f path* .merge_* out &&
+git-checkout-index --all --stage=2 --temp >out &&
+test $(wc -l <out) = 3 &&
+for f in path1 path2 path4
+do
+ test $(grep $f out | cut "-d " -f2) = $f &&
+ p=$(grep $f out | cut "-d " -f1) &&
+ test -f $p &&
+ test $(cat $p) = tree2$f
+done'
+
+test_expect_success \
+'checkout all stages/one file to nothing' '
+rm -f path* .merge_* out &&
+git-checkout-index --stage=all --temp -- path0 >out &&
+test $(wc -l <out) = 0'
+
+test_expect_success \
+'checkout all stages/one file to temporary files' '
+rm -f path* .merge_* out &&
+git-checkout-index --stage=all --temp -- path1 >out &&
+test $(wc -l <out) = 1 &&
+test $(cut "-d " -f2 out) = path1 &&
+cut "-d " -f1 out | (read s1 s2 s3 &&
+test -f $s1 &&
+test -f $s2 &&
+test -f $s3 &&
+test $(cat $s1) = tree1path1 &&
+test $(cat $s2) = tree2path1 &&
+test $(cat $s3) = tree3path1)'
+
+test_expect_success \
+'checkout some stages/one file to temporary files' '
+rm -f path* .merge_* out &&
+git-checkout-index --stage=all --temp -- path2 >out &&
+test $(wc -l <out) = 1 &&
+test $(cut "-d " -f2 out) = path2 &&
+cut "-d " -f1 out | (read s1 s2 s3 &&
+test $s1 = . &&
+test -f $s2 &&
+test -f $s3 &&
+test $(cat $s2) = tree2path2 &&
+test $(cat $s3) = tree3path2)'
+
+test_expect_success \
+'checkout all stages/all files to temporary files' '
+rm -f path* .merge_* out &&
+git-checkout-index -a --stage=all --temp >out &&
+test $(wc -l <out) = 5'
+
+test_expect_success \
+'-- path0: no entry' '
+test x$(grep path0 out | cut "-d " -f2) = x'
+
+test_expect_success \
+'-- path1: all 3 stages' '
+test $(grep path1 out | cut "-d " -f2) = path1 &&
+grep path1 out | cut "-d " -f1 | (read s1 s2 s3 &&
+test -f $s1 &&
+test -f $s2 &&
+test -f $s3 &&
+test $(cat $s1) = tree1path1 &&
+test $(cat $s2) = tree2path1 &&
+test $(cat $s3) = tree3path1)'
+
+test_expect_success \
+'-- path2: no stage 1, have stage 2 and 3' '
+test $(grep path2 out | cut "-d " -f2) = path2 &&
+grep path2 out | cut "-d " -f1 | (read s1 s2 s3 &&
+test $s1 = . &&
+test -f $s2 &&
+test -f $s3 &&
+test $(cat $s2) = tree2path2 &&
+test $(cat $s3) = tree3path2)'
+
+test_expect_success \
+'-- path3: no stage 2, have stage 1 and 3' '
+test $(grep path3 out | cut "-d " -f2) = path3 &&
+grep path3 out | cut "-d " -f1 | (read s1 s2 s3 &&
+test -f $s1 &&
+test $s2 = . &&
+test -f $s3 &&
+test $(cat $s1) = tree1path3 &&
+test $(cat $s3) = tree3path3)'
+
+test_expect_success \
+'-- path4: no stage 3, have stage 1 and 3' '
+test $(grep path4 out | cut "-d " -f2) = path4 &&
+grep path4 out | cut "-d " -f1 | (read s1 s2 s3 &&
+test -f $s1 &&
+test -f $s2 &&
+test $s3 = . &&
+test $(cat $s1) = tree1path4 &&
+test $(cat $s2) = tree2path4)'
+
+test_expect_success \
+'-- asubdir/path5: no stage 2 and 3 have stage 1' '
+test $(grep asubdir/path5 out | cut "-d " -f2) = asubdir/path5 &&
+grep asubdir/path5 out | cut "-d " -f1 | (read s1 s2 s3 &&
+test -f $s1 &&
+test $s2 = . &&
+test $s3 = . &&
+test $(cat $s1) = tree1asubdir/path5)'
+
+test_expect_success \
+'checkout --temp within subdir' '
+(cd asubdir &&
+ git-checkout-index -a --stage=all >out &&
+ test $(wc -l <out) = 1 &&
+ test $(grep path5 out | cut "-d " -f2) = path5 &&
+ grep path5 out | cut "-d " -f1 | (read s1 s2 s3 &&
+ test -f ../$s1 &&
+ test $s2 = . &&
+ test $s3 = . &&
+ test $(cat ../$s1) = tree1asubdir/path5)
+)'
+
+test_expect_success \
+'checkout --temp symlink' '
+rm -f path* .merge_* out .git/index &&
+ln -s b a &&
+git-update-index --add a &&
+t4=$(git-write-tree) &&
+rm -f .git/index &&
+git-read-tree $t4 &&
+git-checkout-index --temp -a >out &&
+test $(wc -l <out) = 1 &&
+test $(cut "-d " -f2 out) = a &&
+p=$(cut "-d " -f1 out) &&
+test -f $p &&
+test $(cat $p) = b'
+
+test_done
diff --git a/t/t8001-annotate.sh b/t/t8001-annotate.sh
index 172908a5b0..2496397da3 100755
--- a/t/t8001-annotate.sh
+++ b/t/t8001-annotate.sh
@@ -3,88 +3,7 @@
test_description='git-annotate'
. ./test-lib.sh
-test_expect_success \
- 'prepare reference tree' \
- 'echo "1A quick brown fox jumps over the" >file &&
- echo "lazy dog" >>file &&
- git add file
- GIT_AUTHOR_NAME="A" git commit -a -m "Initial."'
-
-test_expect_success \
- 'check all lines blamed on A' \
- '[ $(git annotate file | awk "{print \$3}" | grep -c "A") == 2 ]'
-
-test_expect_success \
- 'Setup new lines blamed on B' \
- 'echo "2A quick brown fox jumps over the" >>file &&
- echo "lazy dog" >> file &&
- GIT_AUTHOR_NAME="B" git commit -a -m "Second."'
-
-test_expect_success \
- 'Two lines blamed on A' \
- '[ $(git annotate file | awk "{print \$3}" | grep -c "A") == 2 ]'
-
-test_expect_success \
- 'Two lines blamed on B' \
- '[ $(git annotate file | awk "{print \$3}" | grep -c "B") == 2 ]'
-
-test_expect_success \
- 'merge-setup part 1' \
- 'git checkout -b branch1 master &&
- echo "3A slow green fox jumps into the" >> file &&
- echo "well." >> file &&
- GIT_AUTHOR_NAME="B1" git commit -a -m "Branch1-1"'
-
-test_expect_success \
- 'Two lines blamed on A' \
- '[ $(git annotate file | awk "{print \$3}" | grep -c "^A$") == 2 ]'
-
-test_expect_success \
- 'Two lines blamed on B' \
- '[ $(git annotate file | awk "{print \$3}" | grep -c "^B$") == 2 ]'
-
-test_expect_success \
- 'Two lines blamed on B1' \
- '[ $(git annotate file | awk "{print \$3}" | grep -c "^B1$") == 2 ]'
-
-test_expect_success \
- 'merge-setup part 2' \
- 'git checkout -b branch2 master &&
- sed -e "s/2A quick brown/4A quick brown lazy dog/" < file > file.new &&
- mv file.new file &&
- GIT_AUTHOR_NAME="B2" git commit -a -m "Branch2-1"'
-
-test_expect_success \
- 'Two lines blamed on A' \
- '[ $(git annotate file | awk "{print \$3}" | grep -c "^A$") == 2 ]'
-
-test_expect_success \
- 'One line blamed on B' \
- '[ $(git annotate file | awk "{print \$3}" | grep -c "^B$") == 1 ]'
-
-test_expect_success \
- 'One line blamed on B2' \
- '[ $(git annotate file | awk "{print \$3}" | grep -c "^B2$") == 1 ]'
-
-
-test_expect_success \
- 'merge-setup part 3' \
- 'git pull . branch1'
-
-test_expect_success \
- 'Two lines blamed on A' \
- '[ $(git annotate file | awk "{print \$3}" | grep -c "^A$") == 2 ]'
-
-test_expect_success \
- 'One line blamed on B' \
- '[ $(git annotate file | awk "{print \$3}" | grep -c "^B$") == 1 ]'
-
-test_expect_success \
- 'Two lines blamed on B1' \
- '[ $(git annotate file | awk "{print \$3}" | grep -c "^B1$") == 2 ]'
-
-test_expect_success \
- 'One line blamed on B2' \
- '[ $(git annotate file | awk "{print \$3}" | grep -c "^B2$") == 1 ]'
+PROG='git annotate'
+. ../annotate-tests.sh
test_done
diff --git a/t/t8002-blame.sh b/t/t8002-blame.sh
new file mode 100755
index 0000000000..9777393996
--- /dev/null
+++ b/t/t8002-blame.sh
@@ -0,0 +1,9 @@
+#!/bin/sh
+
+test_description='git-blame'
+. ./test-lib.sh
+
+PROG='git blame -c'
+. ../annotate-tests.sh
+
+test_done
diff --git a/upload-pack.c b/upload-pack.c
index 635abb371d..47560c9527 100644
--- a/upload-pack.c
+++ b/upload-pack.c
@@ -46,7 +46,7 @@ static void create_pack_file(void)
if (!pid) {
int i;
int args;
- char **argv;
+ const char **argv;
char *buf;
char **p;
@@ -56,9 +56,9 @@ static void create_pack_file(void)
}
else
args = nr_has + nr_needs + 5;
- argv = xmalloc(args * sizeof(char *));
+ p = xmalloc(args * sizeof(char *));
+ argv = (const char **) p;
buf = xmalloc(args * 45);
- p = argv;
dup2(fd[1], 1);
close(0);