diff options
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 @@ -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 @@ -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; } @@ -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) { @@ -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(); @@ -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); } @@ -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 @@ -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; } } } @@ -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 @@ -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); @@ -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; @@ -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; @@ -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 */ @@ -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); |