summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore10
-rw-r--r--Documentation/Makefile4
-rw-r--r--Documentation/SubmittingPatches10
-rw-r--r--Documentation/asciidoc.conf1
-rw-r--r--Documentation/config.txt75
-rw-r--r--Documentation/core-tutorial.txt2
-rw-r--r--Documentation/cvs-migration.txt2
-rw-r--r--Documentation/diff-format.txt82
-rw-r--r--Documentation/diff-options.txt12
-rw-r--r--Documentation/everyday.txt54
-rw-r--r--Documentation/git-apply.txt69
-rw-r--r--Documentation/git-archive.txt113
-rw-r--r--Documentation/git-blame.txt137
-rw-r--r--Documentation/git-cherry-pick.txt23
-rw-r--r--Documentation/git-cherry.txt24
-rw-r--r--Documentation/git-clone.txt27
-rw-r--r--Documentation/git-daemon.txt145
-rw-r--r--Documentation/git-diff-index.txt4
-rw-r--r--Documentation/git-diff-tree.txt5
-rw-r--r--Documentation/git-fetch-pack.txt3
-rw-r--r--Documentation/git-for-each-ref.txt185
-rw-r--r--Documentation/git-grep.txt20
-rw-r--r--Documentation/git-http-push.txt2
-rw-r--r--Documentation/git-index-pack.txt52
-rw-r--r--Documentation/git-init-db.txt4
-rw-r--r--Documentation/git-log.txt4
-rw-r--r--Documentation/git-ls-remote.txt18
-rw-r--r--Documentation/git-pack-objects.txt63
-rw-r--r--Documentation/git-pack-refs.txt54
-rw-r--r--Documentation/git-pull.txt2
-rw-r--r--Documentation/git-rebase.txt70
-rw-r--r--Documentation/git-receive-pack.txt2
-rw-r--r--Documentation/git-repack.txt27
-rw-r--r--Documentation/git-repo-config.txt34
-rw-r--r--Documentation/git-rev-list.txt262
-rw-r--r--Documentation/git-rev-parse.txt54
-rw-r--r--Documentation/git-runstatus.txt69
-rw-r--r--Documentation/git-send-email.txt9
-rw-r--r--Documentation/git-send-pack.txt2
-rw-r--r--Documentation/git-shortlog.txt17
-rw-r--r--Documentation/git-show-ref.txt156
-rw-r--r--Documentation/git-show.txt5
-rw-r--r--Documentation/git-svn.txt400
-rw-r--r--Documentation/git-tar-tree.txt3
-rw-r--r--Documentation/git-unpack-objects.txt8
-rw-r--r--Documentation/git-update-index.txt8
-rw-r--r--Documentation/git-update-ref.txt10
-rw-r--r--Documentation/git-upload-archive.txt37
-rw-r--r--Documentation/git-upload-tar.txt39
-rw-r--r--Documentation/git.txt399
-rw-r--r--Documentation/gitk.txt69
-rw-r--r--Documentation/glossary.txt11
-rw-r--r--Documentation/hooks.txt56
-rw-r--r--Documentation/pretty-formats.txt78
-rw-r--r--Documentation/repository-layout.txt31
-rw-r--r--Documentation/technical/racy-git.txt193
-rw-r--r--Documentation/tutorial-2.txt2
-rw-r--r--Documentation/tutorial.txt2
-rw-r--r--Documentation/urls.txt8
-rwxr-xr-xGIT-VERSION-GEN2
-rw-r--r--INSTALL13
-rw-r--r--Makefile218
-rw-r--r--archive-tar.c325
-rw-r--r--archive-zip.c350
-rw-r--r--archive.h45
-rw-r--r--blame.c923
-rw-r--r--builtin-add.c1
-rw-r--r--builtin-annotate.c25
-rw-r--r--builtin-apply.c732
-rw-r--r--builtin-archive.c264
-rw-r--r--builtin-blame.c1890
-rw-r--r--builtin-branch.c221
-rw-r--r--builtin-cat-file.c40
-rw-r--r--builtin-checkout-index.c5
-rw-r--r--builtin-commit-tree.c2
-rw-r--r--builtin-count-objects.c2
-rw-r--r--builtin-diff-stages.c2
-rw-r--r--builtin-diff.c9
-rw-r--r--builtin-fmt-merge-msg.c46
-rw-r--r--builtin-for-each-ref.c896
-rw-r--r--builtin-grep.c599
-rw-r--r--builtin-init-db.c5
-rw-r--r--builtin-log.c120
-rw-r--r--builtin-ls-files.c27
-rw-r--r--builtin-ls-tree.c6
-rw-r--r--builtin-mailinfo.c17
-rw-r--r--builtin-mv.c134
-rw-r--r--builtin-name-rev.c10
-rw-r--r--builtin-pack-objects.c632
-rw-r--r--builtin-pack-refs.c107
-rw-r--r--builtin-prune-packed.c13
-rw-r--r--builtin-prune.c10
-rw-r--r--builtin-push.c54
-rw-r--r--builtin-read-tree.c4
-rw-r--r--builtin-repo-config.c49
-rw-r--r--builtin-rev-list.c171
-rw-r--r--builtin-rev-parse.c20
-rw-r--r--builtin-rm.c2
-rw-r--r--builtin-runstatus.c36
-rw-r--r--builtin-show-branch.c96
-rw-r--r--builtin-show-ref.c147
-rw-r--r--builtin-symbolic-ref.c18
-rw-r--r--builtin-tar-tree.c439
-rw-r--r--builtin-unpack-objects.c212
-rw-r--r--builtin-update-index.c24
-rw-r--r--builtin-update-ref.c22
-rw-r--r--builtin-upload-archive.c175
-rw-r--r--builtin-upload-tar.c74
-rw-r--r--builtin-write-tree.c4
-rw-r--r--builtin.h17
-rw-r--r--cache-tree.c18
-rw-r--r--cache.h78
-rwxr-xr-xcheck-builtins.sh34
-rw-r--r--check-racy.c28
-rw-r--r--color.c176
-rw-r--r--color.h12
-rw-r--r--combine-diff.c108
-rw-r--r--commit.c51
-rw-r--r--commit.h2
-rw-r--r--compat/inet_pton.c220
-rw-r--r--compat/mmap.c6
-rw-r--r--config.c29
-rw-r--r--config.mak.in2
-rw-r--r--configure.ac44
-rw-r--r--connect.c106
-rwxr-xr-xcontrib/completion/git-completion.bash403
-rw-r--r--contrib/emacs/git.el48
-rw-r--r--contrib/emacs/vc-git.el13
-rw-r--r--contrib/gitview/gitview.txt56
-rw-r--r--contrib/vim/README8
-rw-r--r--contrib/vim/syntax/gitcommit.vim18
-rw-r--r--convert-objects.c10
-rw-r--r--csum-file.c6
-rw-r--r--daemon.c437
-rw-r--r--date.c132
-rw-r--r--describe.c18
-rw-r--r--diff-delta.c4
-rw-r--r--diff-lib.c32
-rw-r--r--diff.c555
-rw-r--r--diff.h29
-rw-r--r--diffcore-break.c2
-rw-r--r--diffcore-rename.c8
-rw-r--r--dir.c48
-rw-r--r--dir.h1
-rw-r--r--dump-cache-tree.c2
-rw-r--r--entry.c4
-rw-r--r--environment.c19
-rw-r--r--exec_cmd.c20
-rw-r--r--fetch-clone.c301
-rw-r--r--fetch-pack.c147
-rw-r--r--fetch.c15
-rw-r--r--fsck-objects.c57
-rwxr-xr-xgenerate-cmdlist.sh1
-rwxr-xr-xgit-am.sh23
-rwxr-xr-xgit-annotate.perl708
-rwxr-xr-xgit-bisect.sh11
-rwxr-xr-xgit-branch.sh130
-rwxr-xr-xgit-checkout.sh25
-rwxr-xr-xgit-cherry.sh94
-rwxr-xr-xgit-clone.sh82
-rwxr-xr-xgit-commit.sh592
-rw-r--r--git-compat-util.h18
-rwxr-xr-xgit-cvsexportcommit.perl72
-rwxr-xr-xgit-cvsimport.perl89
-rwxr-xr-xgit-cvsserver.perl142
-rwxr-xr-xgit-fetch.sh95
-rwxr-xr-xgit-ls-remote.sh6
-rwxr-xr-xgit-merge-one-file.sh16
-rwxr-xr-xgit-merge-recursive-old.py (renamed from git-merge-recursive.py)0
-rwxr-xr-xgit-merge.sh9
-rwxr-xr-xgit-parse-remote.sh43
-rwxr-xr-xgit-pull.sh18
-rwxr-xr-xgit-rebase.sh20
-rwxr-xr-xgit-repack.sh59
-rwxr-xr-xgit-reset.sh3
-rwxr-xr-xgit-resolve.sh6
-rwxr-xr-xgit-revert.sh25
-rwxr-xr-xgit-send-email.perl80
-rwxr-xr-xgit-shortlog.perl44
-rwxr-xr-xgit-svn.perl340
-rwxr-xr-xgit-svnimport.perl151
-rwxr-xr-xgit-tag.sh18
-rw-r--r--git.c61
-rw-r--r--git.spec.in35
-rwxr-xr-xgitk697
-rw-r--r--gitweb/README49
-rw-r--r--gitweb/git-favicon.pngbin0 -> 164 bytes
-rw-r--r--gitweb/gitweb.css127
-rwxr-xr-xgitweb/gitweb.perl3641
-rw-r--r--grep.c576
-rw-r--r--grep.h81
-rw-r--r--help.c4
-rw-r--r--http-fetch.c342
-rw-r--r--http-push.c58
-rw-r--r--http.c12
-rw-r--r--http.h4
-rw-r--r--imap-send.c47
-rw-r--r--index-pack.c788
-rw-r--r--interpolate.c108
-rw-r--r--interpolate.h26
-rw-r--r--list-objects.c140
-rw-r--r--list-objects.h12
-rw-r--r--local-fetch.c8
-rw-r--r--log-tree.c108
-rw-r--r--merge-base.c2
-rw-r--r--merge-file.c2
-rw-r--r--merge-index.c5
-rw-r--r--merge-recursive.c1352
-rw-r--r--merge-tree.c10
-rw-r--r--mktag.c2
-rw-r--r--mktree.c5
-rw-r--r--object-refs.c11
-rw-r--r--object.c74
-rw-r--r--object.h17
-rw-r--r--pack-check.c25
-rw-r--r--pack-redundant.c18
-rw-r--r--pack.h3
-rw-r--r--pager.c2
-rw-r--r--patch-id.c2
-rw-r--r--path-list.c7
-rw-r--r--path.c12
-rw-r--r--peek-remote.c5
-rw-r--r--perl/.gitignore4
-rw-r--r--perl/Git.pm837
-rw-r--r--perl/Makefile.PL28
-rw-r--r--perl/private-Error.pm827
-rw-r--r--quote.c101
-rw-r--r--quote.h11
-rw-r--r--read-cache.c116
-rw-r--r--receive-pack.c290
-rw-r--r--refs.c624
-rw-r--r--refs.h21
-rw-r--r--revision.c284
-rw-r--r--revision.h14
-rw-r--r--rsh.c31
-rw-r--r--run-command.c8
-rw-r--r--send-pack.c131
-rw-r--r--server-info.c6
-rw-r--r--sha1_file.c674
-rw-r--r--sha1_name.c116
-rw-r--r--show-index.c2
-rw-r--r--sideband.c78
-rw-r--r--sideband.h13
-rw-r--r--ssh-fetch.c10
-rw-r--r--ssh-upload.c4
-rw-r--r--t/annotate-tests.sh1
-rwxr-xr-xt/t0000-basic.sh22
-rwxr-xr-xt/t1004-read-tree-m-u-wf.sh53
-rwxr-xr-xt/t1200-tutorial.sh2
-rwxr-xr-xt/t1400-update-ref.sh89
-rwxr-xr-xt/t3200-branch.sh21
-rwxr-xr-xt/t3210-pack-refs.sh99
-rwxr-xr-xt/t3401-rebase-partial.sh5
-rwxr-xr-xt/t3402-rebase-merge.sh6
-rwxr-xr-xt/t3403-rebase-skip.sh10
-rwxr-xr-xt/t3700-add.sh32
-rw-r--r--t/t4013/diff.diff-tree_--pretty_--root_--summary_initial2
-rwxr-xr-xt/t4015-diff-whitespace.sh122
-rwxr-xr-xt/t4103-apply-binary.sh4
-rwxr-xr-xt/t4104-apply-boundary.sh115
-rwxr-xr-xt/t4116-apply-reverse.sh85
-rwxr-xr-xt/t4117-apply-reject.sh157
-rwxr-xr-xt/t4118-apply-empty-context.sh55
-rwxr-xr-xt/t5000-tar-tree.sh35
-rwxr-xr-xt/t5400-send-pack.sh14
-rwxr-xr-xt/t5510-fetch.sh85
-rwxr-xr-xt/t5520-pull.sh33
-rwxr-xr-xt/t5600-clone-fail-cleanup.sh6
-rwxr-xr-xt/t5710-info-alternate.sh2
-rwxr-xr-xt/t6001-rev-list-graft.sh113
-rwxr-xr-xt/t6021-merge-criss-cross.sh6
-rwxr-xr-xt/t6022-merge-rename.sh171
-rwxr-xr-xt/t7001-mv.sh27
-rwxr-xr-xt/t7201-co.sh9
-rwxr-xr-xt/t9105-git-svn-commit-diff.sh2
-rwxr-xr-xt/t9106-git-svn-commit-diff-clobber.sh74
-rwxr-xr-xt/t9200-git-cvsexportcommit.sh145
-rwxr-xr-xt/test-lib.sh22
-rw-r--r--t/test9200a.pngbin0 -> 5660 bytes
-rw-r--r--t/test9200b.pngbin0 -> 275 bytes
-rw-r--r--templates/hooks--commit-msg6
-rw-r--r--trace.c150
-rw-r--r--tree-diff.c33
-rw-r--r--tree-walk.c4
-rw-r--r--tree.c5
-rw-r--r--unpack-trees.c10
-rw-r--r--upload-pack.c93
-rw-r--r--write_or_die.c45
-rw-r--r--wt-status.c309
-rw-r--r--wt-status.h25
-rw-r--r--xdiff-interface.c12
-rw-r--r--xdiff/xemit.c7
-rw-r--r--xdiff/xmacros.h5
-rw-r--r--xdiff/xutils.c29
294 files changed, 24828 insertions, 8986 deletions
diff --git a/.gitignore b/.gitignore
index 55cd9844d6..4c8c8e4115 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,6 +8,7 @@ git-apply
git-applymbox
git-applypatch
git-archimport
+git-archive
git-bisect
git-branch
git-cat-file
@@ -36,6 +37,7 @@ git-fetch
git-fetch-pack
git-findtags
git-fmt-merge-msg
+git-for-each-ref
git-format-patch
git-fsck-objects
git-get-tar-commit-id
@@ -62,7 +64,9 @@ git-merge-tree
git-merge-octopus
git-merge-one-file
git-merge-ours
+git-merge-recur
git-merge-recursive
+git-merge-recursive-old
git-merge-resolve
git-merge-stupid
git-mktag
@@ -71,6 +75,7 @@ git-name-rev
git-mv
git-pack-redundant
git-pack-objects
+git-pack-refs
git-parse-remote
git-patch-id
git-peek-remote
@@ -93,6 +98,7 @@ git-rev-list
git-rev-parse
git-revert
git-rm
+git-runstatus
git-send-email
git-send-pack
git-sh-setup
@@ -101,6 +107,7 @@ git-shortlog
git-show
git-show-branch
git-show-index
+git-show-ref
git-ssh-fetch
git-ssh-pull
git-ssh-push
@@ -117,8 +124,8 @@ git-unpack-objects
git-update-index
git-update-ref
git-update-server-info
+git-upload-archive
git-upload-pack
-git-upload-tar
git-var
git-verify-pack
git-verify-tag
@@ -139,6 +146,7 @@ git-core.spec
*.py[co]
config.mak
autom4te.cache
+config.cache
config.log
config.status
config.mak.autogen
diff --git a/Documentation/Makefile b/Documentation/Makefile
index 0d9ffb4ad9..c00f5f62b7 100644
--- a/Documentation/Makefile
+++ b/Documentation/Makefile
@@ -33,6 +33,8 @@ man7dir=$(mandir)/man7
INSTALL?=install
+-include ../config.mak.autogen
+
#
# Please note that there is a minor bug in asciidoc.
# The version after 6.0.3 _will_ include the patch found here:
@@ -105,7 +107,7 @@ WEBDOC_DEST = /pub/software/scm/git/docs
$(patsubst %.txt,%.html,$(wildcard howto/*.txt)): %.html : %.txt
rm -f $@+ $@
- sed -e '1,/^$$/d' $? | asciidoc -b xhtml11 - >$@+
+ sed -e '1,/^$$/d' $< | asciidoc -b xhtml11 - >$@+
mv $@+ $@
install-webdoc : html
diff --git a/Documentation/SubmittingPatches b/Documentation/SubmittingPatches
index 90722c21fa..646b6e7331 100644
--- a/Documentation/SubmittingPatches
+++ b/Documentation/SubmittingPatches
@@ -101,8 +101,13 @@ send it "To:" the mailing list, and optionally "cc:" him. If it
is trivially correct or after the list reached a consensus, send
it "To:" the maintainer and optionally "cc:" the list.
+Also note that your maintainer does not actively involve himself in
+maintaining what are in contrib/ hierarchy. When you send fixes and
+enhancements to them, do not forget to "cc: " the person who primarily
+worked on that hierarchy in contrib/.
-(6) Sign your work
+
+(4) Sign your work
To improve tracking of who did what, we've borrowed the
"sign-off" procedure from the Linux kernel project on patches
@@ -144,6 +149,9 @@ then you just add a line saying
Signed-off-by: Random J Developer <random@developer.example.org>
+This line can be automatically added by git if you run the git-commit
+command with the -s option.
+
Some people also put extra tags at the end. They'll just be ignored for
now, but you can do this to mark internal company procedures or just
point out some special detail about the sign-off.
diff --git a/Documentation/asciidoc.conf b/Documentation/asciidoc.conf
index 8196d787ab..44b1ce4c6b 100644
--- a/Documentation/asciidoc.conf
+++ b/Documentation/asciidoc.conf
@@ -11,6 +11,7 @@
caret=^
startsb=&#91;
endsb=&#93;
+tilde=&#126;
ifdef::backend-docbook[]
[gitlink-inlinemacro]
diff --git a/Documentation/config.txt b/Documentation/config.txt
index ce722a2db0..9d3c71c3b8 100644
--- a/Documentation/config.txt
+++ b/Documentation/config.txt
@@ -71,12 +71,16 @@ core.preferSymlinkRefs::
expect HEAD to be a symbolic link.
core.logAllRefUpdates::
- If true, `git-update-ref` will append a line to
- "$GIT_DIR/logs/<ref>" listing the new SHA1 and the date/time
- of the update. If the file does not exist it will be
- created automatically. This information can be used to
- determine what commit was the tip of a branch "2 days ago".
- This value is false by default (no logging).
+ Updates to a ref <ref> is logged to the file
+ "$GIT_DIR/logs/<ref>", by appending the new and old
+ SHA1, the date/time and the reason of the update, but
+ only when the file exists. If this configuration
+ variable is set to true, missing "$GIT_DIR/logs/<ref>"
+ file is automatically created for branch heads.
+
+ This information can be used to determine what commit
+ was the tip of a branch "2 days ago". This value is
+ false by default (no automated creation of log files).
core.repositoryFormatVersion::
Internal variable identifying the repository format and layout
@@ -119,6 +123,13 @@ apply.whitespace::
Tells `git-apply` how to handle whitespaces, in the same way
as the '--whitespace' option. See gitlink:git-apply[1].
+branch.<name>.remote::
+ When in branch <name>, it tells `git fetch` which remote to fetch.
+
+branch.<name>.merge::
+ When in branch <name>, it tells `git fetch` the default remote branch
+ to be merged.
+
pager.color::
A boolean to enable/disable colored output when the pager is in
use (default is true).
@@ -195,6 +206,12 @@ http.lowSpeedLimit, http.lowSpeedTime::
Can be overridden by the 'GIT_HTTP_LOW_SPEED_LIMIT' and
'GIT_HTTP_LOW_SPEED_TIME' environment variables.
+http.noEPSV::
+ A boolean which disables using of EPSV ftp command by curl.
+ This can helpful with some "poor" ftp servers which doesn't
+ support EPSV mode. Can be overridden by the 'GIT_CURL_FTP_NO_EPSV'
+ environment variable. Default is false (curl will use EPSV).
+
i18n.commitEncoding::
Character encoding the commit messages are stored in; git itself
does not care per se, but this information is necessary e.g. when
@@ -217,6 +234,22 @@ pull.octopus::
pull.twohead::
The default merge strategy to use when pulling a single branch.
+remote.<name>.url::
+ The URL of a remote repository. See gitlink:git-fetch[1] or
+ gitlink:git-push[1].
+
+remote.<name>.fetch::
+ The default set of "refspec" for gitlink:git-fetch[1]. See
+ gitlink:git-fetch[1].
+
+remote.<name>.push::
+ The default set of "refspec" for gitlink:git-push[1]. See
+ gitlink:git-push[1].
+
+repack.usedeltabaseoffset::
+ Allow gitlink:git-repack[1] to create packs that uses
+ delta-base offset. Defaults to false.
+
show.difftree::
The default gitlink:git-diff-tree[1] arguments to be used
for gitlink:git-show[1].
@@ -225,6 +258,20 @@ showbranch.default::
The default set of branches for gitlink:git-show-branch[1].
See gitlink:git-show-branch[1].
+status.color::
+ A boolean to enable/disable color in the output of
+ gitlink:git-status[1]. May be set to `true` (or `always`),
+ `false` (or `never`) or `auto`, in which case colors are used
+ only when the output is to a terminal. Defaults to false.
+
+status.color.<slot>::
+ Use customized color for status colorization. `<slot>` is
+ one of `header` (the header text of the status message),
+ `updated` (files which are updated but not committed),
+ `changed` (files which are changed but not updated in the index),
+ or `untracked` (files which are not tracked by git). The values of
+ these variables may be specified as in diff.color.<slot>.
+
tar.umask::
By default, gitlink:git-tar-tree[1] sets file and directories modes
to 0666 or 0777. While this is both useful and acceptable for projects
@@ -253,3 +300,19 @@ whatchanged.difftree::
imap::
The configuration variables in the 'imap' section are described
in gitlink:git-imap-send[1].
+
+receive.unpackLimit::
+ If the number of objects received in a push is below this
+ limit then the objects will be unpacked into loose object
+ files. However if the number of received objects equals or
+ exceeds this limit then the received pack will be stored as
+ a pack, after adding any missing delta bases. Storing the
+ pack from a push can make the push operation complete faster,
+ especially on slow filesystems.
+
+receive.denyNonFastForwards::
+ If set to true, git-receive-pack will deny a ref update which is
+ not a fast forward. Use this to prevent such an update via a push,
+ even if that push is forced. This configuration variable is
+ set when initializing a shared repository.
+
diff --git a/Documentation/core-tutorial.txt b/Documentation/core-tutorial.txt
index 1185897f70..47505aa20a 100644
--- a/Documentation/core-tutorial.txt
+++ b/Documentation/core-tutorial.txt
@@ -1620,7 +1620,7 @@ suggested in the previous section may be new to you. You do not
have to worry. git supports "shared public repository" style of
cooperation you are probably more familiar with as well.
-See link:cvs-migration.txt[git for CVS users] for the details.
+See link:cvs-migration.html[git for CVS users] for the details.
Bundling your work together
---------------------------
diff --git a/Documentation/cvs-migration.txt b/Documentation/cvs-migration.txt
index d2b0bd38de..6812683a16 100644
--- a/Documentation/cvs-migration.txt
+++ b/Documentation/cvs-migration.txt
@@ -172,7 +172,7 @@ Advanced Shared Repository Management
Git allows you to specify scripts called "hooks" to be run at certain
points. You can use these, for example, to send all commits to the shared
-repository to a mailing list. See link:hooks.txt[Hooks used by git].
+repository to a mailing list. See link:hooks.html[Hooks used by git].
You can enforce finer grained permissions using update hooks. See
link:howto/update-hook-example.txt[Controlling access to branches using
diff --git a/Documentation/diff-format.txt b/Documentation/diff-format.txt
index 617d8f526f..e4520e28e5 100644
--- a/Documentation/diff-format.txt
+++ b/Documentation/diff-format.txt
@@ -144,8 +144,10 @@ the file that rename/copy produces, respectively.
dissimilarity index <number>
index <hash>..<hash> <mode>
-3. TAB, LF, and backslash characters in pathnames are
- represented as `\t`, `\n`, and `\\`, respectively.
+3. TAB, LF, double quote and backslash characters in pathnames
+ are represented as `\t`, `\n`, `\"` and `\\`, respectively.
+ If there is need for such substitution then the whole
+ pathname is put in double quotes.
combined diff format
@@ -156,31 +158,91 @@ to produce 'combined diff', which looks like this:
------------
diff --combined describe.c
-@@@ +98,7 @@@
- return (a_date > b_date) ? -1 : (a_date == b_date) ? 0 : 1;
+index fabadb8,cc95eb0..4866510
+--- a/describe.c
++++ b/describe.c
+@@@ -98,20 -98,12 +98,20 @@@
+ return (a_date > b_date) ? -1 : (a_date == b_date) ? 0 : 1;
}
-
+
- static void describe(char *arg)
-static void describe(struct commit *cmit, int last_one)
++static void describe(char *arg, int last_one)
{
- + unsigned char sha1[20];
- + struct commit *cmit;
+ + unsigned char sha1[20];
+ + struct commit *cmit;
+ struct commit_list *list;
+ static int initialized = 0;
+ struct commit_name *n;
+
+ + if (get_sha1(arg, sha1) < 0)
+ + usage(describe_usage);
+ + cmit = lookup_commit_reference(sha1);
+ + if (!cmit)
+ + usage(describe_usage);
+ +
+ if (!initialized) {
+ initialized = 1;
+ for_each_ref(get_name);
------------
+1. It is preceded with a "git diff" header, that looks like
+ this (when '-c' option is used):
+
+ diff --combined file
++
+or like this (when '--cc' option is used):
+
+ diff --c file
+
+2. It is followed by one or more extended header lines
+ (this example shows a merge with two parents):
+
+ index <hash>,<hash>..<hash>
+ mode <mode>,<mode>..<mode>
+ new file mode <mode>
+ deleted file mode <mode>,<mode>
++
+The `mode <mode>,<mode>..<mode>` line appears only if at least one of
+the <mode> is diferent from the rest. Extended headers with
+information about detected contents movement (renames and
+copying detection) are designed to work with diff of two
+<tree-ish> and are not used by combined diff format.
+
+3. It is followed by two-line from-file/to-file header
+
+ --- a/file
+ +++ b/file
++
+Similar to two-line header for traditional 'unified' diff
+format, `/dev/null` is used to signal created or deleted
+files.
+
+4. Chunk header format is modified to prevent people from
+ accidentally feeding it to `patch -p1`. Combined diff format
+ was created for review of merge commit changes, and was not
+ meant for apply. The change is similar to the change in the
+ extended 'index' header:
+
+ @@@ <from-file-range> <from-file-range> <to-file-range> @@@
++
+There are (number of parents + 1) `@` characters in the chunk
+header for combined diff format.
+
Unlike the traditional 'unified' diff format, which shows two
files A and B with a single column that has `-` (minus --
appears in A but removed in B), `+` (plus -- missing in A but
-added to B), or ` ` (space -- unchanged) prefix, this format
+added to B), or `" "` (space -- unchanged) prefix, this format
compares two or more files file1, file2,... with one file X, and
shows how X differs from each of fileN. One column for each of
fileN is prepended to the output line to note how X's line is
different from it.
A `-` character in the column N means that the line appears in
-fileN but it does not appear in the last file. A `+` character
+fileN but it does not appear in the result. A `+` character
in the column N means that the line appears in the last file,
-and fileN does not have that line.
+and fileN does not have that line (in other words, the line was
+added, from the point of view of that parent).
In the above example output, the function signature was changed
from both files (hence two `-` removals from both file1 and
diff --git a/Documentation/diff-options.txt b/Documentation/diff-options.txt
index b5d9763594..e112172ca5 100644
--- a/Documentation/diff-options.txt
+++ b/Documentation/diff-options.txt
@@ -10,8 +10,16 @@
--patch-with-raw::
Synonym for "-p --raw".
---stat::
- Generate a diffstat.
+--stat[=width[,name-width]]::
+ Generate a diffstat. You can override the default
+ output width for 80-column terminal by "--stat=width".
+ The width of the filename part can be controlled by
+ giving another width to it separated by a comma.
+
+--numstat::
+ Similar to \--stat, but shows number of added and
+ deleted lines in decimal notation and pathname without
+ abbreviation, to make it more machine friendly.
--summary::
Output a condensed summary of extended header information
diff --git a/Documentation/everyday.txt b/Documentation/everyday.txt
index b935c18088..9677671892 100644
--- a/Documentation/everyday.txt
+++ b/Documentation/everyday.txt
@@ -1,22 +1,7 @@
Everyday GIT With 20 Commands Or So
===================================
-GIT suite has over 100 commands, and the manual page for each of
-them discusses what the command does and how it is used in
-detail, but until you know what command should be used in order
-to achieve what you want to do, you cannot tell which manual
-page to look at, and if you know that already you do not need
-the manual.
-
-Does that mean you need to know all of them before you can use
-git? Not at all. Depending on the role you play, the set of
-commands you need to know is slightly different, but in any case
-what you need to learn is far smaller than the full set of
-commands to carry out your day-to-day work. This document is to
-serve as a cheat-sheet and a set of pointers for people playing
-various roles.
-
-<<Basic Repository>> commands are needed by people who has a
+<<Basic Repository>> commands are needed by people who have a
repository --- that is everybody, because every working tree of
git is a repository.
@@ -25,28 +10,27 @@ essential for anybody who makes a commit, even for somebody who
works alone.
If you work with other people, you will need commands listed in
-<<Individual Developer (Participant)>> section as well.
+the <<Individual Developer (Participant)>> section as well.
-People who play <<Integrator>> role need to learn some more
+People who play the <<Integrator>> role need to learn some more
commands in addition to the above.
<<Repository Administration>> commands are for system
-administrators who are responsible to care and feed git
-repositories to support developers.
+administrators who are responsible for the care and feeding
+of git repositories.
Basic Repository[[Basic Repository]]
------------------------------------
-Everybody uses these commands to feed and care git repositories.
+Everybody uses these commands to maintain git repositories.
* gitlink:git-init-db[1] or gitlink:git-clone[1] to create a
new repository.
- * gitlink:git-fsck-objects[1] to validate the repository.
+ * gitlink:git-fsck-objects[1] to check the repository for errors.
- * gitlink:git-prune[1] to garbage collect cruft in the
- repository.
+ * gitlink:git-prune[1] to remove unused objects in the repository.
* gitlink:git-repack[1] to pack loose objects for efficiency.
@@ -78,8 +62,8 @@ $ git repack -a -d <1>
$ git prune
------------
+
-<1> pack all the objects reachable from the refs into one pack
-and remove unneeded other packs
+<1> pack all the objects reachable from the refs into one pack,
+then remove the other packs.
Individual Developer (Standalone)[[Individual Developer (Standalone)]]
@@ -93,9 +77,6 @@ following commands.
* gitlink:git-log[1] to see what happened.
- * gitlink:git-whatchanged[1] to find out where things have
- come from.
-
* gitlink:git-checkout[1] and gitlink:git-branch[1] to switch
branches.
@@ -120,7 +101,7 @@ following commands.
Examples
~~~~~~~~
-Extract a tarball and create a working tree and a new repository to keep track of it.::
+Use a tarball as a starting point for a new repository:
+
------------
$ tar zxf frotz.tar.gz
@@ -203,7 +184,7 @@ $ cd my2.6
$ edit/compile/test; git commit -a -s <1>
$ git format-patch origin <2>
$ git pull <3>
-$ git whatchanged -p ORIG_HEAD.. arch/i386 include/asm-i386 <4>
+$ git log -p ORIG_HEAD.. arch/i386 include/asm-i386 <4>
$ git pull git://git.kernel.org/pub/.../jgarzik/libata-dev.git ALL <5>
$ git reset --hard ORIG_HEAD <6>
$ git prune <7>
@@ -372,12 +353,19 @@ example of managing a shared central repository.
Examples
~~~~~~~~
+We assume the following in /etc/services::
++
+------------
+$ grep 9418 /etc/services
+git 9418/tcp # Git Version Control System
+------------
+
Run git-daemon to serve /pub/scm from inetd.::
+
------------
$ grep git /etc/inetd.conf
git stream tcp nowait nobody \
- /usr/bin/git-daemon git-daemon --inetd --syslog --export-all /pub/scm
+ /usr/bin/git-daemon git-daemon --inetd --export-all /pub/scm
------------
+
The actual configuration line should be on one line.
@@ -397,7 +385,7 @@ service git
wait = no
user = nobody
server = /usr/bin/git-daemon
- server_args = --inetd --syslog --export-all --base-path=/pub/scm
+ server_args = --inetd --export-all --base-path=/pub/scm
log_on_failure += USERID
}
------------
diff --git a/Documentation/git-apply.txt b/Documentation/git-apply.txt
index 2ff74949a7..2cc32d1c5e 100644
--- a/Documentation/git-apply.txt
+++ b/Documentation/git-apply.txt
@@ -10,9 +10,10 @@ SYNOPSIS
--------
[verse]
'git-apply' [--stat] [--numstat] [--summary] [--check] [--index] [--apply]
- [--no-add] [--index-info] [--allow-binary-replacement] [-z] [-pNUM]
- [-CNUM] [--whitespace=<nowarn|warn|error|error-all|strip>]
- [<patch>...]
+ [--no-add] [--index-info] [--allow-binary-replacement | --binary]
+ [-R | --reverse] [--reject] [-z] [-pNUM] [-CNUM] [--inaccurate-eof]
+ [--whitespace=<nowarn|warn|error|error-all|strip>] [--exclude=PATH]
+ [--cached] [--verbose] [<patch>...]
DESCRIPTION
-----------
@@ -55,6 +56,11 @@ OPTIONS
up-to-date, it is flagged as an error. This flag also
causes the index file to be updated.
+--cached::
+ Apply a patch without touching the working tree. Instead, take the
+ cached data, apply the patch, and store the result in the index,
+ without using the working tree. This implies '--index'.
+
--index-info::
Newer git-diff output has embedded 'index information'
for each blob to help identify the original version that
@@ -62,6 +68,16 @@ OPTIONS
the original version of the blob is available locally,
outputs information about them to the standard output.
+-R, --reverse::
+ Apply the patch in reverse.
+
+--reject::
+ For atomicity, gitlink:git-apply[1] by default fails the whole patch and
+ does not touch the working tree when some of the hunks
+ do not apply. This option makes it apply
+ the parts of the patch that are applicable, and leave the
+ rejected hunks in corresponding *.rej files.
+
-z::
When showing the index information, do not munge paths,
but use NUL terminated machine readable format. Without
@@ -79,9 +95,19 @@ OPTIONS
context exist they all must match. By default no context is
ever ignored.
+--unidiff-zero::
+ By default, gitlink:git-apply[1] expects that the patch being
+ applied is a unified diff with at least one line of context.
+ This provides good safety measures, but breaks down when
+ applying a diff generated with --unified=0. To bypass these
+ checks use '--unidiff-zero'.
++
+Note, for the reasons stated above usage of context-free patches are
+discouraged.
+
--apply::
- If you use any of the options marked ``Turns off
- "apply"'' above, git-apply reads and outputs the
+ If you use any of the options marked "Turns off
+ 'apply'" above, gitlink:git-apply[1] reads and outputs the
information you asked without actually applying the
patch. Give this flag after those flags to also apply
the patch.
@@ -93,16 +119,16 @@ OPTIONS
the result with this option, which would apply the
deletion part but not addition part.
---allow-binary-replacement::
- When applying a patch, which is a git-enhanced patch
- that was prepared to record the pre- and post-image object
- name in full, and the path being patched exactly matches
- the object the patch applies to (i.e. "index" line's
- pre-image object name is what is in the working tree),
- and the post-image object is available in the object
- database, use the post-image object as the patch
- result. This allows binary files to be patched in a
- very limited way.
+--allow-binary-replacement, --binary::
+ Historically we did not allow binary patch applied
+ without an explicit permission from the user, and this
+ flag was the way to do so. Currently we always allow binary
+ patch application, so this is a no-op.
+
+--exclude=<path-pattern>::
+ Don't apply changes to files matching the given path pattern. This can
+ be useful when importing patchsets, where you want to exclude certain
+ files or directories.
--whitespace=<option>::
When applying a patch, detect a new or modified line
@@ -110,7 +136,7 @@ OPTIONS
line that solely consists of whitespaces). By default,
the command outputs warning messages and applies the
patch.
- When `git-apply` is used for statistics and not applying a
+ When gitlink:git-apply[1] is used for statistics and not applying a
patch, it defaults to `nowarn`.
You can use different `<option>` to control this
behavior:
@@ -124,6 +150,17 @@ OPTIONS
* `strip` outputs warnings for a few such errors, strips out the
trailing whitespaces and applies the patch.
+--inaccurate-eof::
+ Under certain circumstances, some versions of diff do not correctly
+ detect a missing new-line at the end of the file. As a result, patches
+ created by such diff programs do not record incomplete lines
+ correctly. This option adds support for applying such patches by
+ working around this bug.
+
+--verbose::
+ Report progress to stderr. By default, only a message about the
+ current patch being applied will be printed. This option will cause
+ additional information to be reported.
Configuration
-------------
diff --git a/Documentation/git-archive.txt b/Documentation/git-archive.txt
new file mode 100644
index 0000000000..031fcd5190
--- /dev/null
+++ b/Documentation/git-archive.txt
@@ -0,0 +1,113 @@
+git-archive(1)
+==============
+
+NAME
+----
+git-archive - Creates a archive of the files in the named tree
+
+
+SYNOPSIS
+--------
+'git-archive' --format=<fmt> [--list] [--prefix=<prefix>/] [<extra>]
+ [--remote=<repo>] <tree-ish> [path...]
+
+DESCRIPTION
+-----------
+Creates an archive of the specified format containing the tree
+structure for the named tree. If <prefix> is specified it is
+prepended to the filenames in the archive.
+
+'git-archive' behaves differently when given a tree ID versus when
+given a commit ID or tag ID. In the first case the current time is
+used as modification time of each file in the archive. In the latter
+case the commit time as recorded in the referenced commit object is
+used instead. Additionally the commit ID is stored in a global
+extended pax header if the tar format is used; it can be extracted
+using 'git-get-tar-commit-id'. In ZIP files it is stored as a file
+comment.
+
+OPTIONS
+-------
+
+--format=<fmt>::
+ Format of the resulting archive: 'tar', 'zip'...
+
+--list::
+ Show all available formats.
+
+--prefix=<prefix>/::
+ Prepend <prefix>/ to each filename in the archive.
+
+<extra>::
+ This can be any options that the archiver backend understand.
+ See next section.
+
+--remote=<repo>::
+ Instead of making a tar archive from local repository,
+ retrieve a tar archive from a remote repository.
+
+<tree-ish>::
+ The tree or commit to produce an archive for.
+
+path::
+ If one or more paths are specified, include only these in the
+ archive, otherwise include all files and subdirectories.
+
+BACKEND EXTRA OPTIONS
+---------------------
+
+zip
+~~~
+-0::
+ Store the files instead of deflating them.
+-9::
+ Highest and slowest compression level. You can specify any
+ number from 1 to 9 to adjust compression speed and ratio.
+
+
+CONFIGURATION
+-------------
+By default, file and directories modes are set to 0666 or 0777 in tar
+archives. It is possible to change this by setting the "umask" variable
+in the repository configuration as follows :
+
+[tar]
+ umask = 002 ;# group friendly
+
+The special umask value "user" indicates that the user's current umask
+will be used instead. The default value remains 0, which means world
+readable/writable files and directories.
+
+EXAMPLES
+--------
+git archive --format=tar --prefix=junk/ HEAD | (cd /var/tmp/ && tar xf -)::
+
+ Create a tar archive that contains the contents of the
+ latest commit on the current branch, and extracts it in
+ `/var/tmp/junk` directory.
+
+git archive --format=tar --prefix=git-1.4.0/ v1.4.0 | gzip >git-1.4.0.tar.gz::
+
+ Create a compressed tarball for v1.4.0 release.
+
+git archive --format=tar --prefix=git-1.4.0/ v1.4.0{caret}\{tree\} | gzip >git-1.4.0.tar.gz::
+
+ Create a compressed tarball for v1.4.0 release, but without a
+ global extended pax header.
+
+git archive --format=zip --prefix=git-docs/ HEAD:Documentation/ > git-1.4.0-docs.zip::
+
+ Put everything in the current head's Documentation/ directory
+ into 'git-1.4.0-docs.zip', with the prefix 'git-docs/'.
+
+Author
+------
+Written by Franck Bui-Huu and Rene Scharfe.
+
+Documentation
+--------------
+Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>.
+
+GIT
+---
+Part of the gitlink:git[7] suite
diff --git a/Documentation/git-blame.txt b/Documentation/git-blame.txt
index bfed945914..bdfc666928 100644
--- a/Documentation/git-blame.txt
+++ b/Documentation/git-blame.txt
@@ -3,21 +3,45 @@ git-blame(1)
NAME
----
-git-blame - Blame file lines on commits
+git-blame - Show what revision and author last modified each line of a file
SYNOPSIS
--------
-git-blame file [options] file [revision]
+[verse]
+'git-blame' [-c] [-l] [-t] [-f] [-n] [-p] [-L n,m] [-S <revs-file>]
+ [-M] [-C] [-C] [--since=<date>] [<rev>] [--] <file>
DESCRIPTION
-----------
-Annotates each line in the given file with information from the commit
-which introduced the line. Start annotation from the given revision.
+
+Annotates each line in the given file with information from the revision which
+last modified the line. Optionally, start annotating from the given revision.
+
+Also it can limit the range of lines annotated.
+
+This report doesn't tell you anything about lines which have been deleted or
+replaced; you need to use a tool such as gitlink:git-diff[1] or the "pickaxe"
+interface briefly mentioned in the following paragraph.
+
+Apart from supporting file annotation, git also supports searching the
+development history for when a code snippet occured in a change. This makes it
+possible to track when a code snippet was added to a file, moved or copied
+between files, and eventually deleted or replaced. It works by searching for
+a text string in the diff. A small example:
+
+-----------------------------------------------------------------------------
+$ git log --pretty=oneline -S'blame_usage'
+5040f17eba15504bad66b14a645bddd9b015ebb7 blame -S <ancestry-file>
+ea4c7f9bf69e781dd0cd88d2bccb2bf5cc15c9a7 git-blame: Make the output
+-----------------------------------------------------------------------------
OPTIONS
-------
-c, --compatibility::
- Use the same output mode as git-annotate (Default: off).
+ Use the same output mode as gitlink:git-annotate[1] (Default: off).
+
+-L n,m::
+ Annotate only the specified line range (lines count from 1).
-l, --long::
Show long rev (Default: off).
@@ -26,19 +50,118 @@ OPTIONS
Show raw timestamp (Default: off).
-S, --rev-file <revs-file>::
- Use revs from revs-file instead of calling git-rev-list.
+ Use revs from revs-file instead of calling gitlink:git-rev-list[1].
+
+-f, --show-name::
+ Show filename in the original commit. By default
+ filename is shown if there is any line that came from a
+ file with different name, due to rename detection.
+
+-n, --show-number::
+ Show line number in the original commit (Default: off).
+
+-p, --porcelain::
+ Show in a format designed for machine consumption.
+
+-M::
+ Detect moving lines in the file as well. When a commit
+ moves a block of lines in a file (e.g. the original file
+ has A and then B, and the commit changes it to B and
+ then A), traditional 'blame' algorithm typically blames
+ the lines that were moved up (i.e. B) to the parent and
+ assigns blame to the lines that were moved down (i.e. A)
+ to the child commit. With this option, both groups of
+ lines are blamed on the parent.
+
+-C::
+ In addition to `-M`, detect lines copied from other
+ files that were modified in the same commit. This is
+ useful when you reorganize your program and move code
+ around across files. When this option is given twice,
+ the command looks for copies from all other files in the
+ parent for the commit that creates the file in addition.
-h, --help::
Show help message.
+THE PORCELAIN FORMAT
+--------------------
+
+In this format, each line is output after a header; the
+header at the minumum has the first line which has:
+
+- 40-byte SHA-1 of the commit the line is attributed to;
+- the line number of the line in the original file;
+- the line number of the line in the final file;
+- on a line that starts a group of line from a different
+ commit than the previous one, the number of lines in this
+ group. On subsequent lines this field is absent.
+
+This header line is followed by the following information
+at least once for each commit:
+
+- author name ("author"), email ("author-mail"), time
+ ("author-time"), and timezone ("author-tz"); similarly
+ for committer.
+- filename in the commit the line is attributed to.
+- the first line of the commit log message ("summary").
+
+The contents of the actual line is output after the above
+header, prefixed by a TAB. This is to allow adding more
+header elements later.
+
+
+SPECIFIYING RANGES
+------------------
+
+Unlike `git-blame` and `git-annotate` in older git, the extent
+of annotation can be limited to both line ranges and revision
+ranges. When you are interested in finding the origin for
+ll. 40-60 for file `foo`, you can use `-L` option like this:
+
+ git blame -L 40,60 foo
+
+Also you can use regular expression to specify the line range.
+
+ git blame -L '/^sub hello {/,/^}$/' foo
+
+would limit the annotation to the body of `hello` subroutine.
+
+When you are not interested in changes older than the version
+v2.6.18, or changes older than 3 weeks, you can use revision
+range specifiers similar to `git-rev-list`:
+
+ git blame v2.6.18.. -- foo
+ git blame --since=3.weeks -- foo
+
+When revision range specifiers are used to limit the annotation,
+lines that have not changed since the range boundary (either the
+commit v2.6.18 or the most recent commit that is more than 3
+weeks old in the above example) are blamed for that range
+boundary commit.
+
+A particularly useful way is to see if an added file have lines
+created by copy-and-paste from existing files. Sometimes this
+indicates that the developer was being sloppy and did not
+refactor the code properly. You can first find the commit that
+introduced the file with:
+
+ git log --diff-filter=A --pretty=short -- foo
+
+and then annotate the change between the commit and its
+parents, using `commit{caret}!` notation:
+
+ git blame -C -C -f $commit^! -- foo
+
+
SEE ALSO
--------
gitlink:git-annotate[1]
AUTHOR
------
-Written by Fredrik Kuivinen <freku045@student.liu.se>.
+Written by Junio C Hamano <junkio@cox.net>
GIT
---
diff --git a/Documentation/git-cherry-pick.txt b/Documentation/git-cherry-pick.txt
index bfa950ca19..875edb6b9f 100644
--- a/Documentation/git-cherry-pick.txt
+++ b/Documentation/git-cherry-pick.txt
@@ -7,7 +7,7 @@ git-cherry-pick - Apply the change introduced by an existing commit
SYNOPSIS
--------
-'git-cherry-pick' [--edit] [-n] [-r] <commit>
+'git-cherry-pick' [--edit] [-n] [-x] <commit>
DESCRIPTION
-----------
@@ -24,13 +24,22 @@ OPTIONS
With this option, `git-cherry-pick` will let you edit the commit
message prior committing.
--r|--replay::
- Usually the command appends which commit was
+-x::
+ Cause the command to append which commit was
cherry-picked after the original commit message when
- making a commit. This option, '--replay', causes it to
- use the original commit message intact. This is useful
- when you are reordering the patches in your private tree
- before publishing.
+ making a commit. Do not use this option if you are
+ cherry-picking from your private branch because the
+ information is useless to the recipient. If on the
+ other hand you are cherry-picking between two publicly
+ visible branches (e.g. backporting a fix to a
+ maintenance branch for an older release from a
+ development branch), adding this information can be
+ useful.
+
+-r|--replay::
+ It used to be that the command defaulted to do `-x`
+ described above, and `-r` was to disable it. Now the
+ default is not to do `-x` so this option is a no-op.
-n|--no-commit::
Usually the command automatically creates a commit with
diff --git a/Documentation/git-cherry.txt b/Documentation/git-cherry.txt
index 893baaa6f6..27b67b81a5 100644
--- a/Documentation/git-cherry.txt
+++ b/Documentation/git-cherry.txt
@@ -7,17 +7,33 @@ git-cherry - Find commits not merged upstream
SYNOPSIS
--------
-'git-cherry' [-v] <upstream> [<head>]
+'git-cherry' [-v] <upstream> [<head>] [<limit>]
DESCRIPTION
-----------
The changeset (or "diff") of each commit between the fork-point and <head>
is compared against each commit between the fork-point and <upstream>.
-Every commit with a changeset that doesn't exist in the other branch
-has its id (sha1) reported, prefixed by a symbol. Those existing only
+Every commit that doesn't exist in the <upstream> branch
+has its id (sha1) reported, prefixed by a symbol. The ones that have
+equivalent change already
in the <upstream> branch are prefixed with a minus (-) sign, and those
-that only exist in the <head> branch are prefixed with a plus (+) symbol.
+that only exist in the <head> branch are prefixed with a plus (+) symbol:
+
+ __*__*__*__*__> <upstream>
+ /
+ fork-point
+ \__+__+__-__+__+__-__+__> <head>
+
+
+If a <limit> has been given then the commits along the <head> branch up
+to and including <limit> are not reported:
+
+ __*__*__*__*__> <upstream>
+ /
+ fork-point
+ \__*__*__<limit>__-__+__> <head>
+
Because git-cherry compares the changeset rather than the commit id
(sha1), you can use git-cherry to find out if a commit you made locally
diff --git a/Documentation/git-clone.txt b/Documentation/git-clone.txt
index f973c64313..4cb42237b5 100644
--- a/Documentation/git-clone.txt
+++ b/Documentation/git-clone.txt
@@ -11,7 +11,8 @@ SYNOPSIS
[verse]
'git-clone' [--template=<template_directory>] [-l [-s]] [-q] [-n] [--bare]
[-o <name>] [-u <upload-pack>] [--reference <repository>]
- [--use-separate-remote] <repository> [<directory>]
+ [--use-separate-remote | --use-immingled-remote] <repository>
+ [<directory>]
DESCRIPTION
-----------
@@ -71,10 +72,15 @@ OPTIONS
Make a 'bare' GIT repository. That is, instead of
creating `<directory>` and placing the administrative
files in `<directory>/.git`, make the `<directory>`
- itself the `$GIT_DIR`. This implies `-n` option. When
- this option is used, neither the `origin` branch nor the
- default `remotes/origin` file is created.
-
+ itself the `$GIT_DIR`. This obviously implies the `-n`
+ because there is nowhere to check out the working tree.
+ Also the branch heads at the remote are copied directly
+ to corresponding local branch heads, without mapping
+ them to `refs/remotes/origin/`. When this option is
+ used, neither the `origin` branch nor the default
+ `remotes/origin` file is created.
+
+--origin <name>::
-o <name>::
Instead of using the branch name 'origin' to keep track
of the upstream repository, use <name> instead. Note
@@ -96,8 +102,15 @@ OPTIONS
--use-separate-remote::
Save remotes heads under `$GIT_DIR/remotes/origin/` instead
- of `$GIT_DIR/refs/heads/`. Only the master branch is saved
- in the latter.
+ of `$GIT_DIR/refs/heads/`. Only the local master branch is
+ saved in the latter. This is the default.
+
+--use-immingled-remote::
+ Save remotes heads in the same namespace as the local
+ heads, `$GIT_DIR/refs/heads/'. In regular repositories,
+ this is a legacy setup git-clone created by default in
+ older Git versions, and will be removed before the next
+ major release.
<repository>::
The (possibly remote) repository to clone from. It can
diff --git a/Documentation/git-daemon.txt b/Documentation/git-daemon.txt
index 0f7d274eab..993adc7c5a 100644
--- a/Documentation/git-daemon.txt
+++ b/Documentation/git-daemon.txt
@@ -8,19 +8,21 @@ git-daemon - A really simple server for git repositories
SYNOPSIS
--------
[verse]
-'git-daemon' [--verbose] [--syslog] [--inetd | --port=n] [--export-all]
+'git-daemon' [--verbose] [--syslog] [--export-all]
[--timeout=n] [--init-timeout=n] [--strict-paths]
[--base-path=path] [--user-path | --user-path=path]
- [--reuseaddr] [--detach] [--pid-file=file] [directory...]
+ [--interpolated-path=pathtemplate]
+ [--reuseaddr] [--detach] [--pid-file=file]
+ [--enable=service] [--disable=service]
+ [--allow-override=service] [--forbid-override=service]
+ [--inetd | [--listen=host_or_ipaddr] [--port=n] [--user=user [--group=group]]
+ [directory...]
DESCRIPTION
-----------
A really simple TCP git daemon that normally listens on port "DEFAULT_GIT_PORT"
-aka 9418. It waits for a connection, and will just execute "git-upload-pack"
-when it gets one.
-
-It's careful in that there's a magic request-line that gives the command and
-what directory to upload, and it verifies that the directory is OK.
+aka 9418. It waits for a connection asking for a service, and will serve
+that service if it is enabled.
It verifies that the directory has the magic file "git-daemon-export-ok", and
it will refuse to export any git directory that hasn't explicitly been marked
@@ -28,7 +30,14 @@ for export this way (unless the '--export-all' parameter is specified). If you
pass some directory paths as 'git-daemon' arguments, you can further restrict
the offers to a whitelist comprising of those.
-This is ideally suited for read-only updates, i.e., pulling from git repositories.
+By default, only `upload-pack` service is enabled, which serves
+`git-fetch-pack` and `git-peek-remote` clients that are invoked
+from `git-fetch`, `git-ls-remote`, and `git-clone`.
+
+This is ideally suited for read-only updates, i.e., pulling from
+git repositories.
+
+An `upload-archive` also exists to serve `git-archive`.
OPTIONS
-------
@@ -45,6 +54,16 @@ OPTIONS
'git://example.com/hello.git', `git-daemon` will interpret the path
as '/srv/git/hello.git'.
+--interpolated-path=pathtemplate::
+ To support virtual hosting, an interpolated path template can be
+ used to dynamically construct alternate paths. The template
+ supports %H for the target hostname as supplied by the client but
+ converted to all lowercase, %CH for the canonical hostname,
+ %IP for the server's IP address, %P for the port number,
+ and %D for the absolute path of the named repository.
+ After interpolation, the path is validated against the directory
+ whitelist.
+
--export-all::
Allow pulling from all directories that look like GIT repositories
(have the 'objects' and 'refs' subdirectories), even if they
@@ -52,9 +71,17 @@ OPTIONS
--inetd::
Have the server run as an inetd service. Implies --syslog.
+ Incompatible with --port, --listen, --user and --group options.
---port::
- Listen on an alternative port.
+--listen=host_or_ipaddr::
+ Listen on an a specific IP address or hostname. IP addresses can
+ be either an IPv4 address or an IPV6 address if supported. If IPv6
+ is not supported, then --listen=hostname is also not supported and
+ --listen must be given an IPv4 address.
+ Incompatible with '--inetd' option.
+
+--port=n::
+ Listen on an alternative port. Incompatible with '--inetd' option.
--init-timeout::
Timeout between the moment the connection is established and the
@@ -93,11 +120,109 @@ OPTIONS
--pid-file=file::
Save the process id in 'file'.
+--user=user, --group=group::
+ Change daemon's uid and gid before entering the service loop.
+ When only `--user` is given without `--group`, the
+ primary group ID for the user is used. The values of
+ the option are given to `getpwnam(3)` and `getgrnam(3)`
+ and numeric IDs are not supported.
++
+Giving these options is an error when used with `--inetd`; use
+the facility of inet daemon to achieve the same before spawning
+`git-daemon` if needed.
+
+--enable-service, --disable-service::
+ Enable/disable the service site-wide per default. Note
+ that a service disabled site-wide can still be enabled
+ per repository if it is marked overridable and the
+ repository enables the service with an configuration
+ item.
+
+--allow-override, --forbid-override::
+ Allow/forbid overriding the site-wide default with per
+ repository configuration. By default, all the services
+ are overridable.
+
<directory>::
A directory to add to the whitelist of allowed directories. Unless
--strict-paths is specified this will also include subdirectories
of each named directory.
+SERVICES
+--------
+
+upload-pack::
+ This serves `git-fetch-pack` and `git-peek-remote`
+ clients. It is enabled by default, but a repository can
+ disable it by setting `daemon.uploadpack` configuration
+ item to `false`.
+
+upload-archive::
+ This serves `git-archive --remote`.
+
+EXAMPLES
+--------
+We assume the following in /etc/services::
++
+------------
+$ grep 9418 /etc/services
+git 9418/tcp # Git Version Control System
+------------
+
+git-daemon as inetd server::
+ To set up `git-daemon` as an inetd service that handles any
+ repository under the whitelisted set of directories, /pub/foo
+ and /pub/bar, place an entry like the following into
+ /etc/inetd all on one line:
++
+------------------------------------------------
+ git stream tcp nowait nobody /usr/bin/git-daemon
+ git-daemon --inetd --verbose --export-all
+ /pub/foo /pub/bar
+------------------------------------------------
+
+
+git-daemon as inetd server for virtual hosts::
+ To set up `git-daemon` as an inetd service that handles
+ repositories for different virtual hosts, `www.example.com`
+ and `www.example.org`, place an entry like the following into
+ `/etc/inetd` all on one line:
++
+------------------------------------------------
+ git stream tcp nowait nobody /usr/bin/git-daemon
+ git-daemon --inetd --verbose --export-all
+ --interpolated-path=/pub/%H%D
+ /pub/www.example.org/software
+ /pub/www.example.com/software
+ /software
+------------------------------------------------
++
+In this example, the root-level directory `/pub` will contain
+a subdirectory for each virtual host name supported.
+Further, both hosts advertise repositories simply as
+`git://www.example.com/software/repo.git`. For pre-1.4.0
+clients, a symlink from `/software` into the appropriate
+default repository could be made as well.
+
+
+git-daemon as regular daemon for virtual hosts::
+ To set up `git-daemon` as a regular, non-inetd service that
+ handles repositories for multiple virtual hosts based on
+ their IP addresses, start the daemon like this:
++
+------------------------------------------------
+ git-daemon --verbose --export-all
+ --interpolated-path=/pub/%IP/%D
+ /pub/192.168.1.200/software
+ /pub/10.10.220.23/software
+------------------------------------------------
++
+In this example, the root-level directory `/pub` will contain
+a subdirectory for each virtual host IP address supported.
+Repositories can still be accessed by hostname though, assuming
+they correspond to these IP addresses.
+
+
Author
------
Written by Linus Torvalds <torvalds@osdl.org>, YOSHIFUJI Hideaki
diff --git a/Documentation/git-diff-index.txt b/Documentation/git-diff-index.txt
index 9cd43f105b..2df581c2c9 100644
--- a/Documentation/git-diff-index.txt
+++ b/Documentation/git-diff-index.txt
@@ -54,7 +54,7 @@ If '--cached' is specified, it allows you to ask:
For example, let's say that you have worked on your working directory, updated
some files in the index and are ready to commit. You want to see exactly
-*what* you are going to commit is without having to write a new tree
+*what* you are going to commit, without having to write a new tree
object and compare it that way, and to do that, you just do
git-diff-index --cached HEAD
@@ -68,7 +68,7 @@ matches my working directory. But doing a "git-diff-index" does:
-100644 blob 4161aecc6700a2eb579e842af0b7f22b98443f74 commit.c
+100644 blob 4161aecc6700a2eb579e842af0b7f22b98443f74 git-commit.c
-You can trivially see that the above is a rename.
+You can see easily that the above is a rename.
In fact, "git-diff-index --cached" *should* always be entirely equivalent to
actually doing a "git-write-tree" and comparing that. Except this one is much
diff --git a/Documentation/git-diff-tree.txt b/Documentation/git-diff-tree.txt
index f7e8ff2968..5d6e9dc751 100644
--- a/Documentation/git-diff-tree.txt
+++ b/Documentation/git-diff-tree.txt
@@ -73,10 +73,7 @@ separated with a single space are given.
This flag causes "git-diff-tree --stdin" to also show
the commit message before the differences.
---pretty[=(raw|medium|short)]::
- This is used to control "pretty printing" format of the
- commit message. Without "=<style>", it defaults to
- medium.
+include::pretty-formats.txt[]
--no-commit-id::
git-diff-tree outputs a line with the commit ID when
diff --git a/Documentation/git-fetch-pack.txt b/Documentation/git-fetch-pack.txt
index bff9aa6939..3e6cd880b0 100644
--- a/Documentation/git-fetch-pack.txt
+++ b/Documentation/git-fetch-pack.txt
@@ -32,7 +32,8 @@ OPTIONS
-k::
Do not invoke 'git-unpack-objects' on received data, but
create a single packfile out of it instead, and store it
- in the object database.
+ in the object database. If provided twice then the pack is
+ locked against repacking.
--exec=<git-upload-pack>::
Use this to specify the path to 'git-upload-pack' on the
diff --git a/Documentation/git-for-each-ref.txt b/Documentation/git-for-each-ref.txt
new file mode 100644
index 0000000000..2bf6aef735
--- /dev/null
+++ b/Documentation/git-for-each-ref.txt
@@ -0,0 +1,185 @@
+git-for-each-ref(1)
+===================
+
+NAME
+----
+git-for-each-ref - Output information on each ref
+
+SYNOPSIS
+--------
+'git-for-each-ref' [--count=<count>]\* [--shell|--perl|--python] [--sort=<key>]\* [--format=<format>] [<pattern>]
+
+DESCRIPTION
+-----------
+
+Iterate over all refs that match `<pattern>` and show them
+according to the given `<format>`, after sorting them according
+to the given set of `<key>`. If `<max>` is given, stop after
+showing that many refs. The interporated values in `<format>`
+can optionally be quoted as string literals in the specified
+host language allowing their direct evaluation in that language.
+
+OPTIONS
+-------
+<count>::
+ By default the command shows all refs that match
+ `<pattern>`. This option makes it stop after showing
+ that many refs.
+
+<key>::
+ A field name to sort on. Prefix `-` to sort in
+ descending order of the value. When unspecified,
+ `refname` is used. More than one sort keys can be
+ given.
+
+<format>::
+ A string that interpolates `%(fieldname)` from the
+ object pointed at by a ref being shown. If `fieldname`
+ is prefixed with an asterisk (`*`) and the ref points
+ at a tag object, the value for the field in the object
+ tag refers is used. When unspecified, defaults to
+ `%(objectname) SPC %(objecttype) TAB %(refname)`.
+ It also interpolates `%%` to `%`, and `%xx` where `xx`
+ are hex digits interpolates to character with hex code
+ `xx`; for example `%00` interpolates to `\0` (NUL),
+ `%09` to `\t` (TAB) and `%0a` to `\n` (LF).
+
+<pattern>::
+ If given, the name of the ref is matched against this
+ using fnmatch(3). Refs that do not match the pattern
+ are not shown.
+
+--shell, --perl, --python::
+ If given, strings that substitute `%(fieldname)`
+ placeholders are quoted as string literals suitable for
+ the specified host language. This is meant to produce
+ a scriptlet that can directly be `eval`ed.
+
+
+FIELD NAMES
+-----------
+
+Various values from structured fields in referenced objects can
+be used to interpolate into the resulting output, or as sort
+keys.
+
+For all objects, the following names can be used:
+
+refname::
+ The name of the ref (the part after $GIT_DIR/refs/).
+
+objecttype::
+ The type of the object (`blob`, `tree`, `commit`, `tag`).
+
+objectsize::
+ The size of the object (the same as `git-cat-file -s` reports).
+
+objectname::
+ The object name (aka SHA-1).
+
+In addition to the above, for commit and tag objects, the header
+field names (`tree`, `parent`, `object`, `type`, and `tag`) can
+be used to specify the value in the header field.
+
+Fields that have name-email-date tuple as its value (`author`,
+`committer`, and `tagger`) can be suffixed with `name`, `email`,
+and `date` to extract the named component.
+
+The first line of the message in a commit and tag object is
+`subject`, the remaining lines are `body`. The whole message
+is `contents`.
+
+For sorting purposes, fields with numeric values sort in numeric
+order (`objectsize`, `authordate`, `committerdate`, `taggerdate`).
+All other fields are used to sort in their byte-value order.
+
+In any case, a field name that refers to a field inapplicable to
+the object referred by the ref does not cause an error. It
+returns an empty string instead.
+
+
+EXAMPLES
+--------
+
+An example directly producing formatted text. Show the most recent
+3 tagged commits::
+
+------------
+#!/bin/sh
+
+git-for-each-ref --count=3 --sort='-*authordate' \
+--format='From: %(*authorname) %(*authoremail)
+Subject: %(*subject)
+Date: %(*authordate)
+Ref: %(*refname)
+
+%(*body)
+' 'refs/tags'
+------------
+
+
+A simple example showing the use of shell eval on the output,
+demonstrating the use of --shell. List the prefixes of all heads::
+------------
+#!/bin/sh
+
+git-for-each-ref --shell --format="ref=%(refname)" refs/heads | \
+while read entry
+do
+ eval "$entry"
+ echo `dirname $ref`
+done
+------------
+
+
+A bit more elaborate report on tags, demonstrating that the format
+may be an entire script::
+------------
+#!/bin/sh
+
+fmt='
+ r=%(refname)
+ t=%(*objecttype)
+ T=${r#refs/tags/}
+
+ o=%(*objectname)
+ n=%(*authorname)
+ e=%(*authoremail)
+ s=%(*subject)
+ d=%(*authordate)
+ b=%(*body)
+
+ kind=Tag
+ if test "z$t" = z
+ then
+ # could be a lightweight tag
+ t=%(objecttype)
+ kind="Lightweight tag"
+ o=%(objectname)
+ n=%(authorname)
+ e=%(authoremail)
+ s=%(subject)
+ d=%(authordate)
+ b=%(body)
+ fi
+ echo "$kind $T points at a $t object $o"
+ if test "z$t" = zcommit
+ then
+ echo "The commit was authored by $n $e
+at $d, and titled
+
+ $s
+
+Its message reads as:
+"
+ echo "$b" | sed -e "s/^/ /"
+ echo
+ fi
+'
+
+eval=`git-for-each-ref --shell --format="$fmt" \
+ --sort='*objecttype' \
+ --sort=-taggerdate \
+ refs/tags`
+eval "$eval"
+------------
diff --git a/Documentation/git-grep.txt b/Documentation/git-grep.txt
index 7545dd9a3e..bfbece9864 100644
--- a/Documentation/git-grep.txt
+++ b/Documentation/git-grep.txt
@@ -11,10 +11,10 @@ SYNOPSIS
[verse]
'git-grep' [--cached]
[-a | --text] [-I] [-i | --ignore-case] [-w | --word-regexp]
- [-v | --invert-match] [--full-name]
+ [-v | --invert-match] [-h|-H] [--full-name]
[-E | --extended-regexp] [-G | --basic-regexp] [-F | --fixed-strings]
[-n] [-l | --files-with-matches] [-L | --files-without-match]
- [-c | --count]
+ [-c | --count] [--all-match]
[-A <post-context>] [-B <pre-context>] [-C <context>]
[-f <file>] [-e] <pattern> [--and|--or|--not|(|)|-e <pattern>...]
[<tree>...]
@@ -47,6 +47,13 @@ OPTIONS
-v | --invert-match::
Select non-matching lines.
+-h | -H::
+ By default, the command shows the filename for each
+ match. `-h` option is used to suppress this output.
+ `-H` is there for completeness and does not do anything
+ except it overrides `-h` given earlier on the command
+ line.
+
--full-name::
When run from a subdirectory, the command usually
outputs paths relative to the current directory. This
@@ -89,6 +96,11 @@ OPTIONS
higher precedence than `--or`. `-e` has to be used for all
patterns.
+--all-match::
+ When giving multiple pattern expressions combined with `--or`,
+ this flag is specified to limit the match to files that
+ have lines to match all of them.
+
`<tree>...`::
Search blobs in the trees for specified patterns.
@@ -104,6 +116,10 @@ git grep -e \'#define\' --and \( -e MAX_PATH -e PATH_MAX \)::
Looks for a line that has `#define` and either `MAX_PATH` or
`PATH_MAX`.
+git grep --all-match -e NODE -e Unexpected::
+ Looks for a line that has `NODE` or `Unexpected` in
+ files that have lines that match both.
+
Author
------
Originally written by Linus Torvalds <torvalds@osdl.org>, later
diff --git a/Documentation/git-http-push.txt b/Documentation/git-http-push.txt
index 7e1f894a92..c2485c6e9c 100644
--- a/Documentation/git-http-push.txt
+++ b/Documentation/git-http-push.txt
@@ -34,7 +34,7 @@ OPTIONS
Report the list of objects being walked locally and the
list of objects successfully sent to the remote repository.
-<ref>...:
+<ref>...::
The remote refs to update.
diff --git a/Documentation/git-index-pack.txt b/Documentation/git-index-pack.txt
index 71ce557276..2229ee86b7 100644
--- a/Documentation/git-index-pack.txt
+++ b/Documentation/git-index-pack.txt
@@ -8,7 +8,8 @@ git-index-pack - Build pack index file for an existing packed archive
SYNOPSIS
--------
-'git-index-pack' [-o <index-file>] <pack-file>
+'git-index-pack' [-v] [-o <index-file>] <pack-file>
+'git-index-pack' --stdin [--fix-thin] [--keep] [-v] [-o <index-file>] [<pack-file>]
DESCRIPTION
@@ -21,6 +22,9 @@ objects/pack/ directory of a git repository.
OPTIONS
-------
+-v::
+ Be verbose about what is going on, including progress status.
+
-o <index-file>::
Write the generated pack index into the specified
file. Without this option the name of pack index
@@ -29,6 +33,52 @@ OPTIONS
fails if the name of packed archive does not end
with .pack).
+--stdin::
+ When this flag is provided, the pack is read from stdin
+ instead and a copy is then written to <pack-file>. If
+ <pack-file> is not specified, the pack is written to
+ objects/pack/ directory of the current git repository with
+ a default name determined from the pack content. If
+ <pack-file> is not specified consider using --keep to
+ prevent a race condition between this process and
+ gitlink::git-repack[1] .
+
+--fix-thin::
+ It is possible for gitlink:git-pack-objects[1] to build
+ "thin" pack, which records objects in deltified form based on
+ objects not included in the pack to reduce network traffic.
+ Those objects are expected to be present on the receiving end
+ and they must be included in the pack for that pack to be self
+ contained and indexable. Without this option any attempt to
+ index a thin pack will fail. This option only makes sense in
+ conjunction with --stdin.
+
+--keep::
+ Before moving the index into its final destination
+ create an empty .keep file for the associated pack file.
+ This option is usually necessary with --stdin to prevent a
+ simultaneous gitlink:git-repack[1] process from deleting
+ the newly constructed pack and index before refs can be
+ updated to use objects contained in the pack.
+
+--keep='why'::
+ Like --keep create a .keep file before moving the index into
+ its final destination, but rather than creating an empty file
+ place 'why' followed by an LF into the .keep file. The 'why'
+ message can later be searched for within all .keep files to
+ locate any which have outlived their usefulness.
+
+
+Note
+----
+
+Once the index has been created, the list of object names is sorted
+and the SHA1 hash of that list is printed to stdout. If --stdin was
+also used then this is prefixed by either "pack\t", or "keep\t" if a
+new .keep file was successfully created. This is useful to remove a
+.keep file used as a lock to prevent the race with gitlink:git-repack[1]
+mentioned above.
+
Author
------
diff --git a/Documentation/git-init-db.txt b/Documentation/git-init-db.txt
index 63cd5dab3f..ca7d09dc0a 100644
--- a/Documentation/git-init-db.txt
+++ b/Documentation/git-init-db.txt
@@ -48,6 +48,10 @@ is given:
- 'all' (or 'world' or 'everybody'): Same as 'group', but make the repository
readable by all users.
+By default, the configuration flag receive.denyNonFastforward is enabled
+in shared repositories, so that you cannot force a non fast-forwarding push
+into it.
+
--
diff --git a/Documentation/git-log.txt b/Documentation/git-log.txt
index c9ffff734c..79643ac928 100644
--- a/Documentation/git-log.txt
+++ b/Documentation/git-log.txt
@@ -24,8 +24,8 @@ This manual page describes only the most frequently used options.
OPTIONS
-------
---pretty=<format>::
- Controls the way the commit log is formatted.
+
+include::pretty-formats.txt[]
--max-count=<n>::
Limits the number of commits to show.
diff --git a/Documentation/git-ls-remote.txt b/Documentation/git-ls-remote.txt
index ae4c1a250e..c8a4c5a4d9 100644
--- a/Documentation/git-ls-remote.txt
+++ b/Documentation/git-ls-remote.txt
@@ -3,16 +3,19 @@ git-ls-remote(1)
NAME
----
-git-ls-remote - Look at references other repository has
+git-ls-remote - List references in a remote repository
SYNOPSIS
--------
-'git-ls-remote' [--heads] [--tags] <repository> <refs>...
+[verse]
+'git-ls-remote' [--heads] [--tags] [-u <exec> | --upload-pack <exec>]
+ <repository> <refs>...
DESCRIPTION
-----------
-Displays the references other repository has.
+Displays references available in a remote repository along with the associated
+commit IDs.
OPTIONS
@@ -23,9 +26,16 @@ OPTIONS
both, references stored in refs/heads and refs/tags are
displayed.
+-u <exec>, --upload-pack=<exec>::
+ Specify the full path of gitlink:git-upload-pack[1] on the remote
+ host. This allows listing references from repositories accessed via
+ SSH and where the SSH deamon does not use the PATH configured by the
+ user. Also see the '--exec' option for gitlink:git-peek-remote[1].
+
<repository>::
Location of the repository. The shorthand defined in
- $GIT_DIR/branches/ can be used.
+ $GIT_DIR/branches/ can be used. Use "." (dot) to list references in
+ the local repository.
<refs>...::
When unspecified, all references, after filtering done
diff --git a/Documentation/git-pack-objects.txt b/Documentation/git-pack-objects.txt
index 4991f88c92..fdc6f97289 100644
--- a/Documentation/git-pack-objects.txt
+++ b/Documentation/git-pack-objects.txt
@@ -9,9 +9,9 @@ git-pack-objects - Create a packed archive of objects
SYNOPSIS
--------
[verse]
-'git-pack-objects' [-q] [--no-reuse-delta] [--non-empty]
- [--local] [--incremental] [--window=N] [--depth=N]
- {--stdout | base-name} < object-list
+'git-pack-objects' [-q] [--no-reuse-delta] [--delta-base-offset] [--non-empty]
+ [--local] [--incremental] [--window=N] [--depth=N] [--all-progress]
+ [--revs [--unpacked | --all]*] [--stdout | base-name] < object-list
DESCRIPTION
@@ -47,17 +47,34 @@ base-name::
<base-name> to determine the name of the created file.
When this option is used, the two files are written in
<base-name>-<SHA1>.{pack,idx} files. <SHA1> is a hash
- of object names (currently in random order so it does
- not have any useful meaning) to make the resulting
- filename reasonably unique, and written to the standard
+ of the sorted object names to make the resulting filename
+ based on the pack content, and written to the standard
output of the command.
--stdout::
Write the pack contents (what would have been written to
.pack file) out to the standard output.
---window and --depth::
- These two options affects how the objects contained in
+--revs::
+ Read the revision arguments from the standard input, instead of
+ individual object names. The revision arguments are processed
+ the same way as gitlink:git-rev-list[1] with `--objects` flag
+ uses its `commit` arguments to build the list of objects it
+ outputs. The objects on the resulting list are packed.
+
+--unpacked::
+ This implies `--revs`. When processing the list of
+ revision arguments read from the standard input, limit
+ the objects packed to those that are not already packed.
+
+--all::
+ This implies `--revs`. In addition to the list of
+ revision arguments read from the standard input, pretend
+ as if all refs under `$GIT_DIR/refs` are specified to be
+ included.
+
+--window=[N], --depth=[N]::
+ These two options affect how the objects contained in
the pack are stored using delta compression. The
objects are first internally sorted by type, size and
optionally names and compared against the other objects
@@ -66,6 +83,7 @@ base-name::
it too deep affects the performance on the unpacker
side, because delta data needs to be applied that many
times to get to the necessary object.
+ The default value for both --window and --depth is 10.
--incremental::
This flag causes an object already in a pack ignored
@@ -81,6 +99,23 @@ base-name::
Only create a packed archive if it would contain at
least one object.
+--progress::
+ Progress status is reported on the standard error stream
+ by default when it is attached to a terminal, unless -q
+ is specified. This flag forces progress status even if
+ the standard error stream is not directed to a terminal.
+
+--all-progress::
+ When --stdout is specified then progress report is
+ displayed during the object count and deltification phases
+ but inhibited during the write-out phase. The reason is
+ that in some cases the output stream is directly linked
+ to another command which may wish to display progress
+ status of its own as it processes incoming pack data.
+ This flag is like --progress except that it forces progress
+ report for the write-out phase as well even if --stdout is
+ used.
+
-q::
This flag makes the command not to report its progress
on the standard error stream.
@@ -92,6 +127,17 @@ base-name::
This flag tells the command not to reuse existing deltas
but compute them from scratch.
+--delta-base-offset::
+ A packed archive can express base object of a delta as
+ either 20-byte object name or as an offset in the
+ stream, but older version of git does not understand the
+ latter. By default, git-pack-objects only uses the
+ former format for better compatibility. This option
+ allows the command to use the latter format for
+ compactness. Depending on the average delta chain
+ length, this option typically shrinks the resulting
+ packfile by 3-5 per-cent.
+
Author
------
@@ -103,6 +149,7 @@ Documentation by Junio C Hamano
See Also
--------
+gitlink:git-rev-list[1]
gitlink:git-repack[1]
gitlink:git-prune-packed[1]
diff --git a/Documentation/git-pack-refs.txt b/Documentation/git-pack-refs.txt
new file mode 100644
index 0000000000..5da5105771
--- /dev/null
+++ b/Documentation/git-pack-refs.txt
@@ -0,0 +1,54 @@
+git-pack-refs(1)
+================
+
+NAME
+----
+git-pack-refs - Pack heads and tags for efficient repository access
+
+SYNOPSIS
+--------
+'git-pack-refs' [--all] [--prune]
+
+DESCRIPTION
+-----------
+
+Traditionally, tips of branches and tags (collectively known as
+'refs') were stored one file per ref under `$GIT_DIR/refs`
+directory. While many branch tips tend to be updated often,
+most tags and some branch tips are never updated. When a
+repository has hundreds or thousands of tags, this
+one-file-per-ref format both wastes storage and hurts
+performance.
+
+This command is used to solve the storage and performance
+problem by stashing the refs in a single file,
+`$GIT_DIR/packed-refs`. When a ref is missing from the
+traditional `$GIT_DIR/refs` hierarchy, it is looked up in this
+file and used if found.
+
+Subsequent updates to branches always creates new file under
+`$GIT_DIR/refs` hierarchy.
+
+OPTIONS
+-------
+
+\--all::
+
+The command by default packs all tags and leaves branch tips
+alone. This is because branches are expected to be actively
+developed and packing their tips does not help performance.
+This option causes branch tips to be packed as well. Useful for
+a repository with many branches of historical interests.
+
+\--prune::
+
+After packing the refs, remove loose refs under `$GIT_DIR/refs`
+hierarchy. This should probably become default.
+
+Author
+------
+Written by Linus Torvalds <torvalds@osdl.org>
+
+GIT
+---
+Part of the gitlink:git[7] suite
diff --git a/Documentation/git-pull.txt b/Documentation/git-pull.txt
index 51577fcbe6..2a5aea73ba 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 or a local branch
SYNOPSIS
diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt
index 9d7bcaa38c..03e867a403 100644
--- a/Documentation/git-rebase.txt
+++ b/Documentation/git-rebase.txt
@@ -7,7 +7,7 @@ git-rebase - Rebase local commits to a new head
SYNOPSIS
--------
-'git-rebase' [--merge] [--onto <newbase>] <upstream> [<branch>]
+'git-rebase' [-v] [--merge] [--onto <newbase>] <upstream> [<branch>]
'git-rebase' --continue | --skip | --abort
@@ -51,20 +51,69 @@ would be:
D---E---F---G master
------------
-While, starting from the same point, the result of either of the following
-commands:
+The latter form is just a short-hand of `git checkout topic`
+followed by `git rebase master`.
- git-rebase --onto master~1 master
- git-rebase --onto master~1 master topic
+Here is how you would transplant a topic branch based on one
+branch to another, to pretend that you forked the topic branch
+from the latter branch, using `rebase --onto`.
-would be:
+First let's assume your 'topic' is based on branch 'next'.
+For example feature developed in 'topic' depends on some
+functionality which is found in 'next'.
------------
- A'--B'--C' topic
- /
- D---E---F---G master
+ o---o---o---o---o master
+ \
+ o---o---o---o---o next
+ \
+ o---o---o topic
+------------
+
+We would want to make 'topic' forked from branch 'master',
+for example because the functionality 'topic' branch depend on
+got merged into more stable 'master' branch, like this:
+
+------------
+ o---o---o---o---o master
+ | \
+ | o'--o'--o' topic
+ \
+ o---o---o---o---o next
+------------
+
+We can get this using the following command:
+
+ git-rebase --onto master next topic
+
+
+Another example of --onto option is to rebase part of a
+branch. If we have the following situation:
+
+------------
+ H---I---J topicB
+ /
+ E---F---G topicA
+ /
+ A---B---C---D master
------------
+then the command
+
+ git-rebase --onto master topicA topicB
+
+would result in:
+
+------------
+ H'--I'--J' topicB
+ /
+ | E---F---G topicA
+ |/
+ A---B---C---D master
+------------
+
+This is useful when topicB does not depend on topicA.
+
In case of conflict, git-rebase will stop at the first problematic commit
and leave conflict markers in the tree. You can use git diff to locate
the markers (<<<<<<) and make edits to resolve the conflict. For each
@@ -121,6 +170,9 @@ OPTIONS
is used instead (`git-merge-recursive` when merging a single
head, `git-merge-octopus` otherwise). This implies --merge.
+-v, \--verbose::
+ Display a diffstat of what changed upstream since the last rebase.
+
include::merge-strategies.txt[]
NOTES
diff --git a/Documentation/git-receive-pack.txt b/Documentation/git-receive-pack.txt
index f9457d45ed..0dfadc2a32 100644
--- a/Documentation/git-receive-pack.txt
+++ b/Documentation/git-receive-pack.txt
@@ -73,6 +73,8 @@ packed and is served via a dumb transport.
There are other real-world examples of using update and
post-update hooks found in the Documentation/howto directory.
+git-receive-pack honours the receive.denyNonFastforwards flag, which
+tells it if updates to a ref should be denied if they are not fast-forwards.
OPTIONS
-------
diff --git a/Documentation/git-repack.txt b/Documentation/git-repack.txt
index 951622774a..0fa47e3b01 100644
--- a/Documentation/git-repack.txt
+++ b/Documentation/git-repack.txt
@@ -9,7 +9,7 @@ objects into pack files.
SYNOPSIS
--------
-'git-repack' [-a] [-d] [-f] [-l] [-n] [-q]
+'git-repack' [-a] [-d] [-f] [-l] [-n] [-q] [--window=N] [--depth=N]
DESCRIPTION
-----------
@@ -56,6 +56,31 @@ OPTIONS
Do not update the server information with
`git update-server-info`.
+--window=[N], --depth=[N]::
+ These two options affect how the objects contained in the pack are
+ stored using delta compression. The objects are first internally
+ sorted by type, size and optionally names and compared against the
+ other objects within `--window` to see if using delta compression saves
+ space. `--depth` limits the maximum delta depth; making it too deep
+ affects the performance on the unpacker side, because delta data needs
+ to be applied that many times to get to the necessary object.
+ The default value for both --window and --depth is 10.
+
+
+Configuration
+-------------
+
+When configuration variable `repack.UseDeltaBaseOffset` is set
+for the repository, the command passes `--delta-base-offset`
+option to `git-pack-objects`; this typically results in slightly
+smaller packs, but the generated packs are incompatible with
+versions of git older than (and including) v1.4.3; do not set
+the variable in a repository that older version of git needs to
+be able to read (this includes repositories from which packs can
+be copied out over http or rsync, and people who obtained packs
+that way can try to use older git with it).
+
+
Author
------
Written by Linus Torvalds <torvalds@osdl.org>
diff --git a/Documentation/git-repo-config.txt b/Documentation/git-repo-config.txt
index b03d66f61c..8199615dde 100644
--- a/Documentation/git-repo-config.txt
+++ b/Documentation/git-repo-config.txt
@@ -3,19 +3,19 @@ git-repo-config(1)
NAME
----
-git-repo-config - Get and set options in .git/config
+git-repo-config - Get and set repository or global options.
SYNOPSIS
--------
[verse]
-'git-repo-config' [type] name [value [value_regex]]
-'git-repo-config' [type] --replace-all name [value [value_regex]]
-'git-repo-config' [type] --get name [value_regex]
-'git-repo-config' [type] --get-all name [value_regex]
-'git-repo-config' [type] --unset name [value_regex]
-'git-repo-config' [type] --unset-all name [value_regex]
-'git-repo-config' -l | --list
+'git-repo-config' [--global] [type] name [value [value_regex]]
+'git-repo-config' [--global] [type] --replace-all name [value [value_regex]]
+'git-repo-config' [--global] [type] --get name [value_regex]
+'git-repo-config' [--global] [type] --get-all name [value_regex]
+'git-repo-config' [--global] [type] --unset name [value_regex]
+'git-repo-config' [--global] [type] --unset-all name [value_regex]
+'git-repo-config' [--global] -l | --list
DESCRIPTION
-----------
@@ -41,8 +41,9 @@ This command will fail if:
. Can not write to .git/config,
. no section was provided,
. the section or key is invalid,
-. you try to unset an option which does not exist, or
-. you try to unset/set an option for which multiple lines match.
+. you try to unset an option which does not exist,
+. you try to unset/set an option for which multiple lines match, or
+. you use --global option without $HOME being properly set.
OPTIONS
@@ -54,7 +55,8 @@ OPTIONS
--get::
Get the value for a given key (optionally filtered by a regex
- matching the value).
+ matching the value). Returns error code 1 if the key was not
+ found and error code 2 if multiple key values were found.
--get-all::
Like get, but does not fail if the number of values for the key
@@ -63,14 +65,17 @@ OPTIONS
--get-regexp::
Like --get-all, but interprets the name as a regular expression.
+--global::
+ Use global ~/.gitconfig file rather than the repository .git/config.
+
--unset::
- Remove the line matching the key from .git/config.
+ Remove the line matching the key from config file.
--unset-all::
- Remove all matching lines from .git/config.
+ Remove all matching lines from config file.
-l, --list::
- List all variables set in .git/config.
+ List all variables set in config file.
ENVIRONMENT
@@ -78,6 +83,7 @@ ENVIRONMENT
GIT_CONFIG::
Take the configuration from the given file instead of .git/config.
+ Using the "--global" option forces this to ~/.gitconfig.
GIT_CONFIG_LOCAL::
Currently the same as $GIT_CONFIG; when Git will support global
diff --git a/Documentation/git-rev-list.txt b/Documentation/git-rev-list.txt
index dd9fff16d3..ec43c0b3a8 100644
--- a/Documentation/git-rev-list.txt
+++ b/Documentation/git-rev-list.txt
@@ -17,8 +17,10 @@ SYNOPSIS
[ \--remove-empty ]
[ \--not ]
[ \--all ]
+ [ \--stdin ]
[ \--topo-order ]
[ \--parents ]
+ [ \--(author|committer|grep)=<pattern> ]
[ [\--objects | \--objects-edge] [ \--unpacked ] ]
[ \--pretty | \--header ]
[ \--bisect ]
@@ -27,116 +29,254 @@ SYNOPSIS
DESCRIPTION
-----------
+
Lists commit objects in reverse chronological order starting at the
given commit(s), taking ancestry relationship into account. This is
useful to produce human-readable log output.
-Commits which are stated with a preceding '{caret}' cause listing to stop at
-that point. Their parents are implied. "git-rev-list foo bar {caret}baz" thus
+Commits which are stated with a preceding '{caret}' cause listing to
+stop at that point. Their parents are implied. Thus the following
+command:
+
+-----------------------------------------------------------------------
+ $ git-rev-list foo bar ^baz
+-----------------------------------------------------------------------
+
means "list all the commits which are included in 'foo' and 'bar', but
not in 'baz'".
-A special notation <commit1>..<commit2> can be used as a
-short-hand for {caret}<commit1> <commit2>.
+A special notation "'<commit1>'..'<commit2>'" can be used as a
+short-hand for "{caret}'<commit1>' '<commit2>'". For example, either of
+the following may be used interchangeably:
-Another special notation is <commit1>...<commit2> which is useful for
-merges. The resulting set of commits is the symmetric difference
+-----------------------------------------------------------------------
+ $ git-rev-list origin..HEAD
+ $ git-rev-list HEAD ^origin
+-----------------------------------------------------------------------
+
+Another special notation is "'<commit1>'...'<commit2>'" which is useful
+for merges. The resulting set of commits is the symmetric difference
between the two operands. The following two commands are equivalent:
-------------
-$ git-rev-list A B --not $(git-merge-base --all A B)
-$ git-rev-list A...B
-------------
+-----------------------------------------------------------------------
+ $ git-rev-list A B --not $(git-merge-base --all A B)
+ $ git-rev-list A...B
+-----------------------------------------------------------------------
+
+gitlink:git-rev-list[1] is a very essential git program, since it
+provides the ability to build and traverse commit ancestry graphs. For
+this reason, it has a lot of different options that enables it to be
+used by commands as different as gitlink:git-bisect[1] and
+gitlink:git-repack[1].
OPTIONS
-------
---pretty::
- Print the contents of the commit changesets in human-readable form.
+
+Commit Formatting
+~~~~~~~~~~~~~~~~~
+
+Using these options, gitlink:git-rev-list[1] will act similar to the
+more specialized family of commit log tools: gitlink:git-log[1],
+gitlink:git-show[1], and gitlink:git-whatchanged[1]
+
+include::pretty-formats.txt[]
+
+--relative-date::
+
+ Show dates relative to the current time, e.g. "2 hours ago".
+ Only takes effect for dates shown in human-readable format, such
+ as when using "--pretty".
--header::
- Print the contents of the commit in raw-format; each
- record is separated with a NUL character.
+
+ Print the contents of the commit in raw-format; each record is
+ separated with a NUL character.
--parents::
+
Print the parents of the commit.
---objects::
- Print the object IDs of any object referenced by the listed commits.
- 'git-rev-list --objects foo ^bar' thus means "send me all object IDs
- which I need to download if I have the commit object 'bar', but
- not 'foo'".
+Diff Formatting
+~~~~~~~~~~~~~~~
---objects-edge::
- Similar to `--objects`, but also print the IDs of
- excluded commits prefixed with a `-` character. This is
- used by `git-pack-objects` to build 'thin' pack, which
- records objects in deltified form based on objects
- contained in these excluded commits to reduce network
- traffic.
+Below are listed options that control the formatting of diff output.
+Some of them are specific to gitlink:git-rev-list[1], however other diff
+options may be given. See gitlink:git-diff-files[1] for more options.
---unpacked::
- Only useful with `--objects`; print the object IDs that
- are not in packs.
+-c::
+
+ This flag changes the way a merge commit is displayed. It shows
+ the differences from each of the parents to the merge result
+ simultaneously instead of showing pairwise diff between a parent
+ and the result one at a time. Furthermore, it lists only files
+ which were modified from all parents.
+
+--cc::
+
+ This flag implies the '-c' options and further compresses the
+ patch output by omitting hunks that show differences from only
+ one parent, or show the same change from all but one parent for
+ an Octopus merge.
+
+-r::
+
+ Show recursive diffs.
+
+-t::
+
+ Show the tree objects in the diff output. This implies '-r'.
+
+Commit Limiting
+~~~~~~~~~~~~~~~
+
+Besides specifying a range of commits that should be listed using the
+special notations explained in the description, additional commit
+limiting may be applied.
+
+--
+
+-n 'number', --max-count='number'::
---bisect::
- Limit output to the one commit object which is roughly halfway
- between the included and excluded commits. Thus, if 'git-rev-list
- --bisect foo {caret}bar {caret}baz' outputs 'midpoint', the output
- of 'git-rev-list foo {caret}midpoint' and 'git-rev-list midpoint
- {caret}bar {caret}baz' would be of roughly the same length.
- Finding the change
- which introduces a regression is thus reduced to a binary search:
- repeatedly generate and test new 'midpoint's until the commit chain
- is of length one.
-
---max-count::
Limit the number of commits output.
---max-age=timestamp, --min-age=timestamp::
+--since='date', --after='date'::
+
+ Show commits more recent than a specific date.
+
+--until='date', --before='date'::
+
+ Show commits older than a specific date.
+
+--max-age='timestamp', --min-age='timestamp'::
+
Limit the commits output to specified time range.
---sparse::
- When optional paths are given, the command outputs only
- the commits that changes at least one of them, and also
- ignores merges that do not touch the given paths. This
- flag makes the command output all eligible commits
- (still subject to count and age limitation), but apply
- merge simplification nevertheless.
+--author='pattern', --committer='pattern'::
+
+ Limit the commits output to ones with author/committer
+ header lines that match the specified pattern.
+
+--grep='pattern'::
+
+ Limit the commits output to ones with log message that
+ matches the specified pattern.
--remove-empty::
+
Stop when a given path disappears from the tree.
--no-merges::
+
Do not print commits with more than one parent.
--not::
- Reverses the meaning of the '{caret}' prefix (or lack
- thereof) for all following revision specifiers, up to
- the next `--not`.
+
+ Reverses the meaning of the '{caret}' prefix (or lack thereof)
+ for all following revision specifiers, up to the next '--not'.
--all::
- Pretend as if all the refs in `$GIT_DIR/refs/` are
- listed on the command line as <commit>.
---topo-order::
- By default, the commits are shown in reverse
- chronological order. This option makes them appear in
- topological order (i.e. descendant commits are shown
- before their parents).
+ Pretend as if all the refs in `$GIT_DIR/refs/` are listed on the
+ command line as '<commit>'.
+
+--stdin::
+
+ In addition to the '<commit>' listed on the command
+ line, read them from the standard input.
--merge::
+
After a failed merge, show refs that touch files having a
conflict and don't exist on all heads to merge.
+--boundary::
+
+ Output uninteresting commits at the boundary, which are usually
+ not shown.
+
+--dense, --sparse::
+
+When optional paths are given, the default behaviour ('--dense') is to
+only output commits that changes at least one of them, and also ignore
+merges that do not touch the given paths.
+
+Use the '--sparse' flag to makes the command output all eligible commits
+(still subject to count and age limitation), but apply merge
+simplification nevertheless.
+
+--bisect::
+
+Limit output to the one commit object which is roughly halfway between
+the included and excluded commits. Thus, if
+
+-----------------------------------------------------------------------
+ $ git-rev-list --bisect foo ^bar ^baz
+-----------------------------------------------------------------------
+
+outputs 'midpoint', the output of the two commands
+
+-----------------------------------------------------------------------
+ $ git-rev-list foo ^midpoint
+ $ git-rev-list midpoint ^bar ^baz
+-----------------------------------------------------------------------
+
+would be of roughly the same length. Finding the change which
+introduces a regression is thus reduced to a binary search: repeatedly
+generate and test new 'midpoint's until the commit chain is of length
+one.
+
+--
+
+Commit Ordering
+~~~~~~~~~~~~~~~
+
+By default, the commits are shown in reverse chronological order.
+
+--topo-order::
+
+ This option makes them appear in topological order (i.e.
+ descendant commits are shown before their parents).
+
+--date-order::
+
+ This option is similar to '--topo-order' in the sense that no
+ parent comes before all of its children, but otherwise things
+ are still ordered in the commit timestamp order.
+
+Object Traversal
+~~~~~~~~~~~~~~~~
+
+These options are mostly targeted for packing of git repositories.
+
+--objects::
+
+ Print the object IDs of any object referenced by the listed
+ commits. 'git-rev-list --objects foo ^bar' thus means "send me
+ all object IDs which I need to download if I have the commit
+ object 'bar', but not 'foo'".
+
+--objects-edge::
+
+ Similar to '--objects', but also print the IDs of excluded
+ commits prefixed with a "-" character. This is used by
+ gitlink:git-pack-objects[1] to build "thin" pack, which records
+ objects in deltified form based on objects contained in these
+ excluded commits to reduce network traffic.
+
+--unpacked::
+
+ Only useful with '--objects'; print the object IDs that are not
+ in packs.
+
Author
------
Written by Linus Torvalds <torvalds@osdl.org>
Documentation
--------------
-Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>.
+Documentation by David Greaves, Junio C Hamano, Jonas Fonseca
+and the git-list <git@vger.kernel.org>.
GIT
---
Part of the gitlink:git[7] suite
-
diff --git a/Documentation/git-rev-parse.txt b/Documentation/git-rev-parse.txt
index b761b4b965..4eaf5a0d1e 100644
--- a/Documentation/git-rev-parse.txt
+++ b/Documentation/git-rev-parse.txt
@@ -111,7 +111,9 @@ SPECIFYING REVISIONS
A revision parameter typically, but not necessarily, names a
commit object. They use what is called an 'extended SHA1'
-syntax.
+syntax. Here are various ways to spell object names. The
+ones listed near the end of this list are to name trees and
+blobs contained in a commit.
* The full SHA1 object name (40-byte hexadecimal string), or
a substring of such that is unique within the repository.
@@ -119,12 +121,31 @@ syntax.
name the same commit object if there are no other object in
your repository whose object name starts with dae86e.
+* An output from `git-describe`; i.e. a closest tag, followed by a
+ dash, a `g`, and an abbreviated object name.
+
* A symbolic ref name. E.g. 'master' typically means the commit
object referenced by $GIT_DIR/refs/heads/master. If you
happen to have both heads/master and tags/master, you can
explicitly say 'heads/master' to tell git which one you mean.
+ When ambiguous, a `<name>` is disambiguated by taking the
+ first match in the following rules:
+
+ . if `$GIT_DIR/<name>` exists, that is what you mean (this is usually
+ useful only for `HEAD`, `FETCH_HEAD` and `MERGE_HEAD`);
+
+ . otherwise, `$GIT_DIR/refs/<name>` if exists;
+
+ . otherwise, `$GIT_DIR/refs/tags/<name>` if exists;
+
+ . otherwise, `$GIT_DIR/refs/heads/<name>` if exists;
-* A suffix '@' followed by a date specification enclosed in a brace
+ . otherwise, `$GIT_DIR/refs/remotes/<name>` if exists;
+
+ . otherwise, `$GIT_DIR/refs/remotes/<name>/HEAD` if exists.
+
+* A ref followed by the suffix '@' with a date specification
+ enclosed in a brace
pair (e.g. '\{yesterday\}', '\{1 month 2 weeks 3 days 1 hour 1
second ago\}' or '\{1979-02-26 18:30:00\}') to specify the value
of the ref at a prior point in time. This suffix may only be
@@ -138,11 +159,12 @@ syntax.
'rev{caret}0' means the commit itself and is used when 'rev' is the
object name of a tag object that refers to a commit object.
-* A suffix '~<n>' to a revision parameter means the commit
+* A suffix '{tilde}<n>' to a revision parameter means the commit
object that is the <n>th generation grand-parent of the named
commit object, following only the first parent. I.e. rev~3 is
- equivalent to rev{caret}{caret}{caret} which is equivalent to\
- rev{caret}1{caret}1{caret}1.
+ equivalent to rev{caret}{caret}{caret} which is equivalent to
+ rev{caret}1{caret}1{caret}1. See below for a illustration of
+ the usage of this form.
* A suffix '{caret}' followed by an object type name enclosed in
brace pair (e.g. `v0.99.8{caret}\{commit\}`) means the object
@@ -156,6 +178,15 @@ syntax.
and dereference the tag recursively until a non-tag object is
found.
+* A suffix ':' followed by a path; this names the blob or tree
+ at the given path in the tree-ish object named by the part
+ before the colon.
+
+* A colon, optionally followed by a stage number (0 to 3) and a
+ colon, followed by a path; this names a blob object in the
+ index at the given path. Missing stage number (and the colon
+ that follows it) names an stage 0 entry.
+
Here is an illustration, by Jon Loeliger. Both node B and C are
a commit parents of commit node A. Parent commits are ordered
left-to-right.
@@ -208,14 +239,21 @@ of `r1` and `r2` and is defined as
It it the set of commits that are reachable from either one of
`r1` or `r2` but not from both.
-Here are a few examples:
+Two other shorthands for naming a set that is formed by a commit
+and its parent commits exists. `r1{caret}@` notation means all
+parents of `r1`. `r1{caret}!` includes commit `r1` but excludes
+its all parents.
+
+Here are a handful examples:
D A B D
D F A B C D F
- ^A G B D
+ ^A G B D
^A F B C F
G...I C D F G I
- ^B G I C D F G I
+ ^B G I C D F G I
+ F^@ A B C
+ F^! H D F H
Author
------
diff --git a/Documentation/git-runstatus.txt b/Documentation/git-runstatus.txt
new file mode 100644
index 0000000000..89d7b92731
--- /dev/null
+++ b/Documentation/git-runstatus.txt
@@ -0,0 +1,69 @@
+git-runstatus(1)
+================
+
+NAME
+----
+git-runstatus - A helper for git-status and git-commit
+
+
+SYNOPSIS
+--------
+'git-runstatus' [--color|--nocolor] [--amend] [--verbose] [--untracked]
+
+
+DESCRIPTION
+-----------
+Examines paths in the working tree that has changes unrecorded
+to the index file, and changes between the index file and the
+current HEAD commit. The former paths are what you _could_
+commit by running 'git-update-index' before running 'git
+commit', and the latter paths are what you _would_ commit by
+running 'git commit'.
+
+If there is no path that is different between the index file and
+the current HEAD commit, the command exits with non-zero status.
+
+Note that this is _not_ the user level command you would want to
+run from the command line. Use 'git-status' instead.
+
+
+OPTIONS
+-------
+--color::
+ Show colored status, highlighting modified file names.
+
+--nocolor::
+ Turn off coloring.
+
+--amend::
+ Show status based on HEAD^1, not HEAD, i.e. show what
+ 'git-commit --amend' would do.
+
+--verbose::
+ Show unified diff of all file changes.
+
+--untracked::
+ Show files in untracked directories, too. Without this
+ option only its name and a trailing slash are displayed
+ for each untracked directory.
+
+
+OUTPUT
+------
+The output from this command is designed to be used as a commit
+template comments, and all the output lines are prefixed with '#'.
+
+
+Author
+------
+Originally written by Linus Torvalds <torvalds@osdl.org> as part
+of git-commit, and later rewritten in C by Jeff King.
+
+Documentation
+--------------
+Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>.
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
diff --git a/Documentation/git-send-email.txt b/Documentation/git-send-email.txt
index 481b3f50e3..4c8d907bd5 100644
--- a/Documentation/git-send-email.txt
+++ b/Documentation/git-send-email.txt
@@ -66,8 +66,13 @@ The options available are:
all that is output.
--smtp-server::
- If set, specifies the outgoing SMTP server to use. Defaults to
- localhost.
+ If set, specifies the outgoing SMTP server to use. A full
+ pathname of a sendmail-like program can be specified instead;
+ the program must support the `-i` option. Default value can
+ be specified by the 'sendemail.smtpserver' configuration
+ option; the built-in default is `/usr/sbin/sendmail` or
+ `/usr/lib/sendmail` if such program is available, or
+ `localhost` otherwise.
--subject::
Specify the initial subject of the email thread.
diff --git a/Documentation/git-send-pack.txt b/Documentation/git-send-pack.txt
index 9e67f17302..5376f68548 100644
--- a/Documentation/git-send-pack.txt
+++ b/Documentation/git-send-pack.txt
@@ -43,7 +43,7 @@ OPTIONS
<directory>::
The repository to update.
-<ref>...:
+<ref>...::
The remote refs to update.
diff --git a/Documentation/git-shortlog.txt b/Documentation/git-shortlog.txt
index 7486ebe785..d54fc3e5c6 100644
--- a/Documentation/git-shortlog.txt
+++ b/Documentation/git-shortlog.txt
@@ -7,16 +7,29 @@ git-shortlog - Summarize 'git log' output
SYNOPSIS
--------
-git-log --pretty=short | 'git-shortlog'
+git-log --pretty=short | 'git-shortlog' [-h] [-n] [-s]
DESCRIPTION
-----------
Summarizes 'git log' output in a format suitable for inclusion
-in release announcements. Each commit will be grouped by author
+in release announcements. Each commit will be grouped by author and
the first line of the commit message will be shown.
Additionally, "[PATCH]" will be stripped from the commit description.
+OPTIONS
+-------
+
+-h::
+ Print a short usage message and exit.
+
+-n::
+ Sort output according to the number of commits per author instead
+ of author alphabetic order.
+
+-s::
+ Supress commit description and provide a commit count summary only.
+
FILES
-----
'.mailmap'::
diff --git a/Documentation/git-show-ref.txt b/Documentation/git-show-ref.txt
new file mode 100644
index 0000000000..5973a82517
--- /dev/null
+++ b/Documentation/git-show-ref.txt
@@ -0,0 +1,156 @@
+git-show-ref(1)
+===============
+
+NAME
+----
+git-show-ref - List references in a local repository
+
+SYNOPSIS
+--------
+[verse]
+'git-show-ref' [-q|--quiet] [--verify] [-h|--head] [-d|--dereference]
+ [-s|--hash] [--abbrev] [--tags] [--heads] [--] <pattern>...
+
+DESCRIPTION
+-----------
+
+Displays references available in a local repository along with the associated
+commit IDs. Results can be filtered using a pattern and tags can be
+dereferenced into object IDs. Additionally, it can be used to test whether a
+particular ref exists.
+
+Use of this utility is encouraged in favor of directly accessing files under
+in the `.git` directory.
+
+OPTIONS
+-------
+
+-h, --head::
+
+ Show the HEAD reference.
+
+--tags, --heads::
+
+ Limit to only "refs/heads" and "refs/tags", respectively. These
+ options are not mutually exclusive; when given both, references stored
+ in "refs/heads" and "refs/tags" are displayed.
+
+-d, --dereference::
+
+ Dereference tags into object IDs as well. They will be shown with "^{}"
+ appended.
+
+-s, --hash::
+
+ Only show the SHA1 hash, not the reference name. When also using
+ --dereference the dereferenced tag will still be shown after the SHA1.
+
+--verify::
+
+ Enable stricter reference checking by requiring an exact ref path.
+ Aside from returning an error code of 1, it will also print an error
+ message if '--quiet' was not specified.
+
+--abbrev, --abbrev=len::
+
+ Abbreviate the object name. When using `--hash`, you do
+ not have to say `--hash --abbrev`; `--hash=len` would do.
+
+-q, --quiet::
+
+ Do not print any results to stdout. When combined with '--verify' this
+ can be used to silently check if a reference exists.
+
+<pattern>::
+
+ Show references matching one or more patterns.
+
+OUTPUT
+------
+
+The output is in the format: '<SHA-1 ID>' '<space>' '<reference name>'.
+
+-----------------------------------------------------------------------------
+$ git show-ref --head --dereference
+832e76a9899f560a90ffd62ae2ce83bbeff58f54 HEAD
+832e76a9899f560a90ffd62ae2ce83bbeff58f54 refs/heads/master
+832e76a9899f560a90ffd62ae2ce83bbeff58f54 refs/heads/origin
+3521017556c5de4159da4615a39fa4d5d2c279b5 refs/tags/v0.99.9c
+6ddc0964034342519a87fe013781abf31c6db6ad refs/tags/v0.99.9c^{}
+055e4ae3ae6eb344cbabf2a5256a49ea66040131 refs/tags/v1.0rc4
+423325a2d24638ddcc82ce47be5e40be550f4507 refs/tags/v1.0rc4^{}
+...
+-----------------------------------------------------------------------------
+
+When using --hash (and not --dereference) the output format is: '<SHA-1 ID>'
+
+-----------------------------------------------------------------------------
+$ git show-ref --heads --hash
+2e3ba0114a1f52b47df29743d6915d056be13278
+185008ae97960c8d551adcd9e23565194651b5d1
+03adf42c988195b50e1a1935ba5fcbc39b2b029b
+...
+-----------------------------------------------------------------------------
+
+EXAMPLE
+-------
+
+To show all references called "master", whether tags or heads or anything
+else, and regardless of how deep in the reference naming hierarchy they are,
+use:
+
+-----------------------------------------------------------------------------
+ git show-ref master
+-----------------------------------------------------------------------------
+
+This will show "refs/heads/master" but also "refs/remote/other-repo/master",
+if such references exists.
+
+When using the '--verify' flag, the command requires an exact path:
+
+-----------------------------------------------------------------------------
+ git show-ref --verify refs/heads/master
+-----------------------------------------------------------------------------
+
+will only match the exact branch called "master".
+
+If nothing matches, gitlink:git-show-ref[1] will return an error code of 1,
+and in the case of verification, it will show an error message.
+
+For scripting, you can ask it to be quiet with the "--quiet" flag, which
+allows you to do things like
+
+-----------------------------------------------------------------------------
+ git-show-ref --quiet --verify -- "refs/heads/$headname" ||
+ echo "$headname is not a valid branch"
+-----------------------------------------------------------------------------
+
+to check whether a particular branch exists or not (notice how we don't
+actually want to show any results, and we want to use the full refname for it
+in order to not trigger the problem with ambiguous partial matches).
+
+To show only tags, or only proper branch heads, use "--tags" and/or "--heads"
+respectively (using both means that it shows tags and heads, but not other
+random references under the refs/ subdirectory).
+
+To do automatic tag object dereferencing, use the "-d" or "--dereference"
+flag, so you can do
+
+-----------------------------------------------------------------------------
+ git show-ref --tags --dereference
+-----------------------------------------------------------------------------
+
+to get a listing of all tags together with what they dereference.
+
+SEE ALSO
+--------
+gitlink:git-ls-remote[1], gitlink:git-peek-remote[1]
+
+AUTHORS
+-------
+Written by Linus Torvalds <torvalds@osdl.org>.
+Man page by Jonas Fonseca <fonseca@diku.dk>.
+
+GIT
+---
+Part of the gitlink:git[7] suite
diff --git a/Documentation/git-show.txt b/Documentation/git-show.txt
index 2b4df3f96f..4c880a8717 100644
--- a/Documentation/git-show.txt
+++ b/Documentation/git-show.txt
@@ -26,10 +26,7 @@ OPTIONS
<commitid>::
ID of the commit to show.
---pretty=<format>::
- Controls the output format for the commit logs.
- <format> can be one of 'raw', 'medium', 'short', 'full',
- and 'oneline'.
+include::pretty-formats.txt[]
Author
------
diff --git a/Documentation/git-svn.txt b/Documentation/git-svn.txt
index 7d86809844..a764d1f8ee 100644
--- a/Documentation/git-svn.txt
+++ b/Documentation/git-svn.txt
@@ -12,10 +12,8 @@ SYNOPSIS
DESCRIPTION
-----------
git-svn is a simple conduit for changesets between a single Subversion
-branch and git.
-
-git-svn is not to be confused with git-svnimport. The were designed
-with very different goals in mind.
+branch and git. It is not to be confused with gitlink:git-svnimport[1].
+They were designed with very different goals in mind.
git-svn is designed for an individual developer who wants a
bidirectional flow of changesets between a single branch in Subversion
@@ -34,26 +32,63 @@ git-svnimport is designed for.
COMMANDS
--------
-init::
+--
+
+'init'::
Creates an empty git repository with additional metadata
directories for git-svn. The Subversion URL must be specified
- as a command-line argument.
-
-fetch::
- Fetch unfetched revisions from the Subversion URL we are
- tracking. refs/remotes/git-svn will be updated to the
- latest revision.
-
- Note: You should never attempt to modify the remotes/git-svn
- branch outside of git-svn. Instead, create a branch from
- remotes/git-svn and work on that branch. Use the 'commit'
- command (see below) to write git commits back to
- remotes/git-svn.
-
- See 'Additional Fetch Arguments' if you are interested in
- manually joining branches on commit.
-
-commit::
+ as a command-line argument. Optionally, the target directory
+ to operate on can be specified as a second argument. Normally
+ this command initializes the current directory.
+
+'fetch'::
+
+Fetch unfetched revisions from the Subversion URL we are
+tracking. refs/remotes/git-svn will be updated to the
+latest revision.
+
+Note: You should never attempt to modify the remotes/git-svn
+branch outside of git-svn. Instead, create a branch from
+remotes/git-svn and work on that branch. Use the 'commit'
+command (see below) to write git commits back to
+remotes/git-svn.
+
+See '<<fetch-args,Additional Fetch Arguments>>' if you are interested in
+manually joining branches on commit.
+
+'dcommit'::
+ Commit all diffs from the current HEAD directly to the SVN
+ repository, and then rebase or reset (depending on whether or
+ not there is a diff between SVN and HEAD). It is recommended
+ that you run git-svn fetch and rebase (not pull) your commits
+ against the latest changes in the SVN repository.
+ This is advantageous over 'commit' (below) because it produces
+ cleaner, more linear history.
+
+'log'::
+ This should make it easy to look up svn log messages when svn
+ users refer to -r/--revision numbers.
+
+ The following features from `svn log' are supported:
+
+ --revision=<n>[:<n>] - is supported, non-numeric args are not:
+ HEAD, NEXT, BASE, PREV, etc ...
+ -v/--verbose - it's not completely compatible with
+ the --verbose output in svn log, but
+ reasonably close.
+ --limit=<n> - is NOT the same as --max-count,
+ doesn't count merged/excluded commits
+ --incremental - supported
+
+ New features:
+
+ --show-commit - shows the git commit sha1, as well
+ --oneline - our version of --pretty=oneline
+
+ Any other arguments are passed directly to `git log'
+
+'commit'::
+ You should consider using 'dcommit' instead of this command.
Commit specified commit or tree objects to SVN. This relies on
your imported fetch data being up-to-date. This makes
absolutely no attempts to do patching when committing to SVN, it
@@ -61,9 +96,9 @@ commit::
commit. All merging is assumed to have taken place
independently of git-svn functions.
-rebuild::
+'rebuild'::
Not a part of daily usage, but this is a useful command if
- you've just cloned a repository (using git-clone) that was
+ you've just cloned a repository (using gitlink:git-clone[1]) that was
tracked with git-svn. Unfortunately, git-clone does not clone
git-svn metadata and the svn working tree that git-svn uses for
its operations. This rebuilds the metadata so git-svn can
@@ -71,130 +106,264 @@ rebuild::
specified at the command-line if the directory/repository you're
tracking has moved or changed protocols.
-show-ignore::
+'show-ignore'::
Recursively finds and lists the svn:ignore property on
directories. The output is suitable for appending to
the $GIT_DIR/info/exclude file.
+'commit-diff'::
+ Commits the diff of two tree-ish arguments from the
+ command-line. This command is intended for interopability with
+ git-svnimport and does not rely on being inside an git-svn
+ init-ed repository. This command takes three arguments, (a) the
+ original tree to diff against, (b) the new tree result, (c) the
+ URL of the target Subversion repository. The final argument
+ (URL) may be omitted if you are working from a git-svn-aware
+ repository (that has been init-ed with git-svn).
+ The -r<revision> option is required for this.
+
+'graft-branches'::
+ This command attempts to detect merges/branches from already
+ imported history. Techniques used currently include regexes,
+ file copies, and tree-matches). This command generates (or
+ modifies) the $GIT_DIR/info/grafts file. This command is
+ considered experimental, and inherently flawed because
+ merge-tracking in SVN is inherently flawed and inconsistent
+ across different repositories.
+
+'multi-init'::
+ This command supports git-svnimport-like command-line syntax for
+ importing repositories that are layed out as recommended by the
+ SVN folks. This is a bit more tolerant than the git-svnimport
+ command-line syntax and doesn't require the user to figure out
+ where the repository URL ends and where the repository path
+ begins.
+
+'multi-fetch'::
+ This runs fetch on all known SVN branches we're tracking. This
+ will NOT discover new branches (unlike git-svnimport), so
+ multi-init will need to be re-run (it's idempotent).
+
+--
+
OPTIONS
-------
+--
+
+--shared::
+--template=<template_directory>::
+ Only used with the 'init' command.
+ These are passed directly to gitlink:git-init-db[1].
+
-r <ARG>::
--revision <ARG>::
- Only used with the 'fetch' command.
- Takes any valid -r<argument> svn would accept and passes it
- directly to svn. -r<ARG1>:<ARG2> ranges and "{" DATE "}" syntax
- is also supported. This is passed directly to svn, see svn
- documentation for more details.
+Only used with the 'fetch' command.
+
+Takes any valid -r<argument> svn would accept and passes it
+directly to svn. -r<ARG1>:<ARG2> ranges and "{" DATE "}" syntax
+is also supported. This is passed directly to svn, see svn
+documentation for more details.
- This can allow you to make partial mirrors when running fetch.
+This can allow you to make partial mirrors when running fetch.
-::
--stdin::
- Only used with the 'commit' command.
- Read a list of commits from stdin and commit them in reverse
- order. Only the leading sha1 is read from each line, so
- git-rev-list --pretty=oneline output can be used.
+Only used with the 'commit' command.
+
+Read a list of commits from stdin and commit them in reverse
+order. Only the leading sha1 is read from each line, so
+git-rev-list --pretty=oneline output can be used.
--rmdir::
- Only used with the 'commit' command.
- Remove directories from the SVN tree if there are no files left
- behind. SVN can version empty directories, and they are not
- removed by default if there are no files left in them. git
- cannot version empty directories. Enabling this flag will make
- the commit to SVN act like git.
+Only used with the 'dcommit', 'commit' and 'commit-diff' commands.
+
+Remove directories from the SVN tree if there are no files left
+behind. SVN can version empty directories, and they are not
+removed by default if there are no files left in them. git
+cannot version empty directories. Enabling this flag will make
+the commit to SVN act like git.
- repo-config key: svn.rmdir
+repo-config key: svn.rmdir
-e::
--edit::
- Only used with the 'commit' command.
- Edit the commit message before committing to SVN. This is off by
- default for objects that are commits, and forced on when committing
- tree objects.
+Only used with the 'dcommit', 'commit' and 'commit-diff' commands.
+
+Edit the commit message before committing to SVN. This is off by
+default for objects that are commits, and forced on when committing
+tree objects.
- repo-config key: svn.edit
+repo-config key: svn.edit
-l<num>::
--find-copies-harder::
- Both of these are only used with the 'commit' command.
- They are both passed directly to git-diff-tree see
- git-diff-tree(1) for more information.
+Only used with the 'dcommit', 'commit' and 'commit-diff' commands.
- repo-config key: svn.l
- repo-config key: svn.findcopiesharder
+They are both passed directly to git-diff-tree see
+gitlink:git-diff-tree[1] for more information.
+
+[verse]
+repo-config key: svn.l
+repo-config key: svn.findcopiesharder
-A<filename>::
--authors-file=<filename>::
- Syntax is compatible with the files used by git-svnimport and
- git-cvsimport:
+Syntax is compatible with the files used by git-svnimport and
+git-cvsimport:
------------------------------------------------------------------------
-loginname = Joe User <user@example.com>
+ loginname = Joe User <user@example.com>
------------------------------------------------------------------------
- If this option is specified and git-svn encounters an SVN
- committer name that does not exist in the authors-file, git-svn
- will abort operation. The user will then have to add the
- appropriate entry. Re-running the previous git-svn command
- after the authors-file is modified should continue operation.
+If this option is specified and git-svn encounters an SVN
+committer name that does not exist in the authors-file, git-svn
+will abort operation. The user will then have to add the
+appropriate entry. Re-running the previous git-svn command
+after the authors-file is modified should continue operation.
+
+repo-config key: svn.authorsfile
+
+-q::
+--quiet::
+ Make git-svn less verbose. This only affects git-svn if you
+ have the SVN::* libraries installed and are using them.
+
+--repack[=<n>]::
+--repack-flags=<flags>
+ These should help keep disk usage sane for large fetches
+ with many revisions.
+
+ --repack takes an optional argument for the number of revisions
+ to fetch before repacking. This defaults to repacking every
+ 1000 commits fetched if no argument is specified.
+
+ --repack-flags are passed directly to gitlink:git-repack[1].
+
+repo-config key: svn.repack
+repo-config key: svn.repackflags
+
+-m::
+--merge::
+-s<strategy>::
+--strategy=<strategy>::
+
+These are only used with the 'dcommit' command.
+
+Passed directly to git-rebase when using 'dcommit' if a
+'git-reset' cannot be used (see dcommit).
+
+-n::
+--dry-run::
+
+This is only used with the 'dcommit' command.
- repo-config key: svn.authors-file
+Print out the series of git arguments that would show
+which diffs would be committed to SVN.
+
+--
ADVANCED OPTIONS
----------------
+--
+
-b<refname>::
--branch <refname>::
- Used with 'fetch' or 'commit'.
+Used with 'fetch' or 'commit'.
- This can be used to join arbitrary git branches to remotes/git-svn
- on new commits where the tree object is equivalent.
+This can be used to join arbitrary git branches to remotes/git-svn
+on new commits where the tree object is equivalent.
- When used with different GIT_SVN_ID values, tags and branches in
- SVN can be tracked this way, as can some merges where the heads
- end up having completely equivalent content. This can even be
- used to track branches across multiple SVN _repositories_.
+When used with different GIT_SVN_ID values, tags and branches in
+SVN can be tracked this way, as can some merges where the heads
+end up having completely equivalent content. This can even be
+used to track branches across multiple SVN _repositories_.
- This option may be specified multiple times, once for each
- branch.
+This option may be specified multiple times, once for each
+branch.
- repo-config key: svn.branch
+repo-config key: svn.branch
-i<GIT_SVN_ID>::
--id <GIT_SVN_ID>::
- This sets GIT_SVN_ID (instead of using the environment). See
- the section on "Tracking Multiple Repositories or Branches" for
- more information on using GIT_SVN_ID.
+
+This sets GIT_SVN_ID (instead of using the environment). See the
+section on
+'<<tracking-multiple-repos,Tracking Multiple Repositories or Branches>>'
+for more information on using GIT_SVN_ID.
+
+--follow-parent::
+ This is especially helpful when we're tracking a directory
+ that has been moved around within the repository, or if we
+ started tracking a branch and never tracked the trunk it was
+ descended from.
+
+ This relies on the SVN::* libraries to work.
+
+repo-config key: svn.followparent
+
+--no-metadata::
+ This gets rid of the git-svn-id: lines at the end of every commit.
+
+ With this, you lose the ability to use the rebuild command. If
+ you ever lose your .git/svn/git-svn/.rev_db file, you won't be
+ able to fetch again, either. This is fine for one-shot imports.
+
+ The 'git-svn log' command will not work on repositories using this,
+ either.
+
+repo-config key: svn.nometadata
+
+--
COMPATIBILITY OPTIONS
---------------------
+--
+
--upgrade::
- Only used with the 'rebuild' command.
+Only used with the 'rebuild' command.
- Run this if you used an old version of git-svn that used
- "git-svn-HEAD" instead of "remotes/git-svn" as the branch
- for tracking the remote.
+Run this if you used an old version of git-svn that used
+"git-svn-HEAD" instead of "remotes/git-svn" as the branch
+for tracking the remote.
--no-ignore-externals::
- Only used with the 'fetch' and 'rebuild' command.
+Only used with the 'fetch' and 'rebuild' command.
+
+This command has no effect when you are using the SVN::*
+libraries with git, svn:externals are always avoided.
- By default, git-svn passes --ignore-externals to svn to avoid
- fetching svn:external trees into git. Pass this flag to enable
- externals tracking directly via git.
+By default, git-svn passes --ignore-externals to svn to avoid
+fetching svn:external trees into git. Pass this flag to enable
+externals tracking directly via git.
- Versions of svn that do not support --ignore-externals are
- automatically detected and this flag will be automatically
- enabled for them.
+Versions of svn that do not support --ignore-externals are
+automatically detected and this flag will be automatically
+enabled for them.
- Otherwise, do not enable this flag unless you know what you're
- doing.
+Otherwise, do not enable this flag unless you know what you're
+doing.
- repo-config key: svn.noignoreexternals
+repo-config key: svn.noignoreexternals
+
+--ignore-nodate::
+Only used with the 'fetch' command.
+
+By default git-svn will crash if it tries to import a revision
+from SVN which has '(no date)' listed as the date of the revision.
+This is repository corruption on SVN's part, plain and simple.
+But sometimes you really need those revisions anyway.
+
+If supplied git-svn will convert '(no date)' entries to the UNIX
+epoch (midnight on Jan. 1, 1970). Yes, that's probably very wrong.
+SVN was very wrong.
+
+--
Basic Examples
~~~~~~~~~~~~~~
@@ -202,7 +371,7 @@ Basic Examples
Tracking and contributing to an Subversion managed-project:
------------------------------------------------------------------------
-# Initialize a tree (like git init-db):
+# Initialize a repo (like git init-db):
git-svn init http://svn.foo.org/project/trunk
# Fetch remote revisions:
git-svn fetch
@@ -212,12 +381,26 @@ Tracking and contributing to an Subversion managed-project:
git-svn commit <tree-ish> [<tree-ish_2> ...]
# Commit all the git commits from my-branch that don't exist in SVN:
git-svn commit remotes/git-svn..my-branch
-# Something is committed to SVN, pull the latest into your branch:
- git-svn fetch && git pull . remotes/git-svn
+# Something is committed to SVN, rebase the latest into your branch:
+ git-svn fetch && git rebase remotes/git-svn
# Append svn:ignore settings to the default git exclude file:
git-svn show-ignore >> .git/info/exclude
------------------------------------------------------------------------
+REBASE VS. PULL
+---------------
+
+Originally, git-svn recommended that the remotes/git-svn branch be
+pulled from. This is because the author favored 'git-svn commit B'
+to commit a single head rather than the 'git-svn commit A..B' notation
+to commit multiple commits.
+
+If you use 'git-svn commit A..B' to commit several diffs and you do not
+have the latest remotes/git-svn merged into my-branch, you should use
+'git rebase' to update your work branch instead of 'git pull'. 'pull'
+can cause non-linear history to be flattened when committing into SVN,
+which can lead to merge commits reversing previous commits in SVN.
+
DESIGN PHILOSOPHY
-----------------
Merge tracking in Subversion is lacking and doing branched development
@@ -226,6 +409,7 @@ any automated merge/branch tracking on the Subversion side and leaves it
entirely up to the user on the git side. It's simply not worth it to do
a useful translation when the original signal is weak.
+[[tracking-multiple-repos]]
TRACKING MULTIPLE REPOSITORIES OR BRANCHES
------------------------------------------
This is for advanced users, most users should ignore this section.
@@ -235,12 +419,13 @@ branches or directories in a Subversion repository, git-svn has a simple
hack to allow it to track an arbitrary number of related _or_ unrelated
SVN repositories via one git repository. Simply set the GIT_SVN_ID
environment variable to a name other other than "git-svn" (the default)
-and git-svn will ignore the contents of the $GIT_DIR/git-svn directory
-and instead do all of its work in $GIT_DIR/$GIT_SVN_ID for that
+and git-svn will ignore the contents of the $GIT_DIR/svn/git-svn directory
+and instead do all of its work in $GIT_DIR/svn/$GIT_SVN_ID for that
invocation. The interface branch will be remotes/$GIT_SVN_ID, instead of
remotes/git-svn. Any remotes/$GIT_SVN_ID branch should never be modified
by the user outside of git-svn commands.
+[[fetch-args]]
ADDITIONAL FETCH ARGUMENTS
--------------------------
This is for advanced users, most users should ignore this section.
@@ -251,14 +436,21 @@ optionally be specified in the form of sha1 hex sums at the
command-line. Unfetched SVN revisions may also be tied to particular
git commits with the following syntax:
+------------------------------------------------
svn_revision_number=git_commit_sha1
+------------------------------------------------
-This allows you to tie unfetched SVN revision 375 to your current HEAD::
+This allows you to tie unfetched SVN revision 375 to your current HEAD:
- `git-svn fetch 375=$(git-rev-parse HEAD)`
+------------------------------------------------
+ git-svn fetch 375=$(git-rev-parse HEAD)
+------------------------------------------------
Advanced Example: Tracking a Reorganized Repository
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+Note: this example is now obsolete if you have SVN::* libraries
+installed. Simply use --follow-parent when fetching.
+
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
@@ -289,20 +481,18 @@ he needed to continue tracking /ufoai/trunk where /trunk left off.
BUGS
----
-If somebody commits a conflicting changeset to SVN at a bad moment
-(right before you commit) causing a conflict and your commit to fail,
-your svn working tree ($GIT_DIR/git-svn/tree) may be dirtied. The
-easiest thing to do is probably just to rm -rf $GIT_DIR/git-svn/tree and
-run 'rebuild'.
+
+If you are not using the SVN::* Perl libraries and somebody commits a
+conflicting changeset to SVN at a bad moment (right before you commit)
+causing a conflict and your commit to fail, your svn working tree
+($GIT_DIR/git-svn/tree) may be dirtied. The easiest thing to do is
+probably just to rm -rf $GIT_DIR/git-svn/tree and run 'rebuild'.
We ignore all SVN properties except svn:executable. Too difficult to
map them since we rely heavily on git write-tree being _exactly_ the
same on both the SVN and git working trees and I prefer not to clutter
working trees with metadata files.
-svn:keywords can't be ignored in Subversion (at least I don't know of
-a way to ignore them).
-
Renamed and copied directories are not detected by git and hence not
tracked when committing to SVN. I do not plan on adding support for
this as it's quite difficult and time-consuming to get working for all
@@ -310,6 +500,10 @@ the possible corner cases (git doesn't do it, either). Renamed and
copied files are fully supported if they're similar enough for git to
detect them.
+SEE ALSO
+--------
+gitlink:git-rebase[1]
+
Author
------
Written by Eric Wong <normalperson@yhbt.net>.
diff --git a/Documentation/git-tar-tree.txt b/Documentation/git-tar-tree.txt
index 1e1c7fa856..74a6fddd9a 100644
--- a/Documentation/git-tar-tree.txt
+++ b/Documentation/git-tar-tree.txt
@@ -12,6 +12,9 @@ SYNOPSIS
DESCRIPTION
-----------
+THIS COMMAND IS DEPRECATED. Use `git-archive` with `--format=tar`
+option instead.
+
Creates a tar archive containing the tree structure for the named tree.
When <base> is specified it is added as a leading path to the files in the
generated tar archive.
diff --git a/Documentation/git-unpack-objects.txt b/Documentation/git-unpack-objects.txt
index c20b38b08a..ff6184b0f7 100644
--- a/Documentation/git-unpack-objects.txt
+++ b/Documentation/git-unpack-objects.txt
@@ -8,7 +8,7 @@ git-unpack-objects - Unpack objects from a packed archive
SYNOPSIS
--------
-'git-unpack-objects' [-n] [-q] <pack-file
+'git-unpack-objects' [-n] [-q] [-r] <pack-file
DESCRIPTION
@@ -34,6 +34,12 @@ OPTIONS
The command usually shows percentage progress. This
flag suppresses it.
+-r::
+ When unpacking a corrupt packfile, the command dies at
+ the first corruption. This flag tells it to keep going
+ and make the best effort to recover as many objects as
+ possible.
+
Author
------
diff --git a/Documentation/git-update-index.txt b/Documentation/git-update-index.txt
index 3ae6e74573..0e0a3af1be 100644
--- a/Documentation/git-update-index.txt
+++ b/Documentation/git-update-index.txt
@@ -15,7 +15,7 @@ SYNOPSIS
[--cacheinfo <mode> <object> <file>]\*
[--chmod=(+|-)x]
[--assume-unchanged | --no-assume-unchanged]
- [--really-refresh] [--unresolve] [--again]
+ [--really-refresh] [--unresolve] [--again | -g]
[--info-only] [--index-info]
[-z] [--stdin]
[--verbose]
@@ -80,7 +80,7 @@ OPTIONS
filesystem that has very slow lstat(2) system call
(e.g. cifs).
---again::
+--again, -g::
Runs `git-update-index` itself on the paths whose index
entries are different from those from the `HEAD` commit.
@@ -216,8 +216,8 @@ $ git ls-files -s
------------
-Using "assume unchanged" bit
-----------------------------
+Using ``assume unchanged'' bit
+------------------------------
Many operations in git depend on your filesystem to have an
efficient `lstat(2)` implementation, so that `st_mtime`
diff --git a/Documentation/git-update-ref.txt b/Documentation/git-update-ref.txt
index e062030e91..71bcb7954f 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' [-m <reason>] <ref> <newvalue> [<oldvalue>]
+'git-update-ref' [-m <reason>] (-d <ref> <oldvalue> | <ref> <newvalue> [<oldvalue>])
DESCRIPTION
-----------
@@ -20,7 +20,9 @@ possibly dereferencing the symbolic refs, after verifying that
the current value of the <ref> matches <oldvalue>.
E.g. `git-update-ref refs/heads/master <newvalue> <oldvalue>`
updates the master branch head to <newvalue> only if its current
-value is <oldvalue>.
+value is <oldvalue>. You can specify 40 "0" or an empty string
+as <oldvalue> to make sure that the ref you are creating does
+not exist.
It also allows a "ref" file to be a symbolic pointer to another
ref file by starting with the four-byte header sequence of
@@ -49,6 +51,10 @@ for reading but not for writing (so we'll never write through a
ref symlink to some other tree, if you have copied a whole
archive by creating a symlink tree).
+With `-d` flag, it deletes the named <ref> after verifying it
+still contains <oldvalue>.
+
+
Logging Updates
---------------
If config parameter "core.logAllRefUpdates" is true or the file
diff --git a/Documentation/git-upload-archive.txt b/Documentation/git-upload-archive.txt
new file mode 100644
index 0000000000..388bb53d29
--- /dev/null
+++ b/Documentation/git-upload-archive.txt
@@ -0,0 +1,37 @@
+git-upload-archive(1)
+====================
+
+NAME
+----
+git-upload-archive - Send archive
+
+
+SYNOPSIS
+--------
+'git-upload-archive' <directory>
+
+DESCRIPTION
+-----------
+Invoked by 'git-archive --remote' and sends a generated archive to the
+other end over the git protocol.
+
+This command is usually not invoked directly by the end user. The UI
+for the protocol is on the 'git-archive' side, and the program pair
+is meant to be used to get an archive from a remote repository.
+
+OPTIONS
+-------
+<directory>::
+ The repository to get a tar archive from.
+
+Author
+------
+Written by Franck Bui-Huu.
+
+Documentation
+--------------
+Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
+
+GIT
+---
+Part of the gitlink:git[7] suite
diff --git a/Documentation/git-upload-tar.txt b/Documentation/git-upload-tar.txt
deleted file mode 100644
index 394af62015..0000000000
--- a/Documentation/git-upload-tar.txt
+++ /dev/null
@@ -1,39 +0,0 @@
-git-upload-tar(1)
-=================
-
-NAME
-----
-git-upload-tar - Send tar archive
-
-
-SYNOPSIS
---------
-'git-upload-tar' <directory>
-
-DESCRIPTION
------------
-Invoked by 'git-tar-tree --remote' and sends a generated tar archive
-to the other end over the git protocol.
-
-This command is usually not invoked directly by the end user.
-The UI for the protocol is on the 'git-tar-tree' side, and the
-program pair is meant to be used to get a tar archive from a
-remote repository.
-
-
-OPTIONS
--------
-<directory>::
- The repository to get a tar archive from.
-
-Author
-------
-Written by Junio C Hamano <junio@kernel.org>
-
-Documentation
---------------
-Documentation by Junio C Hamano.
-
-GIT
----
-Part of the gitlink:git[7] suite
diff --git a/Documentation/git.txt b/Documentation/git.txt
index 3de5fa9c82..619d65685e 100644
--- a/Documentation/git.txt
+++ b/Documentation/git.txt
@@ -8,8 +8,9 @@ git - the stupid content tracker
SYNOPSIS
--------
+[verse]
'git' [--version] [--exec-path[=GIT_EXEC_PATH]] [-p|--paginate]
- [--bare] [--git-dir=GIT_DIR] [--help] COMMAND [ARGS]
+ [--bare] [--git-dir=GIT_DIR] [--help] COMMAND [ARGS]
DESCRIPTION
-----------
@@ -71,186 +72,6 @@ GIT COMMANDS
We divide git into high level ("porcelain") commands and low level
("plumbing") commands.
-Low-level commands (plumbing)
------------------------------
-
-Although git includes its
-own porcelain layer, its low-level commands are sufficient to support
-development of alternative porcelains. Developers of such porcelains
-might start by reading about gitlink:git-update-index[1] and
-gitlink:git-read-tree[1].
-
-We divide the low-level commands into commands that manipulate objects (in
-the repository, index, and working tree), commands that interrogate and
-compare objects, and commands that move objects and references between
-repositories.
-
-Manipulation commands
-~~~~~~~~~~~~~~~~~~~~~
-gitlink:git-apply[1]::
- Reads a "diff -up1" or git generated patch file and
- applies it to the working tree.
-
-gitlink:git-checkout-index[1]::
- Copy files from the index to the working tree.
-
-gitlink:git-commit-tree[1]::
- Creates a new commit object.
-
-gitlink:git-hash-object[1]::
- Computes the object ID from a file.
-
-gitlink:git-index-pack[1]::
- Build pack idx file for an existing packed archive.
-
-gitlink:git-init-db[1]::
- Creates an empty git object database, or reinitialize an
- existing one.
-
-gitlink:git-merge-index[1]::
- Runs a merge for files needing merging.
-
-gitlink:git-mktag[1]::
- Creates a tag object.
-
-gitlink:git-mktree[1]::
- Build a tree-object from ls-tree formatted text.
-
-gitlink:git-pack-objects[1]::
- Creates a packed archive of objects.
-
-gitlink:git-prune-packed[1]::
- Remove extra objects that are already in pack files.
-
-gitlink:git-read-tree[1]::
- Reads tree information into the index.
-
-gitlink:git-repo-config[1]::
- Get and set options in .git/config.
-
-gitlink:git-unpack-objects[1]::
- Unpacks objects out of a packed archive.
-
-gitlink:git-update-index[1]::
- Registers files in the working tree to the index.
-
-gitlink:git-write-tree[1]::
- Creates a tree from the index.
-
-
-Interrogation commands
-~~~~~~~~~~~~~~~~~~~~~~
-
-gitlink:git-cat-file[1]::
- Provide content or type/size information for repository objects.
-
-gitlink:git-describe[1]::
- Show the most recent tag that is reachable from a commit.
-
-gitlink:git-diff-index[1]::
- Compares content and mode of blobs between the index and repository.
-
-gitlink:git-diff-files[1]::
- Compares files in the working tree and the index.
-
-gitlink:git-diff-stages[1]::
- Compares two "merge stages" in the index.
-
-gitlink:git-diff-tree[1]::
- Compares the content and mode of blobs found via two tree objects.
-
-gitlink:git-fsck-objects[1]::
- Verifies the connectivity and validity of the objects in the database.
-
-gitlink:git-ls-files[1]::
- Information about files in the index and the working tree.
-
-gitlink:git-ls-tree[1]::
- Displays a tree object in human readable form.
-
-gitlink:git-merge-base[1]::
- Finds as good common ancestors as possible for a merge.
-
-gitlink:git-name-rev[1]::
- Find symbolic names for given revs.
-
-gitlink:git-pack-redundant[1]::
- Find redundant pack files.
-
-gitlink:git-rev-list[1]::
- Lists commit objects in reverse chronological order.
-
-gitlink:git-show-index[1]::
- Displays contents of a pack idx file.
-
-gitlink:git-tar-tree[1]::
- Creates a tar archive of the files in the named tree object.
-
-gitlink:git-unpack-file[1]::
- Creates a temporary file with a blob's contents.
-
-gitlink:git-var[1]::
- Displays a git logical variable.
-
-gitlink:git-verify-pack[1]::
- Validates packed git archive files.
-
-In general, the interrogate commands do not touch the files in
-the working tree.
-
-
-Synching repositories
-~~~~~~~~~~~~~~~~~~~~~
-
-gitlink:git-fetch-pack[1]::
- Updates from a remote repository (engine for ssh and
- local transport).
-
-gitlink:git-http-fetch[1]::
- Downloads a remote git repository via HTTP by walking
- commit chain.
-
-gitlink:git-local-fetch[1]::
- Duplicates another git repository on a local system by
- walking commit chain.
-
-gitlink:git-peek-remote[1]::
- Lists references on a remote repository using
- upload-pack protocol (engine for ssh and local
- transport).
-
-gitlink:git-receive-pack[1]::
- Invoked by 'git-send-pack' to receive what is pushed to it.
-
-gitlink:git-send-pack[1]::
- Pushes to a remote repository, intelligently.
-
-gitlink:git-http-push[1]::
- Push missing objects using HTTP/DAV.
-
-gitlink:git-shell[1]::
- Restricted shell for GIT-only SSH access.
-
-gitlink:git-ssh-fetch[1]::
- Pulls from a remote repository over ssh connection by
- walking commit chain.
-
-gitlink:git-ssh-upload[1]::
- Helper "server-side" program used by git-ssh-fetch.
-
-gitlink:git-update-server-info[1]::
- Updates auxiliary information on a dumb server to help
- clients discover references and packs on it.
-
-gitlink:git-upload-pack[1]::
- Invoked by 'git-fetch-pack' to push
- what are asked for.
-
-gitlink:git-upload-tar[1]::
- Invoked by 'git-tar-tree --remote' to return the tar
- archive the other end asked for.
-
-
High-level commands (porcelain)
-------------------------------
@@ -269,6 +90,9 @@ gitlink:git-am[1]::
gitlink:git-applymbox[1]::
Apply patches from a mailbox, original version by Linus.
+gitlink:git-archive[1]::
+ Creates an archive of files from a named tree.
+
gitlink:git-bisect[1]::
Find the change that introduced a bug by binary search.
@@ -302,6 +126,9 @@ gitlink:git-format-patch[1]::
gitlink:git-grep[1]::
Print lines matching a pattern.
+gitlink:gitk[1]::
+ The git repository browser.
+
gitlink:git-log[1]::
Shows commit logs.
@@ -314,8 +141,11 @@ gitlink:git-merge[1]::
gitlink:git-mv[1]::
Move or rename a file, a directory, or a symlink.
+gitlink:git-pack-refs[1]::
+ Pack heads and tags for efficient repository access.
+
gitlink:git-pull[1]::
- Fetch from and merge with a remote repository.
+ Fetch from and merge with a remote repository or a local branch.
gitlink:git-push[1]::
Update remote refs along with associated objects.
@@ -422,7 +252,7 @@ gitlink:git-annotate[1]::
Annotate file lines with commit info.
gitlink:git-blame[1]::
- Blame file lines on commits.
+ Find out where each line in a file came from.
gitlink:git-check-ref-format[1]::
Make sure ref name is well formed.
@@ -472,6 +302,9 @@ gitlink:git-request-pull[1]::
gitlink:git-rev-parse[1]::
Pick out and massage parameters.
+gitlink:git-runstatus[1]::
+ A helper for git-status and git-commit.
+
gitlink:git-send-email[1]::
Send patch e-mails out of "format-patch --mbox" output.
@@ -482,11 +315,189 @@ gitlink:git-stripspace[1]::
Filter out empty lines.
-Commands not yet documented
----------------------------
+Low-level commands (plumbing)
+-----------------------------
-gitlink:gitk[1]::
- The gitk repository browser.
+Although git includes its
+own porcelain layer, its low-level commands are sufficient to support
+development of alternative porcelains. Developers of such porcelains
+might start by reading about gitlink:git-update-index[1] and
+gitlink:git-read-tree[1].
+
+We divide the low-level commands into commands that manipulate objects (in
+the repository, index, and working tree), commands that interrogate and
+compare objects, and commands that move objects and references between
+repositories.
+
+Manipulation commands
+~~~~~~~~~~~~~~~~~~~~~
+gitlink:git-apply[1]::
+ Reads a "diff -up1" or git generated patch file and
+ applies it to the working tree.
+
+gitlink:git-checkout-index[1]::
+ Copy files from the index to the working tree.
+
+gitlink:git-commit-tree[1]::
+ Creates a new commit object.
+
+gitlink:git-hash-object[1]::
+ Computes the object ID from a file.
+
+gitlink:git-index-pack[1]::
+ Build pack idx file for an existing packed archive.
+
+gitlink:git-init-db[1]::
+ Creates an empty git object database, or reinitialize an
+ existing one.
+
+gitlink:git-merge-index[1]::
+ Runs a merge for files needing merging.
+
+gitlink:git-mktag[1]::
+ Creates a tag object.
+
+gitlink:git-mktree[1]::
+ Build a tree-object from ls-tree formatted text.
+
+gitlink:git-pack-objects[1]::
+ Creates a packed archive of objects.
+
+gitlink:git-prune-packed[1]::
+ Remove extra objects that are already in pack files.
+
+gitlink:git-read-tree[1]::
+ Reads tree information into the index.
+
+gitlink:git-repo-config[1]::
+ Get and set options in .git/config.
+
+gitlink:git-unpack-objects[1]::
+ Unpacks objects out of a packed archive.
+
+gitlink:git-update-index[1]::
+ Registers files in the working tree to the index.
+
+gitlink:git-write-tree[1]::
+ Creates a tree from the index.
+
+
+Interrogation commands
+~~~~~~~~~~~~~~~~~~~~~~
+
+gitlink:git-cat-file[1]::
+ Provide content or type/size information for repository objects.
+
+gitlink:git-describe[1]::
+ Show the most recent tag that is reachable from a commit.
+
+gitlink:git-diff-index[1]::
+ Compares content and mode of blobs between the index and repository.
+
+gitlink:git-diff-files[1]::
+ Compares files in the working tree and the index.
+
+gitlink:git-diff-stages[1]::
+ Compares two "merge stages" in the index.
+
+gitlink:git-diff-tree[1]::
+ Compares the content and mode of blobs found via two tree objects.
+
+gitlink:git-for-each-ref[1]::
+ Output information on each ref.
+
+gitlink:git-fsck-objects[1]::
+ Verifies the connectivity and validity of the objects in the database.
+
+gitlink:git-ls-files[1]::
+ Information about files in the index and the working tree.
+
+gitlink:git-ls-tree[1]::
+ Displays a tree object in human readable form.
+
+gitlink:git-merge-base[1]::
+ Finds as good common ancestors as possible for a merge.
+
+gitlink:git-name-rev[1]::
+ Find symbolic names for given revs.
+
+gitlink:git-pack-redundant[1]::
+ Find redundant pack files.
+
+gitlink:git-rev-list[1]::
+ Lists commit objects in reverse chronological order.
+
+gitlink:git-show-index[1]::
+ Displays contents of a pack idx file.
+
+gitlink:git-show-ref[1]::
+ List references in a local repository.
+
+gitlink:git-tar-tree[1]::
+ Creates a tar archive of the files in the named tree object.
+
+gitlink:git-unpack-file[1]::
+ Creates a temporary file with a blob's contents.
+
+gitlink:git-var[1]::
+ Displays a git logical variable.
+
+gitlink:git-verify-pack[1]::
+ Validates packed git archive files.
+
+In general, the interrogate commands do not touch the files in
+the working tree.
+
+
+Synching repositories
+~~~~~~~~~~~~~~~~~~~~~
+
+gitlink:git-fetch-pack[1]::
+ Updates from a remote repository (engine for ssh and
+ local transport).
+
+gitlink:git-http-fetch[1]::
+ Downloads a remote git repository via HTTP by walking
+ commit chain.
+
+gitlink:git-local-fetch[1]::
+ Duplicates another git repository on a local system by
+ walking commit chain.
+
+gitlink:git-peek-remote[1]::
+ Lists references on a remote repository using
+ upload-pack protocol (engine for ssh and local
+ transport).
+
+gitlink:git-receive-pack[1]::
+ Invoked by 'git-send-pack' to receive what is pushed to it.
+
+gitlink:git-send-pack[1]::
+ Pushes to a remote repository, intelligently.
+
+gitlink:git-http-push[1]::
+ Push missing objects using HTTP/DAV.
+
+gitlink:git-shell[1]::
+ Restricted shell for GIT-only SSH access.
+
+gitlink:git-ssh-fetch[1]::
+ Pulls from a remote repository over ssh connection by
+ walking commit chain.
+
+gitlink:git-ssh-upload[1]::
+ Helper "server-side" program used by git-ssh-fetch.
+
+gitlink:git-update-server-info[1]::
+ Updates auxiliary information on a dumb server to help
+ clients discover references and packs on it.
+
+gitlink:git-upload-archive[1]::
+ Invoked by 'git-archive' to send a generated archive.
+
+gitlink:git-upload-pack[1]::
+ Invoked by 'git-fetch-pack' to push
+ what are asked for.
Configuration Mechanism
@@ -563,6 +574,9 @@ HEAD::
a valid head 'name'
(i.e. the contents of `$GIT_DIR/refs/heads/<head>`).
+For a more complete list of ways to spell object names, see
+"SPECIFYING REVISIONS" section in gitlink:git-rev-parse[1].
+
File/Directory Structure
------------------------
@@ -637,9 +651,18 @@ other
This environment variable overrides `$PAGER`.
'GIT_TRACE'::
- If this variable is set git will print `trace:` messages on
+ If this variable is set to "1", "2" or "true" (comparison
+ is case insensitive), git will print `trace:` messages on
stderr telling about alias expansion, built-in command
execution and external command execution.
+ If this variable is set to an integer value greater than 1
+ and lower than 10 (strictly) then git will interpret this
+ value as an open file descriptor and will try to write the
+ trace messages into this file descriptor.
+ Alternatively, if this variable is set to an absolute path
+ (starting with a '/' character), git will interpret this
+ as a file path and will try to write the trace messages
+ into it.
Discussion[[Discussion]]
------------------------
diff --git a/Documentation/gitk.txt b/Documentation/gitk.txt
index cb482bf98e..f1aeb07f64 100644
--- a/Documentation/gitk.txt
+++ b/Documentation/gitk.txt
@@ -3,26 +3,56 @@ gitk(1)
NAME
----
-gitk - Some git command not yet documented.
-
+gitk - git repository browser
SYNOPSIS
--------
-'gitk' [ --option ] <args>...
+'gitk' [<option>...] [<revs>] [--] [<path>...]
DESCRIPTION
-----------
-Does something not yet documented.
+Displays changes in a repository or a selected set of commits. This includes
+visualizing the commit graph, showing information related to each commit, and
+the files in the trees of each revision.
+Historically, gitk was the first repository browser. It's written in tcl/tk
+and started off in a separate repository but was later merged into the main
+git repository.
OPTIONS
-------
---option::
- Some option not yet documented.
+To control which revisions to shown, the command takes options applicable to
+the gitlink:git-rev-list[1] command. This manual page describes only the most
+frequently used options.
+
+-n <number>, --max-count=<number>::
+
+ Limits the number of commits to show.
+
+--since=<date>::
+
+ Show commits more recent than a specific date.
+
+--until=<date>::
+
+ Show commits older than a specific date.
+
+--all::
+
+ Show all branches.
-<args>...::
- Some argument not yet documented.
+<revs>::
+ Limit the revisions to show. This can be either a single revision
+ meaning show from the given revision and back, or it can be a range in
+ the form "'<from>'..'<to>'" to show all revisions between '<from>' and
+ back to '<to>'. Note, more advanced revision selection can be applied.
+
+<path>::
+
+ Limit commits to the ones touching files in the given paths. Note, to
+ avoid ambiguity wrt. revision names use "--" to separate the paths
+ from any preceeding options.
Examples
--------
@@ -37,13 +67,32 @@ gitk --since="2 weeks ago" \-- gitk::
The "--" is necessary to avoid confusion with the *branch* named
'gitk'
+gitk --max-count=100 --all -- Makefile::
+
+ Show at most 100 changes made to the file 'Makefile'. Instead of only
+ looking for changes in the current branch look in all branches.
+
+See Also
+--------
+'qgit(1)'::
+ A repository browser written in C++ using Qt.
+
+'gitview(1)'::
+ A repository browser written in Python using Gtk. It's based on
+ 'bzrk(1)' and distributed in the contrib area of the git repository.
+
+'tig(1)'::
+ A minimal repository browser and git tool output highlighter written
+ in C using Ncurses.
+
Author
------
-Written by Paul Mackerras <paulus@samba.org>
+Written by Paul Mackerras <paulus@samba.org>.
Documentation
--------------
-Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
+Documentation by Junio C Hamano, Jonas Fonseca, and the git-list
+<git@vger.kernel.org>.
GIT
---
diff --git a/Documentation/glossary.txt b/Documentation/glossary.txt
index 14449ca8ba..894883d7b6 100644
--- a/Documentation/glossary.txt
+++ b/Documentation/glossary.txt
@@ -179,7 +179,7 @@ object name::
character hexadecimal encoding of the hash of the object (possibly
followed by a white space).
-object type:
+object type::
One of the identifiers "commit","tree","tag" and "blob" describing
the type of an object.
@@ -282,6 +282,13 @@ SCM::
SHA1::
Synonym for object name.
+symref::
+ Symbolic reference: instead of containing the SHA1 id itself, it
+ is of the format 'ref: refs/some/thing' and when referenced, it
+ recursively dereferences to this reference. 'HEAD' is a prime
+ example of a symref. Symbolic references are manipulated with
+ the gitlink:git-symbolic-ref[1] command.
+
topic branch::
A regular git branch that is used by a developer to
identify a conceptual line of development. Since branches
@@ -324,7 +331,7 @@ tag::
A tag is most typically used to mark a particular point in the
commit ancestry chain.
-unmerged index:
+unmerged index::
An index which contains unmerged index entries.
working tree::
diff --git a/Documentation/hooks.txt b/Documentation/hooks.txt
index 898b4aaf80..517f49b5cc 100644
--- a/Documentation/hooks.txt
+++ b/Documentation/hooks.txt
@@ -5,8 +5,7 @@ Hooks are little scripts you can place in `$GIT_DIR/hooks`
directory to trigger action at certain points. When
`git-init-db` is run, a handful example hooks are copied in the
`hooks` directory of the new repository, but by default they are
-all disabled. To enable a hook, make it executable with `chmod
-+x`.
+all disabled. To enable a hook, make it executable with `chmod +x`.
This document describes the currently defined hooks.
@@ -16,16 +15,16 @@ applypatch-msg
This hook is invoked by `git-applypatch` script, which is
typically invoked by `git-applymbox`. It takes a single
parameter, the name of the file that holds the proposed commit
-log message. Exiting with non-zero status causes the
-'git-applypatch' to abort before applying the patch.
+log message. Exiting with non-zero status causes
+`git-applypatch` to abort before applying the patch.
The hook is allowed to edit the message file in place, and can
be used to normalize the message into some project standard
format (if the project has one). It can also be used to refuse
the commit after inspecting the message file.
-The default applypatch-msg hook, when enabled, runs the
-commit-msg hook, if the latter is enabled.
+The default 'applypatch-msg' hook, when enabled, runs the
+'commit-msg' hook, if the latter is enabled.
pre-applypatch
--------------
@@ -39,8 +38,8 @@ after application of the patch not committed.
It can be used to inspect the current working tree and refuse to
make a commit if it does not pass certain test.
-The default pre-applypatch hook, when enabled, runs the
-pre-commit hook, if the latter is enabled.
+The default 'pre-applypatch' hook, when enabled, runs the
+'pre-commit' hook, if the latter is enabled.
post-applypatch
---------------
@@ -61,9 +60,9 @@ invoked before obtaining the proposed commit log message and
making a commit. Exiting with non-zero status from this script
causes the `git-commit` to abort.
-The default pre-commit hook, when enabled, catches introduction
+The default 'pre-commit' hook, when enabled, catches introduction
of lines with trailing whitespaces and aborts the commit when
-a such line is found.
+such a line is found.
commit-msg
----------
@@ -79,8 +78,8 @@ be used to normalize the message into some project standard
format (if the project has one). It can also be used to refuse
the commit after inspecting the message file.
-The default commit-msg hook, when enabled, detects duplicate
-Signed-off-by: lines, and aborts the commit when one is found.
+The default 'commit-msg' hook, when enabled, detects duplicate
+"Signed-off-by" lines, and aborts the commit if one is found.
post-commit
-----------
@@ -91,23 +90,24 @@ parameter, and is invoked after a commit is made.
This hook is meant primarily for notification, and cannot affect
the outcome of `git-commit`.
-The default post-commit hook, when enabled, demonstrates how to
+The default 'post-commit' hook, when enabled, demonstrates how to
send out a commit notification e-mail.
update
------
This hook is invoked by `git-receive-pack` on the remote repository,
-which is happens when a `git push` is done on a local repository.
+which happens when a `git push` is done on a local repository.
Just before updating the ref on the remote repository, the update hook
is invoked. Its exit status determines the success or failure of
the ref update.
The hook executes once for each ref to be updated, and takes
three parameters:
- - the name of the ref being updated,
- - the old object name stored in the ref,
- - and the new objectname to be stored in the ref.
+
+ - the name of the ref being updated,
+ - the old object name stored in the ref,
+ - and the new objectname to be stored in the ref.
A zero exit from the update hook allows the ref to be updated.
Exiting with a non-zero status prevents `git-receive-pack`
@@ -126,16 +126,16 @@ Another use suggested on the mailing list is to use this hook to
implement access control which is finer grained than the one
based on filesystem group.
-The standard output of this hook is sent to /dev/null; if you
-want to report something to the git-send-pack on the other end,
-you can redirect your output to your stderr.
+The standard output of this hook is sent to `/dev/null`; if you
+want to report something to the `git-send-pack` on the other end,
+you can redirect your output to your `stderr`.
post-update
-----------
This hook is invoked by `git-receive-pack` on the remote repository,
-which is happens when a `git push` is done on a local repository.
+which happens when a `git push` is done on a local repository.
It executes on the remote repository once after all the refs have
been updated.
@@ -145,16 +145,16 @@ name of ref that was actually updated.
This hook is meant primarily for notification, and cannot affect
the outcome of `git-receive-pack`.
-The post-update hook can tell what are the heads that were pushed,
+The 'post-update' hook can tell what are the heads that were pushed,
but it does not know what their original and updated values are,
so it is a poor place to do log old..new.
-The default post-update hook, when enabled, runs
+When enabled, the default 'post-update' hook runs
`git-update-server-info` to keep the information used by dumb
-transports (e.g., http) up-to-date. If you are publishing
-a git repository that is accessible via http, you should
+transports (e.g., HTTP) up-to-date. If you are publishing
+a git repository that is accessible via HTTP, you should
probably enable this hook.
-The standard output of this hook is sent to /dev/null; if you
-want to report something to the git-send-pack on the other end,
-you can redirect your output to your stderr.
+The standard output of this hook is sent to `/dev/null`; if you
+want to report something to the `git-send-pack` on the other end,
+you can redirect your output to your `stderr`.
diff --git a/Documentation/pretty-formats.txt b/Documentation/pretty-formats.txt
new file mode 100644
index 0000000000..996f628903
--- /dev/null
+++ b/Documentation/pretty-formats.txt
@@ -0,0 +1,78 @@
+--pretty[='<format>']::
+
+ Pretty-prints the details of a commit. `--pretty`
+ without an explicit `=<format>` defaults to 'medium'.
+ If the commit is a merge, and if the pretty-format
+ is not 'oneline', 'email' or 'raw', an additional line is
+ inserted before the 'Author:' line. This line begins with
+ "Merge: " and the sha1s of ancestral commits are printed,
+ separated by spaces. Note that the listed commits may not
+ necessarily be the list of the *direct* parent commits if you
+ have limited your view of history: for example, if you are
+ only interested in changes related to a certain directory or
+ file. Here are some additional details for each format:
+
+ * 'oneline'
+
+ <sha1> <title line>
++
+This is designed to be as compact as possible.
+
+ * 'short'
+
+ commit <sha1>
+ Author: <author>
+
+ <title line>
+
+ * 'medium'
+
+ commit <sha1>
+ Author: <author>
+ Date: <date>
+
+ <title line>
+
+ <full commit message>
+
+ * 'full'
+
+ commit <sha1>
+ Author: <author>
+ Commit: <committer>
+
+ <title line>
+
+ <full commit message>
+
+ * 'fuller'
+
+ commit <sha1>
+ Author: <author>
+ AuthorDate: <date & time>
+ Commit: <committer>
+ CommitDate: <date & time>
+
+ <title line>
+
+ <full commit message>
+
+
+ * 'email'
+
+ From <sha1> <date>
+ From: <author>
+ Date: <date & time>
+ Subject: [PATCH] <title line>
+
+ full commit message>
+
+
+ * 'raw'
++
+The 'raw' format shows the entire commit exactly as
+stored in the commit object. Notably, the SHA1s are
+displayed in full, regardless of whether --abbrev or
+--no-abbrev are used, and 'parents' information show the
+true parent commits, without taking grafts nor history
+simplification into account.
diff --git a/Documentation/repository-layout.txt b/Documentation/repository-layout.txt
index 275d18bb54..e20fb7e74c 100644
--- a/Documentation/repository-layout.txt
+++ b/Documentation/repository-layout.txt
@@ -52,9 +52,20 @@ objects/info/packs::
by default.
objects/info/alternates::
- This file records absolute filesystem paths of alternate
- object stores that this object store borrows objects
- from, one pathname per line.
+ This file records paths to alternate object stores that
+ this object store borrows objects from, one pathname per
+ line. Note that not only native Git tools use it locally,
+ but the HTTP fetcher also tries to use it remotely; this
+ will usually work if you have relative paths (relative
+ to the object database, not to the repository!) in your
+ alternates file, but it will not work if you use absolute
+ paths unless the absolute path in filesystem and web URL
+ is the same. See also 'objects/info/http-alternates'.
+
+objects/info/http-alternates::
+ This file records URLs to alternate object stores that
+ this object store borrows objects from, to be used when
+ the repository is fetched over HTTP.
refs::
References are stored in subdirectories of this
@@ -70,12 +81,16 @@ refs/tags/`name`::
object, or a tag object that points at a commit object).
HEAD::
- A symlink of the form `refs/heads/'name'` to point at
- the current branch, if exists. It does not mean much if
- the repository is not associated with any working tree
+ A symref (see glossary) to the `refs/heads/` namespace
+ describing the currently active branch. It does not mean
+ much if the repository is not associated with any working tree
(i.e. a 'bare' repository), but a valid git repository
- *must* have such a symlink here. It is legal if the
- named branch 'name' does not (yet) exist.
+ *must* have the HEAD file; some porcelains may use it to
+ guess the designated "default" branch of the repository
+ (usually 'master'). It is legal if the named branch
+ 'name' does not (yet) exist. In some legacy setups, it is
+ a symbolic link instead of a symref that points at the current
+ branch.
branches::
A slightly deprecated way to store shorthands to be used
diff --git a/Documentation/technical/racy-git.txt b/Documentation/technical/racy-git.txt
new file mode 100644
index 0000000000..7597d04142
--- /dev/null
+++ b/Documentation/technical/racy-git.txt
@@ -0,0 +1,193 @@
+Use of index and Racy git problem
+=================================
+
+Background
+----------
+
+The index is one of the most important data structure in git.
+It represents a virtual working tree state by recording list of
+paths and their object names and serves as a staging area to
+write out the next tree object to be committed. The state is
+"virtual" in the sense that it does not necessarily have to, and
+often does not, match the files in the working tree.
+
+There are cases git needs to examine the differences between the
+virtual working tree state in the index and the files in the
+working tree. The most obvious case is when the user asks `git
+diff` (or its low level implementation, `git diff-files`) or
+`git-ls-files --modified`. In addition, git internally checks
+if the files in the working tree is different from what are
+recorded in the index to avoid stomping on local changes in them
+during patch application, switching branches, and merging.
+
+In order to speed up this comparison between the files in the
+working tree and the index entries, the index entries record the
+information obtained from the filesystem via `lstat(2)` system
+call when they were last updated. When checking if they differ,
+git first runs `lstat(2)` on the files and compare the result
+with this information (this is what was originally done by the
+`ce_match_stat()` function, which the current code does in
+`ce_match_stat_basic()` function). If some of these "cached
+stat information" fields do not match, git can tell that the
+files are modified without even looking at their contents.
+
+Note: not all members in `struct stat` obtained via `lstat(2)`
+are used for this comparison. For example, `st_atime` obviously
+is not useful. Currently, git compares the file type (regular
+files vs symbolic links) and executable bits (only for regular
+files) from `st_mode` member, `st_mtime` and `st_ctime`
+timestamps, `st_uid`, `st_gid`, `st_ino`, and `st_size` members.
+With a `USE_STDEV` compile-time option, `st_dev` is also
+compared, but this is not enabled by default because this member
+is not stable on network filesystems. With `USE_NSEC`
+compile-time option, `st_mtim.tv_nsec` and `st_ctim.tv_nsec`
+members are also compared, but this is not enabled by default
+because the value of this member becomes meaningless once the
+inode is evicted from the inode cache on filesystems that do not
+store it on disk.
+
+
+Racy git
+--------
+
+There is one slight problem with the optimization based on the
+cached stat information. Consider this sequence:
+
+ $ git update-index 'foo'
+ : modify 'foo' in-place without changing its size
+
+The first `update-index` computes the object name of the
+contents of file `foo` and updates the index entry for `foo`
+along with the `struct stat` information. If the modification
+that follows it happens very fast so that the file's `st_mtime`
+timestamp does not change, after this sequence, the cached stat
+information the index entry records still exactly match what you
+can obtain from the filesystem, but the file `foo` is modified.
+This way, git can incorrectly think files in the working tree
+are unmodified even though they actually are. This is called
+the "racy git" problem (discovered by Pasky), and the entries
+that appear clean when they may not be because of this problem
+are called "racily clean".
+
+To avoid this problem, git does two things:
+
+. When the cached stat information says the file has not been
+ modified, and the `st_mtime` is the same as (or newer than)
+ the timestamp of the index file itself (which is the time `git
+ update-index foo` finished running in the above example), it
+ also compares the contents with the object registered in the
+ index entry to make sure they match.
+
+. When the index file is updated that contains racily clean
+ entries, cached `st_size` information is truncated to zero
+ before writing a new version of the index file.
+
+Because the index file itself is written after collecting all
+the stat information from updated paths, `st_mtime` timestamp of
+it is usually the same as or newer than any of the paths the
+index contains. And no matter how quick the modification that
+follows `git update-index foo` finishes, the resulting
+`st_mtime` timestamp on `foo` cannot get the timestamp earlier
+than the index file. Therefore, index entries that can be
+racily clean are limited to the ones that have the same
+timestamp as the index file itself.
+
+The callers that want to check if an index entry matches the
+corresponding file in the working tree continue to call
+`ce_match_stat()`, but with this change, `ce_match_stat()` uses
+`ce_modified_check_fs()` to see if racily clean ones are
+actually clean after comparing the cached stat information using
+`ce_match_stat_basic()`.
+
+The problem the latter solves is this sequence:
+
+ $ git update-index 'foo'
+ : modify 'foo' in-place without changing its size
+ : wait for enough time
+ $ git update-index 'bar'
+
+Without the latter, the timestamp of the index file gets a newer
+value, and falsely clean entry `foo` would not be caught by the
+timestamp comparison check done with the former logic anymore.
+The latter makes sure that the cached stat information for `foo`
+would never match with the file in the working tree, so later
+checks by `ce_match_stat_basic()` would report the index entry
+does not match the file and git does not have to fall back on more
+expensive `ce_modified_check_fs()`.
+
+
+Runtime penalty
+---------------
+
+The runtime penalty of falling back to `ce_modified_check_fs()`
+from `ce_match_stat()` can be very expensive when there are many
+racily clean entries. An obvious way to artificially create
+this situation is to give the same timestamp to all the files in
+the working tree in a large project, run `git update-index` on
+them, and give the same timestamp to the index file:
+
+ $ date >.datestamp
+ $ git ls-files | xargs touch -r .datestamp
+ $ git ls-files | git update-index --stdin
+ $ touch -r .datestamp .git/index
+
+This will make all index entries racily clean. The linux-2.6
+project, for example, there are over 20,000 files in the working
+tree. On my Athron 64X2 3800+, after the above:
+
+ $ /usr/bin/time git diff-files
+ 1.68user 0.54system 0:02.22elapsed 100%CPU (0avgtext+0avgdata 0maxresident)k
+ 0inputs+0outputs (0major+67111minor)pagefaults 0swaps
+ $ git update-index MAINTAINERS
+ $ /usr/bin/time git diff-files
+ 0.02user 0.12system 0:00.14elapsed 100%CPU (0avgtext+0avgdata 0maxresident)k
+ 0inputs+0outputs (0major+935minor)pagefaults 0swaps
+
+Running `git update-index` in the middle checked the racily
+clean entries, and left the cached `st_mtime` for all the paths
+intact because they were actually clean (so this step took about
+the same amount of time as the first `git diff-files`). After
+that, they are not racily clean anymore but are truly clean, so
+the second invocation of `git diff-files` fully took advantage
+of the cached stat information.
+
+
+Avoiding runtime penalty
+------------------------
+
+In order to avoid the above runtime penalty, the recent "master"
+branch (post 1.4.2) has a code that makes sure the index file
+gets timestamp newer than the youngest files in the index when
+there are many young files with the same timestamp as the
+resulting index file would otherwise would have by waiting
+before finishing writing the index file out.
+
+I suspect that in practice the situation where many paths in the
+index are all racily clean is quite rare. The only code paths
+that can record recent timestamp for large number of paths I
+know of are:
+
+. Initial `git add .` of a large project.
+
+. `git checkout` of a large project from an empty index into an
+ unpopulated working tree.
+
+Note: switching branches with `git checkout` keeps the cached
+stat information of existing working tree files that are the
+same between the current branch and the new branch, which are
+all older than the resulting index file, and they will not
+become racily clean. Only the files that are actually checked
+out can become racily clean.
+
+In a large project where raciness avoidance cost really matters,
+however, the initial computation of all object names in the
+index takes more than one second, and the index file is written
+out after all that happens. Therefore the timestamp of the
+index file will be more than one seconds later than the the
+youngest file in the working tree. This means that in these
+cases there actually will not be any racily clean entry in
+the resulting index.
+
+So in summary I think we should not worry about avoiding the
+runtime penalty and get rid of the "wait before finishing
+writing" code out.
diff --git a/Documentation/tutorial-2.txt b/Documentation/tutorial-2.txt
index 2f4fe1217a..42b6e7d7d2 100644
--- a/Documentation/tutorial-2.txt
+++ b/Documentation/tutorial-2.txt
@@ -368,7 +368,7 @@ in the index file is identical to the one in the working directory.
In addition to being the staging area for new commits, the index file
is also populated from the object database when checking out a
branch, and is used to hold the trees involved in a merge operation.
-See the link:core-tutorial.txt[core tutorial] and the relevant man
+See the link:core-tutorial.html[core tutorial] and the relevant man
pages for details.
What next?
diff --git a/Documentation/tutorial.txt b/Documentation/tutorial.txt
index 554ee0af91..1e4ddfbd11 100644
--- a/Documentation/tutorial.txt
+++ b/Documentation/tutorial.txt
@@ -141,7 +141,7 @@ $ git commit -a
------------------------------------------------
at this point the two branches have diverged, with different changes
-made in each. To merge the changes made in the two branches, run
+made in each. To merge the changes made in experimental into master, run
------------------------------------------------
$ git pull . experimental
diff --git a/Documentation/urls.txt b/Documentation/urls.txt
index 26ecba53fb..670827c323 100644
--- a/Documentation/urls.txt
+++ b/Documentation/urls.txt
@@ -51,6 +51,14 @@ lines are used for `git-push` and `git-fetch`/`git-pull`,
respectively. Multiple `Push:` and `Pull:` lines may
be specified for additional branch mappings.
+Or, equivalently, in the `$GIT_DIR/config` (note the use
+of `fetch` instead of `Pull:`):
+
+[remote "<remote>"]
+ url = <url>
+ push = <refspec>
+ fetch = <refspec>
+
The name of a file in `$GIT_DIR/branches` directory can be
specified as an older notation short-hand; the named
file should contain a single line, a URL in one of the
diff --git a/GIT-VERSION-GEN b/GIT-VERSION-GEN
index 14923c973b..4eac314f3a 100755
--- a/GIT-VERSION-GEN
+++ b/GIT-VERSION-GEN
@@ -1,7 +1,7 @@
#!/bin/sh
GVF=GIT-VERSION-FILE
-DEF_VER=v1.4.2.GIT
+DEF_VER=v1.4.4.1.GIT
LF='
'
diff --git a/INSTALL b/INSTALL
index fa9bf74a20..fce6bc39d5 100644
--- a/INSTALL
+++ b/INSTALL
@@ -38,6 +38,19 @@ Issues of note:
has been actively developed since 1997, and people have moved over to
graphical file managers.
+ - You can use git after building but without installing if you
+ wanted to. Various git commands need to find other git
+ commands and scripts to do their work, so you would need to
+ arrange a few environment variables to tell them that their
+ friends will be found in your built source area instead of at
+ their standard installation area. Something like this works
+ for me:
+
+ GIT_EXEC_PATH=`pwd`
+ PATH=`pwd`:$PATH
+ GITPERLLIB=`pwd`/perl/blib/lib
+ export GIT_EXEC_PATH PATH GITPERLLIB
+
- Git is reasonably self-sufficient, but does depend on a few external
programs and libraries:
diff --git a/Makefile b/Makefile
index a538710ed6..36ce8cd606 100644
--- a/Makefile
+++ b/Makefile
@@ -1,11 +1,6 @@
# The default target of this Makefile is...
all:
-# Define MOZILLA_SHA1 environment variable when running make to make use of
-# a bundled SHA1 routine coming from Mozilla. It is GPL'd and should be fast
-# on non-x86 architectures (e.g. PowerPC), while the OpenSSL version (default
-# choice) has very fast version optimized for i586.
-#
# Define NO_OPENSSL environment variable if you do not have OpenSSL.
# This also implies MOZILLA_SHA1.
#
@@ -60,6 +55,11 @@ all:
# Define ARM_SHA1 environment variable when running make to make use of
# a bundled SHA1 routine optimized for ARM.
#
+# Define MOZILLA_SHA1 environment variable when running make to make use of
+# a bundled SHA1 routine coming from Mozilla. It is GPL'd and should be fast
+# on non-x86 architectures (e.g. PowerPC), while the OpenSSL version (default
+# choice) has very fast version optimized for i586.
+#
# Define NEEDS_SSL_WITH_CRYPTO if you need -lcrypto with -lssl (Darwin).
#
# Define NEEDS_LIBICONV if linking with libc is not enough (Darwin).
@@ -81,18 +81,16 @@ all:
# Define NO_ACCURATE_DIFF if your diff program at least sometimes misses
# a missing newline at the end of the file.
#
-# Define NO_PYTHON if you want to lose all benefits of the recursive merge.
-#
# Define COLLISION_CHECK below if you believe that SHA1's
# 1461501637330902918203684832716283019655932542976 hashes do not give you
# sufficient guarantee that no collisions between objects will ever happen.
-
+#
# Define USE_NSEC below if you want git to care about sub-second file mtimes
# and ctimes. Note that you need recent glibc (at least 2.2.4) for this, and
# it will BREAK YOUR LOCAL DIFFS! show-diff and anything using it will likely
# randomly break unless your underlying filesystem supports those sub-second
# times (my ext3 doesn't).
-
+#
# Define USE_STDEV below if you want git to care about the underlying device
# change being considered an inode change from the update-cache perspective.
@@ -123,12 +121,19 @@ GIT_PYTHON_DIR = $(prefix)/share/git-core/python
# default configuration for gitweb
GITWEB_CONFIG = gitweb_config.perl
+GITWEB_HOME_LINK_STR = projects
GITWEB_SITENAME =
GITWEB_PROJECTROOT = /pub/git
+GITWEB_EXPORT_OK =
+GITWEB_STRICT_EXPORT =
+GITWEB_BASE_URL =
GITWEB_LIST =
GITWEB_HOMETEXT = indextext.html
GITWEB_CSS = gitweb.css
GITWEB_LOGO = git-logo.png
+GITWEB_FAVICON = git-favicon.png
+GITWEB_SITE_HEADER =
+GITWEB_SITE_FOOTER =
export prefix bindir gitexecdir template_dir GIT_PYTHON_DIR
@@ -146,9 +151,15 @@ SPARSE_FLAGS = -D__BIG_ENDIAN__ -D__powerpc__
### --- END CONFIGURATION SECTION ---
+# Those must not be GNU-specific; they are shared with perl/ which may
+# be built by a different compiler. (Note that this is an artifact now
+# but it still might be nice to keep that distinction.)
+BASIC_CFLAGS =
+BASIC_LDFLAGS =
+
SCRIPT_SH = \
- git-bisect.sh git-branch.sh git-checkout.sh \
- git-cherry.sh git-clean.sh git-clone.sh git-commit.sh \
+ git-bisect.sh git-checkout.sh \
+ git-clean.sh git-clone.sh git-commit.sh \
git-fetch.sh \
git-ls-remote.sh \
git-merge-one-file.sh git-parse-remote.sh \
@@ -164,27 +175,24 @@ SCRIPT_SH = \
SCRIPT_PERL = \
git-archimport.perl git-cvsimport.perl git-relink.perl \
git-shortlog.perl git-rerere.perl \
- git-annotate.perl git-cvsserver.perl \
+ git-cvsserver.perl \
git-svnimport.perl git-cvsexportcommit.perl \
git-send-email.perl git-svn.perl
SCRIPT_PYTHON = \
- git-merge-recursive.py
+ git-merge-recursive-old.py
SCRIPTS = $(patsubst %.sh,%,$(SCRIPT_SH)) \
$(patsubst %.perl,%,$(SCRIPT_PERL)) \
$(patsubst %.py,%,$(SCRIPT_PYTHON)) \
git-cherry-pick git-status git-instaweb
-# The ones that do not have to link with lcrypto, lz nor xdiff.
-SIMPLE_PROGRAMS = \
- git-daemon$X
-
# ... and all the rest that could be moved out of bindir to gitexecdir
PROGRAMS = \
git-convert-objects$X git-fetch-pack$X git-fsck-objects$X \
git-hash-object$X git-index-pack$X git-local-fetch$X \
git-merge-base$X \
+ git-daemon$X \
git-merge-index$X git-mktag$X git-mktree$X git-patch-id$X \
git-peek-remote$X git-receive-pack$X \
git-send-pack$X git-shell$X \
@@ -193,15 +201,21 @@ PROGRAMS = \
git-update-server-info$X \
git-upload-pack$X git-verify-pack$X \
git-pack-redundant$X git-var$X \
- git-describe$X git-merge-tree$X git-blame$X git-imap-send$X
+ git-describe$X git-merge-tree$X git-imap-send$X \
+ git-merge-recursive$X \
+ $(EXTRA_PROGRAMS)
+
+# Empty...
+EXTRA_PROGRAMS =
BUILT_INS = \
- git-format-patch$X git-show$X git-whatchanged$X \
+ git-format-patch$X git-show$X git-whatchanged$X git-cherry$X \
git-get-tar-commit-id$X \
$(patsubst builtin-%.o,git-%$X,$(BUILTIN_OBJS))
# what 'all' will build and 'install' will install, in gitexecdir
-ALL_PROGRAMS = $(PROGRAMS) $(SIMPLE_PROGRAMS) $(SCRIPTS)
+ALL_PROGRAMS = $(PROGRAMS) $(SCRIPTS) \
+ git-merge-recur$X
# Backward compatibility -- to be removed after 1.0
PROGRAMS += git-ssh-pull$X git-ssh-push$X
@@ -224,8 +238,8 @@ LIB_FILE=libgit.a
XDIFF_LIB=xdiff/lib.a
LIB_H = \
- blob.h cache.h commit.h csum-file.h delta.h \
- diff.h object.h pack.h pkt-line.h quote.h refs.h \
+ archive.h blob.h cache.h commit.h csum-file.h delta.h grep.h \
+ diff.h object.h pack.h pkt-line.h quote.h refs.h list-objects.h sideband.h \
run-command.h strbuf.h tag.h tree.h git-compat-util.h revision.h \
tree-walk.h log-tree.h dir.h path-list.h unpack-trees.h builtin.h
@@ -236,17 +250,25 @@ DIFF_OBJS = \
LIB_OBJS = \
blob.o commit.o connect.o csum-file.o cache-tree.o base85.o \
- date.o diff-delta.o entry.o exec_cmd.o ident.o lockfile.o \
- object.o pack-check.o patch-delta.o path.o pkt-line.o \
+ date.o diff-delta.o entry.o exec_cmd.o ident.o \
+ interpolate.o \
+ lockfile.o \
+ object.o pack-check.o patch-delta.o path.o pkt-line.o sideband.o \
quote.o read-cache.o refs.o run-command.o dir.o object-refs.o \
server-info.o setup.o sha1_file.o sha1_name.o strbuf.o \
tag.o tree.o usage.o config.o environment.o ctype.o copy.o \
- fetch-clone.o revision.o pager.o tree-walk.o xdiff-interface.o \
- alloc.o merge-file.o path-list.o help.o unpack-trees.o $(DIFF_OBJS)
+ revision.o pager.o tree-walk.o xdiff-interface.o \
+ write_or_die.o trace.o list-objects.o grep.o \
+ alloc.o merge-file.o path-list.o help.o unpack-trees.o $(DIFF_OBJS) \
+ color.o wt-status.o archive-zip.o archive-tar.o
BUILTIN_OBJS = \
builtin-add.o \
+ builtin-annotate.o \
builtin-apply.o \
+ builtin-archive.o \
+ builtin-blame.o \
+ builtin-branch.o \
builtin-cat-file.o \
builtin-checkout-index.o \
builtin-check-ref-format.o \
@@ -258,6 +280,7 @@ BUILTIN_OBJS = \
builtin-diff-stages.o \
builtin-diff-tree.o \
builtin-fmt-merge-msg.o \
+ builtin-for-each-ref.o \
builtin-grep.o \
builtin-init-db.o \
builtin-log.o \
@@ -276,6 +299,7 @@ BUILTIN_OBJS = \
builtin-rev-list.o \
builtin-rev-parse.o \
builtin-rm.o \
+ builtin-runstatus.o \
builtin-show-branch.o \
builtin-stripspace.o \
builtin-symbolic-ref.o \
@@ -283,12 +307,14 @@ BUILTIN_OBJS = \
builtin-unpack-objects.o \
builtin-update-index.o \
builtin-update-ref.o \
- builtin-upload-tar.o \
+ builtin-upload-archive.o \
builtin-verify-pack.o \
- builtin-write-tree.o
+ builtin-write-tree.o \
+ builtin-show-ref.o \
+ builtin-pack-refs.o
GITLIBS = $(LIB_FILE) $(XDIFF_LIB)
-LIBS = $(GITLIBS) -lz
+EXTLIBS = -lz
#
# Platform specific tweaks
@@ -310,14 +336,14 @@ ifeq ($(uname_S),Darwin)
NO_STRLCPY = YesPlease
ifndef NO_FINK
ifeq ($(shell test -d /sw/lib && echo y),y)
- ALL_CFLAGS += -I/sw/include
- ALL_LDFLAGS += -L/sw/lib
+ BASIC_CFLAGS += -I/sw/include
+ BASIC_LDFLAGS += -L/sw/lib
endif
endif
ifndef NO_DARWIN_PORTS
ifeq ($(shell test -d /opt/local/lib && echo y),y)
- ALL_CFLAGS += -I/opt/local/include
- ALL_LDFLAGS += -L/opt/local/lib
+ BASIC_CFLAGS += -I/opt/local/include
+ BASIC_LDFLAGS += -L/opt/local/lib
endif
endif
endif
@@ -326,19 +352,20 @@ ifeq ($(uname_S),SunOS)
NEEDS_NSL = YesPlease
SHELL_PATH = /bin/bash
NO_STRCASESTR = YesPlease
- NO_STRLCPY = YesPlease
ifeq ($(uname_R),5.8)
NEEDS_LIBICONV = YesPlease
NO_UNSETENV = YesPlease
NO_SETENV = YesPlease
+ NO_C99_FORMAT = YesPlease
endif
ifeq ($(uname_R),5.9)
NO_UNSETENV = YesPlease
NO_SETENV = YesPlease
+ NO_C99_FORMAT = YesPlease
endif
INSTALL = ginstall
TAR = gtar
- ALL_CFLAGS += -D__EXTENSIONS__
+ BASIC_CFLAGS += -D__EXTENSIONS__
endif
ifeq ($(uname_O),Cygwin)
NO_D_TYPE_IN_DIRENT = YesPlease
@@ -356,21 +383,22 @@ ifeq ($(uname_O),Cygwin)
endif
ifeq ($(uname_S),FreeBSD)
NEEDS_LIBICONV = YesPlease
- ALL_CFLAGS += -I/usr/local/include
- ALL_LDFLAGS += -L/usr/local/lib
+ BASIC_CFLAGS += -I/usr/local/include
+ BASIC_LDFLAGS += -L/usr/local/lib
endif
ifeq ($(uname_S),OpenBSD)
NO_STRCASESTR = YesPlease
NEEDS_LIBICONV = YesPlease
- ALL_CFLAGS += -I/usr/local/include
- ALL_LDFLAGS += -L/usr/local/lib
+ BASIC_CFLAGS += -I/usr/local/include
+ BASIC_LDFLAGS += -L/usr/local/lib
endif
ifeq ($(uname_S),NetBSD)
ifeq ($(shell expr "$(uname_R)" : '[01]\.'),2)
NEEDS_LIBICONV = YesPlease
endif
- ALL_CFLAGS += -I/usr/pkg/include
- ALL_LDFLAGS += -L/usr/pkg/lib -Wl,-rpath,/usr/pkg/lib
+ BASIC_CFLAGS += -I/usr/pkg/include
+ BASIC_LDFLAGS += -L/usr/pkg/lib
+ ALL_LDFLAGS += -Wl,-rpath,/usr/pkg/lib
endif
ifeq ($(uname_S),AIX)
NO_STRCASESTR=YesPlease
@@ -384,9 +412,9 @@ ifeq ($(uname_S),IRIX64)
NO_STRLCPY = YesPlease
NO_SOCKADDR_STORAGE=YesPlease
SHELL_PATH=/usr/gnu/bin/bash
- ALL_CFLAGS += -DPATH_MAX=1024
+ BASIC_CFLAGS += -DPATH_MAX=1024
# for now, build 32-bit version
- ALL_LDFLAGS += -L/usr/lib32
+ BASIC_LDFLAGS += -L/usr/lib32
endif
ifneq (,$(findstring arm,$(uname_M)))
ARM_SHA1 = YesPlease
@@ -408,7 +436,7 @@ endif
ifndef NO_CURL
ifdef CURLDIR
# This is still problematic -- gcc does not always want -R.
- ALL_CFLAGS += -I$(CURLDIR)/include
+ BASIC_CFLAGS += -I$(CURLDIR)/include
CURL_LIBCURL = -L$(CURLDIR)/lib -R$(CURLDIR)/lib -lcurl
else
CURL_LIBCURL = -lcurl
@@ -429,13 +457,13 @@ ifndef NO_OPENSSL
OPENSSL_LIBSSL = -lssl
ifdef OPENSSLDIR
# Again this may be problematic -- gcc does not always want -R.
- ALL_CFLAGS += -I$(OPENSSLDIR)/include
+ BASIC_CFLAGS += -I$(OPENSSLDIR)/include
OPENSSL_LINK = -L$(OPENSSLDIR)/lib -R$(OPENSSLDIR)/lib
else
OPENSSL_LINK =
endif
else
- ALL_CFLAGS += -DNO_OPENSSL
+ BASIC_CFLAGS += -DNO_OPENSSL
MOZILLA_SHA1 = 1
OPENSSL_LIBSSL =
endif
@@ -447,32 +475,30 @@ endif
ifdef NEEDS_LIBICONV
ifdef ICONVDIR
# Again this may be problematic -- gcc does not always want -R.
- ALL_CFLAGS += -I$(ICONVDIR)/include
+ BASIC_CFLAGS += -I$(ICONVDIR)/include
ICONV_LINK = -L$(ICONVDIR)/lib -R$(ICONVDIR)/lib
else
ICONV_LINK =
endif
- LIBS += $(ICONV_LINK) -liconv
+ EXTLIBS += $(ICONV_LINK) -liconv
endif
ifdef NEEDS_SOCKET
- LIBS += -lsocket
- SIMPLE_LIB += -lsocket
+ EXTLIBS += -lsocket
endif
ifdef NEEDS_NSL
- LIBS += -lnsl
- SIMPLE_LIB += -lnsl
+ EXTLIBS += -lnsl
endif
ifdef NO_D_TYPE_IN_DIRENT
- ALL_CFLAGS += -DNO_D_TYPE_IN_DIRENT
+ BASIC_CFLAGS += -DNO_D_TYPE_IN_DIRENT
endif
ifdef NO_D_INO_IN_DIRENT
- ALL_CFLAGS += -DNO_D_INO_IN_DIRENT
+ BASIC_CFLAGS += -DNO_D_INO_IN_DIRENT
endif
ifdef NO_C99_FORMAT
ALL_CFLAGS += -DNO_C99_FORMAT
endif
ifdef NO_SYMLINK_HEAD
- ALL_CFLAGS += -DNO_SYMLINK_HEAD
+ BASIC_CFLAGS += -DNO_SYMLINK_HEAD
endif
ifdef NO_STRCASESTR
COMPAT_CFLAGS += -DNO_STRCASESTR
@@ -486,7 +512,7 @@ ifdef NO_SETENV
COMPAT_CFLAGS += -DNO_SETENV
COMPAT_OBJS += compat/setenv.o
endif
-ifdef NO_SETENV
+ifdef NO_UNSETENV
COMPAT_CFLAGS += -DNO_UNSETENV
COMPAT_OBJS += compat/unsetenv.o
endif
@@ -495,21 +521,24 @@ ifdef NO_MMAP
COMPAT_OBJS += compat/mmap.o
endif
ifdef NO_IPV6
- ALL_CFLAGS += -DNO_IPV6
+ BASIC_CFLAGS += -DNO_IPV6
endif
ifdef NO_SOCKADDR_STORAGE
ifdef NO_IPV6
- ALL_CFLAGS += -Dsockaddr_storage=sockaddr_in
+ BASIC_CFLAGS += -Dsockaddr_storage=sockaddr_in
else
- ALL_CFLAGS += -Dsockaddr_storage=sockaddr_in6
+ BASIC_CFLAGS += -Dsockaddr_storage=sockaddr_in6
endif
endif
ifdef NO_INET_NTOP
LIB_OBJS += compat/inet_ntop.o
endif
+ifdef NO_INET_PTON
+ LIB_OBJS += compat/inet_pton.o
+endif
ifdef NO_ICONV
- ALL_CFLAGS += -DNO_ICONV
+ BASIC_CFLAGS += -DNO_ICONV
endif
ifdef PPC_SHA1
@@ -525,12 +554,12 @@ ifdef MOZILLA_SHA1
LIB_OBJS += mozilla-sha1/sha1.o
else
SHA1_HEADER = <openssl/sha.h>
- LIBS += $(LIB_4_CRYPTO)
+ EXTLIBS += $(LIB_4_CRYPTO)
endif
endif
endif
ifdef NO_ACCURATE_DIFF
- ALL_CFLAGS += -DNO_ACCURATE_DIFF
+ BASIC_CFLAGS += -DNO_ACCURATE_DIFF
endif
# Shell quote (do not use $(call) to accommodate ancient setups);
@@ -548,14 +577,23 @@ PERL_PATH_SQ = $(subst ','\'',$(PERL_PATH))
PYTHON_PATH_SQ = $(subst ','\'',$(PYTHON_PATH))
GIT_PYTHON_DIR_SQ = $(subst ','\'',$(GIT_PYTHON_DIR))
-ALL_CFLAGS += -DSHA1_HEADER='$(SHA1_HEADER_SQ)' $(COMPAT_CFLAGS)
+LIBS = $(GITLIBS) $(EXTLIBS)
+
+BASIC_CFLAGS += -DSHA1_HEADER='$(SHA1_HEADER_SQ)' $(COMPAT_CFLAGS)
LIB_OBJS += $(COMPAT_OBJS)
+
+ALL_CFLAGS += $(BASIC_CFLAGS)
+ALL_LDFLAGS += $(BASIC_LDFLAGS)
+
export prefix TAR INSTALL DESTDIR SHELL_PATH template_dir
+
+
### Build rules
all: $(ALL_PROGRAMS) $(BUILT_INS) git$X gitk gitweb/gitweb.cgi
-all:
+all: perl/Makefile
+ $(MAKE) -C perl
$(MAKE) -C templates
strip: $(PROGRAMS) git$X
@@ -568,6 +606,9 @@ git$X: git.c common-cmds.h $(BUILTIN_OBJS) $(GITLIBS) GIT-CFLAGS
help.o: common-cmds.h
+git-merge-recur$X: git-merge-recursive$X
+ rm -f $@ && ln git-merge-recursive$X $@
+
$(BUILT_INS): git$X
rm -f $@ && ln git$X $@
@@ -586,9 +627,18 @@ $(patsubst %.sh,%,$(SCRIPT_SH)) : % : %.sh
chmod +x $@+
mv $@+ $@
-$(patsubst %.perl,%,$(SCRIPT_PERL)) : % : %.perl
+$(patsubst %.perl,%,$(SCRIPT_PERL)): perl/Makefile
+$(patsubst %.perl,%,$(SCRIPT_PERL)): % : %.perl
rm -f $@ $@+
- sed -e '1s|#!.*perl|#!$(PERL_PATH_SQ)|' \
+ INSTLIBDIR=`$(MAKE) -C perl -s --no-print-directory instlibdir` && \
+ sed -e '1{' \
+ -e ' s|#!.*perl|#!$(PERL_PATH_SQ)|' \
+ -e ' h' \
+ -e ' s=.*=use lib (split(/:/, $$ENV{GITPERLLIB} || "@@INSTLIBDIR@@"));=' \
+ -e ' H' \
+ -e ' x' \
+ -e '}' \
+ -e 's|@@INSTLIBDIR@@|'"$$INSTLIBDIR"'|g' \
-e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \
$@.perl >$@+
chmod +x $@+
@@ -617,12 +667,19 @@ gitweb/gitweb.cgi: gitweb/gitweb.perl
-e 's|++GIT_VERSION++|$(GIT_VERSION)|g' \
-e 's|++GIT_BINDIR++|$(bindir)|g' \
-e 's|++GITWEB_CONFIG++|$(GITWEB_CONFIG)|g' \
+ -e 's|++GITWEB_HOME_LINK_STR++|$(GITWEB_HOME_LINK_STR)|g' \
-e 's|++GITWEB_SITENAME++|$(GITWEB_SITENAME)|g' \
-e 's|++GITWEB_PROJECTROOT++|$(GITWEB_PROJECTROOT)|g' \
+ -e 's|++GITWEB_EXPORT_OK++|$(GITWEB_EXPORT_OK)|g' \
+ -e 's|++GITWEB_STRICT_EXPORT++|$(GITWEB_STRICT_EXPORT)|g' \
+ -e 's|++GITWEB_BASE_URL++|$(GITWEB_BASE_URL)|g' \
-e 's|++GITWEB_LIST++|$(GITWEB_LIST)|g' \
-e 's|++GITWEB_HOMETEXT++|$(GITWEB_HOMETEXT)|g' \
-e 's|++GITWEB_CSS++|$(GITWEB_CSS)|g' \
-e 's|++GITWEB_LOGO++|$(GITWEB_LOGO)|g' \
+ -e 's|++GITWEB_FAVICON++|$(GITWEB_FAVICON)|g' \
+ -e 's|++GITWEB_SITE_HEADER++|$(GITWEB_SITE_HEADER)|g' \
+ -e 's|++GITWEB_SITE_FOOTER++|$(GITWEB_SITE_FOOTER)|g' \
$< >$@+
chmod +x $@+
mv $@+ $@
@@ -676,11 +733,6 @@ endif
git-%$X: %.o $(GITLIBS)
$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(LIBS)
-$(SIMPLE_PROGRAMS) : $(LIB_FILE)
-$(SIMPLE_PROGRAMS) : git-%$X : %.o
- $(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \
- $(LIB_FILE) $(SIMPLE_LIB)
-
ssh-pull.o: ssh-fetch.c
ssh-push.o: ssh-upload.c
git-local-fetch$X: fetch.o
@@ -708,11 +760,17 @@ $(LIB_FILE): $(LIB_OBJS)
rm -f $@ && $(AR) rcs $@ $(LIB_OBJS)
XDIFF_OBJS=xdiff/xdiffi.o xdiff/xprepare.o xdiff/xutils.o xdiff/xemit.o
+$(XDIFF_OBJS): xdiff/xinclude.h xdiff/xmacros.h xdiff/xdiff.h xdiff/xtypes.h \
+ xdiff/xutils.h xdiff/xprepare.h xdiff/xdiffi.h xdiff/xemit.h
$(XDIFF_LIB): $(XDIFF_OBJS)
rm -f $@ && $(AR) rcs $@ $(XDIFF_OBJS)
+perl/Makefile: perl/Git.pm perl/Makefile.PL GIT-CFLAGS
+ (cd perl && $(PERL_PATH) Makefile.PL \
+ PREFIX='$(prefix_SQ)')
+
doc:
$(MAKE) -C Documentation all
@@ -775,6 +833,7 @@ install: all
$(INSTALL) $(ALL_PROGRAMS) '$(DESTDIR_SQ)$(gitexecdir_SQ)'
$(INSTALL) git$X gitk '$(DESTDIR_SQ)$(bindir_SQ)'
$(MAKE) -C templates DESTDIR='$(DESTDIR_SQ)' install
+ $(MAKE) -C perl install
$(INSTALL) -d -m755 '$(DESTDIR_SQ)$(GIT_PYTHON_DIR_SQ)'
$(INSTALL) $(PYMODULES) '$(DESTDIR_SQ)$(GIT_PYTHON_DIR_SQ)'
if test 'z$(bindir_SQ)' != 'z$(gitexecdir_SQ)'; \
@@ -799,8 +858,9 @@ git.spec: git.spec.in
mv $@+ $@
GIT_TARNAME=git-$(GIT_VERSION)
-dist: git.spec git-tar-tree
- ./git-tar-tree HEAD^{tree} $(GIT_TARNAME) > $(GIT_TARNAME).tar
+dist: git.spec git-archive
+ ./git-archive --format=tar \
+ --prefix=$(GIT_TARNAME)/ HEAD^{tree} > $(GIT_TARNAME).tar
@mkdir -p $(GIT_TARNAME)
@cp git.spec $(GIT_TARNAME)
@echo $(GIT_VERSION) > $(GIT_TARNAME)/version
@@ -845,7 +905,9 @@ clean:
rm -f $(htmldocs).tar.gz $(manpages).tar.gz
rm -f gitweb/gitweb.cgi
$(MAKE) -C Documentation/ clean
- $(MAKE) -C templates clean
+ [ ! -f perl/Makefile ] || $(MAKE) -C perl/ clean || $(MAKE) -C perl/ clean
+ rm -f perl/ppport.h perl/Makefile.old
+ $(MAKE) -C templates/ clean
$(MAKE) -C t/ clean
rm -f GIT-VERSION-FILE GIT-CFLAGS
@@ -859,7 +921,8 @@ check-docs::
do \
case "$$v" in \
git-merge-octopus | git-merge-ours | git-merge-recursive | \
- git-merge-resolve | git-merge-stupid | \
+ git-merge-resolve | git-merge-stupid | git-merge-recur | \
+ git-merge-recursive-old | \
git-ssh-pull | git-ssh-push ) continue ;; \
esac ; \
test -f "Documentation/$$v.txt" || \
@@ -870,3 +933,8 @@ check-docs::
*) echo "no link: $$v";; \
esac ; \
done | sort
+
+### Make sure built-ins do not have dups and listed in git.c
+#
+check-builtins::
+ ./check-builtins.sh
diff --git a/archive-tar.c b/archive-tar.c
new file mode 100644
index 0000000000..ff0f6e2929
--- /dev/null
+++ b/archive-tar.c
@@ -0,0 +1,325 @@
+/*
+ * Copyright (c) 2005, 2006 Rene Scharfe
+ */
+#include <time.h>
+#include "cache.h"
+#include "commit.h"
+#include "strbuf.h"
+#include "tar.h"
+#include "builtin.h"
+#include "archive.h"
+
+#define RECORDSIZE (512)
+#define BLOCKSIZE (RECORDSIZE * 20)
+
+static char block[BLOCKSIZE];
+static unsigned long offset;
+
+static time_t archive_time;
+static int tar_umask;
+static int verbose;
+
+/* writes out the whole block, but only if it is full */
+static void write_if_needed(void)
+{
+ if (offset == BLOCKSIZE) {
+ write_or_die(1, block, BLOCKSIZE);
+ offset = 0;
+ }
+}
+
+/*
+ * queues up writes, so that all our write(2) calls write exactly one
+ * full block; pads writes to RECORDSIZE
+ */
+static void write_blocked(const void *data, unsigned long size)
+{
+ const char *buf = data;
+ unsigned long tail;
+
+ if (offset) {
+ unsigned long chunk = BLOCKSIZE - offset;
+ if (size < chunk)
+ chunk = size;
+ memcpy(block + offset, buf, chunk);
+ size -= chunk;
+ offset += chunk;
+ buf += chunk;
+ write_if_needed();
+ }
+ while (size >= BLOCKSIZE) {
+ write_or_die(1, buf, BLOCKSIZE);
+ size -= BLOCKSIZE;
+ buf += BLOCKSIZE;
+ }
+ if (size) {
+ memcpy(block + offset, buf, size);
+ offset += size;
+ }
+ tail = offset % RECORDSIZE;
+ if (tail) {
+ memset(block + offset, 0, RECORDSIZE - tail);
+ offset += RECORDSIZE - tail;
+ }
+ write_if_needed();
+}
+
+/*
+ * The end of tar archives is marked by 2*512 nul bytes and after that
+ * follows the rest of the block (if any).
+ */
+static void write_trailer(void)
+{
+ int tail = BLOCKSIZE - offset;
+ memset(block + offset, 0, tail);
+ write_or_die(1, block, BLOCKSIZE);
+ if (tail < 2 * RECORDSIZE) {
+ memset(block, 0, offset);
+ write_or_die(1, block, BLOCKSIZE);
+ }
+}
+
+static void strbuf_append_string(struct strbuf *sb, const char *s)
+{
+ int slen = strlen(s);
+ int total = sb->len + slen;
+ if (total > sb->alloc) {
+ sb->buf = xrealloc(sb->buf, total);
+ sb->alloc = total;
+ }
+ memcpy(sb->buf + sb->len, s, slen);
+ sb->len = total;
+}
+
+/*
+ * pax extended header records have the format "%u %s=%s\n". %u contains
+ * the size of the whole string (including the %u), the first %s is the
+ * keyword, the second one is the value. This function constructs such a
+ * string and appends it to a struct strbuf.
+ */
+static void strbuf_append_ext_header(struct strbuf *sb, const char *keyword,
+ const char *value, unsigned int valuelen)
+{
+ char *p;
+ int len, total, tmp;
+
+ /* "%u %s=%s\n" */
+ len = 1 + 1 + strlen(keyword) + 1 + valuelen + 1;
+ for (tmp = len; tmp > 9; tmp /= 10)
+ len++;
+
+ total = sb->len + len;
+ if (total > sb->alloc) {
+ sb->buf = xrealloc(sb->buf, total);
+ sb->alloc = total;
+ }
+
+ p = sb->buf;
+ p += sprintf(p, "%u %s=", len, keyword);
+ memcpy(p, value, valuelen);
+ p += valuelen;
+ *p = '\n';
+ sb->len = total;
+}
+
+static unsigned int ustar_header_chksum(const struct ustar_header *header)
+{
+ char *p = (char *)header;
+ unsigned int chksum = 0;
+ while (p < header->chksum)
+ chksum += *p++;
+ chksum += sizeof(header->chksum) * ' ';
+ p += sizeof(header->chksum);
+ while (p < (char *)header + sizeof(struct ustar_header))
+ chksum += *p++;
+ return chksum;
+}
+
+static int get_path_prefix(const struct strbuf *path, int maxlen)
+{
+ int i = path->len;
+ if (i > maxlen)
+ i = maxlen;
+ do {
+ i--;
+ } while (i > 0 && path->buf[i] != '/');
+ return i;
+}
+
+static void write_entry(const unsigned char *sha1, struct strbuf *path,
+ unsigned int mode, void *buffer, unsigned long size)
+{
+ struct ustar_header header;
+ struct strbuf ext_header;
+
+ memset(&header, 0, sizeof(header));
+ ext_header.buf = NULL;
+ ext_header.len = ext_header.alloc = 0;
+
+ if (!sha1) {
+ *header.typeflag = TYPEFLAG_GLOBAL_HEADER;
+ mode = 0100666;
+ strcpy(header.name, "pax_global_header");
+ } else if (!path) {
+ *header.typeflag = TYPEFLAG_EXT_HEADER;
+ mode = 0100666;
+ sprintf(header.name, "%s.paxheader", sha1_to_hex(sha1));
+ } else {
+ if (verbose)
+ fprintf(stderr, "%.*s\n", path->len, path->buf);
+ if (S_ISDIR(mode)) {
+ *header.typeflag = TYPEFLAG_DIR;
+ mode = (mode | 0777) & ~tar_umask;
+ } else if (S_ISLNK(mode)) {
+ *header.typeflag = TYPEFLAG_LNK;
+ mode |= 0777;
+ } else if (S_ISREG(mode)) {
+ *header.typeflag = TYPEFLAG_REG;
+ mode = (mode | ((mode & 0100) ? 0777 : 0666)) & ~tar_umask;
+ } else {
+ error("unsupported file mode: 0%o (SHA1: %s)",
+ mode, sha1_to_hex(sha1));
+ return;
+ }
+ if (path->len > sizeof(header.name)) {
+ int plen = get_path_prefix(path, sizeof(header.prefix));
+ int rest = path->len - plen - 1;
+ if (plen > 0 && rest <= sizeof(header.name)) {
+ memcpy(header.prefix, path->buf, plen);
+ memcpy(header.name, path->buf + plen + 1, rest);
+ } else {
+ sprintf(header.name, "%s.data",
+ sha1_to_hex(sha1));
+ strbuf_append_ext_header(&ext_header, "path",
+ path->buf, path->len);
+ }
+ } else
+ memcpy(header.name, path->buf, path->len);
+ }
+
+ if (S_ISLNK(mode) && buffer) {
+ if (size > sizeof(header.linkname)) {
+ sprintf(header.linkname, "see %s.paxheader",
+ sha1_to_hex(sha1));
+ strbuf_append_ext_header(&ext_header, "linkpath",
+ buffer, size);
+ } else
+ memcpy(header.linkname, buffer, size);
+ }
+
+ sprintf(header.mode, "%07o", mode & 07777);
+ sprintf(header.size, "%011lo", S_ISREG(mode) ? size : 0);
+ sprintf(header.mtime, "%011lo", archive_time);
+
+ /* XXX: should we provide more meaningful info here? */
+ sprintf(header.uid, "%07o", 0);
+ sprintf(header.gid, "%07o", 0);
+ strlcpy(header.uname, "git", sizeof(header.uname));
+ strlcpy(header.gname, "git", sizeof(header.gname));
+ sprintf(header.devmajor, "%07o", 0);
+ sprintf(header.devminor, "%07o", 0);
+
+ memcpy(header.magic, "ustar", 6);
+ memcpy(header.version, "00", 2);
+
+ sprintf(header.chksum, "%07o", ustar_header_chksum(&header));
+
+ if (ext_header.len > 0) {
+ write_entry(sha1, NULL, 0, ext_header.buf, ext_header.len);
+ free(ext_header.buf);
+ }
+ write_blocked(&header, sizeof(header));
+ if (S_ISREG(mode) && buffer && size > 0)
+ write_blocked(buffer, size);
+}
+
+static void write_global_extended_header(const unsigned char *sha1)
+{
+ struct strbuf ext_header;
+ ext_header.buf = NULL;
+ ext_header.len = ext_header.alloc = 0;
+ strbuf_append_ext_header(&ext_header, "comment", sha1_to_hex(sha1), 40);
+ write_entry(NULL, NULL, 0, ext_header.buf, ext_header.len);
+ free(ext_header.buf);
+}
+
+static int git_tar_config(const char *var, const char *value)
+{
+ if (!strcmp(var, "tar.umask")) {
+ if (!strcmp(value, "user")) {
+ tar_umask = umask(0);
+ umask(tar_umask);
+ } else {
+ tar_umask = git_config_int(var, value);
+ }
+ return 0;
+ }
+ return git_default_config(var, value);
+}
+
+static int write_tar_entry(const unsigned char *sha1,
+ const char *base, int baselen,
+ const char *filename, unsigned mode, int stage)
+{
+ static struct strbuf path;
+ int filenamelen = strlen(filename);
+ void *buffer;
+ char type[20];
+ unsigned long size;
+
+ if (!path.alloc) {
+ path.buf = xmalloc(PATH_MAX);
+ path.alloc = PATH_MAX;
+ path.len = path.eof = 0;
+ }
+ if (path.alloc < baselen + filenamelen) {
+ free(path.buf);
+ path.buf = xmalloc(baselen + filenamelen);
+ path.alloc = baselen + filenamelen;
+ }
+ memcpy(path.buf, base, baselen);
+ memcpy(path.buf + baselen, filename, filenamelen);
+ path.len = baselen + filenamelen;
+ if (S_ISDIR(mode)) {
+ strbuf_append_string(&path, "/");
+ buffer = NULL;
+ size = 0;
+ } else {
+ buffer = read_sha1_file(sha1, type, &size);
+ if (!buffer)
+ die("cannot read %s", sha1_to_hex(sha1));
+ }
+
+ write_entry(sha1, &path, mode, buffer, size);
+ free(buffer);
+
+ return READ_TREE_RECURSIVE;
+}
+
+int write_tar_archive(struct archiver_args *args)
+{
+ int plen = args->base ? strlen(args->base) : 0;
+
+ git_config(git_tar_config);
+
+ archive_time = args->time;
+ verbose = args->verbose;
+
+ if (args->commit_sha1)
+ write_global_extended_header(args->commit_sha1);
+
+ if (args->base && plen > 0 && args->base[plen - 1] == '/') {
+ char *base = xstrdup(args->base);
+ int baselen = strlen(base);
+
+ while (baselen > 0 && base[baselen - 1] == '/')
+ base[--baselen] = '\0';
+ write_tar_entry(args->tree->object.sha1, "", 0, base, 040777, 0);
+ free(base);
+ }
+ read_tree_recursive(args->tree, args->base, plen, 0,
+ args->pathspec, write_tar_entry);
+ write_trailer();
+
+ return 0;
+}
diff --git a/archive-zip.c b/archive-zip.c
new file mode 100644
index 0000000000..36e922a1f2
--- /dev/null
+++ b/archive-zip.c
@@ -0,0 +1,350 @@
+/*
+ * Copyright (c) 2006 Rene Scharfe
+ */
+#include <time.h>
+#include "cache.h"
+#include "commit.h"
+#include "blob.h"
+#include "tree.h"
+#include "quote.h"
+#include "builtin.h"
+#include "archive.h"
+
+static int verbose;
+static int zip_date;
+static int zip_time;
+
+static unsigned char *zip_dir;
+static unsigned int zip_dir_size;
+
+static unsigned int zip_offset;
+static unsigned int zip_dir_offset;
+static unsigned int zip_dir_entries;
+
+#define ZIP_DIRECTORY_MIN_SIZE (1024 * 1024)
+
+struct zip_local_header {
+ unsigned char magic[4];
+ unsigned char version[2];
+ unsigned char flags[2];
+ unsigned char compression_method[2];
+ unsigned char mtime[2];
+ unsigned char mdate[2];
+ unsigned char crc32[4];
+ unsigned char compressed_size[4];
+ unsigned char size[4];
+ unsigned char filename_length[2];
+ unsigned char extra_length[2];
+ unsigned char _end[1];
+};
+
+struct zip_dir_header {
+ unsigned char magic[4];
+ unsigned char creator_version[2];
+ unsigned char version[2];
+ unsigned char flags[2];
+ unsigned char compression_method[2];
+ unsigned char mtime[2];
+ unsigned char mdate[2];
+ unsigned char crc32[4];
+ unsigned char compressed_size[4];
+ unsigned char size[4];
+ unsigned char filename_length[2];
+ unsigned char extra_length[2];
+ unsigned char comment_length[2];
+ unsigned char disk[2];
+ unsigned char attr1[2];
+ unsigned char attr2[4];
+ unsigned char offset[4];
+ unsigned char _end[1];
+};
+
+struct zip_dir_trailer {
+ unsigned char magic[4];
+ unsigned char disk[2];
+ unsigned char directory_start_disk[2];
+ unsigned char entries_on_this_disk[2];
+ unsigned char entries[2];
+ unsigned char size[4];
+ unsigned char offset[4];
+ unsigned char comment_length[2];
+ unsigned char _end[1];
+};
+
+/*
+ * On ARM, padding is added at the end of the struct, so a simple
+ * sizeof(struct ...) reports two bytes more than the payload size
+ * we're interested in.
+ */
+#define ZIP_LOCAL_HEADER_SIZE offsetof(struct zip_local_header, _end)
+#define ZIP_DIR_HEADER_SIZE offsetof(struct zip_dir_header, _end)
+#define ZIP_DIR_TRAILER_SIZE offsetof(struct zip_dir_trailer, _end)
+
+static void copy_le16(unsigned char *dest, unsigned int n)
+{
+ dest[0] = 0xff & n;
+ dest[1] = 0xff & (n >> 010);
+}
+
+static void copy_le32(unsigned char *dest, unsigned int n)
+{
+ dest[0] = 0xff & n;
+ dest[1] = 0xff & (n >> 010);
+ dest[2] = 0xff & (n >> 020);
+ dest[3] = 0xff & (n >> 030);
+}
+
+static void *zlib_deflate(void *data, unsigned long size,
+ unsigned long *compressed_size)
+{
+ z_stream stream;
+ unsigned long maxsize;
+ void *buffer;
+ int result;
+
+ memset(&stream, 0, sizeof(stream));
+ deflateInit(&stream, zlib_compression_level);
+ maxsize = deflateBound(&stream, size);
+ buffer = xmalloc(maxsize);
+
+ stream.next_in = data;
+ stream.avail_in = size;
+ stream.next_out = buffer;
+ stream.avail_out = maxsize;
+
+ do {
+ result = deflate(&stream, Z_FINISH);
+ } while (result == Z_OK);
+
+ if (result != Z_STREAM_END) {
+ free(buffer);
+ return NULL;
+ }
+
+ deflateEnd(&stream);
+ *compressed_size = stream.total_out;
+
+ return buffer;
+}
+
+static char *construct_path(const char *base, int baselen,
+ const char *filename, int isdir, int *pathlen)
+{
+ int filenamelen = strlen(filename);
+ int len = baselen + filenamelen;
+ char *path, *p;
+
+ if (isdir)
+ len++;
+ p = path = xmalloc(len + 1);
+
+ memcpy(p, base, baselen);
+ p += baselen;
+ memcpy(p, filename, filenamelen);
+ p += filenamelen;
+ if (isdir)
+ *p++ = '/';
+ *p = '\0';
+
+ *pathlen = len;
+
+ return path;
+}
+
+static int write_zip_entry(const unsigned char *sha1,
+ const char *base, int baselen,
+ const char *filename, unsigned mode, int stage)
+{
+ struct zip_local_header header;
+ struct zip_dir_header dirent;
+ unsigned long attr2;
+ unsigned long compressed_size;
+ unsigned long uncompressed_size;
+ unsigned long crc;
+ unsigned long direntsize;
+ unsigned long size;
+ int method;
+ int result = -1;
+ int pathlen;
+ unsigned char *out;
+ char *path;
+ char type[20];
+ void *buffer = NULL;
+ void *deflated = NULL;
+
+ crc = crc32(0, NULL, 0);
+
+ path = construct_path(base, baselen, filename, S_ISDIR(mode), &pathlen);
+ if (verbose)
+ fprintf(stderr, "%s\n", path);
+ if (pathlen > 0xffff) {
+ error("path too long (%d chars, SHA1: %s): %s", pathlen,
+ sha1_to_hex(sha1), path);
+ goto out;
+ }
+
+ if (S_ISDIR(mode)) {
+ method = 0;
+ attr2 = 16;
+ result = READ_TREE_RECURSIVE;
+ out = NULL;
+ uncompressed_size = 0;
+ compressed_size = 0;
+ } else if (S_ISREG(mode) || S_ISLNK(mode)) {
+ method = 0;
+ attr2 = S_ISLNK(mode) ? ((mode | 0777) << 16) : 0;
+ if (S_ISREG(mode) && zlib_compression_level != 0)
+ method = 8;
+ result = 0;
+ buffer = read_sha1_file(sha1, type, &size);
+ if (!buffer)
+ die("cannot read %s", sha1_to_hex(sha1));
+ crc = crc32(crc, buffer, size);
+ out = buffer;
+ uncompressed_size = size;
+ compressed_size = size;
+ } else {
+ error("unsupported file mode: 0%o (SHA1: %s)", mode,
+ sha1_to_hex(sha1));
+ goto out;
+ }
+
+ if (method == 8) {
+ deflated = zlib_deflate(buffer, size, &compressed_size);
+ if (deflated && compressed_size - 6 < size) {
+ /* ZLIB --> raw compressed data (see RFC 1950) */
+ /* CMF and FLG ... */
+ out = (unsigned char *)deflated + 2;
+ compressed_size -= 6; /* ... and ADLER32 */
+ } else {
+ method = 0;
+ compressed_size = size;
+ }
+ }
+
+ /* make sure we have enough free space in the dictionary */
+ direntsize = ZIP_DIR_HEADER_SIZE + pathlen;
+ while (zip_dir_size < zip_dir_offset + direntsize) {
+ zip_dir_size += ZIP_DIRECTORY_MIN_SIZE;
+ zip_dir = xrealloc(zip_dir, zip_dir_size);
+ }
+
+ copy_le32(dirent.magic, 0x02014b50);
+ copy_le16(dirent.creator_version, S_ISLNK(mode) ? 0x0317 : 0);
+ copy_le16(dirent.version, 10);
+ copy_le16(dirent.flags, 0);
+ copy_le16(dirent.compression_method, method);
+ copy_le16(dirent.mtime, zip_time);
+ copy_le16(dirent.mdate, zip_date);
+ copy_le32(dirent.crc32, crc);
+ copy_le32(dirent.compressed_size, compressed_size);
+ copy_le32(dirent.size, uncompressed_size);
+ copy_le16(dirent.filename_length, pathlen);
+ copy_le16(dirent.extra_length, 0);
+ copy_le16(dirent.comment_length, 0);
+ copy_le16(dirent.disk, 0);
+ copy_le16(dirent.attr1, 0);
+ copy_le32(dirent.attr2, attr2);
+ copy_le32(dirent.offset, zip_offset);
+ memcpy(zip_dir + zip_dir_offset, &dirent, ZIP_DIR_HEADER_SIZE);
+ zip_dir_offset += ZIP_DIR_HEADER_SIZE;
+ memcpy(zip_dir + zip_dir_offset, path, pathlen);
+ zip_dir_offset += pathlen;
+ zip_dir_entries++;
+
+ copy_le32(header.magic, 0x04034b50);
+ copy_le16(header.version, 10);
+ copy_le16(header.flags, 0);
+ copy_le16(header.compression_method, method);
+ copy_le16(header.mtime, zip_time);
+ copy_le16(header.mdate, zip_date);
+ copy_le32(header.crc32, crc);
+ copy_le32(header.compressed_size, compressed_size);
+ copy_le32(header.size, uncompressed_size);
+ copy_le16(header.filename_length, pathlen);
+ copy_le16(header.extra_length, 0);
+ write_or_die(1, &header, ZIP_LOCAL_HEADER_SIZE);
+ zip_offset += ZIP_LOCAL_HEADER_SIZE;
+ write_or_die(1, path, pathlen);
+ zip_offset += pathlen;
+ if (compressed_size > 0) {
+ write_or_die(1, out, compressed_size);
+ zip_offset += compressed_size;
+ }
+
+out:
+ free(buffer);
+ free(deflated);
+ free(path);
+
+ return result;
+}
+
+static void write_zip_trailer(const unsigned char *sha1)
+{
+ struct zip_dir_trailer trailer;
+
+ copy_le32(trailer.magic, 0x06054b50);
+ copy_le16(trailer.disk, 0);
+ copy_le16(trailer.directory_start_disk, 0);
+ copy_le16(trailer.entries_on_this_disk, zip_dir_entries);
+ copy_le16(trailer.entries, zip_dir_entries);
+ copy_le32(trailer.size, zip_dir_offset);
+ copy_le32(trailer.offset, zip_offset);
+ copy_le16(trailer.comment_length, sha1 ? 40 : 0);
+
+ write_or_die(1, zip_dir, zip_dir_offset);
+ write_or_die(1, &trailer, ZIP_DIR_TRAILER_SIZE);
+ if (sha1)
+ write_or_die(1, sha1_to_hex(sha1), 40);
+}
+
+static void dos_time(time_t *time, int *dos_date, int *dos_time)
+{
+ struct tm *t = localtime(time);
+
+ *dos_date = t->tm_mday + (t->tm_mon + 1) * 32 +
+ (t->tm_year + 1900 - 1980) * 512;
+ *dos_time = t->tm_sec / 2 + t->tm_min * 32 + t->tm_hour * 2048;
+}
+
+int write_zip_archive(struct archiver_args *args)
+{
+ int plen = strlen(args->base);
+
+ dos_time(&args->time, &zip_date, &zip_time);
+
+ zip_dir = xmalloc(ZIP_DIRECTORY_MIN_SIZE);
+ zip_dir_size = ZIP_DIRECTORY_MIN_SIZE;
+ verbose = args->verbose;
+
+ if (args->base && plen > 0 && args->base[plen - 1] == '/') {
+ char *base = xstrdup(args->base);
+ int baselen = strlen(base);
+
+ while (baselen > 0 && base[baselen - 1] == '/')
+ base[--baselen] = '\0';
+ write_zip_entry(args->tree->object.sha1, "", 0, base, 040777, 0);
+ free(base);
+ }
+ read_tree_recursive(args->tree, args->base, plen, 0,
+ args->pathspec, write_zip_entry);
+ write_zip_trailer(args->commit_sha1);
+
+ free(zip_dir);
+
+ return 0;
+}
+
+void *parse_extra_zip_args(int argc, const char **argv)
+{
+ for (; argc > 0; argc--, argv++) {
+ const char *arg = argv[0];
+
+ if (arg[0] == '-' && isdigit(arg[1]) && arg[2] == '\0')
+ zlib_compression_level = arg[1] - '0';
+ else
+ die("Unknown argument for zip format: %s", arg);
+ }
+ return NULL;
+}
diff --git a/archive.h b/archive.h
new file mode 100644
index 0000000000..6838dc788f
--- /dev/null
+++ b/archive.h
@@ -0,0 +1,45 @@
+#ifndef ARCHIVE_H
+#define ARCHIVE_H
+
+#define MAX_EXTRA_ARGS 32
+#define MAX_ARGS (MAX_EXTRA_ARGS + 32)
+
+struct archiver_args {
+ const char *base;
+ struct tree *tree;
+ const unsigned char *commit_sha1;
+ time_t time;
+ const char **pathspec;
+ unsigned int verbose : 1;
+ void *extra;
+};
+
+typedef int (*write_archive_fn_t)(struct archiver_args *);
+
+typedef void *(*parse_extra_args_fn_t)(int argc, const char **argv);
+
+struct archiver {
+ const char *name;
+ struct archiver_args args;
+ write_archive_fn_t write_archive;
+ parse_extra_args_fn_t parse_extra;
+};
+
+extern int parse_archive_args(int argc,
+ const char **argv,
+ struct archiver *ar);
+
+extern void parse_treeish_arg(const char **treeish,
+ struct archiver_args *ar_args,
+ const char *prefix);
+
+extern void parse_pathspec_arg(const char **pathspec,
+ struct archiver_args *args);
+/*
+ * Archive-format specific backends.
+ */
+extern int write_tar_archive(struct archiver_args *);
+extern int write_zip_archive(struct archiver_args *);
+extern void *parse_extra_zip_args(int argc, const char **argv);
+
+#endif /* ARCHIVE_H */
diff --git a/blame.c b/blame.c
deleted file mode 100644
index 7099b53c72..0000000000
--- a/blame.c
+++ /dev/null
@@ -1,923 +0,0 @@
-/*
- * Copyright (C) 2006, Fredrik Kuivinen <freku045@student.liu.se>
- */
-
-#include <assert.h>
-#include <time.h>
-#include <sys/time.h>
-#include <math.h>
-
-#include "cache.h"
-#include "refs.h"
-#include "tag.h"
-#include "commit.h"
-#include "tree.h"
-#include "blob.h"
-#include "diff.h"
-#include "diffcore.h"
-#include "revision.h"
-#include "xdiff-interface.h"
-
-#define DEBUG 0
-
-static const char blame_usage[] = "git-blame [-c] [-l] [-t] [-S <revs-file>] [--] file [commit]\n"
- " -c, --compatibility Use the same output mode as git-annotate (Default: off)\n"
- " -l, --long Show long commit SHA1 (Default: off)\n"
- " -t, --time Show raw timestamp (Default: off)\n"
- " -S, --revs-file Use revisions from revs-file instead of calling git-rev-list\n"
- " -h, --help This message";
-
-static struct commit **blame_lines;
-static int num_blame_lines;
-static char* blame_contents;
-static int blame_len;
-
-struct util_info {
- int *line_map;
- unsigned char sha1[20]; /* blob sha, not commit! */
- char *buf;
- unsigned long size;
- int num_lines;
- const char* pathname;
-
- void* topo_data;
-};
-
-struct chunk {
- int off1, len1; /* --- */
- int off2, len2; /* +++ */
-};
-
-struct patch {
- struct chunk *chunks;
- int num;
-};
-
-static void get_blob(struct commit *commit);
-
-/* Only used for statistics */
-static int num_get_patch = 0;
-static int num_commits = 0;
-static int patch_time = 0;
-
-struct blame_diff_state {
- struct xdiff_emit_state xm;
- struct patch *ret;
-};
-
-static void process_u0_diff(void *state_, char *line, unsigned long len)
-{
- struct blame_diff_state *state = state_;
- struct chunk *chunk;
-
- if (len < 4 || line[0] != '@' || line[1] != '@')
- return;
-
- if (DEBUG)
- printf("chunk line: %.*s", (int)len, line);
- state->ret->num++;
- state->ret->chunks = xrealloc(state->ret->chunks,
- sizeof(struct chunk) * state->ret->num);
- chunk = &state->ret->chunks[state->ret->num - 1];
-
- assert(!strncmp(line, "@@ -", 4));
-
- if (parse_hunk_header(line, len,
- &chunk->off1, &chunk->len1,
- &chunk->off2, &chunk->len2)) {
- state->ret->num--;
- return;
- }
-
- if (chunk->len1 == 0)
- chunk->off1++;
- if (chunk->len2 == 0)
- chunk->off2++;
-
- if (chunk->off1 > 0)
- chunk->off1--;
- if (chunk->off2 > 0)
- chunk->off2--;
-
- assert(chunk->off1 >= 0);
- assert(chunk->off2 >= 0);
-}
-
-static struct patch *get_patch(struct commit *commit, struct commit *other)
-{
- struct blame_diff_state state;
- xpparam_t xpp;
- xdemitconf_t xecfg;
- mmfile_t file_c, file_o;
- xdemitcb_t ecb;
- struct util_info *info_c = (struct util_info *)commit->util;
- struct util_info *info_o = (struct util_info *)other->util;
- struct timeval tv_start, tv_end;
-
- get_blob(commit);
- file_c.ptr = info_c->buf;
- file_c.size = info_c->size;
-
- get_blob(other);
- file_o.ptr = info_o->buf;
- file_o.size = info_o->size;
-
- gettimeofday(&tv_start, NULL);
-
- xpp.flags = XDF_NEED_MINIMAL;
- xecfg.ctxlen = 0;
- xecfg.flags = 0;
- ecb.outf = xdiff_outf;
- ecb.priv = &state;
- memset(&state, 0, sizeof(state));
- state.xm.consume = process_u0_diff;
- state.ret = xmalloc(sizeof(struct patch));
- state.ret->chunks = NULL;
- state.ret->num = 0;
-
- xdl_diff(&file_c, &file_o, &xpp, &xecfg, &ecb);
-
- gettimeofday(&tv_end, NULL);
- patch_time += 1000000 * (tv_end.tv_sec - tv_start.tv_sec) +
- tv_end.tv_usec - tv_start.tv_usec;
-
- num_get_patch++;
- return state.ret;
-}
-
-static void free_patch(struct patch *p)
-{
- free(p->chunks);
- free(p);
-}
-
-static int get_blob_sha1_internal(const unsigned char *sha1, const char *base,
- int baselen, const char *pathname,
- unsigned mode, int stage);
-
-static unsigned char blob_sha1[20];
-static const char* blame_file;
-static int get_blob_sha1(struct tree *t, const char *pathname,
- unsigned char *sha1)
-{
- int i;
- const char *pathspec[2];
- blame_file = pathname;
- pathspec[0] = pathname;
- pathspec[1] = NULL;
- memset(blob_sha1, 0, sizeof(blob_sha1));
- read_tree_recursive(t, "", 0, 0, pathspec, get_blob_sha1_internal);
-
- for (i = 0; i < 20; i++) {
- if (blob_sha1[i] != 0)
- break;
- }
-
- if (i == 20)
- return -1;
-
- memcpy(sha1, blob_sha1, 20);
- return 0;
-}
-
-static int get_blob_sha1_internal(const unsigned char *sha1, const char *base,
- int baselen, const char *pathname,
- unsigned mode, int stage)
-{
- if (S_ISDIR(mode))
- return READ_TREE_RECURSIVE;
-
- if (strncmp(blame_file, base, baselen) ||
- strcmp(blame_file + baselen, pathname))
- return -1;
-
- memcpy(blob_sha1, sha1, 20);
- return -1;
-}
-
-static void get_blob(struct commit *commit)
-{
- struct util_info *info = commit->util;
- char type[20];
-
- if (info->buf)
- return;
-
- info->buf = read_sha1_file(info->sha1, type, &info->size);
-
- assert(!strcmp(type, blob_type));
-}
-
-/* For debugging only */
-static void print_patch(struct patch *p)
-{
- int i;
- printf("Num chunks: %d\n", p->num);
- for (i = 0; i < p->num; i++) {
- printf("%d,%d %d,%d\n", p->chunks[i].off1, p->chunks[i].len1,
- p->chunks[i].off2, p->chunks[i].len2);
- }
-}
-
-#if DEBUG
-/* For debugging only */
-static void print_map(struct commit *cmit, struct commit *other)
-{
- struct util_info *util = cmit->util;
- struct util_info *util2 = other->util;
-
- int i;
- int max =
- util->num_lines >
- util2->num_lines ? util->num_lines : util2->num_lines;
- int num;
-
- for (i = 0; i < max; i++) {
- printf("i: %d ", i);
- num = -1;
-
- if (i < util->num_lines) {
- num = util->line_map[i];
- printf("%d\t", num);
- } else
- printf("\t");
-
- if (i < util2->num_lines) {
- int num2 = util2->line_map[i];
- printf("%d\t", num2);
- if (num != -1 && num2 != num)
- printf("---");
- } else
- printf("\t");
-
- printf("\n");
- }
-}
-#endif
-
-/* p is a patch from commit to other. */
-static void fill_line_map(struct commit *commit, struct commit *other,
- struct patch *p)
-{
- struct util_info *util = commit->util;
- struct util_info *util2 = other->util;
- int *map = util->line_map;
- int *map2 = util2->line_map;
- int cur_chunk = 0;
- int i1, i2;
-
- if (p->num && DEBUG)
- print_patch(p);
-
- if (DEBUG)
- printf("num lines 1: %d num lines 2: %d\n", util->num_lines,
- util2->num_lines);
-
- for (i1 = 0, i2 = 0; i1 < util->num_lines; i1++, i2++) {
- struct chunk *chunk = NULL;
- if (cur_chunk < p->num)
- chunk = &p->chunks[cur_chunk];
-
- if (chunk && chunk->off1 == i1) {
- if (DEBUG && i2 != chunk->off2)
- printf("i2: %d off2: %d\n", i2, chunk->off2);
-
- assert(i2 == chunk->off2);
-
- i1--;
- i2--;
- if (chunk->len1 > 0)
- i1 += chunk->len1;
-
- if (chunk->len2 > 0)
- i2 += chunk->len2;
-
- cur_chunk++;
- } else {
- if (i2 >= util2->num_lines)
- break;
-
- if (map[i1] != map2[i2] && map[i1] != -1) {
- if (DEBUG)
- printf("map: i1: %d %d %p i2: %d %d %p\n",
- i1, map[i1],
- (void *) (i1 != -1 ? blame_lines[map[i1]] : NULL),
- i2, map2[i2],
- (void *) (i2 != -1 ? blame_lines[map2[i2]] : NULL));
- if (map2[i2] != -1 &&
- blame_lines[map[i1]] &&
- !blame_lines[map2[i2]])
- map[i1] = map2[i2];
- }
-
- if (map[i1] == -1 && map2[i2] != -1)
- map[i1] = map2[i2];
- }
-
- if (DEBUG > 1)
- printf("l1: %d l2: %d i1: %d i2: %d\n",
- map[i1], map2[i2], i1, i2);
- }
-}
-
-static int map_line(struct commit *commit, int line)
-{
- struct util_info *info = commit->util;
- assert(line >= 0 && line < info->num_lines);
- return info->line_map[line];
-}
-
-static struct util_info* get_util(struct commit *commit)
-{
- struct util_info *util = commit->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->util = util;
- return util;
-}
-
-static int fill_util_info(struct commit *commit)
-{
- struct util_info *util = commit->util;
-
- assert(util);
- assert(util->pathname);
-
- if (get_blob_sha1(commit->tree, util->pathname, util->sha1))
- return 1;
- else
- return 0;
-}
-
-static void alloc_line_map(struct commit *commit)
-{
- struct util_info *util = commit->util;
- int i;
-
- if (util->line_map)
- return;
-
- get_blob(commit);
-
- util->num_lines = 0;
- for (i = 0; i < util->size; i++) {
- if (util->buf[i] == '\n')
- util->num_lines++;
- }
- if(util->buf[util->size - 1] != '\n')
- util->num_lines++;
-
- util->line_map = xmalloc(sizeof(int) * util->num_lines);
-
- for (i = 0; i < util->num_lines; i++)
- util->line_map[i] = -1;
-}
-
-static void init_first_commit(struct commit* commit, const char* filename)
-{
- struct util_info* util = commit->util;
- int i;
-
- util->pathname = filename;
- if (fill_util_info(commit))
- die("fill_util_info failed");
-
- alloc_line_map(commit);
-
- util = commit->util;
-
- for (i = 0; i < util->num_lines; i++)
- util->line_map[i] = i;
-}
-
-
-static void process_commits(struct rev_info *rev, const char *path,
- struct commit** initial)
-{
- int i;
- struct util_info* util;
- int lines_left;
- int *blame_p;
- int *new_lines;
- int new_lines_len;
-
- struct commit* commit = get_revision(rev);
- assert(commit);
- init_first_commit(commit, path);
-
- util = commit->util;
- num_blame_lines = util->num_lines;
- blame_lines = xmalloc(sizeof(struct commit *) * num_blame_lines);
- blame_contents = util->buf;
- blame_len = util->size;
-
- for (i = 0; i < num_blame_lines; i++)
- blame_lines[i] = NULL;
-
- lines_left = num_blame_lines;
- blame_p = xmalloc(sizeof(int) * num_blame_lines);
- new_lines = xmalloc(sizeof(int) * num_blame_lines);
- do {
- struct commit_list *parents;
- int num_parents;
- struct util_info *util;
-
- if (DEBUG)
- printf("\nProcessing commit: %d %s\n", num_commits,
- sha1_to_hex(commit->object.sha1));
-
- if (lines_left == 0)
- return;
-
- num_commits++;
- memset(blame_p, 0, sizeof(int) * num_blame_lines);
- new_lines_len = 0;
- num_parents = 0;
- for (parents = commit->parents;
- parents != NULL; parents = parents->next)
- num_parents++;
-
- if(num_parents == 0)
- *initial = commit;
-
- if (fill_util_info(commit))
- continue;
-
- alloc_line_map(commit);
- util = commit->util;
-
- for (parents = commit->parents;
- parents != NULL; parents = parents->next) {
- struct commit *parent = parents->item;
- struct patch *patch;
-
- if (parse_commit(parent) < 0)
- die("parse_commit error");
-
- if (DEBUG)
- printf("parent: %s\n",
- sha1_to_hex(parent->object.sha1));
-
- if (fill_util_info(parent)) {
- num_parents--;
- continue;
- }
-
- patch = get_patch(parent, commit);
- alloc_line_map(parent);
- fill_line_map(parent, commit, patch);
-
- for (i = 0; i < patch->num; i++) {
- int l;
- for (l = 0; l < patch->chunks[i].len2; l++) {
- int mapped_line =
- map_line(commit, patch->chunks[i].off2 + l);
- if (mapped_line != -1) {
- blame_p[mapped_line]++;
- if (blame_p[mapped_line] == num_parents)
- new_lines[new_lines_len++] = mapped_line;
- }
- }
- }
- free_patch(patch);
- }
-
- if (DEBUG)
- printf("parents: %d\n", num_parents);
-
- for (i = 0; i < new_lines_len; i++) {
- int mapped_line = new_lines[i];
- if (blame_lines[mapped_line] == NULL) {
- blame_lines[mapped_line] = commit;
- lines_left--;
- if (DEBUG)
- printf("blame: mapped: %d i: %d\n",
- mapped_line, i);
- }
- }
- } while ((commit = get_revision(rev)) != NULL);
-}
-
-
-static int compare_tree_path(struct rev_info* revs,
- struct commit* c1, struct commit* c2)
-{
- int ret;
- const char* paths[2];
- struct util_info* util = c2->util;
- paths[0] = util->pathname;
- paths[1] = NULL;
-
- diff_tree_setup_paths(get_pathspec(revs->prefix, paths),
- &revs->pruning);
- ret = rev_compare_tree(revs, c1->tree, c2->tree);
- diff_tree_release_paths(&revs->pruning);
- return ret;
-}
-
-
-static int same_tree_as_empty_path(struct rev_info *revs, struct tree* t1,
- const char* path)
-{
- int ret;
- const char* paths[2];
- paths[0] = path;
- paths[1] = NULL;
-
- diff_tree_setup_paths(get_pathspec(revs->prefix, paths),
- &revs->pruning);
- ret = rev_same_tree_as_empty(revs, t1);
- diff_tree_release_paths(&revs->pruning);
- return ret;
-}
-
-static const char* find_rename(struct commit* commit, struct commit* parent)
-{
- struct util_info* cutil = commit->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, &diff_opts);
- 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->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->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 = strchr(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,
- int show_raw_time)
-{
- static char time_buf[128];
- time_t t = time;
- int minutes, tz;
- struct tm *tm;
-
- if (show_raw_time) {
- sprintf(time_buf, "%lu %s", time, tz_str);
- return time_buf;
- }
-
- 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->util;
- util->topo_data = data;
-}
-
-static void* topo_getter(struct commit* c)
-{
- struct util_info* util = c->util;
- return util->topo_data;
-}
-
-static int read_ancestry(const char *graft_file,
- unsigned char **start_sha1)
-{
- FILE *fp = fopen(graft_file, "r");
- char buf[1024];
- if (!fp)
- return -1;
- while (fgets(buf, sizeof(buf), fp)) {
- /* The format is just "Commit Parent1 Parent2 ...\n" */
- int len = strlen(buf);
- struct commit_graft *graft = read_graft_line(buf, len);
- register_commit_graft(graft, 0);
- if (!*start_sha1)
- *start_sha1 = graft->sha1;
- }
- fclose(fp);
- return 0;
-}
-
-int main(int argc, const char **argv)
-{
- int i;
- struct commit *initial = NULL;
- unsigned char sha1[20], *sha1_p = NULL;
-
- const char *filename = NULL, *commit = NULL;
- char filename_buf[256];
- int sha1_len = 8;
- int compatibility = 0;
- int show_raw_time = 0;
- int options = 1;
- struct commit* start_commit;
-
- const char* args[10];
- struct rev_info rev;
-
- struct commit_info ci;
- const char *buf;
- int max_digits;
- int longest_file, longest_author;
- int found_rename;
-
- const char* prefix = setup_git_directory();
- git_config(git_default_config);
-
- for(i = 1; i < argc; i++) {
- if(options) {
- if(!strcmp(argv[i], "-h") ||
- !strcmp(argv[i], "--help"))
- usage(blame_usage);
- else if(!strcmp(argv[i], "-l") ||
- !strcmp(argv[i], "--long")) {
- sha1_len = 40;
- continue;
- } else if(!strcmp(argv[i], "-c") ||
- !strcmp(argv[i], "--compatibility")) {
- compatibility = 1;
- continue;
- } else if(!strcmp(argv[i], "-t") ||
- !strcmp(argv[i], "--time")) {
- show_raw_time = 1;
- continue;
- } else if(!strcmp(argv[i], "-S")) {
- if (i + 1 < argc &&
- !read_ancestry(argv[i + 1], &sha1_p)) {
- compatibility = 1;
- i++;
- continue;
- }
- usage(blame_usage);
- } else if(!strcmp(argv[i], "--")) {
- options = 0;
- continue;
- } else if(argv[i][0] == '-')
- usage(blame_usage);
- else
- options = 0;
- }
-
- if(!options) {
- if(!filename)
- filename = argv[i];
- else if(!commit)
- commit = argv[i];
- else
- usage(blame_usage);
- }
- }
-
- if(!filename)
- usage(blame_usage);
- if (commit && sha1_p)
- usage(blame_usage);
- else if(!commit)
- commit = "HEAD";
-
- if(prefix)
- sprintf(filename_buf, "%s%s", prefix, filename);
- else
- strcpy(filename_buf, filename);
- filename = filename_buf;
-
- if (!sha1_p) {
- if (get_sha1(commit, sha1))
- die("get_sha1 failed, commit '%s' not found", commit);
- sha1_p = sha1;
- }
- start_commit = lookup_commit_reference(sha1_p);
- get_util(start_commit)->pathname = filename;
- if (fill_util_info(start_commit)) {
- printf("%s not found in %s\n", filename, commit);
- return 1;
- }
-
-
- init_revisions(&rev, setup_git_directory());
- rev.remove_empty_trees = 1;
- rev.topo_order = 1;
- rev.prune_fn = simplify_commit;
- rev.topo_setter = topo_setter;
- rev.topo_getter = topo_getter;
- rev.parents = 1;
- rev.limited = 1;
-
- commit_list_insert(start_commit, &rev.commits);
-
- args[0] = filename;
- args[1] = NULL;
- diff_tree_setup_paths(args, &rev.pruning);
- prepare_revision_walk(&rev);
- process_commits(&rev, filename, &initial);
-
- buf = blame_contents;
- for (max_digits = 1, i = 10; i <= num_blame_lines + 1; max_digits++)
- i *= 10;
-
- longest_file = 0;
- longest_author = 0;
- found_rename = 0;
- for (i = 0; i < num_blame_lines; i++) {
- struct commit *c = blame_lines[i];
- struct util_info* u;
- if (!c)
- c = initial;
- u = c->util;
-
- if (!found_rename && strcmp(filename, u->pathname))
- found_rename = 1;
- if (longest_file < strlen(u->pathname))
- longest_file = strlen(u->pathname);
- get_commit_info(c, &ci);
- if (longest_author < strlen(ci.author))
- longest_author = strlen(ci.author);
- }
-
- for (i = 0; i < num_blame_lines; i++) {
- struct commit *c = blame_lines[i];
- struct util_info* u;
-
- if (!c)
- c = initial;
-
- u = c->util;
- get_commit_info(c, &ci);
- fwrite(sha1_to_hex(c->object.sha1), sha1_len, 1, stdout);
- if(compatibility) {
- printf("\t(%10s\t%10s\t%d)", ci.author,
- format_time(ci.author_time, ci.author_tz,
- show_raw_time),
- i+1);
- } else {
- if (found_rename)
- printf(" %-*.*s", longest_file, longest_file,
- u->pathname);
- printf(" (%-*.*s %10s %*d) ",
- longest_author, longest_author, ci.author,
- format_time(ci.author_time, ci.author_tz,
- show_raw_time),
- 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 = strchr(buf, '\n') + 1;
- fwrite(buf, next_buf - buf, 1, stdout);
- buf = next_buf;
- }
- }
-
- if (DEBUG) {
- printf("num get patch: %d\n", num_get_patch);
- printf("num commits: %d\n", num_commits);
- printf("patch time: %f\n", patch_time / 1000000.0);
- printf("initial: %s\n", sha1_to_hex(initial->object.sha1));
- }
-
- return 0;
-}
diff --git a/builtin-add.c b/builtin-add.c
index 0cb9c81200..febb75ed99 100644
--- a/builtin-add.c
+++ b/builtin-add.c
@@ -70,7 +70,6 @@ static void fill_directory(struct dir_struct *dir, const char **pathspec)
base = "";
if (baselen) {
char *common = xmalloc(baselen + 1);
- common = xmalloc(baselen + 1);
memcpy(common, *pathspec, baselen);
common[baselen] = 0;
path = base = common;
diff --git a/builtin-annotate.c b/builtin-annotate.c
new file mode 100644
index 0000000000..57c46840d5
--- /dev/null
+++ b/builtin-annotate.c
@@ -0,0 +1,25 @@
+/*
+ * "git annotate" builtin alias
+ *
+ * Copyright (C) 2006 Ryan Anderson
+ */
+#include "git-compat-util.h"
+#include "builtin.h"
+
+int cmd_annotate(int argc, const char **argv, const char *prefix)
+{
+ const char **nargv;
+ int i;
+ nargv = xmalloc(sizeof(char *) * (argc + 2));
+
+ nargv[0] = "blame";
+ nargv[1] = "-c";
+
+ for (i = 1; i < argc; i++) {
+ nargv[i+1] = argv[i];
+ }
+ nargv[argc + 1] = NULL;
+
+ return cmd_blame(argc + 1, nargv, prefix);
+}
+
diff --git a/builtin-apply.c b/builtin-apply.c
index 9cf477c701..61f047fd45 100644
--- a/builtin-apply.c
+++ b/builtin-apply.c
@@ -27,22 +27,25 @@ static const char *prefix;
static int prefix_length = -1;
static int newfd = -1;
+static int unidiff_zero;
static int p_value = 1;
-static int allow_binary_replacement = 0;
-static int check_index = 0;
-static int write_index = 0;
-static int cached = 0;
-static int diffstat = 0;
-static int numstat = 0;
-static int summary = 0;
-static int check = 0;
+static int check_index;
+static int write_index;
+static int cached;
+static int diffstat;
+static int numstat;
+static int summary;
+static int check;
static int apply = 1;
-static int no_add = 0;
-static int show_index_info = 0;
+static int apply_in_reverse;
+static int apply_with_reject;
+static int apply_verbosely;
+static int no_add;
+static int show_index_info;
static int line_termination = '\n';
-static unsigned long p_context = -1;
+static unsigned long p_context = ULONG_MAX;
static const char apply_usage[] =
-"git-apply [--stat] [--numstat] [--summary] [--check] [--index] [--cached] [--apply] [--no-add] [--index-info] [--allow-binary-replacement] [-z] [-pNUM] [-CNUM] [--whitespace=<nowarn|warn|error|error-all|strip>] <patch>...";
+"git-apply [--stat] [--numstat] [--summary] [--check] [--index] [--cached] [--apply] [--no-add] [--index-info] [--allow-binary-replacement] [--reverse] [--reject] [--verbose] [-z] [-pNUM] [-CNUM] [--whitespace=<nowarn|warn|error|error-all|strip>] <patch>...";
static enum whitespace_eol {
nowarn_whitespace,
@@ -50,10 +53,10 @@ static enum whitespace_eol {
error_on_whitespace,
strip_whitespace,
} new_whitespace = warn_on_whitespace;
-static int whitespace_error = 0;
+static int whitespace_error;
static int squelch_whitespace_errors = 5;
-static int applied_after_stripping = 0;
-static const char *patch_input_file = NULL;
+static int applied_after_stripping;
+static const char *patch_input_file;
static void parse_whitespace_option(const char *option)
{
@@ -108,25 +111,44 @@ static int max_change, max_len;
*/
static int linenr = 1;
+/*
+ * This represents one "hunk" from a patch, starting with
+ * "@@ -oldpos,oldlines +newpos,newlines @@" marker. The
+ * patch text is pointed at by patch, and its byte length
+ * is stored in size. leading and trailing are the number
+ * of context lines.
+ */
struct fragment {
unsigned long leading, trailing;
unsigned long oldpos, oldlines;
unsigned long newpos, newlines;
const char *patch;
int size;
+ int rejected;
struct fragment *next;
};
+/*
+ * When dealing with a binary patch, we reuse "leading" field
+ * to store the type of the binary hunk, either deflated "delta"
+ * or deflated "literal".
+ */
+#define binary_patch_method leading
+#define BINARY_DELTA_DEFLATED 1
+#define BINARY_LITERAL_DEFLATED 2
+
struct patch {
char *new_name, *old_name, *def_name;
unsigned int old_mode, new_mode;
- int is_rename, is_copy, is_new, is_delete, is_binary, is_reverse;
-#define BINARY_DELTA_DEFLATED 1
-#define BINARY_LITERAL_DEFLATED 2
+ int is_new, is_delete; /* -1 = unknown, 0 = false, 1 = true */
+ int rejected;
unsigned long deflate_origlen;
int lines_added, lines_deleted;
int score;
- int inaccurate_eof:1;
+ unsigned int inaccurate_eof:1;
+ unsigned int is_binary:1;
+ unsigned int is_copy:1;
+ unsigned int is_rename:1;
struct fragment *fragments;
char *result;
unsigned long resultsize;
@@ -135,6 +157,24 @@ struct patch {
struct patch *next;
};
+static void say_patch_name(FILE *output, const char *pre, struct patch *patch, const char *post)
+{
+ fputs(pre, output);
+ if (patch->old_name && patch->new_name &&
+ strcmp(patch->old_name, patch->new_name)) {
+ write_name_quoted(NULL, 0, patch->old_name, 1, output);
+ fputs(" => ", output);
+ write_name_quoted(NULL, 0, patch->new_name, 1, output);
+ }
+ else {
+ const char *n = patch->new_name;
+ if (!n)
+ n = patch->old_name;
+ write_name_quoted(NULL, 0, n, 1, output);
+ }
+ fputs(post, output);
+}
+
#define CHUNKSIZE (8192)
#define SLOP (16)
@@ -323,7 +363,7 @@ static int gitdiff_hdrend(const char *line, struct patch *patch)
static char *gitdiff_verify_name(const char *line, int isnull, char *orig_name, const char *oldnew)
{
if (!orig_name && !isnull)
- return find_name(line, NULL, 1, 0);
+ return find_name(line, NULL, 1, TERM_TAB);
if (orig_name) {
int len;
@@ -333,7 +373,7 @@ static char *gitdiff_verify_name(const char *line, int isnull, char *orig_name,
len = strlen(name);
if (isnull)
die("git-apply: bad git-diff - expected /dev/null, got %s on line %d", name, linenr);
- another = find_name(line, NULL, 1, 0);
+ another = find_name(line, NULL, 1, TERM_TAB);
if (!another || memcmp(another, name, len))
die("git-apply: bad git-diff - inconsistent %s filename on line %d", oldnew, linenr);
free(another);
@@ -591,9 +631,7 @@ static char *git_header_name(char *line, int llen)
* form.
*/
for (len = 0 ; ; len++) {
- char c = name[len];
-
- switch (c) {
+ switch (name[len]) {
default:
continue;
case '\n':
@@ -819,12 +857,54 @@ static int find_header(char *line, unsigned long size, int *hdrsize, struct patc
return -1;
}
+static void check_whitespace(const char *line, int len)
+{
+ const char *err = "Adds trailing whitespace";
+ int seen_space = 0;
+ int i;
+
+ /*
+ * We know len is at least two, since we have a '+' and we
+ * checked that the last character was a '\n' before calling
+ * this function. That is, an addition of an empty line would
+ * check the '+' here. Sneaky...
+ */
+ if (isspace(line[len-2]))
+ goto error;
+
+ /*
+ * Make sure that there is no space followed by a tab in
+ * indentation.
+ */
+ err = "Space in indent is followed by a tab";
+ for (i = 1; i < len; i++) {
+ if (line[i] == '\t') {
+ if (seen_space)
+ goto error;
+ }
+ else if (line[i] == ' ')
+ seen_space = 1;
+ else
+ break;
+ }
+ return;
+
+ error:
+ whitespace_error++;
+ if (squelch_whitespace_errors &&
+ squelch_whitespace_errors < whitespace_error)
+ ;
+ else
+ fprintf(stderr, "%s.\n%s:%d:%.*s\n",
+ err, patch_input_file, linenr, len-2, line+1);
+}
+
+
/*
- * Parse a unified diff. Note that this really needs
- * to parse each fragment separately, since the only
- * way to know the difference between a "---" that is
- * part of a patch, and a "---" that starts the next
- * patch is to look at the line counts..
+ * Parse a unified diff. Note that this really needs to parse each
+ * fragment separately, since the only way to know the difference
+ * between a "---" that is part of a patch, and a "---" that starts
+ * the next patch is to look at the line counts..
*/
static int parse_fragment(char *line, unsigned long size, struct patch *patch, struct fragment *fragment)
{
@@ -841,31 +921,14 @@ static int parse_fragment(char *line, unsigned long size, struct patch *patch, s
leading = 0;
trailing = 0;
- if (patch->is_new < 0) {
- patch->is_new = !oldlines;
- if (!oldlines)
- patch->old_name = NULL;
- }
- if (patch->is_delete < 0) {
- patch->is_delete = !newlines;
- if (!newlines)
- patch->new_name = NULL;
- }
-
- if (patch->is_new && oldlines)
- return error("new file depends on old contents");
- if (patch->is_delete != !newlines) {
- if (newlines)
- return error("deleted file still has contents");
- fprintf(stderr, "** warning: file %s becomes empty but is not deleted\n", patch->new_name);
- }
-
/* Parse the thing.. */
line += len;
size -= len;
linenr++;
added = deleted = 0;
- for (offset = len; size > 0; offset += len, size -= len, line += len, linenr++) {
+ for (offset = len;
+ 0 < size;
+ offset += len, size -= len, line += len, linenr++) {
if (!oldlines && !newlines)
break;
len = linelen(line, size);
@@ -874,6 +937,7 @@ static int parse_fragment(char *line, unsigned long size, struct patch *patch, s
switch (*line) {
default:
return -1;
+ case '\n': /* newer GNU diff, an empty context line */
case ' ':
oldlines--;
newlines--;
@@ -887,25 +951,8 @@ static int parse_fragment(char *line, unsigned long size, struct patch *patch, s
trailing = 0;
break;
case '+':
- /*
- * We know len is at least two, since we have a '+' and
- * we checked that the last character was a '\n' above.
- * That is, an addition of an empty line would check
- * the '+' here. Sneaky...
- */
- if ((new_whitespace != nowarn_whitespace) &&
- isspace(line[len-2])) {
- whitespace_error++;
- if (squelch_whitespace_errors &&
- squelch_whitespace_errors <
- whitespace_error)
- ;
- else {
- fprintf(stderr, "Adds trailing whitespace.\n%s:%d:%.*s\n",
- patch_input_file,
- linenr, len-2, line+1);
- }
- }
+ if (new_whitespace != nowarn_whitespace)
+ check_whitespace(line, len);
added++;
newlines--;
trailing = 0;
@@ -938,12 +985,18 @@ static int parse_fragment(char *line, unsigned long size, struct patch *patch, s
patch->lines_added += added;
patch->lines_deleted += deleted;
+
+ if (0 < patch->is_new && oldlines)
+ return error("new file depends on old contents");
+ if (0 < patch->is_delete && newlines)
+ return error("deleted file still has contents");
return offset;
}
static int parse_single_patch(char *line, unsigned long size, struct patch *patch)
{
unsigned long offset = 0;
+ unsigned long oldlines = 0, newlines = 0, context = 0;
struct fragment **fragp = &patch->fragments;
while (size > 4 && !memcmp(line, "@@ -", 4)) {
@@ -954,9 +1007,11 @@ static int parse_single_patch(char *line, unsigned long size, struct patch *patc
len = parse_fragment(line, size, patch, fragment);
if (len <= 0)
die("corrupt patch at line %d", linenr);
-
fragment->patch = line;
fragment->size = len;
+ oldlines += fragment->oldlines;
+ newlines += fragment->newlines;
+ context += fragment->leading + fragment->trailing;
*fragp = fragment;
fragp = &fragment->next;
@@ -965,6 +1020,50 @@ static int parse_single_patch(char *line, unsigned long size, struct patch *patc
line += len;
size -= len;
}
+
+ /*
+ * If something was removed (i.e. we have old-lines) it cannot
+ * be creation, and if something was added it cannot be
+ * deletion. However, the reverse is not true; --unified=0
+ * patches that only add are not necessarily creation even
+ * though they do not have any old lines, and ones that only
+ * delete are not necessarily deletion.
+ *
+ * Unfortunately, a real creation/deletion patch do _not_ have
+ * any context line by definition, so we cannot safely tell it
+ * apart with --unified=0 insanity. At least if the patch has
+ * more than one hunk it is not creation or deletion.
+ */
+ if (patch->is_new < 0 &&
+ (oldlines || (patch->fragments && patch->fragments->next)))
+ patch->is_new = 0;
+ if (patch->is_delete < 0 &&
+ (newlines || (patch->fragments && patch->fragments->next)))
+ patch->is_delete = 0;
+ if (!unidiff_zero || context) {
+ /* If the user says the patch is not generated with
+ * --unified=0, or if we have seen context lines,
+ * then not having oldlines means the patch is creation,
+ * and not having newlines means the patch is deletion.
+ */
+ if (patch->is_new < 0 && !oldlines) {
+ patch->is_new = 1;
+ patch->old_name = NULL;
+ }
+ if (patch->is_delete < 0 && !newlines) {
+ patch->is_delete = 1;
+ patch->new_name = NULL;
+ }
+ }
+
+ if (0 < patch->is_new && oldlines)
+ die("new file %s depends on old contents", patch->new_name);
+ if (0 < patch->is_delete && newlines)
+ die("deleted file %s still has contents", patch->old_name);
+ if (!patch->is_delete && !newlines && context)
+ fprintf(stderr, "** warning: file %s becomes empty but "
+ "is not deleted\n", patch->new_name);
+
return offset;
}
@@ -978,51 +1077,82 @@ static inline int metadata_changes(struct patch *patch)
patch->old_mode != patch->new_mode);
}
-static int parse_binary(char *buffer, unsigned long size, struct patch *patch)
+static char *inflate_it(const void *data, unsigned long size,
+ unsigned long inflated_size)
{
- /* We have read "GIT binary patch\n"; what follows is a line
- * that says the patch method (currently, either "deflated
- * literal" or "deflated delta") and the length of data before
- * deflating; a sequence of 'length-byte' followed by base-85
- * encoded data follows.
+ z_stream stream;
+ void *out;
+ int st;
+
+ memset(&stream, 0, sizeof(stream));
+
+ stream.next_in = (unsigned char *)data;
+ stream.avail_in = size;
+ stream.next_out = out = xmalloc(inflated_size);
+ stream.avail_out = inflated_size;
+ inflateInit(&stream);
+ st = inflate(&stream, Z_FINISH);
+ if ((st != Z_STREAM_END) || stream.total_out != inflated_size) {
+ free(out);
+ return NULL;
+ }
+ return out;
+}
+
+static struct fragment *parse_binary_hunk(char **buf_p,
+ unsigned long *sz_p,
+ int *status_p,
+ int *used_p)
+{
+ /* Expect a line that begins with binary patch method ("literal"
+ * or "delta"), followed by the length of data before deflating.
+ * a sequence of 'length-byte' followed by base-85 encoded data
+ * should follow, terminated by a newline.
*
* Each 5-byte sequence of base-85 encodes up to 4 bytes,
* and we would limit the patch line to 66 characters,
* so one line can fit up to 13 groups that would decode
* to 52 bytes max. The length byte 'A'-'Z' corresponds
* to 1-26 bytes, and 'a'-'z' corresponds to 27-52 bytes.
- * The end of binary is signaled with an empty line.
*/
int llen, used;
- struct fragment *fragment;
+ unsigned long size = *sz_p;
+ char *buffer = *buf_p;
+ int patch_method;
+ unsigned long origlen;
char *data = NULL;
+ int hunk_size = 0;
+ struct fragment *frag;
- patch->fragments = fragment = xcalloc(1, sizeof(*fragment));
-
- /* Grab the type of patch */
llen = linelen(buffer, size);
used = llen;
- linenr++;
+
+ *status_p = 0;
if (!strncmp(buffer, "delta ", 6)) {
- patch->is_binary = BINARY_DELTA_DEFLATED;
- patch->deflate_origlen = strtoul(buffer + 6, NULL, 10);
+ patch_method = BINARY_DELTA_DEFLATED;
+ origlen = strtoul(buffer + 6, NULL, 10);
}
else if (!strncmp(buffer, "literal ", 8)) {
- patch->is_binary = BINARY_LITERAL_DEFLATED;
- patch->deflate_origlen = strtoul(buffer + 8, NULL, 10);
+ patch_method = BINARY_LITERAL_DEFLATED;
+ origlen = strtoul(buffer + 8, NULL, 10);
}
else
- return error("unrecognized binary patch at line %d: %.*s",
- linenr-1, llen-1, buffer);
+ return NULL;
+
+ linenr++;
buffer += llen;
while (1) {
int byte_length, max_byte_length, newsize;
llen = linelen(buffer, size);
used += llen;
linenr++;
- if (llen == 1)
+ if (llen == 1) {
+ /* consume the blank line */
+ buffer++;
+ size--;
break;
+ }
/* Minimum line is "A00000\n" which is 7-byte long,
* and the line length must be multiple of 5 plus 2.
*/
@@ -1043,21 +1173,78 @@ static int parse_binary(char *buffer, unsigned long size, struct patch *patch)
if (max_byte_length < byte_length ||
byte_length <= max_byte_length - 4)
goto corrupt;
- newsize = fragment->size + byte_length;
+ newsize = hunk_size + byte_length;
data = xrealloc(data, newsize);
- if (decode_85(data + fragment->size,
- buffer + 1,
- byte_length))
+ if (decode_85(data + hunk_size, buffer + 1, byte_length))
goto corrupt;
- fragment->size = newsize;
+ hunk_size = newsize;
buffer += llen;
size -= llen;
}
- fragment->patch = data;
- return used;
+
+ frag = xcalloc(1, sizeof(*frag));
+ frag->patch = inflate_it(data, hunk_size, origlen);
+ if (!frag->patch)
+ goto corrupt;
+ free(data);
+ frag->size = origlen;
+ *buf_p = buffer;
+ *sz_p = size;
+ *used_p = used;
+ frag->binary_patch_method = patch_method;
+ return frag;
+
corrupt:
- return error("corrupt binary patch at line %d: %.*s",
- linenr-1, llen-1, buffer);
+ free(data);
+ *status_p = -1;
+ error("corrupt binary patch at line %d: %.*s",
+ linenr-1, llen-1, buffer);
+ return NULL;
+}
+
+static int parse_binary(char *buffer, unsigned long size, struct patch *patch)
+{
+ /* We have read "GIT binary patch\n"; what follows is a line
+ * that says the patch method (currently, either "literal" or
+ * "delta") and the length of data before deflating; a
+ * sequence of 'length-byte' followed by base-85 encoded data
+ * follows.
+ *
+ * When a binary patch is reversible, there is another binary
+ * hunk in the same format, starting with patch method (either
+ * "literal" or "delta") with the length of data, and a sequence
+ * of length-byte + base-85 encoded data, terminated with another
+ * empty line. This data, when applied to the postimage, produces
+ * the preimage.
+ */
+ struct fragment *forward;
+ struct fragment *reverse;
+ int status;
+ int used, used_1;
+
+ forward = parse_binary_hunk(&buffer, &size, &status, &used);
+ if (!forward && !status)
+ /* there has to be one hunk (forward hunk) */
+ return error("unrecognized binary patch at line %d", linenr-1);
+ if (status)
+ /* otherwise we already gave an error message */
+ return status;
+
+ reverse = parse_binary_hunk(&buffer, &size, &status, &used_1);
+ if (reverse)
+ used += used_1;
+ else if (status) {
+ /* not having reverse hunk is not an error, but having
+ * a corrupt reverse hunk is.
+ */
+ free((void*) forward->patch);
+ free(forward);
+ return status;
+ }
+ forward->next = reverse;
+ patch->fragments = forward;
+ patch->is_binary = 1;
+ return used;
}
static int parse_chunk(char *buffer, unsigned long size, struct patch *patch)
@@ -1105,14 +1292,12 @@ static int parse_chunk(char *buffer, unsigned long size, struct patch *patch)
}
}
- /* Empty patch cannot be applied if:
- * - it is a binary patch and we do not do binary_replace, or
- * - text patch without metadata change
+ /* Empty patch cannot be applied if it is a text patch
+ * without metadata change. A binary patch appears
+ * empty to us here.
*/
if ((apply || check) &&
- (patch->is_binary
- ? !allow_binary_replacement
- : !metadata_changes(patch)))
+ (!patch->is_binary && !metadata_changes(patch)))
die("patch with only garbage at line %d", linenr);
}
@@ -1143,7 +1328,6 @@ static void reverse_patches(struct patch *p)
swap(frag->newpos, frag->oldpos);
swap(frag->newlines, frag->oldlines);
}
- p->is_reverse = !p->is_reverse;
}
}
@@ -1206,8 +1390,7 @@ static void show_stats(struct patch *patch)
printf(" %s%-*s |%5d %.*s%.*s\n", prefix,
len, name, patch->lines_added + patch->lines_deleted,
add, pluses, del, minuses);
- if (qname)
- free(qname);
+ free(qname);
}
static int read_old_data(struct stat *st, const char *path, void *buf, unsigned long size)
@@ -1345,26 +1528,71 @@ static int apply_line(char *output, const char *patch, int plen)
{
/* plen is number of bytes to be copied from patch,
* starting at patch+1 (patch[0] is '+'). Typically
- * patch[plen] is '\n'.
+ * patch[plen] is '\n', unless this is the incomplete
+ * last line.
*/
+ int i;
int add_nl_to_tail = 0;
- if ((new_whitespace == strip_whitespace) &&
- 1 < plen && isspace(patch[plen-1])) {
+ int fixed = 0;
+ int last_tab_in_indent = -1;
+ int last_space_in_indent = -1;
+ int need_fix_leading_space = 0;
+ char *buf;
+
+ if ((new_whitespace != strip_whitespace) || !whitespace_error) {
+ memcpy(output, patch + 1, plen);
+ return plen;
+ }
+
+ if (1 < plen && isspace(patch[plen-1])) {
if (patch[plen] == '\n')
add_nl_to_tail = 1;
plen--;
while (0 < plen && isspace(patch[plen]))
plen--;
- applied_after_stripping++;
+ fixed = 1;
+ }
+
+ for (i = 1; i < plen; i++) {
+ char ch = patch[i];
+ if (ch == '\t') {
+ last_tab_in_indent = i;
+ if (0 <= last_space_in_indent)
+ need_fix_leading_space = 1;
+ }
+ else if (ch == ' ')
+ last_space_in_indent = i;
+ else
+ break;
}
- memcpy(output, patch + 1, plen);
+
+ buf = output;
+ if (need_fix_leading_space) {
+ /* between patch[1..last_tab_in_indent] strip the
+ * funny spaces, updating them to tab as needed.
+ */
+ for (i = 1; i < last_tab_in_indent; i++, plen--) {
+ char ch = patch[i];
+ if (ch != ' ')
+ *output++ = ch;
+ else if ((i % 8) == 0)
+ *output++ = '\t';
+ }
+ fixed = 1;
+ i = last_tab_in_indent;
+ }
+ else
+ i = 1;
+
+ memcpy(output, patch + i, plen);
if (add_nl_to_tail)
output[plen++] = '\n';
- return plen;
+ if (fixed)
+ applied_after_stripping++;
+ return output + plen - buf;
}
-static int apply_one_fragment(struct buffer_desc *desc, struct fragment *frag,
- int reverse, int inaccurate_eof)
+static int apply_one_fragment(struct buffer_desc *desc, struct fragment *frag, int inaccurate_eof)
{
int match_beginning, match_end;
char *buf = desc->buffer;
@@ -1396,13 +1624,21 @@ static int apply_one_fragment(struct buffer_desc *desc, struct fragment *frag,
if (len < size && patch[len] == '\\')
plen--;
first = *patch;
- if (reverse) {
+ if (apply_in_reverse) {
if (first == '-')
first = '+';
else if (first == '+')
first = '-';
}
switch (first) {
+ case '\n':
+ /* Newer GNU diff, empty context line */
+ if (plen < 0)
+ /* ... followed by '\No newline'; nothing */
+ break;
+ old[oldsize++] = '\n';
+ new[newsize++] = '\n';
+ break;
case ' ':
case '-':
memcpy(old + oldsize, patch + 1, plen);
@@ -1439,14 +1675,25 @@ static int apply_one_fragment(struct buffer_desc *desc, struct fragment *frag,
/*
* If we don't have any leading/trailing data in the patch,
* we want it to match at the beginning/end of the file.
+ *
+ * But that would break if the patch is generated with
+ * --unified=0; sane people wouldn't do that to cause us
+ * trouble, but we try to please not so sane ones as well.
*/
- match_beginning = !leading && (frag->oldpos == 1);
- match_end = !trailing;
+ if (unidiff_zero) {
+ match_beginning = (!leading && !frag->oldpos);
+ match_end = 0;
+ }
+ else {
+ match_beginning = !leading && (frag->oldpos == 1);
+ match_end = !trailing;
+ }
lines = 0;
pos = frag->newpos;
for (;;) {
- offset = find_offset(buf, desc->size, oldlines, oldsize, pos, &lines);
+ offset = find_offset(buf, desc->size,
+ oldlines, oldsize, pos, &lines);
if (match_end && offset + oldsize != desc->size)
offset = -1;
if (match_beginning && offset)
@@ -1459,8 +1706,10 @@ static int apply_one_fragment(struct buffer_desc *desc, struct fragment *frag,
/* Warn if it was necessary to reduce the number
* of context lines.
*/
- if ((leading != frag->leading) || (trailing != frag->trailing))
- fprintf(stderr, "Context reduced to (%ld/%ld) to apply fragment at %d\n",
+ if ((leading != frag->leading) ||
+ (trailing != frag->trailing))
+ fprintf(stderr, "Context reduced to (%ld/%ld)"
+ " to apply fragment at %d\n",
leading, trailing, pos + lines);
if (size > alloc) {
@@ -1470,7 +1719,9 @@ static int apply_one_fragment(struct buffer_desc *desc, struct fragment *frag,
desc->buffer = buf;
}
desc->size = size;
- memmove(buf + offset + newsize, buf + offset + oldsize, size - offset - newsize);
+ memmove(buf + offset + newsize,
+ buf + offset + oldsize,
+ size - offset - newsize);
memcpy(buf + offset, newlines, newsize);
offset = 0;
@@ -1506,28 +1757,6 @@ static int apply_one_fragment(struct buffer_desc *desc, struct fragment *frag,
return offset;
}
-static char *inflate_it(const void *data, unsigned long size,
- unsigned long inflated_size)
-{
- z_stream stream;
- void *out;
- int st;
-
- memset(&stream, 0, sizeof(stream));
-
- stream.next_in = (unsigned char *)data;
- stream.avail_in = size;
- stream.next_out = out = xmalloc(inflated_size);
- stream.avail_out = inflated_size;
- inflateInit(&stream);
- st = inflate(&stream, Z_FINISH);
- if ((st != Z_STREAM_END) || stream.total_out != inflated_size) {
- free(out);
- return NULL;
- }
- return out;
-}
-
static int apply_binary_fragment(struct buffer_desc *desc, struct patch *patch)
{
unsigned long dst_size;
@@ -1535,30 +1764,29 @@ static int apply_binary_fragment(struct buffer_desc *desc, struct patch *patch)
void *data;
void *result;
- /* Binary patch is irreversible */
- if (patch->is_reverse)
- return error("cannot reverse-apply a binary patch to '%s'",
- patch->new_name
- ? patch->new_name : patch->old_name);
-
- data = inflate_it(fragment->patch, fragment->size,
- patch->deflate_origlen);
- if (!data)
- return error("corrupt patch data");
- switch (patch->is_binary) {
+ /* Binary patch is irreversible without the optional second hunk */
+ if (apply_in_reverse) {
+ if (!fragment->next)
+ return error("cannot reverse-apply a binary patch "
+ "without the reverse hunk to '%s'",
+ patch->new_name
+ ? patch->new_name : patch->old_name);
+ fragment = fragment->next;
+ }
+ data = (void*) fragment->patch;
+ switch (fragment->binary_patch_method) {
case BINARY_DELTA_DEFLATED:
result = patch_delta(desc->buffer, desc->size,
data,
- patch->deflate_origlen,
+ fragment->size,
&dst_size);
free(desc->buffer);
desc->buffer = result;
- free(data);
break;
case BINARY_LITERAL_DEFLATED:
free(desc->buffer);
desc->buffer = data;
- dst_size = patch->deflate_origlen;
+ dst_size = fragment->size;
break;
}
if (!desc->buffer)
@@ -1571,13 +1799,6 @@ static int apply_binary(struct buffer_desc *desc, struct patch *patch)
{
const char *name = patch->old_name ? patch->old_name : patch->new_name;
unsigned char sha1[20];
- unsigned char hdr[50];
- int hdrlen;
-
- if (!allow_binary_replacement)
- return error("cannot apply binary patch to '%s' "
- "without --allow-binary-replacement",
- name);
/* For safety, we require patch index line to contain
* full 40-byte textual SHA1 for old and new, at least for now.
@@ -1593,8 +1814,7 @@ static int apply_binary(struct buffer_desc *desc, struct patch *patch)
/* See if the old one matches what the patch
* applies to.
*/
- write_sha1_file_prepare(desc->buffer, desc->size,
- blob_type, sha1, hdr, &hdrlen);
+ hash_sha1_file(desc->buffer, desc->size, blob_type, sha1);
if (strcmp(sha1_to_hex(sha1), patch->old_sha1_prefix))
return error("the patch applies to '%s' (%s), "
"which does not match the "
@@ -1609,7 +1829,7 @@ static int apply_binary(struct buffer_desc *desc, struct patch *patch)
}
get_sha1_hex(patch->new_sha1_prefix, sha1);
- if (!memcmp(sha1, null_sha1, 20)) {
+ if (is_null_sha1(sha1)) {
free(desc->buffer);
desc->alloc = desc->size = 0;
desc->buffer = NULL;
@@ -1639,10 +1859,9 @@ static int apply_binary(struct buffer_desc *desc, struct patch *patch)
name);
/* verify that the result matches */
- write_sha1_file_prepare(desc->buffer, desc->size, blob_type,
- sha1, hdr, &hdrlen);
+ hash_sha1_file(desc->buffer, desc->size, blob_type, sha1);
if (strcmp(sha1_to_hex(sha1), patch->new_sha1_prefix))
- return error("binary patch to '%s' creates incorrect result", name);
+ return error("binary patch to '%s' creates incorrect result (expecting %s, got %s)", name, patch->new_sha1_prefix, sha1_to_hex(sha1));
}
return 0;
@@ -1657,10 +1876,12 @@ static int apply_fragments(struct buffer_desc *desc, struct patch *patch)
return apply_binary(desc, patch);
while (frag) {
- if (apply_one_fragment(desc, frag, patch->is_reverse,
- patch->inaccurate_eof) < 0)
- return error("patch failed: %s:%ld",
- name, frag->oldpos);
+ if (apply_one_fragment(desc, frag, patch->inaccurate_eof)) {
+ error("patch failed: %s:%ld", name, frag->oldpos);
+ if (!apply_with_reject)
+ return -1;
+ frag->rejected = 1;
+ }
frag = frag->next;
}
return 0;
@@ -1696,8 +1917,9 @@ static int apply_data(struct patch *patch, struct stat *st, struct cache_entry *
desc.size = size;
desc.alloc = alloc;
desc.buffer = buf;
+
if (apply_fragments(&desc, patch) < 0)
- return -1;
+ return -1; /* note with --reject this succeeds. */
/* NUL terminate the result */
if (desc.alloc <= desc.size)
@@ -1707,7 +1929,7 @@ static int apply_data(struct patch *patch, struct stat *st, struct cache_entry *
patch->result = desc.buffer;
patch->resultsize = desc.size;
- if (patch->is_delete && patch->resultsize)
+ if (0 < patch->is_delete && patch->resultsize)
return error("removal patch leaves file contents");
return 0;
@@ -1722,6 +1944,7 @@ static int check_patch(struct patch *patch, struct patch *prev_patch)
struct cache_entry *ce = NULL;
int ok_if_exists;
+ patch->rejected = 1; /* we will drop this after we succeed */
if (old_name) {
int changed = 0;
int stat_ret = 0;
@@ -1778,7 +2001,7 @@ static int check_patch(struct patch *patch, struct patch *prev_patch)
old_name, st_mode, patch->old_mode);
}
- if (new_name && prev_patch && prev_patch->is_delete &&
+ if (new_name && prev_patch && 0 < prev_patch->is_delete &&
!strcmp(prev_patch->old_name, new_name))
/* A type-change diff is always split into a patch to
* delete old, immediately followed by a patch to
@@ -1791,7 +2014,8 @@ static int check_patch(struct patch *patch, struct patch *prev_patch)
else
ok_if_exists = 0;
- if (new_name && (patch->is_new | patch->is_rename | patch->is_copy)) {
+ if (new_name &&
+ ((0 < patch->is_new) | (0 < patch->is_rename) | patch->is_copy)) {
if (check_index &&
cache_name_pos(new_name, strlen(new_name)) >= 0 &&
!ok_if_exists)
@@ -1808,7 +2032,7 @@ static int check_patch(struct patch *patch, struct patch *prev_patch)
return error("%s: %s", new_name, strerror(errno));
}
if (!patch->new_mode) {
- if (patch->is_new)
+ if (0 < patch->is_new)
patch->new_mode = S_IFREG | 0644;
else
patch->new_mode = patch->old_mode;
@@ -1827,24 +2051,23 @@ static int check_patch(struct patch *patch, struct patch *prev_patch)
if (apply_data(patch, &st, ce) < 0)
return error("%s: patch does not apply", name);
+ patch->rejected = 0;
return 0;
}
static int check_patch_list(struct patch *patch)
{
struct patch *prev_patch = NULL;
- int error = 0;
+ int err = 0;
for (prev_patch = NULL; patch ; patch = patch->next) {
- error |= check_patch(patch, prev_patch);
+ if (apply_verbosely)
+ say_patch_name(stderr,
+ "Checking patch ", patch, "...\n");
+ err |= check_patch(patch, prev_patch);
prev_patch = patch;
}
- return error;
-}
-
-static inline int is_null_sha1(const unsigned char *sha1)
-{
- return !memcmp(sha1, null_sha1, 20);
+ return err;
}
static void show_index_list(struct patch *list)
@@ -1860,7 +2083,7 @@ static void show_index_list(struct patch *list)
const char *name;
name = patch->old_name ? patch->old_name : patch->new_name;
- if (patch->is_new)
+ if (0 < patch->is_new)
sha1_ptr = null_sha1;
else if (get_sha1(patch->old_sha1_prefix, sha1))
die("sha1 information is lacking or useless (%s).",
@@ -1901,7 +2124,7 @@ static void numstat_patch_list(struct patch *patch)
quote_c_style(name, NULL, stdout, 0);
else
fputs(name, stdout);
- putchar('\n');
+ putchar(line_termination);
}
}
@@ -2150,23 +2373,99 @@ static void write_out_one_result(struct patch *patch, int phase)
if (phase == 0)
remove_file(patch);
if (phase == 1)
- create_file(patch);
+ create_file(patch);
}
-static void write_out_results(struct patch *list, int skipped_patch)
+static int write_out_one_reject(struct patch *patch)
+{
+ FILE *rej;
+ char namebuf[PATH_MAX];
+ struct fragment *frag;
+ int cnt = 0;
+
+ for (cnt = 0, frag = patch->fragments; frag; frag = frag->next) {
+ if (!frag->rejected)
+ continue;
+ cnt++;
+ }
+
+ if (!cnt) {
+ if (apply_verbosely)
+ say_patch_name(stderr,
+ "Applied patch ", patch, " cleanly.\n");
+ return 0;
+ }
+
+ /* This should not happen, because a removal patch that leaves
+ * contents are marked "rejected" at the patch level.
+ */
+ if (!patch->new_name)
+ die("internal error");
+
+ /* Say this even without --verbose */
+ say_patch_name(stderr, "Applying patch ", patch, " with");
+ fprintf(stderr, " %d rejects...\n", cnt);
+
+ cnt = strlen(patch->new_name);
+ if (ARRAY_SIZE(namebuf) <= cnt + 5) {
+ cnt = ARRAY_SIZE(namebuf) - 5;
+ fprintf(stderr,
+ "warning: truncating .rej filename to %.*s.rej",
+ cnt - 1, patch->new_name);
+ }
+ memcpy(namebuf, patch->new_name, cnt);
+ memcpy(namebuf + cnt, ".rej", 5);
+
+ rej = fopen(namebuf, "w");
+ if (!rej)
+ return error("cannot open %s: %s", namebuf, strerror(errno));
+
+ /* Normal git tools never deal with .rej, so do not pretend
+ * this is a git patch by saying --git nor give extended
+ * headers. While at it, maybe please "kompare" that wants
+ * the trailing TAB and some garbage at the end of line ;-).
+ */
+ fprintf(rej, "diff a/%s b/%s\t(rejected hunks)\n",
+ patch->new_name, patch->new_name);
+ for (cnt = 1, frag = patch->fragments;
+ frag;
+ cnt++, frag = frag->next) {
+ if (!frag->rejected) {
+ fprintf(stderr, "Hunk #%d applied cleanly.\n", cnt);
+ continue;
+ }
+ fprintf(stderr, "Rejected hunk #%d.\n", cnt);
+ fprintf(rej, "%.*s", frag->size, frag->patch);
+ if (frag->patch[frag->size-1] != '\n')
+ fputc('\n', rej);
+ }
+ fclose(rej);
+ return -1;
+}
+
+static int write_out_results(struct patch *list, int skipped_patch)
{
int phase;
+ int errs = 0;
+ struct patch *l;
if (!list && !skipped_patch)
- die("No changes");
+ return error("No changes");
for (phase = 0; phase < 2; phase++) {
- struct patch *l = list;
+ l = list;
while (l) {
- write_out_one_result(l, phase);
+ if (l->rejected)
+ errs = 1;
+ else {
+ write_out_one_result(l, phase);
+ if (phase == 1 && write_out_one_reject(l))
+ errs = 1;
+ }
l = l->next;
}
}
+ return errs;
}
static struct lock_file lock_file;
@@ -2194,8 +2493,7 @@ static int use_patch(struct patch *p)
return 1;
}
-static int apply_patch(int fd, const char *filename,
- int reverse, int inaccurate_eof)
+static int apply_patch(int fd, const char *filename, int inaccurate_eof)
{
unsigned long offset, size;
char *buffer = read_patch_file(fd, &size);
@@ -2215,7 +2513,7 @@ static int apply_patch(int fd, const char *filename,
nr = parse_chunk(buffer + offset, size, patch);
if (nr < 0)
break;
- if (reverse)
+ if (apply_in_reverse)
reverse_patches(patch);
if (use_patch(patch)) {
patch_stats(patch);
@@ -2242,11 +2540,13 @@ static int apply_patch(int fd, const char *filename,
die("unable to read index file");
}
- if ((check || apply) && check_patch_list(list) < 0)
+ if ((check || apply) &&
+ check_patch_list(list) < 0 &&
+ !apply_with_reject)
exit(1);
- if (apply)
- write_out_results(list, skipped_patch);
+ if (apply && write_out_results(list, skipped_patch))
+ exit(1);
if (show_index_info)
show_index_list(list);
@@ -2267,7 +2567,7 @@ static int apply_patch(int fd, const char *filename,
static int git_apply_config(const char *var, const char *value)
{
if (!strcmp(var, "apply.whitespace")) {
- apply_default_whitespace = strdup(value);
+ apply_default_whitespace = xstrdup(value);
return 0;
}
return git_default_config(var, value);
@@ -2278,8 +2578,8 @@ int cmd_apply(int argc, const char **argv, const char *prefix)
{
int i;
int read_stdin = 1;
- int reverse = 0;
int inaccurate_eof = 0;
+ int errs = 0;
const char *whitespace_option = NULL;
@@ -2289,7 +2589,7 @@ int cmd_apply(int argc, const char **argv, const char *prefix)
int fd;
if (!strcmp(arg, "-")) {
- apply_patch(0, "<stdin>", reverse, inaccurate_eof);
+ errs |= apply_patch(0, "<stdin>", inaccurate_eof);
read_stdin = 0;
continue;
}
@@ -2315,8 +2615,7 @@ int cmd_apply(int argc, const char **argv, const char *prefix)
}
if (!strcmp(arg, "--allow-binary-replacement") ||
!strcmp(arg, "--binary")) {
- allow_binary_replacement = 1;
- continue;
+ continue; /* now no-op */
}
if (!strcmp(arg, "--numstat")) {
apply = 0;
@@ -2367,7 +2666,19 @@ int cmd_apply(int argc, const char **argv, const char *prefix)
continue;
}
if (!strcmp(arg, "-R") || !strcmp(arg, "--reverse")) {
- reverse = 1;
+ apply_in_reverse = 1;
+ continue;
+ }
+ if (!strcmp(arg, "--unidiff-zero")) {
+ unidiff_zero = 1;
+ continue;
+ }
+ if (!strcmp(arg, "--reject")) {
+ apply = apply_with_reject = apply_verbosely = 1;
+ continue;
+ }
+ if (!strcmp(arg, "--verbose")) {
+ apply_verbosely = 1;
continue;
}
if (!strcmp(arg, "--inaccurate-eof")) {
@@ -2390,18 +2701,19 @@ int cmd_apply(int argc, const char **argv, const char *prefix)
usage(apply_usage);
read_stdin = 0;
set_default_whitespace_mode(whitespace_option);
- apply_patch(fd, arg, reverse, inaccurate_eof);
+ errs |= apply_patch(fd, arg, inaccurate_eof);
close(fd);
}
set_default_whitespace_mode(whitespace_option);
if (read_stdin)
- apply_patch(0, "<stdin>", reverse, inaccurate_eof);
+ errs |= apply_patch(0, "<stdin>", inaccurate_eof);
if (whitespace_error) {
if (squelch_whitespace_errors &&
squelch_whitespace_errors < whitespace_error) {
int squelched =
whitespace_error - squelch_whitespace_errors;
- fprintf(stderr, "warning: squelched %d whitespace error%s\n",
+ fprintf(stderr, "warning: squelched %d "
+ "whitespace error%s\n",
squelched,
squelched == 1 ? "" : "s");
}
@@ -2429,5 +2741,5 @@ int cmd_apply(int argc, const char **argv, const char *prefix)
die("Unable to write new index file");
}
- return 0;
+ return !!errs;
}
diff --git a/builtin-archive.c b/builtin-archive.c
new file mode 100644
index 0000000000..a8a1f079bf
--- /dev/null
+++ b/builtin-archive.c
@@ -0,0 +1,264 @@
+/*
+ * Copyright (c) 2006 Franck Bui-Huu
+ * Copyright (c) 2006 Rene Scharfe
+ */
+#include <time.h>
+#include "cache.h"
+#include "builtin.h"
+#include "archive.h"
+#include "commit.h"
+#include "tree-walk.h"
+#include "exec_cmd.h"
+#include "pkt-line.h"
+#include "sideband.h"
+
+static const char archive_usage[] = \
+"git-archive --format=<fmt> [--prefix=<prefix>/] [--verbose] [<extra>] <tree-ish> [path...]";
+
+static struct archiver_desc
+{
+ const char *name;
+ write_archive_fn_t write_archive;
+ parse_extra_args_fn_t parse_extra;
+} archivers[] = {
+ { "tar", write_tar_archive, NULL },
+ { "zip", write_zip_archive, parse_extra_zip_args },
+};
+
+static int run_remote_archiver(const char *remote, int argc,
+ const char **argv)
+{
+ char *url, buf[LARGE_PACKET_MAX];
+ int fd[2], i, len, rv;
+ pid_t pid;
+ const char *exec = "git-upload-archive";
+ int exec_at = 0;
+
+ for (i = 1; i < argc; i++) {
+ const char *arg = argv[i];
+ if (!strncmp("--exec=", arg, 7)) {
+ if (exec_at)
+ die("multiple --exec specified");
+ exec = arg + 7;
+ exec_at = i;
+ break;
+ }
+ }
+
+ url = xstrdup(remote);
+ pid = git_connect(fd, url, exec);
+ if (pid < 0)
+ return pid;
+
+ for (i = 1; i < argc; i++) {
+ if (i == exec_at)
+ continue;
+ packet_write(fd[1], "argument %s\n", argv[i]);
+ }
+ packet_flush(fd[1]);
+
+ len = packet_read_line(fd[0], buf, sizeof(buf));
+ if (!len)
+ die("git-archive: expected ACK/NAK, got EOF");
+ if (buf[len-1] == '\n')
+ buf[--len] = 0;
+ if (strcmp(buf, "ACK")) {
+ if (len > 5 && !strncmp(buf, "NACK ", 5))
+ die("git-archive: NACK %s", buf + 5);
+ die("git-archive: protocol error");
+ }
+
+ len = packet_read_line(fd[0], buf, sizeof(buf));
+ if (len)
+ die("git-archive: expected a flush");
+
+ /* Now, start reading from fd[0] and spit it out to stdout */
+ rv = recv_sideband("archive", fd[0], 1, 2);
+ close(fd[0]);
+ rv |= finish_connect(pid);
+
+ return !!rv;
+}
+
+static int init_archiver(const char *name, struct archiver *ar)
+{
+ int rv = -1, i;
+
+ for (i = 0; i < ARRAY_SIZE(archivers); i++) {
+ if (!strcmp(name, archivers[i].name)) {
+ memset(ar, 0, sizeof(*ar));
+ ar->name = archivers[i].name;
+ ar->write_archive = archivers[i].write_archive;
+ ar->parse_extra = archivers[i].parse_extra;
+ rv = 0;
+ break;
+ }
+ }
+ return rv;
+}
+
+void parse_pathspec_arg(const char **pathspec, struct archiver_args *ar_args)
+{
+ ar_args->pathspec = get_pathspec(ar_args->base, pathspec);
+}
+
+void parse_treeish_arg(const char **argv, struct archiver_args *ar_args,
+ const char *prefix)
+{
+ const char *name = argv[0];
+ const unsigned char *commit_sha1;
+ time_t archive_time;
+ struct tree *tree;
+ struct commit *commit;
+ unsigned char sha1[20];
+
+ if (get_sha1(name, sha1))
+ die("Not a valid object name");
+
+ commit = lookup_commit_reference_gently(sha1, 1);
+ if (commit) {
+ commit_sha1 = commit->object.sha1;
+ archive_time = commit->date;
+ } else {
+ commit_sha1 = NULL;
+ archive_time = time(NULL);
+ }
+
+ tree = parse_tree_indirect(sha1);
+ if (tree == NULL)
+ die("not a tree object");
+
+ if (prefix) {
+ unsigned char tree_sha1[20];
+ unsigned int mode;
+ int err;
+
+ err = get_tree_entry(tree->object.sha1, prefix,
+ tree_sha1, &mode);
+ if (err || !S_ISDIR(mode))
+ die("current working directory is untracked");
+
+ free(tree);
+ tree = parse_tree_indirect(tree_sha1);
+ }
+ ar_args->tree = tree;
+ ar_args->commit_sha1 = commit_sha1;
+ ar_args->time = archive_time;
+}
+
+int parse_archive_args(int argc, const char **argv, struct archiver *ar)
+{
+ const char *extra_argv[MAX_EXTRA_ARGS];
+ int extra_argc = 0;
+ const char *format = NULL; /* might want to default to "tar" */
+ const char *base = "";
+ int verbose = 0;
+ int i;
+
+ for (i = 1; i < argc; i++) {
+ const char *arg = argv[i];
+
+ if (!strcmp(arg, "--list") || !strcmp(arg, "-l")) {
+ for (i = 0; i < ARRAY_SIZE(archivers); i++)
+ printf("%s\n", archivers[i].name);
+ exit(0);
+ }
+ if (!strcmp(arg, "--verbose") || !strcmp(arg, "-v")) {
+ verbose = 1;
+ continue;
+ }
+ if (!strncmp(arg, "--format=", 9)) {
+ format = arg + 9;
+ continue;
+ }
+ if (!strncmp(arg, "--prefix=", 9)) {
+ base = arg + 9;
+ continue;
+ }
+ if (!strcmp(arg, "--")) {
+ i++;
+ break;
+ }
+ if (arg[0] == '-') {
+ if (extra_argc > MAX_EXTRA_ARGS - 1)
+ die("Too many extra options");
+ extra_argv[extra_argc++] = arg;
+ continue;
+ }
+ break;
+ }
+
+ /* We need at least one parameter -- tree-ish */
+ if (argc - 1 < i)
+ usage(archive_usage);
+ if (!format)
+ die("You must specify an archive format");
+ if (init_archiver(format, ar) < 0)
+ die("Unknown archive format '%s'", format);
+
+ if (extra_argc) {
+ if (!ar->parse_extra)
+ die("'%s' format does not handle %s",
+ ar->name, extra_argv[0]);
+ ar->args.extra = ar->parse_extra(extra_argc, extra_argv);
+ }
+ ar->args.verbose = verbose;
+ ar->args.base = base;
+
+ return i;
+}
+
+static const char *extract_remote_arg(int *ac, const char **av)
+{
+ int ix, iy, cnt = *ac;
+ int no_more_options = 0;
+ const char *remote = NULL;
+
+ for (ix = iy = 1; ix < cnt; ix++) {
+ const char *arg = av[ix];
+ if (!strcmp(arg, "--"))
+ no_more_options = 1;
+ if (!no_more_options) {
+ if (!strncmp(arg, "--remote=", 9)) {
+ if (remote)
+ die("Multiple --remote specified");
+ remote = arg + 9;
+ continue;
+ }
+ if (arg[0] != '-')
+ no_more_options = 1;
+ }
+ if (ix != iy)
+ av[iy] = arg;
+ iy++;
+ }
+ if (remote) {
+ av[--cnt] = NULL;
+ *ac = cnt;
+ }
+ return remote;
+}
+
+int cmd_archive(int argc, const char **argv, const char *prefix)
+{
+ struct archiver ar;
+ int tree_idx;
+ const char *remote = NULL;
+
+ remote = extract_remote_arg(&argc, argv);
+ if (remote)
+ return run_remote_archiver(remote, argc, argv);
+
+ setvbuf(stderr, NULL, _IOLBF, BUFSIZ);
+
+ memset(&ar, 0, sizeof(ar));
+ tree_idx = parse_archive_args(argc, argv, &ar);
+ if (prefix == NULL)
+ prefix = setup_git_directory();
+
+ argv += tree_idx;
+ parse_treeish_arg(argv, &ar.args, prefix);
+ parse_pathspec_arg(argv + 1, &ar.args);
+
+ return ar.write_archive(&ar.args);
+}
diff --git a/builtin-blame.c b/builtin-blame.c
new file mode 100644
index 0000000000..066dee743e
--- /dev/null
+++ b/builtin-blame.c
@@ -0,0 +1,1890 @@
+/*
+ * Pickaxe
+ *
+ * Copyright (c) 2006, Junio C Hamano
+ */
+
+#include "cache.h"
+#include "builtin.h"
+#include "blob.h"
+#include "commit.h"
+#include "tag.h"
+#include "tree-walk.h"
+#include "diff.h"
+#include "diffcore.h"
+#include "revision.h"
+#include "xdiff-interface.h"
+
+#include <time.h>
+#include <sys/time.h>
+#include <regex.h>
+
+static char blame_usage[] =
+"git-blame [-c] [-l] [-t] [-f] [-n] [-p] [-L n,m] [-S <revs-file>] [-M] [-C] [-C] [commit] [--] file\n"
+" -c, --compatibility Use the same output mode as git-annotate (Default: off)\n"
+" -l, --long Show long commit SHA1 (Default: off)\n"
+" -t, --time Show raw timestamp (Default: off)\n"
+" -f, --show-name Show original filename (Default: auto)\n"
+" -n, --show-number Show original linenumber (Default: off)\n"
+" -p, --porcelain Show in a format designed for machine consumption\n"
+" -L n,m Process only line range n,m, counting from 1\n"
+" -M, -C Find line movements within and across files\n"
+" -S revs-file Use revisions from revs-file instead of calling git-rev-list\n";
+
+static int longest_file;
+static int longest_author;
+static int max_orig_digits;
+static int max_digits;
+static int max_score_digits;
+
+#ifndef DEBUG
+#define DEBUG 0
+#endif
+
+/* stats */
+static int num_read_blob;
+static int num_get_patch;
+static int num_commits;
+
+#define PICKAXE_BLAME_MOVE 01
+#define PICKAXE_BLAME_COPY 02
+#define PICKAXE_BLAME_COPY_HARDER 04
+
+/*
+ * blame for a blame_entry with score lower than these thresholds
+ * is not passed to the parent using move/copy logic.
+ */
+static unsigned blame_move_score;
+static unsigned blame_copy_score;
+#define BLAME_DEFAULT_MOVE_SCORE 20
+#define BLAME_DEFAULT_COPY_SCORE 40
+
+/* bits #0..7 in revision.h, #8..11 used for merge_bases() in commit.c */
+#define METAINFO_SHOWN (1u<<12)
+#define MORE_THAN_ONE_PATH (1u<<13)
+
+/*
+ * One blob in a commit that is being suspected
+ */
+struct origin {
+ int refcnt;
+ struct commit *commit;
+ mmfile_t file;
+ unsigned char blob_sha1[20];
+ char path[FLEX_ARRAY];
+};
+
+static char *fill_origin_blob(struct origin *o, mmfile_t *file)
+{
+ if (!o->file.ptr) {
+ char type[10];
+ num_read_blob++;
+ file->ptr = read_sha1_file(o->blob_sha1, type,
+ (unsigned long *)(&(file->size)));
+ o->file = *file;
+ }
+ else
+ *file = o->file;
+ return file->ptr;
+}
+
+static inline struct origin *origin_incref(struct origin *o)
+{
+ if (o)
+ o->refcnt++;
+ return o;
+}
+
+static void origin_decref(struct origin *o)
+{
+ if (o && --o->refcnt <= 0) {
+ if (o->file.ptr)
+ free(o->file.ptr);
+ memset(o, 0, sizeof(*o));
+ free(o);
+ }
+}
+
+struct blame_entry {
+ struct blame_entry *prev;
+ struct blame_entry *next;
+
+ /* the first line of this group in the final image;
+ * internally all line numbers are 0 based.
+ */
+ int lno;
+
+ /* how many lines this group has */
+ int num_lines;
+
+ /* the commit that introduced this group into the final image */
+ struct origin *suspect;
+
+ /* true if the suspect is truly guilty; false while we have not
+ * checked if the group came from one of its parents.
+ */
+ char guilty;
+
+ /* the line number of the first line of this group in the
+ * suspect's file; internally all line numbers are 0 based.
+ */
+ int s_lno;
+
+ /* how significant this entry is -- cached to avoid
+ * scanning the lines over and over
+ */
+ unsigned score;
+};
+
+struct scoreboard {
+ /* the final commit (i.e. where we started digging from) */
+ struct commit *final;
+
+ const char *path;
+
+ /* the contents in the final; pointed into by buf pointers of
+ * blame_entries
+ */
+ const char *final_buf;
+ unsigned long final_buf_size;
+
+ /* linked list of blames */
+ struct blame_entry *ent;
+
+ /* look-up a line in the final buffer */
+ int num_lines;
+ int *lineno;
+};
+
+static int cmp_suspect(struct origin *a, struct origin *b)
+{
+ int cmp = hashcmp(a->commit->object.sha1, b->commit->object.sha1);
+ if (cmp)
+ return cmp;
+ return strcmp(a->path, b->path);
+}
+
+#define cmp_suspect(a, b) ( ((a)==(b)) ? 0 : cmp_suspect(a,b) )
+
+static void sanity_check_refcnt(struct scoreboard *);
+
+static void coalesce(struct scoreboard *sb)
+{
+ struct blame_entry *ent, *next;
+
+ for (ent = sb->ent; ent && (next = ent->next); ent = next) {
+ if (!cmp_suspect(ent->suspect, next->suspect) &&
+ ent->guilty == next->guilty &&
+ ent->s_lno + ent->num_lines == next->s_lno) {
+ ent->num_lines += next->num_lines;
+ ent->next = next->next;
+ if (ent->next)
+ ent->next->prev = ent;
+ origin_decref(next->suspect);
+ free(next);
+ ent->score = 0;
+ next = ent; /* again */
+ }
+ }
+
+ if (DEBUG) /* sanity */
+ sanity_check_refcnt(sb);
+}
+
+static struct origin *make_origin(struct commit *commit, const char *path)
+{
+ struct origin *o;
+ o = xcalloc(1, sizeof(*o) + strlen(path) + 1);
+ o->commit = commit;
+ o->refcnt = 1;
+ strcpy(o->path, path);
+ return o;
+}
+
+static struct origin *get_origin(struct scoreboard *sb,
+ struct commit *commit,
+ const char *path)
+{
+ struct blame_entry *e;
+
+ for (e = sb->ent; e; e = e->next) {
+ if (e->suspect->commit == commit &&
+ !strcmp(e->suspect->path, path))
+ return origin_incref(e->suspect);
+ }
+ return make_origin(commit, path);
+}
+
+static int fill_blob_sha1(struct origin *origin)
+{
+ unsigned mode;
+ char type[10];
+
+ if (!is_null_sha1(origin->blob_sha1))
+ return 0;
+ if (get_tree_entry(origin->commit->object.sha1,
+ origin->path,
+ origin->blob_sha1, &mode))
+ goto error_out;
+ if (sha1_object_info(origin->blob_sha1, type, NULL) ||
+ strcmp(type, blob_type))
+ goto error_out;
+ return 0;
+ error_out:
+ hashclr(origin->blob_sha1);
+ return -1;
+}
+
+static struct origin *find_origin(struct scoreboard *sb,
+ struct commit *parent,
+ struct origin *origin)
+{
+ struct origin *porigin = NULL;
+ struct diff_options diff_opts;
+ const char *paths[2];
+
+ if (parent->util) {
+ /* This is a freestanding copy of origin and not
+ * refcounted.
+ */
+ struct origin *cached = parent->util;
+ if (!strcmp(cached->path, origin->path)) {
+ porigin = get_origin(sb, parent, cached->path);
+ if (porigin->refcnt == 1)
+ hashcpy(porigin->blob_sha1, cached->blob_sha1);
+ return porigin;
+ }
+ /* otherwise it was not very useful; free it */
+ free(parent->util);
+ parent->util = NULL;
+ }
+
+ /* See if the origin->path is different between parent
+ * and origin first. Most of the time they are the
+ * same and diff-tree is fairly efficient about this.
+ */
+ diff_setup(&diff_opts);
+ diff_opts.recursive = 1;
+ diff_opts.detect_rename = 0;
+ diff_opts.output_format = DIFF_FORMAT_NO_OUTPUT;
+ paths[0] = origin->path;
+ paths[1] = NULL;
+
+ diff_tree_setup_paths(paths, &diff_opts);
+ if (diff_setup_done(&diff_opts) < 0)
+ die("diff-setup");
+ diff_tree_sha1(parent->tree->object.sha1,
+ origin->commit->tree->object.sha1,
+ "", &diff_opts);
+ diffcore_std(&diff_opts);
+
+ /* It is either one entry that says "modified", or "created",
+ * or nothing.
+ */
+ if (!diff_queued_diff.nr) {
+ /* The path is the same as parent */
+ porigin = get_origin(sb, parent, origin->path);
+ hashcpy(porigin->blob_sha1, origin->blob_sha1);
+ }
+ else if (diff_queued_diff.nr != 1)
+ die("internal error in blame::find_origin");
+ else {
+ struct diff_filepair *p = diff_queued_diff.queue[0];
+ switch (p->status) {
+ default:
+ die("internal error in blame::find_origin (%c)",
+ p->status);
+ case 'M':
+ porigin = get_origin(sb, parent, origin->path);
+ hashcpy(porigin->blob_sha1, p->one->sha1);
+ break;
+ case 'A':
+ case 'T':
+ /* Did not exist in parent, or type changed */
+ break;
+ }
+ }
+ diff_flush(&diff_opts);
+ if (porigin) {
+ struct origin *cached;
+ cached = make_origin(porigin->commit, porigin->path);
+ hashcpy(cached->blob_sha1, porigin->blob_sha1);
+ parent->util = cached;
+ }
+ return porigin;
+}
+
+static struct origin *find_rename(struct scoreboard *sb,
+ struct commit *parent,
+ struct origin *origin)
+{
+ struct origin *porigin = NULL;
+ struct diff_options diff_opts;
+ int i;
+ const char *paths[2];
+
+ diff_setup(&diff_opts);
+ diff_opts.recursive = 1;
+ diff_opts.detect_rename = DIFF_DETECT_RENAME;
+ diff_opts.output_format = DIFF_FORMAT_NO_OUTPUT;
+ diff_opts.single_follow = origin->path;
+ paths[0] = NULL;
+ diff_tree_setup_paths(paths, &diff_opts);
+ if (diff_setup_done(&diff_opts) < 0)
+ die("diff-setup");
+ diff_tree_sha1(parent->tree->object.sha1,
+ origin->commit->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' || p->status == 'C') &&
+ !strcmp(p->two->path, origin->path)) {
+ porigin = get_origin(sb, parent, p->one->path);
+ hashcpy(porigin->blob_sha1, p->one->sha1);
+ break;
+ }
+ }
+ diff_flush(&diff_opts);
+ return porigin;
+}
+
+struct chunk {
+ /* line number in postimage; up to but not including this
+ * line is the same as preimage
+ */
+ int same;
+
+ /* preimage line number after this chunk */
+ int p_next;
+
+ /* postimage line number after this chunk */
+ int t_next;
+};
+
+struct patch {
+ struct chunk *chunks;
+ int num;
+};
+
+struct blame_diff_state {
+ struct xdiff_emit_state xm;
+ struct patch *ret;
+ unsigned hunk_post_context;
+ unsigned hunk_in_pre_context : 1;
+};
+
+static void process_u_diff(void *state_, char *line, unsigned long len)
+{
+ struct blame_diff_state *state = state_;
+ struct chunk *chunk;
+ int off1, off2, len1, len2, num;
+
+ num = state->ret->num;
+ if (len < 4 || line[0] != '@' || line[1] != '@') {
+ if (state->hunk_in_pre_context && line[0] == ' ')
+ state->ret->chunks[num - 1].same++;
+ else {
+ state->hunk_in_pre_context = 0;
+ if (line[0] == ' ')
+ state->hunk_post_context++;
+ else
+ state->hunk_post_context = 0;
+ }
+ return;
+ }
+
+ if (num && state->hunk_post_context) {
+ chunk = &state->ret->chunks[num - 1];
+ chunk->p_next -= state->hunk_post_context;
+ chunk->t_next -= state->hunk_post_context;
+ }
+ state->ret->num = ++num;
+ state->ret->chunks = xrealloc(state->ret->chunks,
+ sizeof(struct chunk) * num);
+ chunk = &state->ret->chunks[num - 1];
+ if (parse_hunk_header(line, len, &off1, &len1, &off2, &len2)) {
+ state->ret->num--;
+ return;
+ }
+
+ /* Line numbers in patch output are one based. */
+ off1--;
+ off2--;
+
+ chunk->same = len2 ? off2 : (off2 + 1);
+
+ chunk->p_next = off1 + (len1 ? len1 : 1);
+ chunk->t_next = chunk->same + len2;
+ state->hunk_in_pre_context = 1;
+ state->hunk_post_context = 0;
+}
+
+static struct patch *compare_buffer(mmfile_t *file_p, mmfile_t *file_o,
+ int context)
+{
+ struct blame_diff_state state;
+ xpparam_t xpp;
+ xdemitconf_t xecfg;
+ xdemitcb_t ecb;
+
+ xpp.flags = XDF_NEED_MINIMAL;
+ xecfg.ctxlen = context;
+ xecfg.flags = 0;
+ ecb.outf = xdiff_outf;
+ ecb.priv = &state;
+ memset(&state, 0, sizeof(state));
+ state.xm.consume = process_u_diff;
+ state.ret = xmalloc(sizeof(struct patch));
+ state.ret->chunks = NULL;
+ state.ret->num = 0;
+
+ xdl_diff(file_p, file_o, &xpp, &xecfg, &ecb);
+
+ if (state.ret->num) {
+ struct chunk *chunk;
+ chunk = &state.ret->chunks[state.ret->num - 1];
+ chunk->p_next -= state.hunk_post_context;
+ chunk->t_next -= state.hunk_post_context;
+ }
+ return state.ret;
+}
+
+static struct patch *get_patch(struct origin *parent, struct origin *origin)
+{
+ mmfile_t file_p, file_o;
+ struct patch *patch;
+
+ fill_origin_blob(parent, &file_p);
+ fill_origin_blob(origin, &file_o);
+ if (!file_p.ptr || !file_o.ptr)
+ return NULL;
+ patch = compare_buffer(&file_p, &file_o, 0);
+ num_get_patch++;
+ return patch;
+}
+
+static void free_patch(struct patch *p)
+{
+ free(p->chunks);
+ free(p);
+}
+
+static void add_blame_entry(struct scoreboard *sb, struct blame_entry *e)
+{
+ struct blame_entry *ent, *prev = NULL;
+
+ origin_incref(e->suspect);
+
+ for (ent = sb->ent; ent && ent->lno < e->lno; ent = ent->next)
+ prev = ent;
+
+ /* prev, if not NULL, is the last one that is below e */
+ e->prev = prev;
+ if (prev) {
+ e->next = prev->next;
+ prev->next = e;
+ }
+ else {
+ e->next = sb->ent;
+ sb->ent = e;
+ }
+ if (e->next)
+ e->next->prev = e;
+}
+
+static void dup_entry(struct blame_entry *dst, struct blame_entry *src)
+{
+ struct blame_entry *p, *n;
+
+ p = dst->prev;
+ n = dst->next;
+ origin_incref(src->suspect);
+ origin_decref(dst->suspect);
+ memcpy(dst, src, sizeof(*src));
+ dst->prev = p;
+ dst->next = n;
+ dst->score = 0;
+}
+
+static const char *nth_line(struct scoreboard *sb, int lno)
+{
+ return sb->final_buf + sb->lineno[lno];
+}
+
+static void split_overlap(struct blame_entry *split,
+ struct blame_entry *e,
+ int tlno, int plno, int same,
+ struct origin *parent)
+{
+ /* it is known that lines between tlno to same came from
+ * parent, and e has an overlap with that range. it also is
+ * known that parent's line plno corresponds to e's line tlno.
+ *
+ * <---- e ----->
+ * <------>
+ * <------------>
+ * <------------>
+ * <------------------>
+ *
+ * Potentially we need to split e into three parts; before
+ * this chunk, the chunk to be blamed for parent, and after
+ * that portion.
+ */
+ int chunk_end_lno;
+ memset(split, 0, sizeof(struct blame_entry [3]));
+
+ if (e->s_lno < tlno) {
+ /* there is a pre-chunk part not blamed on parent */
+ split[0].suspect = origin_incref(e->suspect);
+ split[0].lno = e->lno;
+ split[0].s_lno = e->s_lno;
+ split[0].num_lines = tlno - e->s_lno;
+ split[1].lno = e->lno + tlno - e->s_lno;
+ split[1].s_lno = plno;
+ }
+ else {
+ split[1].lno = e->lno;
+ split[1].s_lno = plno + (e->s_lno - tlno);
+ }
+
+ if (same < e->s_lno + e->num_lines) {
+ /* there is a post-chunk part not blamed on parent */
+ split[2].suspect = origin_incref(e->suspect);
+ split[2].lno = e->lno + (same - e->s_lno);
+ split[2].s_lno = e->s_lno + (same - e->s_lno);
+ split[2].num_lines = e->s_lno + e->num_lines - same;
+ chunk_end_lno = split[2].lno;
+ }
+ else
+ chunk_end_lno = e->lno + e->num_lines;
+ split[1].num_lines = chunk_end_lno - split[1].lno;
+
+ if (split[1].num_lines < 1)
+ return;
+ split[1].suspect = origin_incref(parent);
+}
+
+static void split_blame(struct scoreboard *sb,
+ struct blame_entry *split,
+ struct blame_entry *e)
+{
+ struct blame_entry *new_entry;
+
+ if (split[0].suspect && split[2].suspect) {
+ /* we need to split e into two and add another for parent */
+ dup_entry(e, &split[0]);
+
+ new_entry = xmalloc(sizeof(*new_entry));
+ memcpy(new_entry, &(split[2]), sizeof(struct blame_entry));
+ add_blame_entry(sb, new_entry);
+
+ new_entry = xmalloc(sizeof(*new_entry));
+ memcpy(new_entry, &(split[1]), sizeof(struct blame_entry));
+ add_blame_entry(sb, new_entry);
+ }
+ else if (!split[0].suspect && !split[2].suspect)
+ /* parent covers the entire area */
+ dup_entry(e, &split[1]);
+ else if (split[0].suspect) {
+ dup_entry(e, &split[0]);
+
+ new_entry = xmalloc(sizeof(*new_entry));
+ memcpy(new_entry, &(split[1]), sizeof(struct blame_entry));
+ add_blame_entry(sb, new_entry);
+ }
+ else {
+ dup_entry(e, &split[1]);
+
+ new_entry = xmalloc(sizeof(*new_entry));
+ memcpy(new_entry, &(split[2]), sizeof(struct blame_entry));
+ add_blame_entry(sb, new_entry);
+ }
+
+ if (DEBUG) { /* sanity */
+ struct blame_entry *ent;
+ int lno = sb->ent->lno, corrupt = 0;
+
+ for (ent = sb->ent; ent; ent = ent->next) {
+ if (lno != ent->lno)
+ corrupt = 1;
+ if (ent->s_lno < 0)
+ corrupt = 1;
+ lno += ent->num_lines;
+ }
+ if (corrupt) {
+ lno = sb->ent->lno;
+ for (ent = sb->ent; ent; ent = ent->next) {
+ printf("L %8d l %8d n %8d\n",
+ lno, ent->lno, ent->num_lines);
+ lno = ent->lno + ent->num_lines;
+ }
+ die("oops");
+ }
+ }
+}
+
+static void decref_split(struct blame_entry *split)
+{
+ int i;
+
+ for (i = 0; i < 3; i++)
+ origin_decref(split[i].suspect);
+}
+
+static void blame_overlap(struct scoreboard *sb, struct blame_entry *e,
+ int tlno, int plno, int same,
+ struct origin *parent)
+{
+ struct blame_entry split[3];
+
+ split_overlap(split, e, tlno, plno, same, parent);
+ if (split[1].suspect)
+ split_blame(sb, split, e);
+ decref_split(split);
+}
+
+static int find_last_in_target(struct scoreboard *sb, struct origin *target)
+{
+ struct blame_entry *e;
+ int last_in_target = -1;
+
+ for (e = sb->ent; e; e = e->next) {
+ if (e->guilty || cmp_suspect(e->suspect, target))
+ continue;
+ if (last_in_target < e->s_lno + e->num_lines)
+ last_in_target = e->s_lno + e->num_lines;
+ }
+ return last_in_target;
+}
+
+static void blame_chunk(struct scoreboard *sb,
+ int tlno, int plno, int same,
+ struct origin *target, struct origin *parent)
+{
+ struct blame_entry *e;
+
+ for (e = sb->ent; e; e = e->next) {
+ if (e->guilty || cmp_suspect(e->suspect, target))
+ continue;
+ if (same <= e->s_lno)
+ continue;
+ if (tlno < e->s_lno + e->num_lines)
+ blame_overlap(sb, e, tlno, plno, same, parent);
+ }
+}
+
+static int pass_blame_to_parent(struct scoreboard *sb,
+ struct origin *target,
+ struct origin *parent)
+{
+ int i, last_in_target, plno, tlno;
+ struct patch *patch;
+
+ last_in_target = find_last_in_target(sb, target);
+ if (last_in_target < 0)
+ return 1; /* nothing remains for this target */
+
+ patch = get_patch(parent, target);
+ plno = tlno = 0;
+ for (i = 0; i < patch->num; i++) {
+ struct chunk *chunk = &patch->chunks[i];
+
+ blame_chunk(sb, tlno, plno, chunk->same, target, parent);
+ plno = chunk->p_next;
+ tlno = chunk->t_next;
+ }
+ /* rest (i.e. anything above tlno) are the same as parent */
+ blame_chunk(sb, tlno, plno, last_in_target, target, parent);
+
+ free_patch(patch);
+ return 0;
+}
+
+static unsigned ent_score(struct scoreboard *sb, struct blame_entry *e)
+{
+ unsigned score;
+ const char *cp, *ep;
+
+ if (e->score)
+ return e->score;
+
+ score = 1;
+ cp = nth_line(sb, e->lno);
+ ep = nth_line(sb, e->lno + e->num_lines);
+ while (cp < ep) {
+ unsigned ch = *((unsigned char *)cp);
+ if (isalnum(ch))
+ score++;
+ cp++;
+ }
+ e->score = score;
+ return score;
+}
+
+static void copy_split_if_better(struct scoreboard *sb,
+ struct blame_entry *best_so_far,
+ struct blame_entry *this)
+{
+ int i;
+
+ if (!this[1].suspect)
+ return;
+ if (best_so_far[1].suspect) {
+ if (ent_score(sb, &this[1]) < ent_score(sb, &best_so_far[1]))
+ return;
+ }
+
+ for (i = 0; i < 3; i++)
+ origin_incref(this[i].suspect);
+ decref_split(best_so_far);
+ memcpy(best_so_far, this, sizeof(struct blame_entry [3]));
+}
+
+static void find_copy_in_blob(struct scoreboard *sb,
+ struct blame_entry *ent,
+ struct origin *parent,
+ struct blame_entry *split,
+ mmfile_t *file_p)
+{
+ const char *cp;
+ int cnt;
+ mmfile_t file_o;
+ struct patch *patch;
+ int i, plno, tlno;
+
+ cp = nth_line(sb, ent->lno);
+ file_o.ptr = (char*) cp;
+ cnt = ent->num_lines;
+
+ while (cnt && cp < sb->final_buf + sb->final_buf_size) {
+ if (*cp++ == '\n')
+ cnt--;
+ }
+ file_o.size = cp - file_o.ptr;
+
+ patch = compare_buffer(file_p, &file_o, 1);
+
+ memset(split, 0, sizeof(struct blame_entry [3]));
+ plno = tlno = 0;
+ for (i = 0; i < patch->num; i++) {
+ struct chunk *chunk = &patch->chunks[i];
+
+ /* tlno to chunk->same are the same as ent */
+ if (ent->num_lines <= tlno)
+ break;
+ if (tlno < chunk->same) {
+ struct blame_entry this[3];
+ split_overlap(this, ent,
+ tlno + ent->s_lno, plno,
+ chunk->same + ent->s_lno,
+ parent);
+ copy_split_if_better(sb, split, this);
+ decref_split(this);
+ }
+ plno = chunk->p_next;
+ tlno = chunk->t_next;
+ }
+ free_patch(patch);
+}
+
+static int find_move_in_parent(struct scoreboard *sb,
+ struct origin *target,
+ struct origin *parent)
+{
+ int last_in_target, made_progress;
+ struct blame_entry *e, split[3];
+ mmfile_t file_p;
+
+ last_in_target = find_last_in_target(sb, target);
+ if (last_in_target < 0)
+ return 1; /* nothing remains for this target */
+
+ fill_origin_blob(parent, &file_p);
+ if (!file_p.ptr)
+ return 0;
+
+ made_progress = 1;
+ while (made_progress) {
+ made_progress = 0;
+ for (e = sb->ent; e; e = e->next) {
+ if (e->guilty || cmp_suspect(e->suspect, target))
+ continue;
+ find_copy_in_blob(sb, e, parent, split, &file_p);
+ if (split[1].suspect &&
+ blame_move_score < ent_score(sb, &split[1])) {
+ split_blame(sb, split, e);
+ made_progress = 1;
+ }
+ decref_split(split);
+ }
+ }
+ return 0;
+}
+
+
+struct blame_list {
+ struct blame_entry *ent;
+ struct blame_entry split[3];
+};
+
+static struct blame_list *setup_blame_list(struct scoreboard *sb,
+ struct origin *target,
+ int *num_ents_p)
+{
+ struct blame_entry *e;
+ int num_ents, i;
+ struct blame_list *blame_list = NULL;
+
+ /* Count the number of entries the target is suspected for,
+ * and prepare a list of entry and the best split.
+ */
+ for (e = sb->ent, num_ents = 0; e; e = e->next)
+ if (!e->guilty && !cmp_suspect(e->suspect, target))
+ num_ents++;
+ if (num_ents) {
+ blame_list = xcalloc(num_ents, sizeof(struct blame_list));
+ for (e = sb->ent, i = 0; e; e = e->next)
+ if (!e->guilty && !cmp_suspect(e->suspect, target))
+ blame_list[i++].ent = e;
+ }
+ *num_ents_p = num_ents;
+ return blame_list;
+}
+
+static int find_copy_in_parent(struct scoreboard *sb,
+ struct origin *target,
+ struct commit *parent,
+ struct origin *porigin,
+ int opt)
+{
+ struct diff_options diff_opts;
+ const char *paths[1];
+ int i, j;
+ int retval;
+ struct blame_list *blame_list;
+ int num_ents;
+
+ blame_list = setup_blame_list(sb, target, &num_ents);
+ if (!blame_list)
+ return 1; /* nothing remains for this target */
+
+ diff_setup(&diff_opts);
+ diff_opts.recursive = 1;
+ diff_opts.output_format = DIFF_FORMAT_NO_OUTPUT;
+
+ paths[0] = NULL;
+ diff_tree_setup_paths(paths, &diff_opts);
+ if (diff_setup_done(&diff_opts) < 0)
+ die("diff-setup");
+
+ /* Try "find copies harder" on new path if requested;
+ * we do not want to use diffcore_rename() actually to
+ * match things up; find_copies_harder is set only to
+ * force diff_tree_sha1() to feed all filepairs to diff_queue,
+ * and this code needs to be after diff_setup_done(), which
+ * usually makes find-copies-harder imply copy detection.
+ */
+ if ((opt & PICKAXE_BLAME_COPY_HARDER) &&
+ (!porigin || strcmp(target->path, porigin->path)))
+ diff_opts.find_copies_harder = 1;
+
+ diff_tree_sha1(parent->tree->object.sha1,
+ target->commit->tree->object.sha1,
+ "", &diff_opts);
+
+ if (!diff_opts.find_copies_harder)
+ diffcore_std(&diff_opts);
+
+ retval = 0;
+ while (1) {
+ int made_progress = 0;
+
+ for (i = 0; i < diff_queued_diff.nr; i++) {
+ struct diff_filepair *p = diff_queued_diff.queue[i];
+ struct origin *norigin;
+ mmfile_t file_p;
+ struct blame_entry this[3];
+
+ if (!DIFF_FILE_VALID(p->one))
+ continue; /* does not exist in parent */
+ if (porigin && !strcmp(p->one->path, porigin->path))
+ /* find_move already dealt with this path */
+ continue;
+
+ norigin = get_origin(sb, parent, p->one->path);
+ hashcpy(norigin->blob_sha1, p->one->sha1);
+ fill_origin_blob(norigin, &file_p);
+ if (!file_p.ptr)
+ continue;
+
+ for (j = 0; j < num_ents; j++) {
+ find_copy_in_blob(sb, blame_list[j].ent,
+ norigin, this, &file_p);
+ copy_split_if_better(sb, blame_list[j].split,
+ this);
+ decref_split(this);
+ }
+ origin_decref(norigin);
+ }
+
+ for (j = 0; j < num_ents; j++) {
+ struct blame_entry *split = blame_list[j].split;
+ if (split[1].suspect &&
+ blame_copy_score < ent_score(sb, &split[1])) {
+ split_blame(sb, split, blame_list[j].ent);
+ made_progress = 1;
+ }
+ decref_split(split);
+ }
+ free(blame_list);
+
+ if (!made_progress)
+ break;
+ blame_list = setup_blame_list(sb, target, &num_ents);
+ if (!blame_list) {
+ retval = 1;
+ break;
+ }
+ }
+ diff_flush(&diff_opts);
+
+ return retval;
+}
+
+/* The blobs of origin and porigin exactly match, so everything
+ * origin is suspected for can be blamed on the parent.
+ */
+static void pass_whole_blame(struct scoreboard *sb,
+ struct origin *origin, struct origin *porigin)
+{
+ struct blame_entry *e;
+
+ if (!porigin->file.ptr && origin->file.ptr) {
+ /* Steal its file */
+ porigin->file = origin->file;
+ origin->file.ptr = NULL;
+ }
+ for (e = sb->ent; e; e = e->next) {
+ if (cmp_suspect(e->suspect, origin))
+ continue;
+ origin_incref(porigin);
+ origin_decref(e->suspect);
+ e->suspect = porigin;
+ }
+}
+
+#define MAXPARENT 16
+
+static void pass_blame(struct scoreboard *sb, struct origin *origin, int opt)
+{
+ int i, pass;
+ struct commit *commit = origin->commit;
+ struct commit_list *parent;
+ struct origin *parent_origin[MAXPARENT], *porigin;
+
+ memset(parent_origin, 0, sizeof(parent_origin));
+
+ /* The first pass looks for unrenamed path to optimize for
+ * common cases, then we look for renames in the second pass.
+ */
+ for (pass = 0; pass < 2; pass++) {
+ struct origin *(*find)(struct scoreboard *,
+ struct commit *, struct origin *);
+ find = pass ? find_rename : find_origin;
+
+ for (i = 0, parent = commit->parents;
+ i < MAXPARENT && parent;
+ parent = parent->next, i++) {
+ struct commit *p = parent->item;
+ int j, same;
+
+ if (parent_origin[i])
+ continue;
+ if (parse_commit(p))
+ continue;
+ porigin = find(sb, p, origin);
+ if (!porigin)
+ continue;
+ if (!hashcmp(porigin->blob_sha1, origin->blob_sha1)) {
+ pass_whole_blame(sb, origin, porigin);
+ origin_decref(porigin);
+ goto finish;
+ }
+ for (j = same = 0; j < i; j++)
+ if (parent_origin[j] &&
+ !hashcmp(parent_origin[j]->blob_sha1,
+ porigin->blob_sha1)) {
+ same = 1;
+ break;
+ }
+ if (!same)
+ parent_origin[i] = porigin;
+ else
+ origin_decref(porigin);
+ }
+ }
+
+ num_commits++;
+ for (i = 0, parent = commit->parents;
+ i < MAXPARENT && parent;
+ parent = parent->next, i++) {
+ struct origin *porigin = parent_origin[i];
+ if (!porigin)
+ continue;
+ if (pass_blame_to_parent(sb, origin, porigin))
+ goto finish;
+ }
+
+ /*
+ * Optionally run "miff" to find moves in parents' files here.
+ */
+ if (opt & PICKAXE_BLAME_MOVE)
+ for (i = 0, parent = commit->parents;
+ i < MAXPARENT && parent;
+ parent = parent->next, i++) {
+ struct origin *porigin = parent_origin[i];
+ if (!porigin)
+ continue;
+ if (find_move_in_parent(sb, origin, porigin))
+ goto finish;
+ }
+
+ /*
+ * Optionally run "ciff" to find copies from parents' files here.
+ */
+ if (opt & PICKAXE_BLAME_COPY)
+ for (i = 0, parent = commit->parents;
+ i < MAXPARENT && parent;
+ parent = parent->next, i++) {
+ struct origin *porigin = parent_origin[i];
+ if (find_copy_in_parent(sb, origin, parent->item,
+ porigin, opt))
+ goto finish;
+ }
+
+ finish:
+ for (i = 0; i < MAXPARENT; i++)
+ origin_decref(parent_origin[i]);
+}
+
+static void assign_blame(struct scoreboard *sb, struct rev_info *revs, int opt)
+{
+ while (1) {
+ struct blame_entry *ent;
+ struct commit *commit;
+ struct origin *suspect = NULL;
+
+ /* find one suspect to break down */
+ for (ent = sb->ent; !suspect && ent; ent = ent->next)
+ if (!ent->guilty)
+ suspect = ent->suspect;
+ if (!suspect)
+ return; /* all done */
+
+ origin_incref(suspect);
+ commit = suspect->commit;
+ if (!commit->object.parsed)
+ parse_commit(commit);
+ if (!(commit->object.flags & UNINTERESTING) &&
+ !(revs->max_age != -1 && commit->date < revs->max_age))
+ pass_blame(sb, suspect, opt);
+
+ /* Take responsibility for the remaining entries */
+ for (ent = sb->ent; ent; ent = ent->next)
+ if (!cmp_suspect(ent->suspect, suspect))
+ ent->guilty = 1;
+ origin_decref(suspect);
+
+ if (DEBUG) /* sanity */
+ sanity_check_refcnt(sb);
+ }
+}
+
+static const char *format_time(unsigned long time, const char *tz_str,
+ int show_raw_time)
+{
+ static char time_buf[128];
+ time_t t = time;
+ int minutes, tz;
+ struct tm *tm;
+
+ if (show_raw_time) {
+ sprintf(time_buf, "%lu %s", time, tz_str);
+ return time_buf;
+ }
+
+ 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;
+}
+
+struct commit_info
+{
+ char *author;
+ char *author_mail;
+ unsigned long author_time;
+ char *author_tz;
+
+ /* filled only when asked for details */
+ char *committer;
+ char *committer_mail;
+ unsigned long committer_time;
+ char *committer_tz;
+
+ char *summary;
+};
+
+static void get_ac_line(const char *inbuf, const char *what,
+ int bufsz, char *person, char **mail,
+ unsigned long *time, char **tz)
+{
+ int len;
+ char *tmp, *endp;
+
+ tmp = strstr(inbuf, what);
+ if (!tmp)
+ goto error_out;
+ tmp += strlen(what);
+ endp = strchr(tmp, '\n');
+ if (!endp)
+ len = strlen(tmp);
+ else
+ len = endp - tmp;
+ if (bufsz <= len) {
+ error_out:
+ /* Ugh */
+ person = *mail = *tz = "(unknown)";
+ *time = 0;
+ return;
+ }
+ memcpy(person, tmp, len);
+
+ tmp = person;
+ tmp += len;
+ *tmp = 0;
+ while (*tmp != ' ')
+ tmp--;
+ *tz = tmp+1;
+
+ *tmp = 0;
+ while (*tmp != ' ')
+ tmp--;
+ *time = strtoul(tmp, NULL, 10);
+
+ *tmp = 0;
+ while (*tmp != ' ')
+ tmp--;
+ *mail = tmp + 1;
+ *tmp = 0;
+}
+
+static void get_commit_info(struct commit *commit,
+ struct commit_info *ret,
+ int detailed)
+{
+ int len;
+ char *tmp, *endp;
+ static char author_buf[1024];
+ static char committer_buf[1024];
+ static char summary_buf[1024];
+
+ /* We've operated without save_commit_buffer, so
+ * we now need to populate them for output.
+ */
+ if (!commit->buffer) {
+ char type[20];
+ unsigned long size;
+ commit->buffer =
+ read_sha1_file(commit->object.sha1, type, &size);
+ }
+ ret->author = author_buf;
+ get_ac_line(commit->buffer, "\nauthor ",
+ sizeof(author_buf), author_buf, &ret->author_mail,
+ &ret->author_time, &ret->author_tz);
+
+ if (!detailed)
+ return;
+
+ ret->committer = committer_buf;
+ get_ac_line(commit->buffer, "\ncommitter ",
+ sizeof(committer_buf), committer_buf, &ret->committer_mail,
+ &ret->committer_time, &ret->committer_tz);
+
+ ret->summary = summary_buf;
+ tmp = strstr(commit->buffer, "\n\n");
+ if (!tmp) {
+ error_out:
+ sprintf(summary_buf, "(%s)", sha1_to_hex(commit->object.sha1));
+ return;
+ }
+ tmp += 2;
+ endp = strchr(tmp, '\n');
+ if (!endp)
+ goto error_out;
+ len = endp - tmp;
+ if (len >= sizeof(summary_buf))
+ goto error_out;
+ memcpy(summary_buf, tmp, len);
+ summary_buf[len] = 0;
+}
+
+#define OUTPUT_ANNOTATE_COMPAT 001
+#define OUTPUT_LONG_OBJECT_NAME 002
+#define OUTPUT_RAW_TIMESTAMP 004
+#define OUTPUT_PORCELAIN 010
+#define OUTPUT_SHOW_NAME 020
+#define OUTPUT_SHOW_NUMBER 040
+#define OUTPUT_SHOW_SCORE 0100
+
+static void emit_porcelain(struct scoreboard *sb, struct blame_entry *ent)
+{
+ int cnt;
+ const char *cp;
+ struct origin *suspect = ent->suspect;
+ char hex[41];
+
+ strcpy(hex, sha1_to_hex(suspect->commit->object.sha1));
+ printf("%s%c%d %d %d\n",
+ hex,
+ ent->guilty ? ' ' : '*', // purely for debugging
+ ent->s_lno + 1,
+ ent->lno + 1,
+ ent->num_lines);
+ if (!(suspect->commit->object.flags & METAINFO_SHOWN)) {
+ struct commit_info ci;
+ suspect->commit->object.flags |= METAINFO_SHOWN;
+ get_commit_info(suspect->commit, &ci, 1);
+ printf("author %s\n", ci.author);
+ printf("author-mail %s\n", ci.author_mail);
+ printf("author-time %lu\n", ci.author_time);
+ printf("author-tz %s\n", ci.author_tz);
+ printf("committer %s\n", ci.committer);
+ printf("committer-mail %s\n", ci.committer_mail);
+ printf("committer-time %lu\n", ci.committer_time);
+ printf("committer-tz %s\n", ci.committer_tz);
+ printf("filename %s\n", suspect->path);
+ printf("summary %s\n", ci.summary);
+ }
+ else if (suspect->commit->object.flags & MORE_THAN_ONE_PATH)
+ printf("filename %s\n", suspect->path);
+
+ cp = nth_line(sb, ent->lno);
+ for (cnt = 0; cnt < ent->num_lines; cnt++) {
+ char ch;
+ if (cnt)
+ printf("%s %d %d\n", hex,
+ ent->s_lno + 1 + cnt,
+ ent->lno + 1 + cnt);
+ putchar('\t');
+ do {
+ ch = *cp++;
+ putchar(ch);
+ } while (ch != '\n' &&
+ cp < sb->final_buf + sb->final_buf_size);
+ }
+}
+
+static void emit_other(struct scoreboard *sb, struct blame_entry *ent, int opt)
+{
+ int cnt;
+ const char *cp;
+ struct origin *suspect = ent->suspect;
+ struct commit_info ci;
+ char hex[41];
+ int show_raw_time = !!(opt & OUTPUT_RAW_TIMESTAMP);
+
+ get_commit_info(suspect->commit, &ci, 1);
+ strcpy(hex, sha1_to_hex(suspect->commit->object.sha1));
+
+ cp = nth_line(sb, ent->lno);
+ for (cnt = 0; cnt < ent->num_lines; cnt++) {
+ char ch;
+
+ printf("%.*s", (opt & OUTPUT_LONG_OBJECT_NAME) ? 40 : 8, hex);
+ if (opt & OUTPUT_ANNOTATE_COMPAT)
+ printf("\t(%10s\t%10s\t%d)", ci.author,
+ format_time(ci.author_time, ci.author_tz,
+ show_raw_time),
+ ent->lno + 1 + cnt);
+ else {
+ if (opt & OUTPUT_SHOW_SCORE)
+ printf(" %*d %02d",
+ max_score_digits, ent->score,
+ ent->suspect->refcnt);
+ if (opt & OUTPUT_SHOW_NAME)
+ printf(" %-*.*s", longest_file, longest_file,
+ suspect->path);
+ if (opt & OUTPUT_SHOW_NUMBER)
+ printf(" %*d", max_orig_digits,
+ ent->s_lno + 1 + cnt);
+ printf(" (%-*.*s %10s %*d) ",
+ longest_author, longest_author, ci.author,
+ format_time(ci.author_time, ci.author_tz,
+ show_raw_time),
+ max_digits, ent->lno + 1 + cnt);
+ }
+ do {
+ ch = *cp++;
+ putchar(ch);
+ } while (ch != '\n' &&
+ cp < sb->final_buf + sb->final_buf_size);
+ }
+}
+
+static void output(struct scoreboard *sb, int option)
+{
+ struct blame_entry *ent;
+
+ if (option & OUTPUT_PORCELAIN) {
+ for (ent = sb->ent; ent; ent = ent->next) {
+ struct blame_entry *oth;
+ struct origin *suspect = ent->suspect;
+ struct commit *commit = suspect->commit;
+ if (commit->object.flags & MORE_THAN_ONE_PATH)
+ continue;
+ for (oth = ent->next; oth; oth = oth->next) {
+ if ((oth->suspect->commit != commit) ||
+ !strcmp(oth->suspect->path, suspect->path))
+ continue;
+ commit->object.flags |= MORE_THAN_ONE_PATH;
+ break;
+ }
+ }
+ }
+
+ for (ent = sb->ent; ent; ent = ent->next) {
+ if (option & OUTPUT_PORCELAIN)
+ emit_porcelain(sb, ent);
+ else {
+ emit_other(sb, ent, option);
+ }
+ }
+}
+
+static int prepare_lines(struct scoreboard *sb)
+{
+ const char *buf = sb->final_buf;
+ unsigned long len = sb->final_buf_size;
+ int num = 0, incomplete = 0, bol = 1;
+
+ if (len && buf[len-1] != '\n')
+ incomplete++; /* incomplete line at the end */
+ while (len--) {
+ if (bol) {
+ sb->lineno = xrealloc(sb->lineno,
+ sizeof(int* ) * (num + 1));
+ sb->lineno[num] = buf - sb->final_buf;
+ bol = 0;
+ }
+ if (*buf++ == '\n') {
+ num++;
+ bol = 1;
+ }
+ }
+ sb->lineno = xrealloc(sb->lineno,
+ sizeof(int* ) * (num + incomplete + 1));
+ sb->lineno[num + incomplete] = buf - sb->final_buf;
+ sb->num_lines = num + incomplete;
+ return sb->num_lines;
+}
+
+static int read_ancestry(const char *graft_file)
+{
+ FILE *fp = fopen(graft_file, "r");
+ char buf[1024];
+ if (!fp)
+ return -1;
+ while (fgets(buf, sizeof(buf), fp)) {
+ /* The format is just "Commit Parent1 Parent2 ...\n" */
+ int len = strlen(buf);
+ struct commit_graft *graft = read_graft_line(buf, len);
+ if (graft)
+ register_commit_graft(graft, 0);
+ }
+ fclose(fp);
+ return 0;
+}
+
+static int lineno_width(int lines)
+{
+ int i, width;
+
+ for (width = 1, i = 10; i <= lines + 1; width++)
+ i *= 10;
+ return width;
+}
+
+static void find_alignment(struct scoreboard *sb, int *option)
+{
+ int longest_src_lines = 0;
+ int longest_dst_lines = 0;
+ unsigned largest_score = 0;
+ struct blame_entry *e;
+
+ for (e = sb->ent; e; e = e->next) {
+ struct origin *suspect = e->suspect;
+ struct commit_info ci;
+ int num;
+
+ if (!(suspect->commit->object.flags & METAINFO_SHOWN)) {
+ suspect->commit->object.flags |= METAINFO_SHOWN;
+ get_commit_info(suspect->commit, &ci, 1);
+ if (strcmp(suspect->path, sb->path))
+ *option |= OUTPUT_SHOW_NAME;
+ num = strlen(suspect->path);
+ if (longest_file < num)
+ longest_file = num;
+ num = strlen(ci.author);
+ if (longest_author < num)
+ longest_author = num;
+ }
+ num = e->s_lno + e->num_lines;
+ if (longest_src_lines < num)
+ longest_src_lines = num;
+ num = e->lno + e->num_lines;
+ if (longest_dst_lines < num)
+ longest_dst_lines = num;
+ if (largest_score < ent_score(sb, e))
+ largest_score = ent_score(sb, e);
+ }
+ max_orig_digits = lineno_width(longest_src_lines);
+ max_digits = lineno_width(longest_dst_lines);
+ max_score_digits = lineno_width(largest_score);
+}
+
+static void sanity_check_refcnt(struct scoreboard *sb)
+{
+ int baa = 0;
+ struct blame_entry *ent;
+
+ for (ent = sb->ent; ent; ent = ent->next) {
+ /* Nobody should have zero or negative refcnt */
+ if (ent->suspect->refcnt <= 0) {
+ fprintf(stderr, "%s in %s has negative refcnt %d\n",
+ ent->suspect->path,
+ sha1_to_hex(ent->suspect->commit->object.sha1),
+ ent->suspect->refcnt);
+ baa = 1;
+ }
+ }
+ for (ent = sb->ent; ent; ent = ent->next) {
+ /* Mark the ones that haven't been checked */
+ if (0 < ent->suspect->refcnt)
+ ent->suspect->refcnt = -ent->suspect->refcnt;
+ }
+ for (ent = sb->ent; ent; ent = ent->next) {
+ /* then pick each and see if they have the the correct
+ * refcnt.
+ */
+ int found;
+ struct blame_entry *e;
+ struct origin *suspect = ent->suspect;
+
+ if (0 < suspect->refcnt)
+ continue;
+ suspect->refcnt = -suspect->refcnt; /* Unmark */
+ for (found = 0, e = sb->ent; e; e = e->next) {
+ if (e->suspect != suspect)
+ continue;
+ found++;
+ }
+ if (suspect->refcnt != found) {
+ fprintf(stderr, "%s in %s has refcnt %d, not %d\n",
+ ent->suspect->path,
+ sha1_to_hex(ent->suspect->commit->object.sha1),
+ ent->suspect->refcnt, found);
+ baa = 2;
+ }
+ }
+ if (baa) {
+ int opt = 0160;
+ find_alignment(sb, &opt);
+ output(sb, opt);
+ die("Baa %d!", baa);
+ }
+}
+
+static int has_path_in_work_tree(const char *path)
+{
+ struct stat st;
+ return !lstat(path, &st);
+}
+
+static unsigned parse_score(const char *arg)
+{
+ char *end;
+ unsigned long score = strtoul(arg, &end, 10);
+ if (*end)
+ return 0;
+ return score;
+}
+
+static const char *add_prefix(const char *prefix, const char *path)
+{
+ if (!prefix || !prefix[0])
+ return path;
+ return prefix_path(prefix, strlen(prefix), path);
+}
+
+static const char *parse_loc(const char *spec,
+ struct scoreboard *sb, long lno,
+ long begin, long *ret)
+{
+ char *term;
+ const char *line;
+ long num;
+ int reg_error;
+ regex_t regexp;
+ regmatch_t match[1];
+
+ /* Allow "-L <something>,+20" to mean starting at <something>
+ * for 20 lines, or "-L <something>,-5" for 5 lines ending at
+ * <something>.
+ */
+ if (1 < begin && (spec[0] == '+' || spec[0] == '-')) {
+ num = strtol(spec + 1, &term, 10);
+ if (term != spec + 1) {
+ if (spec[0] == '-')
+ num = 0 - num;
+ if (0 < num)
+ *ret = begin + num - 2;
+ else if (!num)
+ *ret = begin;
+ else
+ *ret = begin + num;
+ return term;
+ }
+ return spec;
+ }
+ num = strtol(spec, &term, 10);
+ if (term != spec) {
+ *ret = num;
+ return term;
+ }
+ if (spec[0] != '/')
+ return spec;
+
+ /* it could be a regexp of form /.../ */
+ for (term = (char*) spec + 1; *term && *term != '/'; term++) {
+ if (*term == '\\')
+ term++;
+ }
+ if (*term != '/')
+ return spec;
+
+ /* try [spec+1 .. term-1] as regexp */
+ *term = 0;
+ begin--; /* input is in human terms */
+ line = nth_line(sb, begin);
+
+ if (!(reg_error = regcomp(&regexp, spec + 1, REG_NEWLINE)) &&
+ !(reg_error = regexec(&regexp, line, 1, match, 0))) {
+ const char *cp = line + match[0].rm_so;
+ const char *nline;
+
+ while (begin++ < lno) {
+ nline = nth_line(sb, begin);
+ if (line <= cp && cp < nline)
+ break;
+ line = nline;
+ }
+ *ret = begin;
+ regfree(&regexp);
+ *term++ = '/';
+ return term;
+ }
+ else {
+ char errbuf[1024];
+ regerror(reg_error, &regexp, errbuf, 1024);
+ die("-L parameter '%s': %s", spec + 1, errbuf);
+ }
+}
+
+static void prepare_blame_range(struct scoreboard *sb,
+ const char *bottomtop,
+ long lno,
+ long *bottom, long *top)
+{
+ const char *term;
+
+ term = parse_loc(bottomtop, sb, lno, 1, bottom);
+ if (*term == ',') {
+ term = parse_loc(term + 1, sb, lno, *bottom + 1, top);
+ if (*term)
+ usage(blame_usage);
+ }
+ if (*term)
+ usage(blame_usage);
+}
+
+int cmd_blame(int argc, const char **argv, const char *prefix)
+{
+ struct rev_info revs;
+ const char *path;
+ struct scoreboard sb;
+ struct origin *o;
+ struct blame_entry *ent;
+ int i, seen_dashdash, unk, opt;
+ long bottom, top, lno;
+ int output_option = 0;
+ const char *revs_file = NULL;
+ const char *final_commit_name = NULL;
+ char type[10];
+ const char *bottomtop = NULL;
+
+ save_commit_buffer = 0;
+
+ opt = 0;
+ seen_dashdash = 0;
+ for (unk = i = 1; i < argc; i++) {
+ const char *arg = argv[i];
+ if (*arg != '-')
+ break;
+ else if (!strcmp("-c", arg))
+ output_option |= OUTPUT_ANNOTATE_COMPAT;
+ else if (!strcmp("-t", arg))
+ output_option |= OUTPUT_RAW_TIMESTAMP;
+ else if (!strcmp("-l", arg))
+ output_option |= OUTPUT_LONG_OBJECT_NAME;
+ else if (!strcmp("-S", arg) && ++i < argc)
+ revs_file = argv[i];
+ else if (!strncmp("-M", arg, 2)) {
+ opt |= PICKAXE_BLAME_MOVE;
+ blame_move_score = parse_score(arg+2);
+ }
+ else if (!strncmp("-C", arg, 2)) {
+ if (opt & PICKAXE_BLAME_COPY)
+ opt |= PICKAXE_BLAME_COPY_HARDER;
+ opt |= PICKAXE_BLAME_COPY | PICKAXE_BLAME_MOVE;
+ blame_copy_score = parse_score(arg+2);
+ }
+ else if (!strncmp("-L", arg, 2)) {
+ if (!arg[2]) {
+ if (++i >= argc)
+ usage(blame_usage);
+ arg = argv[i];
+ }
+ else
+ arg += 2;
+ if (bottomtop)
+ die("More than one '-L n,m' option given");
+ bottomtop = arg;
+ }
+ else if (!strcmp("--score-debug", arg))
+ output_option |= OUTPUT_SHOW_SCORE;
+ else if (!strcmp("-f", arg) ||
+ !strcmp("--show-name", arg))
+ output_option |= OUTPUT_SHOW_NAME;
+ else if (!strcmp("-n", arg) ||
+ !strcmp("--show-number", arg))
+ output_option |= OUTPUT_SHOW_NUMBER;
+ else if (!strcmp("-p", arg) ||
+ !strcmp("--porcelain", arg))
+ output_option |= OUTPUT_PORCELAIN;
+ else if (!strcmp("--", arg)) {
+ seen_dashdash = 1;
+ i++;
+ break;
+ }
+ else
+ argv[unk++] = arg;
+ }
+
+ if (!blame_move_score)
+ blame_move_score = BLAME_DEFAULT_MOVE_SCORE;
+ if (!blame_copy_score)
+ blame_copy_score = BLAME_DEFAULT_COPY_SCORE;
+
+ /* We have collected options unknown to us in argv[1..unk]
+ * which are to be passed to revision machinery if we are
+ * going to do the "bottom" procesing.
+ *
+ * The remaining are:
+ *
+ * (1) if seen_dashdash, its either
+ * "-options -- <path>" or
+ * "-options -- <path> <rev>".
+ * but the latter is allowed only if there is no
+ * options that we passed to revision machinery.
+ *
+ * (2) otherwise, we may have "--" somewhere later and
+ * might be looking at the first one of multiple 'rev'
+ * parameters (e.g. " master ^next ^maint -- path").
+ * See if there is a dashdash first, and give the
+ * arguments before that to revision machinery.
+ * After that there must be one 'path'.
+ *
+ * (3) otherwise, its one of the three:
+ * "-options <path> <rev>"
+ * "-options <rev> <path>"
+ * "-options <path>"
+ * but again the first one is allowed only if
+ * there is no options that we passed to revision
+ * machinery.
+ */
+
+ if (seen_dashdash) {
+ /* (1) */
+ if (argc <= i)
+ usage(blame_usage);
+ path = add_prefix(prefix, argv[i]);
+ if (i + 1 == argc - 1) {
+ if (unk != 1)
+ usage(blame_usage);
+ argv[unk++] = argv[i + 1];
+ }
+ else if (i + 1 != argc)
+ /* garbage at end */
+ usage(blame_usage);
+ }
+ else {
+ int j;
+ for (j = i; !seen_dashdash && j < argc; j++)
+ if (!strcmp(argv[j], "--"))
+ seen_dashdash = j;
+ if (seen_dashdash) {
+ if (seen_dashdash + 1 != argc - 1)
+ usage(blame_usage);
+ path = add_prefix(prefix, argv[seen_dashdash + 1]);
+ for (j = i; j < seen_dashdash; j++)
+ argv[unk++] = argv[j];
+ }
+ else {
+ /* (3) */
+ path = add_prefix(prefix, argv[i]);
+ if (i + 1 == argc - 1) {
+ final_commit_name = argv[i + 1];
+
+ /* if (unk == 1) we could be getting
+ * old-style
+ */
+ if (unk == 1 && !has_path_in_work_tree(path)) {
+ path = add_prefix(prefix, argv[i + 1]);
+ final_commit_name = argv[i];
+ }
+ }
+ else if (i != argc - 1)
+ usage(blame_usage); /* garbage at end */
+
+ if (!has_path_in_work_tree(path))
+ die("cannot stat path %s: %s",
+ path, strerror(errno));
+ }
+ }
+
+ if (final_commit_name)
+ argv[unk++] = final_commit_name;
+
+ /* Now we got rev and path. We do not want the path pruning
+ * but we may want "bottom" processing.
+ */
+ argv[unk] = NULL;
+
+ init_revisions(&revs, NULL);
+ setup_revisions(unk, argv, &revs, "HEAD");
+ memset(&sb, 0, sizeof(sb));
+
+ /* There must be one and only one positive commit in the
+ * revs->pending array.
+ */
+ for (i = 0; i < revs.pending.nr; i++) {
+ struct object *obj = revs.pending.objects[i].item;
+ if (obj->flags & UNINTERESTING)
+ continue;
+ while (obj->type == OBJ_TAG)
+ obj = deref_tag(obj, NULL, 0);
+ if (obj->type != OBJ_COMMIT)
+ die("Non commit %s?",
+ revs.pending.objects[i].name);
+ if (sb.final)
+ die("More than one commit to dig from %s and %s?",
+ revs.pending.objects[i].name,
+ final_commit_name);
+ sb.final = (struct commit *) obj;
+ final_commit_name = revs.pending.objects[i].name;
+ }
+
+ if (!sb.final) {
+ /* "--not A B -- path" without anything positive */
+ unsigned char head_sha1[20];
+
+ final_commit_name = "HEAD";
+ if (get_sha1(final_commit_name, head_sha1))
+ die("No such ref: HEAD");
+ sb.final = lookup_commit_reference(head_sha1);
+ add_pending_object(&revs, &(sb.final->object), "HEAD");
+ }
+
+ /* If we have bottom, this will mark the ancestors of the
+ * bottom commits we would reach while traversing as
+ * uninteresting.
+ */
+ prepare_revision_walk(&revs);
+
+ o = get_origin(&sb, sb.final, path);
+ if (fill_blob_sha1(o))
+ die("no such path %s in %s", path, final_commit_name);
+
+ sb.final_buf = read_sha1_file(o->blob_sha1, type, &sb.final_buf_size);
+ num_read_blob++;
+ lno = prepare_lines(&sb);
+
+ bottom = top = 0;
+ if (bottomtop)
+ prepare_blame_range(&sb, bottomtop, lno, &bottom, &top);
+ if (bottom && top && top < bottom) {
+ long tmp;
+ tmp = top; top = bottom; bottom = tmp;
+ }
+ if (bottom < 1)
+ bottom = 1;
+ if (top < 1)
+ top = lno;
+ bottom--;
+ if (lno < top)
+ die("file %s has only %lu lines", path, lno);
+
+ ent = xcalloc(1, sizeof(*ent));
+ ent->lno = bottom;
+ ent->num_lines = top - bottom;
+ ent->suspect = o;
+ ent->s_lno = bottom;
+
+ sb.ent = ent;
+ sb.path = path;
+
+ if (revs_file && read_ancestry(revs_file))
+ die("reading graft file %s failed: %s",
+ revs_file, strerror(errno));
+
+ assign_blame(&sb, &revs, opt);
+
+ coalesce(&sb);
+
+ if (!(output_option & OUTPUT_PORCELAIN))
+ find_alignment(&sb, &output_option);
+
+ output(&sb, output_option);
+ free((void *)sb.final_buf);
+ for (ent = sb.ent; ent; ) {
+ struct blame_entry *e = ent->next;
+ free(ent);
+ ent = e;
+ }
+
+ if (DEBUG) {
+ printf("num read blob: %d\n", num_read_blob);
+ printf("num get patch: %d\n", num_get_patch);
+ printf("num commits: %d\n", num_commits);
+ }
+ return 0;
+}
diff --git a/builtin-branch.c b/builtin-branch.c
new file mode 100644
index 0000000000..368b68ec91
--- /dev/null
+++ b/builtin-branch.c
@@ -0,0 +1,221 @@
+/*
+ * Builtin "git branch"
+ *
+ * Copyright (c) 2006 Kristian Høgsberg <krh@redhat.com>
+ * Based on git-branch.sh by Junio C Hamano.
+ */
+
+#include "cache.h"
+#include "refs.h"
+#include "commit.h"
+#include "builtin.h"
+
+static const char builtin_branch_usage[] =
+"git-branch (-d | -D) <branchname> | [-l] [-f] <branchname> [<start-point>] | [-r]";
+
+
+static const char *head;
+static unsigned char head_sha1[20];
+
+static int in_merge_bases(const unsigned char *sha1,
+ struct commit *rev1,
+ struct commit *rev2)
+{
+ struct commit_list *bases, *b;
+ int ret = 0;
+
+ bases = get_merge_bases(rev1, rev2, 1);
+ for (b = bases; b; b = b->next) {
+ if (!hashcmp(sha1, b->item->object.sha1)) {
+ ret = 1;
+ break;
+ }
+ }
+
+ free_commit_list(bases);
+ return ret;
+}
+
+static void delete_branches(int argc, const char **argv, int force)
+{
+ struct commit *rev, *head_rev;
+ unsigned char sha1[20];
+ char *name;
+ int i;
+
+ head_rev = lookup_commit_reference(head_sha1);
+ for (i = 0; i < argc; i++) {
+ if (!strcmp(head, argv[i]))
+ die("Cannot delete the branch you are currently on.");
+
+ name = xstrdup(mkpath("refs/heads/%s", argv[i]));
+ if (!resolve_ref(name, sha1, 1, NULL))
+ die("Branch '%s' not found.", argv[i]);
+
+ rev = lookup_commit_reference(sha1);
+ if (!rev || !head_rev)
+ die("Couldn't look up commit objects.");
+
+ /* This checks whether the merge bases of branch and
+ * HEAD contains branch -- which means that the HEAD
+ * contains everything in both.
+ */
+
+ if (!force &&
+ !in_merge_bases(sha1, rev, head_rev)) {
+ fprintf(stderr,
+ "The branch '%s' is not a strict subset of your current HEAD.\n"
+ "If you are sure you want to delete it, run 'git branch -D %s'.\n",
+ argv[i], argv[i]);
+ exit(1);
+ }
+
+ if (delete_ref(name, sha1))
+ printf("Error deleting branch '%s'\n", argv[i]);
+ else
+ printf("Deleted branch %s.\n", argv[i]);
+
+ free(name);
+ }
+}
+
+static int ref_index, ref_alloc;
+static char **ref_list;
+
+static int append_ref(const char *refname, const unsigned char *sha1, int flags,
+ void *cb_data)
+{
+ if (ref_index >= ref_alloc) {
+ ref_alloc = alloc_nr(ref_alloc);
+ ref_list = xrealloc(ref_list, ref_alloc * sizeof(char *));
+ }
+
+ ref_list[ref_index++] = xstrdup(refname);
+
+ return 0;
+}
+
+static int ref_cmp(const void *r1, const void *r2)
+{
+ return strcmp(*(char **)r1, *(char **)r2);
+}
+
+static void print_ref_list(int remote_only)
+{
+ int i;
+ char c;
+
+ if (remote_only)
+ for_each_remote_ref(append_ref, NULL);
+ else
+ for_each_branch_ref(append_ref, NULL);
+
+ qsort(ref_list, ref_index, sizeof(char *), ref_cmp);
+
+ for (i = 0; i < ref_index; i++) {
+ c = ' ';
+ if (!strcmp(ref_list[i], head))
+ c = '*';
+
+ printf("%c %s\n", c, ref_list[i]);
+ }
+}
+
+static void create_branch(const char *name, const char *start,
+ int force, int reflog)
+{
+ struct ref_lock *lock;
+ struct commit *commit;
+ unsigned char sha1[20];
+ char ref[PATH_MAX], msg[PATH_MAX + 20];
+
+ snprintf(ref, sizeof ref, "refs/heads/%s", name);
+ if (check_ref_format(ref))
+ die("'%s' is not a valid branch name.", name);
+
+ if (resolve_ref(ref, sha1, 1, NULL)) {
+ if (!force)
+ die("A branch named '%s' already exists.", name);
+ else if (!strcmp(head, name))
+ die("Cannot force update the current branch.");
+ }
+
+ if (get_sha1(start, sha1) ||
+ (commit = lookup_commit_reference(sha1)) == NULL)
+ die("Not a valid branch point: '%s'.", start);
+ hashcpy(sha1, commit->object.sha1);
+
+ lock = lock_any_ref_for_update(ref, NULL);
+ if (!lock)
+ die("Failed to lock ref for update: %s.", strerror(errno));
+
+ if (reflog) {
+ log_all_ref_updates = 1;
+ snprintf(msg, sizeof msg, "branch: Created from %s", start);
+ }
+
+ if (write_ref_sha1(lock, sha1, msg) < 0)
+ die("Failed to write ref: %s.", strerror(errno));
+}
+
+int cmd_branch(int argc, const char **argv, const char *prefix)
+{
+ int delete = 0, force_delete = 0, force_create = 0, remote_only = 0;
+ int reflog = 0;
+ int i;
+
+ git_config(git_default_config);
+
+ for (i = 1; i < argc; i++) {
+ const char *arg = argv[i];
+
+ if (arg[0] != '-')
+ break;
+ if (!strcmp(arg, "--")) {
+ i++;
+ break;
+ }
+ if (!strcmp(arg, "-d")) {
+ delete = 1;
+ continue;
+ }
+ if (!strcmp(arg, "-D")) {
+ delete = 1;
+ force_delete = 1;
+ continue;
+ }
+ if (!strcmp(arg, "-f")) {
+ force_create = 1;
+ continue;
+ }
+ if (!strcmp(arg, "-r")) {
+ remote_only = 1;
+ continue;
+ }
+ if (!strcmp(arg, "-l")) {
+ reflog = 1;
+ continue;
+ }
+ usage(builtin_branch_usage);
+ }
+
+ head = xstrdup(resolve_ref("HEAD", head_sha1, 0, NULL));
+ if (!head)
+ die("Failed to resolve HEAD as a valid ref.");
+ if (strncmp(head, "refs/heads/", 11))
+ die("HEAD not found below refs/heads!");
+ head += 11;
+
+ if (delete)
+ delete_branches(argc - i, argv + i, force_delete);
+ else if (i == argc)
+ print_ref_list(remote_only);
+ else if (i == argc - 1)
+ create_branch(argv[i], head, force_create, reflog);
+ else if (i == argc - 2)
+ create_branch(argv[i], argv[i + 1], force_create, reflog);
+ else
+ usage(builtin_branch_usage);
+
+ return 0;
+}
diff --git a/builtin-cat-file.c b/builtin-cat-file.c
index 814fb0743f..6c16bfa1ae 100644
--- a/builtin-cat-file.c
+++ b/builtin-cat-file.c
@@ -9,24 +9,7 @@
#include "tree.h"
#include "builtin.h"
-static void flush_buffer(const char *buf, unsigned long size)
-{
- while (size > 0) {
- long ret = xwrite(1, buf, size);
- if (ret < 0) {
- /* Ignore epipe */
- if (errno == EPIPE)
- break;
- die("git-cat-file: %s", strerror(errno));
- } else if (!ret) {
- die("git-cat-file: disk full?");
- }
- size -= ret;
- buf += ret;
- }
-}
-
-static int pprint_tag(const unsigned char *sha1, const char *buf, unsigned long size)
+static void pprint_tag(const unsigned char *sha1, const char *buf, unsigned long size)
{
/* the parser in tag.c is useless here. */
const char *endp = buf + size;
@@ -42,7 +25,7 @@ static int pprint_tag(const unsigned char *sha1, const char *buf, unsigned long
/* Found the tagger line. Copy out the contents
* of the buffer so far.
*/
- flush_buffer(buf, cp - buf);
+ write_or_die(1, buf, cp - buf);
/*
* Do something intelligent, like pretty-printing
@@ -61,18 +44,18 @@ static int pprint_tag(const unsigned char *sha1, const char *buf, unsigned long
sp++;
if (sp == cp) {
/* give up */
- flush_buffer(tagger,
+ write_or_die(1, tagger,
cp - tagger);
break;
}
while (sp < cp &&
!('0' <= *sp && *sp <= '9'))
sp++;
- flush_buffer(tagger, sp - tagger);
+ write_or_die(1, tagger, sp - tagger);
date = strtoul(sp, &ep, 10);
tz = strtol(ep, NULL, 10);
- sp = show_date(date, tz);
- flush_buffer(sp, strlen(sp));
+ sp = show_date(date, tz, 0);
+ write_or_die(1, sp, strlen(sp));
xwrite(1, "\n", 1);
break;
}
@@ -90,8 +73,7 @@ static int pprint_tag(const unsigned char *sha1, const char *buf, unsigned long
* remainder as is.
*/
if (cp < endp)
- flush_buffer(cp, endp - cp);
- return 0;
+ write_or_die(1, cp, endp - cp);
}
int cmd_cat_file(int argc, const char **argv, const char *prefix)
@@ -145,8 +127,10 @@ int cmd_cat_file(int argc, const char **argv, const char *prefix)
buf = read_sha1_file(sha1, type, &size);
if (!buf)
die("Cannot read object %s", argv[2]);
- if (!strcmp(type, tag_type))
- return pprint_tag(sha1, buf, size);
+ if (!strcmp(type, tag_type)) {
+ pprint_tag(sha1, buf, size);
+ return 0;
+ }
/* otherwise just spit out the data */
break;
@@ -161,6 +145,6 @@ int cmd_cat_file(int argc, const char **argv, const char *prefix)
if (!buf)
die("git-cat-file %s: bad file", argv[2]);
- flush_buffer(buf, size);
+ write_or_die(1, buf, size);
return 0;
}
diff --git a/builtin-checkout-index.c b/builtin-checkout-index.c
index 8d0dbad49e..b097c888a0 100644
--- a/builtin-checkout-index.c
+++ b/builtin-checkout-index.c
@@ -45,7 +45,7 @@
static int line_termination = '\n';
static int checkout_stage; /* default to checkout stage0 */
static int to_tempfile;
-static char topath[4][MAXPATHLEN+1];
+static char topath[4][PATH_MAX + 1];
static struct checkout state;
@@ -122,7 +122,7 @@ static int checkout_file(const char *name, int prefix_length)
return -1;
}
-static int checkout_all(const char *prefix, int prefix_length)
+static void checkout_all(const char *prefix, int prefix_length)
{
int i, errs = 0;
struct cache_entry* last_ce = NULL;
@@ -153,7 +153,6 @@ static int checkout_all(const char *prefix, int prefix_length)
* exit with the same code as die().
*/
exit(128);
- return 0;
}
static const char checkout_cache_usage[] =
diff --git a/builtin-commit-tree.c b/builtin-commit-tree.c
index 9c98796671..e2e690a1ae 100644
--- a/builtin-commit-tree.c
+++ b/builtin-commit-tree.c
@@ -69,7 +69,7 @@ static int new_parent(int idx)
int i;
unsigned char *sha1 = parent_sha1[idx];
for (i = 0; i < idx; i++) {
- if (!memcmp(parent_sha1[i], sha1, 20)) {
+ if (!hashcmp(parent_sha1[i], sha1)) {
error("duplicate parent %s ignored", sha1_to_hex(sha1));
return 0;
}
diff --git a/builtin-count-objects.c b/builtin-count-objects.c
index 1d3729aa99..73c5982423 100644
--- a/builtin-count-objects.c
+++ b/builtin-count-objects.c
@@ -62,7 +62,7 @@ static void count_objects(DIR *d, char *path, int len, int verbose,
hex[40] = 0;
if (get_sha1_hex(hex, sha1))
die("internal error");
- if (has_sha1_pack(sha1))
+ if (has_sha1_pack(sha1, NULL))
(*packed_loose)++;
}
}
diff --git a/builtin-diff-stages.c b/builtin-diff-stages.c
index 5960e08997..70bb89808d 100644
--- a/builtin-diff-stages.c
+++ b/builtin-diff-stages.c
@@ -46,7 +46,7 @@ static void diff_stages(int stage1, int stage2, const char **pathspec)
else if (!two)
diff_addremove(&diff_options, '-', ntohl(one->ce_mode),
one->sha1, name, NULL);
- else if (memcmp(one->sha1, two->sha1, 20) ||
+ else if (hashcmp(one->sha1, two->sha1) ||
(one->ce_mode != two->ce_mode) ||
diff_options.find_copies_harder)
diff_change(&diff_options,
diff --git a/builtin-diff.c b/builtin-diff.c
index 82afce782d..a6590205e8 100644
--- a/builtin-diff.c
+++ b/builtin-diff.c
@@ -68,9 +68,8 @@ static void stuff_change(struct diff_options *opt,
{
struct diff_filespec *one, *two;
- if (memcmp(null_sha1, old_sha1, 20) &&
- memcmp(null_sha1, new_sha1, 20) &&
- !memcmp(old_sha1, new_sha1, 20))
+ if (!is_null_sha1(old_sha1) && !is_null_sha1(new_sha1) &&
+ !hashcmp(old_sha1, new_sha1))
return;
if (opt->reverse_diff) {
@@ -193,7 +192,7 @@ static int builtin_diff_combined(struct rev_info *revs,
parent = xmalloc(ents * sizeof(*parent));
/* Again, the revs are all reverse */
for (i = 0; i < ents; i++)
- memcpy(parent + i, ent[ents - 1 - i].item->sha1, 20);
+ hashcpy((unsigned char*)parent + i, ent[ents - 1 - i].item->sha1);
diff_tree_combined(parent[0], parent + 1, ents - 1,
revs->dense_combined_merges, revs);
return 0;
@@ -291,7 +290,7 @@ int cmd_diff(int argc, const char **argv, const char *prefix)
if (obj->type == OBJ_BLOB) {
if (2 <= blobs)
die("more than two blobs given: '%s'", name);
- memcpy(blob[blobs].sha1, obj->sha1, 20);
+ hashcpy(blob[blobs].sha1, obj->sha1);
blob[blobs].name = name;
blobs++;
continue;
diff --git a/builtin-fmt-merge-msg.c b/builtin-fmt-merge-msg.c
index 485ede7cad..87d3d63ec7 100644
--- a/builtin-fmt-merge-msg.c
+++ b/builtin-fmt-merge-msg.c
@@ -8,7 +8,7 @@
static const char *fmt_merge_msg_usage =
"git-fmt-merge-msg [--summary] [--no-summary] [--file <file>]";
-static int merge_summary = 0;
+static int merge_summary;
static int fmt_merge_msg_config(const char *key, const char *value)
{
@@ -27,8 +27,8 @@ static void append_to_list(struct list *list, char *value, void *payload)
{
if (list->nr == list->alloc) {
list->alloc += 32;
- list->list = realloc(list->list, sizeof(char *) * list->alloc);
- list->payload = realloc(list->payload,
+ list->list = xrealloc(list->list, sizeof(char *) * list->alloc);
+ list->payload = xrealloc(list->payload,
sizeof(char *) * list->alloc);
}
list->payload[list->nr] = payload;
@@ -55,8 +55,7 @@ static void free_list(struct list *list)
for (i = 0; i < list->nr; i++) {
free(list->list[i]);
- if (list->payload[i])
- free(list->payload[i]);
+ free(list->payload[i]);
}
free(list->list);
free(list->payload);
@@ -112,43 +111,43 @@ static int handle_line(char *line)
i = find_in_list(&srcs, src);
if (i < 0) {
i = srcs.nr;
- append_to_list(&srcs, strdup(src),
+ append_to_list(&srcs, xstrdup(src),
xcalloc(1, sizeof(struct src_data)));
}
src_data = srcs.payload[i];
if (pulling_head) {
- origin = strdup(src);
+ origin = xstrdup(src);
src_data->head_status |= 1;
} else if (!strncmp(line, "branch ", 7)) {
- origin = strdup(line + 7);
+ origin = xstrdup(line + 7);
append_to_list(&src_data->branch, origin, NULL);
src_data->head_status |= 2;
} else if (!strncmp(line, "tag ", 4)) {
origin = line;
- append_to_list(&src_data->tag, strdup(origin + 4), NULL);
+ append_to_list(&src_data->tag, xstrdup(origin + 4), NULL);
src_data->head_status |= 2;
} else if (!strncmp(line, "remote branch ", 14)) {
- origin = strdup(line + 14);
+ origin = xstrdup(line + 14);
append_to_list(&src_data->r_branch, origin, NULL);
src_data->head_status |= 2;
} else {
- origin = strdup(src);
- append_to_list(&src_data->generic, strdup(line), NULL);
+ origin = xstrdup(src);
+ append_to_list(&src_data->generic, xstrdup(line), NULL);
src_data->head_status |= 2;
}
if (!strcmp(".", src) || !strcmp(src, origin)) {
int len = strlen(origin);
if (origin[0] == '\'' && origin[len - 1] == '\'') {
- char *new_origin = malloc(len - 1);
+ char *new_origin = xmalloc(len - 1);
memcpy(new_origin, origin + 1, len - 2);
- new_origin[len - 1] = 0;
+ new_origin[len - 2] = 0;
origin = new_origin;
} else
- origin = strdup(origin);
+ origin = xstrdup(origin);
} else {
- char *new_origin = malloc(strlen(origin) + strlen(src) + 5);
+ char *new_origin = xmalloc(strlen(origin) + strlen(src) + 5);
sprintf(new_origin, "%s of %s", origin, src);
origin = new_origin;
}
@@ -204,7 +203,7 @@ static void shortlog(const char *name, unsigned char *sha1,
bol = strstr(commit->buffer, "\n\n");
if (!bol) {
- append_to_list(&subjects, strdup(sha1_to_hex(
+ append_to_list(&subjects, xstrdup(sha1_to_hex(
commit->object.sha1)),
NULL);
continue;
@@ -215,11 +214,11 @@ static void shortlog(const char *name, unsigned char *sha1,
if (eol) {
int len = eol - bol;
- oneline = malloc(len + 1);
+ oneline = xmalloc(len + 1);
memcpy(oneline, bol, len);
oneline[len] = 0;
} else
- oneline = strdup(bol);
+ oneline = xstrdup(bol);
append_to_list(&subjects, oneline, NULL);
}
@@ -250,7 +249,7 @@ int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix)
FILE *in = stdin;
const char *sep = "";
unsigned char head_sha1[20];
- const char *head, *current_branch;
+ const char *current_branch;
git_config(fmt_merge_msg_config);
@@ -278,10 +277,9 @@ int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix)
usage(fmt_merge_msg_usage);
/* get current branch */
- head = strdup(git_path("HEAD"));
- current_branch = resolve_ref(head, head_sha1, 1);
- current_branch += strlen(head) - 4;
- free((char *)head);
+ current_branch = resolve_ref("HEAD", head_sha1, 1, NULL);
+ if (!current_branch)
+ die("No current branch");
if (!strncmp(current_branch, "refs/heads/", 11))
current_branch += 11;
diff --git a/builtin-for-each-ref.c b/builtin-for-each-ref.c
new file mode 100644
index 0000000000..227aa3cd7f
--- /dev/null
+++ b/builtin-for-each-ref.c
@@ -0,0 +1,896 @@
+#include "cache.h"
+#include "refs.h"
+#include "object.h"
+#include "tag.h"
+#include "commit.h"
+#include "tree.h"
+#include "blob.h"
+#include "quote.h"
+#include <fnmatch.h>
+
+/* Quoting styles */
+#define QUOTE_NONE 0
+#define QUOTE_SHELL 1
+#define QUOTE_PERL 2
+#define QUOTE_PYTHON 3
+
+typedef enum { FIELD_STR, FIELD_ULONG, FIELD_TIME } cmp_type;
+
+struct atom_value {
+ const char *s;
+ unsigned long ul; /* used for sorting when not FIELD_STR */
+};
+
+struct ref_sort {
+ struct ref_sort *next;
+ int atom; /* index into used_atom array */
+ unsigned reverse : 1;
+};
+
+struct refinfo {
+ char *refname;
+ unsigned char objectname[20];
+ struct atom_value *value;
+};
+
+static struct {
+ const char *name;
+ cmp_type cmp_type;
+} valid_atom[] = {
+ { "refname" },
+ { "objecttype" },
+ { "objectsize", FIELD_ULONG },
+ { "objectname" },
+ { "tree" },
+ { "parent" }, /* NEEDSWORK: how to address 2nd and later parents? */
+ { "numparent", FIELD_ULONG },
+ { "object" },
+ { "type" },
+ { "tag" },
+ { "author" },
+ { "authorname" },
+ { "authoremail" },
+ { "authordate", FIELD_TIME },
+ { "committer" },
+ { "committername" },
+ { "committeremail" },
+ { "committerdate", FIELD_TIME },
+ { "tagger" },
+ { "taggername" },
+ { "taggeremail" },
+ { "taggerdate", FIELD_TIME },
+ { "creator" },
+ { "creatordate", FIELD_TIME },
+ { "subject" },
+ { "body" },
+ { "contents" },
+};
+
+/*
+ * An atom is a valid field atom listed above, possibly prefixed with
+ * a "*" to denote deref_tag().
+ *
+ * We parse given format string and sort specifiers, and make a list
+ * of properties that we need to extract out of objects. refinfo
+ * structure will hold an array of values extracted that can be
+ * indexed with the "atom number", which is an index into this
+ * array.
+ */
+static const char **used_atom;
+static cmp_type *used_atom_type;
+static int used_atom_cnt, sort_atom_limit, need_tagged;
+
+/*
+ * Used to parse format string and sort specifiers
+ */
+static int parse_atom(const char *atom, const char *ep)
+{
+ const char *sp;
+ char *n;
+ int i, at;
+
+ sp = atom;
+ if (*sp == '*' && sp < ep)
+ sp++; /* deref */
+ if (ep <= sp)
+ die("malformed field name: %.*s", (int)(ep-atom), atom);
+
+ /* Do we have the atom already used elsewhere? */
+ for (i = 0; i < used_atom_cnt; i++) {
+ int len = strlen(used_atom[i]);
+ if (len == ep - atom && !memcmp(used_atom[i], atom, len))
+ return i;
+ }
+
+ /* Is the atom a valid one? */
+ for (i = 0; i < ARRAY_SIZE(valid_atom); i++) {
+ int len = strlen(valid_atom[i].name);
+ if (len == ep - sp && !memcmp(valid_atom[i].name, sp, len))
+ break;
+ }
+
+ if (ARRAY_SIZE(valid_atom) <= i)
+ die("unknown field name: %.*s", (int)(ep-atom), atom);
+
+ /* Add it in, including the deref prefix */
+ at = used_atom_cnt;
+ used_atom_cnt++;
+ used_atom = xrealloc(used_atom,
+ (sizeof *used_atom) * used_atom_cnt);
+ used_atom_type = xrealloc(used_atom_type,
+ (sizeof(*used_atom_type) * used_atom_cnt));
+ n = xmalloc(ep - atom + 1);
+ memcpy(n, atom, ep - atom);
+ n[ep-atom] = 0;
+ used_atom[at] = n;
+ used_atom_type[at] = valid_atom[i].cmp_type;
+ return at;
+}
+
+/*
+ * In a format string, find the next occurrence of %(atom).
+ */
+static const char *find_next(const char *cp)
+{
+ while (*cp) {
+ if (*cp == '%') {
+ /* %( is the start of an atom;
+ * %% is a quoteed per-cent.
+ */
+ if (cp[1] == '(')
+ return cp;
+ else if (cp[1] == '%')
+ cp++; /* skip over two % */
+ /* otherwise this is a singleton, literal % */
+ }
+ cp++;
+ }
+ return NULL;
+}
+
+/*
+ * Make sure the format string is well formed, and parse out
+ * the used atoms.
+ */
+static void verify_format(const char *format)
+{
+ const char *cp, *sp;
+ for (cp = format; *cp && (sp = find_next(cp)); ) {
+ const char *ep = strchr(sp, ')');
+ if (!ep)
+ die("malformatted format string %s", sp);
+ /* sp points at "%(" and ep points at the closing ")" */
+ parse_atom(sp + 2, ep);
+ cp = ep + 1;
+ }
+}
+
+/*
+ * Given an object name, read the object data and size, and return a
+ * "struct object". If the object data we are returning is also borrowed
+ * by the "struct object" representation, set *eaten as well---it is a
+ * signal from parse_object_buffer to us not to free the buffer.
+ */
+static void *get_obj(const unsigned char *sha1, struct object **obj, unsigned long *sz, int *eaten)
+{
+ char type[20];
+ void *buf = read_sha1_file(sha1, type, sz);
+
+ if (buf)
+ *obj = parse_object_buffer(sha1, type, *sz, buf, eaten);
+ else
+ *obj = NULL;
+ return buf;
+}
+
+/* See grab_values */
+static void grab_common_values(struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz)
+{
+ int i;
+
+ for (i = 0; i < used_atom_cnt; i++) {
+ const char *name = used_atom[i];
+ struct atom_value *v = &val[i];
+ if (!!deref != (*name == '*'))
+ continue;
+ if (deref)
+ name++;
+ if (!strcmp(name, "objecttype"))
+ v->s = type_names[obj->type];
+ else if (!strcmp(name, "objectsize")) {
+ char *s = xmalloc(40);
+ sprintf(s, "%lu", sz);
+ v->ul = sz;
+ v->s = s;
+ }
+ else if (!strcmp(name, "objectname")) {
+ char *s = xmalloc(41);
+ strcpy(s, sha1_to_hex(obj->sha1));
+ v->s = s;
+ }
+ }
+}
+
+/* See grab_values */
+static void grab_tag_values(struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz)
+{
+ int i;
+ struct tag *tag = (struct tag *) obj;
+
+ for (i = 0; i < used_atom_cnt; i++) {
+ const char *name = used_atom[i];
+ struct atom_value *v = &val[i];
+ if (!!deref != (*name == '*'))
+ continue;
+ if (deref)
+ name++;
+ if (!strcmp(name, "tag"))
+ v->s = tag->tag;
+ }
+}
+
+static int num_parents(struct commit *commit)
+{
+ struct commit_list *parents;
+ int i;
+
+ for (i = 0, parents = commit->parents;
+ parents;
+ parents = parents->next)
+ i++;
+ return i;
+}
+
+/* See grab_values */
+static void grab_commit_values(struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz)
+{
+ int i;
+ struct commit *commit = (struct commit *) obj;
+
+ for (i = 0; i < used_atom_cnt; i++) {
+ const char *name = used_atom[i];
+ struct atom_value *v = &val[i];
+ if (!!deref != (*name == '*'))
+ continue;
+ if (deref)
+ name++;
+ if (!strcmp(name, "tree")) {
+ char *s = xmalloc(41);
+ strcpy(s, sha1_to_hex(commit->tree->object.sha1));
+ v->s = s;
+ }
+ if (!strcmp(name, "numparent")) {
+ char *s = xmalloc(40);
+ sprintf(s, "%lu", v->ul);
+ v->s = s;
+ v->ul = num_parents(commit);
+ }
+ else if (!strcmp(name, "parent")) {
+ int num = num_parents(commit);
+ int i;
+ struct commit_list *parents;
+ char *s = xmalloc(42 * num);
+ v->s = s;
+ for (i = 0, parents = commit->parents;
+ parents;
+ parents = parents->next, i = i + 42) {
+ struct commit *parent = parents->item;
+ strcpy(s+i, sha1_to_hex(parent->object.sha1));
+ if (parents->next)
+ s[i+40] = ' ';
+ }
+ }
+ }
+}
+
+static const char *find_wholine(const char *who, int wholen, const char *buf, unsigned long sz)
+{
+ const char *eol;
+ while (*buf) {
+ if (!strncmp(buf, who, wholen) &&
+ buf[wholen] == ' ')
+ return buf + wholen + 1;
+ eol = strchr(buf, '\n');
+ if (!eol)
+ return "";
+ eol++;
+ if (eol[1] == '\n')
+ return ""; /* end of header */
+ buf = eol;
+ }
+ return "";
+}
+
+static char *copy_line(const char *buf)
+{
+ const char *eol = strchr(buf, '\n');
+ char *line;
+ int len;
+ if (!eol)
+ return "";
+ len = eol - buf;
+ line = xmalloc(len + 1);
+ memcpy(line, buf, len);
+ line[len] = 0;
+ return line;
+}
+
+static char *copy_name(const char *buf)
+{
+ const char *eol = strchr(buf, '\n');
+ const char *eoname = strstr(buf, " <");
+ char *line;
+ int len;
+ if (!(eoname && eol && eoname < eol))
+ return "";
+ len = eoname - buf;
+ line = xmalloc(len + 1);
+ memcpy(line, buf, len);
+ line[len] = 0;
+ return line;
+}
+
+static char *copy_email(const char *buf)
+{
+ const char *email = strchr(buf, '<');
+ const char *eoemail = strchr(email, '>');
+ char *line;
+ int len;
+ if (!email || !eoemail)
+ return "";
+ eoemail++;
+ len = eoemail - email;
+ line = xmalloc(len + 1);
+ memcpy(line, email, len);
+ line[len] = 0;
+ return line;
+}
+
+static void grab_date(const char *buf, struct atom_value *v)
+{
+ const char *eoemail = strstr(buf, "> ");
+ char *zone;
+ unsigned long timestamp;
+ long tz;
+
+ if (!eoemail)
+ goto bad;
+ timestamp = strtoul(eoemail + 2, &zone, 10);
+ if (timestamp == ULONG_MAX)
+ goto bad;
+ tz = strtol(zone, NULL, 10);
+ if ((tz == LONG_MIN || tz == LONG_MAX) && errno == ERANGE)
+ goto bad;
+ v->s = xstrdup(show_date(timestamp, tz, 0));
+ v->ul = timestamp;
+ return;
+ bad:
+ v->s = "";
+ v->ul = 0;
+}
+
+/* See grab_values */
+static void grab_person(const char *who, struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz)
+{
+ int i;
+ int wholen = strlen(who);
+ const char *wholine = NULL;
+
+ for (i = 0; i < used_atom_cnt; i++) {
+ const char *name = used_atom[i];
+ struct atom_value *v = &val[i];
+ if (!!deref != (*name == '*'))
+ continue;
+ if (deref)
+ name++;
+ if (strncmp(who, name, wholen))
+ continue;
+ if (name[wholen] != 0 &&
+ strcmp(name + wholen, "name") &&
+ strcmp(name + wholen, "email") &&
+ strcmp(name + wholen, "date"))
+ continue;
+ if (!wholine)
+ wholine = find_wholine(who, wholen, buf, sz);
+ if (!wholine)
+ return; /* no point looking for it */
+ if (name[wholen] == 0)
+ v->s = copy_line(wholine);
+ else if (!strcmp(name + wholen, "name"))
+ v->s = copy_name(wholine);
+ else if (!strcmp(name + wholen, "email"))
+ v->s = copy_email(wholine);
+ else if (!strcmp(name + wholen, "date"))
+ grab_date(wholine, v);
+ }
+
+ /* For a tag or a commit object, if "creator" or "creatordate" is
+ * requested, do something special.
+ */
+ if (strcmp(who, "tagger") && strcmp(who, "committer"))
+ return; /* "author" for commit object is not wanted */
+ if (!wholine)
+ wholine = find_wholine(who, wholen, buf, sz);
+ if (!wholine)
+ return;
+ for (i = 0; i < used_atom_cnt; i++) {
+ const char *name = used_atom[i];
+ struct atom_value *v = &val[i];
+ if (!!deref != (*name == '*'))
+ continue;
+ if (deref)
+ name++;
+
+ if (!strcmp(name, "creatordate"))
+ grab_date(wholine, v);
+ else if (!strcmp(name, "creator"))
+ v->s = copy_line(wholine);
+ }
+}
+
+static void find_subpos(const char *buf, unsigned long sz, const char **sub, const char **body)
+{
+ while (*buf) {
+ const char *eol = strchr(buf, '\n');
+ if (!eol)
+ return;
+ if (eol[1] == '\n') {
+ buf = eol + 1;
+ break; /* found end of header */
+ }
+ buf = eol + 1;
+ }
+ while (*buf == '\n')
+ buf++;
+ if (!*buf)
+ return;
+ *sub = buf; /* first non-empty line */
+ buf = strchr(buf, '\n');
+ if (!buf)
+ return; /* no body */
+ while (*buf == '\n')
+ buf++; /* skip blank between subject and body */
+ *body = buf;
+}
+
+/* See grab_values */
+static void grab_sub_body_contents(struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz)
+{
+ int i;
+ const char *subpos = NULL, *bodypos = NULL;
+
+ for (i = 0; i < used_atom_cnt; i++) {
+ const char *name = used_atom[i];
+ struct atom_value *v = &val[i];
+ if (!!deref != (*name == '*'))
+ continue;
+ if (deref)
+ name++;
+ if (strcmp(name, "subject") &&
+ strcmp(name, "body") &&
+ strcmp(name, "contents"))
+ continue;
+ if (!subpos)
+ find_subpos(buf, sz, &subpos, &bodypos);
+ if (!subpos)
+ return;
+
+ if (!strcmp(name, "subject"))
+ v->s = copy_line(subpos);
+ else if (!strcmp(name, "body"))
+ v->s = xstrdup(bodypos);
+ else if (!strcmp(name, "contents"))
+ v->s = xstrdup(subpos);
+ }
+}
+
+/* We want to have empty print-string for field requests
+ * that do not apply (e.g. "authordate" for a tag object)
+ */
+static void fill_missing_values(struct atom_value *val)
+{
+ int i;
+ for (i = 0; i < used_atom_cnt; i++) {
+ struct atom_value *v = &val[i];
+ if (v->s == NULL)
+ v->s = "";
+ }
+}
+
+/*
+ * val is a list of atom_value to hold returned values. Extract
+ * the values for atoms in used_atom array out of (obj, buf, sz).
+ * when deref is false, (obj, buf, sz) is the object that is
+ * pointed at by the ref itself; otherwise it is the object the
+ * ref (which is a tag) refers to.
+ */
+static void grab_values(struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz)
+{
+ grab_common_values(val, deref, obj, buf, sz);
+ switch (obj->type) {
+ case OBJ_TAG:
+ grab_tag_values(val, deref, obj, buf, sz);
+ grab_sub_body_contents(val, deref, obj, buf, sz);
+ grab_person("tagger", val, deref, obj, buf, sz);
+ break;
+ case OBJ_COMMIT:
+ grab_commit_values(val, deref, obj, buf, sz);
+ grab_sub_body_contents(val, deref, obj, buf, sz);
+ grab_person("author", val, deref, obj, buf, sz);
+ grab_person("committer", val, deref, obj, buf, sz);
+ break;
+ case OBJ_TREE:
+ // grab_tree_values(val, deref, obj, buf, sz);
+ break;
+ case OBJ_BLOB:
+ // grab_blob_values(val, deref, obj, buf, sz);
+ break;
+ default:
+ die("Eh? Object of type %d?", obj->type);
+ }
+}
+
+/*
+ * Parse the object referred by ref, and grab needed value.
+ */
+static void populate_value(struct refinfo *ref)
+{
+ void *buf;
+ struct object *obj;
+ int eaten, i;
+ unsigned long size;
+ const unsigned char *tagged;
+
+ ref->value = xcalloc(sizeof(struct atom_value), used_atom_cnt);
+
+ buf = get_obj(ref->objectname, &obj, &size, &eaten);
+ if (!buf)
+ die("missing object %s for %s",
+ sha1_to_hex(ref->objectname), ref->refname);
+ if (!obj)
+ die("parse_object_buffer failed on %s for %s",
+ sha1_to_hex(ref->objectname), ref->refname);
+
+ /* Fill in specials first */
+ for (i = 0; i < used_atom_cnt; i++) {
+ const char *name = used_atom[i];
+ struct atom_value *v = &ref->value[i];
+ if (!strcmp(name, "refname"))
+ v->s = ref->refname;
+ else if (!strcmp(name, "*refname")) {
+ int len = strlen(ref->refname);
+ char *s = xmalloc(len + 4);
+ sprintf(s, "%s^{}", ref->refname);
+ v->s = s;
+ }
+ }
+
+ grab_values(ref->value, 0, obj, buf, size);
+ if (!eaten)
+ free(buf);
+
+ /* If there is no atom that wants to know about tagged
+ * object, we are done.
+ */
+ if (!need_tagged || (obj->type != OBJ_TAG))
+ return;
+
+ /* If it is a tag object, see if we use a value that derefs
+ * the object, and if we do grab the object it refers to.
+ */
+ tagged = ((struct tag *)obj)->tagged->sha1;
+
+ /* NEEDSWORK: This derefs tag only once, which
+ * is good to deal with chains of trust, but
+ * is not consistent with what deref_tag() does
+ * which peels the onion to the core.
+ */
+ buf = get_obj(tagged, &obj, &size, &eaten);
+ if (!buf)
+ die("missing object %s for %s",
+ sha1_to_hex(tagged), ref->refname);
+ if (!obj)
+ die("parse_object_buffer failed on %s for %s",
+ sha1_to_hex(tagged), ref->refname);
+ grab_values(ref->value, 1, obj, buf, size);
+ if (!eaten)
+ free(buf);
+}
+
+/*
+ * Given a ref, return the value for the atom. This lazily gets value
+ * out of the object by calling populate value.
+ */
+static void get_value(struct refinfo *ref, int atom, struct atom_value **v)
+{
+ if (!ref->value) {
+ populate_value(ref);
+ fill_missing_values(ref->value);
+ }
+ *v = &ref->value[atom];
+}
+
+struct grab_ref_cbdata {
+ struct refinfo **grab_array;
+ const char **grab_pattern;
+ int grab_cnt;
+};
+
+/*
+ * A call-back given to for_each_ref(). It is unfortunate that we
+ * need to use global variables to pass extra information to this
+ * function.
+ */
+static int grab_single_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
+{
+ struct grab_ref_cbdata *cb = cb_data;
+ struct refinfo *ref;
+ int cnt;
+
+ if (*cb->grab_pattern) {
+ const char **pattern;
+ int namelen = strlen(refname);
+ for (pattern = cb->grab_pattern; *pattern; pattern++) {
+ const char *p = *pattern;
+ int plen = strlen(p);
+
+ if ((plen <= namelen) &&
+ !strncmp(refname, p, plen) &&
+ (refname[plen] == '\0' ||
+ refname[plen] == '/'))
+ break;
+ if (!fnmatch(p, refname, FNM_PATHNAME))
+ break;
+ }
+ if (!*pattern)
+ return 0;
+ }
+
+ /* We do not open the object yet; sort may only need refname
+ * to do its job and the resulting list may yet to be pruned
+ * by maxcount logic.
+ */
+ ref = xcalloc(1, sizeof(*ref));
+ ref->refname = xstrdup(refname);
+ hashcpy(ref->objectname, sha1);
+
+ cnt = cb->grab_cnt;
+ cb->grab_array = xrealloc(cb->grab_array,
+ sizeof(*cb->grab_array) * (cnt + 1));
+ cb->grab_array[cnt++] = ref;
+ cb->grab_cnt = cnt;
+ return 0;
+}
+
+static int cmp_ref_sort(struct ref_sort *s, struct refinfo *a, struct refinfo *b)
+{
+ struct atom_value *va, *vb;
+ int cmp;
+ cmp_type cmp_type = used_atom_type[s->atom];
+
+ get_value(a, s->atom, &va);
+ get_value(b, s->atom, &vb);
+ switch (cmp_type) {
+ case FIELD_STR:
+ cmp = strcmp(va->s, vb->s);
+ break;
+ default:
+ if (va->ul < vb->ul)
+ cmp = -1;
+ else if (va->ul == vb->ul)
+ cmp = 0;
+ else
+ cmp = 1;
+ break;
+ }
+ return (s->reverse) ? -cmp : cmp;
+}
+
+static struct ref_sort *ref_sort;
+static int compare_refs(const void *a_, const void *b_)
+{
+ struct refinfo *a = *((struct refinfo **)a_);
+ struct refinfo *b = *((struct refinfo **)b_);
+ struct ref_sort *s;
+
+ for (s = ref_sort; s; s = s->next) {
+ int cmp = cmp_ref_sort(s, a, b);
+ if (cmp)
+ return cmp;
+ }
+ return 0;
+}
+
+static void sort_refs(struct ref_sort *sort, struct refinfo **refs, int num_refs)
+{
+ ref_sort = sort;
+ qsort(refs, num_refs, sizeof(struct refinfo *), compare_refs);
+}
+
+static void print_value(struct refinfo *ref, int atom, int quote_style)
+{
+ struct atom_value *v;
+ get_value(ref, atom, &v);
+ switch (quote_style) {
+ case QUOTE_NONE:
+ fputs(v->s, stdout);
+ break;
+ case QUOTE_SHELL:
+ sq_quote_print(stdout, v->s);
+ break;
+ case QUOTE_PERL:
+ perl_quote_print(stdout, v->s);
+ break;
+ case QUOTE_PYTHON:
+ python_quote_print(stdout, v->s);
+ break;
+ }
+}
+
+static int hex1(char ch)
+{
+ if ('0' <= ch && ch <= '9')
+ return ch - '0';
+ else if ('a' <= ch && ch <= 'f')
+ return ch - 'a' + 10;
+ else if ('A' <= ch && ch <= 'F')
+ return ch - 'A' + 10;
+ return -1;
+}
+static int hex2(const char *cp)
+{
+ if (cp[0] && cp[1])
+ return (hex1(cp[0]) << 4) | hex1(cp[1]);
+ else
+ return -1;
+}
+
+static void emit(const char *cp, const char *ep)
+{
+ while (*cp && (!ep || cp < ep)) {
+ if (*cp == '%') {
+ if (cp[1] == '%')
+ cp++;
+ else {
+ int ch = hex2(cp + 1);
+ if (0 <= ch) {
+ putchar(ch);
+ cp += 3;
+ continue;
+ }
+ }
+ }
+ putchar(*cp);
+ cp++;
+ }
+}
+
+static void show_ref(struct refinfo *info, const char *format, int quote_style)
+{
+ const char *cp, *sp, *ep;
+
+ for (cp = format; *cp && (sp = find_next(cp)); cp = ep + 1) {
+ ep = strchr(sp, ')');
+ if (cp < sp)
+ emit(cp, sp);
+ print_value(info, parse_atom(sp + 2, ep), quote_style);
+ }
+ if (*cp) {
+ sp = cp + strlen(cp);
+ emit(cp, sp);
+ }
+ putchar('\n');
+}
+
+static struct ref_sort *default_sort(void)
+{
+ static const char cstr_name[] = "refname";
+
+ struct ref_sort *sort = xcalloc(1, sizeof(*sort));
+
+ sort->next = NULL;
+ sort->atom = parse_atom(cstr_name, cstr_name + strlen(cstr_name));
+ return sort;
+}
+
+int cmd_for_each_ref(int ac, const char **av, char *prefix)
+{
+ int i, num_refs;
+ const char *format = NULL;
+ struct ref_sort *sort = NULL, **sort_tail = &sort;
+ int maxcount = 0;
+ int quote_style = -1; /* unspecified yet */
+ struct refinfo **refs;
+ struct grab_ref_cbdata cbdata;
+
+ for (i = 1; i < ac; i++) {
+ const char *arg = av[i];
+ if (arg[0] != '-')
+ break;
+ if (!strcmp(arg, "--")) {
+ i++;
+ break;
+ }
+ if (!strncmp(arg, "--format=", 9)) {
+ if (format)
+ die("more than one --format?");
+ format = arg + 9;
+ continue;
+ }
+ if (!strcmp(arg, "-s") || !strcmp(arg, "--shell") ) {
+ if (0 <= quote_style)
+ die("more than one quoting style?");
+ quote_style = QUOTE_SHELL;
+ continue;
+ }
+ if (!strcmp(arg, "-p") || !strcmp(arg, "--perl") ) {
+ if (0 <= quote_style)
+ die("more than one quoting style?");
+ quote_style = QUOTE_PERL;
+ continue;
+ }
+ if (!strcmp(arg, "--python") ) {
+ if (0 <= quote_style)
+ die("more than one quoting style?");
+ quote_style = QUOTE_PYTHON;
+ continue;
+ }
+ if (!strncmp(arg, "--count=", 8)) {
+ if (maxcount)
+ die("more than one --count?");
+ maxcount = atoi(arg + 8);
+ if (maxcount <= 0)
+ die("The number %s did not parse", arg);
+ continue;
+ }
+ if (!strncmp(arg, "--sort=", 7)) {
+ struct ref_sort *s = xcalloc(1, sizeof(*s));
+ int len;
+
+ s->next = NULL;
+ *sort_tail = s;
+ sort_tail = &s->next;
+
+ arg += 7;
+ if (*arg == '-') {
+ s->reverse = 1;
+ arg++;
+ }
+ len = strlen(arg);
+ sort->atom = parse_atom(arg, arg+len);
+ continue;
+ }
+ break;
+ }
+ if (quote_style < 0)
+ quote_style = QUOTE_NONE;
+
+ if (!sort)
+ sort = default_sort();
+ sort_atom_limit = used_atom_cnt;
+ if (!format)
+ format = "%(objectname) %(objecttype)\t%(refname)";
+
+ verify_format(format);
+
+ memset(&cbdata, 0, sizeof(cbdata));
+ cbdata.grab_pattern = av + i;
+ for_each_ref(grab_single_ref, &cbdata);
+ refs = cbdata.grab_array;
+ num_refs = cbdata.grab_cnt;
+
+ for (i = 0; i < used_atom_cnt; i++) {
+ if (used_atom[i][0] == '*') {
+ need_tagged = 1;
+ break;
+ }
+ }
+
+ sort_refs(sort, refs, num_refs);
+
+ if (!maxcount || num_refs < maxcount)
+ maxcount = num_refs;
+ for (i = 0; i < maxcount; i++)
+ show_ref(refs[i], format, quote_style);
+ return 0;
+}
diff --git a/builtin-grep.c b/builtin-grep.c
index a561612e7e..ad7dc00cde 100644
--- a/builtin-grep.c
+++ b/builtin-grep.c
@@ -11,6 +11,7 @@
#include "tree-walk.h"
#include "builtin.h"
#include <regex.h>
+#include "grep.h"
#include <fnmatch.h>
#include <sys/wait.h>
@@ -82,558 +83,6 @@ static int pathspec_matches(const char **paths, const char *name)
return 0;
}
-enum grep_pat_token {
- GREP_PATTERN,
- GREP_AND,
- GREP_OPEN_PAREN,
- GREP_CLOSE_PAREN,
- GREP_NOT,
- GREP_OR,
-};
-
-struct grep_pat {
- struct grep_pat *next;
- const char *origin;
- int no;
- enum grep_pat_token token;
- const char *pattern;
- regex_t regexp;
-};
-
-enum grep_expr_node {
- GREP_NODE_ATOM,
- GREP_NODE_NOT,
- GREP_NODE_AND,
- GREP_NODE_OR,
-};
-
-struct grep_expr {
- enum grep_expr_node node;
- union {
- struct grep_pat *atom;
- struct grep_expr *unary;
- struct {
- struct grep_expr *left;
- struct grep_expr *right;
- } binary;
- } u;
-};
-
-struct grep_opt {
- struct grep_pat *pattern_list;
- struct grep_pat **pattern_tail;
- struct grep_expr *pattern_expression;
- int prefix_length;
- regex_t regexp;
- unsigned linenum:1;
- unsigned invert:1;
- unsigned name_only:1;
- unsigned unmatch_name_only:1;
- unsigned count:1;
- unsigned word_regexp:1;
- unsigned fixed:1;
-#define GREP_BINARY_DEFAULT 0
-#define GREP_BINARY_NOMATCH 1
-#define GREP_BINARY_TEXT 2
- unsigned binary:2;
- unsigned extended:1;
- unsigned relative:1;
- int regflags;
- unsigned pre_context;
- unsigned post_context;
-};
-
-static void add_pattern(struct grep_opt *opt, const char *pat,
- const char *origin, int no, enum grep_pat_token t)
-{
- struct grep_pat *p = xcalloc(1, sizeof(*p));
- p->pattern = pat;
- p->origin = origin;
- p->no = no;
- p->token = t;
- *opt->pattern_tail = p;
- opt->pattern_tail = &p->next;
- p->next = NULL;
-}
-
-static void compile_regexp(struct grep_pat *p, struct grep_opt *opt)
-{
- int err = regcomp(&p->regexp, p->pattern, opt->regflags);
- if (err) {
- char errbuf[1024];
- char where[1024];
- if (p->no)
- sprintf(where, "In '%s' at %d, ",
- p->origin, p->no);
- else if (p->origin)
- sprintf(where, "%s, ", p->origin);
- else
- where[0] = 0;
- regerror(err, &p->regexp, errbuf, 1024);
- regfree(&p->regexp);
- die("%s'%s': %s", where, p->pattern, errbuf);
- }
-}
-
-#if DEBUG
-static inline void indent(int in)
-{
- int i;
- for (i = 0; i < in; i++) putchar(' ');
-}
-
-static void dump_pattern_exp(struct grep_expr *x, int in)
-{
- switch (x->node) {
- case GREP_NODE_ATOM:
- indent(in);
- puts(x->u.atom->pattern);
- break;
- case GREP_NODE_NOT:
- indent(in);
- puts("--not");
- dump_pattern_exp(x->u.unary, in+1);
- break;
- case GREP_NODE_AND:
- dump_pattern_exp(x->u.binary.left, in+1);
- indent(in);
- puts("--and");
- dump_pattern_exp(x->u.binary.right, in+1);
- break;
- case GREP_NODE_OR:
- dump_pattern_exp(x->u.binary.left, in+1);
- indent(in);
- puts("--or");
- dump_pattern_exp(x->u.binary.right, in+1);
- break;
- }
-}
-
-static void looking_at(const char *msg, struct grep_pat **list)
-{
- struct grep_pat *p = *list;
- fprintf(stderr, "%s: looking at ", msg);
- if (!p)
- fprintf(stderr, "empty\n");
- else
- fprintf(stderr, "<%s>\n", p->pattern);
-}
-#else
-#define looking_at(a,b) do {} while(0)
-#endif
-
-static struct grep_expr *compile_pattern_expr(struct grep_pat **);
-static struct grep_expr *compile_pattern_atom(struct grep_pat **list)
-{
- struct grep_pat *p;
- struct grep_expr *x;
-
- looking_at("atom", list);
-
- p = *list;
- switch (p->token) {
- case GREP_PATTERN: /* atom */
- x = xcalloc(1, sizeof (struct grep_expr));
- x->node = GREP_NODE_ATOM;
- x->u.atom = p;
- *list = p->next;
- return x;
- case GREP_OPEN_PAREN:
- *list = p->next;
- x = compile_pattern_expr(list);
- if (!x)
- return NULL;
- if (!*list || (*list)->token != GREP_CLOSE_PAREN)
- die("unmatched parenthesis");
- *list = (*list)->next;
- return x;
- default:
- return NULL;
- }
-}
-
-static struct grep_expr *compile_pattern_not(struct grep_pat **list)
-{
- struct grep_pat *p;
- struct grep_expr *x;
-
- looking_at("not", list);
-
- p = *list;
- switch (p->token) {
- case GREP_NOT:
- if (!p->next)
- die("--not not followed by pattern expression");
- *list = p->next;
- x = xcalloc(1, sizeof (struct grep_expr));
- x->node = GREP_NODE_NOT;
- x->u.unary = compile_pattern_not(list);
- if (!x->u.unary)
- die("--not followed by non pattern expression");
- return x;
- default:
- return compile_pattern_atom(list);
- }
-}
-
-static struct grep_expr *compile_pattern_and(struct grep_pat **list)
-{
- struct grep_pat *p;
- struct grep_expr *x, *y, *z;
-
- looking_at("and", list);
-
- x = compile_pattern_not(list);
- p = *list;
- if (p && p->token == GREP_AND) {
- if (!p->next)
- die("--and not followed by pattern expression");
- *list = p->next;
- y = compile_pattern_and(list);
- if (!y)
- die("--and not followed by pattern expression");
- z = xcalloc(1, sizeof (struct grep_expr));
- z->node = GREP_NODE_AND;
- z->u.binary.left = x;
- z->u.binary.right = y;
- return z;
- }
- return x;
-}
-
-static struct grep_expr *compile_pattern_or(struct grep_pat **list)
-{
- struct grep_pat *p;
- struct grep_expr *x, *y, *z;
-
- looking_at("or", list);
-
- x = compile_pattern_and(list);
- p = *list;
- if (x && p && p->token != GREP_CLOSE_PAREN) {
- y = compile_pattern_or(list);
- if (!y)
- die("not a pattern expression %s", p->pattern);
- z = xcalloc(1, sizeof (struct grep_expr));
- z->node = GREP_NODE_OR;
- z->u.binary.left = x;
- z->u.binary.right = y;
- return z;
- }
- return x;
-}
-
-static struct grep_expr *compile_pattern_expr(struct grep_pat **list)
-{
- looking_at("expr", list);
-
- return compile_pattern_or(list);
-}
-
-static void compile_patterns(struct grep_opt *opt)
-{
- struct grep_pat *p;
-
- /* First compile regexps */
- for (p = opt->pattern_list; p; p = p->next) {
- if (p->token == GREP_PATTERN)
- compile_regexp(p, opt);
- else
- opt->extended = 1;
- }
-
- if (!opt->extended)
- return;
-
- /* Then bundle them up in an expression.
- * A classic recursive descent parser would do.
- */
- p = opt->pattern_list;
- opt->pattern_expression = compile_pattern_expr(&p);
-#if DEBUG
- dump_pattern_exp(opt->pattern_expression, 0);
-#endif
- if (p)
- die("incomplete pattern expression: %s", p->pattern);
-}
-
-static char *end_of_line(char *cp, unsigned long *left)
-{
- unsigned long l = *left;
- while (l && *cp != '\n') {
- l--;
- cp++;
- }
- *left = l;
- return cp;
-}
-
-static int word_char(char ch)
-{
- return isalnum(ch) || ch == '_';
-}
-
-static void show_line(struct grep_opt *opt, const char *bol, const char *eol,
- const char *name, unsigned lno, char sign)
-{
- printf("%s%c", name, sign);
- if (opt->linenum)
- printf("%d%c", lno, sign);
- printf("%.*s\n", (int)(eol-bol), bol);
-}
-
-/*
- * NEEDSWORK: share code with diff.c
- */
-#define FIRST_FEW_BYTES 8000
-static int buffer_is_binary(const char *ptr, unsigned long size)
-{
- if (FIRST_FEW_BYTES < size)
- size = FIRST_FEW_BYTES;
- if (memchr(ptr, 0, size))
- return 1;
- return 0;
-}
-
-static int fixmatch(const char *pattern, char *line, regmatch_t *match)
-{
- char *hit = strstr(line, pattern);
- if (!hit) {
- match->rm_so = match->rm_eo = -1;
- return REG_NOMATCH;
- }
- else {
- match->rm_so = hit - line;
- match->rm_eo = match->rm_so + strlen(pattern);
- return 0;
- }
-}
-
-static int match_one_pattern(struct grep_opt *opt, struct grep_pat *p, char *bol, char *eol)
-{
- int hit = 0;
- int at_true_bol = 1;
- regmatch_t pmatch[10];
-
- again:
- if (!opt->fixed) {
- regex_t *exp = &p->regexp;
- hit = !regexec(exp, bol, ARRAY_SIZE(pmatch),
- pmatch, 0);
- }
- else {
- hit = !fixmatch(p->pattern, bol, pmatch);
- }
-
- if (hit && opt->word_regexp) {
- if ((pmatch[0].rm_so < 0) ||
- (eol - bol) <= pmatch[0].rm_so ||
- (pmatch[0].rm_eo < 0) ||
- (eol - bol) < pmatch[0].rm_eo)
- die("regexp returned nonsense");
-
- /* Match beginning must be either beginning of the
- * line, or at word boundary (i.e. the last char must
- * not be a word char). Similarly, match end must be
- * either end of the line, or at word boundary
- * (i.e. the next char must not be a word char).
- */
- if ( ((pmatch[0].rm_so == 0 && at_true_bol) ||
- !word_char(bol[pmatch[0].rm_so-1])) &&
- ((pmatch[0].rm_eo == (eol-bol)) ||
- !word_char(bol[pmatch[0].rm_eo])) )
- ;
- else
- hit = 0;
-
- if (!hit && pmatch[0].rm_so + bol + 1 < eol) {
- /* There could be more than one match on the
- * line, and the first match might not be
- * strict word match. But later ones could be!
- */
- bol = pmatch[0].rm_so + bol + 1;
- at_true_bol = 0;
- goto again;
- }
- }
- return hit;
-}
-
-static int match_expr_eval(struct grep_opt *opt,
- struct grep_expr *x,
- char *bol, char *eol)
-{
- switch (x->node) {
- case GREP_NODE_ATOM:
- return match_one_pattern(opt, x->u.atom, bol, eol);
- break;
- case GREP_NODE_NOT:
- return !match_expr_eval(opt, x->u.unary, bol, eol);
- case GREP_NODE_AND:
- return (match_expr_eval(opt, x->u.binary.left, bol, eol) &&
- match_expr_eval(opt, x->u.binary.right, bol, eol));
- case GREP_NODE_OR:
- return (match_expr_eval(opt, x->u.binary.left, bol, eol) ||
- match_expr_eval(opt, x->u.binary.right, bol, eol));
- }
- die("Unexpected node type (internal error) %d\n", x->node);
-}
-
-static int match_expr(struct grep_opt *opt, char *bol, char *eol)
-{
- struct grep_expr *x = opt->pattern_expression;
- return match_expr_eval(opt, x, bol, eol);
-}
-
-static int match_line(struct grep_opt *opt, char *bol, char *eol)
-{
- struct grep_pat *p;
- if (opt->extended)
- return match_expr(opt, bol, eol);
- for (p = opt->pattern_list; p; p = p->next) {
- if (match_one_pattern(opt, p, bol, eol))
- return 1;
- }
- return 0;
-}
-
-static int grep_buffer(struct grep_opt *opt, const char *name,
- char *buf, unsigned long size)
-{
- char *bol = buf;
- unsigned long left = size;
- unsigned lno = 1;
- struct pre_context_line {
- char *bol;
- char *eol;
- } *prev = NULL, *pcl;
- unsigned last_hit = 0;
- unsigned last_shown = 0;
- int binary_match_only = 0;
- const char *hunk_mark = "";
- unsigned count = 0;
-
- if (buffer_is_binary(buf, size)) {
- switch (opt->binary) {
- case GREP_BINARY_DEFAULT:
- binary_match_only = 1;
- break;
- case GREP_BINARY_NOMATCH:
- return 0; /* Assume unmatch */
- break;
- default:
- break;
- }
- }
-
- if (opt->pre_context)
- prev = xcalloc(opt->pre_context, sizeof(*prev));
- if (opt->pre_context || opt->post_context)
- hunk_mark = "--\n";
-
- while (left) {
- char *eol, ch;
- int hit = 0;
-
- eol = end_of_line(bol, &left);
- ch = *eol;
- *eol = 0;
-
- hit = match_line(opt, bol, eol);
-
- /* "grep -v -e foo -e bla" should list lines
- * that do not have either, so inversion should
- * be done outside.
- */
- if (opt->invert)
- hit = !hit;
- if (opt->unmatch_name_only) {
- if (hit)
- return 0;
- goto next_line;
- }
- if (hit) {
- count++;
- if (binary_match_only) {
- printf("Binary file %s matches\n", name);
- return 1;
- }
- if (opt->name_only) {
- printf("%s\n", name);
- return 1;
- }
- /* Hit at this line. If we haven't shown the
- * pre-context lines, we would need to show them.
- * When asked to do "count", this still show
- * the context which is nonsense, but the user
- * deserves to get that ;-).
- */
- if (opt->pre_context) {
- unsigned from;
- if (opt->pre_context < lno)
- from = lno - opt->pre_context;
- else
- from = 1;
- if (from <= last_shown)
- from = last_shown + 1;
- if (last_shown && from != last_shown + 1)
- printf(hunk_mark);
- while (from < lno) {
- pcl = &prev[lno-from-1];
- show_line(opt, pcl->bol, pcl->eol,
- name, from, '-');
- from++;
- }
- last_shown = lno-1;
- }
- if (last_shown && lno != last_shown + 1)
- printf(hunk_mark);
- if (!opt->count)
- show_line(opt, bol, eol, name, lno, ':');
- last_shown = last_hit = lno;
- }
- else if (last_hit &&
- lno <= last_hit + opt->post_context) {
- /* If the last hit is within the post context,
- * we need to show this line.
- */
- if (last_shown && lno != last_shown + 1)
- printf(hunk_mark);
- show_line(opt, bol, eol, name, lno, '-');
- last_shown = lno;
- }
- if (opt->pre_context) {
- memmove(prev+1, prev,
- (opt->pre_context-1) * sizeof(*prev));
- prev->bol = bol;
- prev->eol = eol;
- }
-
- next_line:
- *eol = ch;
- bol = eol + 1;
- if (!left)
- break;
- left--;
- lno++;
- }
-
- if (opt->unmatch_name_only) {
- /* We did not see any hit, so we want to show this */
- printf("%s\n", name);
- return 1;
- }
-
- /* NEEDSWORK:
- * The real "grep -c foo *.c" gives many "bar.c:0" lines,
- * which feels mostly useless but sometimes useful. Maybe
- * make it another option? For now suppress them.
- */
- if (opt->count && count)
- printf("%s:%u\n", name, count);
- return !!last_hit;
-}
-
static int grep_sha1(struct grep_opt *opt, const unsigned char *sha1, const char *name, int tree_name_len)
{
unsigned long size;
@@ -753,6 +202,8 @@ static int external_grep(struct grep_opt *opt, const char **paths, int cached)
push_arg("-F");
if (opt->linenum)
push_arg("-n");
+ if (!opt->pathname)
+ push_arg("-h");
if (opt->regflags & REG_EXTENDED)
push_arg("-E");
if (opt->regflags & REG_ICASE)
@@ -874,6 +325,7 @@ static int grep_cache(struct grep_opt *opt, const char **paths, int cached)
else
hit |= grep_file(opt, ce->name);
}
+ free_grep_patterns(opt);
return hit;
}
@@ -973,6 +425,7 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
memset(&opt, 0, sizeof(opt));
opt.prefix_length = (prefix && *prefix) ? strlen(prefix) : 0;
opt.relative = 1;
+ opt.pathname = 1;
opt.pattern_tail = &opt.pattern_list;
opt.regflags = REG_NEWLINE;
@@ -1032,10 +485,12 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
opt.linenum = 1;
continue;
}
+ if (!strcmp("-h", arg)) {
+ opt.pathname = 0;
+ continue;
+ }
if (!strcmp("-H", arg)) {
- /* We always show the pathname, so this
- * is a noop.
- */
+ opt.pathname = 1;
continue;
}
if (!strcmp("-l", arg) ||
@@ -1110,8 +565,9 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
/* ignore empty line like grep does */
if (!buf[0])
continue;
- add_pattern(&opt, strdup(buf), argv[1], ++lno,
- GREP_PATTERN);
+ append_grep_pattern(&opt, xstrdup(buf),
+ argv[1], ++lno,
+ GREP_PATTERN);
}
fclose(patterns);
argv++;
@@ -1119,27 +575,36 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
continue;
}
if (!strcmp("--not", arg)) {
- add_pattern(&opt, arg, "command line", 0, GREP_NOT);
+ append_grep_pattern(&opt, arg, "command line", 0,
+ GREP_NOT);
continue;
}
if (!strcmp("--and", arg)) {
- add_pattern(&opt, arg, "command line", 0, GREP_AND);
+ append_grep_pattern(&opt, arg, "command line", 0,
+ GREP_AND);
continue;
}
if (!strcmp("--or", arg))
continue; /* no-op */
if (!strcmp("(", arg)) {
- add_pattern(&opt, arg, "command line", 0, GREP_OPEN_PAREN);
+ append_grep_pattern(&opt, arg, "command line", 0,
+ GREP_OPEN_PAREN);
continue;
}
if (!strcmp(")", arg)) {
- add_pattern(&opt, arg, "command line", 0, GREP_CLOSE_PAREN);
+ append_grep_pattern(&opt, arg, "command line", 0,
+ GREP_CLOSE_PAREN);
+ continue;
+ }
+ if (!strcmp("--all-match", arg)) {
+ opt.all_match = 1;
continue;
}
if (!strcmp("-e", arg)) {
if (1 < argc) {
- add_pattern(&opt, argv[1], "-e option", 0,
- GREP_PATTERN);
+ append_grep_pattern(&opt, argv[1],
+ "-e option", 0,
+ GREP_PATTERN);
argv++;
argc--;
continue;
@@ -1161,8 +626,8 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
/* First unrecognized non-option token */
if (!opt.pattern_list) {
- add_pattern(&opt, arg, "command line", 0,
- GREP_PATTERN);
+ append_grep_pattern(&opt, arg, "command line", 0,
+ GREP_PATTERN);
break;
}
else {
@@ -1179,8 +644,7 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
die("no pattern given.");
if ((opt.regflags != REG_NEWLINE) && opt.fixed)
die("cannot mix --fixed-strings and regexp");
- if (!opt.fixed)
- compile_patterns(&opt);
+ compile_grep_patterns(&opt);
/* Check revs and then paths */
for (i = 1; i < argc; i++) {
@@ -1235,5 +699,6 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
if (grep_object(&opt, paths, real_obj, list.objects[i].name))
hit = 1;
}
+ free_grep_patterns(&opt);
return !hit;
}
diff --git a/builtin-init-db.c b/builtin-init-db.c
index 5085018e46..235a0ee48f 100644
--- a/builtin-init-db.c
+++ b/builtin-init-db.c
@@ -218,8 +218,8 @@ static void create_default_files(const char *git_dir, const char *template_path)
* branch, if it does not exist yet.
*/
strcpy(path + len, "HEAD");
- if (read_ref(path, sha1) < 0) {
- if (create_symref(path, "refs/heads/master") < 0)
+ if (read_ref("HEAD", sha1) < 0) {
+ if (create_symref("HEAD", "refs/heads/master") < 0)
exit(1);
}
@@ -311,6 +311,7 @@ int cmd_init_db(int argc, const char **argv, const char *prefix)
*/
sprintf(buf, "%d", shared_repository);
git_config_set("core.sharedrepository", buf);
+ git_config_set("receive.denyNonFastforwards", "true");
}
return 0;
diff --git a/builtin-log.c b/builtin-log.c
index 691cf3aef7..fedb0137bc 100644
--- a/builtin-log.c
+++ b/builtin-log.c
@@ -101,7 +101,7 @@ static int git_format_config(const char *var, const char *value)
if (!strcmp(var, "format.headers")) {
int len = strlen(value);
extra_headers_size += len + 1;
- extra_headers = realloc(extra_headers, extra_headers_size);
+ extra_headers = xrealloc(extra_headers, extra_headers_size);
extra_headers[extra_headers_size - len - 1] = 0;
strcat(extra_headers, value);
return 0;
@@ -171,8 +171,11 @@ static void reopen_stdout(struct commit *commit, int nr, int keep_subject)
static int get_patch_id(struct commit *commit, struct diff_options *options,
unsigned char *sha1)
{
- diff_tree_sha1(commit->parents->item->object.sha1, commit->object.sha1,
- "", options);
+ if (commit->parents)
+ diff_tree_sha1(commit->parents->item->object.sha1,
+ commit->object.sha1, "", options);
+ else
+ diff_root_tree_sha1(commit->object.sha1, "", options);
diffcore_std(options);
return diff_flush_patch_id(options, sha1);
}
@@ -348,6 +351,9 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
if (!rev.diffopt.output_format)
rev.diffopt.output_format = DIFF_FORMAT_DIFFSTAT | DIFF_FORMAT_PATCH;
+ if (!output_directory)
+ output_directory = prefix;
+
if (output_directory) {
if (use_stdout)
die("standard output, or directory, which one?");
@@ -381,7 +387,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
continue;
nr++;
- list = realloc(list, nr * sizeof(list[0]));
+ list = xrealloc(list, nr * sizeof(list[0]));
list[nr - 1] = commit;
}
total = nr;
@@ -434,3 +440,109 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
return 0;
}
+static int add_pending_commit(const char *arg, struct rev_info *revs, int flags)
+{
+ unsigned char sha1[20];
+ if (get_sha1(arg, sha1) == 0) {
+ struct commit *commit = lookup_commit_reference(sha1);
+ if (commit) {
+ commit->object.flags |= flags;
+ add_pending_object(revs, &commit->object, arg);
+ return 0;
+ }
+ }
+ return -1;
+}
+
+static const char cherry_usage[] =
+"git-cherry [-v] <upstream> [<head>] [<limit>]";
+int cmd_cherry(int argc, const char **argv, const char *prefix)
+{
+ struct rev_info revs;
+ struct diff_options patch_id_opts;
+ struct commit *commit;
+ struct commit_list *list = NULL;
+ const char *upstream;
+ const char *head = "HEAD";
+ const char *limit = NULL;
+ int verbose = 0;
+
+ if (argc > 1 && !strcmp(argv[1], "-v")) {
+ verbose = 1;
+ argc--;
+ argv++;
+ }
+
+ switch (argc) {
+ case 4:
+ limit = argv[3];
+ /* FALLTHROUGH */
+ case 3:
+ head = argv[2];
+ /* FALLTHROUGH */
+ case 2:
+ upstream = argv[1];
+ break;
+ default:
+ usage(cherry_usage);
+ }
+
+ init_revisions(&revs, prefix);
+ revs.diff = 1;
+ revs.combine_merges = 0;
+ revs.ignore_merges = 1;
+ revs.diffopt.recursive = 1;
+
+ if (add_pending_commit(head, &revs, 0))
+ die("Unknown commit %s", head);
+ if (add_pending_commit(upstream, &revs, UNINTERESTING))
+ die("Unknown commit %s", upstream);
+
+ /* Don't say anything if head and upstream are the same. */
+ if (revs.pending.nr == 2) {
+ struct object_array_entry *o = revs.pending.objects;
+ if (hashcmp(o[0].item->sha1, o[1].item->sha1) == 0)
+ return 0;
+ }
+
+ get_patch_ids(&revs, &patch_id_opts, prefix);
+
+ if (limit && add_pending_commit(limit, &revs, UNINTERESTING))
+ die("Unknown commit %s", limit);
+
+ /* reverse the list of commits */
+ prepare_revision_walk(&revs);
+ while ((commit = get_revision(&revs)) != NULL) {
+ /* ignore merges */
+ if (commit->parents && commit->parents->next)
+ continue;
+
+ commit_list_insert(commit, &list);
+ }
+
+ while (list) {
+ unsigned char sha1[20];
+ char sign = '+';
+
+ commit = list->item;
+ if (!get_patch_id(commit, &patch_id_opts, sha1) &&
+ lookup_object(sha1))
+ sign = '-';
+
+ if (verbose) {
+ static char buf[16384];
+ pretty_print_commit(CMIT_FMT_ONELINE, commit, ~0,
+ buf, sizeof(buf), 0, NULL, NULL, 0);
+ printf("%c %s %s\n", sign,
+ sha1_to_hex(commit->object.sha1), buf);
+ }
+ else {
+ printf("%c %s\n", sign,
+ sha1_to_hex(commit->object.sha1));
+ }
+
+ list = list->next;
+ }
+
+ return 0;
+}
diff --git a/builtin-ls-files.c b/builtin-ls-files.c
index 11386c432b..ad8c41e731 100644
--- a/builtin-ls-files.c
+++ b/builtin-ls-files.c
@@ -12,21 +12,22 @@
#include "dir.h"
#include "builtin.h"
-static int abbrev = 0;
-static int show_deleted = 0;
-static int show_cached = 0;
-static int show_others = 0;
-static int show_stage = 0;
-static int show_unmerged = 0;
-static int show_modified = 0;
-static int show_killed = 0;
-static int show_valid_bit = 0;
+static int abbrev;
+static int show_deleted;
+static int show_cached;
+static int show_others;
+static int show_stage;
+static int show_unmerged;
+static int show_modified;
+static int show_killed;
+static int show_valid_bit;
static int line_terminator = '\n';
-static int prefix_len = 0, prefix_offset = 0;
-static const char **pathspec = NULL;
-static int error_unmatch = 0;
-static char *ps_matched = NULL;
+static int prefix_len;
+static int prefix_offset;
+static const char **pathspec;
+static int error_unmatch;
+static char *ps_matched;
static const char *tag_cached = "";
static const char *tag_unmerged = "";
diff --git a/builtin-ls-tree.c b/builtin-ls-tree.c
index 261147fdbe..201defd934 100644
--- a/builtin-ls-tree.c
+++ b/builtin-ls-tree.c
@@ -14,10 +14,10 @@ static int line_termination = '\n';
#define LS_TREE_ONLY 2
#define LS_SHOW_TREES 4
#define LS_NAME_ONLY 8
-static int abbrev = 0;
-static int ls_options = 0;
+static int abbrev;
+static int ls_options;
static const char **pathspec;
-static int chomp_prefix = 0;
+static int chomp_prefix;
static const char *ls_tree_prefix;
static const char ls_tree_usage[] =
diff --git a/builtin-mailinfo.c b/builtin-mailinfo.c
index 24a4fc63b3..b8d7dbc0b7 100644
--- a/builtin-mailinfo.c
+++ b/builtin-mailinfo.c
@@ -16,8 +16,8 @@
static FILE *cmitmsg, *patchfile, *fin, *fout;
-static int keep_subject = 0;
-static const char *metainfo_charset = NULL;
+static int keep_subject;
+static const char *metainfo_charset;
static char line[1000];
static char date[1000];
static char name[1000];
@@ -31,7 +31,7 @@ static char charset[256];
static char multipart_boundary[1000];
static int multipart_boundary_len;
-static int patch_lines = 0;
+static int patch_lines;
static char *sanity_check(char *name, char *email)
{
@@ -451,17 +451,6 @@ static int read_one_header_line(char *line, int sz, FILE *in)
return ofs;
}
-static unsigned hexval(int c)
-{
- if (c >= '0' && c <= '9')
- return c - '0';
- if (c >= 'a' && c <= 'f')
- return c - 'a' + 10;
- if (c >= 'A' && c <= 'F')
- return c - 'A' + 10;
- return ~0;
-}
-
static int decode_q_segment(char *in, char *ot, char *ep, int rfc2047)
{
int c;
diff --git a/builtin-mv.c b/builtin-mv.c
index a731f8d9cf..54dd3bfe8a 100644
--- a/builtin-mv.c
+++ b/builtin-mv.c
@@ -17,12 +17,19 @@ static const char builtin_mv_usage[] =
static const char **copy_pathspec(const char *prefix, const char **pathspec,
int count, int base_name)
{
+ int i;
const char **result = xmalloc((count + 1) * sizeof(const char *));
memcpy(result, pathspec, count * sizeof(const char *));
result[count] = NULL;
- if (base_name) {
- int i;
- for (i = 0; i < count; i++) {
+ for (i = 0; i < count; i++) {
+ int length = strlen(result[i]);
+ if (length > 0 && result[i][length - 1] == '/') {
+ char *without_slash = xmalloc(length);
+ memcpy(without_slash, result[i], length - 1);
+ without_slash[length - 1] = '\0';
+ result[i] = without_slash;
+ }
+ if (base_name) {
const char *last_slash = strrchr(result[i], '/');
if (last_slash)
result[i] = last_slash + 1;
@@ -107,7 +114,10 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
modes = xcalloc(count, sizeof(enum update_mode));
dest_path = copy_pathspec(prefix, argv + argc - 1, 1, 0);
- if (!lstat(dest_path[0], &st) &&
+ if (dest_path[0][0] == '\0')
+ /* special case: "." was normalized to "" */
+ destination = copy_pathspec(dest_path[0], argv + i, count, 1);
+ else if (!lstat(dest_path[0], &st) &&
S_ISDIR(st.st_mode)) {
dest_path[0] = add_slash(dest_path[0]);
destination = copy_pathspec(dest_path[0], argv + i, count, 1);
@@ -119,72 +129,71 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
/* Checking */
for (i = 0; i < count; i++) {
+ const char *src = source[i], *dst = destination[i];
+ int length, src_is_dir;
const char *bad = NULL;
if (show_only)
- printf("Checking rename of '%s' to '%s'\n",
- source[i], destination[i]);
+ printf("Checking rename of '%s' to '%s'\n", src, dst);
- if (lstat(source[i], &st) < 0)
+ length = strlen(src);
+ if (lstat(src, &st) < 0)
bad = "bad source";
-
- if (S_ISDIR(st.st_mode)) {
- const char *dir = source[i], *dest_dir = destination[i];
- int first, last, len = strlen(dir);
-
- if (lstat(dest_dir, &st) == 0) {
- bad = "cannot move directory over file";
- goto next;
- }
+ else if (!strncmp(src, dst, length) &&
+ (dst[length] == 0 || dst[length] == '/')) {
+ bad = "can not move directory into itself";
+ } else if ((src_is_dir = S_ISDIR(st.st_mode))
+ && lstat(dst, &st) == 0)
+ bad = "cannot move directory over file";
+ else if (src_is_dir) {
+ int first, last;
modes[i] = WORKING_DIRECTORY;
- first = cache_name_pos(source[i], len);
+ first = cache_name_pos(src, length);
if (first >= 0)
- die ("Huh? %s/ is in index?", dir);
+ die ("Huh? %s/ is in index?", src);
first = -1 - first;
for (last = first; last < active_nr; last++) {
const char *path = active_cache[last]->name;
- if (strncmp(path, dir, len) || path[len] != '/')
+ if (strncmp(path, src, length)
+ || path[length] != '/')
break;
}
if (last - first < 1)
bad = "source directory is empty";
- else if (!bad) {
- int j, dst_len = strlen(dest_dir);
+ else {
+ int j, dst_len;
if (last - first > 0) {
- source = realloc(source,
+ source = xrealloc(source,
(count + last - first)
* sizeof(char *));
- destination = realloc(destination,
+ destination = xrealloc(destination,
(count + last - first)
* sizeof(char *));
- modes = realloc(modes,
+ modes = xrealloc(modes,
(count + last - first)
* sizeof(enum update_mode));
}
- dest_dir = add_slash(dest_dir);
+ dst = add_slash(dst);
+ dst_len = strlen(dst) - 1;
for (j = 0; j < last - first; j++) {
const char *path =
active_cache[first + j]->name;
source[count + j] = path;
destination[count + j] =
- prefix_path(dest_dir, dst_len,
- path + len);
+ prefix_path(dst, dst_len,
+ path + length);
modes[count + j] = INDEX;
}
count += last - first;
}
-
- goto next;
- }
-
- if (!bad && lstat(destination[i], &st) == 0) {
+ } else if (lstat(dst, &st) == 0) {
bad = "destination exists";
if (force) {
/*
@@ -196,28 +205,17 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
" will overwrite!\n",
bad);
bad = NULL;
- path_list_insert(destination[i],
- &overwritten);
+ path_list_insert(dst, &overwritten);
} else
bad = "Cannot overwrite";
}
- }
-
- if (!bad &&
- !strncmp(destination[i], source[i], strlen(source[i])))
- bad = "can not move directory into itself";
-
- if (!bad && cache_name_pos(source[i], strlen(source[i])) < 0)
+ } else if (cache_name_pos(src, length) < 0)
bad = "not under version control";
+ else if (path_list_has_path(&src_for_dst, dst))
+ bad = "multiple sources for the same target";
+ else
+ path_list_insert(dst, &src_for_dst);
- if (!bad) {
- if (path_list_has_path(&src_for_dst, destination[i]))
- bad = "multiple sources for the same target";
- else
- path_list_insert(destination[i], &src_for_dst);
- }
-
-next:
if (bad) {
if (ignore_errors) {
if (--count > 0) {
@@ -229,33 +227,32 @@ next:
}
} else
die ("%s, source=%s, destination=%s",
- bad, source[i], destination[i]);
+ bad, src, dst);
}
}
for (i = 0; i < count; i++) {
+ const char *src = source[i], *dst = destination[i];
+ enum update_mode mode = modes[i];
if (show_only || verbose)
- printf("Renaming %s to %s\n",
- source[i], destination[i]);
- if (!show_only && modes[i] != INDEX &&
- rename(source[i], destination[i]) < 0 &&
- !ignore_errors)
- die ("renaming %s failed: %s",
- source[i], strerror(errno));
-
- if (modes[i] == WORKING_DIRECTORY)
+ printf("Renaming %s to %s\n", src, dst);
+ if (!show_only && mode != INDEX &&
+ rename(src, dst) < 0 && !ignore_errors)
+ die ("renaming %s failed: %s", src, strerror(errno));
+
+ if (mode == WORKING_DIRECTORY)
continue;
- if (cache_name_pos(source[i], strlen(source[i])) >= 0) {
- path_list_insert(source[i], &deleted);
+ if (cache_name_pos(src, strlen(src)) >= 0) {
+ path_list_insert(src, &deleted);
/* destination can be a directory with 1 file inside */
- if (path_list_has_path(&overwritten, destination[i]))
- path_list_insert(destination[i], &changed);
+ if (path_list_has_path(&overwritten, dst))
+ path_list_insert(dst, &changed);
else
- path_list_insert(destination[i], &added);
+ path_list_insert(dst, &added);
} else
- path_list_insert(destination[i], &added);
+ path_list_insert(dst, &added);
}
if (show_only) {
@@ -265,10 +262,10 @@ next:
} else {
for (i = 0; i < changed.nr; i++) {
const char *path = changed.items[i].path;
- int i = cache_name_pos(path, strlen(path));
- struct cache_entry *ce = active_cache[i];
+ int j = cache_name_pos(path, strlen(path));
+ struct cache_entry *ce = active_cache[j];
- if (i < 0)
+ if (j < 0)
die ("Huh? Cache entry for %s unknown?", path);
refresh_cache_entry(ce, 0);
}
@@ -281,6 +278,7 @@ next:
for (i = 0; i < deleted.nr; i++) {
const char *path = deleted.items[i].path;
remove_file_from_cache(path);
+ cache_tree_invalidate_path(active_cache_tree, path);
}
if (active_cache_changed) {
diff --git a/builtin-name-rev.c b/builtin-name-rev.c
index 571bba4817..618aa314d2 100644
--- a/builtin-name-rev.c
+++ b/builtin-name-rev.c
@@ -75,11 +75,10 @@ copy_data:
}
}
-static int tags_only = 0;
-
-static int name_ref(const char *path, const unsigned char *sha1)
+static int name_ref(const char *path, const unsigned char *sha1, int flags, void *cb_data)
{
struct object *o = parse_object(sha1);
+ int tags_only = *(int*)cb_data;
int deref = 0;
if (tags_only && strncmp(path, "refs/tags/", 10))
@@ -100,7 +99,7 @@ static int name_ref(const char *path, const unsigned char *sha1)
else if (!strncmp(path, "refs/", 5))
path = path + 5;
- name_rev(commit, strdup(path), 0, 0, deref);
+ name_rev(commit, xstrdup(path), 0, 0, deref);
}
return 0;
}
@@ -131,6 +130,7 @@ int cmd_name_rev(int argc, const char **argv, const char *prefix)
{
struct object_array revs = { 0, 0, NULL };
int as_is = 0, all = 0, transform_stdin = 0;
+ int tags_only = 0;
git_config(git_default_config);
@@ -186,7 +186,7 @@ int cmd_name_rev(int argc, const char **argv, const char *prefix)
add_object_array((struct object *)commit, *argv, &revs);
}
- for_each_ref(name_ref);
+ for_each_ref(name_ref, &tags_only);
if (transform_stdin) {
char buffer[2048];
diff --git a/builtin-pack-objects.c b/builtin-pack-objects.c
index 2f9921224d..69e5dd39ca 100644
--- a/builtin-pack-objects.c
+++ b/builtin-pack-objects.c
@@ -9,10 +9,18 @@
#include "pack.h"
#include "csum-file.h"
#include "tree-walk.h"
+#include "diff.h"
+#include "revision.h"
+#include "list-objects.h"
#include <sys/time.h>
#include <signal.h>
-static const char pack_usage[] = "git-pack-objects [-q] [--no-reuse-delta] [--non-empty] [--local] [--incremental] [--window=N] [--depth=N] {--stdout | base-name} < object-list";
+static const char pack_usage[] = "\
+git-pack-objects [{ -q | --progress | --all-progress }] \n\
+ [--local] [--incremental] [--window=N] [--depth=N] \n\
+ [--no-reuse-delta] [--delta-base-offset] [--non-empty] \n\
+ [--revs [--unpacked | --all]*] [--stdout | base-name] \n\
+ [<ref-list | <object-list]";
struct object_entry {
unsigned char sha1[20];
@@ -26,6 +34,7 @@ struct object_entry {
enum object_type type;
enum object_type in_pack_type; /* could be delta */
unsigned long delta_size; /* delta data size (uncompressed) */
+#define in_pack_header_size delta_size /* only when reusing pack data */
struct object_entry *delta; /* delta base object */
struct packed_git *in_pack; /* already in pack */
unsigned int in_pack_offset;
@@ -53,18 +62,22 @@ struct object_entry {
*/
static unsigned char object_list_sha1[20];
-static int non_empty = 0;
-static int no_reuse_delta = 0;
-static int local = 0;
-static int incremental = 0;
+static int non_empty;
+static int no_reuse_delta;
+static int local;
+static int incremental;
+static int allow_ofs_delta;
+
static struct object_entry **sorted_by_sha, **sorted_by_type;
-static struct object_entry *objects = NULL;
-static int nr_objects = 0, nr_alloc = 0, nr_result = 0;
+static struct object_entry *objects;
+static int nr_objects, nr_alloc, nr_result;
static const char *base_name;
static unsigned char pack_file_sha1[20];
static int progress = 1;
-static volatile sig_atomic_t progress_update = 0;
+static volatile sig_atomic_t progress_update;
static int window = 10;
+static int pack_to_stdout;
+static int num_preferred_base;
/*
* The object names in objects array are hashed with this hashtable,
@@ -72,33 +85,41 @@ static int window = 10;
* sorted_by_sha is also possible but this was easier to code and faster.
* This hashtable is built after all the objects are seen.
*/
-static int *object_ix = NULL;
-static int object_ix_hashsz = 0;
+static int *object_ix;
+static int object_ix_hashsz;
/*
* Pack index for existing packs give us easy access to the offsets into
* corresponding pack file where each object's data starts, but the entries
* do not store the size of the compressed representation (uncompressed
- * size is easily available by examining the pack entry header). We build
- * a hashtable of existing packs (pack_revindex), and keep reverse index
- * here -- pack index file is sorted by object name mapping to offset; this
- * pack_revindex[].revindex array is an ordered list of offsets, so if you
- * know the offset of an object, next offset is where its packed
- * representation ends.
+ * size is easily available by examining the pack entry header). It is
+ * also rather expensive to find the sha1 for an object given its offset.
+ *
+ * We build a hashtable of existing packs (pack_revindex), and keep reverse
+ * index here -- pack index file is sorted by object name mapping to offset;
+ * this pack_revindex[].revindex array is a list of offset/index_nr pairs
+ * ordered by offset, so if you know the offset of an object, next offset
+ * is where its packed representation ends and the index_nr can be used to
+ * get the object sha1 from the main index.
*/
+struct revindex_entry {
+ unsigned int offset;
+ unsigned int nr;
+};
struct pack_revindex {
struct packed_git *p;
- unsigned long *revindex;
-} *pack_revindex = NULL;
-static int pack_revindex_hashsz = 0;
+ struct revindex_entry *revindex;
+};
+static struct pack_revindex *pack_revindex;
+static int pack_revindex_hashsz;
/*
* stats
*/
-static int written = 0;
-static int written_delta = 0;
-static int reused = 0;
-static int reused_delta = 0;
+static int written;
+static int written_delta;
+static int reused;
+static int reused_delta;
static int pack_revindex_ix(struct packed_git *p)
{
@@ -136,14 +157,9 @@ static void prepare_pack_ix(void)
static int cmp_offset(const void *a_, const void *b_)
{
- unsigned long a = *(unsigned long *) a_;
- unsigned long b = *(unsigned long *) b_;
- if (a < b)
- return -1;
- else if (a == b)
- return 0;
- else
- return 1;
+ const struct revindex_entry *a = a_;
+ const struct revindex_entry *b = b_;
+ return (a->offset < b->offset) ? -1 : (a->offset > b->offset) ? 1 : 0;
}
/*
@@ -156,25 +172,27 @@ static void prepare_pack_revindex(struct pack_revindex *rix)
int i;
void *index = p->index_base + 256;
- rix->revindex = xmalloc(sizeof(unsigned long) * (num_ent + 1));
+ rix->revindex = xmalloc(sizeof(*rix->revindex) * (num_ent + 1));
for (i = 0; i < num_ent; i++) {
unsigned int hl = *((unsigned int *)((char *) index + 24*i));
- rix->revindex[i] = ntohl(hl);
+ rix->revindex[i].offset = ntohl(hl);
+ rix->revindex[i].nr = i;
}
/* This knows the pack format -- the 20-byte trailer
* follows immediately after the last object data.
*/
- rix->revindex[num_ent] = p->pack_size - 20;
- qsort(rix->revindex, num_ent, sizeof(unsigned long), cmp_offset);
+ rix->revindex[num_ent].offset = p->pack_size - 20;
+ rix->revindex[num_ent].nr = -1;
+ qsort(rix->revindex, num_ent, sizeof(*rix->revindex), cmp_offset);
}
-static unsigned long find_packed_object_size(struct packed_git *p,
- unsigned long ofs)
+static struct revindex_entry * find_packed_object(struct packed_git *p,
+ unsigned int ofs)
{
int num;
int lo, hi;
struct pack_revindex *rix;
- unsigned long *revindex;
+ struct revindex_entry *revindex;
num = pack_revindex_ix(p);
if (num < 0)
die("internal error: pack revindex uninitialized");
@@ -186,10 +204,10 @@ static unsigned long find_packed_object_size(struct packed_git *p,
hi = num_packed_objects(p) + 1;
do {
int mi = (lo + hi) / 2;
- if (revindex[mi] == ofs) {
- return revindex[mi+1] - ofs;
+ if (revindex[mi].offset == ofs) {
+ return revindex + mi;
}
- else if (ofs < revindex[mi])
+ else if (ofs < revindex[mi].offset)
hi = mi;
else
lo = mi + 1;
@@ -197,6 +215,20 @@ static unsigned long find_packed_object_size(struct packed_git *p,
die("internal error: pack revindex corrupt");
}
+static unsigned long find_packed_object_size(struct packed_git *p,
+ unsigned long ofs)
+{
+ struct revindex_entry *entry = find_packed_object(p, ofs);
+ return entry[1].offset - ofs;
+}
+
+static unsigned char *find_packed_object_name(struct packed_git *p,
+ unsigned long ofs)
+{
+ struct revindex_entry *entry = find_packed_object(p, ofs);
+ return (unsigned char *)(p->index_base + 256) + 24 * entry->nr + 4;
+}
+
static void *delta_against(void *buf, unsigned long size, struct object_entry *entry)
{
unsigned long othersize, delta_size;
@@ -227,7 +259,7 @@ static int encode_header(enum object_type type, unsigned long size, unsigned cha
int n = 1;
unsigned char c;
- if (type < OBJ_COMMIT || type > OBJ_DELTA)
+ if (type < OBJ_COMMIT || type > OBJ_REF_DELTA)
die("bad type %d", type);
c = (type << 4) | (size & 15);
@@ -242,6 +274,60 @@ static int encode_header(enum object_type type, unsigned long size, unsigned cha
return n;
}
+/*
+ * we are going to reuse the existing object data as is. make
+ * sure it is not corrupt.
+ */
+static int check_inflate(unsigned char *data, unsigned long len, unsigned long expect)
+{
+ z_stream stream;
+ unsigned char fakebuf[4096];
+ int st;
+
+ memset(&stream, 0, sizeof(stream));
+ stream.next_in = data;
+ stream.avail_in = len;
+ stream.next_out = fakebuf;
+ stream.avail_out = sizeof(fakebuf);
+ inflateInit(&stream);
+
+ while (1) {
+ st = inflate(&stream, Z_FINISH);
+ if (st == Z_STREAM_END || st == Z_OK) {
+ st = (stream.total_out == expect &&
+ stream.total_in == len) ? 0 : -1;
+ break;
+ }
+ if (st != Z_BUF_ERROR) {
+ st = -1;
+ break;
+ }
+ stream.next_out = fakebuf;
+ stream.avail_out = sizeof(fakebuf);
+ }
+ inflateEnd(&stream);
+ return st;
+}
+
+static int revalidate_loose_object(struct object_entry *entry,
+ unsigned char *map,
+ unsigned long mapsize)
+{
+ /* we already know this is a loose object with new type header. */
+ enum object_type type;
+ unsigned long size, used;
+
+ if (pack_to_stdout)
+ return 0;
+
+ used = unpack_object_header_gently(map, mapsize, &type, &size);
+ if (!used)
+ return -1;
+ map += used;
+ mapsize -= used;
+ return check_inflate(map, mapsize, size);
+}
+
static unsigned long write_object(struct sha1file *f,
struct object_entry *entry)
{
@@ -253,13 +339,10 @@ static unsigned long write_object(struct sha1file *f,
enum object_type obj_type;
int to_reuse = 0;
- if (entry->preferred_base)
- return 0;
-
obj_type = entry->type;
if (! entry->in_pack)
to_reuse = 0; /* can't reuse what we don't have */
- else if (obj_type == OBJ_DELTA)
+ else if (obj_type == OBJ_REF_DELTA || obj_type == OBJ_OFS_DELTA)
to_reuse = 1; /* check_object() decided it for us */
else if (obj_type != entry->in_pack_type)
to_reuse = 0; /* pack has delta which is unusable */
@@ -276,6 +359,9 @@ static unsigned long write_object(struct sha1file *f,
map = map_sha1_file(entry->sha1, &mapsize);
if (map && !legacy_loose_object(map)) {
/* We can copy straight into the pack file */
+ if (revalidate_loose_object(entry, map, mapsize))
+ die("corrupt loose object %s",
+ sha1_to_hex(entry->sha1));
sha1write(f, map, mapsize);
munmap(map, mapsize);
written++;
@@ -286,7 +372,7 @@ static unsigned long write_object(struct sha1file *f,
munmap(map, mapsize);
}
- if (! to_reuse) {
+ if (!to_reuse) {
buf = read_sha1_file(entry->sha1, type, &size);
if (!buf)
die("unable to read %s", sha1_to_hex(entry->sha1));
@@ -296,18 +382,35 @@ static unsigned long write_object(struct sha1file *f,
if (entry->delta) {
buf = delta_against(buf, size, entry);
size = entry->delta_size;
- obj_type = OBJ_DELTA;
+ obj_type = (allow_ofs_delta && entry->delta->offset) ?
+ OBJ_OFS_DELTA : OBJ_REF_DELTA;
}
/*
* The object header is a byte of 'type' followed by zero or
- * more bytes of length. For deltas, the 20 bytes of delta
- * sha1 follows that.
+ * more bytes of length.
*/
hdrlen = encode_header(obj_type, size, header);
sha1write(f, header, hdrlen);
- if (entry->delta) {
- sha1write(f, entry->delta, 20);
+ if (obj_type == OBJ_OFS_DELTA) {
+ /*
+ * Deltas with relative base contain an additional
+ * encoding of the relative offset for the delta
+ * base from this object's position in the pack.
+ */
+ unsigned long ofs = entry->offset - entry->delta->offset;
+ unsigned pos = sizeof(header) - 1;
+ header[pos] = ofs & 127;
+ while (ofs >>= 7)
+ header[--pos] = 128 | (--ofs & 127);
+ sha1write(f, header + pos, sizeof(header) - pos);
+ hdrlen += sizeof(header) - pos;
+ } else if (obj_type == OBJ_REF_DELTA) {
+ /*
+ * Deltas with a base reference contain
+ * an additional 20 bytes for the base sha1.
+ */
+ sha1write(f, entry->delta->sha1, 20);
hdrlen += 20;
}
datalen = sha1write_compressed(f, buf, size);
@@ -315,18 +418,40 @@ static unsigned long write_object(struct sha1file *f,
}
else {
struct packed_git *p = entry->in_pack;
- use_packed_git(p);
- datalen = find_packed_object_size(p, entry->in_pack_offset);
- buf = (char *) p->pack_base + entry->in_pack_offset;
+ if (entry->delta) {
+ obj_type = (allow_ofs_delta && entry->delta->offset) ?
+ OBJ_OFS_DELTA : OBJ_REF_DELTA;
+ reused_delta++;
+ }
+ hdrlen = encode_header(obj_type, entry->size, header);
+ sha1write(f, header, hdrlen);
+ if (obj_type == OBJ_OFS_DELTA) {
+ unsigned long ofs = entry->offset - entry->delta->offset;
+ unsigned pos = sizeof(header) - 1;
+ header[pos] = ofs & 127;
+ while (ofs >>= 7)
+ header[--pos] = 128 | (--ofs & 127);
+ sha1write(f, header + pos, sizeof(header) - pos);
+ hdrlen += sizeof(header) - pos;
+ } else if (obj_type == OBJ_REF_DELTA) {
+ sha1write(f, entry->delta->sha1, 20);
+ hdrlen += 20;
+ }
+
+ use_packed_git(p);
+ buf = (char *) p->pack_base
+ + entry->in_pack_offset
+ + entry->in_pack_header_size;
+ datalen = find_packed_object_size(p, entry->in_pack_offset)
+ - entry->in_pack_header_size;
+ if (!pack_to_stdout && check_inflate(buf, datalen, entry->size))
+ die("corrupt delta in pack %s", sha1_to_hex(entry->sha1));
sha1write(f, buf, datalen);
unuse_packed_git(p);
- hdrlen = 0; /* not really */
- if (obj_type == OBJ_DELTA)
- reused_delta++;
reused++;
}
- if (obj_type == OBJ_DELTA)
+ if (entry->delta)
written_delta++;
written++;
return hdrlen + datalen;
@@ -336,17 +461,16 @@ static unsigned long write_one(struct sha1file *f,
struct object_entry *e,
unsigned long offset)
{
- if (e->offset)
+ if (e->offset || e->preferred_base)
/* offset starts from header size and cannot be zero
* if it is written already.
*/
return offset;
- e->offset = offset;
- offset += write_object(f, e);
- /* if we are deltified, write out its base object. */
+ /* if we are deltified, write out its base object first. */
if (e->delta)
offset = write_one(f, e->delta, offset);
- return offset;
+ e->offset = offset;
+ return offset + write_object(f, e);
}
static void write_pack_file(void)
@@ -356,15 +480,15 @@ static void write_pack_file(void)
unsigned long offset;
struct pack_header hdr;
unsigned last_percent = 999;
- int do_progress = 0;
+ int do_progress = progress;
- if (!base_name)
+ if (!base_name) {
f = sha1fd(1, "<stdout>");
- else {
+ do_progress >>= 1;
+ }
+ else
f = sha1create("%s-%s.%s", base_name,
sha1_to_hex(object_list_sha1), "pack");
- do_progress = progress;
- }
if (do_progress)
fprintf(stderr, "Writing %d objects.\n", nr_result);
@@ -441,7 +565,7 @@ static int locate_object_entry_hash(const unsigned char *sha1)
memcpy(&ui, sha1, sizeof(unsigned int));
i = ui % object_ix_hashsz;
while (0 < object_ix[i]) {
- if (!memcmp(sha1, objects[object_ix[i]-1].sha1, 20))
+ if (!hashcmp(sha1, objects[object_ix[i] - 1].sha1))
return i;
if (++i == object_ix_hashsz)
i = 0;
@@ -510,15 +634,15 @@ static int add_object_entry(const unsigned char *sha1, unsigned hash, int exclud
if (!exclude) {
for (p = packed_git; p; p = p->next) {
- struct pack_entry e;
- if (find_pack_entry_one(sha1, &e, p)) {
+ unsigned long offset = find_pack_entry_one(sha1, p);
+ if (offset) {
if (incremental)
return 0;
if (local && !p->pack_local)
return 0;
if (!found_pack) {
- found_offset = e.offset;
- found_pack = e.p;
+ found_offset = offset;
+ found_pack = p;
}
}
}
@@ -534,7 +658,7 @@ static int add_object_entry(const unsigned char *sha1, unsigned hash, int exclud
entry = objects + idx;
nr_objects = idx + 1;
memset(entry, 0, sizeof(*entry));
- memcpy(entry->sha1, sha1, 20);
+ hashcpy(entry->sha1, sha1);
entry->hash = hash;
if (object_ix_hashsz * 3 <= nr_objects * 4)
@@ -607,7 +731,7 @@ static struct pbase_tree_cache *pbase_tree_get(const unsigned char *sha1)
*/
for (neigh = 0; neigh < 8; neigh++) {
ent = pbase_tree_cache[my_ix];
- if (ent && !memcmp(ent->sha1, sha1, 20)) {
+ if (ent && !hashcmp(ent->sha1, sha1)) {
ent->ref++;
return ent;
}
@@ -649,7 +773,7 @@ static struct pbase_tree_cache *pbase_tree_get(const unsigned char *sha1)
free(ent->tree_data);
nent = ent;
}
- memcpy(nent->sha1, sha1, 20);
+ hashcpy(nent->sha1, sha1);
nent->tree_data = data;
nent->tree_size = size;
nent->ref = 1;
@@ -755,7 +879,7 @@ static int check_pbase_path(unsigned hash)
return 0;
}
-static void add_preferred_base_object(char *name, unsigned hash)
+static void add_preferred_base_object(const char *name, unsigned hash)
{
struct pbase_tree *it;
int cmplen = name_cmp_len(name);
@@ -784,12 +908,15 @@ static void add_preferred_base(unsigned char *sha1)
unsigned long size;
unsigned char tree_sha1[20];
+ if (window <= num_preferred_base++)
+ return;
+
data = read_object_with_reference(sha1, tree_type, &size, tree_sha1);
if (!data)
return;
for (it = pbase_tree; it; it = it->next) {
- if (!memcmp(it->pcache.sha1, tree_sha1, 20)) {
+ if (!hashcmp(it->pcache.sha1, tree_sha1)) {
free(data);
return;
}
@@ -799,7 +926,7 @@ static void add_preferred_base(unsigned char *sha1)
it->next = pbase_tree;
pbase_tree = it;
- memcpy(it->pcache.sha1, tree_sha1, 20);
+ hashcpy(it->pcache.sha1, tree_sha1);
it->pcache.tree_data = data;
it->pcache.tree_size = size;
}
@@ -809,26 +936,64 @@ static void check_object(struct object_entry *entry)
char type[20];
if (entry->in_pack && !entry->preferred_base) {
- unsigned char base[20];
- unsigned long size;
- struct object_entry *base_entry;
+ struct packed_git *p = entry->in_pack;
+ unsigned long left = p->pack_size - entry->in_pack_offset;
+ unsigned long size, used;
+ unsigned char *buf;
+ struct object_entry *base_entry = NULL;
+
+ use_packed_git(p);
+ buf = p->pack_base;
+ buf += entry->in_pack_offset;
/* We want in_pack_type even if we do not reuse delta.
* There is no point not reusing non-delta representations.
*/
- check_reuse_pack_delta(entry->in_pack,
- entry->in_pack_offset,
- base, &size,
- &entry->in_pack_type);
+ used = unpack_object_header_gently(buf, left,
+ &entry->in_pack_type, &size);
+ if (!used || left - used <= 20)
+ die("corrupt pack for %s", sha1_to_hex(entry->sha1));
/* Check if it is delta, and the base is also an object
* we are going to pack. If so we will reuse the existing
* delta.
*/
- if (!no_reuse_delta &&
- entry->in_pack_type == OBJ_DELTA &&
- (base_entry = locate_object_entry(base)) &&
- (!base_entry->preferred_base)) {
+ if (!no_reuse_delta) {
+ unsigned char c, *base_name;
+ unsigned long ofs;
+ /* there is at least 20 bytes left in the pack */
+ switch (entry->in_pack_type) {
+ case OBJ_REF_DELTA:
+ base_name = buf + used;
+ used += 20;
+ break;
+ case OBJ_OFS_DELTA:
+ c = buf[used++];
+ ofs = c & 127;
+ while (c & 128) {
+ ofs += 1;
+ if (!ofs || ofs & ~(~0UL >> 7))
+ die("delta base offset overflow in pack for %s",
+ sha1_to_hex(entry->sha1));
+ c = buf[used++];
+ ofs = (ofs << 7) + (c & 127);
+ }
+ if (ofs >= entry->in_pack_offset)
+ die("delta base offset out of bound for %s",
+ sha1_to_hex(entry->sha1));
+ ofs = entry->in_pack_offset - ofs;
+ base_name = find_packed_object_name(p, ofs);
+ break;
+ default:
+ base_name = NULL;
+ }
+ if (base_name)
+ base_entry = locate_object_entry(base_name);
+ }
+ unuse_packed_git(p);
+ entry->in_pack_header_size = used;
+
+ if (base_entry) {
/* Depth value does not matter - find_deltas()
* will never consider reused delta as the
@@ -837,9 +1002,9 @@ static void check_object(struct object_entry *entry)
*/
/* uncompressed size of the delta data */
- entry->size = entry->delta_size = size;
+ entry->size = size;
entry->delta = base_entry;
- entry->type = OBJ_DELTA;
+ entry->type = entry->in_pack_type;
entry->delta_sibling = base_entry->delta_child;
base_entry->delta_child = entry;
@@ -931,7 +1096,7 @@ static struct object_entry **create_sorted_list(entry_sort_t sort)
static int sha1_sort(const struct object_entry *a, const struct object_entry *b)
{
- return memcmp(a->sha1, b->sha1, 20);
+ return hashcmp(a->sha1, b->sha1);
}
static struct object_entry **create_final_object_list(void)
@@ -1163,7 +1328,7 @@ static void prepare_pack(int window, int depth)
find_deltas(sorted_by_type, window+1, depth);
}
-static int reuse_cached_pack(unsigned char *sha1, int pack_to_stdout)
+static int reuse_cached_pack(unsigned char *sha1)
{
static const char cache[] = "pack-cache/pack-%s.%s";
char *cached_pack, *cached_idx;
@@ -1243,14 +1408,105 @@ static int git_pack_config(const char *k, const char *v)
return git_default_config(k, v);
}
+static void read_object_list_from_stdin(void)
+{
+ char line[40 + 1 + PATH_MAX + 2];
+ unsigned char sha1[20];
+ unsigned hash;
+
+ for (;;) {
+ if (!fgets(line, sizeof(line), stdin)) {
+ if (feof(stdin))
+ break;
+ if (!ferror(stdin))
+ die("fgets returned NULL, not EOF, not error!");
+ if (errno != EINTR)
+ die("fgets: %s", strerror(errno));
+ clearerr(stdin);
+ continue;
+ }
+ if (line[0] == '-') {
+ if (get_sha1_hex(line+1, sha1))
+ die("expected edge sha1, got garbage:\n %s",
+ line);
+ add_preferred_base(sha1);
+ continue;
+ }
+ if (get_sha1_hex(line, sha1))
+ die("expected sha1, got garbage:\n %s", line);
+
+ hash = name_hash(line+41);
+ add_preferred_base_object(line+41, hash);
+ add_object_entry(sha1, hash, 0);
+ }
+}
+
+static void show_commit(struct commit *commit)
+{
+ unsigned hash = name_hash("");
+ add_preferred_base_object("", hash);
+ add_object_entry(commit->object.sha1, hash, 0);
+}
+
+static void show_object(struct object_array_entry *p)
+{
+ unsigned hash = name_hash(p->name);
+ add_preferred_base_object(p->name, hash);
+ add_object_entry(p->item->sha1, hash, 0);
+}
+
+static void show_edge(struct commit *commit)
+{
+ add_preferred_base(commit->object.sha1);
+}
+
+static void get_object_list(int ac, const char **av)
+{
+ struct rev_info revs;
+ char line[1000];
+ int flags = 0;
+
+ init_revisions(&revs, NULL);
+ save_commit_buffer = 0;
+ track_object_refs = 0;
+ setup_revisions(ac, av, &revs, NULL);
+
+ while (fgets(line, sizeof(line), stdin) != NULL) {
+ int len = strlen(line);
+ if (line[len - 1] == '\n')
+ line[--len] = 0;
+ if (!len)
+ break;
+ if (*line == '-') {
+ if (!strcmp(line, "--not")) {
+ flags ^= UNINTERESTING;
+ continue;
+ }
+ die("not a rev '%s'", line);
+ }
+ if (handle_revision_arg(line, &revs, flags, 1))
+ die("bad revision '%s'", line);
+ }
+
+ prepare_revision_walk(&revs);
+ mark_edges_uninteresting(revs.commits, &revs, show_edge);
+ traverse_commit_list(&revs, show_commit, show_object);
+}
+
int cmd_pack_objects(int argc, const char **argv, const char *prefix)
{
SHA_CTX ctx;
- char line[40 + 1 + PATH_MAX + 2];
- int depth = 10, pack_to_stdout = 0;
+ int depth = 10;
struct object_entry **list;
- int num_preferred_base = 0;
+ int use_internal_rev_list = 0;
+ int thin = 0;
int i;
+ const char *rp_av[64];
+ int rp_ac;
+
+ rp_av[0] = "pack-objects";
+ rp_av[1] = "--objects"; /* --thin will make it --objects-edge */
+ rp_ac = 2;
git_config(git_pack_config);
@@ -1258,63 +1514,103 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
for (i = 1; i < argc; i++) {
const char *arg = argv[i];
- if (*arg == '-') {
- if (!strcmp("--non-empty", arg)) {
- non_empty = 1;
- continue;
- }
- if (!strcmp("--local", arg)) {
- local = 1;
- continue;
- }
- if (!strcmp("--progress", arg)) {
- progress = 1;
- continue;
- }
- if (!strcmp("--incremental", arg)) {
- incremental = 1;
- continue;
- }
- if (!strncmp("--window=", arg, 9)) {
- char *end;
- window = strtoul(arg+9, &end, 0);
- if (!arg[9] || *end)
- usage(pack_usage);
- continue;
- }
- if (!strncmp("--depth=", arg, 8)) {
- char *end;
- depth = strtoul(arg+8, &end, 0);
- if (!arg[8] || *end)
- usage(pack_usage);
- continue;
- }
- if (!strcmp("--progress", arg)) {
- progress = 1;
- continue;
- }
- if (!strcmp("-q", arg)) {
- progress = 0;
- continue;
- }
- if (!strcmp("--no-reuse-delta", arg)) {
- no_reuse_delta = 1;
- continue;
- }
- if (!strcmp("--stdout", arg)) {
- pack_to_stdout = 1;
- continue;
- }
- usage(pack_usage);
+ if (*arg != '-')
+ break;
+
+ if (!strcmp("--non-empty", arg)) {
+ non_empty = 1;
+ continue;
+ }
+ if (!strcmp("--local", arg)) {
+ local = 1;
+ continue;
+ }
+ if (!strcmp("--incremental", arg)) {
+ incremental = 1;
+ continue;
}
- if (base_name)
- usage(pack_usage);
- base_name = arg;
+ if (!strncmp("--window=", arg, 9)) {
+ char *end;
+ window = strtoul(arg+9, &end, 0);
+ if (!arg[9] || *end)
+ usage(pack_usage);
+ continue;
+ }
+ if (!strncmp("--depth=", arg, 8)) {
+ char *end;
+ depth = strtoul(arg+8, &end, 0);
+ if (!arg[8] || *end)
+ usage(pack_usage);
+ continue;
+ }
+ if (!strcmp("--progress", arg)) {
+ progress = 1;
+ continue;
+ }
+ if (!strcmp("--all-progress", arg)) {
+ progress = 2;
+ continue;
+ }
+ if (!strcmp("-q", arg)) {
+ progress = 0;
+ continue;
+ }
+ if (!strcmp("--no-reuse-delta", arg)) {
+ no_reuse_delta = 1;
+ continue;
+ }
+ if (!strcmp("--delta-base-offset", arg)) {
+ allow_ofs_delta = 1;
+ continue;
+ }
+ if (!strcmp("--stdout", arg)) {
+ pack_to_stdout = 1;
+ continue;
+ }
+ if (!strcmp("--revs", arg)) {
+ use_internal_rev_list = 1;
+ continue;
+ }
+ if (!strcmp("--unpacked", arg) ||
+ !strncmp("--unpacked=", arg, 11) ||
+ !strcmp("--all", arg)) {
+ use_internal_rev_list = 1;
+ if (ARRAY_SIZE(rp_av) - 1 <= rp_ac)
+ die("too many internal rev-list options");
+ rp_av[rp_ac++] = arg;
+ continue;
+ }
+ if (!strcmp("--thin", arg)) {
+ use_internal_rev_list = 1;
+ thin = 1;
+ rp_av[1] = "--objects-edge";
+ continue;
+ }
+ usage(pack_usage);
}
+ /* Traditionally "pack-objects [options] base extra" failed;
+ * we would however want to take refs parameter that would
+ * have been given to upstream rev-list ourselves, which means
+ * we somehow want to say what the base name is. So the
+ * syntax would be:
+ *
+ * pack-objects [options] base <refs...>
+ *
+ * in other words, we would treat the first non-option as the
+ * base_name and send everything else to the internal revision
+ * walker.
+ */
+
+ if (!pack_to_stdout)
+ base_name = argv[i++];
+
if (pack_to_stdout != !base_name)
usage(pack_usage);
+ if (!pack_to_stdout && thin)
+ die("--thin cannot be used to build an indexable pack.");
+
prepare_packed_git();
if (progress) {
@@ -1322,35 +1618,13 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
setup_progress_signal();
}
- for (;;) {
- unsigned char sha1[20];
- unsigned hash;
-
- if (!fgets(line, sizeof(line), stdin)) {
- if (feof(stdin))
- break;
- if (!ferror(stdin))
- die("fgets returned NULL, not EOF, not error!");
- if (errno != EINTR)
- die("fgets: %s", strerror(errno));
- clearerr(stdin);
- continue;
- }
-
- if (line[0] == '-') {
- if (get_sha1_hex(line+1, sha1))
- die("expected edge sha1, got garbage:\n %s",
- line+1);
- if (num_preferred_base++ < window)
- add_preferred_base(sha1);
- continue;
- }
- if (get_sha1_hex(line, sha1))
- die("expected sha1, got garbage:\n %s", line);
- hash = name_hash(line+41);
- add_preferred_base_object(line+41, hash);
- add_object_entry(sha1, hash, 0);
+ if (!use_internal_rev_list)
+ read_object_list_from_stdin();
+ else {
+ rp_av[rp_ac] = NULL;
+ get_object_list(rp_ac, rp_av);
}
+
if (progress)
fprintf(stderr, "Done counting %d objects.\n", nr_objects);
sorted_by_sha = create_final_object_list();
@@ -1367,12 +1641,12 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
if (progress && (nr_objects != nr_result))
fprintf(stderr, "Result has %d objects.\n", nr_result);
- if (reuse_cached_pack(object_list_sha1, pack_to_stdout))
+ if (reuse_cached_pack(object_list_sha1))
;
else {
if (nr_result)
prepare_pack(window, depth);
- if (progress && pack_to_stdout) {
+ if (progress == 1 && pack_to_stdout) {
/* the other end usually displays progress itself */
struct itimerval v = {{0,},};
setitimer(ITIMER_REAL, &v, NULL);
diff --git a/builtin-pack-refs.c b/builtin-pack-refs.c
new file mode 100644
index 0000000000..042d2718f9
--- /dev/null
+++ b/builtin-pack-refs.c
@@ -0,0 +1,107 @@
+#include "cache.h"
+#include "refs.h"
+
+static const char builtin_pack_refs_usage[] =
+"git-pack-refs [--all] [--prune]";
+
+struct ref_to_prune {
+ struct ref_to_prune *next;
+ unsigned char sha1[20];
+ char name[FLEX_ARRAY];
+};
+
+struct pack_refs_cb_data {
+ int prune;
+ int all;
+ struct ref_to_prune *ref_to_prune;
+ FILE *refs_file;
+};
+
+static int do_not_prune(int flags)
+{
+ /* If it is already packed or if it is a symref,
+ * do not prune it.
+ */
+ return (flags & (REF_ISSYMREF|REF_ISPACKED));
+}
+
+static int handle_one_ref(const char *path, const unsigned char *sha1,
+ int flags, void *cb_data)
+{
+ struct pack_refs_cb_data *cb = cb_data;
+
+ if (!cb->all && strncmp(path, "refs/tags/", 10))
+ return 0;
+ /* Do not pack the symbolic refs */
+ if (!(flags & REF_ISSYMREF))
+ fprintf(cb->refs_file, "%s %s\n", sha1_to_hex(sha1), path);
+ if (cb->prune && !do_not_prune(flags)) {
+ int namelen = strlen(path) + 1;
+ struct ref_to_prune *n = xcalloc(1, sizeof(*n) + namelen);
+ hashcpy(n->sha1, sha1);
+ strcpy(n->name, path);
+ n->next = cb->ref_to_prune;
+ cb->ref_to_prune = n;
+ }
+ return 0;
+}
+
+/* make sure nobody touched the ref, and unlink */
+static void prune_ref(struct ref_to_prune *r)
+{
+ struct ref_lock *lock = lock_ref_sha1(r->name + 5, r->sha1);
+
+ if (lock) {
+ unlink(git_path("%s", r->name));
+ unlock_ref(lock);
+ }
+}
+
+static void prune_refs(struct ref_to_prune *r)
+{
+ while (r) {
+ prune_ref(r);
+ r = r->next;
+ }
+}
+
+static struct lock_file packed;
+
+int cmd_pack_refs(int argc, const char **argv, const char *prefix)
+{
+ int fd, i;
+ struct pack_refs_cb_data cbdata;
+
+ memset(&cbdata, 0, sizeof(cbdata));
+
+ for (i = 1; i < argc; i++) {
+ const char *arg = argv[i];
+ if (!strcmp(arg, "--prune")) {
+ cbdata.prune = 1;
+ continue;
+ }
+ if (!strcmp(arg, "--all")) {
+ cbdata.all = 1;
+ continue;
+ }
+ /* perhaps other parameters later... */
+ break;
+ }
+ if (i != argc)
+ usage(builtin_pack_refs_usage);
+
+ fd = hold_lock_file_for_update(&packed, git_path("packed-refs"), 1);
+ cbdata.refs_file = fdopen(fd, "w");
+ if (!cbdata.refs_file)
+ die("unable to create ref-pack file structure (%s)",
+ strerror(errno));
+ for_each_ref(handle_one_ref, &cbdata);
+ fflush(cbdata.refs_file);
+ fsync(fd);
+ fclose(cbdata.refs_file);
+ if (commit_lock_file(&packed) < 0)
+ die("unable to overwrite old ref-pack file (%s)", strerror(errno));
+ if (cbdata.prune)
+ prune_refs(cbdata.ref_to_prune);
+ return 0;
+}
diff --git a/builtin-prune-packed.c b/builtin-prune-packed.c
index d3dd94d9ef..24e3b0a8c2 100644
--- a/builtin-prune-packed.c
+++ b/builtin-prune-packed.c
@@ -4,9 +4,7 @@
static const char prune_packed_usage[] =
"git-prune-packed [-n]";
-static int dryrun;
-
-static void prune_dir(int i, DIR *dir, char *pathname, int len)
+static void prune_dir(int i, DIR *dir, char *pathname, int len, int dryrun)
{
struct dirent *de;
char hex[40];
@@ -19,7 +17,7 @@ static void prune_dir(int i, DIR *dir, char *pathname, int len)
memcpy(hex+2, de->d_name, 38);
if (get_sha1_hex(hex, sha1))
continue;
- if (!has_sha1_pack(sha1))
+ if (!has_sha1_pack(sha1, NULL))
continue;
memcpy(pathname + len, de->d_name, 38);
if (dryrun)
@@ -31,7 +29,7 @@ static void prune_dir(int i, DIR *dir, char *pathname, int len)
rmdir(pathname);
}
-static void prune_packed_objects(void)
+void prune_packed_objects(int dryrun)
{
int i;
static char pathname[PATH_MAX];
@@ -50,7 +48,7 @@ static void prune_packed_objects(void)
d = opendir(pathname);
if (!d)
continue;
- prune_dir(i, d, pathname, len + 3);
+ prune_dir(i, d, pathname, len + 3, dryrun);
closedir(d);
}
}
@@ -58,6 +56,7 @@ static void prune_packed_objects(void)
int cmd_prune_packed(int argc, const char **argv, const char *prefix)
{
int i;
+ int dryrun = 0;
for (i = 1; i < argc; i++) {
const char *arg = argv[i];
@@ -73,6 +72,6 @@ int cmd_prune_packed(int argc, const char **argv, const char *prefix)
usage(prune_packed_usage);
}
sync();
- prune_packed_objects();
+ prune_packed_objects(dryrun);
return 0;
}
diff --git a/builtin-prune.c b/builtin-prune.c
index 89ec7f1426..d853902c51 100644
--- a/builtin-prune.c
+++ b/builtin-prune.c
@@ -11,7 +11,7 @@
#include "cache-tree.h"
static const char prune_usage[] = "git-prune [-n]";
-static int show_only = 0;
+static int show_only;
static struct rev_info revs;
static int prune_object(char *path, const char *filename, const unsigned char *sha1)
@@ -106,7 +106,7 @@ static void process_tree(struct tree *tree,
obj->flags |= SEEN;
if (parse_tree(tree) < 0)
die("bad tree object %s", sha1_to_hex(obj->sha1));
- name = strdup(name);
+ name = xstrdup(name);
add_object(obj, p, path, name);
me.up = path;
me.elem = name;
@@ -174,7 +174,7 @@ static void walk_commit_list(struct rev_info *revs)
}
}
-static int add_one_ref(const char *path, const unsigned char *sha1)
+static int add_one_ref(const char *path, const unsigned char *sha1, int flag, void *cb_data)
{
struct object *object = parse_object(sha1);
if (!object)
@@ -240,7 +240,7 @@ int cmd_prune(int argc, const char **argv, const char *prefix)
revs.tree_objects = 1;
/* Add all external refs */
- for_each_ref(add_one_ref);
+ for_each_ref(add_one_ref, NULL);
/* Add all refs from the index file */
add_cache_refs();
@@ -255,5 +255,7 @@ int cmd_prune(int argc, const char **argv, const char *prefix)
prune_object_dir(get_object_directory());
+ sync();
+ prune_packed_objects(show_only);
return 0;
}
diff --git a/builtin-push.c b/builtin-push.c
index 53bc378f73..d23974e708 100644
--- a/builtin-push.c
+++ b/builtin-push.c
@@ -10,14 +10,14 @@
static const char push_usage[] = "git-push [--all] [--tags] [-f | --force] <repository> [<refspec>...]";
-static int all = 0, tags = 0, force = 0, thin = 1;
-static const char *execute = NULL;
+static int all, tags, force, thin = 1, verbose;
+static const char *execute;
#define BUF_SIZE (2084)
static char buffer[BUF_SIZE];
-static const char **refspec = NULL;
-static int refspec_nr = 0;
+static const char **refspec;
+static int refspec_nr;
static void add_refspec(const char *ref)
{
@@ -27,15 +27,13 @@ static void add_refspec(const char *ref)
refspec_nr = nr;
}
-static int expand_one_ref(const char *ref, const unsigned char *sha1)
+static int expand_one_ref(const char *ref, const unsigned char *sha1, int flag, void *cb_data)
{
/* Ignore the "refs/" at the beginning of the refname */
ref += 5;
- if (strncmp(ref, "tags/", 5))
- return 0;
-
- add_refspec(strdup(ref));
+ if (!strncmp(ref, "tags/", 5))
+ add_refspec(xstrdup(ref));
return 0;
}
@@ -53,7 +51,7 @@ static void expand_refspecs(void)
}
if (!tags)
return;
- for_each_ref(expand_one_ref);
+ for_each_ref(expand_one_ref, NULL);
}
static void set_refspecs(const char **refs, int nr)
@@ -80,12 +78,12 @@ static int get_remotes_uri(const char *repo, const char *uri[MAX_URI])
int is_refspec;
char *s, *p;
- if (!strncmp("URL: ", buffer, 5)) {
+ if (!strncmp("URL:", buffer, 4)) {
is_refspec = 0;
- s = buffer + 5;
- } else if (!strncmp("Push: ", buffer, 6)) {
+ s = buffer + 4;
+ } else if (!strncmp("Push:", buffer, 5)) {
is_refspec = 1;
- s = buffer + 6;
+ s = buffer + 5;
} else
continue;
@@ -102,12 +100,12 @@ static int get_remotes_uri(const char *repo, const char *uri[MAX_URI])
if (!is_refspec) {
if (n < MAX_URI)
- uri[n++] = strdup(s);
+ uri[n++] = xstrdup(s);
else
error("more than %d URL's specified, ignoring the rest", MAX_URI);
}
else if (is_refspec && !has_explicit_refspec)
- add_refspec(strdup(s));
+ add_refspec(xstrdup(s));
}
fclose(f);
if (!n)
@@ -127,13 +125,13 @@ static int get_remote_config(const char* key, const char* value)
!strncmp(key + 7, config_repo, config_repo_len)) {
if (!strcmp(key + 7 + config_repo_len, ".url")) {
if (config_current_uri < MAX_URI)
- config_uri[config_current_uri++] = strdup(value);
+ config_uri[config_current_uri++] = xstrdup(value);
else
error("more than %d URL's specified, ignoring the rest", MAX_URI);
}
else if (config_get_refspecs &&
!strcmp(key + 7 + config_repo_len, ".push"))
- add_refspec(strdup(value));
+ add_refspec(xstrdup(value));
}
return 0;
}
@@ -234,7 +232,7 @@ static int do_push(const char *repo)
common_argc = argc;
for (i = 0; i < n; i++) {
- int error;
+ int err;
int dest_argc = common_argc;
int dest_refspec_nr = refspec_nr;
const char **dest_refspec = refspec;
@@ -250,10 +248,12 @@ static int do_push(const char *repo)
while (dest_refspec_nr--)
argv[dest_argc++] = *dest_refspec++;
argv[dest_argc] = NULL;
- error = run_command_v(argc, argv);
- if (!error)
+ if (verbose)
+ fprintf(stderr, "Pushing to %s\n", dest);
+ err = run_command_v(argc, argv);
+ if (!err)
continue;
- switch (error) {
+ switch (err) {
case -ERR_RUN_COMMAND_FORK:
die("unable to fork for %s", sender);
case -ERR_RUN_COMMAND_EXEC:
@@ -264,7 +264,7 @@ static int do_push(const char *repo)
case -ERR_RUN_COMMAND_WAITPID_NOEXIT:
die("%s died with strange error", sender);
default:
- return -error;
+ return -err;
}
}
return 0;
@@ -283,6 +283,14 @@ int cmd_push(int argc, const char **argv, const char *prefix)
i++;
break;
}
+ if (!strcmp(arg, "-v")) {
+ verbose=1;
+ continue;
+ }
+ if (!strncmp(arg, "--repo=", 7)) {
+ repo = arg+7;
+ continue;
+ }
if (!strcmp(arg, "--all")) {
all = 1;
continue;
diff --git a/builtin-read-tree.c b/builtin-read-tree.c
index 8da8acbb0a..c1867d2a00 100644
--- a/builtin-read-tree.c
+++ b/builtin-read-tree.c
@@ -12,7 +12,7 @@
#include "unpack-trees.h"
#include "builtin.h"
-static struct object_list *trees = NULL;
+static struct object_list *trees;
static int list_tree(unsigned char *sha1)
{
@@ -53,7 +53,7 @@ static void prime_cache_tree_rec(struct cache_tree *it, struct tree *tree)
struct name_entry entry;
int cnt;
- memcpy(it->sha1, tree->object.sha1, 20);
+ hashcpy(it->sha1, tree->object.sha1);
desc.buf = tree->buffer;
desc.size = tree->size;
cnt = 0;
diff --git a/builtin-repo-config.c b/builtin-repo-config.c
index c821e22717..7b6e5725ae 100644
--- a/builtin-repo-config.c
+++ b/builtin-repo-config.c
@@ -3,16 +3,16 @@
#include <regex.h>
static const char git_config_set_usage[] =
-"git-repo-config [ --bool | --int ] [--get | --get-all | --get-regexp | --replace-all | --unset | --unset-all] name [value [value_regex]] | --list";
-
-static char* key = NULL;
-static regex_t* key_regexp = NULL;
-static regex_t* regexp = NULL;
-static int show_keys = 0;
-static int use_key_regexp = 0;
-static int do_all = 0;
-static int do_not_match = 0;
-static int seen = 0;
+"git-repo-config [ --global ] [ --bool | --int ] [--get | --get-all | --get-regexp | --replace-all | --unset | --unset-all] name [value [value_regex]] | --list";
+
+static char *key;
+static regex_t *key_regexp;
+static regex_t *regexp;
+static int show_keys;
+static int use_key_regexp;
+static int do_all;
+static int do_not_match;
+static int seen;
static enum { T_RAW, T_INT, T_BOOL } type = T_RAW;
static int show_all_config(const char *key_, const char *value_)
@@ -72,19 +72,19 @@ static int get_value(const char* key_, const char* regex_)
const char *home = getenv("HOME");
local = getenv("GIT_CONFIG_LOCAL");
if (!local)
- local = repo_config = strdup(git_path("config"));
+ local = repo_config = xstrdup(git_path("config"));
if (home)
- global = strdup(mkpath("%s/.gitconfig", home));
+ global = xstrdup(mkpath("%s/.gitconfig", home));
}
- key = strdup(key_);
+ key = xstrdup(key_);
for (tl=key+strlen(key)-1; tl >= key && *tl != '.'; --tl)
*tl = tolower(*tl);
for (tl=key; *tl && *tl != '.'; ++tl)
*tl = tolower(*tl);
if (use_key_regexp) {
- key_regexp = (regex_t*)malloc(sizeof(regex_t));
+ key_regexp = (regex_t*)xmalloc(sizeof(regex_t));
if (regcomp(key_regexp, key, REG_EXTENDED)) {
fprintf(stderr, "Invalid key pattern: %s\n", key_);
goto free_strings;
@@ -97,7 +97,7 @@ static int get_value(const char* key_, const char* regex_)
regex_++;
}
- regexp = (regex_t*)malloc(sizeof(regex_t));
+ regexp = (regex_t*)xmalloc(sizeof(regex_t));
if (regcomp(regexp, regex_, REG_EXTENDED)) {
fprintf(stderr, "Invalid pattern: %s\n", regex_);
goto free_strings;
@@ -119,13 +119,11 @@ static int get_value(const char* key_, const char* regex_)
if (do_all)
ret = !seen;
else
- ret = (seen == 1) ? 0 : 1;
+ ret = (seen == 1) ? 0 : seen > 1 ? 2 : 1;
free_strings:
- if (repo_config)
- free(repo_config);
- if (global)
- free(global);
+ free(repo_config);
+ free(global);
return ret;
}
@@ -141,7 +139,16 @@ int cmd_repo_config(int argc, const char **argv, const char *prefix)
type = T_BOOL;
else if (!strcmp(argv[1], "--list") || !strcmp(argv[1], "-l"))
return git_config(show_all_config);
- else
+ else if (!strcmp(argv[1], "--global")) {
+ char *home = getenv("HOME");
+ if (home) {
+ char *user_config = xstrdup(mkpath("%s/.gitconfig", home));
+ setenv("GIT_CONFIG", user_config, 1);
+ free(user_config);
+ } else {
+ die("$HOME not set");
+ }
+ } else
break;
argc--;
argv++;
diff --git a/builtin-rev-list.c b/builtin-rev-list.c
index 0dee1734a3..fb7fc92145 100644
--- a/builtin-rev-list.c
+++ b/builtin-rev-list.c
@@ -7,6 +7,7 @@
#include "tree-walk.h"
#include "diff.h"
#include "revision.h"
+#include "list-objects.h"
#include "builtin.h"
/* bits #0-15 in revision.h */
@@ -23,6 +24,7 @@ static const char rev_list_usage[] =
" --no-merges\n"
" --remove-empty\n"
" --all\n"
+" --stdin\n"
" ordering output:\n"
" --topo-order\n"
" --date-order\n"
@@ -39,9 +41,9 @@ static const char rev_list_usage[] =
static struct rev_info revs;
-static int bisect_list = 0;
-static int show_timestamp = 0;
-static int hdr_termination = 0;
+static int bisect_list;
+static int show_timestamp;
+static int hdr_termination;
static const char *header_prefix;
static void show_commit(struct commit *commit)
@@ -85,7 +87,7 @@ static void show_commit(struct commit *commit)
static char pretty_header[16384];
pretty_print_commit(revs.commit_format, commit, ~0,
pretty_header, sizeof(pretty_header),
- revs.abbrev, NULL, NULL);
+ revs.abbrev, NULL, NULL, revs.relative_date);
printf("%s%c", pretty_header, hdr_termination);
}
fflush(stdout);
@@ -93,110 +95,28 @@ static void show_commit(struct commit *commit)
free_commit_list(commit->parents);
commit->parents = NULL;
}
- if (commit->buffer) {
- free(commit->buffer);
- commit->buffer = NULL;
- }
+ free(commit->buffer);
+ commit->buffer = NULL;
}
-static void process_blob(struct blob *blob,
- struct object_array *p,
- struct name_path *path,
- const char *name)
+static void show_object(struct object_array_entry *p)
{
- struct object *obj = &blob->object;
-
- if (!revs.blob_objects)
- return;
- if (obj->flags & (UNINTERESTING | SEEN))
- return;
- obj->flags |= SEEN;
- name = strdup(name);
- add_object(obj, p, path, name);
-}
-
-static void process_tree(struct tree *tree,
- struct object_array *p,
- struct name_path *path,
- const char *name)
-{
- struct object *obj = &tree->object;
- struct tree_desc desc;
- struct name_entry entry;
- struct name_path me;
-
- if (!revs.tree_objects)
- return;
- if (obj->flags & (UNINTERESTING | SEEN))
- return;
- if (parse_tree(tree) < 0)
- die("bad tree object %s", sha1_to_hex(obj->sha1));
- obj->flags |= SEEN;
- name = strdup(name);
- add_object(obj, p, path, name);
- me.up = path;
- me.elem = name;
- me.elem_len = strlen(name);
-
- desc.buf = tree->buffer;
- desc.size = tree->size;
-
- while (tree_entry(&desc, &entry)) {
- if (S_ISDIR(entry.mode))
- process_tree(lookup_tree(entry.sha1), p, &me, entry.path);
- else
- process_blob(lookup_blob(entry.sha1), p, &me, entry.path);
+ /* An object with name "foo\n0000000..." can be used to
+ * confuse downstream git-pack-objects very badly.
+ */
+ const char *ep = strchr(p->name, '\n');
+ if (ep) {
+ printf("%s %.*s\n", sha1_to_hex(p->item->sha1),
+ (int) (ep - p->name),
+ p->name);
}
- free(tree->buffer);
- tree->buffer = NULL;
+ else
+ printf("%s %s\n", sha1_to_hex(p->item->sha1), p->name);
}
-static void show_commit_list(struct rev_info *revs)
+static void show_edge(struct commit *commit)
{
- int i;
- struct commit *commit;
- struct object_array objects = { 0, 0, NULL };
-
- while ((commit = get_revision(revs)) != NULL) {
- process_tree(commit->tree, &objects, NULL, "");
- show_commit(commit);
- }
- for (i = 0; i < revs->pending.nr; i++) {
- struct object_array_entry *pending = revs->pending.objects + i;
- struct object *obj = pending->item;
- const char *name = pending->name;
- if (obj->flags & (UNINTERESTING | SEEN))
- continue;
- if (obj->type == OBJ_TAG) {
- obj->flags |= SEEN;
- add_object_array(obj, name, &objects);
- continue;
- }
- if (obj->type == OBJ_TREE) {
- process_tree((struct tree *)obj, &objects, NULL, name);
- continue;
- }
- if (obj->type == OBJ_BLOB) {
- process_blob((struct blob *)obj, &objects, NULL, name);
- continue;
- }
- die("unknown pending object %s (%s)", sha1_to_hex(obj->sha1), name);
- }
- for (i = 0; i < objects.nr; i++) {
- struct object_array_entry *p = objects.objects + i;
-
- /* An object with name "foo\n0000000..." can be used to
- * confuse downstream git-pack-objects very badly.
- */
- const char *ep = strchr(p->name, '\n');
- if (ep) {
- printf("%s %.*s\n", sha1_to_hex(p->item->sha1),
- (int) (ep - p->name),
- p->name);
- }
- else
- printf("%s %s\n", sha1_to_hex(p->item->sha1), p->name);
- }
+ printf("-%s\n", sha1_to_hex(commit->object.sha1));
}
/*
@@ -277,32 +197,20 @@ static struct commit_list *find_bisection(struct commit_list *list)
return best;
}
-static void mark_edge_parents_uninteresting(struct commit *commit)
+static void read_revisions_from_stdin(struct rev_info *revs)
{
- 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);
- if (revs.edge_hint && !(parent->object.flags & SHOWN)) {
- parent->object.flags |= SHOWN;
- printf("-%s\n", sha1_to_hex(parent->object.sha1));
- }
- }
-}
+ char line[1000];
-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);
+ while (fgets(line, sizeof(line), stdin) != NULL) {
+ int len = strlen(line);
+ if (line[len - 1] == '\n')
+ line[--len] = 0;
+ if (!len)
+ break;
+ if (line[0] == '-')
+ die("options not supported in --stdin mode");
+ if (handle_revision_arg(line, revs, 0, 1))
+ die("bad revision '%s'", line);
}
}
@@ -310,6 +218,7 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix)
{
struct commit_list *list;
int i;
+ int read_from_stdin = 0;
init_revisions(&revs, prefix);
revs.abbrev = 0;
@@ -331,6 +240,12 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix)
bisect_list = 1;
continue;
}
+ if (!strcmp(arg, "--stdin")) {
+ if (read_from_stdin++)
+ die("--stdin given twice?");
+ read_revisions_from_stdin(&revs);
+ continue;
+ }
usage(rev_list_usage);
}
@@ -354,19 +269,19 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix)
revs.diff)
usage(rev_list_usage);
- save_commit_buffer = revs.verbose_header;
+ save_commit_buffer = revs.verbose_header || revs.grep_filter;
track_object_refs = 0;
if (bisect_list)
revs.limited = 1;
prepare_revision_walk(&revs);
if (revs.tree_objects)
- mark_edges_uninteresting(revs.commits);
+ mark_edges_uninteresting(revs.commits, &revs, show_edge);
if (bisect_list)
revs.commits = find_bisection(revs.commits);
- show_commit_list(&revs);
+ traverse_commit_list(&revs, show_commit, show_object);
return 0;
}
diff --git a/builtin-rev-parse.c b/builtin-rev-parse.c
index aca4a36032..3b716fba13 100644
--- a/builtin-rev-parse.c
+++ b/builtin-rev-parse.c
@@ -15,16 +15,16 @@
#define DO_NONFLAGS 8
static int filter = ~0;
-static const char *def = NULL;
+static const char *def;
#define NORMAL 0
#define REVERSED 1
static int show_type = NORMAL;
-static int symbolic = 0;
-static int abbrev = 0;
-static int output_sq = 0;
+static int symbolic;
+static int abbrev;
+static int output_sq;
-static int revs_count = 0;
+static int revs_count;
/*
* Some arguments are relevant "revision" arguments,
@@ -137,7 +137,7 @@ static void show_default(void)
}
}
-static int show_reference(const char *refname, const unsigned char *sha1)
+static int show_reference(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
{
show_rev(NORMAL, sha1, refname);
return 0;
@@ -299,19 +299,19 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
continue;
}
if (!strcmp(arg, "--all")) {
- for_each_ref(show_reference);
+ for_each_ref(show_reference, NULL);
continue;
}
if (!strcmp(arg, "--branches")) {
- for_each_branch_ref(show_reference);
+ for_each_branch_ref(show_reference, NULL);
continue;
}
if (!strcmp(arg, "--tags")) {
- for_each_tag_ref(show_reference);
+ for_each_tag_ref(show_reference, NULL);
continue;
}
if (!strcmp(arg, "--remotes")) {
- for_each_remote_ref(show_reference);
+ for_each_remote_ref(show_reference, NULL);
continue;
}
if (!strcmp(arg, "--show-prefix")) {
diff --git a/builtin-rm.c b/builtin-rm.c
index 593d86744c..33d04bd015 100644
--- a/builtin-rm.c
+++ b/builtin-rm.c
@@ -32,7 +32,7 @@ static int remove_file(const char *name)
ret = unlink(name);
if (!ret && (slash = strrchr(name, '/'))) {
- char *n = strdup(name);
+ char *n = xstrdup(name);
do {
n[slash - name] = 0;
name = n;
diff --git a/builtin-runstatus.c b/builtin-runstatus.c
new file mode 100644
index 0000000000..0b63037dd0
--- /dev/null
+++ b/builtin-runstatus.c
@@ -0,0 +1,36 @@
+#include "wt-status.h"
+#include "cache.h"
+
+extern int wt_status_use_color;
+
+static const char runstatus_usage[] =
+"git-runstatus [--color|--nocolor] [--amend] [--verbose] [--untracked]";
+
+int cmd_runstatus(int argc, const char **argv, const char *prefix)
+{
+ struct wt_status s;
+ int i;
+
+ git_config(git_status_config);
+ wt_status_prepare(&s);
+
+ for (i = 1; i < argc; i++) {
+ if (!strcmp(argv[i], "--color"))
+ wt_status_use_color = 1;
+ else if (!strcmp(argv[i], "--nocolor"))
+ wt_status_use_color = 0;
+ else if (!strcmp(argv[i], "--amend")) {
+ s.amend = 1;
+ s.reference = "HEAD^1";
+ }
+ else if (!strcmp(argv[i], "--verbose"))
+ s.verbose = 1;
+ else if (!strcmp(argv[i], "--untracked"))
+ s.untracked = 1;
+ else
+ usage(runstatus_usage);
+ }
+
+ wt_status_print(&s);
+ return s.commitable ? 0 : 1;
+}
diff --git a/builtin-show-branch.c b/builtin-show-branch.c
index 2a1b848f6c..fb1a4000d9 100644
--- a/builtin-show-branch.c
+++ b/builtin-show-branch.c
@@ -8,9 +8,9 @@
static const char show_branch_usage[] =
"git-show-branch [--sparse] [--current] [--all] [--heads] [--tags] [--topo-order] [--more=count | --list | --independent | --merge-base ] [--topics] [<refs>...]";
-static int default_num = 0;
-static int default_alloc = 0;
-static const char **default_arg = NULL;
+static int default_num;
+static int default_alloc;
+static const char **default_arg;
#define UNINTERESTING 01
@@ -163,7 +163,7 @@ static void name_commits(struct commit_list *list,
en += sprintf(en, "^");
else
en += sprintf(en, "^%d", nth);
- name_commit(p, strdup(newname), 0);
+ name_commit(p, xstrdup(newname), 0);
i++;
name_first_parent_chain(p);
}
@@ -261,7 +261,7 @@ static void show_one_commit(struct commit *commit, int no_name)
struct commit_name *name = commit->util;
if (commit->object.parsed)
pretty_print_commit(CMIT_FMT_ONELINE, commit, ~0,
- pretty, sizeof(pretty), 0, NULL, NULL);
+ pretty, sizeof(pretty), 0, NULL, NULL, 0);
else
strcpy(pretty, "(unavailable)");
if (!strncmp(pretty, "[PATCH] ", 8))
@@ -346,7 +346,7 @@ static void sort_ref_range(int bottom, int top)
compare_ref_name);
}
-static int append_ref(const char *refname, const unsigned char *sha1)
+static int append_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
{
struct commit *commit = lookup_commit_reference_gently(sha1, 1);
int i;
@@ -364,12 +364,12 @@ static int append_ref(const char *refname, const unsigned char *sha1)
refname, MAX_REVS);
return 0;
}
- ref_name[ref_name_cnt++] = strdup(refname);
+ ref_name[ref_name_cnt++] = xstrdup(refname);
ref_name[ref_name_cnt] = NULL;
return 0;
}
-static int append_head_ref(const char *refname, const unsigned char *sha1)
+static int append_head_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
{
unsigned char tmp[20];
int ofs = 11;
@@ -378,16 +378,16 @@ static int append_head_ref(const char *refname, const unsigned char *sha1)
/* If both heads/foo and tags/foo exists, get_sha1 would
* get confused.
*/
- if (get_sha1(refname + ofs, tmp) || memcmp(tmp, sha1, 20))
+ if (get_sha1(refname + ofs, tmp) || hashcmp(tmp, sha1))
ofs = 5;
- return append_ref(refname + ofs, sha1);
+ return append_ref(refname + ofs, sha1, flag, cb_data);
}
-static int append_tag_ref(const char *refname, const unsigned char *sha1)
+static int append_tag_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
{
if (strncmp(refname, "refs/tags/", 10))
return 0;
- return append_ref(refname + 5, sha1);
+ return append_ref(refname + 5, sha1, flag, cb_data);
}
static const char *match_ref_pattern = NULL;
@@ -401,7 +401,7 @@ static int count_slash(const char *s)
return cnt;
}
-static int append_matching_ref(const char *refname, const unsigned char *sha1)
+static int append_matching_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
{
/* we want to allow pattern hold/<asterisk> to show all
* branches under refs/heads/hold/, and v0.99.9? to show
@@ -417,41 +417,39 @@ static int append_matching_ref(const char *refname, const unsigned char *sha1)
if (fnmatch(match_ref_pattern, tail, 0))
return 0;
if (!strncmp("refs/heads/", refname, 11))
- return append_head_ref(refname, sha1);
+ return append_head_ref(refname, sha1, flag, cb_data);
if (!strncmp("refs/tags/", refname, 10))
- return append_tag_ref(refname, sha1);
- return append_ref(refname, sha1);
+ return append_tag_ref(refname, sha1, flag, cb_data);
+ return append_ref(refname, sha1, flag, cb_data);
}
static void snarf_refs(int head, int tag)
{
if (head) {
int orig_cnt = ref_name_cnt;
- for_each_ref(append_head_ref);
+ for_each_ref(append_head_ref, NULL);
sort_ref_range(orig_cnt, ref_name_cnt);
}
if (tag) {
int orig_cnt = ref_name_cnt;
- for_each_ref(append_tag_ref);
+ for_each_ref(append_tag_ref, NULL);
sort_ref_range(orig_cnt, ref_name_cnt);
}
}
-static int rev_is_head(char *head_path, int headlen, char *name,
+static int rev_is_head(char *head, int headlen, char *name,
unsigned char *head_sha1, unsigned char *sha1)
{
- int namelen;
- if ((!head_path[0]) ||
- (head_sha1 && sha1 && memcmp(head_sha1, sha1, 20)))
+ if ((!head[0]) ||
+ (head_sha1 && sha1 && hashcmp(head_sha1, sha1)))
return 0;
- namelen = strlen(name);
- if ((headlen < namelen) ||
- memcmp(head_path + headlen - namelen, name, namelen))
- return 0;
- if (headlen == namelen ||
- head_path[headlen - namelen - 1] == '/')
- return 1;
- return 0;
+ if (!strncmp(head, "refs/heads/", 11))
+ head += 11;
+ if (!strncmp(name, "refs/heads/", 11))
+ name += 11;
+ else if (!strncmp(name, "heads/", 6))
+ name += 6;
+ return !strcmp(head, name);
}
static int show_merge_base(struct commit_list *seen, int num_rev)
@@ -495,7 +493,7 @@ static void append_one_rev(const char *av)
{
unsigned char revkey[20];
if (!get_sha1(av, revkey)) {
- append_ref(av, revkey);
+ append_ref(av, revkey, 0, NULL);
return;
}
if (strchr(av, '*') || strchr(av, '?') || strchr(av, '[')) {
@@ -503,7 +501,7 @@ static void append_one_rev(const char *av)
int saved_matches = ref_name_cnt;
match_ref_pattern = av;
match_ref_slash = count_slash(av);
- for_each_ref(append_matching_ref);
+ for_each_ref(append_matching_ref, NULL);
if (saved_matches == ref_name_cnt &&
ref_name_cnt < MAX_REVS)
error("no matching refs with %s", av);
@@ -521,7 +519,7 @@ static int git_show_branch_config(const char *var, const char *value)
default_alloc = default_alloc * 3 / 2 + 20;
default_arg = xrealloc(default_arg, sizeof *default_arg * default_alloc);
}
- default_arg[default_num++] = strdup(value);
+ default_arg[default_num++] = xstrdup(value);
default_arg[default_num] = NULL;
return 0;
}
@@ -559,9 +557,9 @@ int cmd_show_branch(int ac, const char **av, const char *prefix)
int all_heads = 0, all_tags = 0;
int all_mask, all_revs;
int lifo = 1;
- char head_path[128];
- const char *head_path_p;
- int head_path_len;
+ char head[128];
+ const char *head_p;
+ int head_len;
unsigned char head_sha1[20];
int merge_base = 0;
int independent = 0;
@@ -638,31 +636,31 @@ int cmd_show_branch(int ac, const char **av, const char *prefix)
ac--; av++;
}
- head_path_p = resolve_ref(git_path("HEAD"), head_sha1, 1);
- if (head_path_p) {
- head_path_len = strlen(head_path_p);
- memcpy(head_path, head_path_p, head_path_len + 1);
+ head_p = resolve_ref("HEAD", head_sha1, 1, NULL);
+ if (head_p) {
+ head_len = strlen(head_p);
+ memcpy(head, head_p, head_len + 1);
}
else {
- head_path_len = 0;
- head_path[0] = 0;
+ head_len = 0;
+ head[0] = 0;
}
- if (with_current_branch && head_path_p) {
+ if (with_current_branch && head_p) {
int has_head = 0;
for (i = 0; !has_head && i < ref_name_cnt; i++) {
/* We are only interested in adding the branch
* HEAD points at.
*/
- if (rev_is_head(head_path,
- head_path_len,
+ if (rev_is_head(head,
+ head_len,
ref_name[i],
head_sha1, NULL))
has_head++;
}
if (!has_head) {
- int pfxlen = strlen(git_path("refs/heads/"));
- append_one_rev(head_path + pfxlen);
+ int pfxlen = strlen("refs/heads/");
+ append_one_rev(head + pfxlen);
}
}
@@ -713,8 +711,8 @@ int cmd_show_branch(int ac, const char **av, const char *prefix)
if (1 < num_rev || extra < 0) {
for (i = 0; i < num_rev; i++) {
int j;
- int is_head = rev_is_head(head_path,
- head_path_len,
+ int is_head = rev_is_head(head,
+ head_len,
ref_name[i],
head_sha1,
rev[i]->object.sha1);
diff --git a/builtin-show-ref.c b/builtin-show-ref.c
new file mode 100644
index 0000000000..06ec400d7f
--- /dev/null
+++ b/builtin-show-ref.c
@@ -0,0 +1,147 @@
+#include "cache.h"
+#include "refs.h"
+#include "object.h"
+#include "tag.h"
+
+static const char show_ref_usage[] = "git show-ref [-q|--quiet] [--verify] [-h|--head] [-d|--dereference] [-s|--hash[=<length>]] [--abbrev[=<length>]] [--tags] [--heads] [--] [pattern*]";
+
+static int deref_tags = 0, show_head = 0, tags_only = 0, heads_only = 0,
+ found_match = 0, verify = 0, quiet = 0, hash_only = 0, abbrev = 0;
+static const char **pattern;
+
+static int show_ref(const char *refname, const unsigned char *sha1, int flag, void *cbdata)
+{
+ struct object *obj;
+ const char *hex;
+
+ if (tags_only || heads_only) {
+ int match;
+
+ match = heads_only && !strncmp(refname, "refs/heads/", 11);
+ match |= tags_only && !strncmp(refname, "refs/tags/", 10);
+ if (!match)
+ return 0;
+ }
+ if (pattern) {
+ int reflen = strlen(refname);
+ const char **p = pattern, *m;
+ while ((m = *p++) != NULL) {
+ int len = strlen(m);
+ if (len > reflen)
+ continue;
+ if (memcmp(m, refname + reflen - len, len))
+ continue;
+ if (len == reflen)
+ goto match;
+ /* "--verify" requires an exact match */
+ if (verify)
+ continue;
+ if (refname[reflen - len - 1] == '/')
+ goto match;
+ }
+ return 0;
+ }
+
+match:
+ found_match++;
+ obj = parse_object(sha1);
+ if (!obj) {
+ if (quiet)
+ return 0;
+ die("git-show-ref: bad ref %s (%s)", refname, sha1_to_hex(sha1));
+ }
+ if (quiet)
+ return 0;
+
+ hex = find_unique_abbrev(sha1, abbrev);
+ if (hash_only)
+ printf("%s\n", hex);
+ else
+ printf("%s %s\n", hex, refname);
+ if (deref_tags && obj->type == OBJ_TAG) {
+ obj = deref_tag(obj, refname, 0);
+ hex = find_unique_abbrev(obj->sha1, abbrev);
+ printf("%s %s^{}\n", hex, refname);
+ }
+ return 0;
+}
+
+int cmd_show_ref(int argc, const char **argv, const char *prefix)
+{
+ int i;
+
+ for (i = 1; i < argc; i++) {
+ const char *arg = argv[i];
+ if (*arg != '-') {
+ pattern = argv + i;
+ break;
+ }
+ if (!strcmp(arg, "--")) {
+ pattern = argv + i + 1;
+ if (!*pattern)
+ pattern = NULL;
+ break;
+ }
+ if (!strcmp(arg, "-q") || !strcmp(arg, "--quiet")) {
+ quiet = 1;
+ continue;
+ }
+ if (!strcmp(arg, "-h") || !strcmp(arg, "--head")) {
+ show_head = 1;
+ continue;
+ }
+ if (!strcmp(arg, "-d") || !strcmp(arg, "--dereference")) {
+ deref_tags = 1;
+ continue;
+ }
+ if (!strcmp(arg, "-s") || !strcmp(arg, "--hash")) {
+ hash_only = 1;
+ continue;
+ }
+ if (!strncmp(arg, "--hash=", 7) ||
+ (!strncmp(arg, "--abbrev", 8) &&
+ (arg[8] == '=' || arg[8] == '\0'))) {
+ if (arg[3] != 'h' && !arg[8])
+ /* --abbrev only */
+ abbrev = DEFAULT_ABBREV;
+ else {
+ /* --hash= or --abbrev= */
+ char *end;
+ if (arg[3] == 'h') {
+ hash_only = 1;
+ arg += 7;
+ }
+ else
+ arg += 9;
+ abbrev = strtoul(arg, &end, 10);
+ if (*end || abbrev > 40)
+ usage(show_ref_usage);
+ if (abbrev < MINIMUM_ABBREV)
+ abbrev = MINIMUM_ABBREV;
+ }
+ continue;
+ }
+ if (!strcmp(arg, "--verify")) {
+ verify = 1;
+ continue;
+ }
+ if (!strcmp(arg, "--tags")) {
+ tags_only = 1;
+ continue;
+ }
+ if (!strcmp(arg, "--heads")) {
+ heads_only = 1;
+ continue;
+ }
+ usage(show_ref_usage);
+ }
+ if (show_head)
+ head_ref(show_ref, NULL);
+ for_each_ref(show_ref, NULL);
+ if (!found_match) {
+ if (verify && !quiet)
+ die("No match");
+ return 1;
+ }
+ return 0;
+}
diff --git a/builtin-symbolic-ref.c b/builtin-symbolic-ref.c
index b4ec6f28ed..d8be0527f4 100644
--- a/builtin-symbolic-ref.c
+++ b/builtin-symbolic-ref.c
@@ -1,5 +1,6 @@
#include "builtin.h"
#include "cache.h"
+#include "refs.h"
static const char git_symbolic_ref_usage[] =
"git-symbolic-ref name [ref]";
@@ -7,15 +8,14 @@ static const char git_symbolic_ref_usage[] =
static void check_symref(const char *HEAD)
{
unsigned char sha1[20];
- const char *git_HEAD = strdup(git_path("%s", HEAD));
- const char *git_refs_heads_master = resolve_ref(git_HEAD, sha1, 0);
- if (git_refs_heads_master) {
- /* we want to strip the .git/ part */
- int pfxlen = strlen(git_HEAD) - strlen(HEAD);
- puts(git_refs_heads_master + pfxlen);
- }
- else
+ int flag;
+ const char *refs_heads_master = resolve_ref(HEAD, sha1, 0, &flag);
+
+ if (!refs_heads_master)
die("No such ref: %s", HEAD);
+ else if (!(flag & REF_ISSYMREF))
+ die("ref %s is not a symbolic ref", HEAD);
+ puts(refs_heads_master);
}
int cmd_symbolic_ref(int argc, const char **argv, const char *prefix)
@@ -26,7 +26,7 @@ int cmd_symbolic_ref(int argc, const char **argv, const char *prefix)
check_symref(argv[1]);
break;
case 3:
- create_symref(strdup(git_path("%s", argv[1])), argv[2]);
+ create_symref(argv[1], argv[2]);
break;
default:
usage(git_symbolic_ref_usage);
diff --git a/builtin-tar-tree.c b/builtin-tar-tree.c
index 215892b696..4d4cfec878 100644
--- a/builtin-tar-tree.c
+++ b/builtin-tar-tree.c
@@ -3,416 +3,69 @@
*/
#include <time.h>
#include "cache.h"
-#include "tree-walk.h"
#include "commit.h"
-#include "strbuf.h"
#include "tar.h"
#include "builtin.h"
-#include "pkt-line.h"
-
-#define RECORDSIZE (512)
-#define BLOCKSIZE (RECORDSIZE * 20)
+#include "quote.h"
static const char tar_tree_usage[] =
-"git-tar-tree [--remote=<repo>] <ent> [basedir]";
-
-static char block[BLOCKSIZE];
-static unsigned long offset;
-
-static time_t archive_time;
-static int tar_umask;
-
-/* tries hard to write, either succeeds or dies in the attempt */
-static void reliable_write(const void *data, unsigned long size)
-{
- const char *buf = data;
-
- while (size > 0) {
- long ret = xwrite(1, buf, size);
- if (ret < 0) {
- if (errno == EPIPE)
- exit(0);
- die("git-tar-tree: %s", strerror(errno));
- } else if (!ret) {
- die("git-tar-tree: disk full?");
- }
- size -= ret;
- buf += ret;
- }
-}
-
-/* writes out the whole block, but only if it is full */
-static void write_if_needed(void)
-{
- if (offset == BLOCKSIZE) {
- reliable_write(block, BLOCKSIZE);
- offset = 0;
- }
-}
-
-/*
- * queues up writes, so that all our write(2) calls write exactly one
- * full block; pads writes to RECORDSIZE
- */
-static void write_blocked(const void *data, unsigned long size)
-{
- const char *buf = data;
- unsigned long tail;
-
- if (offset) {
- unsigned long chunk = BLOCKSIZE - offset;
- if (size < chunk)
- chunk = size;
- memcpy(block + offset, buf, chunk);
- size -= chunk;
- offset += chunk;
- buf += chunk;
- write_if_needed();
- }
- while (size >= BLOCKSIZE) {
- reliable_write(buf, BLOCKSIZE);
- size -= BLOCKSIZE;
- buf += BLOCKSIZE;
- }
- if (size) {
- memcpy(block + offset, buf, size);
- offset += size;
- }
- tail = offset % RECORDSIZE;
- if (tail) {
- memset(block + offset, 0, RECORDSIZE - tail);
- offset += RECORDSIZE - tail;
- }
- write_if_needed();
-}
-
-/*
- * The end of tar archives is marked by 2*512 nul bytes and after that
- * follows the rest of the block (if any).
- */
-static void write_trailer(void)
-{
- int tail = BLOCKSIZE - offset;
- memset(block + offset, 0, tail);
- reliable_write(block, BLOCKSIZE);
- if (tail < 2 * RECORDSIZE) {
- memset(block, 0, offset);
- reliable_write(block, BLOCKSIZE);
- }
-}
-
-static void strbuf_append_string(struct strbuf *sb, const char *s)
-{
- int slen = strlen(s);
- int total = sb->len + slen;
- if (total > sb->alloc) {
- sb->buf = xrealloc(sb->buf, total);
- sb->alloc = total;
- }
- memcpy(sb->buf + sb->len, s, slen);
- sb->len = total;
-}
-
-/*
- * pax extended header records have the format "%u %s=%s\n". %u contains
- * the size of the whole string (including the %u), the first %s is the
- * keyword, the second one is the value. This function constructs such a
- * string and appends it to a struct strbuf.
- */
-static void strbuf_append_ext_header(struct strbuf *sb, const char *keyword,
- const char *value, unsigned int valuelen)
-{
- char *p;
- int len, total, tmp;
-
- /* "%u %s=%s\n" */
- len = 1 + 1 + strlen(keyword) + 1 + valuelen + 1;
- for (tmp = len; tmp > 9; tmp /= 10)
- len++;
-
- total = sb->len + len;
- if (total > sb->alloc) {
- sb->buf = xrealloc(sb->buf, total);
- sb->alloc = total;
- }
-
- p = sb->buf;
- p += sprintf(p, "%u %s=", len, keyword);
- memcpy(p, value, valuelen);
- p += valuelen;
- *p = '\n';
- sb->len = total;
-}
-
-static unsigned int ustar_header_chksum(const struct ustar_header *header)
-{
- char *p = (char *)header;
- unsigned int chksum = 0;
- while (p < header->chksum)
- chksum += *p++;
- chksum += sizeof(header->chksum) * ' ';
- p += sizeof(header->chksum);
- while (p < (char *)header + sizeof(struct ustar_header))
- chksum += *p++;
- return chksum;
-}
-
-static int get_path_prefix(const struct strbuf *path, int maxlen)
-{
- int i = path->len;
- if (i > maxlen)
- i = maxlen;
- do {
- i--;
- } while (i > 0 && path->buf[i] != '/');
- return i;
-}
-
-static void write_entry(const unsigned char *sha1, struct strbuf *path,
- unsigned int mode, void *buffer, unsigned long size)
-{
- struct ustar_header header;
- struct strbuf ext_header;
-
- memset(&header, 0, sizeof(header));
- ext_header.buf = NULL;
- ext_header.len = ext_header.alloc = 0;
-
- if (!sha1) {
- *header.typeflag = TYPEFLAG_GLOBAL_HEADER;
- mode = 0100666;
- strcpy(header.name, "pax_global_header");
- } else if (!path) {
- *header.typeflag = TYPEFLAG_EXT_HEADER;
- mode = 0100666;
- sprintf(header.name, "%s.paxheader", sha1_to_hex(sha1));
- } else {
- if (S_ISDIR(mode)) {
- *header.typeflag = TYPEFLAG_DIR;
- mode = (mode | 0777) & ~tar_umask;
- } else if (S_ISLNK(mode)) {
- *header.typeflag = TYPEFLAG_LNK;
- mode |= 0777;
- } else if (S_ISREG(mode)) {
- *header.typeflag = TYPEFLAG_REG;
- mode = (mode | ((mode & 0100) ? 0777 : 0666)) & ~tar_umask;
- } else {
- error("unsupported file mode: 0%o (SHA1: %s)",
- mode, sha1_to_hex(sha1));
- return;
- }
- if (path->len > sizeof(header.name)) {
- int plen = get_path_prefix(path, sizeof(header.prefix));
- int rest = path->len - plen - 1;
- if (plen > 0 && rest <= sizeof(header.name)) {
- memcpy(header.prefix, path->buf, plen);
- memcpy(header.name, path->buf + plen + 1, rest);
- } else {
- sprintf(header.name, "%s.data",
- sha1_to_hex(sha1));
- strbuf_append_ext_header(&ext_header, "path",
- path->buf, path->len);
- }
- } else
- memcpy(header.name, path->buf, path->len);
- }
-
- if (S_ISLNK(mode) && buffer) {
- if (size > sizeof(header.linkname)) {
- sprintf(header.linkname, "see %s.paxheader",
- sha1_to_hex(sha1));
- strbuf_append_ext_header(&ext_header, "linkpath",
- buffer, size);
- } else
- memcpy(header.linkname, buffer, size);
- }
-
- sprintf(header.mode, "%07o", mode & 07777);
- sprintf(header.size, "%011lo", S_ISREG(mode) ? size : 0);
- sprintf(header.mtime, "%011lo", archive_time);
-
- /* XXX: should we provide more meaningful info here? */
- sprintf(header.uid, "%07o", 0);
- sprintf(header.gid, "%07o", 0);
- strlcpy(header.uname, "git", sizeof(header.uname));
- strlcpy(header.gname, "git", sizeof(header.gname));
- sprintf(header.devmajor, "%07o", 0);
- sprintf(header.devminor, "%07o", 0);
-
- memcpy(header.magic, "ustar", 6);
- memcpy(header.version, "00", 2);
-
- sprintf(header.chksum, "%07o", ustar_header_chksum(&header));
-
- if (ext_header.len > 0) {
- write_entry(sha1, NULL, 0, ext_header.buf, ext_header.len);
- free(ext_header.buf);
- }
- write_blocked(&header, sizeof(header));
- if (S_ISREG(mode) && buffer && size > 0)
- write_blocked(buffer, size);
-}
-
-static void write_global_extended_header(const unsigned char *sha1)
-{
- struct strbuf ext_header;
- ext_header.buf = NULL;
- ext_header.len = ext_header.alloc = 0;
- strbuf_append_ext_header(&ext_header, "comment", sha1_to_hex(sha1), 40);
- write_entry(NULL, NULL, 0, ext_header.buf, ext_header.len);
- free(ext_header.buf);
-}
-
-static void traverse_tree(struct tree_desc *tree, struct strbuf *path)
-{
- int pathlen = path->len;
- struct name_entry entry;
-
- while (tree_entry(tree, &entry)) {
- void *eltbuf;
- char elttype[20];
- unsigned long eltsize;
+"git-tar-tree [--remote=<repo>] <tree-ish> [basedir]\n"
+"*** Note that this command is now deprecated; use git-archive instead.";
- eltbuf = read_sha1_file(entry.sha1, elttype, &eltsize);
- if (!eltbuf)
- die("cannot read %s", sha1_to_hex(entry.sha1));
-
- path->len = pathlen;
- strbuf_append_string(path, entry.path);
- if (S_ISDIR(entry.mode))
- strbuf_append_string(path, "/");
-
- write_entry(entry.sha1, path, entry.mode, eltbuf, eltsize);
-
- if (S_ISDIR(entry.mode)) {
- struct tree_desc subtree;
- subtree.buf = eltbuf;
- subtree.size = eltsize;
- traverse_tree(&subtree, path);
- }
- free(eltbuf);
- }
-}
-
-int git_tar_config(const char *var, const char *value)
+int cmd_tar_tree(int argc, const char **argv, const char *prefix)
{
- if (!strcmp(var, "tar.umask")) {
- if (!strcmp(value, "user")) {
- tar_umask = umask(0);
- umask(tar_umask);
- } else {
- tar_umask = git_config_int(var, value);
- }
- return 0;
+ /*
+ * git-tar-tree is now a wrapper around git-archive --format=tar
+ *
+ * $0 --remote=<repo> arg... ==>
+ * git-archive --format=tar --remote=<repo> arg...
+ * $0 tree-ish ==>
+ * git-archive --format=tar tree-ish
+ * $0 tree-ish basedir ==>
+ * git-archive --format-tar --prefix=basedir tree-ish
+ */
+ int i;
+ const char **nargv = xcalloc(sizeof(*nargv), argc + 2);
+ char *basedir_arg;
+ int nargc = 0;
+
+ nargv[nargc++] = "git-archive";
+ nargv[nargc++] = "--format=tar";
+
+ if (2 <= argc && !strncmp("--remote=", argv[1], 9)) {
+ nargv[nargc++] = argv[1];
+ argv++;
+ argc--;
}
- return git_default_config(var, value);
-}
-
-static int generate_tar(int argc, const char **argv, const char *prefix)
-{
- unsigned char sha1[20], tree_sha1[20];
- struct commit *commit;
- struct tree_desc tree;
- struct strbuf current_path;
- void *buffer;
-
- current_path.buf = xmalloc(PATH_MAX);
- current_path.alloc = PATH_MAX;
- current_path.len = current_path.eof = 0;
-
- git_config(git_tar_config);
-
switch (argc) {
- case 3:
- strbuf_append_string(&current_path, argv[2]);
- strbuf_append_string(&current_path, "/");
- /* FALLTHROUGH */
- case 2:
- if (get_sha1(argv[1], sha1))
- die("Not a valid object name %s", argv[1]);
- break;
default:
usage(tar_tree_usage);
+ break;
+ case 3:
+ /* base-path */
+ basedir_arg = xmalloc(strlen(argv[2]) + 11);
+ sprintf(basedir_arg, "--prefix=%s/", argv[2]);
+ nargv[nargc++] = basedir_arg;
+ /* fallthru */
+ case 2:
+ /* tree-ish */
+ nargv[nargc++] = argv[1];
}
+ nargv[nargc] = NULL;
- commit = lookup_commit_reference_gently(sha1, 1);
- if (commit) {
- write_global_extended_header(commit->object.sha1);
- archive_time = commit->date;
- } else
- archive_time = time(NULL);
-
- tree.buf = buffer = read_object_with_reference(sha1, tree_type,
- &tree.size, tree_sha1);
- if (!tree.buf)
- die("not a reference to a tag, commit or tree object: %s",
- sha1_to_hex(sha1));
-
- if (current_path.len > 0)
- write_entry(tree_sha1, &current_path, 040777, NULL, 0);
- traverse_tree(&tree, &current_path);
- write_trailer();
- free(buffer);
- free(current_path.buf);
- return 0;
-}
-
-static const char *exec = "git-upload-tar";
-
-static int remote_tar(int argc, const char **argv)
-{
- int fd[2], ret, len;
- pid_t pid;
- char buf[1024];
- char *url;
-
- if (argc < 3 || 4 < argc)
- usage(tar_tree_usage);
-
- /* --remote=<repo> */
- url = strdup(argv[1]+9);
- pid = git_connect(fd, url, exec);
- if (pid < 0)
- return 1;
-
- packet_write(fd[1], "want %s\n", argv[2]);
- if (argv[3])
- packet_write(fd[1], "base %s\n", argv[3]);
- packet_flush(fd[1]);
-
- len = packet_read_line(fd[0], buf, sizeof(buf));
- if (!len)
- die("git-tar-tree: expected ACK/NAK, got EOF");
- if (buf[len-1] == '\n')
- buf[--len] = 0;
- if (strcmp(buf, "ACK")) {
- if (5 < len && !strncmp(buf, "NACK ", 5))
- die("git-tar-tree: NACK %s", buf + 5);
- die("git-tar-tree: protocol error");
+ fprintf(stderr,
+ "*** git-tar-tree is now deprecated.\n"
+ "*** Running git-archive instead.\n***");
+ for (i = 0; i < nargc; i++) {
+ fputc(' ', stderr);
+ sq_quote_print(stderr, nargv[i]);
}
- /* expect a flush */
- len = packet_read_line(fd[0], buf, sizeof(buf));
- if (len)
- die("git-tar-tree: expected a flush");
-
- /* Now, start reading from fd[0] and spit it out to stdout */
- ret = copy_fd(fd[0], 1);
- close(fd[0]);
-
- ret |= finish_connect(pid);
- return !!ret;
-}
-
-int cmd_tar_tree(int argc, const char **argv, const char *prefix)
-{
- if (argc < 2)
- usage(tar_tree_usage);
- if (!strncmp("--remote=", argv[1], 9))
- return remote_tar(argc, argv);
- return generate_tar(argc, argv, prefix);
+ fputc('\n', stderr);
+ return cmd_archive(nargc, nargv, prefix);
}
/* ustar header + extended global header content */
+#define RECORDSIZE (512)
#define HEADERSIZE (2 * RECORDSIZE)
int cmd_get_tar_commit_id(int argc, const char **argv, const char *prefix)
diff --git a/builtin-unpack-objects.c b/builtin-unpack-objects.c
index 63f4b8e45d..e6d7574844 100644
--- a/builtin-unpack-objects.c
+++ b/builtin-unpack-objects.c
@@ -10,29 +10,27 @@
#include <sys/time.h>
-static int dry_run, quiet;
-static const char unpack_usage[] = "git-unpack-objects [-n] [-q] < pack-file";
+static int dry_run, quiet, recover, has_errors;
+static const char unpack_usage[] = "git-unpack-objects [-n] [-q] [-r] < pack-file";
/* We always read in 4kB chunks. */
static unsigned char buffer[4096];
-static unsigned long offset, len, eof;
+static unsigned long offset, len, consumed_bytes;
static SHA_CTX ctx;
/*
* Make sure at least "min" bytes are available in the buffer, and
* return the pointer to the buffer.
*/
-static void * fill(int min)
+static void *fill(int min)
{
if (min <= len)
return buffer + offset;
- if (eof)
- die("unable to fill input");
if (min > sizeof(buffer))
die("cannot fill %d bytes", min);
if (offset) {
SHA1_Update(&ctx, buffer, offset);
- memcpy(buffer, buffer + offset, len);
+ memmove(buffer, buffer + offset, len);
offset = 0;
}
do {
@@ -53,6 +51,7 @@ static void use(int bytes)
die("used more bytes than were available");
len -= bytes;
offset += bytes;
+ consumed_bytes += bytes;
}
static void *get_data(unsigned long size)
@@ -73,8 +72,15 @@ static void *get_data(unsigned long size)
use(len - stream.avail_in);
if (stream.total_out == size && ret == Z_STREAM_END)
break;
- if (ret != Z_OK)
- die("inflate returned %d\n", ret);
+ if (ret != Z_OK) {
+ error("inflate returned %d\n", ret);
+ free(buf);
+ buf = NULL;
+ if (!recover)
+ exit(1);
+ has_errors = 1;
+ break;
+ }
stream.next_in = fill(1);
stream.avail_in = len;
}
@@ -84,37 +90,51 @@ static void *get_data(unsigned long size)
struct delta_info {
unsigned char base_sha1[20];
+ unsigned long base_offset;
unsigned long size;
void *delta;
+ unsigned nr;
struct delta_info *next;
};
static struct delta_info *delta_list;
-static void add_delta_to_list(unsigned char *base_sha1, void *delta, unsigned long size)
+static void add_delta_to_list(unsigned nr, unsigned const char *base_sha1,
+ unsigned long base_offset,
+ void *delta, unsigned long size)
{
struct delta_info *info = xmalloc(sizeof(*info));
- memcpy(info->base_sha1, base_sha1, 20);
+ hashcpy(info->base_sha1, base_sha1);
+ info->base_offset = base_offset;
info->size = size;
info->delta = delta;
+ info->nr = nr;
info->next = delta_list;
delta_list = info;
}
-static void added_object(unsigned char *sha1, const char *type, void *data, unsigned long size);
+struct obj_info {
+ unsigned long offset;
+ unsigned char sha1[20];
+};
-static void write_object(void *buf, unsigned long size, const char *type)
+static struct obj_info *obj_list;
+
+static void added_object(unsigned nr, const char *type, void *data,
+ unsigned long size);
+
+static void write_object(unsigned nr, void *buf, unsigned long size,
+ const char *type)
{
- unsigned char sha1[20];
- if (write_sha1_file(buf, size, type, sha1) < 0)
+ if (write_sha1_file(buf, size, type, obj_list[nr].sha1) < 0)
die("failed to write object");
- added_object(sha1, type, buf, size);
+ added_object(nr, type, buf, size);
}
-static int resolve_delta(const char *type,
- void *base, unsigned long base_size,
- void *delta, unsigned long delta_size)
+static void resolve_delta(unsigned nr, const char *type,
+ void *base, unsigned long base_size,
+ void *delta, unsigned long delta_size)
{
void *result;
unsigned long result_size;
@@ -125,21 +145,23 @@ static int resolve_delta(const char *type,
if (!result)
die("failed to apply delta");
free(delta);
- write_object(result, result_size, type);
+ write_object(nr, result, result_size, type);
free(result);
- return 0;
}
-static void added_object(unsigned char *sha1, const char *type, void *data, unsigned long size)
+static void added_object(unsigned nr, const char *type, void *data,
+ unsigned long size)
{
struct delta_info **p = &delta_list;
struct delta_info *info;
while ((info = *p) != NULL) {
- if (!memcmp(info->base_sha1, sha1, 20)) {
+ if (!hashcmp(info->base_sha1, obj_list[nr].sha1) ||
+ info->base_offset == obj_list[nr].offset) {
*p = info->next;
p = &delta_list;
- resolve_delta(type, data, size, info->delta, info->size);
+ resolve_delta(info->nr, type, data, size,
+ info->delta, info->size);
free(info);
continue;
}
@@ -147,7 +169,8 @@ static void added_object(unsigned char *sha1, const char *type, void *data, unsi
}
}
-static int unpack_non_delta_entry(enum object_type kind, unsigned long size)
+static void unpack_non_delta_entry(enum object_type kind, unsigned long size,
+ unsigned nr)
{
void *buf = get_data(size);
const char *type;
@@ -159,39 +182,92 @@ static int unpack_non_delta_entry(enum object_type kind, unsigned long size)
case OBJ_TAG: type = tag_type; break;
default: die("bad type %d", kind);
}
- if (!dry_run)
- write_object(buf, size, type);
+ if (!dry_run && buf)
+ write_object(nr, buf, size, type);
free(buf);
- return 0;
}
-static int unpack_delta_entry(unsigned long delta_size)
+static void unpack_delta_entry(enum object_type kind, unsigned long delta_size,
+ unsigned nr)
{
void *delta_data, *base;
unsigned long base_size;
char type[20];
unsigned char base_sha1[20];
- int result;
- memcpy(base_sha1, fill(20), 20);
- use(20);
+ if (kind == OBJ_REF_DELTA) {
+ hashcpy(base_sha1, fill(20));
+ use(20);
+ delta_data = get_data(delta_size);
+ if (dry_run || !delta_data) {
+ free(delta_data);
+ return;
+ }
+ if (!has_sha1_file(base_sha1)) {
+ hashcpy(obj_list[nr].sha1, null_sha1);
+ add_delta_to_list(nr, base_sha1, 0, delta_data, delta_size);
+ return;
+ }
+ } else {
+ unsigned base_found = 0;
+ unsigned char *pack, c;
+ unsigned long base_offset;
+ unsigned lo, mid, hi;
- delta_data = get_data(delta_size);
- if (dry_run) {
- free(delta_data);
- return 0;
- }
+ pack = fill(1);
+ c = *pack;
+ use(1);
+ base_offset = c & 127;
+ while (c & 128) {
+ base_offset += 1;
+ if (!base_offset || base_offset & ~(~0UL >> 7))
+ die("offset value overflow for delta base object");
+ pack = fill(1);
+ c = *pack;
+ use(1);
+ base_offset = (base_offset << 7) + (c & 127);
+ }
+ base_offset = obj_list[nr].offset - base_offset;
- if (!has_sha1_file(base_sha1)) {
- add_delta_to_list(base_sha1, delta_data, delta_size);
- return 0;
+ delta_data = get_data(delta_size);
+ if (dry_run || !delta_data) {
+ free(delta_data);
+ return;
+ }
+ lo = 0;
+ hi = nr;
+ while (lo < hi) {
+ mid = (lo + hi)/2;
+ if (base_offset < obj_list[mid].offset) {
+ hi = mid;
+ } else if (base_offset > obj_list[mid].offset) {
+ lo = mid + 1;
+ } else {
+ hashcpy(base_sha1, obj_list[mid].sha1);
+ base_found = !is_null_sha1(base_sha1);
+ break;
+ }
+ }
+ if (!base_found) {
+ /* The delta base object is itself a delta that
+ has not been resolved yet. */
+ hashcpy(obj_list[nr].sha1, null_sha1);
+ add_delta_to_list(nr, null_sha1, base_offset, delta_data, delta_size);
+ return;
+ }
}
+
base = read_sha1_file(base_sha1, type, &base_size);
- if (!base)
- die("failed to read delta-pack base object %s", sha1_to_hex(base_sha1));
- result = resolve_delta(type, base, base_size, delta_data, delta_size);
+ if (!base) {
+ error("failed to read delta-pack base object %s",
+ sha1_to_hex(base_sha1));
+ if (!recover)
+ exit(1);
+ has_errors = 1;
+ return;
+ }
+ resolve_delta(nr, type, base, base_size, delta_data, delta_size);
free(base);
- return result;
}
static void unpack_one(unsigned nr, unsigned total)
@@ -201,6 +277,8 @@ static void unpack_one(unsigned nr, unsigned total)
unsigned long size;
enum object_type type;
+ obj_list[nr].offset = consumed_bytes;
+
pack = fill(1);
c = *pack;
use(1);
@@ -209,7 +287,7 @@ static void unpack_one(unsigned nr, unsigned total)
shift = 4;
while (c & 0x80) {
pack = fill(1);
- c = *pack++;
+ c = *pack;
use(1);
size += (c & 0x7f) << shift;
shift += 7;
@@ -218,13 +296,14 @@ static void unpack_one(unsigned nr, unsigned total)
static unsigned long last_sec;
static unsigned last_percent;
struct timeval now;
- unsigned percentage = (nr * 100) / total;
+ unsigned percentage = ((nr+1) * 100) / total;
gettimeofday(&now, NULL);
if (percentage != last_percent || now.tv_sec != last_sec) {
last_sec = now.tv_sec;
last_percent = percentage;
- fprintf(stderr, "%4u%% (%u/%u) done\r", percentage, nr, total);
+ fprintf(stderr, "%4u%% (%u/%u) done\r",
+ percentage, (nr+1), total);
}
}
switch (type) {
@@ -232,13 +311,18 @@ static void unpack_one(unsigned nr, unsigned total)
case OBJ_TREE:
case OBJ_BLOB:
case OBJ_TAG:
- unpack_non_delta_entry(type, size);
+ unpack_non_delta_entry(type, size, nr);
return;
- case OBJ_DELTA:
- unpack_delta_entry(size);
+ case OBJ_REF_DELTA:
+ case OBJ_OFS_DELTA:
+ unpack_delta_entry(type, size, nr);
return;
default:
- die("bad object type %d", type);
+ error("bad object type %d", type);
+ has_errors = 1;
+ if (recover)
+ return;
+ exit(1);
}
}
@@ -254,9 +338,10 @@ static void unpack_all(void)
die("unknown pack file version %d", ntohl(hdr->hdr_version));
fprintf(stderr, "Unpacking %d objects\n", nr_objects);
+ obj_list = xmalloc(nr_objects * sizeof(*obj_list));
use(sizeof(struct pack_header));
for (i = 0; i < nr_objects; i++)
- unpack_one(i+1, nr_objects);
+ unpack_one(i, nr_objects);
if (delta_list)
die("unresolved deltas left after unpacking");
}
@@ -282,6 +367,25 @@ int cmd_unpack_objects(int argc, const char **argv, const char *prefix)
quiet = 1;
continue;
}
+ if (!strcmp(arg, "-r")) {
+ recover = 1;
+ continue;
+ }
+ if (!strncmp(arg, "--pack_header=", 14)) {
+ struct pack_header *hdr;
+ char *c;
+
+ hdr = (struct pack_header *)buffer;
+ hdr->hdr_signature = htonl(PACK_SIGNATURE);
+ hdr->hdr_version = htonl(strtoul(arg + 14, &c, 10));
+ if (*c != ',')
+ die("bad %s", arg);
+ hdr->hdr_entries = htonl(strtoul(c + 1, &c, 10));
+ if (*c)
+ die("bad %s", arg);
+ len = sizeof(*hdr);
+ continue;
+ }
usage(unpack_usage);
}
@@ -292,7 +396,7 @@ int cmd_unpack_objects(int argc, const char **argv, const char *prefix)
unpack_all();
SHA1_Update(&ctx, buffer, offset);
SHA1_Final(sha1, &ctx);
- if (memcmp(fill(20), sha1, 20))
+ if (hashcmp(fill(20), sha1))
die("final sha1 did not match");
use(20);
@@ -308,5 +412,5 @@ int cmd_unpack_objects(int argc, const char **argv, const char *prefix)
/* All done */
if (!quiet)
fprintf(stderr, "\n");
- return 0;
+ return has_errors;
}
diff --git a/builtin-update-index.c b/builtin-update-index.c
index d2556f376b..182331d341 100644
--- a/builtin-update-index.c
+++ b/builtin-update-index.c
@@ -23,7 +23,7 @@ static int allow_replace;
static int info_only;
static int force_remove;
static int verbose;
-static int mark_valid_only = 0;
+static int mark_valid_only;
#define MARK_VALID 1
#define UNMARK_VALID 2
@@ -112,11 +112,13 @@ static int add_file_to_cache(const char *path)
ce->ce_mode = create_ce_mode(st.st_mode);
if (!trust_executable_bit) {
/* If there is an existing entry, pick the mode bits
- * from it.
+ * from it, otherwise assume unexecutable.
*/
int pos = cache_name_pos(path, namelen);
if (0 <= pos)
ce->ce_mode = active_cache[pos]->ce_mode;
+ else if (S_ISREG(st.st_mode))
+ ce->ce_mode = create_ce_mode(S_IFREG | 0666);
}
if (index_path(ce->sha1, path, &st, !info_only))
@@ -142,7 +144,7 @@ static int add_cacheinfo(unsigned int mode, const unsigned char *sha1,
size = cache_entry_size(len);
ce = xcalloc(1, size);
- memcpy(ce->sha1, sha1, 20);
+ hashcpy(ce->sha1, sha1);
memcpy(ce->name, path, len);
ce->ce_flags = create_ce_flags(len, stage);
ce->ce_mode = create_ce_mode(mode);
@@ -306,7 +308,7 @@ static void read_index_info(int line_termination)
}
static const char update_index_usage[] =
-"git-update-index [-q] [--add] [--replace] [--remove] [--unmerged] [--refresh] [--really-refresh] [--cacheinfo] [--chmod=(+|-)x] [--assume-unchanged] [--info-only] [--force-remove] [--stdin] [--index-info] [--unresolve] [--again] [--ignore-missing] [-z] [--verbose] [--] <file>...";
+"git-update-index [-q] [--add] [--replace] [--remove] [--unmerged] [--refresh] [--really-refresh] [--cacheinfo] [--chmod=(+|-)x] [--assume-unchanged] [--info-only] [--force-remove] [--stdin] [--index-info] [--unresolve] [--again | -g] [--ignore-missing] [-z] [--verbose] [--] <file>...";
static unsigned char head_sha1[20];
static unsigned char merge_head_sha1[20];
@@ -333,7 +335,7 @@ static struct cache_entry *read_one_ent(const char *which,
size = cache_entry_size(namelen);
ce = xcalloc(1, size);
- memcpy(ce->sha1, sha1, 20);
+ hashcpy(ce->sha1, sha1);
memcpy(ce->name, path, namelen);
ce->ce_flags = create_ce_flags(namelen, stage);
ce->ce_mode = create_ce_mode(mode);
@@ -378,7 +380,7 @@ static int unresolve_one(const char *path)
ret = -1;
goto free_return;
}
- if (!memcmp(ce_2->sha1, ce_3->sha1, 20) &&
+ if (!hashcmp(ce_2->sha1, ce_3->sha1) &&
ce_2->ce_mode == ce_3->ce_mode) {
fprintf(stderr, "%s: identical in both, skipping.\n",
path);
@@ -404,9 +406,9 @@ static int unresolve_one(const char *path)
static void read_head_pointers(void)
{
- if (read_ref(git_path("HEAD"), head_sha1))
+ if (read_ref("HEAD", head_sha1))
die("No HEAD -- no initial commit yet?\n");
- if (read_ref(git_path("MERGE_HEAD"), merge_head_sha1)) {
+ if (read_ref("MERGE_HEAD", merge_head_sha1)) {
fprintf(stderr, "Not in the middle of a merge.\n");
exit(0);
}
@@ -443,7 +445,7 @@ static int do_reupdate(int ac, const char **av,
int has_head = 1;
const char **pathspec = get_pathspec(prefix, av + 1);
- if (read_ref(git_path("HEAD"), head_sha1))
+ if (read_ref("HEAD", head_sha1))
/* If there is no HEAD, that means it is an initial
* commit. Update everything in the index.
*/
@@ -460,7 +462,7 @@ static int do_reupdate(int ac, const char **av,
old = read_one_ent(NULL, head_sha1,
ce->name, ce_namelen(ce), 0);
if (old && ce->ce_mode == old->ce_mode &&
- !memcmp(ce->sha1, old->sha1, 20)) {
+ !hashcmp(ce->sha1, old->sha1)) {
free(old);
continue; /* unchanged */
}
@@ -595,7 +597,7 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)
active_cache_changed = 0;
goto finish;
}
- if (!strcmp(path, "--again")) {
+ if (!strcmp(path, "--again") || !strcmp(path, "-g")) {
has_errors = do_reupdate(argc - i, argv + i,
prefix, prefix_length);
if (has_errors)
diff --git a/builtin-update-ref.c b/builtin-update-ref.c
index 5bd71825fd..b34e5987dd 100644
--- a/builtin-update-ref.c
+++ b/builtin-update-ref.c
@@ -3,15 +3,16 @@
#include "builtin.h"
static const char git_update_ref_usage[] =
-"git-update-ref <refname> <value> [<oldval>] [-m <reason>]";
+"git-update-ref [-m <reason>] (-d <refname> <value> | <refname> <value> [<oldval>])";
int cmd_update_ref(int argc, const char **argv, const char *prefix)
{
const char *refname=NULL, *value=NULL, *oldval=NULL, *msg=NULL;
struct ref_lock *lock;
unsigned char sha1[20], oldsha1[20];
- int i;
+ int i, delete;
+ delete = 0;
setup_ident();
git_config(git_default_config);
@@ -26,6 +27,10 @@ int cmd_update_ref(int argc, const char **argv, const char *prefix)
die("Refusing to perform update with \\n in message.");
continue;
}
+ if (!strcmp("-d", argv[i])) {
+ delete = 1;
+ continue;
+ }
if (!refname) {
refname = argv[i];
continue;
@@ -44,11 +49,18 @@ int cmd_update_ref(int argc, const char **argv, const char *prefix)
if (get_sha1(value, sha1))
die("%s: not a valid SHA1", value);
- memset(oldsha1, 0, 20);
- if (oldval && get_sha1(oldval, oldsha1))
+
+ if (delete) {
+ if (oldval)
+ usage(git_update_ref_usage);
+ return delete_ref(refname, sha1);
+ }
+
+ hashclr(oldsha1);
+ if (oldval && *oldval && get_sha1(oldval, oldsha1))
die("%s: not a valid old SHA1", oldval);
- lock = lock_any_ref_for_update(refname, oldval ? oldsha1 : NULL, 0);
+ lock = lock_any_ref_for_update(refname, oldval ? oldsha1 : NULL);
if (!lock)
return 1;
if (write_ref_sha1(lock, sha1, msg) < 0)
diff --git a/builtin-upload-archive.c b/builtin-upload-archive.c
new file mode 100644
index 0000000000..45c92e163c
--- /dev/null
+++ b/builtin-upload-archive.c
@@ -0,0 +1,175 @@
+/*
+ * Copyright (c) 2006 Franck Bui-Huu
+ */
+#include <time.h>
+#include <sys/wait.h>
+#include <sys/poll.h>
+#include "cache.h"
+#include "builtin.h"
+#include "archive.h"
+#include "pkt-line.h"
+#include "sideband.h"
+
+static const char upload_archive_usage[] =
+ "git-upload-archive <repo>";
+
+static const char deadchild[] =
+"git-upload-archive: archiver died with error";
+
+static const char lostchild[] =
+"git-upload-archive: archiver process was lost";
+
+
+static int run_upload_archive(int argc, const char **argv, const char *prefix)
+{
+ struct archiver ar;
+ const char *sent_argv[MAX_ARGS];
+ const char *arg_cmd = "argument ";
+ char *p, buf[4096];
+ int treeish_idx;
+ int sent_argc;
+ int len;
+
+ if (argc != 2)
+ usage(upload_archive_usage);
+
+ if (strlen(argv[1]) > sizeof(buf))
+ die("insanely long repository name");
+
+ strcpy(buf, argv[1]); /* enter-repo smudges its argument */
+
+ if (!enter_repo(buf, 0))
+ die("not a git archive");
+
+ /* put received options in sent_argv[] */
+ sent_argc = 1;
+ sent_argv[0] = "git-upload-archive";
+ for (p = buf;;) {
+ /* This will die if not enough free space in buf */
+ len = packet_read_line(0, p, (buf + sizeof buf) - p);
+ if (len == 0)
+ break; /* got a flush */
+ if (sent_argc > MAX_ARGS - 2)
+ die("Too many options (>29)");
+
+ if (p[len-1] == '\n') {
+ p[--len] = 0;
+ }
+ if (len < strlen(arg_cmd) ||
+ strncmp(arg_cmd, p, strlen(arg_cmd)))
+ die("'argument' token or flush expected");
+
+ len -= strlen(arg_cmd);
+ memmove(p, p + strlen(arg_cmd), len);
+ sent_argv[sent_argc++] = p;
+ p += len;
+ *p++ = 0;
+ }
+ sent_argv[sent_argc] = NULL;
+
+ /* parse all options sent by the client */
+ treeish_idx = parse_archive_args(sent_argc, sent_argv, &ar);
+
+ parse_treeish_arg(sent_argv + treeish_idx, &ar.args, prefix);
+ parse_pathspec_arg(sent_argv + treeish_idx + 1, &ar.args);
+
+ return ar.write_archive(&ar.args);
+}
+
+static void error_clnt(const char *fmt, ...)
+{
+ char buf[1024];
+ va_list params;
+ int len;
+
+ va_start(params, fmt);
+ len = vsprintf(buf, fmt, params);
+ va_end(params);
+ send_sideband(1, 3, buf, len, LARGE_PACKET_MAX);
+ die("sent error to the client: %s", buf);
+}
+
+static void process_input(int child_fd, int band)
+{
+ char buf[16384];
+ ssize_t sz = read(child_fd, buf, sizeof(buf));
+ if (sz < 0) {
+ if (errno != EINTR)
+ error_clnt("read error: %s\n", strerror(errno));
+ return;
+ }
+ send_sideband(1, band, buf, sz, LARGE_PACKET_MAX);
+}
+
+int cmd_upload_archive(int argc, const char **argv, const char *prefix)
+{
+ pid_t writer;
+ int fd1[2], fd2[2];
+ /*
+ * Set up sideband subprocess.
+ *
+ * We (parent) monitor and read from child, sending its fd#1 and fd#2
+ * multiplexed out to our fd#1. If the child dies, we tell the other
+ * end over channel #3.
+ */
+ if (pipe(fd1) < 0 || pipe(fd2) < 0) {
+ int err = errno;
+ packet_write(1, "NACK pipe failed on the remote side\n");
+ die("upload-archive: %s", strerror(err));
+ }
+ writer = fork();
+ if (writer < 0) {
+ int err = errno;
+ packet_write(1, "NACK fork failed on the remote side\n");
+ die("upload-archive: %s", strerror(err));
+ }
+ if (!writer) {
+ /* child - connect fd#1 and fd#2 to the pipe */
+ dup2(fd1[1], 1);
+ dup2(fd2[1], 2);
+ close(fd1[1]); close(fd2[1]);
+ close(fd1[0]); close(fd2[0]); /* we do not read from pipe */
+
+ exit(run_upload_archive(argc, argv, prefix));
+ }
+
+ /* parent - read from child, multiplex and send out to fd#1 */
+ close(fd1[1]); close(fd2[1]); /* we do not write to pipe */
+ packet_write(1, "ACK\n");
+ packet_flush(1);
+
+ while (1) {
+ struct pollfd pfd[2];
+ int status;
+
+ pfd[0].fd = fd1[0];
+ pfd[0].events = POLLIN;
+ pfd[1].fd = fd2[0];
+ pfd[1].events = POLLIN;
+ if (poll(pfd, 2, -1) < 0) {
+ if (errno != EINTR) {
+ error("poll failed resuming: %s",
+ strerror(errno));
+ sleep(1);
+ }
+ continue;
+ }
+ if (pfd[0].revents & POLLIN)
+ /* Data stream ready */
+ process_input(pfd[0].fd, 1);
+ if (pfd[1].revents & POLLIN)
+ /* Status stream ready */
+ process_input(pfd[1].fd, 2);
+ /* Always finish to read data when available */
+ if ((pfd[0].revents | pfd[1].revents) & POLLIN)
+ continue;
+
+ if (waitpid(writer, &status, 0) < 0)
+ error_clnt("%s", lostchild);
+ else if (!WIFEXITED(status) || WEXITSTATUS(status) > 0)
+ error_clnt("%s", deadchild);
+ packet_flush(1);
+ break;
+ }
+ return 0;
+}
diff --git a/builtin-upload-tar.c b/builtin-upload-tar.c
deleted file mode 100644
index 7b401bbb77..0000000000
--- a/builtin-upload-tar.c
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright (c) 2006 Junio C Hamano
- */
-#include "cache.h"
-#include "pkt-line.h"
-#include "exec_cmd.h"
-#include "builtin.h"
-
-static const char upload_tar_usage[] = "git-upload-tar <repo>";
-
-static int nak(const char *reason)
-{
- packet_write(1, "NACK %s\n", reason);
- packet_flush(1);
- return 1;
-}
-
-int cmd_upload_tar(int argc, const char **argv, const char *prefix)
-{
- int len;
- const char *dir = argv[1];
- char buf[8192];
- unsigned char sha1[20];
- char *base = NULL;
- char hex[41];
- int ac;
- const char *av[4];
-
- if (argc != 2)
- usage(upload_tar_usage);
- if (strlen(dir) < sizeof(buf)-1)
- strcpy(buf, dir); /* enter-repo smudges its argument */
- else
- packet_write(1, "NACK insanely long repository name %s\n", dir);
- if (!enter_repo(buf, 0)) {
- packet_write(1, "NACK not a git archive %s\n", dir);
- packet_flush(1);
- return 1;
- }
-
- len = packet_read_line(0, buf, sizeof(buf));
- if (len < 5 || strncmp("want ", buf, 5))
- return nak("expected want");
- if (buf[len-1] == '\n')
- buf[--len] = 0;
- if (get_sha1(buf + 5, sha1))
- return nak("expected sha1");
- strcpy(hex, sha1_to_hex(sha1));
-
- len = packet_read_line(0, buf, sizeof(buf));
- if (len) {
- if (len < 5 || strncmp("base ", buf, 5))
- return nak("expected (optional) base");
- if (buf[len-1] == '\n')
- buf[--len] = 0;
- base = strdup(buf + 5);
- len = packet_read_line(0, buf, sizeof(buf));
- }
- if (len)
- return nak("expected flush");
-
- packet_write(1, "ACK\n");
- packet_flush(1);
-
- ac = 0;
- av[ac++] = "tar-tree";
- av[ac++] = hex;
- if (base)
- av[ac++] = base;
- av[ac++] = NULL;
- execv_git_cmd(av);
- /* should it return that is an error */
- return 1;
-}
diff --git a/builtin-write-tree.c b/builtin-write-tree.c
index ca06149f18..50670dc7bf 100644
--- a/builtin-write-tree.c
+++ b/builtin-write-tree.c
@@ -50,10 +50,10 @@ int write_tree(unsigned char *sha1, int missing_ok, const char *prefix)
if (prefix) {
struct cache_tree *subtree =
cache_tree_find(active_cache_tree, prefix);
- memcpy(sha1, subtree->sha1, 20);
+ hashcpy(sha1, subtree->sha1);
}
else
- memcpy(sha1, active_cache_tree->sha1, 20);
+ hashcpy(sha1, active_cache_tree->sha1);
rollback_lock_file(lock_file);
diff --git a/builtin.h b/builtin.h
index ade58c4a1f..43fed329ba 100644
--- a/builtin.h
+++ b/builtin.h
@@ -1,8 +1,7 @@
#ifndef BUILTIN_H
#define BUILTIN_H
-#include <stdio.h>
-#include <limits.h>
+#include "git-compat-util.h"
extern const char git_version_string[];
extern const char git_usage_string[];
@@ -12,12 +11,18 @@ extern int mailinfo(FILE *in, FILE *out, int ks, const char *encoding, const cha
extern int split_mbox(const char **mbox, const char *dir, int allow_bare, int nr_prec, int skip);
extern void stripspace(FILE *in, FILE *out);
extern int write_tree(unsigned char *sha1, int missing_ok, const char *prefix);
+extern void prune_packed_objects(int);
extern int cmd_add(int argc, const char **argv, const char *prefix);
+extern int cmd_annotate(int argc, const char **argv, const char *prefix);
extern int cmd_apply(int argc, const char **argv, const char *prefix);
+extern int cmd_archive(int argc, const char **argv, const char *prefix);
+extern int cmd_blame(int argc, const char **argv, const char *prefix);
+extern int cmd_branch(int argc, const char **argv, const char *prefix);
extern int cmd_cat_file(int argc, const char **argv, const char *prefix);
extern int cmd_checkout_index(int argc, const char **argv, const char *prefix);
extern int cmd_check_ref_format(int argc, const char **argv, const char *prefix);
+extern int cmd_cherry(int argc, const char **argv, const char *prefix);
extern int cmd_commit_tree(int argc, const char **argv, const char *prefix);
extern int cmd_count_objects(int argc, const char **argv, const char *prefix);
extern int cmd_diff_files(int argc, const char **argv, const char *prefix);
@@ -26,6 +31,7 @@ extern int cmd_diff(int argc, const char **argv, const char *prefix);
extern int cmd_diff_stages(int argc, const char **argv, const char *prefix);
extern int cmd_diff_tree(int argc, const char **argv, const char *prefix);
extern int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix);
+extern int cmd_for_each_ref(int argc, const char **argv, const char *prefix);
extern int cmd_format_patch(int argc, const char **argv, const char *prefix);
extern int cmd_get_tar_commit_id(int argc, const char **argv, const char *prefix);
extern int cmd_grep(int argc, const char **argv, const char *prefix);
@@ -39,6 +45,7 @@ extern int cmd_mailsplit(int argc, const char **argv, const char *prefix);
extern int cmd_mv(int argc, const char **argv, const char *prefix);
extern int cmd_name_rev(int argc, const char **argv, const char *prefix);
extern int cmd_pack_objects(int argc, const char **argv, const char *prefix);
+extern int cmd_pickaxe(int argc, const char **argv, const char *prefix);
extern int cmd_prune(int argc, const char **argv, const char *prefix);
extern int cmd_prune_packed(int argc, const char **argv, const char *prefix);
extern int cmd_push(int argc, const char **argv, const char *prefix);
@@ -47,18 +54,22 @@ extern int cmd_repo_config(int argc, const char **argv, const char *prefix);
extern int cmd_rev_list(int argc, const char **argv, const char *prefix);
extern int cmd_rev_parse(int argc, const char **argv, const char *prefix);
extern int cmd_rm(int argc, const char **argv, const char *prefix);
-extern int cmd_show_branch(int argc, const char **argv, const char *prefix);
+extern int cmd_runstatus(int argc, const char **argv, const char *prefix);
extern int cmd_show(int argc, const char **argv, const char *prefix);
+extern int cmd_show_branch(int argc, const char **argv, const char *prefix);
extern int cmd_stripspace(int argc, const char **argv, const char *prefix);
extern int cmd_symbolic_ref(int argc, const char **argv, const char *prefix);
extern int cmd_tar_tree(int argc, const char **argv, const char *prefix);
extern int cmd_unpack_objects(int argc, const char **argv, const char *prefix);
extern int cmd_update_index(int argc, const char **argv, const char *prefix);
extern int cmd_update_ref(int argc, const char **argv, const char *prefix);
+extern int cmd_upload_archive(int argc, const char **argv, const char *prefix);
extern int cmd_upload_tar(int argc, const char **argv, const char *prefix);
extern int cmd_version(int argc, const char **argv, const char *prefix);
extern int cmd_whatchanged(int argc, const char **argv, const char *prefix);
extern int cmd_write_tree(int argc, const char **argv, const char *prefix);
extern int cmd_verify_pack(int argc, const char **argv, const char *prefix);
+extern int cmd_show_ref(int argc, const char **argv, const char *prefix);
+extern int cmd_pack_refs(int argc, const char **argv, const char *prefix);
#endif
diff --git a/cache-tree.c b/cache-tree.c
index d9f7e1e3dd..9b73c8669a 100644
--- a/cache-tree.c
+++ b/cache-tree.c
@@ -2,7 +2,9 @@
#include "tree.h"
#include "cache-tree.h"
+#ifndef DEBUG
#define DEBUG 0
+#endif
struct cache_tree *cache_tree(void)
{
@@ -280,6 +282,8 @@ static int update_one(struct cache_tree *it,
baselen + sublen + 1,
missing_ok,
dryrun);
+ if (subcnt < 0)
+ return subcnt;
i += subcnt - 1;
sub->used = 1;
}
@@ -335,7 +339,7 @@ static int update_one(struct cache_tree *it,
offset += sprintf(buffer + offset,
"%o %.*s", mode, entlen, path + baselen);
buffer[offset++] = 0;
- memcpy(buffer + offset, sha1, 20);
+ hashcpy((unsigned char*)buffer + offset, sha1);
offset += 20;
#if DEBUG
@@ -344,12 +348,8 @@ static int update_one(struct cache_tree *it,
#endif
}
- if (dryrun) {
- unsigned char hdr[200];
- int hdrlen;
- write_sha1_file_prepare(buffer, offset, tree_type, it->sha1,
- hdr, &hdrlen);
- }
+ if (dryrun)
+ hash_sha1_file(buffer, offset, tree_type, it->sha1);
else
write_sha1_file(buffer, offset, tree_type, it->sha1);
free(buffer);
@@ -412,7 +412,7 @@ static void *write_one(struct cache_tree *it,
#endif
if (0 <= it->entry_count) {
- memcpy(buffer + *offset, it->sha1, 20);
+ hashcpy((unsigned char*)buffer + *offset, it->sha1);
*offset += 20;
}
for (i = 0; i < it->subtree_nr; i++) {
@@ -478,7 +478,7 @@ static struct cache_tree *read_one(const char **buffer, unsigned long *size_p)
if (0 <= it->entry_count) {
if (size < 20)
goto free_return;
- memcpy(it->sha1, buf, 20);
+ hashcpy(it->sha1, (unsigned char*)buf);
buf += 20;
size -= 20;
}
diff --git a/cache.h b/cache.h
index af7740258d..f2ec5c8c14 100644
--- a/cache.h
+++ b/cache.h
@@ -123,7 +123,7 @@ extern int cache_errno;
#define INDEX_ENVIRONMENT "GIT_INDEX_FILE"
#define GRAFT_ENVIRONMENT "GIT_GRAFT_FILE"
-extern char *get_git_dir(void);
+extern const char *get_git_dir(void);
extern char *get_object_directory(void);
extern char *get_refs_directory(void);
extern char *get_index_file(void);
@@ -145,6 +145,7 @@ extern void verify_non_filename(const char *prefix, const char *name);
extern int read_cache(void);
extern int read_cache_from(const char *path);
extern int write_cache(int newfd, struct cache_entry **cache, int entries);
+extern int discard_cache(void);
extern int verify_path(const char *path);
extern int cache_name_pos(const char *name, int namelen);
#define ADD_CACHE_OK_TO_ADD 1 /* Ok to add */
@@ -178,6 +179,7 @@ struct lock_file {
extern int hold_lock_file_for_update(struct lock_file *, const char *path, int);
extern int commit_lock_file(struct lock_file *);
extern void rollback_lock_file(struct lock_file *);
+extern int delete_ref(const char *, unsigned char *sha1);
/* Environment bits from configuration mechanism */
extern int use_legacy_headers;
@@ -210,6 +212,22 @@ extern char *sha1_pack_name(const unsigned char *sha1);
extern char *sha1_pack_index_name(const unsigned char *sha1);
extern const char *find_unique_abbrev(const unsigned char *sha1, int);
extern const unsigned char null_sha1[20];
+static inline int is_null_sha1(const unsigned char *sha1)
+{
+ return !memcmp(sha1, null_sha1, 20);
+}
+static inline int hashcmp(const unsigned char *sha1, const unsigned char *sha2)
+{
+ return memcmp(sha1, sha2, 20);
+}
+static inline void hashcpy(unsigned char *sha_dst, const unsigned char *sha_src)
+{
+ memcpy(sha_dst, sha_src, 20);
+}
+static inline void hashclr(unsigned char *hash)
+{
+ memset(hash, 0, 20);
+}
int git_mkstemp(char *path, size_t n, const char *template);
@@ -227,22 +245,17 @@ char *enter_repo(char *path, int strict);
extern int sha1_object_info(const unsigned char *, char *, unsigned long *);
extern void * unpack_sha1_file(void *map, unsigned long mapsize, char *type, unsigned long *size);
extern void * read_sha1_file(const unsigned char *sha1, char *type, unsigned long *size);
+extern int hash_sha1_file(void *buf, unsigned long len, const char *type, unsigned char *sha1);
extern int write_sha1_file(void *buf, unsigned long len, const char *type, unsigned char *return_sha1);
-extern char *write_sha1_file_prepare(void *buf,
- unsigned long len,
- const char *type,
- unsigned char *sha1,
- unsigned char *hdr,
- int *hdrlen);
extern int check_sha1_signature(const unsigned char *sha1, void *buf, unsigned long size, const char *type);
extern int write_sha1_from_fd(const unsigned char *sha1, int fd, char *buffer,
size_t bufsize, size_t *bufposn);
extern int write_sha1_to_fd(int fd, const unsigned char *sha1);
-extern int move_temp_to_file(const char *tmpfile, char *filename);
+extern int move_temp_to_file(const char *tmpfile, const char *filename);
-extern int has_sha1_pack(const unsigned char *sha1);
+extern int has_sha1_pack(const unsigned char *sha1, const char **ignore);
extern int has_sha1_file(const unsigned char *sha1);
extern void *map_sha1_file(const unsigned char *sha1, unsigned long *);
extern int legacy_loose_object(unsigned char *);
@@ -250,6 +263,24 @@ extern int legacy_loose_object(unsigned char *);
extern int has_pack_file(const unsigned char *sha1);
extern int has_pack_index(const unsigned char *sha1);
+enum object_type {
+ OBJ_NONE = 0,
+ OBJ_COMMIT = 1,
+ OBJ_TREE = 2,
+ OBJ_BLOB = 3,
+ OBJ_TAG = 4,
+ /* 5 for future expansion */
+ OBJ_OFS_DELTA = 6,
+ OBJ_REF_DELTA = 7,
+ OBJ_BAD,
+};
+
+extern signed char hexval_table[256];
+static inline unsigned int hexval(unsigned int c)
+{
+ return hexval_table[c];
+}
+
/* Convert to/from hex/sha1 representation */
#define MINIMUM_ABBREV 4
#define DEFAULT_ABBREV 7
@@ -258,9 +289,9 @@ extern int get_sha1(const char *str, unsigned char *sha1);
extern int get_sha1_hex(const char *hex, unsigned char *sha1);
extern char *sha1_to_hex(const unsigned char *sha1); /* static buffer result! */
extern int read_ref(const char *filename, unsigned char *sha1);
-extern const char *resolve_ref(const char *path, unsigned char *sha1, int);
-extern int create_symref(const char *git_HEAD, const char *refs_heads_master);
-extern int validate_symref(const char *git_HEAD);
+extern const char *resolve_ref(const char *path, unsigned char *sha1, int, int *);
+extern int create_symref(const char *ref, const char *refs_heads_master);
+extern int validate_symref(const char *ref);
extern int base_name_compare(const char *name1, int len1, int mode1, const char *name2, int len2, int mode2);
extern int cache_name_compare(const char *name1, int len1, const char *name2, int len2);
@@ -270,7 +301,7 @@ extern void *read_object_with_reference(const unsigned char *sha1,
unsigned long *size,
unsigned char *sha1_ret);
-const char *show_date(unsigned long time, int timezone);
+const char *show_date(unsigned long time, int timezone, int relative);
const char *show_rfc2822_date(unsigned long time, int timezone);
int parse_date(const char *date, char *buf, int bufsize);
void datestamp(char *buf, int bufsize);
@@ -331,7 +362,7 @@ struct ref {
#define REF_HEADS (1u << 1)
#define REF_TAGS (1u << 2)
-extern int git_connect(int fd[2], char *url, const char *prog);
+extern pid_t git_connect(int fd[2], char *url, const char *prog);
extern int finish_connect(pid_t pid);
extern int path_match(const char *path, int nr, char **match);
extern int match_refs(struct ref *src, struct ref *dst, struct ref ***dst_tail,
@@ -345,6 +376,7 @@ extern struct packed_git *parse_pack_index_file(const unsigned char *sha1,
char *idx_path);
extern void prepare_packed_git(void);
+extern void reprepare_packed_git(void);
extern void install_packed_git(struct packed_git *pack);
extern struct packed_git *find_sha1_pack(const unsigned char *sha1,
@@ -355,9 +387,10 @@ extern void unuse_packed_git(struct packed_git *);
extern struct packed_git *add_packed_git(char *, int, int);
extern int num_packed_objects(const struct packed_git *p);
extern int nth_packed_object_sha1(const struct packed_git *, int, unsigned char*);
-extern int find_pack_entry_one(const unsigned char *, struct pack_entry *, struct packed_git *);
-extern void *unpack_entry_gently(struct pack_entry *, char *, unsigned long *);
-extern void packed_object_info_detail(struct pack_entry *, char *, unsigned long *, unsigned long *, unsigned int *, unsigned char *);
+extern unsigned long find_pack_entry_one(const unsigned char *, struct packed_git *);
+extern void *unpack_entry_gently(struct packed_git *, unsigned long, char *, unsigned long *);
+extern unsigned long unpack_object_header_gently(const unsigned char *buf, unsigned long len, enum object_type *type, unsigned long *sizep);
+extern void packed_object_info_detail(struct packed_git *, unsigned long, char *, unsigned long *, unsigned long *, unsigned int *, unsigned char *);
/* Dumb servers support */
extern int update_server_info(int);
@@ -380,10 +413,8 @@ extern char git_default_name[MAX_GITNAME];
extern char git_commit_encoding[MAX_ENCODING_LENGTH];
extern int copy_fd(int ifd, int ofd);
-
-/* Finish off pack transfer receiving end */
-extern int receive_unpack_pack(int fd[2], const char *me, int quiet, int);
-extern int receive_keep_pack(int fd[2], const char *me, int quiet, int);
+extern void write_or_die(int fd, const void *buf, size_t count);
+extern int write_or_whine(int fd, const void *buf, size_t count, const char *msg);
/* pager.c */
extern void setup_pager(void);
@@ -405,4 +436,9 @@ extern struct commit *alloc_commit_node(void);
extern struct tag *alloc_tag_node(void);
extern void alloc_report(void);
+/* trace.c */
+extern int nfvasprintf(char **str, const char *fmt, va_list va);
+extern void trace_printf(const char *format, ...);
+extern void trace_argv_printf(const char **argv, int count, const char *format, ...);
+
#endif /* CACHE_H */
diff --git a/check-builtins.sh b/check-builtins.sh
new file mode 100755
index 0000000000..d6fe6cf174
--- /dev/null
+++ b/check-builtins.sh
@@ -0,0 +1,34 @@
+#!/bin/sh
+
+{
+ cat <<\EOF
+sayIt:
+ $(foreach b,$(BUILT_INS),echo XXX $b YYY;)
+EOF
+ cat Makefile
+} |
+make -f - sayIt 2>/dev/null |
+sed -n -e 's/.*XXX \(.*\) YYY.*/\1/p' |
+sort |
+{
+ bad=0
+ while read builtin
+ do
+ base=`expr "$builtin" : 'git-\(.*\)'`
+ x=`sed -ne 's/.*{ "'$base'", \(cmd_[^, ]*\).*/'$base' \1/p' git.c`
+ if test -z "$x"
+ then
+ echo "$base is builtin but not listed in git.c command list"
+ bad=1
+ fi
+ for sfx in sh perl py
+ do
+ if test -f "$builtin.$sfx"
+ then
+ echo "$base is builtin but $builtin.$sfx still exists"
+ bad=1
+ fi
+ done
+ done
+ exit $bad
+}
diff --git a/check-racy.c b/check-racy.c
new file mode 100644
index 0000000000..d6a08b4a55
--- /dev/null
+++ b/check-racy.c
@@ -0,0 +1,28 @@
+#include "cache.h"
+
+int main(int ac, char **av)
+{
+ int i;
+ int dirty, clean, racy;
+
+ dirty = clean = racy = 0;
+ read_cache();
+ for (i = 0; i < active_nr; i++) {
+ struct cache_entry *ce = active_cache[i];
+ struct stat st;
+
+ if (lstat(ce->name, &st)) {
+ error("lstat(%s): %s", ce->name, strerror(errno));
+ continue;
+ }
+
+ if (ce_match_stat(ce, &st, 0))
+ dirty++;
+ else if (ce_match_stat(ce, &st, 2))
+ racy++;
+ else
+ clean++;
+ }
+ printf("dirty %d, clean %d, racy %d\n", dirty, clean, racy);
+ return 0;
+}
diff --git a/color.c b/color.c
new file mode 100644
index 0000000000..d8c8399d59
--- /dev/null
+++ b/color.c
@@ -0,0 +1,176 @@
+#include "color.h"
+#include "cache.h"
+#include "git-compat-util.h"
+
+#include <stdarg.h>
+
+#define COLOR_RESET "\033[m"
+
+static int parse_color(const char *name, int len)
+{
+ static const char * const color_names[] = {
+ "normal", "black", "red", "green", "yellow",
+ "blue", "magenta", "cyan", "white"
+ };
+ char *end;
+ int i;
+ for (i = 0; i < ARRAY_SIZE(color_names); i++) {
+ const char *str = color_names[i];
+ if (!strncasecmp(name, str, len) && !str[len])
+ return i - 1;
+ }
+ i = strtol(name, &end, 10);
+ if (*name && !*end && i >= -1 && i <= 255)
+ return i;
+ return -2;
+}
+
+static int parse_attr(const char *name, int len)
+{
+ static const int attr_values[] = { 1, 2, 4, 5, 7 };
+ static const char * const attr_names[] = {
+ "bold", "dim", "ul", "blink", "reverse"
+ };
+ int i;
+ for (i = 0; i < ARRAY_SIZE(attr_names); i++) {
+ const char *str = attr_names[i];
+ if (!strncasecmp(name, str, len) && !str[len])
+ return attr_values[i];
+ }
+ return -1;
+}
+
+void color_parse(const char *value, const char *var, char *dst)
+{
+ const char *ptr = value;
+ int attr = -1;
+ int fg = -2;
+ int bg = -2;
+
+ if (!strcasecmp(value, "reset")) {
+ strcpy(dst, "\033[m");
+ return;
+ }
+
+ /* [fg [bg]] [attr] */
+ while (*ptr) {
+ const char *word = ptr;
+ int val, len = 0;
+
+ while (word[len] && !isspace(word[len]))
+ len++;
+
+ ptr = word + len;
+ while (*ptr && isspace(*ptr))
+ ptr++;
+
+ val = parse_color(word, len);
+ if (val >= -1) {
+ if (fg == -2) {
+ fg = val;
+ continue;
+ }
+ if (bg == -2) {
+ bg = val;
+ continue;
+ }
+ goto bad;
+ }
+ val = parse_attr(word, len);
+ if (val < 0 || attr != -1)
+ goto bad;
+ attr = val;
+ }
+
+ if (attr >= 0 || fg >= 0 || bg >= 0) {
+ int sep = 0;
+
+ *dst++ = '\033';
+ *dst++ = '[';
+ if (attr >= 0) {
+ *dst++ = '0' + attr;
+ sep++;
+ }
+ if (fg >= 0) {
+ if (sep++)
+ *dst++ = ';';
+ if (fg < 8) {
+ *dst++ = '3';
+ *dst++ = '0' + fg;
+ } else {
+ dst += sprintf(dst, "38;5;%d", fg);
+ }
+ }
+ if (bg >= 0) {
+ if (sep++)
+ *dst++ = ';';
+ if (bg < 8) {
+ *dst++ = '4';
+ *dst++ = '0' + bg;
+ } else {
+ dst += sprintf(dst, "48;5;%d", bg);
+ }
+ }
+ *dst++ = 'm';
+ }
+ *dst = 0;
+ return;
+bad:
+ die("bad config value '%s' for variable '%s'", value, var);
+}
+
+int git_config_colorbool(const char *var, const char *value)
+{
+ if (!value)
+ return 1;
+ if (!strcasecmp(value, "auto")) {
+ if (isatty(1) || (pager_in_use && pager_use_color)) {
+ char *term = getenv("TERM");
+ if (term && strcmp(term, "dumb"))
+ return 1;
+ }
+ return 0;
+ }
+ if (!strcasecmp(value, "never"))
+ return 0;
+ if (!strcasecmp(value, "always"))
+ return 1;
+ return git_config_bool(var, value);
+}
+
+static int color_vprintf(const char *color, const char *fmt,
+ va_list args, const char *trail)
+{
+ int r = 0;
+
+ if (*color)
+ r += printf("%s", color);
+ r += vprintf(fmt, args);
+ if (*color)
+ r += printf("%s", COLOR_RESET);
+ if (trail)
+ r += printf("%s", trail);
+ return r;
+}
+
+
+
+int color_printf(const char *color, const char *fmt, ...)
+{
+ va_list args;
+ int r;
+ va_start(args, fmt);
+ r = color_vprintf(color, fmt, args, NULL);
+ va_end(args);
+ return r;
+}
+
+int color_printf_ln(const char *color, const char *fmt, ...)
+{
+ va_list args;
+ int r;
+ va_start(args, fmt);
+ r = color_vprintf(color, fmt, args, "\n");
+ va_end(args);
+ return r;
+}
diff --git a/color.h b/color.h
new file mode 100644
index 0000000000..88bb8ff1bd
--- /dev/null
+++ b/color.h
@@ -0,0 +1,12 @@
+#ifndef COLOR_H
+#define COLOR_H
+
+/* "\033[1;38;5;2xx;48;5;2xxm\0" is 23 bytes */
+#define COLOR_MAXLEN 24
+
+int git_config_colorbool(const char *var, const char *value);
+void color_parse(const char *var, const char *value, char *dst);
+int color_printf(const char *color, const char *fmt, ...);
+int color_printf_ln(const char *color, const char *fmt, ...);
+
+#endif /* COLOR_H */
diff --git a/combine-diff.c b/combine-diff.c
index ba8baca0ab..29d0c9cf95 100644
--- a/combine-diff.c
+++ b/combine-diff.c
@@ -7,13 +7,6 @@
#include "xdiff-interface.h"
#include "log-tree.h"
-static int uninteresting(struct diff_filepair *p)
-{
- if (diff_unmodified_pair(p))
- return 1;
- return 0;
-}
-
static struct combine_diff_path *intersect_paths(struct combine_diff_path *curr, int n, int num_parent)
{
struct diff_queue_struct *q = &diff_queued_diff;
@@ -25,7 +18,7 @@ static struct combine_diff_path *intersect_paths(struct combine_diff_path *curr,
for (i = 0; i < q->nr; i++) {
int len;
const char *path;
- if (uninteresting(q->queue[i]))
+ if (diff_unmodified_pair(q->queue[i]))
continue;
path = q->queue[i]->two->path;
len = strlen(path);
@@ -38,9 +31,9 @@ static struct combine_diff_path *intersect_paths(struct combine_diff_path *curr,
memset(p->parent, 0,
sizeof(p->parent[0]) * num_parent);
- memcpy(p->sha1, q->queue[i]->two->sha1, 20);
+ hashcpy(p->sha1, q->queue[i]->two->sha1);
p->mode = q->queue[i]->two->mode;
- memcpy(p->parent[n].sha1, q->queue[i]->one->sha1, 20);
+ hashcpy(p->parent[n].sha1, q->queue[i]->one->sha1);
p->parent[n].mode = q->queue[i]->one->mode;
p->parent[n].status = q->queue[i]->status;
*tail = p;
@@ -57,14 +50,13 @@ static struct combine_diff_path *intersect_paths(struct combine_diff_path *curr,
const char *path;
int len;
- if (uninteresting(q->queue[i]))
+ if (diff_unmodified_pair(q->queue[i]))
continue;
path = q->queue[i]->two->path;
len = strlen(path);
if (len == p->len && !memcmp(path, p->path, len)) {
found = 1;
- memcpy(p->parent[n].sha1,
- q->queue[i]->one->sha1, 20);
+ hashcpy(p->parent[n].sha1, q->queue[i]->one->sha1);
p->parent[n].mode = q->queue[i]->one->mode;
p->parent[n].status = q->queue[i]->status;
break;
@@ -101,7 +93,7 @@ static char *grab_blob(const unsigned char *sha1, unsigned long *size)
{
char *blob;
char type[20];
- if (!memcmp(sha1, null_sha1, 20)) {
+ if (is_null_sha1(sha1)) {
/* deleted blob */
*size = 0;
return xcalloc(1, 1);
@@ -497,6 +489,16 @@ static void show_parent_lno(struct sline *sline, unsigned long l0, unsigned long
printf(" -%lu,%lu", l0, l1-l0);
}
+static int hunk_comment_line(const char *bol)
+{
+ int ch;
+
+ if (!bol)
+ return 0;
+ ch = *bol & 0xff;
+ return (isalpha(ch) || ch == '_' || ch == '$');
+}
+
static void dump_sline(struct sline *sline, unsigned long cnt, int num_parent,
int use_color)
{
@@ -516,8 +518,13 @@ static void dump_sline(struct sline *sline, unsigned long cnt, int num_parent,
struct sline *sl = &sline[lno];
unsigned long hunk_end;
unsigned long rlines;
- while (lno <= cnt && !(sline[lno].flag & mark))
+ const char *hunk_comment = NULL;
+
+ while (lno <= cnt && !(sline[lno].flag & mark)) {
+ if (hunk_comment_line(sline[lno].bol))
+ hunk_comment = sline[lno].bol;
lno++;
+ }
if (cnt < lno)
break;
else {
@@ -534,6 +541,22 @@ static void dump_sline(struct sline *sline, unsigned long cnt, int num_parent,
show_parent_lno(sline, lno, hunk_end, i);
printf(" +%lu,%lu ", lno+1, rlines);
for (i = 0; i <= num_parent; i++) putchar(combine_marker);
+
+ if (hunk_comment) {
+ int comment_end = 0;
+ for (i = 0; i < 40; i++) {
+ int ch = hunk_comment[i] & 0xff;
+ if (!ch || ch == '\n')
+ break;
+ if (!isspace(ch))
+ comment_end = i;
+ }
+ if (comment_end)
+ putchar(' ');
+ for (i = 0; i < comment_end; i++)
+ putchar(hunk_comment[i]);
+ }
+
printf("%s\n", c_reset);
while (lno < hunk_end) {
struct lline *ll;
@@ -609,16 +632,16 @@ static void dump_quoted_path(const char *prefix, const char *path,
printf("%s\n", c_reset);
}
-static int show_patch_diff(struct combine_diff_path *elem, int num_parent,
- int dense, struct rev_info *rev)
+static void show_patch_diff(struct combine_diff_path *elem, int num_parent,
+ int dense, struct rev_info *rev)
{
struct diff_options *opt = &rev->diffopt;
unsigned long result_size, cnt, lno;
char *result, *cp;
struct sline *sline; /* survived lines */
int mode_differs = 0;
- int i, show_hunks, shown_header = 0;
- int working_tree_file = !memcmp(elem->sha1, null_sha1, 20);
+ int i, show_hunks;
+ int working_tree_file = is_null_sha1(elem->sha1);
int abbrev = opt->full_index ? 40 : DEFAULT_ABBREV;
mmfile_t result_file;
@@ -695,8 +718,8 @@ static int show_patch_diff(struct combine_diff_path *elem, int num_parent,
for (i = 0; i < num_parent; i++) {
int j;
for (j = 0; j < i; j++) {
- if (!memcmp(elem->parent[i].sha1,
- elem->parent[j].sha1, 20)) {
+ if (!hashcmp(elem->parent[i].sha1,
+ elem->parent[j].sha1)) {
reuse_combine_diff(sline, cnt, i, j);
break;
}
@@ -715,8 +738,10 @@ static int show_patch_diff(struct combine_diff_path *elem, int num_parent,
int use_color = opt->color_diff;
const char *c_meta = diff_get_color(use_color, DIFF_METAINFO);
const char *c_reset = diff_get_color(use_color, DIFF_RESET);
+ int added = 0;
+ int deleted = 0;
- if (rev->loginfo)
+ if (rev->loginfo && !rev->no_commit_id)
show_log(rev, opt->msg_sep);
dump_quoted_path(dense ? "diff --cc " : "diff --combined ",
elem->path, c_meta, c_reset);
@@ -730,7 +755,10 @@ static int show_patch_diff(struct combine_diff_path *elem, int num_parent,
printf("..%s%s\n", abb, c_reset);
if (mode_differs) {
- int added = !!elem->mode;
+ deleted = !elem->mode;
+
+ /* We say it was added if nobody had it */
+ added = !deleted;
for (i = 0; added && i < num_parent; i++)
if (elem->parent[i].status !=
DIFF_STATUS_ADDED)
@@ -739,7 +767,7 @@ static int show_patch_diff(struct combine_diff_path *elem, int num_parent,
printf("%snew file mode %06o",
c_meta, elem->mode);
else {
- if (!elem->mode)
+ if (deleted)
printf("%sdeleted file ", c_meta);
printf("mode ");
for (i = 0; i < num_parent; i++) {
@@ -751,8 +779,14 @@ static int show_patch_diff(struct combine_diff_path *elem, int num_parent,
}
printf("%s\n", c_reset);
}
- dump_quoted_path("--- a/", elem->path, c_meta, c_reset);
- dump_quoted_path("+++ b/", elem->path, c_meta, c_reset);
+ if (added)
+ dump_quoted_path("--- /dev/", "null", c_meta, c_reset);
+ else
+ dump_quoted_path("--- a/", elem->path, c_meta, c_reset);
+ if (deleted)
+ dump_quoted_path("+++ /dev/", "null", c_meta, c_reset);
+ else
+ dump_quoted_path("+++ b/", elem->path, c_meta, c_reset);
dump_sline(sline, cnt, num_parent, opt->color_diff);
}
free(result);
@@ -769,7 +803,6 @@ static int show_patch_diff(struct combine_diff_path *elem, int num_parent,
}
free(sline[0].p_lno);
free(sline);
- return shown_header;
}
#define COLONS "::::::::::::::::::::::::::::::::"
@@ -786,7 +819,7 @@ static void show_raw_diff(struct combine_diff_path *p, int num_parent, struct re
if (!line_termination)
inter_name_termination = 0;
- if (rev->loginfo)
+ if (rev->loginfo && !rev->no_commit_id)
show_log(rev, opt->msg_sep);
if (opt->output_format & DIFF_FORMAT_RAW) {
@@ -837,11 +870,10 @@ void show_combined_diff(struct combine_diff_path *p,
return;
if (opt->output_format & (DIFF_FORMAT_RAW |
DIFF_FORMAT_NAME |
- DIFF_FORMAT_NAME_STATUS)) {
+ DIFF_FORMAT_NAME_STATUS))
show_raw_diff(p, num_parent, rev);
- } else if (opt->output_format & DIFF_FORMAT_PATCH) {
+ else if (opt->output_format & DIFF_FORMAT_PATCH)
show_patch_diff(p, num_parent, dense, rev);
- }
}
void diff_tree_combined(const unsigned char *sha1,
@@ -859,15 +891,17 @@ void diff_tree_combined(const unsigned char *sha1,
diffopts.output_format = DIFF_FORMAT_NO_OUTPUT;
diffopts.recursive = 1;
- show_log_first = !!rev->loginfo;
+ show_log_first = !!rev->loginfo && !rev->no_commit_id;
needsep = 0;
/* find set of paths that everybody touches */
for (i = 0; i < num_parent; i++) {
/* show stat against the first parent even
* when doing combined diff.
*/
- if (i == 0 && opt->output_format & DIFF_FORMAT_DIFFSTAT)
- diffopts.output_format = DIFF_FORMAT_DIFFSTAT;
+ int stat_opt = (opt->output_format &
+ (DIFF_FORMAT_NUMSTAT|DIFF_FORMAT_DIFFSTAT));
+ if (i == 0 && stat_opt)
+ diffopts.output_format = stat_opt;
else
diffopts.output_format = DIFF_FORMAT_NO_OUTPUT;
diff_tree_sha1(parent[i], sha1, "", &diffopts);
@@ -897,7 +931,8 @@ void diff_tree_combined(const unsigned char *sha1,
}
needsep = 1;
}
- else if (opt->output_format & DIFF_FORMAT_DIFFSTAT)
+ else if (opt->output_format &
+ (DIFF_FORMAT_NUMSTAT|DIFF_FORMAT_DIFFSTAT))
needsep = 1;
if (opt->output_format & DIFF_FORMAT_PATCH) {
if (needsep)
@@ -936,6 +971,7 @@ void diff_tree_combined_merge(const unsigned char *sha1,
for (parents = commit->parents, num_parent = 0;
parents;
parents = parents->next, num_parent++)
- memcpy(parent + num_parent, parents->item->object.sha1, 20);
+ hashcpy((unsigned char*)(parent + num_parent),
+ parents->item->object.sha1);
diff_tree_combined(sha1, parent, num_parent, dense, rev);
}
diff --git a/commit.c b/commit.c
index 77f0ca175c..a6d543eee7 100644
--- a/commit.c
+++ b/commit.c
@@ -7,15 +7,15 @@ int save_commit_buffer = 1;
struct sort_node
{
/*
- * the number of children of the associated commit
- * that also occur in the list being sorted.
- */
+ * the number of children of the associated commit
+ * that also occur in the list being sorted.
+ */
unsigned int indegree;
/*
- * reference to original list item that we will re-use
- * on output.
- */
+ * reference to original list item that we will re-use
+ * on output.
+ */
struct commit_list * list_item;
};
@@ -123,7 +123,7 @@ static int commit_graft_pos(const unsigned char *sha1)
while (lo < hi) {
int mi = (lo + hi) / 2;
struct commit_graft *graft = commit_graft[mi];
- int cmp = memcmp(sha1, graft->sha1, 20);
+ int cmp = hashcmp(sha1, graft->sha1);
if (!cmp)
return mi;
if (cmp < 0)
@@ -467,7 +467,8 @@ static int add_rfc2047(char *buf, const char *line, int len)
return bp - buf;
}
-static int add_user_info(const char *what, enum cmit_fmt fmt, char *buf, const char *line)
+static int add_user_info(const char *what, enum cmit_fmt fmt, char *buf,
+ const char *line, int relative_date)
{
char *date;
int namelen;
@@ -507,14 +508,16 @@ static int add_user_info(const char *what, enum cmit_fmt fmt, char *buf, const c
}
switch (fmt) {
case CMIT_FMT_MEDIUM:
- ret += sprintf(buf + ret, "Date: %s\n", show_date(time, tz));
+ ret += sprintf(buf + ret, "Date: %s\n",
+ show_date(time, tz, relative_date));
break;
case CMIT_FMT_EMAIL:
ret += sprintf(buf + ret, "Date: %s\n",
show_rfc2822_date(time, tz));
break;
case CMIT_FMT_FULLER:
- ret += sprintf(buf + ret, "%sDate: %s\n", what, show_date(time, tz));
+ ret += sprintf(buf + ret, "%sDate: %s\n", what,
+ show_date(time, tz, relative_date));
break;
default:
/* notin' */
@@ -545,10 +548,13 @@ static int add_merge_info(enum cmit_fmt fmt, char *buf, const struct commit *com
while (parent) {
struct commit *p = parent->item;
- const char *hex = abbrev
- ? find_unique_abbrev(p->object.sha1, abbrev)
- : sha1_to_hex(p->object.sha1);
- const char *dots = (abbrev && strlen(hex) != 40) ? "..." : "";
+ const char *hex = NULL;
+ const char *dots;
+ if (abbrev)
+ hex = find_unique_abbrev(p->object.sha1, abbrev);
+ if (!hex)
+ hex = sha1_to_hex(p->object.sha1);
+ dots = (abbrev && strlen(hex) != 40) ? "..." : "";
parent = parent->next;
offset += sprintf(buf + offset, " %s%s", hex, dots);
@@ -557,7 +563,10 @@ static int add_merge_info(enum cmit_fmt fmt, char *buf, const struct commit *com
return offset;
}
-unsigned long pretty_print_commit(enum cmit_fmt fmt, const struct commit *commit, unsigned long len, char *buf, unsigned long space, int abbrev, const char *subject, const char *after_subject)
+unsigned long pretty_print_commit(enum cmit_fmt fmt, const struct commit *commit,
+ unsigned long len, char *buf, unsigned long space,
+ int abbrev, const char *subject,
+ const char *after_subject, int relative_date)
{
int hdr = 1, body = 0;
unsigned long offset = 0;
@@ -646,12 +655,14 @@ unsigned long pretty_print_commit(enum cmit_fmt fmt, const struct commit *commit
if (!memcmp(line, "author ", 7))
offset += add_user_info("Author", fmt,
buf + offset,
- line + 7);
+ line + 7,
+ relative_date);
if (!memcmp(line, "committer ", 10) &&
(fmt == CMIT_FMT_FULL || fmt == CMIT_FMT_FULLER))
offset += add_user_info("Commit", fmt,
buf + offset,
- line + 10);
+ line + 10,
+ relative_date);
continue;
}
@@ -727,10 +738,10 @@ struct commit *pop_commit(struct commit_list **stack)
int count_parents(struct commit * commit)
{
- int count = 0;
+ int count;
struct commit_list * parents = commit->parents;
- for (count=0;parents; parents=parents->next,count++)
- ;
+ for (count = 0; parents; parents = parents->next,count++)
+ ;
return count;
}
diff --git a/commit.h b/commit.h
index 779ed82ed0..fc13de9780 100644
--- a/commit.h
+++ b/commit.h
@@ -52,7 +52,7 @@ enum cmit_fmt {
};
extern enum cmit_fmt get_commit_format(const char *arg);
-extern unsigned long pretty_print_commit(enum cmit_fmt fmt, const struct commit *, unsigned long len, char *buf, unsigned long space, int abbrev, const char *subject, const char *after_subject);
+extern unsigned long pretty_print_commit(enum cmit_fmt fmt, const struct commit *, unsigned long len, char *buf, unsigned long space, int abbrev, const char *subject, const char *after_subject, int relative_date);
/** Removes the first commit from a list sorted by date, and adds all
* of its parents.
diff --git a/compat/inet_pton.c b/compat/inet_pton.c
new file mode 100644
index 0000000000..5704e0d2b6
--- /dev/null
+++ b/compat/inet_pton.c
@@ -0,0 +1,220 @@
+/*
+ * Copyright (C) 1996-2001 Internet Software Consortium.
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM
+ * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+ * INTERNET SOFTWARE CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
+ * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
+ * FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+ * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
+ * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <stdio.h>
+#include <string.h>
+
+#ifndef NS_INT16SZ
+#define NS_INT16SZ 2
+#endif
+
+#ifndef NS_INADDRSZ
+#define NS_INADDRSZ 4
+#endif
+
+#ifndef NS_IN6ADDRSZ
+#define NS_IN6ADDRSZ 16
+#endif
+
+/*
+ * WARNING: Don't even consider trying to compile this on a system where
+ * sizeof(int) < 4. sizeof(int) > 4 is fine; all the world's not a VAX.
+ */
+
+static int inet_pton4(const char *src, unsigned char *dst);
+static int inet_pton6(const char *src, unsigned char *dst);
+
+/* int
+ * inet_pton4(src, dst)
+ * like inet_aton() but without all the hexadecimal and shorthand.
+ * return:
+ * 1 if `src' is a valid dotted quad, else 0.
+ * notice:
+ * does not touch `dst' unless it's returning 1.
+ * author:
+ * Paul Vixie, 1996.
+ */
+static int
+inet_pton4(const char *src, unsigned char *dst)
+{
+ static const char digits[] = "0123456789";
+ int saw_digit, octets, ch;
+ unsigned char tmp[NS_INADDRSZ], *tp;
+
+ saw_digit = 0;
+ octets = 0;
+ *(tp = tmp) = 0;
+ while ((ch = *src++) != '\0') {
+ const char *pch;
+
+ if ((pch = strchr(digits, ch)) != NULL) {
+ unsigned int new = *tp * 10 + (pch - digits);
+
+ if (new > 255)
+ return (0);
+ *tp = new;
+ if (! saw_digit) {
+ if (++octets > 4)
+ return (0);
+ saw_digit = 1;
+ }
+ } else if (ch == '.' && saw_digit) {
+ if (octets == 4)
+ return (0);
+ *++tp = 0;
+ saw_digit = 0;
+ } else
+ return (0);
+ }
+ if (octets < 4)
+ return (0);
+ memcpy(dst, tmp, NS_INADDRSZ);
+ return (1);
+}
+
+/* int
+ * inet_pton6(src, dst)
+ * convert presentation level address to network order binary form.
+ * return:
+ * 1 if `src' is a valid [RFC1884 2.2] address, else 0.
+ * notice:
+ * (1) does not touch `dst' unless it's returning 1.
+ * (2) :: in a full address is silently ignored.
+ * credit:
+ * inspired by Mark Andrews.
+ * author:
+ * Paul Vixie, 1996.
+ */
+
+#ifndef NO_IPV6
+static int
+inet_pton6(const char *src, unsigned char *dst)
+{
+ static const char xdigits_l[] = "0123456789abcdef",
+ xdigits_u[] = "0123456789ABCDEF";
+ unsigned char tmp[NS_IN6ADDRSZ], *tp, *endp, *colonp;
+ const char *xdigits, *curtok;
+ int ch, saw_xdigit;
+ unsigned int val;
+
+ memset((tp = tmp), '\0', NS_IN6ADDRSZ);
+ endp = tp + NS_IN6ADDRSZ;
+ colonp = NULL;
+ /* Leading :: requires some special handling. */
+ if (*src == ':')
+ if (*++src != ':')
+ return (0);
+ curtok = src;
+ saw_xdigit = 0;
+ val = 0;
+ while ((ch = *src++) != '\0') {
+ const char *pch;
+
+ if ((pch = strchr((xdigits = xdigits_l), ch)) == NULL)
+ pch = strchr((xdigits = xdigits_u), ch);
+ if (pch != NULL) {
+ val <<= 4;
+ val |= (pch - xdigits);
+ if (val > 0xffff)
+ return (0);
+ saw_xdigit = 1;
+ continue;
+ }
+ if (ch == ':') {
+ curtok = src;
+ if (!saw_xdigit) {
+ if (colonp)
+ return (0);
+ colonp = tp;
+ continue;
+ }
+ if (tp + NS_INT16SZ > endp)
+ return (0);
+ *tp++ = (unsigned char) (val >> 8) & 0xff;
+ *tp++ = (unsigned char) val & 0xff;
+ saw_xdigit = 0;
+ val = 0;
+ continue;
+ }
+ if (ch == '.' && ((tp + NS_INADDRSZ) <= endp) &&
+ inet_pton4(curtok, tp) > 0) {
+ tp += NS_INADDRSZ;
+ saw_xdigit = 0;
+ break; /* '\0' was seen by inet_pton4(). */
+ }
+ return (0);
+ }
+ if (saw_xdigit) {
+ if (tp + NS_INT16SZ > endp)
+ return (0);
+ *tp++ = (unsigned char) (val >> 8) & 0xff;
+ *tp++ = (unsigned char) val & 0xff;
+ }
+ if (colonp != NULL) {
+ /*
+ * Since some memmove()'s erroneously fail to handle
+ * overlapping regions, we'll do the shift by hand.
+ */
+ const int n = tp - colonp;
+ int i;
+
+ for (i = 1; i <= n; i++) {
+ endp[- i] = colonp[n - i];
+ colonp[n - i] = 0;
+ }
+ tp = endp;
+ }
+ if (tp != endp)
+ return (0);
+ memcpy(dst, tmp, NS_IN6ADDRSZ);
+ return (1);
+}
+#endif
+
+/* int
+ * isc_net_pton(af, src, dst)
+ * convert from presentation format (which usually means ASCII printable)
+ * to network format (which is usually some kind of binary format).
+ * return:
+ * 1 if the address was valid for the specified address family
+ * 0 if the address wasn't valid (`dst' is untouched in this case)
+ * -1 if some other error occurred (`dst' is untouched in this case, too)
+ * author:
+ * Paul Vixie, 1996.
+ */
+int
+inet_pton(int af, const char *src, void *dst)
+{
+ switch (af) {
+ case AF_INET:
+ return (inet_pton4(src, dst));
+#ifndef NO_IPV6
+ case AF_INET6:
+ return (inet_pton6(src, dst));
+#endif
+ default:
+ errno = EAFNOSUPPORT;
+ return (-1);
+ }
+ /* NOTREACHED */
+}
diff --git a/compat/mmap.c b/compat/mmap.c
index 55cb120764..a4d2e507f7 100644
--- a/compat/mmap.c
+++ b/compat/mmap.c
@@ -7,6 +7,7 @@
void *gitfakemmap(void *start, size_t length, int prot , int flags, int fd, off_t offset)
{
int n = 0;
+ off_t current_offset = lseek(fd, 0, SEEK_CUR);
if (start != NULL || !(flags & MAP_PRIVATE))
die("Invalid usage of gitfakemmap.");
@@ -39,6 +40,11 @@ void *gitfakemmap(void *start, size_t length, int prot , int flags, int fd, off_
n += count;
}
+ if (current_offset != lseek(fd, current_offset, SEEK_SET)) {
+ errno = EINVAL;
+ return MAP_FAILED;
+ }
+
return start;
}
diff --git a/config.c b/config.c
index 82b3562454..3cae3901aa 100644
--- a/config.c
+++ b/config.c
@@ -103,6 +103,11 @@ static char *parse_value(void)
}
}
+static inline int iskeychar(int c)
+{
+ return isalnum(c) || c == '-';
+}
+
static int get_value(config_fn_t fn, char *name, unsigned int len)
{
int c;
@@ -113,7 +118,7 @@ static int get_value(config_fn_t fn, char *name, unsigned int len)
c = get_next_char();
if (c == EOF)
break;
- if (!isalnum(c))
+ if (!iskeychar(c))
break;
name[len++] = tolower(c);
if (len >= MAXNAME)
@@ -181,7 +186,7 @@ static int get_base_var(char *name)
return baselen;
if (isspace(c))
return get_extended_base_var(name, baselen, c);
- if (!isalnum(c) && c != '.')
+ if (!iskeychar(c) && c != '.')
return -1;
if (baselen > MAXNAME / 2)
return -1;
@@ -350,19 +355,18 @@ int git_config(config_fn_t fn)
home = getenv("HOME");
filename = getenv("GIT_CONFIG_LOCAL");
if (!filename)
- filename = repo_config = strdup(git_path("config"));
+ filename = repo_config = xstrdup(git_path("config"));
}
if (home) {
- char *user_config = strdup(mkpath("%s/.gitconfig", home));
+ char *user_config = xstrdup(mkpath("%s/.gitconfig", home));
if (!access(user_config, R_OK))
ret = git_config_from_file(fn, user_config);
free(user_config);
}
ret += git_config_from_file(fn, filename);
- if (repo_config)
- free(repo_config);
+ free(repo_config);
return ret;
}
@@ -546,8 +550,8 @@ int git_config_set_multivar(const char* key, const char* value,
if (!config_filename)
config_filename = git_path("config");
}
- config_filename = strdup(config_filename);
- lock_file = strdup(mkpath("%s.lock", config_filename));
+ config_filename = xstrdup(config_filename);
+ lock_file = xstrdup(mkpath("%s.lock", config_filename));
/*
* Since "key" actually contains the section name and the real
@@ -566,7 +570,7 @@ int git_config_set_multivar(const char* key, const char* value,
/*
* Validate the key and while at it, lower case it for matching.
*/
- store.key = (char*)malloc(strlen(key)+1);
+ store.key = xmalloc(strlen(key) + 1);
dot = 0;
for (i = 0; key[i]; i++) {
unsigned char c = key[i];
@@ -574,7 +578,7 @@ int git_config_set_multivar(const char* key, const char* value,
dot = 1;
/* Leave the extended basename untouched.. */
if (!dot || i > store.baselen) {
- if (!isalnum(c) || (i == store.baselen+1 && !isalpha(c))) {
+ if (!iskeychar(c) || (i == store.baselen+1 && !isalpha(c))) {
fprintf(stderr, "invalid key: %s\n", key);
free(store.key);
ret = 1;
@@ -634,7 +638,7 @@ int git_config_set_multivar(const char* key, const char* value,
} else
store.do_not_match = 0;
- store.value_regex = (regex_t*)malloc(sizeof(regex_t));
+ store.value_regex = (regex_t*)xmalloc(sizeof(regex_t));
if (regcomp(store.value_regex, value_regex,
REG_EXTENDED)) {
fprintf(stderr, "Invalid pattern: %s\n",
@@ -734,8 +738,7 @@ int git_config_set_multivar(const char* key, const char* value,
out_free:
if (0 <= fd)
close(fd);
- if (config_filename)
- free(config_filename);
+ free(config_filename);
if (lock_file) {
unlink(lock_file);
free(lock_file);
diff --git a/config.mak.in b/config.mak.in
index 369e6116e0..1cafa19ed4 100644
--- a/config.mak.in
+++ b/config.mak.in
@@ -2,6 +2,7 @@
# @configure_input@
CC = @CC@
+CFLAGS = @CFLAGS@
AR = @AR@
TAR = @TAR@
#INSTALL = @INSTALL@ # needs install-sh or install.sh in sources
@@ -37,4 +38,5 @@ NO_C99_FORMAT=@NO_C99_FORMAT@
NO_STRCASESTR=@NO_STRCASESTR@
NO_STRLCPY=@NO_STRLCPY@
NO_SETENV=@NO_SETENV@
+NO_ICONV=@NO_ICONV@
diff --git a/configure.ac b/configure.ac
index e890131c46..cff5722eb9 100644
--- a/configure.ac
+++ b/configure.ac
@@ -75,7 +75,6 @@ GIT_ARG_SET_PATH(shell)
# Define PERL_PATH to provide path to Perl.
GIT_ARG_SET_PATH(perl)
#
-# Define NO_PYTHON if you want to lose all benefits of the recursive merge.
# Define PYTHON_PATH to provide path to Python.
AC_ARG_WITH(python,[AS_HELP_STRING([--with-python=PATH], [provide PATH to python])
AS_HELP_STRING([--without-python], [don't use python scripts])],
@@ -95,12 +94,11 @@ AC_SUBST(PYTHON_PATH)
## Checks for programs.
AC_MSG_NOTICE([CHECKS for programs])
#
-AC_PROG_CC
+AC_PROG_CC([cc gcc])
#AC_PROG_INSTALL # needs install-sh or install.sh in sources
AC_CHECK_TOOL(AR, ar, :)
AC_CHECK_PROGS(TAR, [gtar tar])
#
-# Define NO_PYTHON if you want to lose all benefits of the recursive merge.
# Define PYTHON_PATH to provide path to Python.
if test -z "$NO_PYTHON"; then
if test -z "$PYTHON_PATH"; then
@@ -143,10 +141,15 @@ AC_CHECK_LIB([expat], [XML_ParserCreate],
AC_SUBST(NO_EXPAT)
#
# Define NEEDS_LIBICONV if linking with libc is not enough (Darwin).
+# Define NO_ICONV if neither libc nor libiconv support iconv.
AC_CHECK_LIB([c], [iconv],
-[NEEDS_LIBICONV=],
-[NEEDS_LIBICONV=YesPlease])
+ [NEEDS_LIBICONV=],
+ AC_CHECK_LIB([iconv], [iconv],
+ [NEEDS_LIBICONV=YesPlease],
+ [NO_ICONV=YesPlease]))
AC_SUBST(NEEDS_LIBICONV)
+AC_SUBST(NO_ICONV)
+test -n "$NEEDS_LIBICONV" && LIBS="$LIBS -liconv"
#
# Define NEEDS_SOCKET if linking with libc is not enough (SunOS,
# Patrick Mauritz).
@@ -154,6 +157,7 @@ AC_CHECK_LIB([c], [socket],
[NEEDS_SOCKET=],
[NEEDS_SOCKET=YesPlease])
AC_SUBST(NEEDS_SOCKET)
+test -n "$NEEDS_SOCKET" && LIBS="$LIBS -lsocket"
## Checks for header files.
@@ -181,8 +185,10 @@ AC_SUBST(NO_D_TYPE_IN_DIRENT)
# sockaddr_storage.
AC_CHECK_TYPE(struct sockaddr_storage,
[NO_SOCKADDR_STORAGE=],
-[NO_SOCKADDR_STORAGE=YesPlease],
-[#include <netinet/in.h>])
+[NO_SOCKADDR_STORAGE=YesPlease],[
+#include <sys/types.h>
+#include <sys/socket.h>
+])
AC_SUBST(NO_SOCKADDR_STORAGE)
#
# Define NO_IPV6 if you lack IPv6 support and getaddrinfo().
@@ -201,8 +207,8 @@ AC_SUBST(NO_IPV6)
# do not support the 'size specifiers' introduced by C99, namely ll, hh,
# j, z, t. (representing long long int, char, intmax_t, size_t, ptrdiff_t).
# some C compilers supported these specifiers prior to C99 as an extension.
-AC_CACHE_CHECK(whether formatted IO functions support C99 size specifiers,
- ac_cv_c_c99_format,
+AC_CACHE_CHECK([whether formatted IO functions support C99 size specifiers],
+ [ac_cv_c_c99_format],
[# Actually git uses only %z (%zu) in alloc.c, and %t (%td) in mktag.c
AC_RUN_IFELSE(
[AC_LANG_PROGRAM([AC_INCLUDES_DEFAULT],
@@ -257,6 +263,16 @@ AC_SUBST(NO_SETENV)
# Enable it on Windows. By default, symrefs are still used.
#
# Define WITH_OWN_SUBPROCESS_PY if you want to use with python 2.3.
+AC_CACHE_CHECK([for subprocess.py],
+ [ac_cv_python_has_subprocess_py],
+[if $PYTHON_PATH -c 'import subprocess' 2>/dev/null; then
+ ac_cv_python_has_subprocess_py=yes
+else
+ ac_cv_python_has_subprocess_py=no
+fi])
+if test $ac_cv_python_has_subprocess_py != yes; then
+ GIT_CONF_APPEND_LINE([WITH_OWN_SUBPROCESS_PY=YesPlease])
+fi
#
# Define NO_ACCURATE_DIFF if your diff program at least sometimes misses
# a missing newline at the end of the file.
@@ -326,6 +342,16 @@ GIT_PARSE_WITH(expat))
# library directories by defining CFLAGS and LDFLAGS appropriately.
#
# Define NO_MMAP if you want to avoid mmap.
+#
+# Define NO_ICONV if your libc does not properly support iconv.
+AC_ARG_WITH(iconv,
+AS_HELP_STRING([--without-iconv],
+[if your architecture doesn't properly support iconv])
+AS_HELP_STRING([--with-iconv=PATH],
+[PATH is prefix for libiconv library and headers])
+AS_HELP_STRING([],
+[used only if you need linking with libiconv]),
+GIT_PARSE_WITH(iconv))
## --enable-FEATURE[=ARG] and --disable-FEATURE
#
diff --git a/connect.c b/connect.c
index 4422a0d8d3..b9666cc0d8 100644
--- a/connect.c
+++ b/connect.c
@@ -10,14 +10,14 @@
#include <netdb.h>
#include <signal.h>
-static char *server_capabilities = NULL;
+static char *server_capabilities;
static int check_ref(const char *name, int len, unsigned int flags)
{
if (!flags)
return 1;
- if (len > 45 || memcmp(name, "refs/", 5))
+ if (len < 5 || memcmp(name, "refs/", 5))
return 0;
/* Skip the "refs/" part */
@@ -69,7 +69,7 @@ struct ref **get_remote_heads(int in, struct ref **list,
if (len != name_len + 41) {
if (server_capabilities)
free(server_capabilities);
- server_capabilities = strdup(name + name_len + 1);
+ server_capabilities = xstrdup(name + name_len + 1);
}
if (!check_ref(name, name_len, flags))
@@ -77,7 +77,7 @@ struct ref **get_remote_heads(int in, struct ref **list,
if (nr_match && !path_match(name, nr_match, match))
continue;
ref = xcalloc(1, sizeof(*ref) + len - 40);
- memcpy(ref->old_sha1, old_sha1, 20);
+ hashcpy(ref->old_sha1, old_sha1);
memcpy(ref->name, buffer + 41, len - 40);
*list = ref;
list = &ref->next;
@@ -174,21 +174,58 @@ static int count_refspec_match(const char *pattern,
struct ref *refs,
struct ref **matched_ref)
{
- int match;
int patlen = strlen(pattern);
+ struct ref *matched_weak = NULL;
+ struct ref *matched = NULL;
+ int weak_match = 0;
+ int match = 0;
- for (match = 0; refs; refs = refs->next) {
+ for (weak_match = match = 0; refs; refs = refs->next) {
char *name = refs->name;
int namelen = strlen(name);
+ int weak_match;
+
if (namelen < patlen ||
memcmp(name + namelen - patlen, pattern, patlen))
continue;
if (namelen != patlen && name[namelen - patlen - 1] != '/')
continue;
- match++;
- *matched_ref = refs;
+
+ /* A match is "weak" if it is with refs outside
+ * heads or tags, and did not specify the pattern
+ * in full (e.g. "refs/remotes/origin/master") or at
+ * least from the toplevel (e.g. "remotes/origin/master");
+ * otherwise "git push $URL master" would result in
+ * ambiguity between remotes/origin/master and heads/master
+ * at the remote site.
+ */
+ if (namelen != patlen &&
+ patlen != namelen - 5 &&
+ strncmp(name, "refs/heads/", 11) &&
+ strncmp(name, "refs/tags/", 10)) {
+ /* We want to catch the case where only weak
+ * matches are found and there are multiple
+ * matches, and where more than one strong
+ * matches are found, as ambiguous. One
+ * strong match with zero or more weak matches
+ * are acceptable as a unique match.
+ */
+ matched_weak = refs;
+ weak_match++;
+ }
+ else {
+ matched = refs;
+ match++;
+ }
+ }
+ if (!matched) {
+ *matched_ref = matched_weak;
+ return weak_match;
+ }
+ else {
+ *matched_ref = matched;
+ return match;
}
- return match;
}
static void link_dst_tail(struct ref *ref, struct ref ***tail)
@@ -208,7 +245,7 @@ static struct ref *try_explicit_object_name(const char *name)
len = strlen(name) + 1;
ref = xcalloc(1, sizeof(*ref) + len);
memcpy(ref->name, name, len);
- memcpy(ref->new_sha1, sha1, 20);
+ hashcpy(ref->new_sha1, sha1);
return ref;
}
@@ -318,7 +355,7 @@ int match_refs(struct ref *src, struct ref *dst, struct ref ***dst_tail,
int len = strlen(src->name) + 1;
dst_peer = xcalloc(1, sizeof(*dst_peer) + len);
memcpy(dst_peer->name, src->name, len);
- memcpy(dst_peer->new_sha1, src->new_sha1, 20);
+ hashcpy(dst_peer->new_sha1, src->new_sha1);
link_dst_tail(dst_peer, dst_tail);
}
dst_peer->peer_ref = src;
@@ -493,8 +530,8 @@ static void git_tcp_connect(int fd[2], char *host)
}
-static char *git_proxy_command = NULL;
-static const char *rhost_name = NULL;
+static char *git_proxy_command;
+static const char *rhost_name;
static int rhost_len;
static int git_proxy_command_options(const char *var, const char *value)
@@ -599,12 +636,19 @@ static void git_proxy_connect(int fd[2], char *host)
close(pipefd[1][0]);
}
+#define MAX_CMD_LEN 1024
+
/*
- * Yeah, yeah, fixme. Need to pass in the heads etc.
+ * This returns 0 if the transport protocol does not need fork(2),
+ * or a process id if it does. Once done, finish the connection
+ * with finish_connect() with the value returned from this function
+ * (it is safe to call finish_connect() with 0 to support the former
+ * case).
+ *
+ * Does not return a negative value on error; it just dies.
*/
-int git_connect(int fd[2], char *url, const char *prog)
+pid_t git_connect(int fd[2], char *url, const char *prog)
{
- char command[1024];
char *host, *path = url;
char *end;
int c;
@@ -661,7 +705,7 @@ int git_connect(int fd[2], char *url, const char *prog)
if (path[1] == '~')
path++;
else {
- path = strdup(ptr);
+ path = xstrdup(ptr);
free_path = 1;
}
@@ -672,7 +716,7 @@ int git_connect(int fd[2], char *url, const char *prog)
/* These underlying connection commands die() if they
* cannot connect.
*/
- char *target_host = strdup(host);
+ char *target_host = xstrdup(host);
if (git_use_proxy(host))
git_proxy_connect(fd, host);
else
@@ -697,8 +741,18 @@ int git_connect(int fd[2], char *url, const char *prog)
if (pid < 0)
die("unable to fork");
if (!pid) {
- snprintf(command, sizeof(command), "%s %s", prog,
- sq_quote(path));
+ char command[MAX_CMD_LEN];
+ char *posn = command;
+ int size = MAX_CMD_LEN;
+ int of = 0;
+
+ of |= add_to_string(&posn, &size, prog, 0);
+ of |= add_to_string(&posn, &size, " ", 0);
+ of |= add_to_string(&posn, &size, path, 1);
+
+ if (of)
+ die("command line too long");
+
dup2(pipefd[1][0], 0);
dup2(pipefd[0][1], 1);
close(pipefd[0][0]);
@@ -737,14 +791,12 @@ int git_connect(int fd[2], char *url, const char *prog)
int finish_connect(pid_t pid)
{
- int ret;
+ if (pid == 0)
+ return 0;
- for (;;) {
- ret = waitpid(pid, NULL, 0);
- if (!ret)
- break;
+ while (waitpid(pid, NULL, 0) < 0) {
if (errno != EINTR)
- break;
+ return -1;
}
- return ret;
+ return 0;
}
diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash
new file mode 100755
index 0000000000..a43a177160
--- /dev/null
+++ b/contrib/completion/git-completion.bash
@@ -0,0 +1,403 @@
+#
+# bash completion support for core Git.
+#
+# Copyright (C) 2006 Shawn Pearce
+# Conceptually based on gitcompletion (http://gitweb.hawaga.org.uk/).
+#
+# The contained completion routines provide support for completing:
+#
+# *) local and remote branch names
+# *) local and remote tag names
+# *) .git/remotes file names
+# *) git 'subcommands'
+# *) tree paths within 'ref:path/to/file' expressions
+#
+# To use these routines:
+#
+# 1) Copy this file to somewhere (e.g. ~/.git-completion.sh).
+# 2) Added the following line to your .bashrc:
+# source ~/.git-completion.sh
+#
+
+__gitdir ()
+{
+ echo "${__git_dir:-$(git rev-parse --git-dir 2>/dev/null)}"
+}
+
+__git_refs ()
+{
+ local cmd i is_hash=y dir="${1:-$(__gitdir)}"
+ if [ -d "$dir" ]; then
+ cmd=git-peek-remote
+ else
+ cmd=git-ls-remote
+ fi
+ for i in $($cmd "$dir" 2>/dev/null); do
+ case "$is_hash,$i" in
+ y,*) is_hash=n ;;
+ n,*^{}) is_hash=y ;;
+ n,refs/tags/*) is_hash=y; echo "${i#refs/tags/}" ;;
+ n,refs/heads/*) is_hash=y; echo "${i#refs/heads/}" ;;
+ n,*) is_hash=y; echo "$i" ;;
+ esac
+ done
+}
+
+__git_refs2 ()
+{
+ local cmd i is_hash=y dir="${1:-$(__gitdir)}"
+ if [ -d "$dir" ]; then
+ cmd=git-peek-remote
+ else
+ cmd=git-ls-remote
+ fi
+ for i in $($cmd "$dir" 2>/dev/null); do
+ case "$is_hash,$i" in
+ y,*) is_hash=n ;;
+ n,*^{}) is_hash=y ;;
+ n,refs/tags/*) is_hash=y; echo "${i#refs/tags/}:${i#refs/tags/}" ;;
+ n,refs/heads/*) is_hash=y; echo "${i#refs/heads/}:${i#refs/heads/}" ;;
+ n,*) is_hash=y; echo "$i:$i" ;;
+ esac
+ done
+}
+
+__git_remotes ()
+{
+ local i ngoff IFS=$'\n' d="$(__gitdir)"
+ shopt -q nullglob || ngoff=1
+ shopt -s nullglob
+ for i in "$d/remotes"/*; do
+ echo ${i#$d/remotes/}
+ done
+ [ "$ngoff" ] && shopt -u nullglob
+ for i in $(git --git-dir="$d" repo-config --list); do
+ case "$i" in
+ remote.*.url=*)
+ i="${i#remote.}"
+ echo "${i/.url=*/}"
+ ;;
+ esac
+ done
+}
+
+__git_complete_file ()
+{
+ local pfx ls ref cur="${COMP_WORDS[COMP_CWORD]}"
+ case "$cur" in
+ ?*:*)
+ ref="${cur%%:*}"
+ cur="${cur#*:}"
+ case "$cur" in
+ ?*/*)
+ pfx="${cur%/*}"
+ cur="${cur##*/}"
+ ls="$ref:$pfx"
+ pfx="$pfx/"
+ ;;
+ *)
+ ls="$ref"
+ ;;
+ esac
+ COMPREPLY=($(compgen -P "$pfx" \
+ -W "$(git --git-dir="$(__gitdir)" ls-tree "$ls" \
+ | sed '/^100... blob /s,^.* ,,
+ /^040000 tree /{
+ s,^.* ,,
+ s,$,/,
+ }
+ s/^.* //')" \
+ -- "$cur"))
+ ;;
+ *)
+ COMPREPLY=($(compgen -W "$(__git_refs)" -- "$cur"))
+ ;;
+ esac
+}
+
+__git_aliases ()
+{
+ local i IFS=$'\n'
+ for i in $(git --git-dir="$(__gitdir)" repo-config --list); do
+ case "$i" in
+ alias.*)
+ i="${i#alias.}"
+ echo "${i/=*/}"
+ ;;
+ esac
+ done
+}
+
+__git_aliased_command ()
+{
+ local word cmdline=$(git --git-dir="$(__gitdir)" \
+ repo-config --get "alias.$1")
+ for word in $cmdline; do
+ if [ "${word##-*}" ]; then
+ echo $word
+ return
+ fi
+ done
+}
+
+_git_branch ()
+{
+ local cur="${COMP_WORDS[COMP_CWORD]}"
+ COMPREPLY=($(compgen -W "-l -f -d -D $(__git_refs)" -- "$cur"))
+}
+
+_git_cat_file ()
+{
+ local cur="${COMP_WORDS[COMP_CWORD]}"
+ case "${COMP_WORDS[0]},$COMP_CWORD" in
+ git-cat-file*,1)
+ COMPREPLY=($(compgen -W "-p -t blob tree commit tag" -- "$cur"))
+ ;;
+ git,2)
+ COMPREPLY=($(compgen -W "-p -t blob tree commit tag" -- "$cur"))
+ ;;
+ *)
+ __git_complete_file
+ ;;
+ esac
+}
+
+_git_checkout ()
+{
+ local cur="${COMP_WORDS[COMP_CWORD]}"
+ COMPREPLY=($(compgen -W "-l -b $(__git_refs)" -- "$cur"))
+}
+
+_git_diff ()
+{
+ __git_complete_file
+}
+
+_git_diff_tree ()
+{
+ local cur="${COMP_WORDS[COMP_CWORD]}"
+ COMPREPLY=($(compgen -W "-r -p -M $(__git_refs)" -- "$cur"))
+}
+
+_git_fetch ()
+{
+ local cur="${COMP_WORDS[COMP_CWORD]}"
+
+ case "${COMP_WORDS[0]},$COMP_CWORD" in
+ git-fetch*,1)
+ COMPREPLY=($(compgen -W "$(__git_remotes)" -- "$cur"))
+ ;;
+ git,2)
+ COMPREPLY=($(compgen -W "$(__git_remotes)" -- "$cur"))
+ ;;
+ *)
+ case "$cur" in
+ *:*)
+ cur="${cur#*:}"
+ COMPREPLY=($(compgen -W "$(__git_refs)" -- "$cur"))
+ ;;
+ *)
+ local remote
+ case "${COMP_WORDS[0]}" in
+ git-fetch) remote="${COMP_WORDS[1]}" ;;
+ git) remote="${COMP_WORDS[2]}" ;;
+ esac
+ COMPREPLY=($(compgen -W "$(__git_refs2 "$remote")" -- "$cur"))
+ ;;
+ esac
+ ;;
+ esac
+}
+
+_git_ls_remote ()
+{
+ local cur="${COMP_WORDS[COMP_CWORD]}"
+ COMPREPLY=($(compgen -W "$(__git_remotes)" -- "$cur"))
+}
+
+_git_ls_tree ()
+{
+ __git_complete_file
+}
+
+_git_log ()
+{
+ local pfx cur="${COMP_WORDS[COMP_CWORD]}"
+ case "$cur" in
+ *...*)
+ pfx="${cur%...*}..."
+ cur="${cur#*...}"
+ COMPREPLY=($(compgen -P "$pfx" -W "$(__git_refs)" -- "$cur"))
+ ;;
+ *..*)
+ pfx="${cur%..*}.."
+ cur="${cur#*..}"
+ COMPREPLY=($(compgen -P "$pfx" -W "$(__git_refs)" -- "$cur"))
+ ;;
+ *)
+ COMPREPLY=($(compgen -W "$(__git_refs)" -- "$cur"))
+ ;;
+ esac
+}
+
+_git_merge_base ()
+{
+ local cur="${COMP_WORDS[COMP_CWORD]}"
+ COMPREPLY=($(compgen -W "$(__git_refs)" -- "$cur"))
+}
+
+_git_pull ()
+{
+ local cur="${COMP_WORDS[COMP_CWORD]}"
+
+ case "${COMP_WORDS[0]},$COMP_CWORD" in
+ git-pull*,1)
+ COMPREPLY=($(compgen -W "$(__git_remotes)" -- "$cur"))
+ ;;
+ git,2)
+ COMPREPLY=($(compgen -W "$(__git_remotes)" -- "$cur"))
+ ;;
+ *)
+ local remote
+ case "${COMP_WORDS[0]}" in
+ git-pull) remote="${COMP_WORDS[1]}" ;;
+ git) remote="${COMP_WORDS[2]}" ;;
+ esac
+ COMPREPLY=($(compgen -W "$(__git_refs "$remote")" -- "$cur"))
+ ;;
+ esac
+}
+
+_git_push ()
+{
+ local cur="${COMP_WORDS[COMP_CWORD]}"
+
+ case "${COMP_WORDS[0]},$COMP_CWORD" in
+ git-push*,1)
+ COMPREPLY=($(compgen -W "$(__git_remotes)" -- "$cur"))
+ ;;
+ git,2)
+ COMPREPLY=($(compgen -W "$(__git_remotes)" -- "$cur"))
+ ;;
+ *)
+ case "$cur" in
+ *:*)
+ local remote
+ case "${COMP_WORDS[0]}" in
+ git-push) remote="${COMP_WORDS[1]}" ;;
+ git) remote="${COMP_WORDS[2]}" ;;
+ esac
+ cur="${cur#*:}"
+ COMPREPLY=($(compgen -W "$(__git_refs "$remote")" -- "$cur"))
+ ;;
+ *)
+ COMPREPLY=($(compgen -W "$(__git_refs2)" -- "$cur"))
+ ;;
+ esac
+ ;;
+ esac
+}
+
+_git_reset ()
+{
+ local cur="${COMP_WORDS[COMP_CWORD]}"
+ local opt="--mixed --hard --soft"
+ COMPREPLY=($(compgen -W "$opt $(__git_refs)" -- "$cur"))
+}
+
+_git_show ()
+{
+ local cur="${COMP_WORDS[COMP_CWORD]}"
+ COMPREPLY=($(compgen -W "$(__git_refs)" -- "$cur"))
+}
+
+_git ()
+{
+ local i c=1 command __git_dir
+
+ while [ $c -lt $COMP_CWORD ]; do
+ i="${COMP_WORDS[c]}"
+ case "$i" in
+ --git-dir=*) __git_dir="${i#--git-dir=}" ;;
+ --bare) __git_dir="." ;;
+ --version|--help|-p|--paginate) ;;
+ *) command="$i"; break ;;
+ esac
+ c=$((++c))
+ done
+
+ if [ $c -eq $COMP_CWORD -a -z "$command" ]; then
+ COMPREPLY=($(compgen \
+ -W "--git-dir= --version \
+ $(git help -a|egrep '^ ') \
+ $(__git_aliases)" \
+ -- "${COMP_WORDS[COMP_CWORD]}"))
+ return;
+ fi
+
+ local expansion=$(__git_aliased_command "$command")
+ [ "$expansion" ] && command="$expansion"
+
+ case "$command" in
+ branch) _git_branch ;;
+ cat-file) _git_cat_file ;;
+ checkout) _git_checkout ;;
+ diff) _git_diff ;;
+ diff-tree) _git_diff_tree ;;
+ fetch) _git_fetch ;;
+ log) _git_log ;;
+ ls-remote) _git_ls_remote ;;
+ ls-tree) _git_ls_tree ;;
+ merge-base) _git_merge_base ;;
+ pull) _git_pull ;;
+ push) _git_push ;;
+ reset) _git_reset ;;
+ show) _git_show ;;
+ show-branch) _git_log ;;
+ whatchanged) _git_log ;;
+ *) COMPREPLY=() ;;
+ esac
+}
+
+_gitk ()
+{
+ local cur="${COMP_WORDS[COMP_CWORD]}"
+ COMPREPLY=($(compgen -W "--all $(__git_refs)" -- "$cur"))
+}
+
+complete -o default -o nospace -F _git git
+complete -o default -F _gitk gitk
+complete -o default -F _git_branch git-branch
+complete -o default -o nospace -F _git_cat_file git-cat-file
+complete -o default -F _git_checkout git-checkout
+complete -o default -o nospace -F _git_diff git-diff
+complete -o default -F _git_diff_tree git-diff-tree
+complete -o default -o nospace -F _git_fetch git-fetch
+complete -o default -o nospace -F _git_log git-log
+complete -o default -F _git_ls_remote git-ls-remote
+complete -o default -o nospace -F _git_ls_tree git-ls-tree
+complete -o default -F _git_merge_base git-merge-base
+complete -o default -o nospace -F _git_pull git-pull
+complete -o default -o nospace -F _git_push git-push
+complete -o default -F _git_reset git-reset
+complete -o default -F _git_show git-show
+complete -o default -o nospace -F _git_log git-show-branch
+complete -o default -o nospace -F _git_log git-whatchanged
+
+# The following are necessary only for Cygwin, and only are needed
+# when the user has tab-completed the executable name and consequently
+# included the '.exe' suffix.
+#
+if [ Cygwin = "$(uname -o 2>/dev/null)" ]; then
+complete -o default -o nospace -F _git git.exe
+complete -o default -F _git_branch git-branch.exe
+complete -o default -o nospace -F _git_cat_file git-cat-file.exe
+complete -o default -o nospace -F _git_diff git-diff.exe
+complete -o default -o nospace -F _git_diff_tree git-diff-tree.exe
+complete -o default -o nospace -F _git_log git-log.exe
+complete -o default -o nospace -F _git_ls_tree git-ls-tree.exe
+complete -o default -F _git_merge_base git-merge-base.exe
+complete -o default -o nospace -F _git_push git-push.exe
+complete -o default -o nospace -F _git_log git-show-branch.exe
+complete -o default -o nospace -F _git_log git-whatchanged.exe
+fi
diff --git a/contrib/emacs/git.el b/contrib/emacs/git.el
index 68de9be0c7..972c402ea0 100644
--- a/contrib/emacs/git.el
+++ b/contrib/emacs/git.el
@@ -422,8 +422,8 @@ and returns the process output as a string."
(propertize
(concat " ("
(if (eq state 'copy) "copied from "
- (if (eq (git-fileinfo->state info) 'added) "renamed to "
- "renamed from "))
+ (if (eq (git-fileinfo->state info) 'added) "renamed from "
+ "renamed to "))
(git-escape-file-name (git-fileinfo->orig-name info))
")") 'face 'git-status-face)
"")))
@@ -589,6 +589,7 @@ and returns the process output as a string."
(let ((commit (git-commit-tree buffer tree head)))
(git-update-ref "HEAD" commit head)
(condition-case nil (delete-file ".git/MERGE_HEAD") (error nil))
+ (condition-case nil (delete-file ".git/MERGE_MSG") (error nil))
(with-current-buffer buffer (erase-buffer))
(git-set-files-state files 'uptodate)
(when (file-directory-p ".git/rr-cache")
@@ -670,6 +671,32 @@ and returns the process output as a string."
(unless git-status (error "Not in git-status buffer."))
(ewoc-goto-prev git-status n))
+(defun git-next-unmerged-file (&optional n)
+ "Move the selection down N unmerged files."
+ (interactive "p")
+ (unless git-status (error "Not in git-status buffer."))
+ (let* ((last (ewoc-locate git-status))
+ (node (ewoc-next git-status last)))
+ (while (and node (> n 0))
+ (when (eq 'unmerged (git-fileinfo->state (ewoc-data node)))
+ (setq n (1- n))
+ (setq last node))
+ (setq node (ewoc-next git-status node)))
+ (ewoc-goto-node git-status last)))
+
+(defun git-prev-unmerged-file (&optional n)
+ "Move the selection up N unmerged files."
+ (interactive "p")
+ (unless git-status (error "Not in git-status buffer."))
+ (let* ((last (ewoc-locate git-status))
+ (node (ewoc-prev git-status last)))
+ (while (and node (> n 0))
+ (when (eq 'unmerged (git-fileinfo->state (ewoc-data node)))
+ (setq n (1- n))
+ (setq last node))
+ (setq node (ewoc-prev git-status node)))
+ (ewoc-goto-node git-status last)))
+
(defun git-add-file ()
"Add marked file(s) to the index cache."
(interactive)
@@ -862,7 +889,7 @@ and returns the process output as a string."
'face 'git-header-face)
(propertize git-log-msg-separator 'face 'git-separator-face)
"\n")
- (cond ((and merge-heads (file-readable-p ".git/MERGE_MSG"))
+ (cond ((file-readable-p ".git/MERGE_MSG")
(insert-file-contents ".git/MERGE_MSG"))
(sign-off
(insert (format "\n\nSigned-off-by: %s <%s>\n"
@@ -873,7 +900,8 @@ and returns the process output as a string."
(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))))
+ (log-edit #'git-do-commit nil #'git-log-edit-files buffer)
+ (re-search-forward (regexp-quote (concat git-log-msg-separator "\n")) nil t))))
(defun git-find-file ()
"Visit the current file in its own buffer."
@@ -884,6 +912,15 @@ and returns the process output as a string."
(when (eq 'unmerged (git-fileinfo->state info))
(smerge-mode))))
+(defun git-find-file-other-window ()
+ "Visit the current file in its own buffer in another window."
+ (interactive)
+ (unless git-status (error "Not in git-status buffer."))
+ (let ((info (ewoc-data (ewoc-locate git-status))))
+ (find-file-other-window (git-fileinfo->name info))
+ (when (eq 'unmerged (git-fileinfo->state info))
+ (smerge-mode))))
+
(defun git-find-file-imerge ()
"Visit the current file in interactive merge mode."
(interactive)
@@ -967,7 +1004,10 @@ and returns the process output as a string."
(define-key map "m" 'git-mark-file)
(define-key map "M" 'git-mark-all)
(define-key map "n" 'git-next-file)
+ (define-key map "N" 'git-next-unmerged-file)
+ (define-key map "o" 'git-find-file-other-window)
(define-key map "p" 'git-prev-file)
+ (define-key map "P" 'git-prev-unmerged-file)
(define-key map "q" 'git-status-quit)
(define-key map "r" 'git-remove-file)
(define-key map "R" 'git-resolve-file)
diff --git a/contrib/emacs/vc-git.el b/contrib/emacs/vc-git.el
index 3f6ed699f0..8b6361922f 100644
--- a/contrib/emacs/vc-git.el
+++ b/contrib/emacs/vc-git.el
@@ -23,13 +23,18 @@
;; system.
;;
;; To install: put this file on the load-path and add GIT to the list
-;; of supported backends in `vc-handled-backends'.
+;; of supported backends in `vc-handled-backends'; the following line,
+;; placed in your ~/.emacs, will accomplish this:
+;;
+;; (add-to-list 'vc-handled-backends 'GIT)
;;
;; TODO
;; - changelog generation
;; - working with revisions other than HEAD
;;
+(eval-when-compile (require 'cl))
+
(defvar git-commits-coding-system 'utf-8
"Default coding system for git commits.")
@@ -54,7 +59,7 @@
(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))
+ (and (ignore-errors (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"))))))))
@@ -119,10 +124,10 @@
(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)))
+ (call-process "git" nil buf nil "blame" 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)
+ (and (re-search-forward "[0-9a-f]+ (.* \\([0-9]+\\)-\\([0-9]+\\)-\\([0-9]+\\) \\([0-9]+\\):\\([0-9]+\\):\\([0-9]+\\) \\([-+0-9]+\\) +[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))))))
diff --git a/contrib/gitview/gitview.txt b/contrib/gitview/gitview.txt
index 6924df286e..77c29de305 100644
--- a/contrib/gitview/gitview.txt
+++ b/contrib/gitview/gitview.txt
@@ -7,40 +7,50 @@ gitview - A GTK based repository browser for git
SYNOPSIS
--------
-'gitview' [options] [args]
+'gitview' [options] [args]
DESCRIPTION
---------
-Dependencies
+Dependencies:
* Python 2.4
* PyGTK 2.8 or later
* PyCairo 1.0 or later
OPTIONS
-------
- --without-diff
- If the user doesn't want to list the commit diffs in the main window. This may speed up the repository browsing.
-
- <args>
- All the valid option for git-rev-list(1)
- Key Bindings:
- F4:
- To maximize the window
- F5:
- To reread references.
- F11:
- Full screen
- F12:
- Leave full screen
+-------
+--without-diff::
+
+ If the user doesn't want to list the commit diffs in the main window.
+ This may speed up the repository browsing.
+
+<args>::
+
+ All the valid option for gitlink:git-rev-list[1].
+
+Key Bindings
+------------
+F4::
+ To maximize the window
+
+F5::
+ To reread references.
+
+F11::
+ Full screen
+
+F12::
+ Leave full screen
EXAMPLES
-------
- gitview v2.6.12.. include/scsi drivers/scsi
- Show as the changes since version v2.6.12 that changed any file in the include/scsi
- or drivers/scsi subdirectories
+--------
+
+gitview v2.6.12.. include/scsi drivers/scsi::
+
+ Show as the changes since version v2.6.12 that changed any file in the
+ include/scsi or drivers/scsi subdirectories
- gitview --since=2.weeks.ago
- Show the changes during the last two weeks
+gitview --since=2.weeks.ago::
+ Show the changes during the last two weeks
diff --git a/contrib/vim/README b/contrib/vim/README
new file mode 100644
index 0000000000..9e7881fea9
--- /dev/null
+++ b/contrib/vim/README
@@ -0,0 +1,8 @@
+To syntax highlight git's commit messages, you need to:
+ 1. Copy syntax/gitcommit.vim to vim's syntax directory:
+ $ mkdir -p $HOME/.vim/syntax
+ $ cp syntax/gitcommit.vim $HOME/.vim/syntax
+ 2. Auto-detect the editing of git commit files:
+ $ cat >>$HOME/.vimrc <<'EOF'
+ autocmd BufNewFile,BufRead COMMIT_EDITMSG set filetype=gitcommit
+ EOF
diff --git a/contrib/vim/syntax/gitcommit.vim b/contrib/vim/syntax/gitcommit.vim
new file mode 100644
index 0000000000..a9de09fa2f
--- /dev/null
+++ b/contrib/vim/syntax/gitcommit.vim
@@ -0,0 +1,18 @@
+syn region gitLine start=/^#/ end=/$/
+syn region gitCommit start=/^# Updated but not checked in:$/ end=/^#$/ contains=gitHead,gitCommitFile
+syn region gitHead contained start=/^# (.*)/ end=/^#$/
+syn region gitChanged start=/^# Changed but not updated:/ end=/^#$/ contains=gitHead,gitChangedFile
+syn region gitUntracked start=/^# Untracked files:/ end=/^#$/ contains=gitHead,gitUntrackedFile
+
+syn match gitCommitFile contained /^#\t.*/hs=s+2
+syn match gitChangedFile contained /^#\t.*/hs=s+2
+syn match gitUntrackedFile contained /^#\t.*/hs=s+2
+
+hi def link gitLine Comment
+hi def link gitCommit Comment
+hi def link gitChanged Comment
+hi def link gitHead Comment
+hi def link gitUntracked Comment
+hi def link gitCommitFile Type
+hi def link gitChangedFile Constant
+hi def link gitUntrackedFile Constant
diff --git a/convert-objects.c b/convert-objects.c
index 168771ed85..881258311a 100644
--- a/convert-objects.c
+++ b/convert-objects.c
@@ -1,4 +1,4 @@
-#define _XOPEN_SOURCE 500 /* glibc2 and AIX 5.3L need this */
+#define _XOPEN_SOURCE 600 /* glibc2 and AIX 5.3L need 500, OpenBSD needs 600 for S_ISLNK() */
#define _XOPEN_SOURCE_EXTENDED 1 /* AIX 5.3L needs this */
#define _GNU_SOURCE
#include <time.h>
@@ -23,7 +23,7 @@ static struct entry * convert_entry(unsigned char *sha1);
static struct entry *insert_new(unsigned char *sha1, int pos)
{
struct entry *new = xcalloc(1, sizeof(struct entry));
- memcpy(new->old_sha1, sha1, 20);
+ hashcpy(new->old_sha1, sha1);
memmove(convert + pos + 1, convert + pos, (nr_convert - pos) * sizeof(struct entry *));
convert[pos] = new;
nr_convert++;
@@ -39,7 +39,7 @@ static struct entry *lookup_entry(unsigned char *sha1)
while (low < high) {
int next = (low + high) / 2;
struct entry *n = convert[next];
- int cmp = memcmp(sha1, n->old_sha1, 20);
+ int cmp = hashcmp(sha1, n->old_sha1);
if (!cmp)
return n;
if (cmp < 0) {
@@ -54,7 +54,7 @@ static struct entry *lookup_entry(unsigned char *sha1)
static void convert_binary_sha1(void *buffer)
{
struct entry *entry = convert_entry(buffer);
- memcpy(buffer, entry->new_sha1, 20);
+ hashcpy(buffer, entry->new_sha1);
}
static void convert_ascii_sha1(void *buffer)
@@ -104,7 +104,7 @@ static int write_subdirectory(void *buffer, unsigned long size, const char *base
if (!slash) {
newlen += sprintf(new + newlen, "%o %s", mode, path);
new[newlen++] = '\0';
- memcpy(new + newlen, (char *) buffer + len - 20, 20);
+ hashcpy((unsigned char*)new + newlen, (unsigned char *) buffer + len - 20);
newlen += 20;
used += len;
diff --git a/csum-file.c b/csum-file.c
index 6a7b40fd09..b7174c6c05 100644
--- a/csum-file.c
+++ b/csum-file.c
@@ -10,7 +10,7 @@
#include "cache.h"
#include "csum-file.h"
-static int sha1flush(struct sha1file *f, unsigned int count)
+static void sha1flush(struct sha1file *f, unsigned int count)
{
void *buf = f->buffer;
@@ -21,7 +21,7 @@ static int sha1flush(struct sha1file *f, unsigned int count)
count -= ret;
if (count)
continue;
- return 0;
+ return;
}
if (!ret)
die("sha1 file '%s' write error. Out of diskspace", f->name);
@@ -38,7 +38,7 @@ int sha1close(struct sha1file *f, unsigned char *result, int update)
}
SHA1_Final(f->buffer, &f->ctx);
if (result)
- memcpy(result, f->buffer, 20);
+ hashcpy(result, f->buffer);
if (update)
sha1flush(f, 20);
if (close(f->fd))
diff --git a/daemon.c b/daemon.c
index 810837f0c4..e66bb802da 100644
--- a/daemon.c
+++ b/daemon.c
@@ -7,39 +7,77 @@
#include <netinet/in.h>
#include <arpa/inet.h>
#include <syslog.h>
+#include <pwd.h>
+#include <grp.h>
+#include <limits.h>
#include "pkt-line.h"
#include "cache.h"
#include "exec_cmd.h"
+#include "interpolate.h"
+
+#ifndef HOST_NAME_MAX
+#define HOST_NAME_MAX 256
+#endif
static int log_syslog;
static int verbose;
static int reuseaddr;
static const char daemon_usage[] =
-"git-daemon [--verbose] [--syslog] [--inetd | --port=n] [--export-all]\n"
+"git-daemon [--verbose] [--syslog] [--export-all]\n"
" [--timeout=n] [--init-timeout=n] [--strict-paths]\n"
" [--base-path=path] [--user-path | --user-path=path]\n"
-" [--reuseaddr] [--detach] [--pid-file=file] [directory...]";
+" [--interpolated-path=path]\n"
+" [--reuseaddr] [--detach] [--pid-file=file]\n"
+" [--[enable|disable|allow-override|forbid-override]=service]\n"
+" [--inetd | [--listen=host_or_ipaddr] [--port=n]\n"
+" [--user=user [--group=group]]\n"
+" [directory...]";
/* List of acceptable pathname prefixes */
-static char **ok_paths = NULL;
-static int strict_paths = 0;
+static char **ok_paths;
+static int strict_paths;
/* If this is set, git-daemon-export-ok is not required */
-static int export_all_trees = 0;
+static int export_all_trees;
/* Take all paths relative to this one if non-NULL */
-static char *base_path = NULL;
+static char *base_path;
+static char *interpolated_path;
+
+/* Flag indicating client sent extra args. */
+static int saw_extended_args;
/* If defined, ~user notation is allowed and the string is inserted
* after ~user/. E.g. a request to git://host/~alice/frotz would
* go to /home/alice/pub_git/frotz with --user-path=pub_git.
*/
-static const char *user_path = NULL;
+static const char *user_path;
/* Timeout, and initial timeout */
-static unsigned int timeout = 0;
-static unsigned int init_timeout = 0;
+static unsigned int timeout;
+static unsigned int init_timeout;
+
+/*
+ * Static table for now. Ugh.
+ * Feel free to make dynamic as needed.
+ */
+#define INTERP_SLOT_HOST (0)
+#define INTERP_SLOT_CANON_HOST (1)
+#define INTERP_SLOT_IP (2)
+#define INTERP_SLOT_PORT (3)
+#define INTERP_SLOT_DIR (4)
+#define INTERP_SLOT_PERCENT (5)
+
+static struct interp interp_table[] = {
+ { "%H", 0},
+ { "%CH", 0},
+ { "%IP", 0},
+ { "%P", 0},
+ { "%D", 0},
+ { "%%", 0},
+};
+
static void logreport(int priority, const char *err, va_list params)
{
@@ -148,10 +186,14 @@ static int avoid_alias(char *p)
}
}
-static char *path_ok(char *dir)
+static char *path_ok(struct interp *itable)
{
static char rpath[PATH_MAX];
+ static char interp_path[PATH_MAX];
char *path;
+ char *dir;
+
+ dir = itable[INTERP_SLOT_DIR].value;
if (avoid_alias(dir)) {
logerror("'%s': aliased", dir);
@@ -180,16 +222,27 @@ static char *path_ok(char *dir)
dir = rpath;
}
}
+ else if (interpolated_path && saw_extended_args) {
+ if (*dir != '/') {
+ /* Allow only absolute */
+ logerror("'%s': Non-absolute path denied (interpolated-path active)", dir);
+ return NULL;
+ }
+
+ interpolate(interp_path, PATH_MAX, interpolated_path,
+ interp_table, ARRAY_SIZE(interp_table));
+ loginfo("Interpolated dir '%s'", interp_path);
+
+ dir = interp_path;
+ }
else if (base_path) {
if (*dir != '/') {
/* Allow only absolute */
logerror("'%s': Non-absolute path denied (base-path active)", dir);
return NULL;
}
- else {
- snprintf(rpath, PATH_MAX, "%s%s", base_path, dir);
- dir = rpath;
- }
+ snprintf(rpath, PATH_MAX, "%s%s", base_path, dir);
+ dir = rpath;
}
path = enter_repo(dir, strict_paths);
@@ -229,15 +282,46 @@ static char *path_ok(char *dir)
return NULL; /* Fallthrough. Deny by default */
}
-static int upload(char *dir)
+typedef int (*daemon_service_fn)(void);
+struct daemon_service {
+ const char *name;
+ const char *config_name;
+ daemon_service_fn fn;
+ int enabled;
+ int overridable;
+};
+
+static struct daemon_service *service_looking_at;
+static int service_enabled;
+
+static int git_daemon_config(const char *var, const char *value)
+{
+ if (!strncmp(var, "daemon.", 7) &&
+ !strcmp(var + 7, service_looking_at->config_name)) {
+ service_enabled = git_config_bool(var, value);
+ return 0;
+ }
+
+ /* we are not interested in parsing any other configuration here */
+ return 0;
+}
+
+static int run_service(struct interp *itable, struct daemon_service *service)
{
- /* Timeout as string */
- char timeout_buf[64];
const char *path;
+ int enabled = service->enabled;
+
+ loginfo("Request %s for '%s'",
+ service->name,
+ itable[INTERP_SLOT_DIR].value);
- loginfo("Request for '%s'", dir);
+ if (!enabled && !service->overridable) {
+ logerror("'%s': service not enabled.", service->name);
+ errno = EACCES;
+ return -1;
+ }
- if (!(path = path_ok(dir)))
+ if (!(path = path_ok(itable)))
return -1;
/*
@@ -257,12 +341,34 @@ static int upload(char *dir)
return -1;
}
+ if (service->overridable) {
+ service_looking_at = service;
+ service_enabled = -1;
+ git_config(git_daemon_config);
+ if (0 <= service_enabled)
+ enabled = service_enabled;
+ }
+ if (!enabled) {
+ logerror("'%s': service not enabled for '%s'",
+ service->name, path);
+ errno = EACCES;
+ return -1;
+ }
+
/*
* We'll ignore SIGTERM from now on, we have a
* good client.
*/
signal(SIGTERM, SIG_IGN);
+ return service->fn();
+}
+
+static int upload_pack(void)
+{
+ /* Timeout as string */
+ char timeout_buf[64];
+
snprintf(timeout_buf, sizeof timeout_buf, "--timeout=%u", timeout);
/* git-upload-pack only ever reads stuff, so this is safe */
@@ -270,10 +376,143 @@ static int upload(char *dir)
return -1;
}
+static int upload_archive(void)
+{
+ execl_git_cmd("upload-archive", ".", NULL);
+ return -1;
+}
+
+static struct daemon_service daemon_service[] = {
+ { "upload-archive", "uploadarch", upload_archive, 0, 1 },
+ { "upload-pack", "uploadpack", upload_pack, 1, 1 },
+};
+
+static void enable_service(const char *name, int ena) {
+ int i;
+ for (i = 0; i < ARRAY_SIZE(daemon_service); i++) {
+ if (!strcmp(daemon_service[i].name, name)) {
+ daemon_service[i].enabled = ena;
+ return;
+ }
+ }
+ die("No such service %s", name);
+}
+
+static void make_service_overridable(const char *name, int ena) {
+ int i;
+ for (i = 0; i < ARRAY_SIZE(daemon_service); i++) {
+ if (!strcmp(daemon_service[i].name, name)) {
+ daemon_service[i].overridable = ena;
+ return;
+ }
+ }
+ die("No such service %s", name);
+}
+
+/*
+ * Separate the "extra args" information as supplied by the client connection.
+ * Any resulting data is squirrelled away in the given interpolation table.
+ */
+static void parse_extra_args(struct interp *table, char *extra_args, int buflen)
+{
+ char *val;
+ int vallen;
+ char *end = extra_args + buflen;
+
+ while (extra_args < end && *extra_args) {
+ saw_extended_args = 1;
+ if (strncasecmp("host=", extra_args, 5) == 0) {
+ val = extra_args + 5;
+ vallen = strlen(val) + 1;
+ if (*val) {
+ /* Split <host>:<port> at colon. */
+ char *host = val;
+ char *port = strrchr(host, ':');
+ if (port) {
+ *port = 0;
+ port++;
+ interp_set_entry(table, INTERP_SLOT_PORT, port);
+ }
+ interp_set_entry(table, INTERP_SLOT_HOST, host);
+ }
+
+ /* On to the next one */
+ extra_args = val + vallen;
+ }
+ }
+}
+
+void fill_in_extra_table_entries(struct interp *itable)
+{
+ char *hp;
+
+ /*
+ * Replace literal host with lowercase-ized hostname.
+ */
+ hp = interp_table[INTERP_SLOT_HOST].value;
+ if (!hp)
+ return;
+ for ( ; *hp; hp++)
+ *hp = tolower(*hp);
+
+ /*
+ * Locate canonical hostname and its IP address.
+ */
+#ifndef NO_IPV6
+ {
+ struct addrinfo hints;
+ struct addrinfo *ai, *ai0;
+ int gai;
+ static char addrbuf[HOST_NAME_MAX + 1];
+
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_flags = AI_CANONNAME;
+
+ gai = getaddrinfo(interp_table[INTERP_SLOT_HOST].value, 0, &hints, &ai0);
+ if (!gai) {
+ for (ai = ai0; ai; ai = ai->ai_next) {
+ struct sockaddr_in *sin_addr = (void *)ai->ai_addr;
+
+ inet_ntop(AF_INET, &sin_addr->sin_addr,
+ addrbuf, sizeof(addrbuf));
+ interp_set_entry(interp_table,
+ INTERP_SLOT_CANON_HOST, ai->ai_canonname);
+ interp_set_entry(interp_table,
+ INTERP_SLOT_IP, addrbuf);
+ break;
+ }
+ freeaddrinfo(ai0);
+ }
+ }
+#else
+ {
+ struct hostent *hent;
+ struct sockaddr_in sa;
+ char **ap;
+ static char addrbuf[HOST_NAME_MAX + 1];
+
+ hent = gethostbyname(interp_table[INTERP_SLOT_HOST].value);
+
+ ap = hent->h_addr_list;
+ memset(&sa, 0, sizeof sa);
+ sa.sin_family = hent->h_addrtype;
+ sa.sin_port = htons(0);
+ memcpy(&sa.sin_addr, *ap, hent->h_length);
+
+ inet_ntop(hent->h_addrtype, &sa.sin_addr,
+ addrbuf, sizeof(addrbuf));
+
+ interp_set_entry(interp_table, INTERP_SLOT_CANON_HOST, hent->h_name);
+ interp_set_entry(interp_table, INTERP_SLOT_IP, addrbuf);
+ }
+#endif
+}
+
+
static int execute(struct sockaddr *addr)
{
static char line[1000];
- int pktlen, len;
+ int pktlen, len, i;
if (addr) {
char addrbuf[256] = "";
@@ -307,11 +546,37 @@ static int execute(struct sockaddr *addr)
loginfo("Extended attributes (%d bytes) exist <%.*s>",
(int) pktlen - len,
(int) pktlen - len, line + len + 1);
- if (len && line[len-1] == '\n')
+ if (len && line[len-1] == '\n') {
line[--len] = 0;
+ pktlen--;
+ }
+
+ /*
+ * Initialize the path interpolation table for this connection.
+ */
+ interp_clear_table(interp_table, ARRAY_SIZE(interp_table));
+ interp_set_entry(interp_table, INTERP_SLOT_PERCENT, "%");
- if (!strncmp("git-upload-pack ", line, 16))
- return upload(line+16);
+ if (len != pktlen) {
+ parse_extra_args(interp_table, line + len + 1, pktlen - len - 1);
+ fill_in_extra_table_entries(interp_table);
+ }
+
+ for (i = 0; i < ARRAY_SIZE(daemon_service); i++) {
+ struct daemon_service *s = &(daemon_service[i]);
+ int namelen = strlen(s->name);
+ if (!strncmp("git-", line, 4) &&
+ !strncmp(s->name, line + 4, namelen) &&
+ line[namelen + 4] == ' ') {
+ /*
+ * Note: The directory here is probably context sensitive,
+ * and might depend on the actual service being performed.
+ */
+ interp_set_entry(interp_table,
+ INTERP_SLOT_DIR, line + namelen + 5);
+ return run_service(interp_table, s);
+ }
+ }
logerror("Protocol error: '%s'", line);
return -1;
@@ -333,12 +598,12 @@ static int execute(struct sockaddr *addr)
static int max_connections = 25;
/* These are updated by the signal handler */
-static volatile unsigned int children_reaped = 0;
+static volatile unsigned int children_reaped;
static pid_t dead_child[MAX_CHILDREN];
/* These are updated by the main loop */
-static unsigned int children_spawned = 0;
-static unsigned int children_deleted = 0;
+static unsigned int children_spawned;
+static unsigned int children_deleted;
static struct child {
pid_t pid;
@@ -504,29 +769,27 @@ static int set_reuse_addr(int sockfd)
#ifndef NO_IPV6
-static int socksetup(int port, int **socklist_p)
+static int socksetup(char *listen_addr, int listen_port, int **socklist_p)
{
int socknum = 0, *socklist = NULL;
int maxfd = -1;
char pbuf[NI_MAXSERV];
-
struct addrinfo hints, *ai0, *ai;
int gai;
- sprintf(pbuf, "%d", port);
+ sprintf(pbuf, "%d", listen_port);
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
hints.ai_flags = AI_PASSIVE;
- gai = getaddrinfo(NULL, pbuf, &hints, &ai0);
+ gai = getaddrinfo(listen_addr, pbuf, &hints, &ai0);
if (gai)
die("getaddrinfo() failed: %s\n", gai_strerror(gai));
for (ai = ai0; ai; ai = ai->ai_next) {
int sockfd;
- int *newlist;
sockfd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
if (sockfd < 0)
@@ -560,11 +823,7 @@ static int socksetup(int port, int **socklist_p)
continue; /* not fatal */
}
- newlist = realloc(socklist, sizeof(int) * (socknum + 1));
- if (!newlist)
- die("memory allocation failed: %s", strerror(errno));
-
- socklist = newlist;
+ socklist = xrealloc(socklist, sizeof(int) * (socknum + 1));
socklist[socknum++] = sockfd;
if (maxfd < sockfd)
@@ -579,20 +838,27 @@ static int socksetup(int port, int **socklist_p)
#else /* NO_IPV6 */
-static int socksetup(int port, int **socklist_p)
+static int socksetup(char *listen_addr, int listen_port, int **socklist_p)
{
struct sockaddr_in sin;
int sockfd;
+ memset(&sin, 0, sizeof sin);
+ sin.sin_family = AF_INET;
+ sin.sin_port = htons(listen_port);
+
+ if (listen_addr) {
+ /* Well, host better be an IP address here. */
+ if (inet_pton(AF_INET, listen_addr, &sin.sin_addr.s_addr) <= 0)
+ return 0;
+ } else {
+ sin.sin_addr.s_addr = htonl(INADDR_ANY);
+ }
+
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0)
return 0;
- memset(&sin, 0, sizeof sin);
- sin.sin_family = AF_INET;
- sin.sin_addr.s_addr = htonl(INADDR_ANY);
- sin.sin_port = htons(port);
-
if (set_reuse_addr(sockfd)) {
close(sockfd);
return 0;
@@ -701,23 +967,33 @@ static void store_pid(const char *path)
fclose(f);
}
-static int serve(int port)
+static int serve(char *listen_addr, int listen_port, struct passwd *pass, gid_t gid)
{
int socknum, *socklist;
- socknum = socksetup(port, &socklist);
+ socknum = socksetup(listen_addr, listen_port, &socklist);
if (socknum == 0)
- die("unable to allocate any listen sockets on port %u", port);
+ die("unable to allocate any listen sockets on host %s port %u",
+ listen_addr, listen_port);
+
+ if (pass && gid &&
+ (initgroups(pass->pw_name, gid) || setgid (gid) ||
+ setuid(pass->pw_uid)))
+ die("cannot drop privileges");
return service_loop(socknum, socklist);
}
int main(int argc, char **argv)
{
- int port = DEFAULT_GIT_PORT;
+ int listen_port = 0;
+ char *listen_addr = NULL;
int inetd_mode = 0;
- const char *pid_file = NULL;
+ const char *pid_file = NULL, *user_name = NULL, *group_name = NULL;
int detach = 0;
+ struct passwd *pass = NULL;
+ struct group *group;
+ gid_t gid = 0;
int i;
/* Without this we cannot rely on waitpid() to tell
@@ -728,12 +1004,20 @@ int main(int argc, char **argv)
for (i = 1; i < argc; i++) {
char *arg = argv[i];
+ if (!strncmp(arg, "--listen=", 9)) {
+ char *p = arg + 9;
+ char *ph = listen_addr = xmalloc(strlen(arg + 9) + 1);
+ while (*p)
+ *ph++ = tolower(*p++);
+ *ph = 0;
+ continue;
+ }
if (!strncmp(arg, "--port=", 7)) {
char *end;
unsigned long n;
n = strtoul(arg+7, &end, 0);
if (arg[7] && !*end) {
- port = n;
+ listen_port = n;
continue;
}
}
@@ -770,6 +1054,10 @@ int main(int argc, char **argv)
base_path = arg+12;
continue;
}
+ if (!strncmp(arg, "--interpolated-path=", 20)) {
+ interpolated_path = arg+20;
+ continue;
+ }
if (!strcmp(arg, "--reuseaddr")) {
reuseaddr = 1;
continue;
@@ -791,6 +1079,30 @@ int main(int argc, char **argv)
log_syslog = 1;
continue;
}
+ if (!strncmp(arg, "--user=", 7)) {
+ user_name = arg + 7;
+ continue;
+ }
+ if (!strncmp(arg, "--group=", 8)) {
+ group_name = arg + 8;
+ continue;
+ }
+ if (!strncmp(arg, "--enable=", 9)) {
+ enable_service(arg + 9, 1);
+ continue;
+ }
+ if (!strncmp(arg, "--disable=", 10)) {
+ enable_service(arg + 10, 0);
+ continue;
+ }
+ if (!strncmp(arg, "--allow-override=", 17)) {
+ make_service_overridable(arg + 17, 1);
+ continue;
+ }
+ if (!strncmp(arg, "--forbid-override=", 18)) {
+ make_service_overridable(arg + 18, 0);
+ continue;
+ }
if (!strcmp(arg, "--")) {
ok_paths = &argv[i+1];
break;
@@ -802,6 +1114,33 @@ int main(int argc, char **argv)
usage(daemon_usage);
}
+ if (inetd_mode && (group_name || user_name))
+ die("--user and --group are incompatible with --inetd");
+
+ if (inetd_mode && (listen_port || listen_addr))
+ die("--listen= and --port= are incompatible with --inetd");
+ else if (listen_port == 0)
+ listen_port = DEFAULT_GIT_PORT;
+
+ if (group_name && !user_name)
+ die("--group supplied without --user");
+
+ if (user_name) {
+ pass = getpwnam(user_name);
+ if (!pass)
+ die("user not found - %s", user_name);
+
+ if (!group_name)
+ gid = pass->pw_gid;
+ else {
+ group = getgrnam(group_name);
+ if (!group)
+ die("group not found - %s", group_name);
+
+ gid = group->gr_gid;
+ }
+ }
+
if (log_syslog) {
openlog("git-daemon", 0, LOG_DAEMON);
set_die_routine(daemon_die);
@@ -831,5 +1170,5 @@ int main(int argc, char **argv)
if (pid_file)
store_pid(pid_file);
- return serve(port);
+ return serve(listen_addr, listen_port, pass, gid);
}
diff --git a/date.c b/date.c
index 66be23ab21..1825922732 100644
--- a/date.c
+++ b/date.c
@@ -37,6 +37,16 @@ static const char *weekday_names[] = {
"Sundays", "Mondays", "Tuesdays", "Wednesdays", "Thursdays", "Fridays", "Saturdays"
};
+static time_t gm_time_t(unsigned long time, int tz)
+{
+ int minutes;
+
+ minutes = tz < 0 ? -tz : tz;
+ minutes = (minutes / 100)*60 + (minutes % 100);
+ minutes = tz < 0 ? -minutes : minutes;
+ return time + minutes * 60;
+}
+
/*
* The "tz" thing is passed in as this strange "decimal parse of tz"
* thing, which means that tz -0100 is passed in as the integer -100,
@@ -44,21 +54,58 @@ static const char *weekday_names[] = {
*/
static struct tm *time_to_tm(unsigned long time, int tz)
{
- time_t t;
- int minutes;
-
- minutes = tz < 0 ? -tz : tz;
- minutes = (minutes / 100)*60 + (minutes % 100);
- minutes = tz < 0 ? -minutes : minutes;
- t = time + minutes * 60;
+ time_t t = gm_time_t(time, tz);
return gmtime(&t);
}
-const char *show_date(unsigned long time, int tz)
+const char *show_date(unsigned long time, int tz, int relative)
{
struct tm *tm;
static char timebuf[200];
+ if (relative) {
+ unsigned long diff;
+ time_t t = gm_time_t(time, tz);
+ struct timeval now;
+ gettimeofday(&now, NULL);
+ if (now.tv_sec < t)
+ return "in the future";
+ diff = now.tv_sec - t;
+ if (diff < 90) {
+ snprintf(timebuf, sizeof(timebuf), "%lu seconds ago", diff);
+ return timebuf;
+ }
+ /* Turn it into minutes */
+ diff = (diff + 30) / 60;
+ if (diff < 90) {
+ snprintf(timebuf, sizeof(timebuf), "%lu minutes ago", diff);
+ return timebuf;
+ }
+ /* Turn it into hours */
+ diff = (diff + 30) / 60;
+ if (diff < 36) {
+ snprintf(timebuf, sizeof(timebuf), "%lu hours ago", diff);
+ return timebuf;
+ }
+ /* We deal with number of days from here on */
+ diff = (diff + 12) / 24;
+ if (diff < 14) {
+ snprintf(timebuf, sizeof(timebuf), "%lu days ago", diff);
+ return timebuf;
+ }
+ /* Say weeks for the past 10 weeks or so */
+ if (diff < 70) {
+ snprintf(timebuf, sizeof(timebuf), "%lu weeks ago", (diff + 3) / 7);
+ return timebuf;
+ }
+ /* Say months for the past 12 months or so */
+ if (diff < 360) {
+ snprintf(timebuf, sizeof(timebuf), "%lu months ago", (diff + 15) / 30);
+ return timebuf;
+ }
+ /* Else fall back on absolute format.. */
+ }
+
tm = time_to_tm(time, tz);
if (!tm)
return NULL;
@@ -209,8 +256,12 @@ static int match_alpha(const char *date, struct tm *tm, int *offset)
}
if (match_string(date, "PM") == 2) {
- if (tm->tm_hour > 0 && tm->tm_hour < 12)
- tm->tm_hour += 12;
+ tm->tm_hour = (tm->tm_hour % 12) + 12;
+ return 2;
+ }
+
+ if (match_string(date, "AM") == 2) {
+ tm->tm_hour = (tm->tm_hour % 12) + 0;
return 2;
}
@@ -551,6 +602,34 @@ static void date_tea(struct tm *tm, int *num)
date_time(tm, 17);
}
+static void date_pm(struct tm *tm, int *num)
+{
+ int hour, n = *num;
+ *num = 0;
+
+ hour = tm->tm_hour;
+ if (n) {
+ hour = n;
+ tm->tm_min = 0;
+ tm->tm_sec = 0;
+ }
+ tm->tm_hour = (hour % 12) + 12;
+}
+
+static void date_am(struct tm *tm, int *num)
+{
+ int hour, n = *num;
+ *num = 0;
+
+ hour = tm->tm_hour;
+ if (n) {
+ hour = n;
+ tm->tm_min = 0;
+ tm->tm_sec = 0;
+ }
+ tm->tm_hour = (hour % 12);
+}
+
static const struct special {
const char *name;
void (*fn)(struct tm *, int *);
@@ -559,6 +638,8 @@ static const struct special {
{ "noon", date_noon },
{ "midnight", date_midnight },
{ "tea", date_tea },
+ { "PM", date_pm },
+ { "AM", date_am },
{ NULL }
};
@@ -584,10 +665,10 @@ static const char *approxidate_alpha(const char *date, struct tm *tm, int *num)
const struct typelen *tl;
const struct special *s;
const char *end = date;
- int n = 1, i;
+ int i;
- while (isalpha(*++end))
- n++;
+ while (isalpha(*++end));
+ ;
for (i = 0; i < 12; i++) {
int match = match_string(date, month_names[i]);
@@ -665,6 +746,27 @@ static const char *approxidate_alpha(const char *date, struct tm *tm, int *num)
return end;
}
+static const char *approxidate_digit(const char *date, struct tm *tm, int *num)
+{
+ char *end;
+ unsigned long number = strtoul(date, &end, 10);
+
+ switch (*end) {
+ case ':':
+ case '.':
+ case '/':
+ case '-':
+ if (isdigit(end[1])) {
+ int match = match_multi_number(number, *end, date, end, tm);
+ if (match)
+ return date + match;
+ }
+ }
+
+ *num = number;
+ return end;
+}
+
unsigned long approxidate(const char *date)
{
int number = 0;
@@ -684,9 +786,7 @@ unsigned long approxidate(const char *date)
break;
date++;
if (isdigit(c)) {
- char *end;
- number = strtoul(date-1, &end, 10);
- date = end;
+ date = approxidate_digit(date-1, &tm, &number);
continue;
}
if (isalpha(c))
diff --git a/describe.c b/describe.c
index 324ca8965b..f4029ee74e 100644
--- a/describe.c
+++ b/describe.c
@@ -8,12 +8,12 @@
static const char describe_usage[] =
"git-describe [--all] [--tags] [--abbrev=<n>] <committish>*";
-static int all = 0; /* Default to annotated tags only */
-static int tags = 0; /* But allow any tags if --tags is specified */
+static int all; /* Default to annotated tags only */
+static int tags; /* But allow any tags if --tags is specified */
static int abbrev = DEFAULT_ABBREV;
-static int names = 0, allocs = 0;
+static int names, allocs;
static struct commit_name {
const struct commit *commit;
int prio; /* annotated tag = 2, tag = 1, head = 0 */
@@ -42,7 +42,7 @@ static void add_to_known_names(const char *path,
struct commit_name *name = xmalloc(sizeof(struct commit_name) + len);
name->commit = commit;
- name->prio = prio;
+ name->prio = prio;
memcpy(name->path, path, len);
idx = names;
if (idx >= allocs) {
@@ -53,7 +53,7 @@ static void add_to_known_names(const char *path,
names = ++idx;
}
-static int get_name(const char *path, const unsigned char *sha1)
+static int get_name(const char *path, const unsigned char *sha1, int flag, void *cb_data)
{
struct commit *commit = lookup_commit_reference_gently(sha1, 1);
struct object *object;
@@ -113,7 +113,7 @@ static void describe(const char *arg, int last_one)
if (!initialized) {
initialized = 1;
- for_each_ref(get_name);
+ for_each_ref(get_name, NULL);
qsort(name_array, names, sizeof(*name_array), compare_names);
}
@@ -154,14 +154,16 @@ int main(int argc, char **argv)
tags = 1;
else if (!strncmp(arg, "--abbrev=", 9)) {
abbrev = strtoul(arg + 9, NULL, 10);
- if (abbrev < MINIMUM_ABBREV || 40 <= abbrev)
+ if (abbrev < MINIMUM_ABBREV || 40 < abbrev)
abbrev = DEFAULT_ABBREV;
}
else
usage(describe_usage);
}
- if (i == argc)
+ setup_git_directory();
+
+ if (argc <= i)
describe("HEAD", 1);
else
while (i < argc) {
diff --git a/diff-delta.c b/diff-delta.c
index 7da9205a5d..fa16d06c8d 100644
--- a/diff-delta.c
+++ b/diff-delta.c
@@ -152,7 +152,7 @@ struct delta_index * create_delta_index(const void *buf, unsigned long bufsize)
initialization in create_delta(). */
entries = (bufsize - 1) / RABIN_WINDOW;
hsize = entries / 4;
- for (i = 4; (1 << i) < hsize && i < 31; i++);
+ for (i = 4; (1u << i) < hsize && i < 31; i++);
hsize = 1 << i;
hmask = hsize - 1;
@@ -392,7 +392,7 @@ create_delta(const struct delta_index *index,
outsize = max_size + MAX_OP_SIZE + 1;
if (max_size && outpos > max_size)
break;
- out = realloc(out, outsize);
+ out = xrealloc(out, outsize);
if (!out) {
free(tmp);
return NULL;
diff --git a/diff-lib.c b/diff-lib.c
index 116b5a9d68..fc69fb92a5 100644
--- a/diff-lib.c
+++ b/diff-lib.c
@@ -48,7 +48,7 @@ int run_diff_files(struct rev_info *revs, int silent_on_removed)
memcpy(dpath->path, ce->name, path_len);
dpath->path[path_len] = '\0';
dpath->mode = 0;
- memset(dpath->sha1, 0, 20);
+ hashclr(dpath->sha1);
memset(&(dpath->parent[0]), 0,
sizeof(struct combine_diff_parent)*5);
@@ -66,8 +66,7 @@ int run_diff_files(struct rev_info *revs, int silent_on_removed)
if (2 <= stage) {
int mode = ntohl(nce->ce_mode);
num_compare_stages++;
- memcpy(dpath->parent[stage-2].sha1,
- nce->sha1, 20);
+ hashcpy(dpath->parent[stage-2].sha1, nce->sha1);
dpath->parent[stage-2].mode =
canon_mode(mode);
dpath->parent[stage-2].status =
@@ -214,8 +213,33 @@ static int show_modified(struct rev_info *revs,
return -1;
}
+ if (revs->combine_merges && !cached &&
+ (hashcmp(sha1, old->sha1) || hashcmp(old->sha1, new->sha1))) {
+ struct combine_diff_path *p;
+ int pathlen = ce_namelen(new);
+
+ p = xmalloc(combine_diff_path_size(2, pathlen));
+ p->path = (char *) &p->parent[2];
+ p->next = NULL;
+ p->len = pathlen;
+ memcpy(p->path, new->name, pathlen);
+ p->path[pathlen] = 0;
+ p->mode = ntohl(mode);
+ hashclr(p->sha1);
+ memset(p->parent, 0, 2 * sizeof(struct combine_diff_parent));
+ p->parent[0].status = DIFF_STATUS_MODIFIED;
+ p->parent[0].mode = ntohl(new->ce_mode);
+ hashcpy(p->parent[0].sha1, new->sha1);
+ p->parent[1].status = DIFF_STATUS_MODIFIED;
+ p->parent[1].mode = ntohl(old->ce_mode);
+ hashcpy(p->parent[1].sha1, old->sha1);
+ show_combined_diff(p, 2, revs->dense_combined_merges, revs);
+ free(p);
+ return 0;
+ }
+
oldmode = old->ce_mode;
- if (mode == oldmode && !memcmp(sha1, old->sha1, 20) &&
+ if (mode == oldmode && !hashcmp(sha1, old->sha1) &&
!revs->diffopt.find_copies_harder)
return 0;
diff --git a/diff.c b/diff.c
index 8861b853e7..33153787b8 100644
--- a/diff.c
+++ b/diff.c
@@ -10,22 +10,23 @@
#include "diffcore.h"
#include "delta.h"
#include "xdiff-interface.h"
+#include "color.h"
static int use_size_cache;
-static int diff_detect_rename_default = 0;
+static int diff_detect_rename_default;
static int diff_rename_limit_default = -1;
-static int diff_use_color_default = 0;
+static int diff_use_color_default;
-/* "\033[1;38;5;2xx;48;5;2xxm\0" is 23 bytes */
-static char diff_colors[][24] = {
+static char diff_colors[][COLOR_MAXLEN] = {
"\033[m", /* reset */
- "", /* normal */
- "\033[1m", /* bold */
- "\033[36m", /* cyan */
- "\033[31m", /* red */
- "\033[32m", /* green */
- "\033[33m" /* yellow */
+ "", /* PLAIN (normal) */
+ "\033[1m", /* METAINFO (bold) */
+ "\033[36m", /* FRAGINFO (cyan) */
+ "\033[31m", /* OLD (red) */
+ "\033[32m", /* NEW (green) */
+ "\033[33m", /* COMMIT (yellow) */
+ "\033[41m", /* WHITESPACE (red background) */
};
static int parse_diff_color_slot(const char *var, int ofs)
@@ -42,122 +43,11 @@ static int parse_diff_color_slot(const char *var, int ofs)
return DIFF_FILE_NEW;
if (!strcasecmp(var+ofs, "commit"))
return DIFF_COMMIT;
+ if (!strcasecmp(var+ofs, "whitespace"))
+ return DIFF_WHITESPACE;
die("bad config variable '%s'", var);
}
-static int parse_color(const char *name, int len)
-{
- static const char * const color_names[] = {
- "normal", "black", "red", "green", "yellow",
- "blue", "magenta", "cyan", "white"
- };
- char *end;
- int i;
- for (i = 0; i < ARRAY_SIZE(color_names); i++) {
- const char *str = color_names[i];
- if (!strncasecmp(name, str, len) && !str[len])
- return i - 1;
- }
- i = strtol(name, &end, 10);
- if (*name && !*end && i >= -1 && i <= 255)
- return i;
- return -2;
-}
-
-static int parse_attr(const char *name, int len)
-{
- static const int attr_values[] = { 1, 2, 4, 5, 7 };
- static const char * const attr_names[] = {
- "bold", "dim", "ul", "blink", "reverse"
- };
- int i;
- for (i = 0; i < ARRAY_SIZE(attr_names); i++) {
- const char *str = attr_names[i];
- if (!strncasecmp(name, str, len) && !str[len])
- return attr_values[i];
- }
- return -1;
-}
-
-static void parse_diff_color_value(const char *value, const char *var, char *dst)
-{
- const char *ptr = value;
- int attr = -1;
- int fg = -2;
- int bg = -2;
-
- if (!strcasecmp(value, "reset")) {
- strcpy(dst, "\033[m");
- return;
- }
-
- /* [fg [bg]] [attr] */
- while (*ptr) {
- const char *word = ptr;
- int val, len = 0;
-
- while (word[len] && !isspace(word[len]))
- len++;
-
- ptr = word + len;
- while (*ptr && isspace(*ptr))
- ptr++;
-
- val = parse_color(word, len);
- if (val >= -1) {
- if (fg == -2) {
- fg = val;
- continue;
- }
- if (bg == -2) {
- bg = val;
- continue;
- }
- goto bad;
- }
- val = parse_attr(word, len);
- if (val < 0 || attr != -1)
- goto bad;
- attr = val;
- }
-
- if (attr >= 0 || fg >= 0 || bg >= 0) {
- int sep = 0;
-
- *dst++ = '\033';
- *dst++ = '[';
- if (attr >= 0) {
- *dst++ = '0' + attr;
- sep++;
- }
- if (fg >= 0) {
- if (sep++)
- *dst++ = ';';
- if (fg < 8) {
- *dst++ = '3';
- *dst++ = '0' + fg;
- } else {
- dst += sprintf(dst, "38;5;%d", fg);
- }
- }
- if (bg >= 0) {
- if (sep++)
- *dst++ = ';';
- if (bg < 8) {
- *dst++ = '4';
- *dst++ = '0' + bg;
- } else {
- dst += sprintf(dst, "48;5;%d", bg);
- }
- }
- *dst++ = 'm';
- }
- *dst = 0;
- return;
-bad:
- die("bad config value '%s' for variable '%s'", value, var);
-}
-
/*
* These are to give UI layer defaults.
* The core-level commands such as git-diff-files should
@@ -171,22 +61,7 @@ int git_diff_ui_config(const char *var, const char *value)
return 0;
}
if (!strcmp(var, "diff.color")) {
- if (!value)
- diff_use_color_default = 1; /* bool */
- else if (!strcasecmp(value, "auto")) {
- diff_use_color_default = 0;
- if (isatty(1) || (pager_in_use && pager_use_color)) {
- char *term = getenv("TERM");
- if (term && strcmp(term, "dumb"))
- diff_use_color_default = 1;
- }
- }
- else if (!strcasecmp(value, "never"))
- diff_use_color_default = 0;
- else if (!strcasecmp(value, "always"))
- diff_use_color_default = 1;
- else
- diff_use_color_default = git_config_bool(var, value);
+ diff_use_color_default = git_config_colorbool(var, value);
return 0;
}
if (!strcmp(var, "diff.renames")) {
@@ -201,7 +76,7 @@ int git_diff_ui_config(const char *var, const char *value)
}
if (!strncmp(var, "diff.color.", 11)) {
int slot = parse_diff_color_slot(var, 11);
- parse_diff_color_value(value, var, diff_colors[slot]);
+ color_parse(value, var, diff_colors[slot]);
return 0;
}
return git_default_config(var, value);
@@ -216,7 +91,7 @@ static char *quote_one(const char *str)
return NULL;
needlen = quote_c_style(str, NULL, NULL, 0);
if (!needlen)
- return strdup(str);
+ return xstrdup(str);
xp = xmalloc(needlen + 1);
quote_c_style(str, xp, NULL, 0);
return xp;
@@ -333,7 +208,7 @@ static void emit_rewrite_diff(const char *name_a,
diff_populate_filespec(two, 0);
lc_a = count_lines(one->data, one->size);
lc_b = count_lines(two->data, two->size);
- printf("--- %s\n+++ %s\n@@ -", name_a, name_b);
+ printf("--- a/%s\n+++ b/%s\n@@ -", name_a, name_b);
print_line_count(lc_a);
printf(" +");
print_line_count(lc_b);
@@ -511,9 +386,89 @@ const char *diff_get_color(int diff_use_color, enum color_diff ix)
return "";
}
+static void emit_line(const char *set, const char *reset, const char *line, int len)
+{
+ if (len > 0 && line[len-1] == '\n')
+ len--;
+ fputs(set, stdout);
+ fwrite(line, len, 1, stdout);
+ puts(reset);
+}
+
+static void emit_add_line(const char *reset, struct emit_callback *ecbdata, const char *line, int len)
+{
+ int col0 = ecbdata->nparents;
+ int last_tab_in_indent = -1;
+ int last_space_in_indent = -1;
+ int i;
+ int tail = len;
+ int need_highlight_leading_space = 0;
+ const char *ws = diff_get_color(ecbdata->color_diff, DIFF_WHITESPACE);
+ const char *set = diff_get_color(ecbdata->color_diff, DIFF_FILE_NEW);
+
+ if (!*ws) {
+ emit_line(set, reset, line, len);
+ return;
+ }
+
+ /* The line is a newly added line. Does it have funny leading
+ * whitespaces? In indent, SP should never precede a TAB.
+ */
+ for (i = col0; i < len; i++) {
+ if (line[i] == '\t') {
+ last_tab_in_indent = i;
+ if (0 <= last_space_in_indent)
+ need_highlight_leading_space = 1;
+ }
+ else if (line[i] == ' ')
+ last_space_in_indent = i;
+ else
+ break;
+ }
+ fputs(set, stdout);
+ fwrite(line, col0, 1, stdout);
+ fputs(reset, stdout);
+ if (((i == len) || line[i] == '\n') && i != col0) {
+ /* The whole line was indent */
+ emit_line(ws, reset, line + col0, len - col0);
+ return;
+ }
+ i = col0;
+ if (need_highlight_leading_space) {
+ while (i < last_tab_in_indent) {
+ if (line[i] == ' ') {
+ fputs(ws, stdout);
+ putchar(' ');
+ fputs(reset, stdout);
+ }
+ else
+ putchar(line[i]);
+ i++;
+ }
+ }
+ tail = len - 1;
+ if (line[tail] == '\n' && i < tail)
+ tail--;
+ while (i < tail) {
+ if (!isspace(line[tail]))
+ break;
+ tail--;
+ }
+ if ((i < tail && line[tail + 1] != '\n')) {
+ /* This has whitespace between tail+1..len */
+ fputs(set, stdout);
+ fwrite(line + i, tail - i + 1, 1, stdout);
+ fputs(reset, stdout);
+ emit_line(ws, reset, line + tail + 1, len - tail - 1);
+ }
+ else
+ emit_line(set, reset, line + i, len - i);
+}
+
static void fn_out_consume(void *priv, char *line, unsigned long len)
{
int i;
+ int color;
struct emit_callback *ecbdata = priv;
const char *set = diff_get_color(ecbdata->color_diff, DIFF_METAINFO);
const char *reset = diff_get_color(ecbdata->color_diff, DIFF_RESET);
@@ -531,45 +486,52 @@ static void fn_out_consume(void *priv, char *line, unsigned long len)
;
if (2 <= i && i < len && line[i] == ' ') {
ecbdata->nparents = i - 1;
- set = diff_get_color(ecbdata->color_diff, DIFF_FRAGINFO);
+ emit_line(diff_get_color(ecbdata->color_diff, DIFF_FRAGINFO),
+ reset, line, len);
+ return;
}
- else if (len < ecbdata->nparents)
+
+ if (len < ecbdata->nparents) {
set = reset;
- else {
- int nparents = ecbdata->nparents;
- int color = DIFF_PLAIN;
- if (ecbdata->diff_words && nparents != 1)
- /* fall back to normal diff */
- free_diff_words_data(ecbdata);
- if (ecbdata->diff_words) {
- if (line[0] == '-') {
- diff_words_append(line, len,
- &ecbdata->diff_words->minus);
- return;
- } else if (line[0] == '+') {
- diff_words_append(line, len,
- &ecbdata->diff_words->plus);
- return;
- }
- if (ecbdata->diff_words->minus.text.size ||
- ecbdata->diff_words->plus.text.size)
- diff_words_show(ecbdata->diff_words);
- line++;
- len--;
- } else
- for (i = 0; i < nparents && len; i++) {
- if (line[i] == '-')
- color = DIFF_FILE_OLD;
- else if (line[i] == '+')
- color = DIFF_FILE_NEW;
- }
- set = diff_get_color(ecbdata->color_diff, color);
+ emit_line(reset, reset, line, len);
+ return;
}
- if (len > 0 && line[len-1] == '\n')
+
+ color = DIFF_PLAIN;
+ if (ecbdata->diff_words && ecbdata->nparents != 1)
+ /* fall back to normal diff */
+ free_diff_words_data(ecbdata);
+ if (ecbdata->diff_words) {
+ if (line[0] == '-') {
+ diff_words_append(line, len,
+ &ecbdata->diff_words->minus);
+ return;
+ } else if (line[0] == '+') {
+ diff_words_append(line, len,
+ &ecbdata->diff_words->plus);
+ return;
+ }
+ if (ecbdata->diff_words->minus.text.size ||
+ ecbdata->diff_words->plus.text.size)
+ diff_words_show(ecbdata->diff_words);
+ line++;
len--;
- fputs (set, stdout);
- fwrite (line, len, 1, stdout);
- puts (reset);
+ emit_line(set, reset, line, len);
+ return;
+ }
+ for (i = 0; i < ecbdata->nparents && len; i++) {
+ if (line[i] == '-')
+ color = DIFF_FILE_OLD;
+ else if (line[i] == '+')
+ color = DIFF_FILE_NEW;
+ }
+
+ if (color != DIFF_FILE_NEW) {
+ emit_line(diff_get_color(ecbdata->color_diff, color),
+ reset, line, len);
+ return;
+ }
+ emit_add_line(reset, ecbdata, line, len);
}
static char *pprint_rename(const char *a, const char *b)
@@ -658,7 +620,7 @@ static struct diffstat_file *diffstat_add(struct diffstat_t *diffstat,
x->is_renamed = 1;
}
else
- x->name = strdup(name_a);
+ x->name = xstrdup(name_a);
return x;
}
@@ -673,21 +635,76 @@ static void diffstat_consume(void *priv, char *line, unsigned long len)
x->deleted++;
}
-static const char pluses[] = "++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++";
-static const char minuses[]= "----------------------------------------------------------------------";
const char mime_boundary_leader[] = "------------";
-static void show_stats(struct diffstat_t* data)
+static int scale_linear(int it, int width, int max_change)
+{
+ /*
+ * make sure that at least one '-' is printed if there were deletions,
+ * and likewise for '+'.
+ */
+ if (max_change < 2)
+ return it;
+ return ((it - 1) * (width - 1) + max_change - 1) / (max_change - 1);
+}
+
+static void show_name(const char *prefix, const char *name, int len,
+ const char *reset, const char *set)
+{
+ printf(" %s%s%-*s%s |", set, prefix, len, name, reset);
+}
+
+static void show_graph(char ch, int cnt, const char *set, const char *reset)
+{
+ if (cnt <= 0)
+ return;
+ printf("%s", set);
+ while (cnt--)
+ putchar(ch);
+ printf("%s", reset);
+}
+
+static void show_stats(struct diffstat_t* data, struct diff_options *options)
{
int i, len, add, del, total, adds = 0, dels = 0;
- int max, max_change = 0, max_len = 0;
+ int max_change = 0, max_len = 0;
int total_files = data->nr;
+ int width, name_width;
+ const char *reset, *set, *add_c, *del_c;
if (data->nr == 0)
return;
+ width = options->stat_width ? options->stat_width : 80;
+ name_width = options->stat_name_width ? options->stat_name_width : 50;
+
+ /* Sanity: give at least 5 columns to the graph,
+ * but leave at least 10 columns for the name.
+ */
+ if (width < name_width + 15) {
+ if (name_width <= 25)
+ width = name_width + 15;
+ else
+ name_width = width - 15;
+ }
+
+ /* Find the longest filename and max number of changes */
+ reset = diff_get_color(options->color_diff, DIFF_RESET);
+ set = diff_get_color(options->color_diff, DIFF_PLAIN);
+ add_c = diff_get_color(options->color_diff, DIFF_FILE_NEW);
+ del_c = diff_get_color(options->color_diff, DIFF_FILE_OLD);
+
for (i = 0; i < data->nr; i++) {
struct diffstat_file *file = data->files[i];
+ int change = file->added + file->deleted;
+
+ len = quote_c_style(file->name, NULL, NULL, 0);
+ if (len) {
+ char *qname = xmalloc(len + 1);
+ quote_c_style(file->name, qname, NULL, 0);
+ free(file->name);
+ file->name = qname;
+ }
len = strlen(file->name);
if (max_len < len)
@@ -695,54 +712,53 @@ static void show_stats(struct diffstat_t* data)
if (file->is_binary || file->is_unmerged)
continue;
- if (max_change < file->added + file->deleted)
- max_change = file->added + file->deleted;
+ if (max_change < change)
+ max_change = change;
}
+ /* Compute the width of the graph part;
+ * 10 is for one blank at the beginning of the line plus
+ * " | count " between the name and the graph.
+ *
+ * From here on, name_width is the width of the name area,
+ * and width is the width of the graph area.
+ */
+ name_width = (name_width < max_len) ? name_width : max_len;
+ if (width < (name_width + 10) + max_change)
+ width = width - (name_width + 10);
+ else
+ width = max_change;
+
for (i = 0; i < data->nr; i++) {
const char *prefix = "";
char *name = data->files[i]->name;
int added = data->files[i]->added;
int deleted = data->files[i]->deleted;
-
- if (0 < (len = quote_c_style(name, NULL, NULL, 0))) {
- char *qname = xmalloc(len + 1);
- quote_c_style(name, qname, NULL, 0);
- free(name);
- data->files[i]->name = name = qname;
- }
+ int name_len;
/*
* "scale" the filename
*/
- len = strlen(name);
- max = max_len;
- if (max > 50)
- max = 50;
- if (len > max) {
+ len = name_width;
+ name_len = strlen(name);
+ if (name_width < name_len) {
char *slash;
prefix = "...";
- max -= 3;
- name += len - max;
+ len -= 3;
+ name += name_len - len;
slash = strchr(name, '/');
if (slash)
name = slash;
}
- len = max;
-
- /*
- * scale the add/delete
- */
- max = max_change;
- if (max + len > 70)
- max = 70 - len;
if (data->files[i]->is_binary) {
- printf(" %s%-*s | Bin\n", prefix, len, name);
+ show_name(prefix, name, len, reset, set);
+ printf(" Bin\n");
goto free_diffstat_file;
}
else if (data->files[i]->is_unmerged) {
- printf(" %s%-*s | Unmerged\n", prefix, len, name);
+ show_name(prefix, name, len, reset, set);
+ printf(" Unmerged\n");
goto free_diffstat_file;
}
else if (!data->files[i]->is_renamed &&
@@ -751,27 +767,49 @@ static void show_stats(struct diffstat_t* data)
goto free_diffstat_file;
}
+ /*
+ * scale the add/delete
+ */
add = added;
del = deleted;
total = add + del;
adds += add;
dels += del;
- if (max_change > 0) {
- total = (total * max + max_change / 2) / max_change;
- add = (add * max + max_change / 2) / max_change;
- del = total - add;
+ if (width <= max_change) {
+ add = scale_linear(add, width, max_change);
+ del = scale_linear(del, width, max_change);
+ total = add + del;
}
- printf(" %s%-*s |%5d %.*s%.*s\n", prefix,
- len, name, added + deleted,
- add, pluses, del, minuses);
+ show_name(prefix, name, len, reset, set);
+ printf("%5d ", added + deleted);
+ show_graph('+', add, add_c, reset);
+ show_graph('-', del, del_c, reset);
+ putchar('\n');
free_diffstat_file:
free(data->files[i]->name);
free(data->files[i]);
}
free(data->files);
- printf(" %d files changed, %d insertions(+), %d deletions(-)\n",
- total_files, adds, dels);
+ printf("%s %d files changed, %d insertions(+), %d deletions(-)%s\n",
+ set, total_files, adds, dels, reset);
+}
+
+static void show_numstat(struct diffstat_t* data, struct diff_options *options)
+{
+ int i;
+
+ for (i = 0; i < data->nr; i++) {
+ struct diffstat_file *file = data->files[i];
+
+ printf("%d\t%d\t", file->added, file->deleted);
+ if (options->line_termination &&
+ quote_c_style(file->name, NULL, NULL, 0))
+ quote_c_style(file->name, NULL, stdout, 0);
+ else
+ fputs(file->name, stdout);
+ putchar(options->line_termination);
+ }
}
struct checkdiff_t {
@@ -838,7 +876,7 @@ static unsigned char *deflate_it(char *data,
return deflated;
}
-static void emit_binary_diff(mmfile_t *one, mmfile_t *two)
+static void emit_binary_diff_body(mmfile_t *one, mmfile_t *two)
{
void *cp;
void *delta;
@@ -849,7 +887,6 @@ static void emit_binary_diff(mmfile_t *one, mmfile_t *two)
unsigned long deflate_size;
unsigned long data_size;
- printf("GIT binary patch\n");
/* We could do deflated delta, or we could do just deflated two,
* whichever is smaller.
*/
@@ -898,15 +935,20 @@ static void emit_binary_diff(mmfile_t *one, mmfile_t *two)
free(data);
}
+static void emit_binary_diff(mmfile_t *one, mmfile_t *two)
+{
+ printf("GIT binary patch\n");
+ emit_binary_diff_body(one, two);
+ emit_binary_diff_body(two, one);
+}
+
#define FIRST_FEW_BYTES 8000
static int mmfile_is_binary(mmfile_t *mf)
{
long sz = mf->size;
if (FIRST_FEW_BYTES < sz)
sz = FIRST_FEW_BYTES;
- if (memchr(mf->ptr, 0, sz))
- return 1;
- return 0;
+ return !!memchr(mf->ptr, 0, sz);
}
static void builtin_diff(const char *name_a,
@@ -1103,8 +1145,8 @@ void fill_filespec(struct diff_filespec *spec, const unsigned char *sha1,
{
if (mode) {
spec->mode = canon_mode(mode);
- memcpy(spec->sha1, sha1, 20);
- spec->sha1_valid = !!memcmp(sha1, null_sha1, 20);
+ hashcpy(spec->sha1, sha1);
+ spec->sha1_valid = !is_null_sha1(sha1);
}
}
@@ -1142,7 +1184,7 @@ static int work_tree_matches(const char *name, const unsigned char *sha1)
if ((lstat(name, &st) < 0) ||
!S_ISREG(st.st_mode) || /* careful! */
ce_match_stat(ce, &st, 0) ||
- memcmp(sha1, ce->sha1, 20))
+ hashcmp(sha1, ce->sha1))
return 0;
/* we return 1 only when we can stat, it is a regular file,
* stat information matches, and sha1 recorded in the cache
@@ -1170,7 +1212,7 @@ static struct sha1_size_cache *locate_size_cache(unsigned char *sha1,
while (last > first) {
int cmp, next = (last + first) >> 1;
e = sha1_size_cache[next];
- cmp = memcmp(e->sha1, sha1, 20);
+ cmp = hashcmp(e->sha1, sha1);
if (!cmp)
return e;
if (cmp < 0) {
@@ -1196,7 +1238,7 @@ static struct sha1_size_cache *locate_size_cache(unsigned char *sha1,
sizeof(*sha1_size_cache));
e = xmalloc(sizeof(struct sha1_size_cache));
sha1_size_cache[first] = e;
- memcpy(e->sha1, sha1, 20);
+ hashcpy(e->sha1, sha1);
e->size = size;
return e;
}
@@ -1518,7 +1560,7 @@ static void diff_fill_sha1_info(struct diff_filespec *one)
}
}
else
- memset(one->sha1, 0, 20);
+ hashclr(one->sha1);
}
static void run_diff(struct diff_filepair *p, struct diff_options *o)
@@ -1581,9 +1623,15 @@ static void run_diff(struct diff_filepair *p, struct diff_options *o)
;
}
- if (memcmp(one->sha1, two->sha1, 20)) {
+ if (hashcmp(one->sha1, two->sha1)) {
int abbrev = o->full_index ? 40 : DEFAULT_ABBREV;
+ if (o->binary) {
+ mmfile_t mf;
+ if ((!fill_mmfile(&mf, one) && mmfile_is_binary(&mf)) ||
+ (!fill_mmfile(&mf, two) && mmfile_is_binary(&mf)))
+ abbrev = 40;
+ }
len += snprintf(msg + len, sizeof(msg) - len,
"index %.*s..%.*s",
abbrev, sha1_to_hex(one->sha1),
@@ -1700,6 +1748,7 @@ int diff_setup_done(struct diff_options *options)
DIFF_FORMAT_CHECKDIFF |
DIFF_FORMAT_NO_OUTPUT))
options->output_format &= ~(DIFF_FORMAT_RAW |
+ DIFF_FORMAT_NUMSTAT |
DIFF_FORMAT_DIFFSTAT |
DIFF_FORMAT_SUMMARY |
DIFF_FORMAT_PATCH);
@@ -1709,7 +1758,9 @@ int diff_setup_done(struct diff_options *options)
* recursive bits for other formats here.
*/
if (options->output_format & (DIFF_FORMAT_PATCH |
+ DIFF_FORMAT_NUMSTAT |
DIFF_FORMAT_DIFFSTAT |
+ DIFF_FORMAT_SUMMARY |
DIFF_FORMAT_CHECKDIFF))
options->recursive = 1;
/*
@@ -1797,8 +1848,36 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)
else if (!strcmp(arg, "--patch-with-raw")) {
options->output_format |= DIFF_FORMAT_PATCH | DIFF_FORMAT_RAW;
}
- else if (!strcmp(arg, "--stat"))
+ else if (!strcmp(arg, "--numstat")) {
+ options->output_format |= DIFF_FORMAT_NUMSTAT;
+ }
+ else if (!strncmp(arg, "--stat", 6)) {
+ char *end;
+ int width = options->stat_width;
+ int name_width = options->stat_name_width;
+ arg += 6;
+ end = (char *)arg;
+
+ switch (*arg) {
+ case '-':
+ if (!strncmp(arg, "-width=", 7))
+ width = strtoul(arg + 7, &end, 10);
+ else if (!strncmp(arg, "-name-width=", 12))
+ name_width = strtoul(arg + 12, &end, 10);
+ break;
+ case '=':
+ width = strtoul(arg+1, &end, 10);
+ if (*end == ',')
+ name_width = strtoul(end+1, &end, 10);
+ }
+
+ /* Important! This checks all the error cases! */
+ if (*end)
+ return 0;
options->output_format |= DIFF_FORMAT_DIFFSTAT;
+ options->stat_name_width = name_width;
+ options->stat_width = width;
+ }
else if (!strcmp(arg, "--check"))
options->output_format |= DIFF_FORMAT_CHECKDIFF;
else if (!strcmp(arg, "--summary"))
@@ -1814,7 +1893,7 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)
options->full_index = 1;
else if (!strcmp(arg, "--binary")) {
options->output_format |= DIFF_FORMAT_PATCH;
- options->full_index = options->binary = 1;
+ options->binary = 1;
}
else if (!strcmp(arg, "-a") || !strcmp(arg, "--text")) {
options->text = 1;
@@ -2100,7 +2179,7 @@ int diff_unmodified_pair(struct diff_filepair *p)
* dealing with a change.
*/
if (one->sha1_valid && two->sha1_valid &&
- !memcmp(one->sha1, two->sha1, sizeof(one->sha1)))
+ !hashcmp(one->sha1, two->sha1))
return 1; /* no change */
if (!one->sha1_valid && !two->sha1_valid)
return 1; /* both look at the same file on the filesystem. */
@@ -2239,7 +2318,7 @@ static void diff_resolve_rename_copy(void)
if (!p->status)
p->status = DIFF_STATUS_RENAMED;
}
- else if (memcmp(p->one->sha1, p->two->sha1, 20) ||
+ else if (hashcmp(p->one->sha1, p->two->sha1) ||
p->one->mode != p->two->mode)
p->status = DIFF_STATUS_MODIFIED;
else {
@@ -2546,7 +2625,7 @@ void diff_flush(struct diff_options *options)
separator++;
}
- if (output_format & DIFF_FORMAT_DIFFSTAT) {
+ if (output_format & (DIFF_FORMAT_DIFFSTAT|DIFF_FORMAT_NUMSTAT)) {
struct diffstat_t diffstat;
memset(&diffstat, 0, sizeof(struct diffstat_t));
@@ -2556,7 +2635,10 @@ void diff_flush(struct diff_options *options)
if (check_pair_status(p))
diff_flush_stat(p, options, &diffstat);
}
- show_stats(&diffstat);
+ if (output_format & DIFF_FORMAT_NUMSTAT)
+ show_numstat(&diffstat, options);
+ if (output_format & DIFF_FORMAT_DIFFSTAT)
+ show_stats(&diffstat, options);
separator++;
}
@@ -2583,6 +2665,9 @@ void diff_flush(struct diff_options *options)
}
}
+ if (output_format & DIFF_FORMAT_CALLBACK)
+ options->format_callback(q, options, options->format_callback_data);
+
for (i = 0; i < q->nr; i++)
diff_free_filepair(q->queue[i]);
free_queue:
diff --git a/diff.h b/diff.h
index b007240a5d..101b2b505d 100644
--- a/diff.h
+++ b/diff.h
@@ -8,6 +8,7 @@
struct rev_info;
struct diff_options;
+struct diff_queue_struct;
typedef void (*change_fn_t)(struct diff_options *options,
unsigned old_mode, unsigned new_mode,
@@ -20,25 +21,32 @@ typedef void (*add_remove_fn_t)(struct diff_options *options,
const unsigned char *sha1,
const char *base, const char *path);
+typedef void (*diff_format_fn_t)(struct diff_queue_struct *q,
+ struct diff_options *options, void *data);
+
#define DIFF_FORMAT_RAW 0x0001
#define DIFF_FORMAT_DIFFSTAT 0x0002
-#define DIFF_FORMAT_SUMMARY 0x0004
-#define DIFF_FORMAT_PATCH 0x0008
+#define DIFF_FORMAT_NUMSTAT 0x0004
+#define DIFF_FORMAT_SUMMARY 0x0008
+#define DIFF_FORMAT_PATCH 0x0010
/* These override all above */
-#define DIFF_FORMAT_NAME 0x0010
-#define DIFF_FORMAT_NAME_STATUS 0x0020
-#define DIFF_FORMAT_CHECKDIFF 0x0040
+#define DIFF_FORMAT_NAME 0x0100
+#define DIFF_FORMAT_NAME_STATUS 0x0200
+#define DIFF_FORMAT_CHECKDIFF 0x0400
/* Same as output_format = 0 but we know that -s flag was given
* and we should not give default value to output_format.
*/
-#define DIFF_FORMAT_NO_OUTPUT 0x0080
+#define DIFF_FORMAT_NO_OUTPUT 0x0800
+
+#define DIFF_FORMAT_CALLBACK 0x1000
struct diff_options {
const char *filter;
const char *orderfile;
const char *pickaxe;
+ const char *single_follow;
unsigned recursive:1,
tree_in_recursive:1,
binary:1,
@@ -63,11 +71,16 @@ struct diff_options {
const char *stat_sep;
long xdl_opts;
+ int stat_width;
+ int stat_name_width;
+
int nr_paths;
const char **paths;
int *pathlens;
change_fn_t change;
add_remove_fn_t add_remove;
+ diff_format_fn_t format_callback;
+ void *format_callback_data;
};
enum color_diff {
@@ -78,6 +91,7 @@ enum color_diff {
DIFF_FILE_OLD = 4,
DIFF_FILE_NEW = 5,
DIFF_COMMIT = 6,
+ DIFF_WHITESPACE = 7,
};
const char *diff_get_color(int diff_use_color, enum color_diff ix);
@@ -89,6 +103,8 @@ extern int diff_tree(struct tree_desc *t1, struct tree_desc *t2,
const char *base, struct diff_options *opt);
extern int diff_tree_sha1(const unsigned char *old, const unsigned char *new,
const char *base, struct diff_options *opt);
+extern int diff_root_tree_sha1(const unsigned char *new, const char *base,
+ struct diff_options *opt);
struct combine_diff_path {
struct combine_diff_path *next;
@@ -158,6 +174,7 @@ extern void diffcore_std_no_resolve(struct diff_options *);
" --patch-with-raw\n" \
" output both a patch and the diff-raw format.\n" \
" --stat show diffstat instead of patch.\n" \
+" --numstat show numeric diffstat instead of patch.\n" \
" --patch-with-stat\n" \
" output a patch and prepend its diffstat.\n" \
" --name-only show only names of changed files.\n" \
diff --git a/diffcore-break.c b/diffcore-break.c
index ed0e14c6d8..acb18db1db 100644
--- a/diffcore-break.c
+++ b/diffcore-break.c
@@ -56,7 +56,7 @@ static int should_break(struct diff_filespec *src,
return 0; /* leave symlink rename alone */
if (src->sha1_valid && dst->sha1_valid &&
- !memcmp(src->sha1, dst->sha1, 20))
+ !hashcmp(src->sha1, dst->sha1))
return 0; /* they are the same */
if (diff_populate_filespec(src, 0) || diff_populate_filespec(dst, 0))
diff --git a/diffcore-rename.c b/diffcore-rename.c
index 0ec488a903..57a74b6bb8 100644
--- a/diffcore-rename.c
+++ b/diffcore-rename.c
@@ -101,7 +101,7 @@ static int is_exact_match(struct diff_filespec *src,
int contents_too)
{
if (src->sha1_valid && dst->sha1_valid &&
- !memcmp(src->sha1, dst->sha1, 20))
+ !hashcmp(src->sha1, dst->sha1))
return 1;
if (!contents_too)
return 0;
@@ -256,11 +256,15 @@ void diffcore_rename(struct diff_options *options)
for (i = 0; i < q->nr; i++) {
struct diff_filepair *p = q->queue[i];
- if (!DIFF_FILE_VALID(p->one))
+ if (!DIFF_FILE_VALID(p->one)) {
if (!DIFF_FILE_VALID(p->two))
continue; /* unmerged */
+ else if (options->single_follow &&
+ strcmp(options->single_follow, p->two->path))
+ continue; /* not interested */
else
locate_rename_dst(p->two, 1);
+ }
else if (!DIFF_FILE_VALID(p->two)) {
/* If the source is a broken "delete", and
* they did not really want to get broken,
diff --git a/dir.c b/dir.c
index 092d07736c..96389b32e6 100644
--- a/dir.c
+++ b/dir.c
@@ -101,8 +101,8 @@ void add_exclude(const char *string, const char *base,
x->baselen = baselen;
if (which->nr == which->alloc) {
which->alloc = alloc_nr(which->alloc);
- which->excludes = realloc(which->excludes,
- which->alloc * sizeof(x));
+ which->excludes = xrealloc(which->excludes,
+ which->alloc * sizeof(x));
}
which->excludes[which->nr++] = x;
}
@@ -112,17 +112,15 @@ static int add_excludes_from_file_1(const char *fname,
int baselen,
struct exclude_list *which)
{
+ struct stat st;
int fd, i;
long size;
char *buf, *entry;
fd = open(fname, O_RDONLY);
- if (fd < 0)
+ if (fd < 0 || fstat(fd, &st) < 0)
goto err;
- size = lseek(fd, 0, SEEK_END);
- if (size < 0)
- goto err;
- lseek(fd, 0, SEEK_SET);
+ size = st.st_size;
if (size == 0) {
close(fd);
return 0;
@@ -285,7 +283,7 @@ static int dir_exists(const char *dirname, int len)
* Also, we ignore the name ".git" (even if it is not a directory).
* That likely will not change.
*/
-static int read_directory_recursive(struct dir_struct *dir, const char *path, const char *base, int baselen)
+static int read_directory_recursive(struct dir_struct *dir, const char *path, const char *base, int baselen, int check_only)
{
DIR *fdir = opendir(path);
int contents = 0;
@@ -293,7 +291,7 @@ static int read_directory_recursive(struct dir_struct *dir, const char *path, co
if (fdir) {
int exclude_stk;
struct dirent *de;
- char fullname[MAXPATHLEN + 1];
+ char fullname[PATH_MAX + 1];
memcpy(fullname, base, baselen);
exclude_stk = push_exclude_per_directory(dir, base, baselen);
@@ -316,7 +314,6 @@ static int read_directory_recursive(struct dir_struct *dir, const char *path, co
switch (DTYPE(de)) {
struct stat st;
- int subdir, rewind_base;
default:
continue;
case DT_UNKNOWN:
@@ -330,26 +327,30 @@ static int read_directory_recursive(struct dir_struct *dir, const char *path, co
case DT_DIR:
memcpy(fullname + baselen + len, "/", 2);
len++;
- rewind_base = dir->nr;
- subdir = read_directory_recursive(dir, fullname, fullname,
- baselen + len);
if (dir->show_other_directories &&
- (subdir || !dir->hide_empty_directories) &&
!dir_exists(fullname, baselen + len)) {
- /* Rewind the read subdirectory */
- while (dir->nr > rewind_base)
- free(dir->entries[--dir->nr]);
+ if (dir->hide_empty_directories &&
+ !read_directory_recursive(dir,
+ fullname, fullname,
+ baselen + len, 1))
+ continue;
break;
}
- contents += subdir;
+
+ contents += read_directory_recursive(dir,
+ fullname, fullname, baselen + len, 0);
continue;
case DT_REG:
case DT_LNK:
break;
}
- add_name(dir, fullname, baselen + len);
contents++;
+ if (check_only)
+ goto exit_early;
+ else
+ add_name(dir, fullname, baselen + len);
}
+exit_early:
closedir(fdir);
pop_exclude_per_directory(dir, exclude_stk);
@@ -395,7 +396,14 @@ int read_directory(struct dir_struct *dir, const char *path, const char *base, i
}
}
- read_directory_recursive(dir, path, base, baselen);
+ read_directory_recursive(dir, path, base, baselen, 0);
qsort(dir->entries, dir->nr, sizeof(struct dir_entry *), cmp_name);
return dir->nr;
}
+
+int
+file_exists(const char *f)
+{
+ struct stat sb;
+ return stat(f, &sb) == 0;
+}
diff --git a/dir.h b/dir.h
index 56a1b7fce2..313f8ab64e 100644
--- a/dir.h
+++ b/dir.h
@@ -47,5 +47,6 @@ extern int excluded(struct dir_struct *, const char *);
extern void add_excludes_from_file(struct dir_struct *, const char *fname);
extern void add_exclude(const char *string, const char *base,
int baselen, struct exclude_list *which);
+extern int file_exists(const char *);
#endif
diff --git a/dump-cache-tree.c b/dump-cache-tree.c
index 1ccaf51773..1f73f1ea7d 100644
--- a/dump-cache-tree.c
+++ b/dump-cache-tree.c
@@ -33,7 +33,7 @@ static int dump_cache_tree(struct cache_tree *it,
}
else {
dump_one(it, pfx, "");
- if (memcmp(it->sha1, ref->sha1, 20) ||
+ if (hashcmp(it->sha1, ref->sha1) ||
ref->entry_count != it->entry_count ||
ref->subtree_nr != it->subtree_nr) {
dump_one(ref, pfx, "#(ref) ");
diff --git a/entry.c b/entry.c
index 793724fd52..b2ea0efa82 100644
--- a/entry.c
+++ b/entry.c
@@ -135,7 +135,7 @@ static int write_entry(struct cache_entry *ce, char *path, struct checkout *stat
int checkout_entry(struct cache_entry *ce, struct checkout *state, char *topath)
{
- static char path[MAXPATHLEN+1];
+ static char path[PATH_MAX + 1];
struct stat st;
int len = state->base_dir_len;
@@ -172,5 +172,3 @@ int checkout_entry(struct cache_entry *ce, struct checkout *state, char *topath)
create_directories(path, state);
return write_entry(ce, path, state, 0);
}
-
-
diff --git a/environment.c b/environment.c
index 87162b2572..84d870ca4e 100644
--- a/environment.c
+++ b/environment.c
@@ -13,20 +13,21 @@ char git_default_email[MAX_GITNAME];
char git_default_name[MAX_GITNAME];
int use_legacy_headers = 1;
int trust_executable_bit = 1;
-int assume_unchanged = 0;
-int prefer_symlink_refs = 0;
-int log_all_ref_updates = 0;
+int assume_unchanged;
+int prefer_symlink_refs;
+int log_all_ref_updates;
int warn_ambiguous_refs = 1;
-int repository_format_version = 0;
+int repository_format_version;
char git_commit_encoding[MAX_ENCODING_LENGTH] = "utf-8";
int shared_repository = PERM_UMASK;
-const char *apply_default_whitespace = NULL;
+const char *apply_default_whitespace;
int zlib_compression_level = Z_DEFAULT_COMPRESSION;
int pager_in_use;
int pager_use_color = 1;
-static char *git_dir, *git_object_dir, *git_index_file, *git_refs_dir,
- *git_graft_file;
+static const char *git_dir;
+static char *git_object_dir, *git_index_file, *git_refs_dir, *git_graft_file;
+
static void setup_git_env(void)
{
git_dir = getenv(GIT_DIR_ENVIRONMENT);
@@ -46,10 +47,10 @@ static void setup_git_env(void)
}
git_graft_file = getenv(GRAFT_ENVIRONMENT);
if (!git_graft_file)
- git_graft_file = strdup(git_path("info/grafts"));
+ git_graft_file = xstrdup(git_path("info/grafts"));
}
-char *get_git_dir(void)
+const char *get_git_dir(void)
{
if (!git_dir)
setup_git_env();
diff --git a/exec_cmd.c b/exec_cmd.c
index 62f51fcd6e..5d6a1247b4 100644
--- a/exec_cmd.c
+++ b/exec_cmd.c
@@ -5,7 +5,7 @@
extern char **environ;
static const char *builtin_exec_path = GIT_EXEC_PATH;
-static const char *current_exec_path = NULL;
+static const char *current_exec_path;
void git_set_exec_path(const char *exec_path)
{
@@ -97,26 +97,12 @@ int execv_git_cmd(const char **argv)
tmp = argv[0];
argv[0] = git_command;
- if (getenv("GIT_TRACE")) {
- const char **p = argv;
- fputs("trace: exec:", stderr);
- while (*p) {
- fputc(' ', stderr);
- sq_quote_print(stderr, *p);
- ++p;
- }
- putc('\n', stderr);
- fflush(stderr);
- }
+ trace_argv_printf(argv, -1, "trace: exec:");
/* execve() can only ever return if it fails */
execve(git_command, (char **)argv, environ);
- if (getenv("GIT_TRACE")) {
- fprintf(stderr, "trace: exec failed: %s\n",
- strerror(errno));
- fflush(stderr);
- }
+ trace_printf("trace: exec failed: %s\n", strerror(errno));
argv[0] = tmp;
}
diff --git a/fetch-clone.c b/fetch-clone.c
deleted file mode 100644
index 5e84c4620f..0000000000
--- a/fetch-clone.c
+++ /dev/null
@@ -1,301 +0,0 @@
-#include "cache.h"
-#include "exec_cmd.h"
-#include "pkt-line.h"
-#include <sys/wait.h>
-#include <sys/time.h>
-
-static int finish_pack(const char *pack_tmp_name, const char *me)
-{
- int pipe_fd[2];
- pid_t pid;
- char idx[PATH_MAX];
- char final[PATH_MAX];
- char hash[41];
- unsigned char sha1[20];
- char *cp;
- int err = 0;
-
- if (pipe(pipe_fd) < 0)
- die("%s: unable to set up pipe", me);
-
- strcpy(idx, pack_tmp_name); /* ".git/objects/pack-XXXXXX" */
- cp = strrchr(idx, '/');
- memcpy(cp, "/pidx", 5);
-
- pid = fork();
- if (pid < 0)
- die("%s: unable to fork off git-index-pack", me);
- if (!pid) {
- close(0);
- dup2(pipe_fd[1], 1);
- close(pipe_fd[0]);
- close(pipe_fd[1]);
- execl_git_cmd("index-pack", "-o", idx, pack_tmp_name, NULL);
- error("cannot exec git-index-pack <%s> <%s>",
- idx, pack_tmp_name);
- exit(1);
- }
- close(pipe_fd[1]);
- if (read(pipe_fd[0], hash, 40) != 40) {
- error("%s: unable to read from git-index-pack", me);
- err = 1;
- }
- close(pipe_fd[0]);
-
- for (;;) {
- int status, code;
- int retval = waitpid(pid, &status, 0);
-
- if (retval < 0) {
- if (errno == EINTR)
- continue;
- error("waitpid failed (%s)", strerror(errno));
- goto error_die;
- }
- if (WIFSIGNALED(status)) {
- int sig = WTERMSIG(status);
- error("git-index-pack died of signal %d", sig);
- goto error_die;
- }
- if (!WIFEXITED(status)) {
- error("git-index-pack died of unnatural causes %d",
- status);
- goto error_die;
- }
- code = WEXITSTATUS(status);
- if (code) {
- error("git-index-pack died with error code %d", code);
- goto error_die;
- }
- if (err)
- goto error_die;
- break;
- }
- hash[40] = 0;
- if (get_sha1_hex(hash, sha1)) {
- error("git-index-pack reported nonsense '%s'", hash);
- goto error_die;
- }
- /* Now we have pack in pack_tmp_name[], and
- * idx in idx[]; rename them to their final names.
- */
- snprintf(final, sizeof(final),
- "%s/pack/pack-%s.pack", get_object_directory(), hash);
- move_temp_to_file(pack_tmp_name, final);
- chmod(final, 0444);
- snprintf(final, sizeof(final),
- "%s/pack/pack-%s.idx", get_object_directory(), hash);
- move_temp_to_file(idx, final);
- chmod(final, 0444);
- return 0;
-
- error_die:
- unlink(idx);
- unlink(pack_tmp_name);
- exit(1);
-}
-
-static pid_t setup_sideband(int sideband, const char *me, int fd[2], int xd[2])
-{
- pid_t side_pid;
-
- if (!sideband) {
- fd[0] = xd[0];
- fd[1] = xd[1];
- return 0;
- }
- /* xd[] is talking with upload-pack; subprocess reads from
- * xd[0], spits out band#2 to stderr, and feeds us band#1
- * through our fd[0].
- */
- if (pipe(fd) < 0)
- die("%s: unable to set up pipe", me);
- side_pid = fork();
- if (side_pid < 0)
- die("%s: unable to fork off sideband demultiplexer", me);
- if (!side_pid) {
- /* subprocess */
- close(fd[0]);
- if (xd[0] != xd[1])
- close(xd[1]);
- while (1) {
- char buf[1024];
- int len = packet_read_line(xd[0], buf, sizeof(buf));
- if (len == 0)
- break;
- if (len < 1)
- die("%s: protocol error: no band designator",
- me);
- len--;
- switch (buf[0] & 0xFF) {
- case 3:
- safe_write(2, "remote: ", 8);
- safe_write(2, buf+1, len);
- safe_write(2, "\n", 1);
- exit(1);
- case 2:
- safe_write(2, "remote: ", 8);
- safe_write(2, buf+1, len);
- continue;
- case 1:
- safe_write(fd[1], buf+1, len);
- continue;
- default:
- die("%s: protocol error: bad band #%d",
- me, (buf[0] & 0xFF));
- }
- }
- exit(0);
- }
- close(xd[0]);
- close(fd[1]);
- fd[1] = xd[1];
- return side_pid;
-}
-
-int receive_unpack_pack(int xd[2], const char *me, int quiet, int sideband)
-{
- int status;
- pid_t pid, side_pid;
- int fd[2];
-
- side_pid = setup_sideband(sideband, me, fd, xd);
- pid = fork();
- if (pid < 0)
- die("%s: unable to fork off git-unpack-objects", me);
- if (!pid) {
- dup2(fd[0], 0);
- close(fd[0]);
- close(fd[1]);
- execl_git_cmd("unpack-objects", quiet ? "-q" : NULL, NULL);
- die("git-unpack-objects exec failed");
- }
- close(fd[0]);
- close(fd[1]);
- while (waitpid(pid, &status, 0) < 0) {
- if (errno != EINTR)
- die("waiting for git-unpack-objects: %s",
- strerror(errno));
- }
- if (WIFEXITED(status)) {
- int code = WEXITSTATUS(status);
- if (code)
- die("git-unpack-objects died with error code %d",
- code);
- return 0;
- }
- if (WIFSIGNALED(status)) {
- int sig = WTERMSIG(status);
- die("git-unpack-objects died of signal %d", sig);
- }
- die("git-unpack-objects died of unnatural causes %d", status);
-}
-
-/*
- * We average out the download speed over this many "events", where
- * an event is a minimum of about half a second. That way, we get
- * a reasonably stable number.
- */
-#define NR_AVERAGE (4)
-
-/*
- * A "binary msec" is a power-of-two-msec, aka 1/1024th of a second.
- * Keeping the time in that format means that "bytes / msecs" means
- * the same as kB/s (modulo rounding).
- *
- * 1000512 is a magic number (usecs in a second, rounded up by half
- * of 1024, to make "rounding" come out right ;)
- */
-#define usec_to_binarymsec(x) ((int)(x) / (1000512 >> 10))
-
-int receive_keep_pack(int xd[2], const char *me, int quiet, int sideband)
-{
- char tmpfile[PATH_MAX];
- int ofd, ifd, fd[2];
- unsigned long total;
- static struct timeval prev_tv;
- struct average {
- unsigned long bytes;
- unsigned long time;
- } download[NR_AVERAGE] = { {0, 0}, };
- unsigned long avg_bytes, avg_time;
- int idx = 0;
-
- setup_sideband(sideband, me, fd, xd);
-
- ifd = fd[0];
- snprintf(tmpfile, sizeof(tmpfile),
- "%s/pack/tmp-XXXXXX", get_object_directory());
- ofd = mkstemp(tmpfile);
- if (ofd < 0)
- return error("unable to create temporary file %s", tmpfile);
-
- gettimeofday(&prev_tv, NULL);
- total = 0;
- avg_bytes = 0;
- avg_time = 0;
- while (1) {
- char buf[8192];
- ssize_t sz, wsz, pos;
- sz = read(ifd, buf, sizeof(buf));
- if (sz == 0)
- break;
- if (sz < 0) {
- if (errno != EINTR && errno != EAGAIN) {
- error("error reading pack (%s)", strerror(errno));
- close(ofd);
- unlink(tmpfile);
- return -1;
- }
- sz = 0;
- }
- pos = 0;
- while (pos < sz) {
- wsz = write(ofd, buf + pos, sz - pos);
- if (wsz < 0) {
- error("error writing pack (%s)",
- strerror(errno));
- close(ofd);
- unlink(tmpfile);
- return -1;
- }
- pos += wsz;
- }
- total += sz;
- if (!quiet) {
- static unsigned long last;
- struct timeval tv;
- unsigned long diff = total - last;
- /* not really "msecs", but a power-of-two millisec (1/1024th of a sec) */
- unsigned long msecs;
-
- gettimeofday(&tv, NULL);
- msecs = tv.tv_sec - prev_tv.tv_sec;
- msecs <<= 10;
- msecs += usec_to_binarymsec(tv.tv_usec - prev_tv.tv_usec);
-
- if (msecs > 500) {
- prev_tv = tv;
- last = total;
-
- /* Update averages ..*/
- avg_bytes += diff;
- avg_time += msecs;
- avg_bytes -= download[idx].bytes;
- avg_time -= download[idx].time;
- download[idx].bytes = diff;
- download[idx].time = msecs;
- idx++;
- if (idx >= NR_AVERAGE)
- idx = 0;
-
- fprintf(stderr, "%4lu.%03luMB (%lu kB/s) \r",
- total >> 20,
- 1000*((total >> 10) & 1023)>>10,
- avg_bytes / avg_time );
- }
- }
- }
- close(ofd);
- return finish_pack(tmpfile, me);
-}
diff --git a/fetch-pack.c b/fetch-pack.c
index b7824dbed4..0a169dce85 100644
--- a/fetch-pack.c
+++ b/fetch-pack.c
@@ -3,6 +3,9 @@
#include "pkt-line.h"
#include "commit.h"
#include "tag.h"
+#include "exec_cmd.h"
+#include "sideband.h"
+#include <sys/wait.h>
static int keep_pack;
static int quiet;
@@ -24,8 +27,8 @@ static const char *exec = "git-upload-pack";
*/
#define MAX_IN_VAIN 256
-static struct commit_list *rev_list = NULL;
-static int non_common_revs = 0, multi_ack = 0, use_thin_pack = 0, use_sideband;
+static struct commit_list *rev_list;
+static int non_common_revs, multi_ack, use_thin_pack, use_sideband;
static void rev_list_push(struct commit *commit, int mark)
{
@@ -42,7 +45,7 @@ static void rev_list_push(struct commit *commit, int mark)
}
}
-static int rev_list_insert_ref(const char *path, const unsigned char *sha1)
+static int rev_list_insert_ref(const char *path, const unsigned char *sha1, int flag, void *cb_data)
{
struct object *o = deref_tag(parse_object(sha1), path, 0);
@@ -143,7 +146,7 @@ static int find_common(int fd[2], unsigned char *result_sha1,
unsigned in_vain = 0;
int got_continue = 0;
- for_each_ref(rev_list_insert_ref);
+ for_each_ref(rev_list_insert_ref, NULL);
fetching = 0;
for ( ; refs ; refs = refs->next) {
@@ -166,11 +169,13 @@ static int find_common(int fd[2], unsigned char *result_sha1,
}
if (!fetching)
- packet_write(fd[1], "want %s%s%s%s\n",
+ packet_write(fd[1], "want %s%s%s%s%s%s\n",
sha1_to_hex(remote),
(multi_ack ? " multi_ack" : ""),
- (use_sideband ? " side-band" : ""),
- (use_thin_pack ? " thin-pack" : ""));
+ (use_sideband == 2 ? " side-band-64k" : ""),
+ (use_sideband == 1 ? " side-band" : ""),
+ (use_thin_pack ? " thin-pack" : ""),
+ " ofs-delta");
else
packet_write(fd[1], "want %s\n", sha1_to_hex(remote));
fetching++;
@@ -250,9 +255,9 @@ done:
return retval;
}
-static struct commit_list *complete = NULL;
+static struct commit_list *complete;
-static int mark_complete(const char *path, const unsigned char *sha1)
+static int mark_complete(const char *path, const unsigned char *sha1, int flag, void *cb_data)
{
struct object *o = parse_object(sha1);
@@ -364,7 +369,7 @@ static int everything_local(struct ref **refs, int nr_match, char **match)
}
}
- for_each_ref(mark_complete);
+ for_each_ref(mark_complete, NULL);
if (cutoff)
mark_recent_complete_commits(cutoff);
@@ -404,7 +409,7 @@ static int everything_local(struct ref **refs, int nr_match, char **match)
continue;
}
- memcpy(ref->new_sha1, local, 20);
+ hashcpy(ref->new_sha1, local);
if (!verbose)
continue;
fprintf(stderr,
@@ -414,6 +419,103 @@ static int everything_local(struct ref **refs, int nr_match, char **match)
return retval;
}
+static pid_t setup_sideband(int fd[2], int xd[2])
+{
+ pid_t side_pid;
+
+ if (!use_sideband) {
+ fd[0] = xd[0];
+ fd[1] = xd[1];
+ return 0;
+ }
+ /* xd[] is talking with upload-pack; subprocess reads from
+ * xd[0], spits out band#2 to stderr, and feeds us band#1
+ * through our fd[0].
+ */
+ if (pipe(fd) < 0)
+ die("fetch-pack: unable to set up pipe");
+ side_pid = fork();
+ if (side_pid < 0)
+ die("fetch-pack: unable to fork off sideband demultiplexer");
+ if (!side_pid) {
+ /* subprocess */
+ close(fd[0]);
+ if (xd[0] != xd[1])
+ close(xd[1]);
+ if (recv_sideband("fetch-pack", xd[0], fd[1], 2))
+ exit(1);
+ exit(0);
+ }
+ close(xd[0]);
+ close(fd[1]);
+ fd[1] = xd[1];
+ return side_pid;
+}
+
+static int get_pack(int xd[2], const char **argv)
+{
+ int status;
+ pid_t pid, side_pid;
+ int fd[2];
+
+ side_pid = setup_sideband(fd, xd);
+ pid = fork();
+ if (pid < 0)
+ die("fetch-pack: unable to fork off %s", argv[0]);
+ if (!pid) {
+ dup2(fd[0], 0);
+ close(fd[0]);
+ close(fd[1]);
+ execv_git_cmd(argv);
+ die("%s exec failed", argv[0]);
+ }
+ close(fd[0]);
+ close(fd[1]);
+ while (waitpid(pid, &status, 0) < 0) {
+ if (errno != EINTR)
+ die("waiting for %s: %s", argv[0], strerror(errno));
+ }
+ if (WIFEXITED(status)) {
+ int code = WEXITSTATUS(status);
+ if (code)
+ die("%s died with error code %d", argv[0], code);
+ return 0;
+ }
+ if (WIFSIGNALED(status)) {
+ int sig = WTERMSIG(status);
+ die("%s died of signal %d", argv[0], sig);
+ }
+ die("%s died of unnatural causes %d", argv[0], status);
+}
+
+static int explode_rx_pack(int xd[2])
+{
+ const char *argv[3] = { "unpack-objects", quiet ? "-q" : NULL, NULL };
+ return get_pack(xd, argv);
+}
+
+static int keep_rx_pack(int xd[2])
+{
+ const char *argv[6];
+ char keep_arg[256];
+ int n = 0;
+
+ argv[n++] = "index-pack";
+ argv[n++] = "--stdin";
+ if (!quiet)
+ argv[n++] = "-v";
+ if (use_thin_pack)
+ argv[n++] = "--fix-thin";
+ if (keep_pack > 1) {
+ int s = sprintf(keep_arg, "--keep=fetch-pack %i on ", getpid());
+ if (gethostname(keep_arg + s, sizeof(keep_arg) - s))
+ strcpy(keep_arg + s, "localhost");
+ argv[n++] = keep_arg;
+ }
+ argv[n] = NULL;
+ return get_pack(xd, argv);
+}
+
static int fetch_pack(int fd[2], int nr_match, char **match)
{
struct ref *ref;
@@ -426,7 +528,12 @@ static int fetch_pack(int fd[2], int nr_match, char **match)
fprintf(stderr, "Server supports multi_ack\n");
multi_ack = 1;
}
- if (server_supports("side-band")) {
+ if (server_supports("side-band-64k")) {
+ if (verbose)
+ fprintf(stderr, "Server supports side-band-64k\n");
+ use_sideband = 2;
+ }
+ else if (server_supports("side-band")) {
if (verbose)
fprintf(stderr, "Server supports side-band\n");
use_sideband = 1;
@@ -440,17 +547,13 @@ static int fetch_pack(int fd[2], int nr_match, char **match)
goto all_done;
}
if (find_common(fd, sha1, ref) < 0)
- if (!keep_pack)
+ if (keep_pack != 1)
/* When cloning, it is not unusual to have
* no common commit.
*/
fprintf(stderr, "warning: no common commits\n");
- if (keep_pack)
- status = receive_keep_pack(fd, "git-fetch-pack", quiet, use_sideband);
- else
- status = receive_unpack_pack(fd, "git-fetch-pack", quiet, use_sideband);
-
+ status = (keep_pack) ? keep_rx_pack(fd) : explode_rx_pack(fd);
if (status)
die("git-fetch-pack: fetch failed.");
@@ -487,7 +590,7 @@ int main(int argc, char **argv)
continue;
}
if (!strcmp("--keep", arg) || !strcmp("-k", arg)) {
- keep_pack = 1;
+ keep_pack++;
continue;
}
if (!strcmp("--thin", arg)) {
@@ -511,15 +614,13 @@ int main(int argc, char **argv)
}
if (!dest)
usage(fetch_pack_usage);
- if (keep_pack)
- use_thin_pack = 0;
pid = git_connect(fd, dest, exec);
if (pid < 0)
return 1;
ret = fetch_pack(fd, nr_heads, heads);
close(fd[0]);
close(fd[1]);
- finish_connect(pid);
+ ret |= finish_connect(pid);
if (!ret && nr_heads) {
/* If the heads to pull were given, we should have
@@ -534,5 +635,5 @@ int main(int argc, char **argv)
}
}
- return ret;
+ return !!ret;
}
diff --git a/fetch.c b/fetch.c
index aeb6bf2639..c426c04997 100644
--- a/fetch.c
+++ b/fetch.c
@@ -84,7 +84,7 @@ static int process_commit(struct commit *commit)
if (commit->object.flags & COMPLETE)
return 0;
- memcpy(current_commit_sha1, commit->object.sha1, 20);
+ hashcpy(current_commit_sha1, commit->object.sha1);
pull_say("walk %s\n", sha1_to_hex(commit->object.sha1));
@@ -201,7 +201,7 @@ static int interpret_target(char *target, unsigned char *sha1)
return -1;
}
-static int mark_complete(const char *path, const unsigned char *sha1)
+static int mark_complete(const char *path, const unsigned char *sha1, int flag, void *cb_data)
{
struct commit *commit = lookup_commit_reference_gently(sha1, 1);
if (commit) {
@@ -234,8 +234,8 @@ int pull_targets_stdin(char ***target, const char ***write_ref)
*target = xrealloc(*target, targets_alloc * sizeof(**target));
*write_ref = xrealloc(*write_ref, targets_alloc * sizeof(**write_ref));
}
- (*target)[targets] = strdup(tg_one);
- (*write_ref)[targets] = rf_one ? strdup(rf_one) : NULL;
+ (*target)[targets] = xstrdup(tg_one);
+ (*write_ref)[targets] = rf_one ? xstrdup(rf_one) : NULL;
targets++;
}
return targets;
@@ -266,7 +266,7 @@ int pull(int targets, char **target, const char **write_ref,
if (!write_ref || !write_ref[i])
continue;
- lock[i] = lock_ref_sha1(write_ref[i], NULL, 0);
+ lock[i] = lock_ref_sha1(write_ref[i], NULL);
if (!lock[i]) {
error("Can't lock ref %s", write_ref[i]);
goto unlock_and_fail;
@@ -274,7 +274,7 @@ int pull(int targets, char **target, const char **write_ref,
}
if (!get_recover)
- for_each_ref(mark_complete);
+ for_each_ref(mark_complete, NULL);
for (i = 0; i < targets; i++) {
if (interpret_target(target[i], &sha1[20 * i])) {
@@ -302,8 +302,7 @@ int pull(int targets, char **target, const char **write_ref,
if (ret)
goto unlock_and_fail;
}
- if (msg)
- free(msg);
+ free(msg);
return 0;
diff --git a/fsck-objects.c b/fsck-objects.c
index e167f4105f..46b628cb94 100644
--- a/fsck-objects.c
+++ b/fsck-objects.c
@@ -14,12 +14,12 @@
#define REACHABLE 0x0001
#define SEEN 0x0002
-static int show_root = 0;
-static int show_tags = 0;
-static int show_unreachable = 0;
-static int check_full = 0;
-static int check_strict = 0;
-static int keep_cache_objects = 0;
+static int show_root;
+static int show_tags;
+static int show_unreachable;
+static int check_full;
+static int check_strict;
+static int keep_cache_objects;
static unsigned char head_sha1[20];
#ifdef NO_D_INO_IN_DIRENT
@@ -356,7 +356,7 @@ static void add_sha1_list(unsigned char *sha1, unsigned long ino)
int nr;
entry->ino = ino;
- memcpy(entry->sha1, sha1, 20);
+ hashcpy(entry->sha1, sha1);
nr = sha1_list.nr;
if (nr == MAX_SHA1_ENTRIES) {
fsck_sha1_list();
@@ -366,13 +366,13 @@ static void add_sha1_list(unsigned char *sha1, unsigned long ino)
sha1_list.nr = ++nr;
}
-static int fsck_dir(int i, char *path)
+static void fsck_dir(int i, char *path)
{
DIR *dir = opendir(path);
struct dirent *de;
if (!dir)
- return 0;
+ return;
while ((de = readdir(dir)) != NULL) {
char name[100];
@@ -398,12 +398,11 @@ static int fsck_dir(int i, char *path)
fprintf(stderr, "bad sha1 file: %s/%s\n", path, de->d_name);
}
closedir(dir);
- return 0;
}
-static int default_refs = 0;
+static int default_refs;
-static int fsck_handle_ref(const char *refname, const unsigned char *sha1)
+static int fsck_handle_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
{
struct object *obj;
@@ -425,9 +424,24 @@ static int fsck_handle_ref(const char *refname, const unsigned char *sha1)
static void get_default_heads(void)
{
- for_each_ref(fsck_handle_ref);
- if (!default_refs)
- die("No default references");
+ for_each_ref(fsck_handle_ref, NULL);
+
+ /*
+ * Not having any default heads isn't really fatal, but
+ * it does mean that "--unreachable" no longer makes any
+ * sense (since in this case everything will obviously
+ * be unreachable by definition.
+ *
+ * Showing dangling objects is valid, though (as those
+ * dangling objects are likely lost heads).
+ *
+ * So we just print a warning about it, and clear the
+ * "show_unreachable" flag.
+ */
+ if (!default_refs) {
+ error("No default references");
+ show_unreachable = 0;
+ }
}
static void fsck_object_dir(const char *path)
@@ -444,16 +458,15 @@ static void fsck_object_dir(const char *path)
static int fsck_head_link(void)
{
unsigned char sha1[20];
- const char *git_HEAD = strdup(git_path("HEAD"));
- const char *git_refs_heads_master = resolve_ref(git_HEAD, sha1, 1);
- int pfxlen = strlen(git_HEAD) - 4; /* strip .../.git/ part */
+ int flag;
+ const char *head_points_at = resolve_ref("HEAD", sha1, 1, &flag);
- if (!git_refs_heads_master)
+ if (!head_points_at || !(flag & REF_ISSYMREF))
return error("HEAD is not a symbolic ref");
- if (strncmp(git_refs_heads_master + pfxlen, "refs/heads/", 11))
+ if (strncmp(head_points_at, "refs/heads/", 11))
return error("HEAD points to something strange (%s)",
- git_refs_heads_master + pfxlen);
- if (!memcmp(null_sha1, sha1, 20))
+ head_points_at);
+ if (is_null_sha1(sha1))
return error("HEAD: not a valid git pointer");
return 0;
}
diff --git a/generate-cmdlist.sh b/generate-cmdlist.sh
index ec1eda20de..5450918be3 100755
--- a/generate-cmdlist.sh
+++ b/generate-cmdlist.sh
@@ -12,6 +12,7 @@ struct cmdname_help common_cmds[] = {"
sort <<\EOF |
add
apply
+archive
bisect
branch
checkout
diff --git a/git-am.sh b/git-am.sh
index d0af786aec..afe322b20f 100755
--- a/git-am.sh
+++ b/git-am.sh
@@ -166,10 +166,25 @@ fi
if test -d "$dotest"
then
- if test ",$#," != ",0," || ! tty -s
- then
- die "previous dotest directory $dotest still exists but mbox given."
- fi
+ case "$#,$skip$resolved" in
+ 0,*t*)
+ # Explicit resume command and we do not have file, so
+ # we are happy.
+ : ;;
+ 0,)
+ # No file input but without resume parameters; catch
+ # user error to feed us a patch from standard input
+ # when there is already .dotest. This is somewhat
+ # unreliable -- stdin could be /dev/null for example
+ # and the caller did not intend to feed us a patch but
+ # wanted to continue unattended.
+ tty -s
+ ;;
+ *)
+ false
+ ;;
+ esac ||
+ die "previous dotest directory $dotest still exists but mbox given."
resume=yes
else
# Make sure we are not given --skip nor --resolved
diff --git a/git-annotate.perl b/git-annotate.perl
deleted file mode 100755
index 215ed26f3a..0000000000
--- a/git-annotate.perl
+++ /dev/null
@@ -1,708 +0,0 @@
-#!/usr/bin/perl
-# Copyright 2006, Ryan Anderson <ryan@michonline.com>
-#
-# GPL v2 (See COPYING)
-#
-# This file is licensed under the GPL v2, or a later version
-# at the discretion of Linus Torvalds.
-
-use warnings;
-use strict;
-use Getopt::Long;
-use POSIX qw(strftime gmtime);
-use File::Basename qw(basename dirname);
-
-sub usage() {
- print STDERR "Usage: ${\basename $0} [-s] [-S revs-file] file [ revision ]
- -l, --long
- Show long rev (Defaults off)
- -t, --time
- Show raw timestamp (Defaults off)
- -r, --rename
- Follow renames (Defaults on).
- -S, --rev-file revs-file
- Use revs from revs-file instead of calling git-rev-list
- -h, --help
- This message.
-";
-
- exit(1);
-}
-
-our ($help, $longrev, $rename, $rawtime, $starting_rev, $rev_file) = (0, 0, 1);
-
-my $rc = GetOptions( "long|l" => \$longrev,
- "time|t" => \$rawtime,
- "help|h" => \$help,
- "rename|r" => \$rename,
- "rev-file|S=s" => \$rev_file);
-if (!$rc or $help or !@ARGV) {
- usage();
-}
-
-my $filename = shift @ARGV;
-if (@ARGV) {
- $starting_rev = shift @ARGV;
-}
-
-my @stack = (
- {
- 'rev' => defined $starting_rev ? $starting_rev : "HEAD",
- 'filename' => $filename,
- },
-);
-
-our @filelines = ();
-
-if (defined $starting_rev) {
- @filelines = git_cat_file($starting_rev, $filename);
-} else {
- open(F,"<",$filename)
- or die "Failed to open filename: $!";
-
- while(<F>) {
- chomp;
- push @filelines, $_;
- }
- close(F);
-
-}
-
-our %revs;
-our @revqueue;
-our $head;
-
-my $revsprocessed = 0;
-while (my $bound = pop @stack) {
- my @revisions = git_rev_list($bound->{'rev'}, $bound->{'filename'});
- foreach my $revinst (@revisions) {
- my ($rev, @parents) = @$revinst;
- $head ||= $rev;
-
- if (!defined($rev)) {
- $rev = "";
- }
- $revs{$rev}{'filename'} = $bound->{'filename'};
- if (scalar @parents > 0) {
- $revs{$rev}{'parents'} = \@parents;
- next;
- }
-
- if (!$rename) {
- next;
- }
-
- my $newbound = find_parent_renames($rev, $bound->{'filename'});
- if ( exists $newbound->{'filename'} && $newbound->{'filename'} ne $bound->{'filename'}) {
- push @stack, $newbound;
- $revs{$rev}{'parents'} = [$newbound->{'rev'}];
- }
- }
-}
-push @revqueue, $head;
-init_claim( defined $starting_rev ? $head : 'dirty');
-unless (defined $starting_rev) {
- my $diff = open_pipe("git","diff","HEAD", "--",$filename)
- or die "Failed to call git diff to check for dirty state: $!";
-
- _git_diff_parse($diff, [$head], "dirty", (
- 'author' => gitvar_name("GIT_AUTHOR_IDENT"),
- 'author_date' => sprintf("%s +0000",time()),
- )
- );
- close($diff);
-}
-handle_rev();
-
-
-my $i = 0;
-foreach my $l (@filelines) {
- my ($output, $rev, $committer, $date);
- if (ref $l eq 'ARRAY') {
- ($output, $rev, $committer, $date) = @$l;
- if (!$longrev && length($rev) > 8) {
- $rev = substr($rev,0,8);
- }
- } else {
- $output = $l;
- ($rev, $committer, $date) = ('unknown', 'unknown', 'unknown');
- }
-
- printf("%s\t(%10s\t%10s\t%d)%s\n", $rev, $committer,
- format_date($date), ++$i, $output);
-}
-
-sub init_claim {
- my ($rev) = @_;
- for (my $i = 0; $i < @filelines; $i++) {
- $filelines[$i] = [ $filelines[$i], '', '', '', 1];
- # line,
- # rev,
- # author,
- # date,
- # 1 <-- belongs to the original file.
- }
- $revs{$rev}{'lines'} = \@filelines;
-}
-
-
-sub handle_rev {
- my $revseen = 0;
- my %seen;
- while (my $rev = shift @revqueue) {
- next if $seen{$rev}++;
-
- my %revinfo = git_commit_info($rev);
-
- if (exists $revs{$rev}{parents} &&
- scalar @{$revs{$rev}{parents}} != 0) {
-
- git_diff_parse($revs{$rev}{'parents'}, $rev, %revinfo);
- push @revqueue, @{$revs{$rev}{'parents'}};
-
- } else {
- # We must be at the initial rev here, so claim everything that is left.
- for (my $i = 0; $i < @{$revs{$rev}{lines}}; $i++) {
- if (ref ${$revs{$rev}{lines}}[$i] eq '' || ${$revs{$rev}{lines}}[$i][1] eq '') {
- claim_line($i, $rev, $revs{$rev}{lines}, %revinfo);
- }
- }
- }
- }
-}
-
-
-sub git_rev_list {
- my ($rev, $file) = @_;
-
- my $revlist;
- if ($rev_file) {
- open($revlist, '<' . $rev_file)
- or die "Failed to open $rev_file : $!";
- } else {
- $revlist = open_pipe("git-rev-list","--parents","--remove-empty",$rev,"--",$file)
- or die "Failed to exec git-rev-list: $!";
- }
-
- my @revs;
- while(my $line = <$revlist>) {
- chomp $line;
- my ($rev, @parents) = split /\s+/, $line;
- push @revs, [ $rev, @parents ];
- }
- close($revlist);
-
- printf("0 revs found for rev %s (%s)\n", $rev, $file) if (@revs == 0);
- return @revs;
-}
-
-sub find_parent_renames {
- my ($rev, $file) = @_;
-
- my $patch = open_pipe("git-diff-tree", "-M50", "-r","--name-status", "-z","$rev")
- or die "Failed to exec git-diff: $!";
-
- local $/ = "\0";
- my %bound;
- my $junk = <$patch>;
- while (my $change = <$patch>) {
- chomp $change;
- my $filename = <$patch>;
- if (!defined $filename) {
- next;
- }
- chomp $filename;
-
- if ($change =~ m/^[AMD]$/ ) {
- next;
- } elsif ($change =~ m/^R/ ) {
- my $oldfilename = $filename;
- $filename = <$patch>;
- chomp $filename;
- if ( $file eq $filename ) {
- my $parent = git_find_parent($rev, $oldfilename);
- @bound{'rev','filename'} = ($parent, $oldfilename);
- last;
- }
- }
- }
- close($patch);
-
- return \%bound;
-}
-
-
-sub git_find_parent {
- my ($rev, $filename) = @_;
-
- my $revparent = open_pipe("git-rev-list","--remove-empty", "--parents","--max-count=1","$rev","--",$filename)
- or die "Failed to open git-rev-list to find a single parent: $!";
-
- my $parentline = <$revparent>;
- chomp $parentline;
- my ($revfound,$parent) = split m/\s+/, $parentline;
-
- close($revparent);
-
- return $parent;
-}
-
-sub git_find_all_parents {
- my ($rev) = @_;
-
- my $revparent = open_pipe("git-rev-list","--remove-empty", "--parents","--max-count=1","$rev")
- or die "Failed to open git-rev-list to find a single parent: $!";
-
- my $parentline = <$revparent>;
- chomp $parentline;
- my ($origrev, @parents) = split m/\s+/, $parentline;
-
- close($revparent);
-
- return @parents;
-}
-
-sub git_merge_base {
- my ($rev1, $rev2) = @_;
-
- my $mb = open_pipe("git-merge-base", $rev1, $rev2)
- or die "Failed to open git-merge-base: $!";
-
- my $base = <$mb>;
- chomp $base;
-
- close($mb);
-
- return $base;
-}
-
-# Construct a set of pseudo parents that are in the same order,
-# and the same quantity as the real parents,
-# but whose SHA1s are as similar to the logical parents
-# as possible.
-sub get_pseudo_parents {
- my ($all, $fake) = @_;
-
- my @all = @$all;
- my @fake = @$fake;
-
- my @pseudo;
-
- my %fake = map {$_ => 1} @fake;
- my %seenfake;
-
- my $fakeidx = 0;
- foreach my $p (@all) {
- if (exists $fake{$p}) {
- if ($fake[$fakeidx] ne $p) {
- die sprintf("parent mismatch: %s != %s\nall:%s\nfake:%s\n",
- $fake[$fakeidx], $p,
- join(", ", @all),
- join(", ", @fake),
- );
- }
-
- push @pseudo, $p;
- $fakeidx++;
- $seenfake{$p}++;
-
- } else {
- my $base = git_merge_base($fake[$fakeidx], $p);
- if ($base ne $fake[$fakeidx]) {
- die sprintf("Result of merge-base doesn't match fake: %s,%s != %s\n",
- $fake[$fakeidx], $p, $base);
- }
-
- # The details of how we parse the diffs
- # mean that we cannot have a duplicate
- # revision in the list, so if we've already
- # seen the revision we would normally add, just use
- # the actual revision.
- if ($seenfake{$base}) {
- push @pseudo, $p;
- } else {
- push @pseudo, $base;
- $seenfake{$base}++;
- }
- }
- }
-
- return @pseudo;
-}
-
-
-# Get a diff between the current revision and a parent.
-# Record the commit information that results.
-sub git_diff_parse {
- my ($parents, $rev, %revinfo) = @_;
-
- my @pseudo_parents;
- my @command = ("git-diff-tree");
- my $revision_spec;
-
- if (scalar @$parents == 1) {
-
- $revision_spec = join("..", $parents->[0], $rev);
- @pseudo_parents = @$parents;
- } else {
- my @all_parents = git_find_all_parents($rev);
-
- if (@all_parents != @$parents) {
- @pseudo_parents = get_pseudo_parents(\@all_parents, $parents);
- } else {
- @pseudo_parents = @$parents;
- }
-
- $revision_spec = $rev;
- push @command, "-c";
- }
-
- my @filenames = ( $revs{$rev}{'filename'} );
-
- foreach my $parent (@$parents) {
- push @filenames, $revs{$parent}{'filename'};
- }
-
- push @command, "-p", "-M", $revision_spec, "--", @filenames;
-
-
- my $diff = open_pipe( @command )
- or die "Failed to call git-diff for annotation: $!";
-
- _git_diff_parse($diff, \@pseudo_parents, $rev, %revinfo);
-
- close($diff);
-}
-
-sub _git_diff_parse {
- my ($diff, $parents, $rev, %revinfo) = @_;
-
- my $ri = 0;
-
- my $slines = $revs{$rev}{'lines'};
- my (%plines, %pi);
-
- my $gotheader = 0;
- my ($remstart);
- my $parent_count = @$parents;
-
- my $diff_header_regexp = "^@";
- $diff_header_regexp .= "@" x @$parents;
- $diff_header_regexp .= ' -\d+,\d+' x @$parents;
- $diff_header_regexp .= ' \+(\d+),\d+';
- $diff_header_regexp .= " " . ("@" x @$parents);
-
- my %claim_regexps;
- my $allparentplus = '^' . '\\+' x @$parents . '(.*)$';
-
- {
- my $i = 0;
- foreach my $parent (@$parents) {
-
- $pi{$parent} = 0;
- my $r = '^' . '.' x @$parents . '(.*)$';
- my $p = $r;
- substr($p,$i+1, 1) = '\\+';
-
- my $m = $r;
- substr($m,$i+1, 1) = '-';
-
- $claim_regexps{$parent}{plus} = $p;
- $claim_regexps{$parent}{minus} = $m;
-
- $plines{$parent} = [];
-
- $i++;
- }
- }
-
- DIFF:
- while(<$diff>) {
- chomp;
- #printf("%d:%s:\n", $gotheader, $_);
- if (m/$diff_header_regexp/) {
- $remstart = $1 - 1;
- # (0-based arrays)
-
- $gotheader = 1;
-
- foreach my $parent (@$parents) {
- for (my $i = $ri; $i < $remstart; $i++) {
- $plines{$parent}[$pi{$parent}++] = $slines->[$i];
- }
- }
- $ri = $remstart;
-
- next DIFF;
-
- } elsif (!$gotheader) {
- # Skip over the leadin.
- next DIFF;
- }
-
- if (m/^\\/) {
- ;
- # Skip \No newline at end of file.
- # But this can be internationalized, so only look
- # for an initial \
-
- } else {
- my %claims = ();
- my $negclaim = 0;
- my $allclaimed = 0;
- my $line;
-
- if (m/$allparentplus/) {
- claim_line($ri, $rev, $slines, %revinfo);
- $allclaimed = 1;
-
- }
-
- PARENT:
- foreach my $parent (keys %claim_regexps) {
- my $m = $claim_regexps{$parent}{minus};
- my $p = $claim_regexps{$parent}{plus};
-
- if (m/$m/) {
- $line = $1;
- $plines{$parent}[$pi{$parent}++] = [ $line, '', '', '', 0 ];
- $negclaim++;
-
- } elsif (m/$p/) {
- $line = $1;
- if (get_line($slines, $ri) eq $line) {
- # Found a match, claim
- $claims{$parent}++;
-
- } else {
- die sprintf("Sync error: %d\n|%s\n|%s\n%s => %s\n",
- $ri, $line,
- get_line($slines, $ri),
- $rev, $parent);
- }
- }
- }
-
- if (%claims) {
- foreach my $parent (@$parents) {
- next if $claims{$parent} || $allclaimed;
- $plines{$parent}[$pi{$parent}++] = $slines->[$ri];
- #[ $line, '', '', '', 0 ];
- }
- $ri++;
-
- } elsif ($negclaim) {
- next DIFF;
-
- } else {
- if (substr($_,scalar @$parents) ne get_line($slines,$ri) ) {
- foreach my $parent (@$parents) {
- printf("parent %s is on line %d\n", $parent, $pi{$parent});
- }
-
- my @context;
- for (my $i = -2; $i < 2; $i++) {
- push @context, get_line($slines, $ri + $i);
- }
- my $context = join("\n", @context);
-
- my $justline = substr($_, scalar @$parents);
- die sprintf("Line %d, does not match:\n|%s|\n|%s|\n%s\n",
- $ri,
- $justline,
- $context);
- }
- foreach my $parent (@$parents) {
- $plines{$parent}[$pi{$parent}++] = $slines->[$ri];
- }
- $ri++;
- }
- }
- }
-
- for (my $i = $ri; $i < @{$slines} ; $i++) {
- foreach my $parent (@$parents) {
- push @{$plines{$parent}}, $slines->[$ri];
- }
- $ri++;
- }
-
- foreach my $parent (@$parents) {
- $revs{$parent}{lines} = $plines{$parent};
- }
-
- return;
-}
-
-sub get_line {
- my ($lines, $index) = @_;
-
- return ref $lines->[$index] ne '' ? $lines->[$index][0] : $lines->[$index];
-}
-
-sub git_cat_file {
- my ($rev, $filename) = @_;
- 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): " . $!;
-
- my @lines;
- while(<$catfile>) {
- chomp;
- push @lines, $_;
- }
- close($catfile);
-
- return @lines;
-}
-
-sub git_ls_tree {
- my ($rev, $filename) = @_;
-
- my $lstree = open_pipe("git","ls-tree",$rev,$filename)
- or die "Failed to call 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 ($tfilename eq $filename);
- die "git-ls-tree failed to find blob for $filename";
-
-}
-
-
-
-sub claim_line {
- my ($floffset, $rev, $lines, %revinfo) = @_;
- my $oline = get_line($lines, $floffset);
- @{$lines->[$floffset]} = ( $oline, $rev,
- $revinfo{'author'}, $revinfo{'author_date'} );
- #printf("Claiming line %d with rev %s: '%s'\n",
- # $floffset, $rev, $oline) if 1;
-}
-
-sub git_commit_info {
- my ($rev) = @_;
- my $commit = open_pipe("git-cat-file", "commit", $rev)
- or die "Failed to call git-cat-file: $!";
-
- my %info;
- while(<$commit>) {
- chomp;
- last if (length $_ == 0);
-
- if (m/^author (.*) <(.*)> (.*)$/) {
- $info{'author'} = $1;
- $info{'author_email'} = $2;
- $info{'author_date'} = $3;
- } elsif (m/^committer (.*) <(.*)> (.*)$/) {
- $info{'committer'} = $1;
- $info{'committer_email'} = $2;
- $info{'committer_date'} = $3;
- }
- }
- close($commit);
-
- return %info;
-}
-
-sub format_date {
- if ($rawtime) {
- return $_[0];
- }
- my ($timestamp, $timezone) = split(' ', $_[0]);
- 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..
-sub gitvar {
- my ($var) = @_;
- my $fh;
- my $pid = open($fh, '-|');
- die "$!" unless defined $pid;
- if (!$pid) {
- exec('git-var', $var) or die "$!";
- }
- my ($val) = <$fh>;
- close $fh or die "$!";
- chomp($val);
- return $val;
-}
-
-sub gitvar_name {
- my ($name) = @_;
- my $val = gitvar($name);
- my @field = split(/\s+/, $val);
- return join(' ', @field[0...(@field-4)]);
-}
-
-sub open_pipe {
- if ($^O eq '##INSERT_ACTIVESTATE_STRING_HERE##') {
- return open_pipe_activestate(@_);
- } else {
- return open_pipe_normal(@_);
- }
-}
-
-sub open_pipe_activestate {
- tie *fh, "Git::ActiveStatePipe", @_;
- return *fh;
-}
-
-sub open_pipe_normal {
- my (@execlist) = @_;
-
- my $pid = open my $kid, "-|";
- defined $pid or die "Cannot fork: $!";
-
- unless ($pid) {
- exec @execlist;
- die "Cannot exec @execlist: $!";
- }
-
- return $kid;
-}
-
-package Git::ActiveStatePipe;
-use strict;
-
-sub TIEHANDLE {
- my ($class, @params) = @_;
- my $cmdline = join " ", @params;
- my @data = qx{$cmdline};
- bless { i => 0, data => \@data }, $class;
-}
-
-sub READLINE {
- my $self = shift;
- if ($self->{i} >= scalar @{$self->{data}}) {
- return undef;
- }
- return $self->{'data'}->[ $self->{i}++ ];
-}
-
-sub CLOSE {
- my $self = shift;
- delete $self->{data};
- delete $self->{i};
-}
-
-sub EOF {
- my $self = shift;
- return ($self->{i} >= scalar @{$self->{data}});
-}
diff --git a/git-bisect.sh b/git-bisect.sh
index 06a8d26945..6da31e87a0 100755
--- a/git-bisect.sh
+++ b/git-bisect.sh
@@ -179,11 +179,12 @@ bisect_reset() {
*)
usage ;;
esac
- git checkout "$branch" &&
- rm -fr "$GIT_DIR/refs/bisect"
- rm -f "$GIT_DIR/refs/heads/bisect" "$GIT_DIR/head-name"
- rm -f "$GIT_DIR/BISECT_LOG"
- rm -f "$GIT_DIR/BISECT_NAMES"
+ if git checkout "$branch"; then
+ rm -fr "$GIT_DIR/refs/bisect"
+ rm -f "$GIT_DIR/refs/heads/bisect" "$GIT_DIR/head-name"
+ rm -f "$GIT_DIR/BISECT_LOG"
+ rm -f "$GIT_DIR/BISECT_NAMES"
+ fi
}
bisect_replay () {
diff --git a/git-branch.sh b/git-branch.sh
deleted file mode 100755
index e0501ec23f..0000000000
--- a/git-branch.sh
+++ /dev/null
@@ -1,130 +0,0 @@
-#!/bin/sh
-
-USAGE='[-l] [(-d | -D) <branchname>] | [[-f] <branchname> [<start-point>]] | -r'
-LONG_USAGE='If no arguments, show available branches and mark current branch with a star.
-If one argument, create a new branch <branchname> based off of current HEAD.
-If two arguments, create a new branch <branchname> based off of <start-point>.'
-
-SUBDIRECTORY_OK='Yes'
-. git-sh-setup
-
-headref=$(git-symbolic-ref HEAD | sed -e 's|^refs/heads/||')
-
-delete_branch () {
- option="$1"
- shift
- for branch_name
- do
- case ",$headref," in
- ",$branch_name,")
- die "Cannot delete the branch you are on." ;;
- ,,)
- die "What branch are you on anyway?" ;;
- esac
- branch=$(cat "$GIT_DIR/refs/heads/$branch_name") &&
- branch=$(git-rev-parse --verify "$branch^0") ||
- die "Seriously, what branch are you talking about?"
- case "$option" in
- -D)
- ;;
- *)
- mbs=$(git-merge-base -a "$branch" HEAD | tr '\012' ' ')
- case " $mbs " in
- *' '$branch' '*)
- # the merge base of branch and HEAD contains branch --
- # which means that the HEAD contains everything in both.
- ;;
- *)
- echo >&2 "The branch '$branch_name' is not a strict subset of your current HEAD.
-If you are sure you want to delete it, run 'git branch -D $branch_name'."
- exit 1
- ;;
- esac
- ;;
- esac
- rm -f "$GIT_DIR/logs/refs/heads/$branch_name"
- rm -f "$GIT_DIR/refs/heads/$branch_name"
- echo "Deleted branch $branch_name."
- done
- exit 0
-}
-
-ls_remote_branches () {
- git-rev-parse --symbolic --all |
- sed -ne 's|^refs/\(remotes/\)|\1|p' |
- sort
-}
-
-force=
-create_log=
-while case "$#,$1" in 0,*) break ;; *,-*) ;; *) break ;; esac
-do
- case "$1" in
- -d | -D)
- delete_branch "$@"
- exit
- ;;
- -r)
- ls_remote_branches
- exit
- ;;
- -f)
- force="$1"
- ;;
- -l)
- create_log="yes"
- ;;
- --)
- shift
- break
- ;;
- -*)
- usage
- ;;
- esac
- shift
-done
-
-case "$#" in
-0)
- git-rev-parse --symbolic --branches |
- sort |
- while read ref
- do
- if test "$headref" = "$ref"
- then
- pfx='*'
- else
- pfx=' '
- fi
- echo "$pfx $ref"
- done
- exit 0 ;;
-1)
- head=HEAD ;;
-2)
- head="$2^0" ;;
-esac
-branchname="$1"
-
-rev=$(git-rev-parse --verify "$head") || exit
-
-git-check-ref-format "heads/$branchname" ||
- die "we do not like '$branchname' as a branch name."
-
-if [ -e "$GIT_DIR/refs/heads/$branchname" ]
-then
- if test '' = "$force"
- then
- die "$branchname already exists."
- elif test "$branchname" = "$headref"
- then
- die "cannot force-update the current branch."
- fi
-fi
-if test "$create_log" = 'yes'
-then
- mkdir -p $(dirname "$GIT_DIR/logs/refs/heads/$branchname")
- touch "$GIT_DIR/logs/refs/heads/$branchname"
-fi
-git update-ref -m "branch: Created from $head" "refs/heads/$branchname" $rev
diff --git a/git-checkout.sh b/git-checkout.sh
index 580a9e8a23..737abd0c09 100755
--- a/git-checkout.sh
+++ b/git-checkout.sh
@@ -4,8 +4,8 @@ USAGE='[-f] [-b <new_branch>] [-m] [<branch>] [<paths>...]'
SUBDIRECTORY_OK=Sometimes
. git-sh-setup
-old=$(git-rev-parse HEAD)
old_name=HEAD
+old=$(git-rev-parse --verify $old_name 2>/dev/null)
new=
new_name=
force=
@@ -22,7 +22,7 @@ while [ "$#" != "0" ]; do
shift
[ -z "$newbranch" ] &&
die "git checkout: -b needs a branch name"
- [ -e "$GIT_DIR/refs/heads/$newbranch" ] &&
+ git-show-ref --verify --quiet -- "refs/heads/$newbranch" &&
die "git checkout: branch $newbranch already exists"
git-check-ref-format "heads/$newbranch" ||
die "git checkout: we do not like '$newbranch' as a branch name."
@@ -51,7 +51,8 @@ while [ "$#" != "0" ]; do
fi
new="$rev"
new_name="$arg^0"
- if [ -f "$GIT_DIR/refs/heads/$arg" ]; then
+ if git-show-ref --verify --quiet -- "refs/heads/$arg"
+ then
branch="$arg"
fi
elif rev=$(git-rev-parse --verify "$arg^{tree}" 2>/dev/null)
@@ -76,6 +77,11 @@ while [ "$#" != "0" ]; do
esac
done
+case "$force$merge" in
+11)
+ die "git checkout: -f and -m are incompatible"
+esac
+
# The behaviour of the command with and without explicit path
# parameters is quite different.
#
@@ -106,7 +112,11 @@ Did you intend to checkout '$@' which can not be resolved as commit?"
git-ls-tree --full-name -r "$new" "$@" |
git-update-index --index-info || exit $?
fi
- git-checkout-index -f -u -- "$@"
+
+ # Make sure the request is about existing paths.
+ git-ls-files --error-unmatch -- "$@" >/dev/null || exit
+ git-ls-files -- "$@" |
+ git-checkout-index -f -u --stdin
exit $?
else
# Make sure we did not fall back on $arg^{tree} codepath
@@ -139,6 +149,13 @@ fi
die "git checkout: to checkout the requested commit you need to specify
a name for a new branch which is created and switched to"
+if [ "X$old" = X ]
+then
+ echo "warning: You do not appear to currently be on a branch." >&2
+ echo "warning: Forcing checkout of $new_name." >&2
+ force=1
+fi
+
if [ "$force" ]
then
git-read-tree --reset -u $new
diff --git a/git-cherry.sh b/git-cherry.sh
deleted file mode 100755
index f0e8831fa4..0000000000
--- a/git-cherry.sh
+++ /dev/null
@@ -1,94 +0,0 @@
-#!/bin/sh
-#
-# Copyright (c) 2005 Junio C Hamano.
-#
-
-USAGE='[-v] <upstream> [<head>] [<limit>]'
-LONG_USAGE=' __*__*__*__*__> <upstream>
- /
- fork-point
- \__+__+__+__+__+__+__+__> <head>
-
-Each commit between the fork-point (or <limit> if given) and <head> is
-examined, and compared against the change each commit between the
-fork-point and <upstream> introduces. If the change seems to be in
-the upstream, it is shown on the standard output with prefix "+".
-Otherwise it is shown with prefix "-".'
-. git-sh-setup
-
-case "$1" in -v) verbose=t; shift ;; esac
-
-case "$#,$1" in
-1,*..*)
- upstream=$(expr "z$1" : 'z\(.*\)\.\.') ours=$(expr "z$1" : '.*\.\.\(.*\)$')
- set x "$upstream" "$ours"
- shift ;;
-esac
-
-case "$#" in
-1) upstream=`git-rev-parse --verify "$1"` &&
- ours=`git-rev-parse --verify HEAD` || exit
- limit="$upstream"
- ;;
-2) upstream=`git-rev-parse --verify "$1"` &&
- ours=`git-rev-parse --verify "$2"` || exit
- limit="$upstream"
- ;;
-3) upstream=`git-rev-parse --verify "$1"` &&
- ours=`git-rev-parse --verify "$2"` &&
- limit=`git-rev-parse --verify "$3"` || exit
- ;;
-*) usage ;;
-esac
-
-# Note that these list commits in reverse order;
-# not that the order in inup matters...
-inup=`git-rev-list ^$ours $upstream` &&
-ours=`git-rev-list $ours ^$limit` || exit
-
-tmp=.cherry-tmp$$
-patch=$tmp-patch
-mkdir $patch
-trap "rm -rf $tmp-*" 0 1 2 3 15
-
-_x40='[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]'
-_x40="$_x40$_x40$_x40$_x40$_x40$_x40$_x40$_x40"
-
-for c in $inup
-do
- git-diff-tree -p $c
-done | git-patch-id |
-while read id name
-do
- echo $name >>$patch/$id
-done
-
-LF='
-'
-
-O=
-for c in $ours
-do
- set x `git-diff-tree -p $c | git-patch-id`
- if test "$2" != ""
- then
- if test -f "$patch/$2"
- then
- sign=-
- else
- sign=+
- fi
- case "$verbose" in
- t)
- c=$(git-rev-list --pretty=oneline --max-count=1 $c)
- esac
- case "$O" in
- '') O="$sign $c" ;;
- *) O="$sign $c$LF$O" ;;
- esac
- fi
-done
-case "$O" in
-'') ;;
-*) echo "$O" ;;
-esac
diff --git a/git-clone.sh b/git-clone.sh
index 7060bdab01..9ed4135544 100755
--- a/git-clone.sh
+++ b/git-clone.sh
@@ -8,11 +8,15 @@
# See git-sh-setup why.
unset CDPATH
-usage() {
- echo >&2 "Usage: $0 [--template=<template_directory>] [--use-separate-remote] [--reference <reference-repo>] [--bare] [-l [-s]] [-q] [-u <upload-pack>] [--origin <name>] [-n] <repo> [<dir>]"
+die() {
+ echo >&2 "$@"
exit 1
}
+usage() {
+ die "Usage: $0 [--template=<template_directory>] [--use-immingled-remote] [--reference <reference-repo>] [--bare] [-l [-s]] [-q] [-u <upload-pack>] [--origin <name>] [-n] <repo> [<dir>]"
+}
+
get_repo_base() {
(cd "$1" && (cd .git ; pwd)) 2> /dev/null
}
@@ -31,11 +35,13 @@ clone_dumb_http () {
cd "$2" &&
clone_tmp="$GIT_DIR/clone-tmp" &&
mkdir -p "$clone_tmp" || exit 1
- http_fetch "$1/info/refs" "$clone_tmp/refs" || {
- echo >&2 "Cannot get remote repository information.
+ if [ -n "$GIT_CURL_FTP_NO_EPSV" -o \
+ "`git-repo-config --bool http.noEPSV`" = true ]; then
+ curl_extra_args="${curl_extra_args} --disable-epsv"
+ fi
+ http_fetch "$1/info/refs" "$clone_tmp/refs" ||
+ die "Cannot get remote repository information.
Perhaps git-update-server-info needs to be run there?"
- exit 1;
- }
while read sha1 refname
do
name=`expr "z$refname" : 'zrefs/\(.*\)'` &&
@@ -109,7 +115,7 @@ bare=
reference=
origin=
origin_override=
-use_separate_remote=
+use_separate_remote=t
while
case "$#,$1" in
0,*) break ;;
@@ -128,7 +134,10 @@ while
template="$1" ;;
*,-q|*,--quiet) quiet=-q ;;
*,--use-separate-remote)
+ # default
use_separate_remote=t ;;
+ *,--use-immingled-remote)
+ use_separate_remote= ;;
1,--reference) usage ;;
*,--reference)
shift; reference="$1" ;;
@@ -139,17 +148,12 @@ while
'')
usage ;;
*/*)
- echo >&2 "'$2' is not suitable for an origin name"
- exit 1
+ die "'$2' is not suitable for an origin name"
esac
- git-check-ref-format "heads/$2" || {
- echo >&2 "'$2' is not suitable for a branch name"
- exit 1
- }
- test -z "$origin_override" || {
- echo >&2 "Do not give more than one --origin options."
- exit 1
- }
+ git-check-ref-format "heads/$2" ||
+ die "'$2' is not suitable for a branch name"
+ test -z "$origin_override" ||
+ die "Do not give more than one --origin options."
origin_override=yes
origin="$2"; shift
;;
@@ -165,26 +169,18 @@ do
done
repo="$1"
-if test -z "$repo"
-then
- echo >&2 'you must specify a repository to clone.'
- exit 1
-fi
+test -n "$repo" ||
+ die 'you must specify a repository to clone.'
-# --bare implies --no-checkout
+# --bare implies --no-checkout and --use-immingled-remote
if test yes = "$bare"
then
if test yes = "$origin_override"
then
- echo >&2 '--bare and --origin $origin options are incompatible.'
- exit 1
- fi
- if test t = "$use_separate_remote"
- then
- echo >&2 '--bare and --use-separate-remote options are incompatible.'
- exit 1
+ die '--bare and --origin $origin options are incompatible.'
fi
no_checkout=yes
+ use_separate_remote=
fi
if test -z "$origin"
@@ -202,7 +198,7 @@ fi
dir="$2"
# Try using "humanish" part of source repo if user didn't specify one
[ -z "$dir" ] && dir=$(echo "$repo" | sed -e 's|/$||' -e 's|:*/*\.git$||' -e 's|.*[/:]||g')
-[ -e "$dir" ] && echo "$dir already exists." && usage
+[ -e "$dir" ] && die "destination directory '$dir' already exists."
mkdir -p "$dir" &&
D=$(cd "$dir" && pwd) &&
trap 'err=$?; cd ..; rm -rf "$D"; exit $err' 0
@@ -229,7 +225,7 @@ then
cd reference-tmp &&
tar xf -)
else
- echo >&2 "$reference: not a local directory." && usage
+ die "reference repository '$reference' is not a local directory."
fi
fi
@@ -238,10 +234,8 @@ rm -f "$GIT_DIR/CLONE_HEAD"
# We do local magic only when the user tells us to.
case "$local,$use_local" in
yes,yes)
- ( cd "$repo/objects" ) || {
- echo >&2 "-l flag seen but $repo is not local."
- exit 1
- }
+ ( cd "$repo/objects" ) ||
+ die "-l flag seen but repository '$repo' is not local."
case "$local_shared" in
no)
@@ -298,23 +292,20 @@ yes,yes)
fi
git-ls-remote "$repo" >"$GIT_DIR/CLONE_HEAD" || exit 1
;;
- https://*|http://*)
+ https://*|http://*|ftp://*)
if test -z "@@NO_CURL@@"
then
clone_dumb_http "$repo" "$D"
else
- echo >&2 "http transport not supported, rebuild Git with curl support"
- exit 1
+ die "http transport not supported, rebuild Git with curl support"
fi
;;
*)
- cd "$D" && case "$upload_pack" in
+ case "$upload_pack" in
'') git-fetch-pack --all -k $quiet "$repo" ;;
*) git-fetch-pack --all -k $quiet "$upload_pack" "$repo" ;;
- esac >"$GIT_DIR/CLONE_HEAD" || {
- echo >&2 "fetch-pack from '$repo' failed."
- exit 1
- }
+ esac >"$GIT_DIR/CLONE_HEAD" ||
+ die "fetch-pack from '$repo' failed."
;;
esac
;;
@@ -410,7 +401,8 @@ Pull: refs/heads/$head_points_at:$origin_track" &&
case "$no_checkout" in
'')
- git-read-tree -m -u -v HEAD HEAD
+ test "z$quiet" = z && v=-v || v=
+ git-read-tree -m -u $v HEAD HEAD
esac
fi
rm -f "$GIT_DIR/CLONE_HEAD" "$GIT_DIR/REMOTE_HEAD"
diff --git a/git-commit.sh b/git-commit.sh
index 4cf3fab05c..81c3a0cb61 100755
--- a/git-commit.sh
+++ b/git-commit.sh
@@ -32,54 +32,7 @@ save_index () {
cp -p "$THIS_INDEX" "$NEXT_INDEX"
}
-report () {
- header="#
-# $1:
-# ($2)
-#
-"
- trailer=""
- while read status name newname
- do
- printf '%s' "$header"
- header=""
- trailer="#
-"
- case "$status" in
- M ) echo "# modified: $name";;
- D*) echo "# deleted: $name";;
- T ) echo "# typechange: $name";;
- C*) echo "# copied: $name -> $newname";;
- R*) echo "# renamed: $name -> $newname";;
- A*) echo "# new file: $name";;
- U ) echo "# unmerged: $name";;
- esac
- done
- printf '%s' "$trailer"
- [ "$header" ]
-}
-
run_status () {
- (
- # We always show status for the whole tree.
- cd "$TOP"
-
- IS_INITIAL="$initial_commit"
- REFERENCE=HEAD
- case "$amend" in
- t)
- # If we are amending the initial commit, there
- # is no HEAD^1.
- if git-rev-parse --verify "HEAD^1" >/dev/null 2>&1
- then
- REFERENCE="HEAD^1"
- IS_INITIAL=
- else
- IS_INITIAL=t
- fi
- ;;
- esac
-
# If TMP_INDEX is defined, that means we are doing
# "--only" partial commit, and that index file is used
# to build the tree for the commit. Otherwise, if
@@ -88,93 +41,22 @@ run_status () {
# so the regular index file is what we use to compare.
if test '' != "$TMP_INDEX"
then
- GIT_INDEX_FILE="$TMP_INDEX"
- export GIT_INDEX_FILE
+ GIT_INDEX_FILE="$TMP_INDEX"
+ export GIT_INDEX_FILE
elif test -f "$NEXT_INDEX"
then
- GIT_INDEX_FILE="$NEXT_INDEX"
- export GIT_INDEX_FILE
+ GIT_INDEX_FILE="$NEXT_INDEX"
+ export GIT_INDEX_FILE
fi
- case "$branch" in
- refs/heads/master) ;;
- *) echo "# On branch $branch" ;;
+ case "$status_only" in
+ t) color= ;;
+ *) color=--nocolor ;;
esac
-
- if test -z "$IS_INITIAL"
- then
- git-diff-index -M --cached --name-status \
- --diff-filter=MDTCRA $REFERENCE |
- sed -e '
- s/\\/\\\\/g
- s/ /\\ /g
- ' |
- report "Updated but not checked in" "will commit"
- committable="$?"
- else
- echo '#
-# Initial commit
-#'
- git-ls-files |
- sed -e '
- s/\\/\\\\/g
- s/ /\\ /g
- s/^/A /
- ' |
- report "Updated but not checked in" "will commit"
-
- committable="$?"
- fi
-
- git-diff-files --name-status |
- sed -e '
- s/\\/\\\\/g
- s/ /\\ /g
- ' |
- report "Changed but not updated" \
- "use git-update-index to mark for commit"
-
- option=""
- if test -z "$untracked_files"; then
- option="--directory --no-empty-directory"
- fi
- hdr_shown=
- if test -f "$GIT_DIR/info/exclude"
- then
- git-ls-files --others $option \
- --exclude-from="$GIT_DIR/info/exclude" \
- --exclude-per-directory=.gitignore
- else
- git-ls-files --others $option \
- --exclude-per-directory=.gitignore
- fi |
- while read line; do
- if [ -z "$hdr_shown" ]; then
- echo '#'
- echo '# Untracked files:'
- echo '# (use "git add" to add to commit)'
- echo '#'
- hdr_shown=1
- fi
- echo "# $line"
- done
-
- if test -n "$verbose" -a -z "$IS_INITIAL"
- then
- git-diff-index --cached -M -p --diff-filter=MDTCRA $REFERENCE
- fi
- case "$committable" in
- 0)
- case "$amend" in
- t)
- echo "# No changes" ;;
- *)
- echo "nothing to commit" ;;
- esac
- exit 1 ;;
- esac
- exit 0
- )
+ git-runstatus ${color} \
+ ${verbose:+--verbose} \
+ ${amend:+--amend} \
+ ${untracked_files:+--untracked}
}
trap '
@@ -205,179 +87,181 @@ only_include_assumed=
untracked_files=
while case "$#" in 0) break;; esac
do
- case "$1" in
- -F|--F|-f|--f|--fi|--fil|--file)
- case "$#" in 1) usage ;; esac
- shift
- no_edit=t
- log_given=t$log_given
- logfile="$1"
- shift
- ;;
- -F*|-f*)
- no_edit=t
- log_given=t$log_given
- logfile=`expr "z$1" : 'z-[Ff]\(.*\)'`
- shift
- ;;
- --F=*|--f=*|--fi=*|--fil=*|--file=*)
- no_edit=t
- log_given=t$log_given
- logfile=`expr "z$1" : 'z-[^=]*=\(.*\)'`
- shift
- ;;
- -a|--a|--al|--all)
- all=t
- shift
- ;;
- --au=*|--aut=*|--auth=*|--autho=*|--author=*)
- force_author=`expr "z$1" : 'z-[^=]*=\(.*\)'`
- shift
- ;;
- --au|--aut|--auth|--autho|--author)
- case "$#" in 1) usage ;; esac
- shift
- force_author="$1"
- shift
- ;;
- -e|--e|--ed|--edi|--edit)
- edit_flag=t
- shift
- ;;
- -i|--i|--in|--inc|--incl|--inclu|--includ|--include)
- also=t
- shift
- ;;
- -o|--o|--on|--onl|--only)
- only=t
- shift
- ;;
- -m|--m|--me|--mes|--mess|--messa|--messag|--message)
- case "$#" in 1) usage ;; esac
- shift
- log_given=m$log_given
- if test "$log_message" = ''
- then
- log_message="$1"
- else
- log_message="$log_message
+ case "$1" in
+ -F|--F|-f|--f|--fi|--fil|--file)
+ case "$#" in 1) usage ;; esac
+ shift
+ no_edit=t
+ log_given=t$log_given
+ logfile="$1"
+ shift
+ ;;
+ -F*|-f*)
+ no_edit=t
+ log_given=t$log_given
+ logfile=`expr "z$1" : 'z-[Ff]\(.*\)'`
+ shift
+ ;;
+ --F=*|--f=*|--fi=*|--fil=*|--file=*)
+ no_edit=t
+ log_given=t$log_given
+ logfile=`expr "z$1" : 'z-[^=]*=\(.*\)'`
+ shift
+ ;;
+ -a|--a|--al|--all)
+ all=t
+ shift
+ ;;
+ --au=*|--aut=*|--auth=*|--autho=*|--author=*)
+ force_author=`expr "z$1" : 'z-[^=]*=\(.*\)'`
+ shift
+ ;;
+ --au|--aut|--auth|--autho|--author)
+ case "$#" in 1) usage ;; esac
+ shift
+ force_author="$1"
+ shift
+ ;;
+ -e|--e|--ed|--edi|--edit)
+ edit_flag=t
+ shift
+ ;;
+ -i|--i|--in|--inc|--incl|--inclu|--includ|--include)
+ also=t
+ shift
+ ;;
+ -o|--o|--on|--onl|--only)
+ only=t
+ shift
+ ;;
+ -m|--m|--me|--mes|--mess|--messa|--messag|--message)
+ case "$#" in 1) usage ;; esac
+ shift
+ log_given=m$log_given
+ if test "$log_message" = ''
+ then
+ log_message="$1"
+ else
+ log_message="$log_message
$1"
- fi
- no_edit=t
- shift
- ;;
- -m*)
- log_given=m$log_given
- if test "$log_message" = ''
- then
- log_message=`expr "z$1" : 'z-m\(.*\)'`
- else
- log_message="$log_message
+ fi
+ no_edit=t
+ shift
+ ;;
+ -m*)
+ log_given=m$log_given
+ if test "$log_message" = ''
+ then
+ log_message=`expr "z$1" : 'z-m\(.*\)'`
+ else
+ log_message="$log_message
`expr "z$1" : 'z-m\(.*\)'`"
- fi
- no_edit=t
- shift
- ;;
- --m=*|--me=*|--mes=*|--mess=*|--messa=*|--messag=*|--message=*)
- log_given=m$log_given
- if test "$log_message" = ''
- then
- log_message=`expr "z$1" : 'z-[^=]*=\(.*\)'`
- else
- log_message="$log_message
+ fi
+ no_edit=t
+ shift
+ ;;
+ --m=*|--me=*|--mes=*|--mess=*|--messa=*|--messag=*|--message=*)
+ log_given=m$log_given
+ if test "$log_message" = ''
+ then
+ log_message=`expr "z$1" : 'z-[^=]*=\(.*\)'`
+ else
+ log_message="$log_message
`expr "z$1" : 'zq-[^=]*=\(.*\)'`"
- fi
- no_edit=t
- shift
- ;;
- -n|--n|--no|--no-|--no-v|--no-ve|--no-ver|--no-veri|--no-verif|--no-verify)
- verify=
- shift
- ;;
- --a|--am|--ame|--amen|--amend)
- amend=t
- log_given=t$log_given
- use_commit=HEAD
- shift
- ;;
- -c)
- case "$#" in 1) usage ;; esac
- shift
- log_given=t$log_given
- use_commit="$1"
- no_edit=
- shift
- ;;
- --ree=*|--reed=*|--reedi=*|--reedit=*|--reedit-=*|--reedit-m=*|\
- --reedit-me=*|--reedit-mes=*|--reedit-mess=*|--reedit-messa=*|\
- --reedit-messag=*|--reedit-message=*)
- log_given=t$log_given
- use_commit=`expr "z$1" : 'z-[^=]*=\(.*\)'`
- no_edit=
- shift
- ;;
- --ree|--reed|--reedi|--reedit|--reedit-|--reedit-m|--reedit-me|\
- --reedit-mes|--reedit-mess|--reedit-messa|--reedit-messag|--reedit-message)
- case "$#" in 1) usage ;; esac
- shift
- log_given=t$log_given
- use_commit="$1"
- no_edit=
- shift
- ;;
- -C)
- case "$#" in 1) usage ;; esac
- shift
- log_given=t$log_given
- use_commit="$1"
- no_edit=t
- shift
- ;;
- --reu=*|--reus=*|--reuse=*|--reuse-=*|--reuse-m=*|--reuse-me=*|\
- --reuse-mes=*|--reuse-mess=*|--reuse-messa=*|--reuse-messag=*|\
- --reuse-message=*)
- log_given=t$log_given
- use_commit=`expr "z$1" : 'z-[^=]*=\(.*\)'`
- no_edit=t
- shift
- ;;
- --reu|--reus|--reuse|--reuse-|--reuse-m|--reuse-me|--reuse-mes|\
- --reuse-mess|--reuse-messa|--reuse-messag|--reuse-message)
- case "$#" in 1) usage ;; esac
- shift
- log_given=t$log_given
- use_commit="$1"
- no_edit=t
- shift
- ;;
- -s|--s|--si|--sig|--sign|--signo|--signof|--signoff)
- signoff=t
- shift
- ;;
- -v|--v|--ve|--ver|--verb|--verbo|--verbos|--verbose)
- verbose=t
- shift
- ;;
- -u|--u|--un|--unt|--untr|--untra|--untrac|--untrack|--untracke|--untracked|\
- --untracked-|--untracked-f|--untracked-fi|--untracked-fil|--untracked-file|\
- --untracked-files)
- untracked_files=t
- shift
- ;;
- --)
- shift
- break
- ;;
- -*)
- usage
- ;;
- *)
- break
- ;;
- esac
+ fi
+ no_edit=t
+ shift
+ ;;
+ -n|--n|--no|--no-|--no-v|--no-ve|--no-ver|--no-veri|--no-verif|\
+ --no-verify)
+ verify=
+ shift
+ ;;
+ --a|--am|--ame|--amen|--amend)
+ amend=t
+ log_given=t$log_given
+ use_commit=HEAD
+ shift
+ ;;
+ -c)
+ case "$#" in 1) usage ;; esac
+ shift
+ log_given=t$log_given
+ use_commit="$1"
+ no_edit=
+ shift
+ ;;
+ --ree=*|--reed=*|--reedi=*|--reedit=*|--reedit-=*|--reedit-m=*|\
+ --reedit-me=*|--reedit-mes=*|--reedit-mess=*|--reedit-messa=*|\
+ --reedit-messag=*|--reedit-message=*)
+ log_given=t$log_given
+ use_commit=`expr "z$1" : 'z-[^=]*=\(.*\)'`
+ no_edit=
+ shift
+ ;;
+ --ree|--reed|--reedi|--reedit|--reedit-|--reedit-m|--reedit-me|\
+ --reedit-mes|--reedit-mess|--reedit-messa|--reedit-messag|\
+ --reedit-message)
+ case "$#" in 1) usage ;; esac
+ shift
+ log_given=t$log_given
+ use_commit="$1"
+ no_edit=
+ shift
+ ;;
+ -C)
+ case "$#" in 1) usage ;; esac
+ shift
+ log_given=t$log_given
+ use_commit="$1"
+ no_edit=t
+ shift
+ ;;
+ --reu=*|--reus=*|--reuse=*|--reuse-=*|--reuse-m=*|--reuse-me=*|\
+ --reuse-mes=*|--reuse-mess=*|--reuse-messa=*|--reuse-messag=*|\
+ --reuse-message=*)
+ log_given=t$log_given
+ use_commit=`expr "z$1" : 'z-[^=]*=\(.*\)'`
+ no_edit=t
+ shift
+ ;;
+ --reu|--reus|--reuse|--reuse-|--reuse-m|--reuse-me|--reuse-mes|\
+ --reuse-mess|--reuse-messa|--reuse-messag|--reuse-message)
+ case "$#" in 1) usage ;; esac
+ shift
+ log_given=t$log_given
+ use_commit="$1"
+ no_edit=t
+ shift
+ ;;
+ -s|--s|--si|--sig|--sign|--signo|--signof|--signoff)
+ signoff=t
+ shift
+ ;;
+ -v|--v|--ve|--ver|--verb|--verbo|--verbos|--verbose)
+ verbose=t
+ shift
+ ;;
+ -u|--u|--un|--unt|--untr|--untra|--untrac|--untrack|--untracke|\
+ --untracked|--untracked-|--untracked-f|--untracked-fi|--untracked-fil|\
+ --untracked-file|--untracked-files)
+ untracked_files=t
+ shift
+ ;;
+ --)
+ shift
+ break
+ ;;
+ -*)
+ usage
+ ;;
+ *)
+ break
+ ;;
+ esac
done
case "$edit_flag" in t) no_edit= ;; esac
@@ -386,33 +270,33 @@ case "$edit_flag" in t) no_edit= ;; esac
case "$amend,$initial_commit" in
t,t)
- die "You do not have anything to amend." ;;
+ die "You do not have anything to amend." ;;
t,)
- if [ -f "$GIT_DIR/MERGE_HEAD" ]; then
- die "You are in the middle of a merge -- cannot amend."
- fi ;;
+ if [ -f "$GIT_DIR/MERGE_HEAD" ]; then
+ die "You are in the middle of a merge -- cannot amend."
+ fi ;;
esac
case "$log_given" in
tt*)
- die "Only one of -c/-C/-F can be used." ;;
+ die "Only one of -c/-C/-F can be used." ;;
*tm*|*mt*)
- die "Option -m cannot be combined with -c/-C/-F." ;;
+ die "Option -m cannot be combined with -c/-C/-F." ;;
esac
case "$#,$also,$only,$amend" in
*,t,t,*)
- die "Only one of --include/--only can be used." ;;
+ die "Only one of --include/--only can be used." ;;
0,t,,* | 0,,t,)
- die "No paths with --include/--only does not make sense." ;;
+ die "No paths with --include/--only does not make sense." ;;
0,,t,t)
- only_include_assumed="# Clever... amending the last one with dirty index." ;;
+ only_include_assumed="# Clever... amending the last one with dirty index." ;;
0,,,*)
- ;;
+ ;;
*,,,*)
- only_include_assumed="# Explicit paths specified without -i nor -o; assuming --only paths..."
- also=
- ;;
+ only_include_assumed="# Explicit paths specified without -i nor -o; assuming --only paths..."
+ also=
+ ;;
esac
unset only
case "$all,$also,$#" in
@@ -459,47 +343,47 @@ t,)
,)
case "$#" in
0)
- ;; # commit as-is
+ ;; # commit as-is
*)
- if test -f "$GIT_DIR/MERGE_HEAD"
- then
- refuse_partial "Cannot do a partial commit during a merge."
- fi
- TMP_INDEX="$GIT_DIR/tmp-index$$"
- if test -z "$initial_commit"
- then
- # make sure index is clean at the specified paths, or
- # they are additions.
- dirty_in_index=`git-diff-index --cached --name-status \
- --diff-filter=DMTU HEAD -- "$@"`
- test -z "$dirty_in_index" ||
- refuse_partial "Different in index and the last commit:
+ if test -f "$GIT_DIR/MERGE_HEAD"
+ then
+ refuse_partial "Cannot do a partial commit during a merge."
+ fi
+ TMP_INDEX="$GIT_DIR/tmp-index$$"
+ if test -z "$initial_commit"
+ then
+ # make sure index is clean at the specified paths, or
+ # they are additions.
+ dirty_in_index=`git-diff-index --cached --name-status \
+ --diff-filter=DMTU HEAD -- "$@"`
+ test -z "$dirty_in_index" ||
+ refuse_partial "Different in index and the last commit:
$dirty_in_index"
- fi
- commit_only=`git-ls-files --error-unmatch -- "$@"` || exit
-
- # Build the temporary index and update the real index
- # the same way.
- if test -z "$initial_commit"
- then
- cp "$THIS_INDEX" "$TMP_INDEX"
- GIT_INDEX_FILE="$TMP_INDEX" git-read-tree -m HEAD
- else
- rm -f "$TMP_INDEX"
- fi || exit
-
- echo "$commit_only" |
- GIT_INDEX_FILE="$TMP_INDEX" \
- git-update-index --add --remove --stdin &&
-
- save_index &&
- echo "$commit_only" |
- (
- GIT_INDEX_FILE="$NEXT_INDEX"
- export GIT_INDEX_FILE
- git-update-index --remove --stdin
- ) || exit
- ;;
+ fi
+ commit_only=`git-ls-files --error-unmatch -- "$@"` || exit
+
+ # Build the temporary index and update the real index
+ # the same way.
+ if test -z "$initial_commit"
+ then
+ cp "$THIS_INDEX" "$TMP_INDEX"
+ GIT_INDEX_FILE="$TMP_INDEX" git-read-tree -m HEAD
+ else
+ rm -f "$TMP_INDEX"
+ fi || exit
+
+ echo "$commit_only" |
+ GIT_INDEX_FILE="$TMP_INDEX" \
+ git-update-index --add --remove --stdin &&
+
+ save_index &&
+ echo "$commit_only" |
+ (
+ GIT_INDEX_FILE="$NEXT_INDEX"
+ export GIT_INDEX_FILE
+ git-update-index --remove --stdin
+ ) || exit
+ ;;
esac
;;
esac
@@ -517,7 +401,7 @@ else
fi
GIT_INDEX_FILE="$USE_INDEX" \
- git-update-index -q $unmerged_ok_if_status --refresh || exit
+ git-update-index -q $unmerged_ok_if_status --refresh || exit
################################################################
# If the request is status, just show it and exit.
@@ -557,7 +441,7 @@ then
elif test "$use_commit" != ""
then
git-cat-file commit "$use_commit" | sed -e '1,/^$/d'
-elif test -f "$GIT_DIR/MERGE_HEAD" && test -f "$GIT_DIR/MERGE_MSG"
+elif test -f "$GIT_DIR/MERGE_MSG"
then
cat "$GIT_DIR/MERGE_MSG"
elif test -f "$GIT_DIR/SQUASH_MSG"
@@ -638,15 +522,15 @@ then
PARENTS=$(git-cat-file commit HEAD |
sed -n -e '/^$/q' -e 's/^parent /-p /p')
fi
- current=$(git-rev-parse --verify HEAD)
+ current="$(git-rev-parse --verify HEAD)"
else
if [ -z "$(git-ls-files)" ]; then
echo >&2 Nothing to commit
exit 1
fi
PARENTS=""
- current=
rloga='commit (initial)'
+ current=''
fi
if test -z "$no_edit"
@@ -722,8 +606,8 @@ then
fi &&
commit=$(cat "$GIT_DIR"/COMMIT_MSG | git-commit-tree $tree $PARENTS) &&
rlogm=$(sed -e 1q "$GIT_DIR"/COMMIT_MSG) &&
- git-update-ref -m "$rloga: $rlogm" HEAD $commit $current &&
- rm -f -- "$GIT_DIR/MERGE_HEAD" &&
+ git-update-ref -m "$rloga: $rlogm" HEAD $commit "$current" &&
+ rm -f -- "$GIT_DIR/MERGE_HEAD" "$GIT_DIR/MERGE_MSG" &&
if test -f "$NEXT_INDEX"
then
mv "$NEXT_INDEX" "$THIS_INDEX"
diff --git a/git-compat-util.h b/git-compat-util.h
index b2e18954c0..0272d043d0 100644
--- a/git-compat-util.h
+++ b/git-compat-util.h
@@ -26,6 +26,13 @@
#include <sys/types.h>
#include <dirent.h>
+/* On most systems <limits.h> would have given us this, but
+ * not on some systems (e.g. GNU/Hurd).
+ */
+#ifndef PATH_MAX
+#define PATH_MAX 4096
+#endif
+
#ifdef __GNUC__
#define NORETURN __attribute__((__noreturn__))
#else
@@ -84,6 +91,14 @@ extern char *gitstrcasestr(const char *haystack, const char *needle);
extern size_t gitstrlcpy(char *, const char *, size_t);
#endif
+static inline char* xstrdup(const char *str)
+{
+ char *ret = strdup(str);
+ if (!ret)
+ die("Out of memory, strdup failed");
+ return ret;
+}
+
static inline void *xmalloc(size_t size)
{
void *ret = malloc(size);
@@ -172,7 +187,4 @@ static inline int sane_case(int x, int high)
return x;
}
-#ifndef MAXPATHLEN
-#define MAXPATHLEN 256
-#endif
#endif
diff --git a/git-cvsexportcommit.perl b/git-cvsexportcommit.perl
index 99b3dc392a..7bac16e946 100755
--- a/git-cvsexportcommit.perl
+++ b/git-cvsexportcommit.perl
@@ -1,10 +1,10 @@
#!/usr/bin/perl -w
# Known limitations:
-# - cannot add or remove binary files
# - does not propagate permissions
# - tells "ready for commit" even when things could not be completed
-# (eg addition of a binary file)
+# (not sure this is true anymore, more testing is needed)
+# - does not handle whitespace in pathnames at all.
use strict;
use Getopt::Std;
@@ -68,9 +68,9 @@ foreach my $line (@commit) {
if ($stage eq 'headers') {
if ($line =~ m/^parent (\w{40})$/) { # found a parent
push @parents, $1;
- } elsif ($line =~ m/^author (.+) \d+ \+\d+$/) {
+ } elsif ($line =~ m/^author (.+) \d+ [-+]\d+$/) {
$author = $1;
- } elsif ($line =~ m/^committer (.+) \d+ \+\d+$/) {
+ } elsif ($line =~ m/^committer (.+) \d+ [-+]\d+$/) {
$committer = $1;
}
} else {
@@ -135,10 +135,21 @@ foreach my $f (@files) {
if ($fields[4] eq 'M') {
push @mfiles, $fields[5];
}
- if ($fields[4] eq 'R') {
+ if ($fields[4] eq 'D') {
push @dfiles, $fields[5];
}
}
+my (@binfiles, @abfiles, @dbfiles, @bfiles, @mbfiles);
+@binfiles = grep m/^Binary files/, safe_pipe_capture('git-diff-tree', '-p', $parent, $commit);
+map { chomp } @binfiles;
+@abfiles = grep s/^Binary files \/dev\/null and b\/(.*) differ$/$1/, @binfiles;
+@dbfiles = grep s/^Binary files a\/(.*) and \/dev\/null differ$/$1/, @binfiles;
+@mbfiles = grep s/^Binary files a\/(.*) and b\/(.*) differ$/$1/, @binfiles;
+push @bfiles, @abfiles;
+push @bfiles, @dbfiles;
+push @bfiles, @mbfiles;
+push @mfiles, @mbfiles;
+
$opt_v && print "The commit affects:\n ";
$opt_v && print join ("\n ", @afiles,@mfiles,@dfiles) . "\n\n";
undef @files; # don't need it anymore
@@ -153,6 +164,10 @@ foreach my $d (@dirs) {
}
foreach my $f (@afiles) {
# This should return only one value
+ if ($f =~ m,(.*)/[^/]*$,) {
+ my $p = $1;
+ next if (grep { $_ eq $p } @dirs);
+ }
my @status = grep(m/^File/, safe_pipe_capture('cvs', '-q', 'status' ,$f));
if (@status > 1) { warn 'Strange! cvs status returned more than one line?'};
if (-d dirname $f and $status[0] !~ m/Status: Unknown$/
@@ -162,6 +177,7 @@ foreach my $f (@afiles) {
warn "Status was: $status[0]\n";
}
}
+
foreach my $f (@mfiles, @dfiles) {
# TODO:we need to handle removed in cvs
my @status = grep(m/^File/, safe_pipe_capture('cvs', '-q', 'status' ,$f));
@@ -200,24 +216,31 @@ foreach my $d (@dirs) {
print "'Patching' binary files\n";
-my @bfiles = grep(m/^Binary/, safe_pipe_capture('git-diff-tree', '-p', $parent, $commit));
-@bfiles = map { chomp } @bfiles;
foreach my $f (@bfiles) {
# check that the file in cvs matches the "old" file
# extract the file to $tmpdir and compare with cmp
- my $tree = safe_pipe_capture('git-rev-parse', "$parent^{tree}");
- chomp $tree;
- my $blob = `git-ls-tree $tree "$f" | cut -f 1 | cut -d ' ' -f 3`;
- chomp $blob;
- `git-cat-file blob $blob > $tmpdir/blob`;
- if (system('cmp', '-s', $f, "$tmpdir/blob")) {
- warn "Binary file $f in CVS does not match parent.\n";
- $dirty = 1;
- next;
+ if (not(grep { $_ eq $f } @afiles)) {
+ my $tree = safe_pipe_capture('git-rev-parse', "$parent^{tree}");
+ chomp $tree;
+ my $blob = `git-ls-tree $tree "$f" | cut -f 1 | cut -d ' ' -f 3`;
+ chomp $blob;
+ `git-cat-file blob $blob > $tmpdir/blob`;
+ if (system('cmp', '-s', $f, "$tmpdir/blob")) {
+ warn "Binary file $f in CVS does not match parent.\n";
+ if (not $opt_f) {
+ $dirty = 1;
+ next;
+ }
+ }
+ }
+ if (not(grep { $_ eq $f } @dfiles)) {
+ my $tree = safe_pipe_capture('git-rev-parse', "$commit^{tree}");
+ chomp $tree;
+ my $blob = `git-ls-tree $tree "$f" | cut -f 1 | cut -d ' ' -f 3`;
+ chomp $blob;
+ # replace with the new file
+ `git-cat-file blob $blob > $f`;
}
-
- # replace with the new file
- `git-cat-file blob $blob > $f`;
# TODO: something smart with file modes
@@ -231,7 +254,10 @@ if ($dirty) {
my $fuzz = $opt_p ? 0 : 2;
print "Patching non-binary files\n";
-print `(git-diff-tree -p $parent -p $commit | patch -p1 -F $fuzz ) 2>&1`;
+
+if (scalar(@afiles)+scalar(@dfiles)+scalar(@mfiles) != scalar(@bfiles)) {
+ print `(git-diff-tree -p $parent -p $commit | patch -p1 -F $fuzz ) 2>&1`;
+}
my $dirtypatch = 0;
if (($? >> 8) == 2) {
@@ -242,7 +268,11 @@ if (($? >> 8) == 2) {
}
foreach my $f (@afiles) {
- system('cvs', 'add', $f);
+ if (grep { $_ eq $f } @bfiles) {
+ system('cvs', 'add','-kb',$f);
+ } else {
+ system('cvs', 'add', $f);
+ }
if ($?) {
$dirty = 1;
warn "Failed to cvs add $f -- you may need to do it manually";
diff --git a/git-cvsimport.perl b/git-cvsimport.perl
index e5a00a1285..4310dea132 100755
--- a/git-cvsimport.perl
+++ b/git-cvsimport.perl
@@ -161,8 +161,22 @@ sub new {
sub conn {
my $self = shift;
my $repo = $self->{'fullrep'};
- if($repo =~ s/^:pserver:(?:(.*?)(?::(.*?))?@)?([^:\/]*)(?::(\d*))?//) {
- my($user,$pass,$serv,$port) = ($1,$2,$3,$4);
+ if($repo =~ s/^:pserver(?:([^:]*)):(?:(.*?)(?::(.*?))?@)?([^:\/]*)(?::(\d*))?//) {
+ my($param,$user,$pass,$serv,$port) = ($1,$2,$3,$4,$5);
+
+ my($proxyhost,$proxyport);
+ if($param && ($param =~ m/proxy=([^;]+)/)) {
+ $proxyhost = $1;
+ # Default proxyport, if not specified, is 8080.
+ $proxyport = 8080;
+ if($ENV{"CVS_PROXY_PORT"}) {
+ $proxyport = $ENV{"CVS_PROXY_PORT"};
+ }
+ if($param =~ m/proxyport=([^;]+)/){
+ $proxyport = $1;
+ }
+ }
+
$user="anonymous" unless defined $user;
my $rr2 = "-";
unless($port) {
@@ -187,13 +201,43 @@ sub conn {
}
$pass="A" unless $pass;
- my $s = IO::Socket::INET->new(PeerHost => $serv, PeerPort => $port);
- die "Socket to $serv: $!\n" unless defined $s;
+ my ($s, $rep);
+ if($proxyhost) {
+
+ # Use a HTTP Proxy. Only works for HTTP proxies that
+ # don't require user authentication
+ #
+ # See: http://www.ietf.org/rfc/rfc2817.txt
+
+ $s = IO::Socket::INET->new(PeerHost => $proxyhost, PeerPort => $proxyport);
+ die "Socket to $proxyhost: $!\n" unless defined $s;
+ $s->write("CONNECT $serv:$port HTTP/1.1\r\nHost: $serv:$port\r\n\r\n")
+ or die "Write to $proxyhost: $!\n";
+ $s->flush();
+
+ $rep = <$s>;
+
+ # The answer should look like 'HTTP/1.x 2yy ....'
+ if(!($rep =~ m#^HTTP/1\.. 2[0-9][0-9]#)) {
+ die "Proxy connect: $rep\n";
+ }
+ # Skip up to the empty line of the proxy server output
+ # including the response headers.
+ while ($rep = <$s>) {
+ last if (!defined $rep ||
+ $rep eq "\n" ||
+ $rep eq "\r\n");
+ }
+ } else {
+ $s = IO::Socket::INET->new(PeerHost => $serv, PeerPort => $port);
+ die "Socket to $serv: $!\n" unless defined $s;
+ }
+
$s->write("BEGIN AUTH REQUEST\n$repo\n$user\n$pass\nEND AUTH REQUEST\n")
or die "Write to $serv: $!\n";
$s->flush();
- my $rep = <$s>;
+ $rep = <$s>;
if($rep ne "I LOVE YOU\n") {
$rep="<unknown>" unless $rep;
@@ -495,22 +539,17 @@ unless(-d $git_dir) {
$tip_at_start = `git-rev-parse --verify HEAD`;
# Get the last import timestamps
- opendir(D,"$git_dir/refs/heads");
- while(defined(my $head = readdir(D))) {
- next if $head =~ /^\./;
- open(F,"$git_dir/refs/heads/$head")
- or die "Bad head branch: $head: $!\n";
- chomp(my $ftag = <F>);
- close(F);
- open(F,"git-cat-file commit $ftag |");
- while(<F>) {
- next unless /^author\s.*\s(\d+)\s[-+]\d{4}$/;
- $branch_date{$head} = $1;
- last;
- }
- close(F);
+ my $fmt = '($ref, $author) = (%(refname), %(author));';
+ open(H, "git-for-each-ref --perl --format='$fmt' refs/heads |") or
+ die "Cannot run git-for-each-ref: $!\n";
+ while(defined(my $entry = <H>)) {
+ my ($ref, $author);
+ eval($entry) || die "cannot eval refs list: $@";
+ my ($head) = ($ref =~ m|^refs/heads/(.*)|);
+ $author =~ /^.*\s(\d+)\s[-+]\d{4}$/;
+ $branch_date{$head} = $1;
}
- closedir(D);
+ close(H);
}
-d $git_dir
@@ -881,6 +920,16 @@ while(<CVS>) {
}
commit() if $branch and $state != 11;
+# The heuristic of repacking every 1024 commits can leave a
+# lot of unpacked data. If there is more than 1MB worth of
+# not-packed objects, repack once more.
+my $line = `git-count-objects`;
+if ($line =~ /^(\d+) objects, (\d+) kilobytes$/) {
+ my ($n_objects, $kb) = ($1, $2);
+ 1024 < $kb
+ and system("git repack -a -d");
+}
+
foreach my $git_index (values %index) {
if ($git_index ne '.git/index') {
unlink($git_index);
diff --git a/git-cvsserver.perl b/git-cvsserver.perl
index 2130d57020..ca519b7e49 100755
--- a/git-cvsserver.perl
+++ b/git-cvsserver.perl
@@ -275,7 +275,7 @@ sub req_Directory
$state->{directory} = "" if ( $state->{directory} eq "." );
$state->{directory} .= "/" if ( $state->{directory} =~ /\S/ );
- if ( not defined($state->{prependdir}) and $state->{localdir} eq "." and $state->{path} =~ /\S/ )
+ if ( (not defined($state->{prependdir}) or $state->{prependdir} eq '') and $state->{localdir} eq "." and $state->{path} =~ /\S/ )
{
$log->info("Setting prepend to '$state->{path}'");
$state->{prependdir} = $state->{path};
@@ -805,7 +805,14 @@ sub req_update
$meta = $updater->getmeta($filename);
}
- next unless ( $meta->{revision} );
+ if ( ! defined $meta )
+ {
+ $meta = {
+ name => $filename,
+ revision => 0,
+ filehash => 'added'
+ };
+ }
my $oldmeta = $meta;
@@ -835,7 +842,7 @@ sub req_update
and not exists ( $state->{opt}{C} ) )
{
$log->info("Tell the client the file is modified");
- print "MT text U\n";
+ print "MT text M \n";
print "MT fname $filename\n";
print "MT newline\n";
next;
@@ -855,15 +862,36 @@ sub req_update
}
}
elsif ( not defined ( $state->{entries}{$filename}{modified_hash} )
- or $state->{entries}{$filename}{modified_hash} eq $oldmeta->{filehash} )
+ or $state->{entries}{$filename}{modified_hash} eq $oldmeta->{filehash}
+ or $meta->{filehash} eq 'added' )
{
- $log->info("Updating '$filename'");
- # normal update, just send the new revision (either U=Update, or A=Add, or R=Remove)
- print "MT +updated\n";
- print "MT text U\n";
- print "MT fname $filename\n";
- print "MT newline\n";
- print "MT -updated\n";
+ # normal update, just send the new revision (either U=Update,
+ # or A=Add, or R=Remove)
+ if ( defined($wrev) && $wrev < 0 )
+ {
+ $log->info("Tell the client the file is scheduled for removal");
+ print "MT text R \n";
+ print "MT fname $filename\n";
+ print "MT newline\n";
+ next;
+ }
+ elsif ( !defined($wrev) || $wrev == 0 )
+ {
+ $log->info("Tell the client the file will be added");
+ print "MT text A \n";
+ print "MT fname $filename\n";
+ print "MT newline\n";
+ next;
+
+ }
+ else {
+ $log->info("Updating '$filename' $wrev");
+ print "MT +updated\n";
+ print "MT text U \n";
+ print "MT fname $filename\n";
+ print "MT newline\n";
+ print "MT -updated\n";
+ }
my ( $filepart, $dirpart ) = filenamesplit($filename,1);
@@ -1709,6 +1737,17 @@ sub argsfromdir
return if ( scalar ( @{$state->{args}} ) > 1 );
+ my @gethead = @{$updater->gethead};
+
+ # push added files
+ foreach my $file (keys %{$state->{entries}}) {
+ if ( exists $state->{entries}{$file}{revision} &&
+ $state->{entries}{$file}{revision} == 0 )
+ {
+ push @gethead, { name => $file, filehash => 'added' };
+ }
+ }
+
if ( scalar(@{$state->{args}}) == 1 )
{
my $arg = $state->{args}[0];
@@ -1716,7 +1755,7 @@ sub argsfromdir
$log->info("Only one arg specified, checking for directory expansion on '$arg'");
- foreach my $file ( @{$updater->gethead} )
+ foreach my $file ( @gethead )
{
next if ( $file->{filehash} eq "deleted" and not defined ( $state->{entries}{$file->{name}} ) );
next unless ( $file->{name} =~ /^$arg\// or $file->{name} eq $arg );
@@ -1729,7 +1768,7 @@ sub argsfromdir
$state->{args} = [];
- foreach my $file ( @{$updater->gethead} )
+ foreach my $file ( @gethead )
{
next if ( $file->{filehash} eq "deleted" and not defined ( $state->{entries}{$file->{name}} ) );
next unless ( $file->{name} =~ s/^$state->{prependdir}// );
@@ -2079,9 +2118,17 @@ sub new
mode TEXT NOT NULL
)
");
+ $self->{dbh}->do("
+ CREATE INDEX revision_ix1
+ ON revision (name,revision)
+ ");
+ $self->{dbh}->do("
+ CREATE INDEX revision_ix2
+ ON revision (name,commithash)
+ ");
}
- # Construct the revision table if required
+ # Construct the head table if required
unless ( $self->{tables}{head} )
{
$self->{dbh}->do("
@@ -2095,6 +2142,10 @@ sub new
mode TEXT NOT NULL
)
");
+ $self->{dbh}->do("
+ CREATE INDEX head_ix1
+ ON head (name)
+ ");
}
# Construct the properties table if required
@@ -2292,67 +2343,72 @@ sub update
if ( defined ( $lastpicked ) )
{
- my $filepipe = open(FILELIST, '-|', 'git-diff-tree', '-r', $lastpicked, $commit->{hash}) or die("Cannot call git-diff-tree : $!");
+ my $filepipe = open(FILELIST, '-|', 'git-diff-tree', '-z', '-r', $lastpicked, $commit->{hash}) or die("Cannot call git-diff-tree : $!");
+ local ($/) = "\0";
while ( <FILELIST> )
{
- unless ( /^:\d{6}\s+\d{3}(\d)\d{2}\s+[a-zA-Z0-9]{40}\s+([a-zA-Z0-9]{40})\s+(\w)\s+(.*)$/o )
+ chomp;
+ unless ( /^:\d{6}\s+\d{3}(\d)\d{2}\s+[a-zA-Z0-9]{40}\s+([a-zA-Z0-9]{40})\s+(\w)$/o )
{
die("Couldn't process git-diff-tree line : $_");
}
+ my ($mode, $hash, $change) = ($1, $2, $3);
+ my $name = <FILELIST>;
+ chomp($name);
- # $log->debug("File mode=$1, hash=$2, change=$3, name=$4");
+ # $log->debug("File mode=$mode, hash=$hash, change=$change, name=$name");
my $git_perms = "";
- $git_perms .= "r" if ( $1 & 4 );
- $git_perms .= "w" if ( $1 & 2 );
- $git_perms .= "x" if ( $1 & 1 );
+ $git_perms .= "r" if ( $mode & 4 );
+ $git_perms .= "w" if ( $mode & 2 );
+ $git_perms .= "x" if ( $mode & 1 );
$git_perms = "rw" if ( $git_perms eq "" );
- if ( $3 eq "D" )
+ if ( $change eq "D" )
{
- #$log->debug("DELETE $4");
- $head->{$4} = {
- name => $4,
- revision => $head->{$4}{revision} + 1,
+ #$log->debug("DELETE $name");
+ $head->{$name} = {
+ name => $name,
+ revision => $head->{$name}{revision} + 1,
filehash => "deleted",
commithash => $commit->{hash},
modified => $commit->{date},
author => $commit->{author},
mode => $git_perms,
};
- $self->insert_rev($4, $head->{$4}{revision}, $2, $commit->{hash}, $commit->{date}, $commit->{author}, $git_perms);
+ $self->insert_rev($name, $head->{$name}{revision}, $hash, $commit->{hash}, $commit->{date}, $commit->{author}, $git_perms);
}
- elsif ( $3 eq "M" )
+ elsif ( $change eq "M" )
{
- #$log->debug("MODIFIED $4");
- $head->{$4} = {
- name => $4,
- revision => $head->{$4}{revision} + 1,
- filehash => $2,
+ #$log->debug("MODIFIED $name");
+ $head->{$name} = {
+ name => $name,
+ revision => $head->{$name}{revision} + 1,
+ filehash => $hash,
commithash => $commit->{hash},
modified => $commit->{date},
author => $commit->{author},
mode => $git_perms,
};
- $self->insert_rev($4, $head->{$4}{revision}, $2, $commit->{hash}, $commit->{date}, $commit->{author}, $git_perms);
+ $self->insert_rev($name, $head->{$name}{revision}, $hash, $commit->{hash}, $commit->{date}, $commit->{author}, $git_perms);
}
- elsif ( $3 eq "A" )
+ elsif ( $change eq "A" )
{
- #$log->debug("ADDED $4");
- $head->{$4} = {
- name => $4,
+ #$log->debug("ADDED $name");
+ $head->{$name} = {
+ name => $name,
revision => 1,
- filehash => $2,
+ filehash => $hash,
commithash => $commit->{hash},
modified => $commit->{date},
author => $commit->{author},
mode => $git_perms,
};
- $self->insert_rev($4, $head->{$4}{revision}, $2, $commit->{hash}, $commit->{date}, $commit->{author}, $git_perms);
+ $self->insert_rev($name, $head->{$name}{revision}, $hash, $commit->{hash}, $commit->{date}, $commit->{author}, $git_perms);
}
else
{
- $log->warn("UNKNOWN FILE CHANGE mode=$1, hash=$2, change=$3, name=$4");
+ $log->warn("UNKNOWN FILE CHANGE mode=$mode, hash=$hash, change=$change, name=$name");
die;
}
}
@@ -2361,10 +2417,12 @@ sub update
# this is used to detect files removed from the repo
my $seen_files = {};
- my $filepipe = open(FILELIST, '-|', 'git-ls-tree', '-r', $commit->{hash}) or die("Cannot call git-ls-tree : $!");
+ my $filepipe = open(FILELIST, '-|', 'git-ls-tree', '-z', '-r', $commit->{hash}) or die("Cannot call git-ls-tree : $!");
+ local $/ = "\0";
while ( <FILELIST> )
{
- unless ( /^(\d+)\s+(\w+)\s+([a-zA-Z0-9]+)\s+(.*)$/o )
+ chomp;
+ unless ( /^(\d+)\s+(\w+)\s+([a-zA-Z0-9]+)\t(.*)$/o )
{
die("Couldn't process git-ls-tree line : $_");
}
diff --git a/git-fetch.sh b/git-fetch.sh
index c2eebee798..eb32476bbd 100755
--- a/git-fetch.sh
+++ b/git-fetch.sh
@@ -20,7 +20,7 @@ verbose=
update_head_ok=
exec=
upload_pack=
-keep=--thin
+keep=
while case "$#" in 0) break ;; esac
do
case "$1" in
@@ -51,7 +51,7 @@ do
verbose=Yes
;;
-k|--k|--ke|--kee|--keep)
- keep=--keep
+ keep='-k -k'
;;
--reflog-action=*)
rloga=`expr "z$1" : 'z-[^=]*=\(.*\)'`
@@ -68,11 +68,10 @@ done
case "$#" in
0)
- test -f "$GIT_DIR/branches/origin" ||
- test -f "$GIT_DIR/remotes/origin" ||
- git-repo-config --get remote.origin.url >/dev/null ||
- die "Where do you want to fetch from today?"
- set origin ;;
+ origin=$(get_default_remote)
+ test -n "$(get_remote_url ${origin})" ||
+ die "Where do you want to fetch from today?"
+ set x $origin ; shift ;;
esac
remote_nick="$1"
@@ -130,38 +129,44 @@ append_fetch_head () {
then
headc_=$(git-rev-parse --verify "$head_^0") || exit
echo "$headc_ $not_for_merge_ $note_" >>"$GIT_DIR/FETCH_HEAD"
- [ "$verbose" ] && echo >&2 "* committish: $head_"
- [ "$verbose" ] && echo >&2 " $note_"
else
echo "$head_ not-for-merge $note_" >>"$GIT_DIR/FETCH_HEAD"
- [ "$verbose" ] && echo >&2 "* non-commit: $head_"
- [ "$verbose" ] && echo >&2 " $note_"
- fi
- if test "$local_name_" != ""
- then
- # We are storing the head locally. Make sure that it is
- # a fast forward (aka "reverse push").
- fast_forward_local "$local_name_" "$head_" "$note_"
fi
+
+ update_local_ref "$local_name_" "$head_" "$note_"
}
-fast_forward_local () {
- mkdir -p "$(dirname "$GIT_DIR/$1")"
+update_local_ref () {
+ # If we are storing the head locally make sure that it is
+ # a fast forward (aka "reverse push").
+
+ label_=$(git-cat-file -t $2)
+ newshort_=$(git-rev-parse --short $2)
+ if test -z "$1" ; then
+ [ "$verbose" ] && echo >&2 "* fetched $3"
+ [ "$verbose" ] && echo >&2 " $label_: $newshort_"
+ return 0
+ fi
+ oldshort_=$(git show-ref --hash --abbrev "$1" 2>/dev/null)
+
case "$1" in
refs/tags/*)
# Tags need not be pointing at commits so there
# is no way to guarantee "fast-forward" anyway.
- if test -f "$GIT_DIR/$1"
+ if test -n "$oldshort_"
then
- if now_=$(cat "$GIT_DIR/$1") && test "$now_" = "$2"
+ if now_=$(git show-ref --hash "$1") && test "$now_" = "$2"
then
- [ "$verbose" ] && echo >&2 "* $1: same as $3" ||:
+ [ "$verbose" ] && echo >&2 "* $1: same as $3"
+ [ "$verbose" ] && echo >&2 " $label_: $newshort_" ||:
else
echo >&2 "* $1: updating with $3"
+ echo >&2 " $label_: $newshort_"
git-update-ref -m "$rloga: updating tag" "$1" "$2"
fi
else
echo >&2 "* $1: storing $3"
+ echo >&2 " $label_: $newshort_"
git-update-ref -m "$rloga: storing tag" "$1" "$2"
fi
;;
@@ -179,31 +184,34 @@ fast_forward_local () {
if test -n "$verbose"
then
echo >&2 "* $1: same as $3"
+ echo >&2 " $label_: $newshort_"
fi
;;
*,$local)
echo >&2 "* $1: fast forward to $3"
- echo >&2 " from $local to $2"
+ echo >&2 " old..new: $oldshort_..$newshort_"
git-update-ref -m "$rloga: fast-forward" "$1" "$2" "$local"
;;
*)
false
;;
esac || {
- echo >&2 "* $1: does not fast forward to $3;"
case ",$force,$single_force," in
*,t,*)
- echo >&2 " forcing update."
+ echo >&2 "* $1: forcing update to non-fast forward $3"
+ echo >&2 " old...new: $oldshort_...$newshort_"
git-update-ref -m "$rloga: forced-update" "$1" "$2" "$local"
;;
*)
- echo >&2 " not updating."
+ echo >&2 "* $1: not updating to non-fast forward $3"
+ echo >&2 " old...new: $oldshort_...$newshort_"
exit 1
;;
esac
}
else
echo >&2 "* $1: storing $3"
+ echo >&2 " $label_: $newshort_"
git-update-ref -m "$rloga: storing head" "$1" "$2"
fi
;;
@@ -258,6 +266,7 @@ fi
fetch_main () {
reflist="$1"
refs=
+ rref=
for ref in $reflist
do
@@ -286,10 +295,15 @@ fetch_main () {
# There are transports that can fetch only one head at a time...
case "$remote" in
- http://* | https://*)
+ http://* | https://* | ftp://*)
+ proto=`expr "$remote" : '\([^:]*\):'`
if [ -n "$GIT_SSL_NO_VERIFY" ]; then
curl_extra_args="-k"
fi
+ if [ -n "$GIT_CURL_FTP_NO_EPSV" -o \
+ "`git-repo-config --bool http.noEPSV`" = true ]; then
+ noepsv_opt="--disable-epsv"
+ fi
max_depth=5
depth=0
head="ref: $remote_name"
@@ -301,12 +315,12 @@ fetch_main () {
$u =~ s{([^-a-zA-Z0-9/.])}{sprintf"%%%02x",ord($1)}eg;
print "$u";
' "$head")
- head=$(curl -nsfL $curl_extra_args "$remote/$remote_name_quoted")
+ head=$(curl -nsfL $curl_extra_args $noepsv_opt "$remote/$remote_name_quoted")
depth=$( expr \( $depth + 1 \) )
done
expr "z$head" : "z$_x40\$" >/dev/null ||
die "Failed to fetch $remote_name from $remote"
- echo >&2 Fetching "$remote_name from $remote" using http
+ echo >&2 "Fetching $remote_name from $remote using $proto"
git-http-fetch -v -a "$head" "$remote/" || exit
;;
rsync://*)
@@ -350,13 +364,14 @@ fetch_main () {
done
case "$remote" in
- http://* | https://* | rsync://* )
+ http://* | https://* | ftp://* | rsync://* )
;; # we are already done.
*)
( : subshell because we muck with IFS
+ pack_lockfile=
IFS=" $LF"
(
- git-fetch-pack $exec $keep "$remote" $rref || echo failed "$remote"
+ git-fetch-pack --thin $exec $keep "$remote" $rref || echo failed "$remote"
) |
while read sha1 remote_name
do
@@ -364,6 +379,12 @@ fetch_main () {
failed)
echo >&2 "Fetch failure: $remote"
exit 1 ;;
+ # special line coming from index-pack with the pack name
+ pack)
+ continue ;;
+ keep)
+ pack_lockfile="$GIT_OBJECT_DIRECTORY/pack/pack-$remote_name.keep"
+ continue ;;
esac
found=
single_force=
@@ -394,6 +415,7 @@ fetch_main () {
append_fetch_head "$sha1" "$remote" \
"$remote_name" "$remote_nick" "$local_name" "$not_for_merge"
done
+ if [ "$pack_lockfile" ]; then rm -f "$pack_lockfile"; fi
) || exit ;;
esac
@@ -410,10 +432,11 @@ case "$no_tags$tags" in
# using local tracking branch.
taglist=$(IFS=" " &&
git-ls-remote $upload_pack --tags "$remote" |
- sed -ne 's|^\([0-9a-f]*\)[ ]\(refs/tags/.*\)^{}$|\1 \2|p' |
+ sed -n -e 's|^\('"$_x40"'\) \(refs/tags/.*\)^{}$|\1 \2|p' \
+ -e 's|^\('"$_x40"'\) \(refs/tags/.*\)$|\1 \2|p' |
while read sha1 name
do
- test -f "$GIT_DIR/$name" && continue
+ git-show-ref --verify --quiet -- "$name" && continue
git-check-ref-format "$name" || {
echo >&2 "warning: tag ${name} ignored"
continue
@@ -432,10 +455,10 @@ esac
# If the original head was empty (i.e. no "master" yet), or
# if we were told not to worry, we do not have to check.
-case ",$update_head_ok,$orig_head," in
-*,, | t,* )
+case "$orig_head" in
+'')
;;
-*)
+?*)
curr_head=$(git-rev-parse --verify HEAD 2>/dev/null)
if test "$curr_head" != "$orig_head"
then
diff --git a/git-ls-remote.sh b/git-ls-remote.sh
index 2fdcaf7886..0f88953f29 100755
--- a/git-ls-remote.sh
+++ b/git-ls-remote.sh
@@ -49,10 +49,14 @@ trap "rm -fr $tmp-*" 0 1 2 3 15
tmpdir=$tmp-d
case "$peek_repo" in
-http://* | https://* )
+http://* | https://* | ftp://* )
if [ -n "$GIT_SSL_NO_VERIFY" ]; then
curl_extra_args="-k"
fi
+ if [ -n "$GIT_CURL_FTP_NO_EPSV" -o \
+ "`git-repo-config --bool http.noEPSV`" = true ]; then
+ curl_extra_args="${curl_extra_args} --disable-epsv"
+ fi
curl -nsf $curl_extra_args --header "Pragma: no-cache" "$peek_repo/info/refs" ||
echo "failed slurping"
;;
diff --git a/git-merge-one-file.sh b/git-merge-one-file.sh
index fba4b0cb5f..c49e4c65af 100755
--- a/git-merge-one-file.sh
+++ b/git-merge-one-file.sh
@@ -23,6 +23,12 @@ case "${1:-.}${2:-.}${3:-.}" in
"$1.." | "$1.$1" | "$1$1.")
if [ "$2" ]; then
echo "Removing $4"
+ else
+ # read-tree checked that index matches HEAD already,
+ # so we know we do not have this path tracked.
+ # there may be an unrelated working tree file here,
+ # which we should just leave unmolested.
+ exit 0
fi
if test -f "$4"; then
rm -f -- "$4" &&
@@ -34,8 +40,16 @@ case "${1:-.}${2:-.}${3:-.}" in
#
# Added in one.
#
-".$2." | "..$3" )
+".$2.")
+ # the other side did not add and we added so there is nothing
+ # to be done.
+ ;;
+"..$3")
echo "Adding $4"
+ test -f "$4" || {
+ echo "ERROR: untracked $4 is overwritten by the merge."
+ exit 1
+ }
git-update-index --add --cacheinfo "$6$7" "$2$3" "$4" &&
exec git-checkout-index -u -f -- "$4"
;;
diff --git a/git-merge-recursive.py b/git-merge-recursive-old.py
index 4039435ce4..4039435ce4 100755
--- a/git-merge-recursive.py
+++ b/git-merge-recursive-old.py
diff --git a/git-merge.sh b/git-merge.sh
index a9cfafb1df..cb094388bb 100755
--- a/git-merge.sh
+++ b/git-merge.sh
@@ -9,7 +9,7 @@ USAGE='[-n] [--no-commit] [--squash] [-s <strategy>]... <merge-message> <head> <
LF='
'
-all_strategies='recursive octopus resolve stupid ours'
+all_strategies='recur recursive recursive-old octopus resolve stupid ours'
default_twohead_strategies='recursive'
default_octopus_strategies='octopus'
no_trivial_merge_strategies='ours'
@@ -17,8 +17,7 @@ use_strategies=
index_merge=t
if test "@@NO_PYTHON@@"; then
- all_strategies='resolve octopus stupid ours'
- default_twohead_strategies='resolve'
+ all_strategies='recur recursive resolve octopus stupid ours'
fi
dropsave() {
@@ -94,6 +93,8 @@ finish () {
esac
}
+case "$#" in 0) usage ;; esac
+
rloga=
while case "$#" in 0) break ;; esac
do
@@ -198,7 +199,7 @@ f,*)
;;
?,1,"$head",*)
# Again the most common case of merging one remote.
- echo "Updating from $head to $1"
+ echo "Updating $(git-rev-parse --short $head)..$(git-rev-parse --short $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 187f0883c9..c325ef761e 100755
--- a/git-parse-remote.sh
+++ b/git-parse-remote.sh
@@ -68,6 +68,12 @@ get_remote_url () {
esac
}
+get_default_remote () {
+ curr_branch=$(git-symbolic-ref HEAD | sed -e 's|^refs/heads/||')
+ origin=$(git-repo-config --get "branch.$curr_branch.remote")
+ echo ${origin:-origin}
+}
+
get_remote_default_refs_for_push () {
data_source=$(get_data_source "$1")
case "$data_source" in
@@ -86,9 +92,22 @@ get_remote_default_refs_for_push () {
# Subroutine to canonicalize remote:local notation.
canon_refs_list_for_fetch () {
- # Leave only the first one alone; add prefix . to the rest
+ # If called from get_remote_default_refs_for_fetch
+ # leave the branches in branch.${curr_branch}.merge alone,
+ # or the first one otherwise; add prefix . to the rest
# to prevent the secondary branches to be merged by default.
- dot_prefix=
+ merge_branches=
+ if test "$1" = "-d"
+ then
+ shift ; remote="$1" ; shift
+ if test "$remote" = "$(get_default_remote)"
+ then
+ curr_branch=$(git-symbolic-ref HEAD | \
+ sed -e 's|^refs/heads/||')
+ merge_branches=$(git-repo-config \
+ --get-all "branch.${curr_branch}.merge")
+ fi
+ fi
for ref
do
force=
@@ -101,6 +120,18 @@ canon_refs_list_for_fetch () {
expr "z$ref" : 'z.*:' >/dev/null || ref="${ref}:"
remote=$(expr "z$ref" : 'z\([^:]*\):')
local=$(expr "z$ref" : 'z[^:]*:\(.*\)')
+ dot_prefix=.
+ if test -z "$merge_branches"
+ then
+ merge_branches=$remote
+ dot_prefix=
+ else
+ for merge_branch in $merge_branches
+ do
+ [ "$remote" = "$merge_branch" ] &&
+ dot_prefix= && break
+ done
+ fi
case "$remote" in
'') remote=HEAD ;;
refs/heads/* | refs/tags/* | refs/remotes/*) ;;
@@ -120,7 +151,6 @@ canon_refs_list_for_fetch () {
die "* refusing to create funny ref '$local_ref_name' locally"
fi
echo "${dot_prefix}${force}${remote}:${local}"
- dot_prefix=.
done
}
@@ -131,7 +161,7 @@ get_remote_default_refs_for_fetch () {
'' | config-partial | branches-partial)
echo "HEAD:" ;;
config)
- canon_refs_list_for_fetch \
+ canon_refs_list_for_fetch -d "$1" \
$(git-repo-config --get-all "remote.$1.fetch") ;;
branches)
remote_branch=$(sed -ne '/#/s/.*#//p' "$GIT_DIR/branches/$1")
@@ -139,10 +169,7 @@ get_remote_default_refs_for_fetch () {
echo "refs/heads/${remote_branch}:refs/heads/$1"
;;
remotes)
- # This prefixes the second and later default refspecs
- # with a '.', to signal git-fetch to mark them
- # not-for-merge.
- canon_refs_list_for_fetch $(sed -ne '/^Pull: */{
+ canon_refs_list_for_fetch -d "$1" $(sed -ne '/^Pull: */{
s///p
}' "$GIT_DIR/remotes/$1")
;;
diff --git a/git-pull.sh b/git-pull.sh
index f380437997..e23beb685d 100755
--- a/git-pull.sh
+++ b/git-pull.sh
@@ -44,10 +44,10 @@ do
shift
done
-orig_head=$(git-rev-parse --verify HEAD) || die "Pulling into a black hole?"
+orig_head=$(git-rev-parse --verify HEAD 2>/dev/null)
git-fetch --update-head-ok --reflog-action=pull "$@" || exit 1
-curr_head=$(git-rev-parse --verify HEAD)
+curr_head=$(git-rev-parse --verify HEAD 2>/dev/null)
if test "$curr_head" != "$orig_head"
then
# The fetch involved updating the current branch.
@@ -58,7 +58,7 @@ then
echo >&2 "Warning: fetch updated the current branch head."
echo >&2 "Warning: fast forwarding your working tree from"
- echo >&2 "Warning: $orig_head commit."
+ echo >&2 "Warning: commit $orig_head."
git-update-index --refresh 2>/dev/null
git-read-tree -u -m "$orig_head" "$curr_head" ||
die 'Cannot fast-forward your working tree.
@@ -80,6 +80,11 @@ case "$merge_head" in
exit 0
;;
?*' '?*)
+ if test -z "$orig_head"
+ then
+ echo >&2 "Cannot merge multiple branches into empty head"
+ exit 1
+ fi
var=`git-repo-config --get pull.octopus`
if test -n "$var"
then
@@ -95,6 +100,13 @@ case "$merge_head" in
;;
esac
+if test -z "$orig_head"
+then
+ git-update-ref -m "initial pull" HEAD $merge_head "" &&
+ git-read-tree --reset -u HEAD || exit 1
+ exit
+fi
+
case "$strategy_args" in
'')
strategy_args=$strategy_default_args
diff --git a/git-rebase.sh b/git-rebase.sh
index 7d3a5d0e71..546fa446fc 100755
--- a/git-rebase.sh
+++ b/git-rebase.sh
@@ -3,7 +3,7 @@
# Copyright (c) 2005 Junio C Hamano.
#
-USAGE='[--onto <newbase>] <upstream> [<branch>]'
+USAGE='[-v] [--onto <newbase>] <upstream> [<branch>]'
LONG_USAGE='git-rebase replaces <branch> with a new branch of the
same name. When the --onto option is provided the new branch starts
out with a HEAD equal to <newbase>, otherwise it is equal to <upstream>
@@ -39,6 +39,7 @@ strategy=recursive
do_merge=
dotest=$GIT_DIR/.dotest-merge
prec=4
+verbose=
continue_merge () {
test -n "$prev_head" || die "prev_head must be defined"
@@ -190,6 +191,9 @@ do
esac
do_merge=t
;;
+ -v|--verbose)
+ verbose=t
+ ;;
-*)
usage
;;
@@ -273,6 +277,12 @@ then
exit 0
fi
+if test -n "$verbose"
+then
+ echo "Changes from $mb to $onto:"
+ git-diff-tree --stat --summary "$mb" "$onto"
+fi
+
# Rewind the head to "$onto"; this saves our current head in ORIG_HEAD.
git-reset --hard "$onto"
@@ -286,17 +296,17 @@ fi
if test -z "$do_merge"
then
- git-format-patch -k --stdout --full-index "$upstream"..ORIG_HEAD |
+ git-format-patch -k --stdout --full-index --ignore-if-in-upstream "$upstream"..ORIG_HEAD |
git am --binary -3 -k --resolvemsg="$RESOLVEMSG" \
--reflog-action=rebase
exit $?
fi
-if test "@@NO_PYTHON@@" && test "$strategy" = "recursive"
+if test "@@NO_PYTHON@@" && test "$strategy" = "recursive-old"
then
- die 'The recursive merge strategy currently relies on Python,
+ die 'The recursive-old merge strategy is written in Python,
which this installation of git was not configured with. Please consider
-a different merge strategy (e.g. octopus, resolve, stupid, ours)
+a different merge strategy (e.g. recursive, resolve, or stupid)
or install Python and git with Python support.'
fi
diff --git a/git-repack.sh b/git-repack.sh
index 9da92fb061..f150a558ca 100755
--- a/git-repack.sh
+++ b/git-repack.sh
@@ -3,7 +3,8 @@
# Copyright (c) 2005 Linus Torvalds
#
-USAGE='[-a] [-d] [-f] [-l] [-n] [-q]'
+USAGE='[-a] [-d] [-f] [-l] [-n] [-q] [--window=N] [--depth=N]'
+SUBDIRECTORY_OK='Yes'
. git-sh-setup
no_update_info= all_into_one= remove_redundant=
@@ -24,29 +25,44 @@ do
shift
done
-rm -f .tmp-pack-*
+# Later we will default repack.UseDeltaBaseOffset to true
+default_dbo=false
+
+case "`git repo-config --bool repack.usedeltabaseoffset ||
+ echo $default_dbo`" in
+true)
+ extra="$extra --delta-base-offset" ;;
+esac
+
PACKDIR="$GIT_OBJECT_DIRECTORY/pack"
+PACKTMP="$GIT_DIR/.tmp-$$-pack"
+rm -f "$PACKTMP"-*
+trap 'rm -f "$PACKTMP"-*' 0 1 2 3 15
# There will be more repacking strategies to come...
case ",$all_into_one," in
,,)
- rev_list='--unpacked'
- pack_objects='--incremental'
+ args='--unpacked --incremental'
;;
,t,)
- rev_list=
- pack_objects=
-
- # Redundancy check in all-into-one case is trivial.
- existing=`cd "$PACKDIR" && \
- find . -type f \( -name '*.pack' -o -name '*.idx' \) -print`
+ if [ -d "$PACKDIR" ]; then
+ for e in `cd "$PACKDIR" && find . -type f -name '*.pack' \
+ | sed -e 's/^\.\///' -e 's/\.pack$//'`
+ do
+ if [ -e "$PACKDIR/$e.keep" ]; then
+ : keep
+ else
+ args="$args --unpacked=$e.pack"
+ existing="$existing $e"
+ fi
+ done
+ fi
+ [ -z "$args" ] && args='--unpacked --incremental'
;;
esac
-pack_objects="$pack_objects $local $quiet $no_reuse_delta$extra"
-name=$( { git-rev-list --objects --all $rev_list ||
- echo "git-rev-list died with exit code $?"
- } |
- git-pack-objects --non-empty $pack_objects .tmp-pack) ||
+
+args="$args $local $quiet $no_reuse_delta$extra"
+name=$(git-pack-objects --non-empty --all $args </dev/null "$PACKTMP") ||
exit 1
if [ -z "$name" ]; then
echo Nothing new to pack.
@@ -64,8 +80,8 @@ else
"$PACKDIR/old-pack-$name.$sfx"
fi
done &&
- mv -f .tmp-pack-$name.pack "$PACKDIR/pack-$name.pack" &&
- mv -f .tmp-pack-$name.idx "$PACKDIR/pack-$name.idx" &&
+ mv -f "$PACKTMP-$name.pack" "$PACKDIR/pack-$name.pack" &&
+ mv -f "$PACKTMP-$name.idx" "$PACKDIR/pack-$name.idx" &&
test -f "$PACKDIR/pack-$name.pack" &&
test -f "$PACKDIR/pack-$name.idx" || {
echo >&2 "Couldn't replace the existing pack with updated one."
@@ -78,17 +94,16 @@ fi
if test "$remove_redundant" = t
then
- # We know $existing are all redundant only when
- # all-into-one is used.
- if test "$all_into_one" != '' && test "$existing" != ''
+ # We know $existing are all redundant.
+ if [ -n "$existing" ]
then
sync
( cd "$PACKDIR" &&
for e in $existing
do
case "$e" in
- ./pack-$name.pack | ./pack-$name.idx) ;;
- *) rm -f $e ;;
+ pack-$name) ;;
+ *) rm -f "$e.pack" "$e.idx" "$e.keep" ;;
esac
done
)
diff --git a/git-reset.sh b/git-reset.sh
index 36fc8ce25b..3133b5bd25 100755
--- a/git-reset.sh
+++ b/git-reset.sh
@@ -3,9 +3,6 @@
USAGE='[--mixed | --soft | --hard] [<commit-ish>]'
. git-sh-setup
-tmp=${GIT_DIR}/reset.$$
-trap 'rm -f $tmp-*' 0 1 2 3 15
-
update=
reset_type=--mixed
case "$1" in
diff --git a/git-resolve.sh b/git-resolve.sh
index a7bc680d90..36b90e3849 100755
--- a/git-resolve.sh
+++ b/git-resolve.sh
@@ -5,6 +5,10 @@
# Resolve two trees.
#
+echo 'WARNING: This command is DEPRECATED and will be removed very soon.' >&2
+echo 'WARNING: Please use git-merge or git-pull instead.' >&2
+sleep 2
+
USAGE='<head> <remote> <merge-message>'
. git-sh-setup
@@ -42,7 +46,7 @@ case "$common" in
exit 0
;;
"$head")
- echo "Updating from $head to $merge"
+ echo "Updating $(git-rev-parse --short $head)..$(git-rev-parse --short $merge)"
git-read-tree -u -m $head $merge || exit 1
git-update-ref -m "resolve $merge_name: Fast forward" \
HEAD "$merge" "$head"
diff --git a/git-revert.sh b/git-revert.sh
index 2bf35d116c..6eab3c72df 100755
--- a/git-revert.sh
+++ b/git-revert.sh
@@ -7,18 +7,20 @@
case "$0" in
*-revert* )
test -t 0 && edit=-e
+ replay=
me=revert
USAGE='[--edit | --no-edit] [-n] <commit-ish>' ;;
*-cherry-pick* )
+ replay=t
edit=
me=cherry-pick
- USAGE='[--edit] [-n] [-r] <commit-ish>' ;;
+ USAGE='[--edit] [-n] [-r] [-x] <commit-ish>' ;;
* )
die "What are you talking about?" ;;
esac
. git-sh-setup
-no_commit= replay=
+no_commit=
while case "$#" in 0) break ;; esac
do
case "$1" in
@@ -32,8 +34,10 @@ do
--n|--no|--no-|--no-e|--no-ed|--no-edi|--no-edit)
edit=
;;
- -r|--r|--re|--rep|--repl|--repla|--replay)
- replay=t
+ -r)
+ : no-op ;;
+ -x|--i-really-want-to-expose-my-private-commit-object-name)
+ replay=
;;
-*)
usage
@@ -121,7 +125,7 @@ cherry-pick)
git-cat-file commit $commit | sed -e '1,/^$/d'
case "$replay" in
'')
- echo "(cherry picked from $commit commit)"
+ echo "(cherry picked from commit $commit)"
test "$rev" = "$commit" ||
echo "(original 'git cherry-pick' arguments: $@)"
;;
@@ -141,9 +145,18 @@ git-read-tree -m -u --aggressive $base $head $next &&
result=$(git-write-tree 2>/dev/null) || {
echo >&2 "Simple $me fails; trying Automatic $me."
git-merge-index -o git-merge-one-file -a || {
+ mv -f .msg "$GIT_DIR/MERGE_MSG"
+ {
+ echo '
+Conflicts:
+'
+ git ls-files --unmerged |
+ sed -e 's/^[^ ]* / /' |
+ uniq
+ } >>"$GIT_DIR/MERGE_MSG"
echo >&2 "Automatic $me failed. After resolving the conflicts,"
echo >&2 "mark the corrected paths with 'git-update-index <paths>'"
- echo >&2 "and commit with 'git commit -F .msg'"
+ echo >&2 "and commit the result."
case "$me" in
cherry-pick)
echo >&2 "You may choose to use the following when making"
diff --git a/git-send-email.perl b/git-send-email.perl
index a83c7e9094..4c87c20c15 100755
--- a/git-send-email.perl
+++ b/git-send-email.perl
@@ -21,6 +21,7 @@ use warnings;
use Term::ReadLine;
use Getopt::Long;
use Data::Dumper;
+use Git;
package FakeTerm;
sub new {
@@ -82,16 +83,18 @@ sub cleanup_compose_files();
my $compose_filename = ".msg.$$";
# Variables we fill in automatically, or via prompting:
-my (@to,@cc,@initial_cc,@bcclist,
+my (@to,@cc,@initial_cc,@bcclist,@xh,
$initial_reply_to,$initial_subject,@files,$from,$compose,$time);
# Behavior modification variables
-my ($chain_reply_to, $quiet, $suppress_from, $no_signed_off_cc) = (1, 0, 0, 0);
+my ($chain_reply_to, $quiet, $suppress_from, $no_signed_off_cc,
+ $dry_run) = (1, 0, 0, 0, 0);
my $smtp_server;
# Example reply to:
#$initial_reply_to = ''; #<20050203173208.GA23964@foobar.com>';
+my $repo = Git->repository();
my $term = eval {
new Term::ReadLine 'git-send-email';
};
@@ -114,6 +117,7 @@ my $rc = GetOptions("from=s" => \$from,
"quiet" => \$quiet,
"suppress-from" => \$suppress_from,
"no-signed-off-cc|no-signed-off-by-cc" => \$no_signed_off_cc,
+ "dry-run" => \$dry_run,
);
# Verify the user input
@@ -132,33 +136,12 @@ foreach my $entry (@bcclist) {
# Now, let's fill any that aren't set in with defaults:
-sub gitvar {
- my ($var) = @_;
- my $fh;
- my $pid = open($fh, '-|');
- die "$!" unless defined $pid;
- if (!$pid) {
- exec('git-var', $var) or die "$!";
- }
- my ($val) = <$fh>;
- close $fh or die "$!";
- chomp($val);
- return $val;
-}
-
-sub gitvar_ident {
- my ($name) = @_;
- my $val = gitvar($name);
- my @field = split(/\s+/, $val);
- return join(' ', @field[0...(@field-3)]);
-}
-
-my ($author) = gitvar_ident('GIT_AUTHOR_IDENT');
-my ($committer) = gitvar_ident('GIT_COMMITTER_IDENT');
+my ($author) = $repo->ident_person('author');
+my ($committer) = $repo->ident_person('committer');
my %aliases;
-chomp(my @alias_files = `git-repo-config --get-all sendemail.aliasesfile`);
-chomp(my $aliasfiletype = `git-repo-config sendemail.aliasfiletype`);
+my @alias_files = $repo->config('sendemail.aliasesfile');
+my $aliasfiletype = $repo->config('sendemail.aliasfiletype');
my %parse_alias = (
# multiline formats can be supported in the future
mutt => sub { my $fh = shift; while (<$fh>) {
@@ -183,7 +166,7 @@ my %parse_alias = (
}}}
);
-if (@alias_files && defined $parse_alias{$aliasfiletype}) {
+if (@alias_files and $aliasfiletype and defined $parse_alias{$aliasfiletype}) {
foreach my $file (@alias_files) {
open my $fh, '<', $file or die "opening $file: $!\n";
$parse_alias{$aliasfiletype}->($fh);
@@ -248,6 +231,9 @@ if (!defined $initial_reply_to && $prompting) {
}
if (!$smtp_server) {
+ $smtp_server = $repo->config('sendemail.smtpserver');
+}
+if (!$smtp_server) {
foreach (qw( /usr/sbin/sendmail /usr/lib/sendmail )) {
if (-x $_) {
$smtp_server = $_;
@@ -425,12 +411,14 @@ sub send_message
my $date = format_2822_time($time++);
my $gitversion = '@@GIT_VERSION@@';
if ($gitversion =~ m/..GIT_VERSION../) {
- $gitversion = `git --version`;
- chomp $gitversion;
- # keep only what's after the last space
- $gitversion =~ s/^.* //;
+ $gitversion = Git::version();
}
+ my ($author_name) = ($from =~ /^(.*?)\s+</);
+ if ($author_name && $author_name =~ /\./ && $author_name !~ /^".*"$/) {
+ my ($name, $addr) = ($from =~ /^(.*?)(\s+<.*)/);
+ $from = "\"$name\"$addr";
+ }
my $header = "From: $from
To: $to
Cc: $cc
@@ -444,8 +432,13 @@ X-Mailer: git-send-email $gitversion
$header .= "In-Reply-To: $reply_to\n";
$header .= "References: $references\n";
}
+ if (@xh) {
+ $header .= join("\n", @xh) . "\n";
+ }
- if ($smtp_server =~ m#^/#) {
+ if ($dry_run) {
+ # We don't want to send the email.
+ } elsif ($smtp_server =~ m#^/#) {
my $pid = open my $sm, '|-';
defined $pid or die $!;
if (!$pid) {
@@ -494,15 +487,22 @@ foreach my $t (@files) {
my $author_not_sender = undef;
@cc = @initial_cc;
- my $found_mbox = 0;
+ @xh = ();
+ my $input_format = undef;
my $header_done = 0;
$message = "";
while(<F>) {
if (!$header_done) {
- $found_mbox = 1, next if (/^From /);
+ if (/^From /) {
+ $input_format = 'mbox';
+ next;
+ }
chomp;
+ if (!defined $input_format && /^[-A-Za-z]+:\s/) {
+ $input_format = 'mbox';
+ }
- if ($found_mbox) {
+ if (defined $input_format && $input_format eq 'mbox') {
if (/^Subject:\s+(.*)$/) {
$subject = $1;
@@ -510,13 +510,16 @@ foreach my $t (@files) {
if ($2 eq $from) {
next if ($suppress_from);
}
- else {
+ elsif ($1 eq 'From') {
$author_not_sender = $2;
}
printf("(mbox) Adding cc: %s from line '%s'\n",
$2, $_) unless $quiet;
push @cc, $2;
}
+ elsif (!/^Date:\s/ && /^[-A-Za-z]+:\s+\S/) {
+ push @xh, $_;
+ }
} else {
# In the traditional
@@ -524,6 +527,7 @@ foreach my $t (@files) {
# line 1 = cc
# line 2 = subject
# So let's support that, too.
+ $input_format = 'lots';
if (@cc == 0) {
printf("(non-mbox) Adding cc: %s from line '%s'\n",
$_, $_) unless $quiet;
@@ -560,7 +564,7 @@ foreach my $t (@files) {
send_message();
# set up for the next message
- if ($chain_reply_to || length($reply_to) == 0) {
+ if ($chain_reply_to || !defined $reply_to || length($reply_to) == 0) {
$reply_to = $message_id;
if (length $references > 0) {
$references .= " $message_id";
diff --git a/git-shortlog.perl b/git-shortlog.perl
index 0b14f833ee..334fec7477 100755
--- a/git-shortlog.perl
+++ b/git-shortlog.perl
@@ -1,6 +1,18 @@
#!/usr/bin/perl -w
use strict;
+use Getopt::Std;
+use File::Basename qw(basename dirname);
+
+our ($opt_h, $opt_n, $opt_s);
+getopts('hns');
+
+$opt_h && usage();
+
+sub usage {
+ print STDERR "Usage: ${\basename $0} [-h] [-n] [-s] < <log_data>\n";
+ exit(1);
+}
my (%mailmap);
my (%email);
@@ -38,16 +50,38 @@ sub by_name($$) {
uc($a) cmp uc($b);
}
+sub by_nbentries($$) {
+ my ($a, $b) = @_;
+ my $a_entries = $map{$a};
+ my $b_entries = $map{$b};
+
+ @$b_entries - @$a_entries || by_name $a, $b;
+}
+
+my $sort_method = $opt_n ? \&by_nbentries : \&by_name;
+
+sub summary_output {
+ my ($obj, $num, $key);
+
+ foreach $key (sort $sort_method keys %map) {
+ $obj = $map{$key};
+ $num = @$obj;
+ printf "%s: %u\n", $key, $num;
+ $n_output += $num;
+ }
+}
sub shortlog_output {
- my ($obj, $key, $desc);
+ my ($obj, $num, $key, $desc);
+
+ foreach $key (sort $sort_method keys %map) {
+ $obj = $map{$key};
+ $num = @$obj;
- foreach $key (sort by_name keys %map) {
# output author
- printf "%s:\n", $key;
+ printf "%s (%u):\n", $key, $num;
# output author's 1-line summaries
- $obj = $map{$key};
foreach $desc (reverse @$obj) {
print " $desc\n";
$n_output++;
@@ -152,7 +186,7 @@ sub finalize {
&setup_mailmap;
&changelog_input;
-&shortlog_output;
+$opt_s ? &summary_output : &shortlog_output;
&finalize;
exit(0);
diff --git a/git-svn.perl b/git-svn.perl
index 0d58bb9b37..47cd3e27fe 100755
--- a/git-svn.perl
+++ b/git-svn.perl
@@ -39,9 +39,23 @@ memoize('revisions_eq');
memoize('cmt_metadata');
memoize('get_commit_time');
-my ($SVN_PATH, $SVN, $SVN_LOG, $_use_lib);
+my ($SVN_PATH, $SVN, $SVN_LOG, $_use_lib, $AUTH_BATON, $AUTH_CALLBACKS);
+
+sub nag_lib {
+ print STDERR <<EOF;
+! Please consider installing the SVN Perl libraries (version 1.1.0 or
+! newer). You will generally get better performance and fewer bugs,
+! especially if you:
+! 1) have a case-insensitive filesystem
+! 2) replace symlinks with files (and vice-versa) in commits
+
+EOF
+}
+
$_use_lib = 1 unless $ENV{GIT_SVN_NO_LIB};
libsvn_load();
+nag_lib() unless $_use_lib;
+
my $_optimize_commits = 1 unless $ENV{GIT_SVN_NO_OPTIMIZE_COMMITS};
my $sha1 = qr/[a-f\d]{40}/;
my $sha1_short = qr/[a-f\d]{4,40}/;
@@ -51,7 +65,9 @@ my ($_revision,$_stdin,$_no_ignore_ext,$_no_stop_copy,$_help,$_rmdir,$_edit,
$_message, $_file, $_follow_parent, $_no_metadata,
$_template, $_shared, $_no_default_regex, $_no_graft_copy,
$_limit, $_verbose, $_incremental, $_oneline, $_l_fmt, $_show_commit,
- $_version, $_upgrade, $_authors, $_branch_all_refs, @_opt_m);
+ $_version, $_upgrade, $_authors, $_branch_all_refs, @_opt_m,
+ $_merge, $_strategy, $_dry_run, $_ignore_nodate, $_non_recursive,
+ $_username, $_config_dir, $_no_auth_cache);
my (@_branch_from, %tree_map, %users, %rusers, %equiv);
my ($_svn_co_url_revs, $_svn_pg_peg_revs);
my @repo_path_split_cache;
@@ -64,6 +80,10 @@ my %fc_opts = ( 'no-ignore-externals' => \$_no_ignore_ext,
'repack:i' => \$_repack,
'no-metadata' => \$_no_metadata,
'quiet|q' => \$_q,
+ 'username=s' => \$_username,
+ 'config-dir=s' => \$_config_dir,
+ 'no-auth-cache' => \$_no_auth_cache,
+ 'ignore-nodate' => \$_ignore_nodate,
'repack-flags|repack-args|repack-opts=s' => \$_repack_flags);
my ($_trunk, $_tags, $_branches);
@@ -112,11 +132,18 @@ my %cmd = (
'incremental' => \$_incremental,
'oneline' => \$_oneline,
'show-commit' => \$_show_commit,
+ 'non-recursive' => \$_non_recursive,
'authors-file|A=s' => \$_authors,
} ],
'commit-diff' => [ \&commit_diff, 'Commit a diff between two trees',
{ 'message|m=s' => \$_message,
'file|F=s' => \$_file,
+ 'revision|r=s' => \$_revision,
+ %cmt_opts } ],
+ dcommit => [ \&dcommit, 'Commit several diffs to merge with upstream',
+ { 'merge|m|M' => \$_merge,
+ 'strategy|s=s' => \$_strategy,
+ 'dry-run|n' => \$_dry_run,
%cmt_opts } ],
);
@@ -161,11 +188,11 @@ Usage: $0 <command> [options] [arguments]\n
foreach (sort keys %cmd) {
next if $cmd && $cmd ne $_;
- print $fd ' ',pack('A13',$_),$cmd{$_}->[1],"\n";
+ print $fd ' ',pack('A17',$_),$cmd{$_}->[1],"\n";
foreach (keys %{$cmd{$_}->[2]}) {
# prints out arguments as they should be passed:
my $x = s#[:=]s$## ? '<arg>' : s#[:=]i$## ? '<num>' : '';
- print $fd ' ' x 17, join(', ', map { length $_ > 1 ?
+ print $fd ' ' x 21, join(', ', map { length $_ > 1 ?
"--$_" : "-$_" }
split /\|/,$_)," $x\n";
}
@@ -209,7 +236,7 @@ sub rebuild {
my @commit = grep(/^git-svn-id: /,`git-cat-file commit $c`);
next if (!@commit); # skip merges
my ($url, $rev, $uuid) = extract_metadata($commit[$#commit]);
- if (!$rev || !$uuid) {
+ if (!defined $rev || !$uuid) {
croak "Unable to extract revision or UUID from ",
"$c, $commit[$#commit]\n";
}
@@ -500,6 +527,8 @@ sub commit_lib {
my @lock = $SVN::Core::VERSION ge '1.2.0' ? (undef, 0) : ();
my $commit_msg = "$GIT_SVN_DIR/.svn-commit.tmp.$$";
+ my $repo;
+ ($repo, $SVN_PATH) = repo_path_split($SVN_URL);
set_svn_commit_env();
foreach my $c (@revs) {
my $log_msg = get_commit_message($c, $commit_msg);
@@ -508,9 +537,11 @@ sub commit_lib {
# can't track down... (it's probably in the SVN code)
defined(my $pid = open my $fh, '-|') or croak $!;
if (!$pid) {
+ $SVN_LOG = libsvn_connect($repo);
+ $SVN = libsvn_connect($repo);
my $ed = SVN::Git::Editor->new(
{ r => $r_last,
- ra => $SVN,
+ ra => $SVN_LOG,
c => $c,
svn_path => $SVN_PATH
},
@@ -557,6 +588,50 @@ sub commit_lib {
unlink $commit_msg;
}
+sub dcommit {
+ my $gs = "refs/remotes/$GIT_SVN";
+ chomp(my @refs = safe_qx(qw/git-rev-list --no-merges/, "$gs..HEAD"));
+ my $last_rev;
+ foreach my $d (reverse @refs) {
+ if (quiet_run('git-rev-parse','--verify',"$d~1") != 0) {
+ die "Commit $d\n",
+ "has no parent commit, and therefore ",
+ "nothing to diff against.\n",
+ "You should be working from a repository ",
+ "originally created by git-svn\n";
+ }
+ unless (defined $last_rev) {
+ (undef, $last_rev, undef) = cmt_metadata("$d~1");
+ unless (defined $last_rev) {
+ die "Unable to extract revision information ",
+ "from commit $d~1\n";
+ }
+ }
+ if ($_dry_run) {
+ print "diff-tree $d~1 $d\n";
+ } else {
+ if (my $r = commit_diff("$d~1", $d, undef, $last_rev)) {
+ $last_rev = $r;
+ } # else: no changes, same $last_rev
+ }
+ }
+ return if $_dry_run;
+ fetch();
+ my @diff = safe_qx(qw/git-diff-tree HEAD/, $gs);
+ my @finish;
+ if (@diff) {
+ @finish = qw/rebase/;
+ push @finish, qw/--merge/ if $_merge;
+ push @finish, "--strategy=$_strategy" if $_strategy;
+ print STDERR "W: HEAD and $gs differ, using @finish:\n", @diff;
+ } else {
+ print "No changes between current HEAD and $gs\n",
+ "Hard resetting to the latest $gs\n";
+ @finish = qw/reset --mixed/;
+ }
+ sys('git', @finish, $gs);
+}
+
sub show_ignore {
$SVN_URL ||= file_to_s("$GIT_SVN_DIR/info/url");
$_use_lib ? show_ignore_lib() : show_ignore_cmd();
@@ -644,12 +719,17 @@ sub multi_init {
}
$_trunk = $url . $_trunk;
}
+ my $ch_id;
if ($GIT_SVN eq 'git-svn') {
- print "GIT_SVN_ID set to 'trunk' for $_trunk\n";
+ $ch_id = 1;
$GIT_SVN = $ENV{GIT_SVN_ID} = 'trunk';
}
init_vars();
- init($_trunk);
+ unless (-d $GIT_SVN_DIR) {
+ print "GIT_SVN_ID set to 'trunk' for $_trunk\n" if $ch_id;
+ init($_trunk);
+ sys('git-repo-config', 'svn.trunk', $_trunk);
+ }
complete_url_ls_init($url, $_branches, '--branches/-b', '');
complete_url_ls_init($url, $_tags, '--tags/-t', 'tags/');
}
@@ -709,13 +789,18 @@ sub show_log {
# ignore
} elsif (/^:\d{6} \d{6} $sha1_short/o) {
push @{$c->{raw}}, $_;
+ } elsif (/^[ACRMDT]\t/) {
+ # we could add $SVN_PATH here, but that requires
+ # remote access at the moment (repo_path_split)...
+ s#^([ACRMDT])\t# $1 #;
+ push @{$c->{changed}}, $_;
} elsif (/^diff /) {
$d = 1;
push @{$c->{diff}}, $_;
} elsif ($d) {
push @{$c->{diff}}, $_;
} elsif (/^ (git-svn-id:.+)$/) {
- (undef, $c->{r}, undef) = extract_metadata($1);
+ ($c->{url}, $c->{r}, undef) = extract_metadata($1);
} elsif (s/^ //) {
push @{$c->{l}}, $_;
}
@@ -751,6 +836,14 @@ sub commit_diff {
print STDERR "Needed URL or usable git-svn id command-line\n";
commit_diff_usage();
}
+ my $r = shift;
+ unless (defined $r) {
+ if (defined $_revision) {
+ $r = $_revision
+ } else {
+ die "-r|--revision is a required argument\n";
+ }
+ }
if (defined $_message && defined $_file) {
print STDERR "Both --message/-m and --file/-F specified ",
"for the commit message.\n",
@@ -767,13 +860,22 @@ sub commit_diff {
($repo, $SVN_PATH) = repo_path_split($SVN_URL);
$SVN_LOG ||= libsvn_connect($repo);
$SVN ||= libsvn_connect($repo);
+ if ($r eq 'HEAD') {
+ $r = $SVN->get_latest_revnum;
+ } elsif ($r !~ /^\d+$/) {
+ die "revision argument: $r not understood by git-svn\n";
+ }
my @lock = $SVN::Core::VERSION ge '1.2.0' ? (undef, 0) : ();
- my $ed = SVN::Git::Editor->new({ r => $SVN->get_latest_revnum,
- ra => $SVN, c => $tb,
+ my $rev_committed;
+ my $ed = SVN::Git::Editor->new({ r => $r,
+ ra => $SVN_LOG, c => $tb,
svn_path => $SVN_PATH
},
$SVN->get_commit_editor($_message,
- sub {print "Committed $_[0]\n"},@lock)
+ sub {
+ $rev_committed = $_[0];
+ print "Committed $_[0]\n";
+ }, @lock)
);
my $mods = libsvn_checkout_tree($ta, $tb, $ed);
if (@$mods == 0) {
@@ -782,6 +884,8 @@ sub commit_diff {
} else {
$ed->close_edit;
}
+ $_message = $_file = undef;
+ return $rev_committed;
}
########################### utility functions #########################
@@ -806,7 +910,8 @@ sub git_svn_log_cmd {
my ($r_min, $r_max) = @_;
my @cmd = (qw/git-log --abbrev-commit --pretty=raw
--default/, "refs/remotes/$GIT_SVN");
- push @cmd, '--summary' if $_verbose;
+ push @cmd, '-r' unless $_non_recursive;
+ push @cmd, qw/--raw --name-status/ if $_verbose;
return @cmd unless defined $r_max;
if ($r_max == $r_min) {
push @cmd, '--max-count=1';
@@ -817,7 +922,7 @@ sub git_svn_log_cmd {
my ($c_min, $c_max);
$c_max = revdb_get($REVDB, $r_max);
$c_min = revdb_get($REVDB, $r_min);
- if ($c_min && $c_max) {
+ if (defined $c_min && defined $c_max) {
if ($r_max > $r_max) {
push @cmd, "$c_min..$c_max";
} else {
@@ -898,16 +1003,21 @@ sub complete_url_ls_init {
print STDERR "W: Unrecognized URL: $u\n";
die "This should never happen\n";
}
+ # don't try to init already existing refs
my $id = $pfx.$1;
- print "init $u => $id\n";
$GIT_SVN = $ENV{GIT_SVN_ID} = $id;
init_vars();
- init($u);
+ unless (-d $GIT_SVN_DIR) {
+ print "init $u => $id\n";
+ init($u);
+ }
}
exit 0;
}
waitpid $pid, 0;
croak $? if $?;
+ my ($n) = ($switch =~ /^--(\w+)/);
+ sys('git-repo-config', "svn.$n", $var);
}
sub common_prefix {
@@ -1208,6 +1318,7 @@ sub assert_svn_wc_clean {
}
my @status = grep(!/^Performing status on external/,(`svn status`));
@status = grep(!/^\s*$/,@status);
+ @status = grep(!/^X/,@status) if $_no_ignore_ext;
if (scalar @status) {
print STDERR "Tree ($SVN_WC) is not clean:\n";
print STDERR $_ foreach @status;
@@ -1430,10 +1541,13 @@ sub svn_checkout_tree {
apply_mod_line_blob($m);
svn_check_prop_executable($m);
} elsif ($m->{chg} eq 'T') {
- sys(qw(svn rm --force),$m->{file_b});
- apply_mod_line_blob($m);
- sys(qw(svn add), $m->{file_b});
svn_check_prop_executable($m);
+ apply_mod_line_blob($m);
+ if ($m->{mode_a} =~ /^120/ && $m->{mode_b} !~ /^120/) {
+ sys(qw(svn propdel svn:special), $m->{file_b});
+ } else {
+ sys(qw(svn propset svn:special *),$m->{file_b});
+ }
} elsif ($m->{chg} eq 'A') {
svn_ensure_parent_path( $m->{file_b} );
apply_mod_line_blob($m);
@@ -1696,6 +1810,8 @@ sub next_log_entry {
my $rev = $1;
my ($author, $date, $lines) = split(/\s*\|\s*/, $_, 3);
($lines) = ($lines =~ /(\d+)/);
+ $date = '1970-01-01 00:00:00 +0000'
+ if ($_ignore_nodate && $date eq '(no date)');
my ($Y,$m,$d,$H,$M,$S,$tz) = ($date =~
/(\d{4})\-(\d\d)\-(\d\d)\s
(\d\d)\:(\d\d)\:(\d\d)\s([\-\+]\d+)/x)
@@ -2130,7 +2246,7 @@ sub load_authors {
open my $authors, '<', $_authors or die "Can't open $_authors $!\n";
while (<$authors>) {
chomp;
- next unless /^(\S+?)\s*=\s*(.+?)\s*<(.+)>\s*$/;
+ next unless /^(\S+?|\(no author\))\s*=\s*(.+?)\s*<(.+)>\s*$/;
my ($user, $name, $email) = ($1, $2, $3);
$users{$user} = [$name, $email];
}
@@ -2387,7 +2503,7 @@ sub extract_metadata {
my $id = shift or return (undef, undef, undef);
my ($url, $rev, $uuid) = ($id =~ /^git-svn-id:\s(\S+?)\@(\d+)
\s([a-f\d\-]+)$/x);
- if (!$rev || !$uuid || !$url) {
+ if (!defined $rev || !$uuid || !$url) {
# some of the original repositories I made had
# identifiers like this:
($rev, $uuid) = ($id =~/^git-svn-id:\s(\d+)\@([a-f\d\-]+)/);
@@ -2509,6 +2625,12 @@ sub show_commit {
}
}
+sub show_commit_changed_paths {
+ my ($c) = @_;
+ return unless $c->{changed};
+ print "Changed paths:\n", @{$c->{changed}};
+}
+
sub show_commit_normal {
my ($c) = @_;
print '-' x72, "\nr$c->{r} | ";
@@ -2518,7 +2640,8 @@ sub show_commit_normal {
my $nr_line = 0;
if (my $l = $c->{l}) {
- while ($l->[$#$l] eq "\n" && $l->[($#$l - 1)] eq "\n") {
+ while ($l->[$#$l] eq "\n" && $#$l > 0
+ && $l->[($#$l - 1)] eq "\n") {
pop @$l;
}
$nr_line = scalar @$l;
@@ -2530,11 +2653,15 @@ sub show_commit_normal {
} else {
$nr_line .= ' lines';
}
- print $nr_line, "\n\n";
+ print $nr_line, "\n";
+ show_commit_changed_paths($c);
+ print "\n";
print $_ foreach @$l;
}
} else {
- print "1 line\n\n";
+ print "1 line\n";
+ show_commit_changed_paths($c);
+ print "\n";
}
foreach my $x (qw/raw diff/) {
@@ -2560,26 +2687,163 @@ sub libsvn_load {
my $kill_stupid_warnings = $SVN::Node::none.$SVN::Node::file.
$SVN::Node::dir.$SVN::Node::unknown.
$SVN::Node::none.$SVN::Node::file.
- $SVN::Node::dir.$SVN::Node::unknown;
+ $SVN::Node::dir.$SVN::Node::unknown.
+ $SVN::Auth::SSL::CNMISMATCH.
+ $SVN::Auth::SSL::NOTYETVALID.
+ $SVN::Auth::SSL::EXPIRED.
+ $SVN::Auth::SSL::UNKNOWNCA.
+ $SVN::Auth::SSL::OTHER;
1;
};
}
+sub _simple_prompt {
+ my ($cred, $realm, $default_username, $may_save, $pool) = @_;
+ $may_save = undef if $_no_auth_cache;
+ $default_username = $_username if defined $_username;
+ if (defined $default_username && length $default_username) {
+ if (defined $realm && length $realm) {
+ print "Authentication realm: $realm\n";
+ }
+ $cred->username($default_username);
+ } else {
+ _username_prompt($cred, $realm, $may_save, $pool);
+ }
+ $cred->password(_read_password("Password for '" .
+ $cred->username . "': ", $realm));
+ $cred->may_save($may_save);
+ $SVN::_Core::SVN_NO_ERROR;
+}
+
+sub _ssl_server_trust_prompt {
+ my ($cred, $realm, $failures, $cert_info, $may_save, $pool) = @_;
+ $may_save = undef if $_no_auth_cache;
+ print "Error validating server certificate for '$realm':\n";
+ if ($failures & $SVN::Auth::SSL::UNKNOWNCA) {
+ print " - The certificate is not issued by a trusted ",
+ "authority. Use the\n",
+ " fingerprint to validate the certificate manually!\n";
+ }
+ if ($failures & $SVN::Auth::SSL::CNMISMATCH) {
+ print " - The certificate hostname does not match.\n";
+ }
+ if ($failures & $SVN::Auth::SSL::NOTYETVALID) {
+ print " - The certificate is not yet valid.\n";
+ }
+ if ($failures & $SVN::Auth::SSL::EXPIRED) {
+ print " - The certificate has expired.\n";
+ }
+ if ($failures & $SVN::Auth::SSL::OTHER) {
+ print " - The certificate has an unknown error.\n";
+ }
+ printf( "Certificate information:\n".
+ " - Hostname: %s\n".
+ " - Valid: from %s until %s\n".
+ " - Issuer: %s\n".
+ " - Fingerprint: %s\n",
+ map $cert_info->$_, qw(hostname valid_from valid_until
+ issuer_dname fingerprint) );
+ my $choice;
+prompt:
+ print $may_save ?
+ "(R)eject, accept (t)emporarily or accept (p)ermanently? " :
+ "(R)eject or accept (t)emporarily? ";
+ $choice = lc(substr(<STDIN> || 'R', 0, 1));
+ if ($choice =~ /^t$/i) {
+ $cred->may_save(undef);
+ } elsif ($choice =~ /^r$/i) {
+ return -1;
+ } elsif ($may_save && $choice =~ /^p$/i) {
+ $cred->may_save($may_save);
+ } else {
+ goto prompt;
+ }
+ $cred->accepted_failures($failures);
+ $SVN::_Core::SVN_NO_ERROR;
+}
+
+sub _ssl_client_cert_prompt {
+ my ($cred, $realm, $may_save, $pool) = @_;
+ $may_save = undef if $_no_auth_cache;
+ print "Client certificate filename: ";
+ chomp(my $filename = <STDIN>);
+ $cred->cert_file($filename);
+ $cred->may_save($may_save);
+ $SVN::_Core::SVN_NO_ERROR;
+}
+
+sub _ssl_client_cert_pw_prompt {
+ my ($cred, $realm, $may_save, $pool) = @_;
+ $may_save = undef if $_no_auth_cache;
+ $cred->password(_read_password("Password: ", $realm));
+ $cred->may_save($may_save);
+ $SVN::_Core::SVN_NO_ERROR;
+}
+
+sub _username_prompt {
+ my ($cred, $realm, $may_save, $pool) = @_;
+ $may_save = undef if $_no_auth_cache;
+ if (defined $realm && length $realm) {
+ print "Authentication realm: $realm\n";
+ }
+ my $username;
+ if (defined $_username) {
+ $username = $_username;
+ } else {
+ print "Username: ";
+ chomp($username = <STDIN>);
+ }
+ $cred->username($username);
+ $cred->may_save($may_save);
+ $SVN::_Core::SVN_NO_ERROR;
+}
+
+sub _read_password {
+ my ($prompt, $realm) = @_;
+ print $prompt;
+ require Term::ReadKey;
+ Term::ReadKey::ReadMode('noecho');
+ my $password = '';
+ while (defined(my $key = Term::ReadKey::ReadKey(0))) {
+ last if $key =~ /[\012\015]/; # \n\r
+ $password .= $key;
+ }
+ Term::ReadKey::ReadMode('restore');
+ print "\n";
+ $password;
+}
+
sub libsvn_connect {
my ($url) = @_;
- my $auth = SVN::Core::auth_open([SVN::Client::get_simple_provider(),
- SVN::Client::get_ssl_server_trust_file_provider(),
- SVN::Client::get_username_provider()]);
- my $s = eval { SVN::Ra->new(url => $url, auth => $auth) };
- return $s;
+ if (!$AUTH_BATON || !$AUTH_CALLBACKS) {
+ SVN::_Core::svn_config_ensure($_config_dir, undef);
+ ($AUTH_BATON, $AUTH_CALLBACKS) = SVN::Core::auth_open_helper([
+ SVN::Client::get_simple_provider(),
+ SVN::Client::get_ssl_server_trust_file_provider(),
+ SVN::Client::get_simple_prompt_provider(
+ \&_simple_prompt, 2),
+ SVN::Client::get_ssl_client_cert_prompt_provider(
+ \&_ssl_client_cert_prompt, 2),
+ SVN::Client::get_ssl_client_cert_pw_prompt_provider(
+ \&_ssl_client_cert_pw_prompt, 2),
+ SVN::Client::get_username_provider(),
+ SVN::Client::get_ssl_server_trust_prompt_provider(
+ \&_ssl_server_trust_prompt),
+ SVN::Client::get_username_prompt_provider(
+ \&_username_prompt, 2),
+ ]);
+ }
+ SVN::Ra->new(url => $url, auth => $AUTH_BATON,
+ auth_provider_callbacks => $AUTH_CALLBACKS);
}
sub libsvn_get_file {
- my ($gui, $f, $rev) = @_;
+ my ($gui, $f, $rev, $chg) = @_;
my $p = $f;
if (length $SVN_PATH > 0) {
return unless ($p =~ s#^\Q$SVN_PATH\E/##);
}
+ print "\t$chg\t$f\n" unless $_q;
my ($hash, $pid, $in, $out);
my $pool = SVN::Pool->new;
@@ -2682,8 +2946,7 @@ sub libsvn_fetch {
$pool->clear;
}
foreach (@amr) {
- print "\t$_->[0]\t$_->[1]\n" unless $_q;
- libsvn_get_file($gui, $_->[1], $rev)
+ libsvn_get_file($gui, $_->[1], $rev, $_->[0]);
}
close $gui or croak $?;
return libsvn_log_entry($rev, $author, $date, $msg, [$last_commit]);
@@ -2761,8 +3024,7 @@ sub libsvn_traverse {
if (defined $files) {
push @$files, $file;
} else {
- print "\tA\t$file\n" unless $_q;
- libsvn_get_file($gui, $file, $rev);
+ libsvn_get_file($gui, $file, $rev, 'A');
}
}
}
@@ -3053,7 +3315,7 @@ sub copy_remote_ref {
my $ref = "refs/remotes/$GIT_SVN";
if (safe_qx('git-ls-remote', $origin, $ref)) {
sys(qw/git fetch/, $origin, "$ref:$ref");
- } else {
+ } elsif ($_cp_remote && !$_upgrade) {
die "Unable to find remote reference: ",
"refs/remotes/$GIT_SVN on $origin\n";
}
@@ -3270,9 +3532,11 @@ sub chg_file {
seek $fh, 0, 0 or croak $!;
my $exp = $md5->hexdigest;
- my $atd = $self->apply_textdelta($fbat, undef, $self->{pool});
- my $got = SVN::TxDelta::send_stream($fh, @$atd, $self->{pool});
+ my $pool = SVN::Pool->new;
+ my $atd = $self->apply_textdelta($fbat, undef, $pool);
+ my $got = SVN::TxDelta::send_stream($fh, @$atd, $pool);
die "Checksum mismatch\nexpected: $exp\ngot: $got\n" if ($got ne $exp);
+ $pool->clear;
close $fh or croak $!;
}
diff --git a/git-svnimport.perl b/git-svnimport.perl
index 26dc454795..cbaa8ab37c 100755
--- a/git-svnimport.perl
+++ b/git-svnimport.perl
@@ -31,7 +31,7 @@ $SIG{'PIPE'}="IGNORE";
$ENV{'TZ'}="UTC";
our($opt_h,$opt_o,$opt_v,$opt_u,$opt_C,$opt_i,$opt_m,$opt_M,$opt_t,$opt_T,
- $opt_b,$opt_r,$opt_I,$opt_A,$opt_s,$opt_l,$opt_d,$opt_D);
+ $opt_b,$opt_r,$opt_I,$opt_A,$opt_s,$opt_l,$opt_d,$opt_D,$opt_S,$opt_F,$opt_P);
sub usage() {
print STDERR <<END;
@@ -39,17 +39,19 @@ Usage: ${\basename $0} # fetch/update GIT from SVN
[-o branch-for-HEAD] [-h] [-v] [-l max_rev]
[-C GIT_repository] [-t tagname] [-T trunkname] [-b branchname]
[-d|-D] [-i] [-u] [-r] [-I ignorefilename] [-s start_chg]
- [-m] [-M regex] [-A author_file] [SVN_URL]
+ [-m] [-M regex] [-A author_file] [-S] [-F] [-P project_name] [SVN_URL]
END
exit(1);
}
-getopts("A:b:C:dDhiI:l:mM:o:rs:t:T:uv") or usage();
+getopts("A:b:C:dDFhiI:l:mM:o:rs:t:T:SP:uv") or usage();
usage if $opt_h;
my $tag_name = $opt_t || "tags";
my $trunk_name = $opt_T || "trunk";
my $branch_name = $opt_b || "branches";
+my $project_name = $opt_P || "";
+$project_name = "/" . $project_name if ($project_name);
@ARGV == 1 or @ARGV == 2 or usage();
@@ -193,6 +195,13 @@ sub ignore {
}
}
+sub dir_list {
+ my($self,$path,$rev) = @_;
+ my ($dirents,undef,$properties)
+ = $self->{'svn'}->get_dir($path,$rev,undef);
+ return $dirents;
+}
+
package main;
use URI;
@@ -342,35 +351,16 @@ if ($opt_A) {
open BRANCHES,">>", "$git_dir/svn2git";
-sub node_kind($$$) {
- my ($branch, $path, $revision) = @_;
+sub node_kind($$) {
+ my ($svnpath, $revision) = @_;
my $pool=SVN::Pool->new;
- my $kind = $svn->{'svn'}->check_path(revert_split_path($branch,$path),$revision,$pool);
+ my $kind = $svn->{'svn'}->check_path($svnpath,$revision,$pool);
$pool->clear;
return $kind;
}
-sub revert_split_path($$) {
- my($branch,$path) = @_;
-
- my $svnpath;
- $path = "" if $path eq "/"; # this should not happen, but ...
- if($branch eq "/") {
- $svnpath = "$trunk_name/$path";
- } elsif($branch =~ m#^/#) {
- $svnpath = "$tag_name$branch/$path";
- } else {
- $svnpath = "$branch_name/$branch/$path";
- }
-
- $svnpath =~ s#/+$##;
- return $svnpath;
-}
-
sub get_file($$$) {
- my($rev,$branch,$path) = @_;
-
- my $svnpath = revert_split_path($branch,$path);
+ my($svnpath,$rev,$path) = @_;
# now get it
my ($name,$mode);
@@ -413,10 +403,9 @@ sub get_file($$$) {
}
sub get_ignore($$$$$) {
- my($new,$old,$rev,$branch,$path) = @_;
+ my($new,$old,$rev,$path,$svnpath) = @_;
return unless $opt_I;
- my $svnpath = revert_split_path($branch,$path);
my $name = $svn->ignore("$svnpath",$rev);
if ($path eq '/') {
$path = $opt_I;
@@ -435,11 +424,25 @@ sub get_ignore($$$$$) {
close $F;
unlink $name;
push(@$new,['0644',$sha,$path]);
- } else {
+ } elsif (defined $old) {
push(@$old,$path);
}
}
+sub project_path($$)
+{
+ my ($path, $project) = @_;
+
+ $path = "/".$path unless ($path =~ m#^\/#) ;
+ return $1 if ($path =~ m#^$project\/(.*)$#);
+
+ $path =~ s#\.#\\\.#g;
+ $path =~ s#\+#\\\+#g;
+ return "/" if ($project =~ m#^$path.*$#);
+
+ return undef;
+}
+
sub split_path($$) {
my($rev,$path) = @_;
my $branch;
@@ -459,7 +462,11 @@ sub split_path($$) {
print STDERR "$rev: Unrecognized path: $path\n" unless (defined $no_error{$path});
return ()
}
- $path = "/" if $path eq "";
+ if ($path eq "") {
+ $path = "/";
+ } elsif ($project_name) {
+ $path = project_path($path, $project_name);
+ }
return ($branch,$path);
}
@@ -480,6 +487,27 @@ sub branch_rev($$) {
return $therev;
}
+sub expand_svndir($$$);
+
+sub expand_svndir($$$)
+{
+ my ($svnpath, $rev, $path) = @_;
+ my @list;
+ get_ignore(\@list, undef, $rev, $path, $svnpath);
+ my $dirents = $svn->dir_list($svnpath, $rev);
+ foreach my $p(keys %$dirents) {
+ my $kind = node_kind($svnpath.'/'.$p, $rev);
+ if ($kind eq $SVN::Node::file) {
+ my $f = get_file($svnpath.'/'.$p, $rev, $path.'/'.$p);
+ push(@list, $f) if $f;
+ } elsif ($kind eq $SVN::Node::dir) {
+ push(@list,
+ expand_svndir($svnpath.'/'.$p, $rev, $path.'/'.$p));
+ }
+ }
+ return @list;
+}
+
sub copy_path($$$$$$$$) {
# Somebody copied a whole subdirectory.
# We need to find the index entries from the old version which the
@@ -488,8 +516,11 @@ sub copy_path($$$$$$$$) {
my($newrev,$newbranch,$path,$oldpath,$rev,$node_kind,$new,$parents) = @_;
my($srcbranch,$srcpath) = split_path($rev,$oldpath);
- unless(defined $srcbranch) {
- print "Path not found when copying from $oldpath @ $rev\n";
+ unless(defined $srcbranch && defined $srcpath) {
+ print "Path not found when copying from $oldpath @ $rev.\n".
+ "Will try to copy from original SVN location...\n"
+ if $opt_v;
+ push (@$new, expand_svndir($oldpath, $rev, $path));
return;
}
my $therev = branch_rev($srcbranch, $rev);
@@ -503,7 +534,7 @@ sub copy_path($$$$$$$$) {
}
print "$newrev:$newbranch:$path: copying from $srcbranch:$srcpath @ $rev\n" if $opt_v;
if ($node_kind eq $SVN::Node::dir) {
- $srcpath =~ s#/*$#/#;
+ $srcpath =~ s#/*$#/#;
}
my $pid = open my $f,'-|';
@@ -531,21 +562,34 @@ sub copy_path($$$$$$$$) {
sub commit {
my($branch, $changed_paths, $revision, $author, $date, $message) = @_;
- my($author_name,$author_email,$dest);
+ my($committer_name,$committer_email,$dest);
+ my($author_name,$author_email);
my(@old,@new,@parents);
if (not defined $author or $author eq "") {
- $author_name = $author_email = "unknown";
+ $committer_name = $committer_email = "unknown";
} elsif (defined $users_file) {
die "User $author is not listed in $users_file\n"
unless exists $users{$author};
- ($author_name,$author_email) = @{$users{$author}};
+ ($committer_name,$committer_email) = @{$users{$author}};
} elsif ($author =~ /^(.*?)\s+<(.*)>$/) {
- ($author_name, $author_email) = ($1, $2);
+ ($committer_name, $committer_email) = ($1, $2);
} else {
$author =~ s/^<(.*)>$/$1/;
- $author_name = $author_email = $author;
+ $committer_name = $committer_email = $author;
+ }
+
+ if ($opt_F && $message =~ /From:\s+(.*?)\s+<(.*)>\s*\n/) {
+ ($author_name, $author_email) = ($1, $2);
+ print "Author from From: $1 <$2>\n" if ($opt_v);;
+ } elsif ($opt_S && $message =~ /Signed-off-by:\s+(.*?)\s+<(.*)>\s*\n/) {
+ ($author_name, $author_email) = ($1, $2);
+ print "Author from Signed-off-by: $1 <$2>\n" if ($opt_v);;
+ } else {
+ $author_name = $committer_name;
+ $author_email = $committer_email;
}
+
$date = pdate($date);
my $tag;
@@ -569,10 +613,12 @@ sub commit {
if(defined $oldpath) {
my $p;
($parent,$p) = split_path($revision,$oldpath);
- if($parent eq "/") {
- $parent = $opt_o;
- } else {
- $parent =~ s#^/##; # if it's a tag
+ if(defined $parent) {
+ if($parent eq "/") {
+ $parent = $opt_o;
+ } else {
+ $parent =~ s#^/##; # if it's a tag
+ }
}
} else {
$parent = undef;
@@ -638,9 +684,10 @@ sub commit {
push(@old,$path); # remove any old stuff
}
if(($action->[0] eq "A") || ($action->[0] eq "R")) {
- my $node_kind = node_kind($branch,$path,$revision);
+ my $node_kind = node_kind($action->[3], $revision);
if ($node_kind eq $SVN::Node::file) {
- my $f = get_file($revision,$branch,$path);
+ my $f = get_file($action->[3],
+ $revision, $path);
if ($f) {
push(@new,$f) if $f;
} else {
@@ -655,19 +702,20 @@ sub commit {
\@new, \@parents);
} else {
get_ignore(\@new, \@old, $revision,
- $branch, $path);
+ $path, $action->[3]);
}
}
} elsif ($action->[0] eq "D") {
push(@old,$path);
} elsif ($action->[0] eq "M") {
- my $node_kind = node_kind($branch,$path,$revision);
+ my $node_kind = node_kind($action->[3], $revision);
if ($node_kind eq $SVN::Node::file) {
- my $f = get_file($revision,$branch,$path);
+ my $f = get_file($action->[3],
+ $revision, $path);
push(@new,$f) if $f;
} elsif ($node_kind eq $SVN::Node::dir) {
get_ignore(\@new, \@old, $revision,
- $branch,$path);
+ $path, $action->[3]);
}
} else {
die "$revision: unknown action '".$action->[0]."' for $path\n";
@@ -772,8 +820,8 @@ sub commit {
"GIT_AUTHOR_NAME=$author_name",
"GIT_AUTHOR_EMAIL=$author_email",
"GIT_AUTHOR_DATE=".strftime("+0000 %Y-%m-%d %H:%M:%S",gmtime($date)),
- "GIT_COMMITTER_NAME=$author_name",
- "GIT_COMMITTER_EMAIL=$author_email",
+ "GIT_COMMITTER_NAME=$committer_name",
+ "GIT_COMMITTER_EMAIL=$committer_email",
"GIT_COMMITTER_DATE=".strftime("+0000 %Y-%m-%d %H:%M:%S",gmtime($date)),
"git-commit-tree", $tree,@par);
die "Cannot exec git-commit-tree: $!\n";
@@ -825,7 +873,7 @@ sub commit {
print $out ("object $cid\n".
"type commit\n".
"tag $dest\n".
- "tagger $author_name <$author_email>\n") and
+ "tagger $committer_name <$committer_email> 0 +0000\n") and
close($out)
or die "Cannot create tag object $dest: $!\n";
@@ -870,6 +918,7 @@ sub commit_all {
while(my($path,$action) = each %$changed_paths) {
($branch,$path) = split_path($revision,$path);
next if not defined $branch;
+ next if not defined $path;
$done{$branch}{$path} = $action;
}
while(($branch,$changed_paths) = each %done) {
diff --git a/git-tag.sh b/git-tag.sh
index a0afa25821..ac269e3277 100755
--- a/git-tag.sh
+++ b/git-tag.sh
@@ -47,8 +47,10 @@ do
-d)
shift
tag_name="$1"
- rm "$GIT_DIR/refs/tags/$tag_name" && \
- echo "Deleted tag $tag_name."
+ tag=$(git-show-ref --verify --hash -- "refs/tags/$tag_name") ||
+ die "Seriously, what tag are you talking about?"
+ git-update-ref -m 'tag: delete' -d "refs/tags/$tag_name" "$tag" &&
+ echo "Deleted tag $tag_name."
exit $?
;;
-*)
@@ -63,8 +65,11 @@ done
name="$1"
[ "$name" ] || usage
-if [ -e "$GIT_DIR/refs/tags/$name" -a -z "$force" ]; then
- die "tag '$name' already exists"
+prev=0000000000000000000000000000000000000000
+if git-show-ref --verify --quiet -- "refs/tags/$name"
+then
+ test -n "$force" || die "tag '$name' already exists"
+ prev=`git rev-parse "refs/tags/$name"`
fi
shift
git-check-ref-format "tags/$name" ||
@@ -107,6 +112,5 @@ if [ "$annotate" ]; then
object=$(git-mktag < "$GIT_DIR"/TAG_TMP)
fi
-leading=`expr "refs/tags/$name" : '\(.*\)/'` &&
-mkdir -p "$GIT_DIR/$leading" &&
-echo $object > "$GIT_DIR/refs/tags/$name"
+git update-ref "refs/tags/$name" "$object" "$prev"
+
diff --git a/git.c b/git.c
index 5da7787d86..1aa07a5164 100644
--- a/git.c
+++ b/git.c
@@ -16,7 +16,7 @@
#include "builtin.h"
const char git_usage_string[] =
- "git [--version] [--exec-path[=GIT_EXEC_PATH]] [--help] COMMAND [ ARGS ]";
+ "git [--version] [--exec-path[=GIT_EXEC_PATH]] [-p|--paginate] [--bare] [--git-dir=GIT_DIR] [--help] COMMAND [ARGS]";
static void prepend_to_path(const char *dir, int len)
{
@@ -29,13 +29,15 @@ static void prepend_to_path(const char *dir, int len)
path_len = len + strlen(old_path) + 1;
- path = malloc(path_len + 1);
+ path = xmalloc(path_len + 1);
memcpy(path, dir, len);
path[len] = ':';
memcpy(path + len + 1, old_path, path_len - len);
setenv("PATH", path, 1);
+
+ free(path);
}
static int handle_options(const char*** argv, int* argc)
@@ -92,12 +94,12 @@ static int handle_options(const char*** argv, int* argc)
}
static const char *alias_command;
-static char *alias_string = NULL;
+static char *alias_string;
static int git_alias_config(const char *var, const char *value)
{
if (!strncmp(var, "alias.", 6) && !strcmp(var + 6, alias_command)) {
- alias_string = strdup(value);
+ alias_string = xstrdup(value);
}
return 0;
}
@@ -120,7 +122,7 @@ static int split_cmdline(char *cmdline, const char ***argv)
; /* skip */
if (count >= size) {
size += 16;
- *argv = realloc(*argv, sizeof(char*) * size);
+ *argv = xrealloc(*argv, sizeof(char*) * size);
}
(*argv)[count++] = cmdline + dst;
} else if(!quoted && (c == '\'' || c == '"')) {
@@ -179,20 +181,12 @@ static int handle_alias(int *argcp, const char ***argv)
if (!strcmp(alias_command, new_argv[0]))
die("recursive alias: %s", alias_command);
- if (getenv("GIT_TRACE")) {
- int i;
- fprintf(stderr, "trace: alias expansion: %s =>",
- alias_command);
- for (i = 0; i < count; ++i) {
- fputc(' ', stderr);
- sq_quote_print(stderr, new_argv[i]);
- }
- fputc('\n', stderr);
- fflush(stderr);
- }
+ trace_argv_printf(new_argv, count,
+ "trace: alias expansion: %s =>",
+ alias_command);
- new_argv = realloc(new_argv, sizeof(char*) *
- (count + *argcp + 1));
+ new_argv = xrealloc(new_argv, sizeof(char*) *
+ (count + *argcp + 1));
/* insert after command name */
memcpy(new_argv + count, *argv + 1, sizeof(char*) * *argcp);
new_argv[count+*argcp] = NULL;
@@ -225,18 +219,24 @@ static void handle_internal_command(int argc, const char **argv, char **envp)
int option;
} commands[] = {
{ "add", cmd_add, RUN_SETUP },
+ { "annotate", cmd_annotate, },
{ "apply", cmd_apply },
+ { "archive", cmd_archive },
+ { "blame", cmd_blame, RUN_SETUP | USE_PAGER },
+ { "branch", cmd_branch, RUN_SETUP },
{ "cat-file", cmd_cat_file, RUN_SETUP },
{ "checkout-index", cmd_checkout_index, RUN_SETUP },
{ "check-ref-format", cmd_check_ref_format },
+ { "cherry", cmd_cherry, RUN_SETUP },
{ "commit-tree", cmd_commit_tree, RUN_SETUP },
- { "count-objects", cmd_count_objects },
- { "diff", cmd_diff, RUN_SETUP },
+ { "count-objects", cmd_count_objects, RUN_SETUP },
+ { "diff", cmd_diff, RUN_SETUP | USE_PAGER },
{ "diff-files", cmd_diff_files, RUN_SETUP },
{ "diff-index", cmd_diff_index, RUN_SETUP },
{ "diff-stages", cmd_diff_stages, RUN_SETUP },
{ "diff-tree", cmd_diff_tree, RUN_SETUP },
{ "fmt-merge-msg", cmd_fmt_merge_msg, RUN_SETUP },
+ { "for-each-ref", cmd_for_each_ref, RUN_SETUP },
{ "format-patch", cmd_format_patch, RUN_SETUP },
{ "get-tar-commit-id", cmd_get_tar_commit_id },
{ "grep", cmd_grep, RUN_SETUP },
@@ -250,6 +250,7 @@ static void handle_internal_command(int argc, const char **argv, char **envp)
{ "mv", cmd_mv, RUN_SETUP },
{ "name-rev", cmd_name_rev, RUN_SETUP },
{ "pack-objects", cmd_pack_objects, RUN_SETUP },
+ { "pickaxe", cmd_blame, RUN_SETUP | USE_PAGER },
{ "prune", cmd_prune, RUN_SETUP },
{ "prune-packed", cmd_prune_packed, RUN_SETUP },
{ "push", cmd_push, RUN_SETUP },
@@ -258,19 +259,22 @@ static void handle_internal_command(int argc, const char **argv, char **envp)
{ "rev-list", cmd_rev_list, RUN_SETUP },
{ "rev-parse", cmd_rev_parse, RUN_SETUP },
{ "rm", cmd_rm, RUN_SETUP },
+ { "runstatus", cmd_runstatus, RUN_SETUP },
{ "show-branch", cmd_show_branch, RUN_SETUP },
{ "show", cmd_show, RUN_SETUP | USE_PAGER },
{ "stripspace", cmd_stripspace },
{ "symbolic-ref", cmd_symbolic_ref, RUN_SETUP },
- { "tar-tree", cmd_tar_tree, RUN_SETUP },
+ { "tar-tree", cmd_tar_tree },
{ "unpack-objects", cmd_unpack_objects, RUN_SETUP },
{ "update-index", cmd_update_index, RUN_SETUP },
{ "update-ref", cmd_update_ref, RUN_SETUP },
- { "upload-tar", cmd_upload_tar },
+ { "upload-archive", cmd_upload_archive },
{ "version", cmd_version },
{ "whatchanged", cmd_whatchanged, RUN_SETUP | USE_PAGER },
{ "write-tree", cmd_write_tree, RUN_SETUP },
{ "verify-pack", cmd_verify_pack },
+ { "show-ref", cmd_show_ref, RUN_SETUP },
+ { "pack-refs", cmd_pack_refs, RUN_SETUP },
};
int i;
@@ -291,16 +295,7 @@ static void handle_internal_command(int argc, const char **argv, char **envp)
prefix = setup_git_directory();
if (p->option & USE_PAGER)
setup_pager();
- if (getenv("GIT_TRACE")) {
- int i;
- fprintf(stderr, "trace: built-in: git");
- for (i = 0; i < argc; ++i) {
- fputc(' ', stderr);
- sq_quote_print(stderr, argv[i]);
- }
- putc('\n', stderr);
- fflush(stderr);
- }
+ trace_argv_printf(argv, argc, "trace: built-in: git");
exit(p->fn(argc, argv, prefix));
}
@@ -308,7 +303,7 @@ static void handle_internal_command(int argc, const char **argv, char **envp)
int main(int argc, const char **argv, char **envp)
{
- const char *cmd = argv[0];
+ const char *cmd = argv[0] ? argv[0] : "git-help";
char *slash = strrchr(cmd, '/');
const char *exec_path = NULL;
int done_alias = 0;
diff --git a/git.spec.in b/git.spec.in
index 8ccd2564e7..83268fc9d9 100644
--- a/git.spec.in
+++ b/git.spec.in
@@ -9,7 +9,7 @@ URL: http://kernel.org/pub/software/scm/git/
Source: http://kernel.org/pub/software/scm/git/%{name}-%{version}.tar.gz
BuildRequires: zlib-devel >= 1.2, openssl-devel, curl-devel, expat-devel %{!?_without_docs:, xmlto, asciidoc > 6.0.3}
BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n)
-Requires: git-core, git-svn, git-cvs, git-arch, git-email, gitk
+Requires: git-core, git-svn, git-cvs, git-arch, git-email, gitk, perl-Git
%description
This is a stupid (but extremely fast) directory content manager. It
@@ -70,6 +70,16 @@ Requires: git-core = %{version}-%{release}, tk >= 8.4
%description -n gitk
Git revision tree visualiser ('gitk')
+%package -n perl-Git
+Summary: Perl interface to Git
+Group: Development/Libraries
+Requires: git-core = %{version}-%{release}
+Requires: perl(:MODULE_COMPAT_%(eval "`%{__perl} -V:version`"; echo $version))
+BuildRequires: perl(Error)
+
+%description -n perl-Git
+Perl interface to Git
+
%prep
%setup -q
@@ -80,12 +90,18 @@ make %{_smp_mflags} CFLAGS="$RPM_OPT_FLAGS" WITH_OWN_SUBPROCESS_PY=YesPlease \
%install
rm -rf $RPM_BUILD_ROOT
make %{_smp_mflags} DESTDIR=$RPM_BUILD_ROOT WITH_OWN_SUBPROCESS_PY=YesPlease \
- prefix=%{_prefix} mandir=%{_mandir} \
+ prefix=%{_prefix} mandir=%{_mandir} INSTALLDIRS=vendor \
install %{!?_without_docs: install-doc}
+find $RPM_BUILD_ROOT -type f -name .packlist -exec rm -f {} ';'
+find $RPM_BUILD_ROOT -type f -name '*.bs' -empty -exec rm -f {} ';'
+find $RPM_BUILD_ROOT -type f -name perllocal.pod -exec rm -f {} ';'
-(find $RPM_BUILD_ROOT%{_bindir} -type f | grep -vE "arch|svn|cvs|email|gitk" | sed -e s@^$RPM_BUILD_ROOT@@) > bin-man-doc-files
+(find $RPM_BUILD_ROOT%{_bindir} -type f | grep -vE "archimport|svn|cvs|email|gitk" | sed -e s@^$RPM_BUILD_ROOT@@) > bin-man-doc-files
+(find $RPM_BUILD_ROOT%{perl_vendorlib} -type f | sed -e s@^$RPM_BUILD_ROOT@@) >> perl-files
%if %{!?_without_docs:1}0
-(find $RPM_BUILD_ROOT%{_mandir} $RPM_BUILD_ROOT/Documentation -type f | grep -vE "arch|svn|git-cvs|email|gitk" | sed -e s@^$RPM_BUILD_ROOT@@ -e 's/$/*/' ) >> bin-man-doc-files
+(find $RPM_BUILD_ROOT%{_mandir} $RPM_BUILD_ROOT/Documentation -type f | grep -vE "archimport|svn|git-cvs|email|gitk" | sed -e s@^$RPM_BUILD_ROOT@@ -e 's/$/*/' ) >> bin-man-doc-files
+%else
+rm -rf $RPM_BUILD_ROOT%{_mandir}
%endif
%clean
@@ -110,10 +126,10 @@ rm -rf $RPM_BUILD_ROOT
%files arch
%defattr(-,root,root)
-%doc Documentation/*arch*.txt
-%{_bindir}/*arch*
-%{!?_without_docs: %{_mandir}/man1/*arch*.1*}
-%{!?_without_docs: %doc Documentation/*arch*.html }
+%doc Documentation/git-archimport.txt
+%{_bindir}/git-archimport
+%{!?_without_docs: %{_mandir}/man1/git-archimport.1*}
+%{!?_without_docs: %doc Documentation/git-archimport.html }
%files email
%defattr(-,root,root)
@@ -129,6 +145,9 @@ rm -rf $RPM_BUILD_ROOT
%{!?_without_docs: %{_mandir}/man1/*gitk*.1*}
%{!?_without_docs: %doc Documentation/*gitk*.html }
+%files -n perl-Git -f perl-files
+%defattr(-,root,root)
+
%files core -f bin-man-doc-files
%defattr(-,root,root)
%{_datadir}/git-core/
diff --git a/gitk b/gitk
index a92ab007b4..ab383b3ad2 100755
--- a/gitk
+++ b/gitk
@@ -2,7 +2,7 @@
# Tcl ignores the next line -*- tcl -*- \
exec wish "$0" -- "$@"
-# Copyright (C) 2005 Paul Mackerras. All rights reserved.
+# Copyright (C) 2005-2006 Paul Mackerras. All rights reserved.
# This program is free software; it may be used, copied, modified
# and distributed under the terms of the GNU General Public Licence,
# either version 2, or (at your option) any later version.
@@ -17,13 +17,12 @@ proc gitdir {} {
}
proc start_rev_list {view} {
- global startmsecs nextupdate ncmupdate
+ global startmsecs nextupdate
global commfd leftover tclencoding datemode
global viewargs viewfiles commitidx
set startmsecs [clock clicks -milliseconds]
set nextupdate [expr {$startmsecs + 100}]
- set ncmupdate 1
set commitidx($view) 0
set args $viewargs($view)
if {$viewfiles($view) ne {}} {
@@ -79,7 +78,7 @@ proc getcommitlines {fd view} {
global parentlist childlist children curview hlview
global vparentlist vchildlist vdisporder vcmitlisted
- set stuff [read $fd]
+ set stuff [read $fd 500000]
if {$stuff == {}} {
if {![eof $fd]} return
global viewname
@@ -185,7 +184,7 @@ proc getcommitlines {fd view} {
}
if {$gotsome} {
if {$view == $curview} {
- layoutmore
+ while {[layoutmore $nextupdate]} doupdate
} elseif {[info exists hlview] && $view == $hlview} {
vhighlightmore
}
@@ -196,20 +195,13 @@ proc getcommitlines {fd view} {
}
proc doupdate {} {
- global commfd nextupdate numcommits ncmupdate
+ global commfd nextupdate numcommits
foreach v [array names commfd] {
fileevent $commfd($v) readable {}
}
update
set nextupdate [expr {[clock clicks -milliseconds] + 100}]
- if {$numcommits < 100} {
- set ncmupdate [expr {$numcommits + 1}]
- } elseif {$numcommits < 10000} {
- set ncmupdate [expr {$numcommits + 10}]
- } else {
- set ncmupdate [expr {$numcommits + 100}]
- }
foreach v [array names commfd] {
set fd $commfd($v)
fileevent $fd readable [list getcommitlines $fd $v]
@@ -341,13 +333,13 @@ proc readrefs {} {
set tag {}
catch {
set commit [exec git rev-parse "$id^0"]
- if {"$commit" != "$id"} {
+ if {$commit != $id} {
set tagids($name) $commit
lappend idtags($commit) $name
}
}
catch {
- set tagcontents($name) [exec git cat-file tag "$id"]
+ set tagcontents($name) [exec git cat-file tag $id]
}
} elseif { $type == "heads" } {
set headids($name) $id
@@ -384,6 +376,23 @@ proc error_popup msg {
show_error $w $w $msg
}
+proc confirm_popup msg {
+ global confirm_ok
+ set confirm_ok 0
+ set w .confirm
+ toplevel $w
+ wm transient $w .
+ message $w.m -text $msg -justify center -aspect 400
+ pack $w.m -side top -fill x -padx 20 -pady 20
+ button $w.ok -text OK -command "set confirm_ok 1; destroy $w"
+ pack $w.ok -side left -fill x
+ button $w.cancel -text Cancel -command "destroy $w"
+ pack $w.cancel -side right -fill x
+ bind $w <Visibility> "grab $w; focus $w"
+ tkwait window $w
+ return $confirm_ok
+}
+
proc makewindow {} {
global canv canv2 canv3 linespc charspc ctext cflist
global textfont mainfont uifont
@@ -394,6 +403,7 @@ proc makewindow {} {
global highlight_files gdttype
global searchstring sstring
global bgcolor fgcolor bglist fglist diffcolors
+ global headctxmenu
menu .bar
.bar add cascade -label "File" -menu .bar.file
@@ -711,6 +721,16 @@ proc makewindow {} {
$rowctxmenu add command -label "Make patch" -command mkpatch
$rowctxmenu add command -label "Create tag" -command mktag
$rowctxmenu add command -label "Write commit to file" -command writecommit
+ $rowctxmenu add command -label "Create new branch" -command mkbranch
+ $rowctxmenu add command -label "Cherry-pick this commit" \
+ -command cherrypick
+
+ set headctxmenu .headctxmenu
+ menu $headctxmenu -tearoff 0
+ $headctxmenu add command -label "Check out this branch" \
+ -command cobranch
+ $headctxmenu add command -label "Remove this branch" \
+ -command rmbranch
}
# mouse-2 makes all windows scan vertically, but only the one
@@ -1669,7 +1689,7 @@ proc showview {n} {
show_status "Reading commits..."
}
if {[info exists commfd($n)]} {
- layoutmore
+ layoutmore {}
} else {
finishcommits
}
@@ -2350,20 +2370,38 @@ proc visiblerows {} {
return [list $r0 $r1]
}
-proc layoutmore {} {
+proc layoutmore {tmax} {
global rowlaidout rowoptim commitidx numcommits optim_delay
global uparrowlen curview
- set row $rowlaidout
- set rowlaidout [layoutrows $row $commitidx($curview) 0]
- set orow [expr {$rowlaidout - $uparrowlen - 1}]
- if {$orow > $rowoptim} {
- optimize_rows $rowoptim 0 $orow
- set rowoptim $orow
- }
- set canshow [expr {$rowoptim - $optim_delay}]
- if {$canshow > $numcommits} {
- showstuff $canshow
+ while {1} {
+ if {$rowoptim - $optim_delay > $numcommits} {
+ showstuff [expr {$rowoptim - $optim_delay}]
+ } elseif {$rowlaidout - $uparrowlen - 1 > $rowoptim} {
+ set nr [expr {$rowlaidout - $uparrowlen - 1 - $rowoptim}]
+ if {$nr > 100} {
+ set nr 100
+ }
+ optimize_rows $rowoptim 0 [expr {$rowoptim + $nr}]
+ incr rowoptim $nr
+ } elseif {$commitidx($curview) > $rowlaidout} {
+ set nr [expr {$commitidx($curview) - $rowlaidout}]
+ # may need to increase this threshold if uparrowlen or
+ # mingaplen are increased...
+ if {$nr > 150} {
+ set nr 150
+ }
+ set row $rowlaidout
+ set rowlaidout [layoutrows $row [expr {$row + $nr}] 0]
+ if {$rowlaidout == $row} {
+ return 0
+ }
+ } else {
+ return 0
+ }
+ if {$tmax ne {} && [clock clicks -milliseconds] >= $tmax} {
+ return 1
+ }
}
}
@@ -3236,6 +3274,8 @@ proc drawtags {id x xt y1} {
-font $font -tags [list tag.$id text]]
if {$ntags >= 0} {
$canv bind $t <1> [list showtag $tag 1]
+ } elseif {$nheads >= 0} {
+ $canv bind $t <Button-3> [list headmenu %X %Y $id $tag]
}
}
return $xt
@@ -3263,8 +3303,7 @@ proc show_status {msg} {
proc finishcommits {} {
global commitidx phase curview
- global canv mainfont ctext maincursor textcursor
- global findinprogress pending_select
+ global pending_select
if {$commitidx($curview) > 0} {
drawrest
@@ -3275,6 +3314,108 @@ proc finishcommits {} {
catch {unset pending_select}
}
+# Insert a new commit as the child of the commit on row $row.
+# The new commit will be displayed on row $row and the commits
+# on that row and below will move down one row.
+proc insertrow {row newcmit} {
+ global displayorder parentlist childlist commitlisted
+ global commitrow curview rowidlist rowoffsets numcommits
+ global rowrangelist idrowranges rowlaidout rowoptim numcommits
+ global linesegends selectedline
+
+ if {$row >= $numcommits} {
+ puts "oops, inserting new row $row but only have $numcommits rows"
+ return
+ }
+ set p [lindex $displayorder $row]
+ set displayorder [linsert $displayorder $row $newcmit]
+ set parentlist [linsert $parentlist $row $p]
+ set kids [lindex $childlist $row]
+ lappend kids $newcmit
+ lset childlist $row $kids
+ set childlist [linsert $childlist $row {}]
+ set commitlisted [linsert $commitlisted $row 1]
+ set l [llength $displayorder]
+ for {set r $row} {$r < $l} {incr r} {
+ set id [lindex $displayorder $r]
+ set commitrow($curview,$id) $r
+ }
+
+ set idlist [lindex $rowidlist $row]
+ set offs [lindex $rowoffsets $row]
+ set newoffs {}
+ foreach x $idlist {
+ if {$x eq {} || ($x eq $p && [llength $kids] == 1)} {
+ lappend newoffs {}
+ } else {
+ lappend newoffs 0
+ }
+ }
+ if {[llength $kids] == 1} {
+ set col [lsearch -exact $idlist $p]
+ lset idlist $col $newcmit
+ } else {
+ set col [llength $idlist]
+ lappend idlist $newcmit
+ lappend offs {}
+ lset rowoffsets $row $offs
+ }
+ set rowidlist [linsert $rowidlist $row $idlist]
+ set rowoffsets [linsert $rowoffsets [expr {$row+1}] $newoffs]
+
+ set rowrangelist [linsert $rowrangelist $row {}]
+ set l [llength $rowrangelist]
+ for {set r 0} {$r < $l} {incr r} {
+ set ranges [lindex $rowrangelist $r]
+ if {$ranges ne {} && [lindex $ranges end] >= $row} {
+ set newranges {}
+ foreach x $ranges {
+ if {$x >= $row} {
+ lappend newranges [expr {$x + 1}]
+ } else {
+ lappend newranges $x
+ }
+ }
+ lset rowrangelist $r $newranges
+ }
+ }
+ if {[llength $kids] > 1} {
+ set rp1 [expr {$row + 1}]
+ set ranges [lindex $rowrangelist $rp1]
+ if {$ranges eq {}} {
+ set ranges [list $row $rp1]
+ } elseif {[lindex $ranges end-1] == $rp1} {
+ lset ranges end-1 $row
+ }
+ lset rowrangelist $rp1 $ranges
+ }
+ foreach id [array names idrowranges] {
+ set ranges $idrowranges($id)
+ if {$ranges ne {} && [lindex $ranges end] >= $row} {
+ set newranges {}
+ foreach x $ranges {
+ if {$x >= $row} {
+ lappend newranges [expr {$x + 1}]
+ } else {
+ lappend newranges $x
+ }
+ }
+ set idrowranges($id) $newranges
+ }
+ }
+
+ set linesegends [linsert $linesegends $row {}]
+
+ incr rowlaidout
+ incr rowoptim
+ incr numcommits
+
+ if {[info exists selectedline] && $selectedline >= $row} {
+ incr selectedline
+ }
+ redisplay
+}
+
# Don't change the text pane cursor if it is currently the hand cursor,
# showing that we are over a sha1 ID link.
proc settextcursor {c} {
@@ -3307,9 +3448,7 @@ proc notbusy {what} {
}
proc drawrest {} {
- global numcommits
global startmsecs
- global canvy0 numcommits linespc
global rowlaidout commitidx curview
global pending_select
@@ -3323,6 +3462,7 @@ proc drawrest {} {
}
set drawmsecs [expr {[clock clicks -milliseconds] - $startmsecs}]
+ #global numcommits
#puts "overall $drawmsecs ms for $numcommits commits"
}
@@ -3603,27 +3743,20 @@ proc viewnextline {dir} {
# add a list of tag or branch names at position pos
# returns the number of names inserted
-proc appendrefs {pos l var} {
- global ctext commitrow linknum curview idtags $var
+proc appendrefs {pos tags var} {
+ global ctext commitrow linknum curview $var
if {[catch {$ctext index $pos}]} {
return 0
}
- set tags {}
- foreach id $l {
- foreach tag [set $var\($id\)] {
- lappend tags [concat $tag $id]
- }
- }
- set tags [lsort -index 1 $tags]
+ set tags [lsort $tags]
set sep {}
foreach tag $tags {
- set name [lindex $tag 0]
- set id [lindex $tag 1]
+ set id [set $var\($tag\)]
set lk link$linknum
incr linknum
$ctext insert $pos $sep
- $ctext insert $pos $name $lk
+ $ctext insert $pos $tag $lk
$ctext tag conf $lk -foreground blue
if {[info exists commitrow($curview,$id)]} {
$ctext tag bind $lk <1> \
@@ -3637,6 +3770,18 @@ proc appendrefs {pos l var} {
return [llength $tags]
}
+proc taglist {ids} {
+ global idtags
+
+ set tags {}
+ foreach id $ids {
+ foreach tag $idtags($id) {
+ lappend tags $tag
+ }
+ }
+ return $tags
+}
+
# called when we have finished computing the nearby tags
proc dispneartags {} {
global selectedline currentid ctext anc_tags desc_tags showneartags
@@ -3646,15 +3791,15 @@ proc dispneartags {} {
set id $currentid
$ctext conf -state normal
if {[info exists desc_heads($id)]} {
- if {[appendrefs branch $desc_heads($id) idheads] > 1} {
+ if {[appendrefs branch $desc_heads($id) headids] > 1} {
$ctext insert "branch -2c" "es"
}
}
if {[info exists anc_tags($id)]} {
- appendrefs follows $anc_tags($id) idtags
+ appendrefs follows [taglist $anc_tags($id)] tagids
}
if {[info exists desc_tags($id)]} {
- appendrefs precedes $desc_tags($id) idtags
+ appendrefs precedes [taglist $desc_tags($id)] tagids
}
$ctext conf -state disabled
}
@@ -3787,7 +3932,7 @@ proc selectline {l isnew} {
$ctext mark set branch "end -1c"
$ctext mark gravity branch left
if {[info exists desc_heads($id)]} {
- if {[appendrefs branch $desc_heads($id) idheads] > 1} {
+ if {[appendrefs branch $desc_heads($id) headids] > 1} {
# turn "Branch" into "Branches"
$ctext insert "branch -2c" "es"
}
@@ -3796,13 +3941,13 @@ proc selectline {l isnew} {
$ctext mark set follows "end -1c"
$ctext mark gravity follows left
if {[info exists anc_tags($id)]} {
- appendrefs follows $anc_tags($id) idtags
+ appendrefs follows [taglist $anc_tags($id)] tagids
}
$ctext insert end "\nPrecedes: "
$ctext mark set precedes "end -1c"
$ctext mark gravity precedes left
if {[info exists desc_tags($id)]} {
- appendrefs precedes $desc_tags($id) idtags
+ appendrefs precedes [taglist $desc_tags($id)] tagids
}
$ctext insert end "\n"
}
@@ -4295,12 +4440,27 @@ proc getblobdiffline {bdf ids} {
}
}
+proc prevfile {} {
+ global difffilestart ctext
+ set prev [lindex $difffilestart 0]
+ set here [$ctext index @0,0]
+ foreach loc $difffilestart {
+ if {[$ctext compare $loc >= $here]} {
+ $ctext yview $prev
+ return
+ }
+ set prev $loc
+ }
+ $ctext yview $prev
+}
+
proc nextfile {} {
global difffilestart ctext
set here [$ctext index @0,0]
foreach loc $difffilestart {
if {[$ctext compare $loc > $here]} {
$ctext yview $loc
+ return
}
}
}
@@ -4463,6 +4623,7 @@ proc redisplay {} {
drawvisible
if {[info exists selectedline]} {
selectline $selectedline 0
+ allcanvs yview moveto [lindex $span 0]
}
}
@@ -4930,6 +5091,7 @@ proc domktag {} {
set tagids($tag) $id
lappend idtags($id) $tag
redrawtags $id
+ addedtag $id
}
proc redrawtags {id} {
@@ -5020,10 +5182,164 @@ proc wrcomcan {} {
unset wrcomtop
}
+proc mkbranch {} {
+ global rowmenuid mkbrtop
+
+ set top .makebranch
+ catch {destroy $top}
+ toplevel $top
+ label $top.title -text "Create new branch"
+ grid $top.title - -pady 10
+ label $top.id -text "ID:"
+ entry $top.sha1 -width 40 -relief flat
+ $top.sha1 insert 0 $rowmenuid
+ $top.sha1 conf -state readonly
+ grid $top.id $top.sha1 -sticky w
+ label $top.nlab -text "Name:"
+ entry $top.name -width 40
+ grid $top.nlab $top.name -sticky w
+ frame $top.buts
+ button $top.buts.go -text "Create" -command [list mkbrgo $top]
+ button $top.buts.can -text "Cancel" -command "catch {destroy $top}"
+ grid $top.buts.go $top.buts.can
+ grid columnconfigure $top.buts 0 -weight 1 -uniform a
+ grid columnconfigure $top.buts 1 -weight 1 -uniform a
+ grid $top.buts - -pady 10 -sticky ew
+ focus $top.name
+}
+
+proc mkbrgo {top} {
+ global headids idheads
+
+ set name [$top.name get]
+ set id [$top.sha1 get]
+ if {$name eq {}} {
+ error_popup "Please specify a name for the new branch"
+ return
+ }
+ catch {destroy $top}
+ nowbusy newbranch
+ update
+ if {[catch {
+ exec git branch $name $id
+ } err]} {
+ notbusy newbranch
+ error_popup $err
+ } else {
+ addedhead $id $name
+ # XXX should update list of heads displayed for selected commit
+ notbusy newbranch
+ redrawtags $id
+ }
+}
+
+proc cherrypick {} {
+ global rowmenuid curview commitrow
+ global mainhead desc_heads anc_tags desc_tags allparents allchildren
+
+ if {[info exists desc_heads($rowmenuid)]
+ && [lsearch -exact $desc_heads($rowmenuid) $mainhead] >= 0} {
+ set ok [confirm_popup "Commit [string range $rowmenuid 0 7] is already\
+ included in branch $mainhead -- really re-apply it?"]
+ if {!$ok} return
+ }
+ nowbusy cherrypick
+ update
+ set oldhead [exec git rev-parse HEAD]
+ # Unfortunately git-cherry-pick writes stuff to stderr even when
+ # no error occurs, and exec takes that as an indication of error...
+ if {[catch {exec sh -c "git cherry-pick -r $rowmenuid 2>&1"} err]} {
+ notbusy cherrypick
+ error_popup $err
+ return
+ }
+ set newhead [exec git rev-parse HEAD]
+ if {$newhead eq $oldhead} {
+ notbusy cherrypick
+ error_popup "No changes committed"
+ return
+ }
+ set allparents($newhead) $oldhead
+ lappend allchildren($oldhead) $newhead
+ set desc_heads($newhead) $mainhead
+ if {[info exists anc_tags($oldhead)]} {
+ set anc_tags($newhead) $anc_tags($oldhead)
+ }
+ set desc_tags($newhead) {}
+ if {[info exists commitrow($curview,$oldhead)]} {
+ insertrow $commitrow($curview,$oldhead) $newhead
+ if {$mainhead ne {}} {
+ movedhead $newhead $mainhead
+ }
+ redrawtags $oldhead
+ redrawtags $newhead
+ }
+ notbusy cherrypick
+}
+
+# context menu for a head
+proc headmenu {x y id head} {
+ global headmenuid headmenuhead headctxmenu
+
+ set headmenuid $id
+ set headmenuhead $head
+ tk_popup $headctxmenu $x $y
+}
+
+proc cobranch {} {
+ global headmenuid headmenuhead mainhead headids
+
+ # check the tree is clean first??
+ set oldmainhead $mainhead
+ nowbusy checkout
+ update
+ if {[catch {
+ exec git checkout $headmenuhead
+ } err]} {
+ notbusy checkout
+ error_popup $err
+ } else {
+ notbusy checkout
+ set mainhead $headmenuhead
+ if {[info exists headids($oldmainhead)]} {
+ redrawtags $headids($oldmainhead)
+ }
+ redrawtags $headmenuid
+ }
+}
+
+proc rmbranch {} {
+ global desc_heads headmenuid headmenuhead mainhead
+ global headids idheads
+
+ set head $headmenuhead
+ set id $headmenuid
+ if {$head eq $mainhead} {
+ error_popup "Cannot delete the currently checked-out branch"
+ return
+ }
+ if {$desc_heads($id) eq $head} {
+ # the stuff on this branch isn't on any other branch
+ if {![confirm_popup "The commits on branch $head aren't on any other\
+ branch.\nReally delete branch $head?"]} return
+ }
+ nowbusy rmbranch
+ update
+ if {[catch {exec git branch -D $head} err]} {
+ notbusy rmbranch
+ error_popup $err
+ return
+ }
+ removedhead $id $head
+ redrawtags $id
+ notbusy rmbranch
+}
+
# Stuff for finding nearby tags
proc getallcommits {} {
- global allcstart allcommits allcfd
+ global allcstart allcommits allcfd allids
+ set allids {}
set fd [open [concat | git rev-list --all --topo-order --parents] r]
set allcfd $fd
fconfigure $fd -blocking 0
@@ -5107,10 +5423,52 @@ proc combine_atags {l1 l2} {
return $res
}
+proc forward_pass {id children} {
+ global idtags desc_tags idheads desc_heads alldtags tagisdesc
+
+ set dtags {}
+ set dheads {}
+ foreach child $children {
+ if {[info exists idtags($child)]} {
+ set ctags [list $child]
+ } else {
+ set ctags $desc_tags($child)
+ }
+ if {$dtags eq {}} {
+ set dtags $ctags
+ } elseif {$ctags ne $dtags} {
+ set dtags [combine_dtags $dtags $ctags]
+ }
+ set cheads $desc_heads($child)
+ if {$dheads eq {}} {
+ set dheads $cheads
+ } elseif {$cheads ne $dheads} {
+ set dheads [lsort -unique [concat $dheads $cheads]]
+ }
+ }
+ set desc_tags($id) $dtags
+ if {[info exists idtags($id)]} {
+ set adt $dtags
+ foreach tag $dtags {
+ set adt [concat $adt $alldtags($tag)]
+ }
+ set adt [lsort -unique $adt]
+ set alldtags($id) $adt
+ foreach tag $adt {
+ set tagisdesc($id,$tag) -1
+ set tagisdesc($tag,$id) 1
+ }
+ }
+ if {[info exists idheads($id)]} {
+ set dheads [concat $dheads $idheads($id)]
+ }
+ set desc_heads($id) $dheads
+}
+
proc getallclines {fd} {
global allparents allchildren allcommits allcstart
- global desc_tags anc_tags idtags alldtags tagisdesc allids
- global desc_heads idheads
+ global desc_tags anc_tags idtags tagisdesc allids
+ global idheads travindex
while {[gets $fd line] >= 0} {
set id [lindex $line 0]
@@ -5125,43 +5483,7 @@ proc getallclines {fd} {
}
# compute nearest tagged descendents as we go
# also compute descendent heads
- set dtags {}
- set dheads {}
- foreach child $allchildren($id) {
- if {[info exists idtags($child)]} {
- set ctags [list $child]
- } else {
- set ctags $desc_tags($child)
- }
- if {$dtags eq {}} {
- set dtags $ctags
- } elseif {$ctags ne $dtags} {
- set dtags [combine_dtags $dtags $ctags]
- }
- set cheads $desc_heads($child)
- if {$dheads eq {}} {
- set dheads $cheads
- } elseif {$cheads ne $dheads} {
- set dheads [lsort -unique [concat $dheads $cheads]]
- }
- }
- set desc_tags($id) $dtags
- if {[info exists idtags($id)]} {
- set adt $dtags
- foreach tag $dtags {
- set adt [concat $adt $alldtags($tag)]
- }
- set adt [lsort -unique $adt]
- set alldtags($id) $adt
- foreach tag $adt {
- set tagisdesc($id,$tag) -1
- set tagisdesc($tag,$id) 1
- }
- }
- if {[info exists idheads($id)]} {
- lappend dheads $id
- }
- set desc_heads($id) $dheads
+ forward_pass $id $allchildren($id)
if {[clock clicks -milliseconds] - $allcstart >= 50} {
fileevent $fd readable {}
after idle restartgetall $fd
@@ -5169,7 +5491,9 @@ proc getallclines {fd} {
}
}
if {[eof $fd]} {
- after idle restartatags [llength $allids]
+ set travindex [llength $allids]
+ set allcommits "traversing"
+ after idle restartatags
if {[catch {close $fd} err]} {
error_popup "Error reading full commit graph: $err.\n\
Results may be incomplete."
@@ -5178,10 +5502,11 @@ proc getallclines {fd} {
}
# walk backward through the tree and compute nearest tagged ancestors
-proc restartatags {i} {
- global allids allparents idtags anc_tags t0
+proc restartatags {} {
+ global allids allparents idtags anc_tags travindex
set t0 [clock clicks -milliseconds]
+ set i $travindex
while {[incr i -1] >= 0} {
set id [lindex $allids $i]
set atags {}
@@ -5199,17 +5524,195 @@ proc restartatags {i} {
}
set anc_tags($id) $atags
if {[clock clicks -milliseconds] - $t0 >= 50} {
- after idle restartatags $i
+ set travindex $i
+ after idle restartatags
return
}
}
set allcommits "done"
+ set travindex 0
notbusy allcommits
dispneartags
}
+# update the desc_tags and anc_tags arrays for a new tag just added
+proc addedtag {id} {
+ global desc_tags anc_tags allparents allchildren allcommits
+ global idtags tagisdesc alldtags
+
+ if {![info exists desc_tags($id)]} return
+ set adt $desc_tags($id)
+ foreach t $desc_tags($id) {
+ set adt [concat $adt $alldtags($t)]
+ }
+ set adt [lsort -unique $adt]
+ set alldtags($id) $adt
+ foreach t $adt {
+ set tagisdesc($id,$t) -1
+ set tagisdesc($t,$id) 1
+ }
+ if {[info exists anc_tags($id)]} {
+ set todo $anc_tags($id)
+ while {$todo ne {}} {
+ set do [lindex $todo 0]
+ set todo [lrange $todo 1 end]
+ if {[info exists tagisdesc($id,$do)]} continue
+ set tagisdesc($do,$id) -1
+ set tagisdesc($id,$do) 1
+ if {[info exists anc_tags($do)]} {
+ set todo [concat $todo $anc_tags($do)]
+ }
+ }
+ }
+
+ set lastold $desc_tags($id)
+ set lastnew [list $id]
+ set nup 0
+ set nch 0
+ set todo $allparents($id)
+ while {$todo ne {}} {
+ set do [lindex $todo 0]
+ set todo [lrange $todo 1 end]
+ if {![info exists desc_tags($do)]} continue
+ if {$desc_tags($do) ne $lastold} {
+ set lastold $desc_tags($do)
+ set lastnew [combine_dtags $lastold [list $id]]
+ incr nch
+ }
+ if {$lastold eq $lastnew} continue
+ set desc_tags($do) $lastnew
+ incr nup
+ if {![info exists idtags($do)]} {
+ set todo [concat $todo $allparents($do)]
+ }
+ }
+
+ if {![info exists anc_tags($id)]} return
+ set lastold $anc_tags($id)
+ set lastnew [list $id]
+ set nup 0
+ set nch 0
+ set todo $allchildren($id)
+ while {$todo ne {}} {
+ set do [lindex $todo 0]
+ set todo [lrange $todo 1 end]
+ if {![info exists anc_tags($do)]} continue
+ if {$anc_tags($do) ne $lastold} {
+ set lastold $anc_tags($do)
+ set lastnew [combine_atags $lastold [list $id]]
+ incr nch
+ }
+ if {$lastold eq $lastnew} continue
+ set anc_tags($do) $lastnew
+ incr nup
+ if {![info exists idtags($do)]} {
+ set todo [concat $todo $allchildren($do)]
+ }
+ }
+}
+
+# update the desc_heads array for a new head just added
+proc addedhead {hid head} {
+ global desc_heads allparents headids idheads
+
+ set headids($head) $hid
+ lappend idheads($hid) $head
+
+ set todo [list $hid]
+ while {$todo ne {}} {
+ set do [lindex $todo 0]
+ set todo [lrange $todo 1 end]
+ if {![info exists desc_heads($do)] ||
+ [lsearch -exact $desc_heads($do) $head] >= 0} continue
+ set oldheads $desc_heads($do)
+ lappend desc_heads($do) $head
+ set heads $desc_heads($do)
+ while {1} {
+ set p $allparents($do)
+ if {[llength $p] != 1 || ![info exists desc_heads($p)] ||
+ $desc_heads($p) ne $oldheads} break
+ set do $p
+ set desc_heads($do) $heads
+ }
+ set todo [concat $todo $p]
+ }
+}
+
+# update the desc_heads array for a head just removed
+proc removedhead {hid head} {
+ global desc_heads allparents headids idheads
+
+ unset headids($head)
+ if {$idheads($hid) eq $head} {
+ unset idheads($hid)
+ } else {
+ set i [lsearch -exact $idheads($hid) $head]
+ if {$i >= 0} {
+ set idheads($hid) [lreplace $idheads($hid) $i $i]
+ }
+ }
+
+ set todo [list $hid]
+ while {$todo ne {}} {
+ set do [lindex $todo 0]
+ set todo [lrange $todo 1 end]
+ if {![info exists desc_heads($do)]} continue
+ set i [lsearch -exact $desc_heads($do) $head]
+ if {$i < 0} continue
+ set oldheads $desc_heads($do)
+ set heads [lreplace $desc_heads($do) $i $i]
+ while {1} {
+ set desc_heads($do) $heads
+ set p $allparents($do)
+ if {[llength $p] != 1 || ![info exists desc_heads($p)] ||
+ $desc_heads($p) ne $oldheads} break
+ set do $p
+ }
+ set todo [concat $todo $p]
+ }
+}
+
+# update things for a head moved to a child of its previous location
+proc movedhead {id name} {
+ global headids idheads
+
+ set oldid $headids($name)
+ set headids($name) $id
+ if {$idheads($oldid) eq $name} {
+ unset idheads($oldid)
+ } else {
+ set i [lsearch -exact $idheads($oldid) $name]
+ if {$i >= 0} {
+ set idheads($oldid) [lreplace $idheads($oldid) $i $i]
+ }
+ }
+ lappend idheads($id) $name
+}
+
+proc changedrefs {} {
+ global desc_heads desc_tags anc_tags allcommits allids
+ global allchildren allparents idtags travindex
+
+ if {![info exists allcommits]} return
+ catch {unset desc_heads}
+ catch {unset desc_tags}
+ catch {unset anc_tags}
+ catch {unset alldtags}
+ catch {unset tagisdesc}
+ foreach id $allids {
+ forward_pass $id $allchildren($id)
+ }
+ if {$allcommits ne "reading"} {
+ set travindex [llength $allids]
+ if {$allcommits ne "traversing"} {
+ set allcommits "traversing"
+ after idle restartatags
+ }
+ }
+}
+
proc rereadrefs {} {
- global idtags idheads idotherrefs
+ global idtags idheads idotherrefs mainhead
set refids [concat [array names idtags] \
[array names idheads] [array names idotherrefs]]
@@ -5218,12 +5721,16 @@ proc rereadrefs {} {
set ref($id) [listrefs $id]
}
}
+ set oldmainhead $mainhead
readrefs
+ changedrefs
set refids [lsort -unique [concat $refids [array names idtags] \
[array names idheads] [array names idotherrefs]]]
foreach id $refids {
set v [listrefs $id]
- if {![info exists ref($id)] || $ref($id) != $v} {
+ if {![info exists ref($id)] || $ref($id) != $v ||
+ ($id eq $oldmainhead && $id ne $mainhead) ||
+ ($id eq $mainhead && $id ne $oldmainhead)} {
redrawtags $id
}
}
diff --git a/gitweb/README b/gitweb/README
index 27c6dac143..e02e90f042 100644
--- a/gitweb/README
+++ b/gitweb/README
@@ -1,4 +1,5 @@
GIT web Interface
+=================
The one working on:
http://www.kernel.org/git/
@@ -6,7 +7,8 @@ The one working on:
From the git version 1.4.0 gitweb is bundled with git.
-How to configure gitweb for your local system:
+How to configure gitweb for your local system
+---------------------------------------------
You can specify the following configuration variables when building GIT:
* GITWEB_SITENAME
@@ -24,11 +26,54 @@ You can specify the following configuration variables when building GIT:
* GITWEB_LOGO
Points to the location where you put git-logo.png on your web server.
* GITWEB_CONFIG
- This file will be loaded using 'require'. If the environment
+ This file will be loaded using 'require' and can be used to override any
+ of the options above as well as some other options - see the top of
+ 'gitweb.cgi' for their full list and description. If the environment
$GITWEB_CONFIG is set when gitweb.cgi is executed the file in the
environment variable will be loaded instead of the file
specified when gitweb.cgi was created.
+
+Runtime gitweb configuration
+----------------------------
+
+You can adjust gitweb behaviour using the file specified in `GITWEB_CONFIG`
+(defaults to 'gitweb_config.perl' in the same directory as the CGI).
+See the top of 'gitweb.cgi' for the list of variables and some description.
+The most notable thing that is not configurable at compile time are the
+optional features, stored in the '%features' variable. You can find further
+description on how to reconfigure the default features setting in your
+`GITWEB_CONFIG` or per-project in `project.git/config` inside 'gitweb.cgi'.
+
+
+Webserver configuration
+-----------------------
+
+If you want to have one URL for both gitweb and your http://
+repositories, you can configure apache like this:
+
+<VirtualHost www:80>
+ ServerName git.domain.org
+ DocumentRoot /pub/git
+ RewriteEngine on
+ RewriteRule ^/(.*\.git/(?!/?(info|objects|refs)).*)?$ /cgi-bin/gitweb.cgi%{REQUEST_URI} [L,PT]
+ SetEnv GITWEB_CONFIG /etc/gitweb.conf
+</VirtualHost>
+
+The above configuration expects your public repositories to live under
+/pub/git and will serve them as http://git.domain.org/dir-under-pub-git,
+both as cloneable GIT URL and as browseable gitweb interface.
+If you then start your git-daemon with --base-path=/pub/git --export-all
+then you can even use the git:// URL with exactly the same path.
+
+Setting the environment variable GITWEB_CONFIG will tell gitweb to use
+the named file (i.e. in this example /etc/gitweb.conf) as a
+configuration for gitweb. Perl variables defined in here will
+override the defaults given at the head of the gitweb.perl (or
+gitweb.cgi). Look at the comments in that file for information on
+which variables and what they mean.
+
+
Originally written by:
Kay Sievers <kay.sievers@vrfy.org>
diff --git a/gitweb/git-favicon.png b/gitweb/git-favicon.png
new file mode 100644
index 0000000000..de637c0608
--- /dev/null
+++ b/gitweb/git-favicon.png
Binary files differ
diff --git a/gitweb/gitweb.css b/gitweb/gitweb.css
index 47c1ade87e..974b47f19c 100644
--- a/gitweb/gitweb.css
+++ b/gitweb/gitweb.css
@@ -16,6 +16,18 @@ a:hover, a:visited, a:active {
color: #880000;
}
+span.cntrl {
+ border: dashed #aaaaaa;
+ border-width: 1px;
+ padding: 0px 2px 0px 2px;
+ margin: 0px 2px 0px 2px;
+}
+
+img.logo {
+ float: right;
+ border-width: 0px;
+}
+
div.page_header {
height: 25px;
padding: 8px;
@@ -42,6 +54,7 @@ div.page_nav a:visited {
div.page_path {
padding: 8px;
+ font-weight: bold;
border: solid #d9d8d1;
border-width: 0px 0px 1px;
}
@@ -115,11 +128,26 @@ div.list_head {
font-style: italic;
}
+div.author_date {
+ padding: 8px;
+ border: solid #d9d8d1;
+ border-width: 0px 0px 1px 0px;
+ font-style: italic;
+}
+
a.list {
text-decoration: none;
color: #000000;
}
+a.subject, a.name {
+ font-weight: bold;
+}
+
+table.tags a.subject {
+ font-weight: normal;
+}
+
a.list:hover {
text-decoration: underline;
color: #880000;
@@ -157,6 +185,12 @@ table.blame {
border-collapse: collapse;
}
+table.blame td {
+ padding: 0px 5px;
+ font-size: 12px;
+ vertical-align: top;
+}
+
th {
padding: 2px 5px;
font-size: 12px;
@@ -221,6 +255,10 @@ table.diff_tree span.file_status.mode_chnge {
color: #777777;
}
+table.diff_tree span.file_status.copied {
+ color: #70a070;
+}
+
/* age2: 60*60*24*2 <= age */
table.project_list td.age2, table.blame td.age2 {
font-style: italic;
@@ -260,25 +298,83 @@ td.mode {
font-family: monospace;
}
+/* styling of diffs (patchsets): commitdiff and blobdiff views */
+div.diff.header,
+div.diff.extended_header {
+ white-space: normal;
+}
+
+div.diff.header {
+ font-weight: bold;
+
+ background-color: #edece6;
+
+ margin-top: 4px;
+ padding: 4px 0px 2px 0px;
+ border: solid #d9d8d1;
+ border-width: 1px 0px 1px 0px;
+}
+
+div.diff.header a.path {
+ text-decoration: underline;
+}
+
+div.diff.extended_header,
+div.diff.extended_header a.path,
+div.diff.extended_header a.hash {
+ color: #777777;
+}
+
+div.diff.extended_header .info {
+ color: #b0b0b0;
+}
+
+div.diff.extended_header {
+ background-color: #f6f5ee;
+ padding: 2px 0px 2px 0px;
+}
+
+div.diff a.path,
+div.diff a.hash {
+ text-decoration: none;
+}
+
+div.diff a.path:hover,
+div.diff a.hash:hover {
+ text-decoration: underline;
+}
+
+div.diff.to_file a.path,
+div.diff.to_file {
+ color: #007000;
+}
+
div.diff.add {
color: #008800;
}
+div.diff.from_file a.path,
+div.diff.from_file {
+ color: #aa0000;
+}
+
div.diff.rem {
color: #cc0000;
}
div.diff.chunk_header {
color: #990099;
+
+ border: dotted #ffe0ff;
+ border-width: 1px 0px 0px 0px;
+ margin-top: 2px;
}
-div.diff_info {
- font-family: monospace;
- color: #000099;
- background-color: #edece6;
- font-style: italic;
+div.diff.incomplete {
+ color: #cccccc;
}
+
div.index_include {
border: solid #d9d8d1;
border-width: 0px 0px 1px;
@@ -286,6 +382,8 @@ div.index_include {
}
div.search {
+ font-size: 12px;
+ font-weight: normal;
margin: 4px 8px;
position: absolute;
top: 56px;
@@ -321,15 +419,30 @@ a.rss_logo:hover {
background-color: #ee5500;
}
-span.tag {
+span.refs span {
padding: 0px 4px;
font-size: 10px;
font-weight: normal;
- background-color: #ffffaa;
border: 1px solid;
+ background-color: #ffaaff;
+ border-color: #ffccff #ff00ee #ff00ee #ffccff;
+}
+
+span.refs span.ref {
+ background-color: #aaaaff;
+ border-color: #ccccff #0033cc #0033cc #ccccff;
+}
+
+span.refs span.tag {
+ background-color: #ffffaa;
border-color: #ffffcc #ffee00 #ffee00 #ffffcc;
}
+span.refs span.head {
+ background-color: #aaffaa;
+ border-color: #ccffcc #00cc33 #00cc33 #ccffcc;
+}
+
span.atnight {
color: #cc0000;
}
diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl
index 626fcc9201..758759576c 100755
--- a/gitweb/gitweb.perl
+++ b/gitweb/gitweb.perl
@@ -15,6 +15,7 @@ use CGI::Carp qw(fatalsToBrowser);
use Encode;
use Fcntl ':mode';
use File::Find qw();
+use File::Basename qw(basename);
binmode STDOUT, ':utf8';
our $cgi = new CGI;
@@ -30,27 +31,53 @@ our $GIT = "++GIT_BINDIR++/git";
#our $projectroot = "/pub/scm";
our $projectroot = "++GITWEB_PROJECTROOT++";
-# location for temporary files needed for diffs
-our $git_temp = "/tmp/gitweb";
-
# target of the home link on top of all pages
-our $home_link = $my_uri;
+our $home_link = $my_uri || "/";
+
+# string of the home link on top of all pages
+our $home_link_str = "++GITWEB_HOME_LINK_STR++";
# name of your site or organization to appear in page titles
# replace this with something more descriptive for clearer bookmarks
-our $site_name = "++GITWEB_SITENAME++" || $ENV{'SERVER_NAME'} || "Untitled";
+our $site_name = "++GITWEB_SITENAME++"
+ || ($ENV{'SERVER_NAME'} || "Untitled") . " Git";
+# filename of html text to include at top of each page
+our $site_header = "++GITWEB_SITE_HEADER++";
# html text to include at home page
our $home_text = "++GITWEB_HOMETEXT++";
-
-# URI of default stylesheet
-our $stylesheet = "++GITWEB_CSS++";
-# URI of GIT logo
+# filename of html text to include at bottom of each page
+our $site_footer = "++GITWEB_SITE_FOOTER++";
+
+# URI of stylesheets
+our @stylesheets = ("++GITWEB_CSS++");
+# URI of a single stylesheet, which can be overridden in GITWEB_CONFIG.
+our $stylesheet = undef;
+# URI of GIT logo (72x27 size)
our $logo = "++GITWEB_LOGO++";
+# URI of GIT favicon, assumed to be image/png type
+our $favicon = "++GITWEB_FAVICON++";
+
+# URI and label (title) of GIT logo link
+#our $logo_url = "http://www.kernel.org/pub/software/scm/git/docs/";
+#our $logo_label = "git documentation";
+our $logo_url = "http://git.or.cz/";
+our $logo_label = "git homepage";
# source of projects list
our $projects_list = "++GITWEB_LIST++";
+# show repository only if this file exists
+# (only effective if this variable evaluates to true)
+our $export_ok = "++GITWEB_EXPORT_OK++";
+
+# only allow viewing of repositories also shown on the overview page
+our $strict_export = "++GITWEB_STRICT_EXPORT++";
+
+# list of git base URLs used for URL to where fetch project from,
+# i.e. full URL is "$git_base_url/$project"
+our @git_base_url_list = grep { $_ ne '' } ("++GITWEB_BASE_URL++");
+
# default blob_plain mimetype and default charset for text/plain blob
our $default_blob_plain_mimetype = 'text/plain';
our $default_text_plain_charset = undef;
@@ -59,16 +86,192 @@ our $default_text_plain_charset = undef;
# (relative to the current git repository)
our $mimetypes_file = undef;
+# You define site-wide feature defaults here; override them with
+# $GITWEB_CONFIG as necessary.
+our %feature = (
+ # feature => {
+ # 'sub' => feature-sub (subroutine),
+ # 'override' => allow-override (boolean),
+ # 'default' => [ default options...] (array reference)}
+ #
+ # if feature is overridable (it means that allow-override has true value,
+ # then feature-sub will be called with default options as parameters;
+ # return value of feature-sub indicates if to enable specified feature
+ #
+ # use gitweb_check_feature(<feature>) to check if <feature> is enabled
+
+ # Enable the 'blame' blob view, showing the last commit that modified
+ # each line in the file. This can be very CPU-intensive.
+
+ # To enable system wide have in $GITWEB_CONFIG
+ # $feature{'blame'}{'default'} = [1];
+ # To have project specific config enable override in $GITWEB_CONFIG
+ # $feature{'blame'}{'override'} = 1;
+ # and in project config gitweb.blame = 0|1;
+ 'blame' => {
+ 'sub' => \&feature_blame,
+ 'override' => 0,
+ 'default' => [0]},
+
+ # Enable the 'snapshot' link, providing a compressed tarball of any
+ # tree. This can potentially generate high traffic if you have large
+ # project.
+
+ # To disable system wide have in $GITWEB_CONFIG
+ # $feature{'snapshot'}{'default'} = [undef];
+ # To have project specific config enable override in $GITWEB_CONFIG
+ # $feature{'blame'}{'override'} = 1;
+ # and in project config gitweb.snapshot = none|gzip|bzip2;
+ 'snapshot' => {
+ 'sub' => \&feature_snapshot,
+ 'override' => 0,
+ # => [content-encoding, suffix, program]
+ 'default' => ['x-gzip', 'gz', 'gzip']},
+
+ # Enable the pickaxe search, which will list the commits that modified
+ # a given string in a file. This can be practical and quite faster
+ # alternative to 'blame', but still potentially CPU-intensive.
+
+ # To enable system wide have in $GITWEB_CONFIG
+ # $feature{'pickaxe'}{'default'} = [1];
+ # To have project specific config enable override in $GITWEB_CONFIG
+ # $feature{'pickaxe'}{'override'} = 1;
+ # and in project config gitweb.pickaxe = 0|1;
+ 'pickaxe' => {
+ 'sub' => \&feature_pickaxe,
+ 'override' => 0,
+ 'default' => [1]},
+
+ # Make gitweb use an alternative format of the URLs which can be
+ # more readable and natural-looking: project name is embedded
+ # directly in the path and the query string contains other
+ # auxiliary information. All gitweb installations recognize
+ # URL in either format; this configures in which formats gitweb
+ # generates links.
+
+ # To enable system wide have in $GITWEB_CONFIG
+ # $feature{'pathinfo'}{'default'} = [1];
+ # Project specific override is not supported.
+
+ # Note that you will need to change the default location of CSS,
+ # favicon, logo and possibly other files to an absolute URL. Also,
+ # if gitweb.cgi serves as your indexfile, you will need to force
+ # $my_uri to contain the script name in your $GITWEB_CONFIG.
+ 'pathinfo' => {
+ 'override' => 0,
+ 'default' => [0]},
+
+ # Make gitweb consider projects in project root subdirectories
+ # to be forks of existing projects. Given project $projname.git,
+ # projects matching $projname/*.git will not be shown in the main
+ # projects list, instead a '+' mark will be added to $projname
+ # there and a 'forks' view will be enabled for the project, listing
+ # all the forks. This feature is supported only if project list
+ # is taken from a directory, not file.
+
+ # To enable system wide have in $GITWEB_CONFIG
+ # $feature{'forks'}{'default'} = [1];
+ # Project specific override is not supported.
+ 'forks' => {
+ 'override' => 0,
+ 'default' => [0]},
+);
+
+sub gitweb_check_feature {
+ my ($name) = @_;
+ return unless exists $feature{$name};
+ my ($sub, $override, @defaults) = (
+ $feature{$name}{'sub'},
+ $feature{$name}{'override'},
+ @{$feature{$name}{'default'}});
+ if (!$override) { return @defaults; }
+ if (!defined $sub) {
+ warn "feature $name is not overrideable";
+ return @defaults;
+ }
+ return $sub->(@defaults);
+}
+
+sub feature_blame {
+ my ($val) = git_get_project_config('blame', '--bool');
+
+ if ($val eq 'true') {
+ return 1;
+ } elsif ($val eq 'false') {
+ return 0;
+ }
+
+ return $_[0];
+}
+
+sub feature_snapshot {
+ my ($ctype, $suffix, $command) = @_;
+
+ my ($val) = git_get_project_config('snapshot');
+
+ if ($val eq 'gzip') {
+ return ('x-gzip', 'gz', 'gzip');
+ } elsif ($val eq 'bzip2') {
+ return ('x-bzip2', 'bz2', 'bzip2');
+ } elsif ($val eq 'none') {
+ return ();
+ }
+
+ return ($ctype, $suffix, $command);
+}
+
+sub gitweb_have_snapshot {
+ my ($ctype, $suffix, $command) = gitweb_check_feature('snapshot');
+ my $have_snapshot = (defined $ctype && defined $suffix);
+
+ return $have_snapshot;
+}
+
+sub feature_pickaxe {
+ my ($val) = git_get_project_config('pickaxe', '--bool');
+
+ if ($val eq 'true') {
+ return (1);
+ } elsif ($val eq 'false') {
+ return (0);
+ }
+
+ return ($_[0]);
+}
+
+# checking HEAD file with -e is fragile if the repository was
+# initialized long time ago (i.e. symlink HEAD) and was pack-ref'ed
+# and then pruned.
+sub check_head_link {
+ my ($dir) = @_;
+ my $headfile = "$dir/HEAD";
+ return ((-e $headfile) ||
+ (-l $headfile && readlink($headfile) =~ /^refs\/heads\//));
+}
+
+sub check_export_ok {
+ my ($dir) = @_;
+ return (check_head_link($dir) &&
+ (!$export_ok || -e "$dir/$export_ok"));
+}
+
+# rename detection options for git-diff and git-diff-tree
+# - default is '-M', with the cost proportional to
+# (number of removed files) * (number of new files).
+# - more costly is '-C' (or '-C', '-M'), with the cost proportional to
+# (number of changed files + number of removed files) * (number of new files)
+# - even more costly is '-C', '--find-copies-harder' with cost
+# (number of files in the original tree) * (number of new files)
+# - one might want to include '-B' option, e.g. '-B', '-M'
+our @diff_opts = ('-M'); # taken from git_commit
+
our $GITWEB_CONFIG = $ENV{'GITWEB_CONFIG'} || "++GITWEB_CONFIG++";
-require $GITWEB_CONFIG if -e $GITWEB_CONFIG;
+do $GITWEB_CONFIG if -e $GITWEB_CONFIG;
# version of the core git binary
our $git_version = qx($GIT --version) =~ m/git version (.*)$/ ? $1 : "unknown";
$projects_list ||= $projectroot;
-if (! -d $git_temp) {
- mkdir($git_temp, 0700) || die_error(undef, "Couldn't mkdir $git_temp");
-}
# ======================================================================
# input validation and dispatch
@@ -77,65 +280,68 @@ if (defined $action) {
if ($action =~ m/[^0-9a-zA-Z\.\-_]/) {
die_error(undef, "Invalid action parameter");
}
- # action which does not check rest of parameters
- if ($action eq "opml") {
- git_opml();
- exit;
- }
}
-our $project = ($cgi->param('p') || $ENV{'PATH_INFO'});
+# parameters which are pathnames
+our $project = $cgi->param('p');
if (defined $project) {
- $project =~ s|^/||;
- $project =~ s|/$||;
-}
-if (defined $project && $project) {
- if (!validate_input($project)) {
- die_error(undef, "Invalid project parameter");
- }
- if (!(-d "$projectroot/$project")) {
- die_error(undef, "No such directory");
- }
- if (!(-e "$projectroot/$project/HEAD")) {
+ if (!validate_pathname($project) ||
+ !(-d "$projectroot/$project") ||
+ !check_head_link("$projectroot/$project") ||
+ ($export_ok && !(-e "$projectroot/$project/$export_ok")) ||
+ ($strict_export && !project_in_list($project))) {
+ undef $project;
die_error(undef, "No such project");
}
- $ENV{'GIT_DIR'} = "$projectroot/$project";
-} else {
- git_project_list();
- exit;
}
our $file_name = $cgi->param('f');
if (defined $file_name) {
- if (!validate_input($file_name)) {
+ if (!validate_pathname($file_name)) {
die_error(undef, "Invalid file parameter");
}
}
+our $file_parent = $cgi->param('fp');
+if (defined $file_parent) {
+ if (!validate_pathname($file_parent)) {
+ die_error(undef, "Invalid file parent parameter");
+ }
+}
+
+# parameters which are refnames
our $hash = $cgi->param('h');
if (defined $hash) {
- if (!validate_input($hash)) {
+ if (!validate_refname($hash)) {
die_error(undef, "Invalid hash parameter");
}
}
our $hash_parent = $cgi->param('hp');
if (defined $hash_parent) {
- if (!validate_input($hash_parent)) {
+ if (!validate_refname($hash_parent)) {
die_error(undef, "Invalid hash parent parameter");
}
}
our $hash_base = $cgi->param('hb');
if (defined $hash_base) {
- if (!validate_input($hash_base)) {
+ if (!validate_refname($hash_base)) {
die_error(undef, "Invalid hash base parameter");
}
}
+our $hash_parent_base = $cgi->param('hpb');
+if (defined $hash_parent_base) {
+ if (!validate_refname($hash_parent_base)) {
+ die_error(undef, "Invalid hash parent base parameter");
+ }
+}
+
+# other parameters
our $page = $cgi->param('pg');
if (defined $page) {
- if ($page =~ m/[^0-9]$/) {
+ if ($page =~ m/[^0-9]/) {
die_error(undef, "Invalid page parameter");
}
}
@@ -148,6 +354,62 @@ if (defined $searchtext) {
$searchtext = quotemeta $searchtext;
}
+our $searchtype = $cgi->param('st');
+if (defined $searchtype) {
+ if ($searchtype =~ m/[^a-z]/) {
+ die_error(undef, "Invalid searchtype parameter");
+ }
+}
+
+# now read PATH_INFO and use it as alternative to parameters
+sub evaluate_path_info {
+ return if defined $project;
+ my $path_info = $ENV{"PATH_INFO"};
+ return if !$path_info;
+ $path_info =~ s,^/+,,;
+ return if !$path_info;
+ # find which part of PATH_INFO is project
+ $project = $path_info;
+ $project =~ s,/+$,,;
+ while ($project && !check_head_link("$projectroot/$project")) {
+ $project =~ s,/*[^/]*$,,;
+ }
+ # validate project
+ $project = validate_pathname($project);
+ if (!$project ||
+ ($export_ok && !-e "$projectroot/$project/$export_ok") ||
+ ($strict_export && !project_in_list($project))) {
+ undef $project;
+ return;
+ }
+ # do not change any parameters if an action is given using the query string
+ return if $action;
+ $path_info =~ s,^$project/*,,;
+ my ($refname, $pathname) = split(/:/, $path_info, 2);
+ if (defined $pathname) {
+ # we got "project.git/branch:filename" or "project.git/branch:dir/"
+ # we could use git_get_type(branch:pathname), but it needs $git_dir
+ $pathname =~ s,^/+,,;
+ if (!$pathname || substr($pathname, -1) eq "/") {
+ $action ||= "tree";
+ $pathname =~ s,/$,,;
+ } else {
+ $action ||= "blob_plain";
+ }
+ $hash_base ||= validate_refname($refname);
+ $file_name ||= validate_pathname($pathname);
+ } elsif (defined $refname) {
+ # we got "project.git/branch"
+ $action ||= "shortlog";
+ $hash ||= validate_refname($refname);
+ }
+}
+evaluate_path_info();
+
+# path to the current git repository
+our $git_dir;
+$git_dir = "$projectroot/$project" if $project;
+
# dispatch
my %actions = (
"blame" => \&git_blame2,
@@ -158,47 +420,149 @@ my %actions = (
"commitdiff" => \&git_commitdiff,
"commitdiff_plain" => \&git_commitdiff_plain,
"commit" => \&git_commit,
+ "forks" => \&git_forks,
"heads" => \&git_heads,
"history" => \&git_history,
"log" => \&git_log,
"rss" => \&git_rss,
"search" => \&git_search,
+ "search_help" => \&git_search_help,
"shortlog" => \&git_shortlog,
"summary" => \&git_summary,
"tag" => \&git_tag,
"tags" => \&git_tags,
"tree" => \&git_tree,
+ "snapshot" => \&git_snapshot,
+ # those below don't need $project
+ "opml" => \&git_opml,
+ "project_list" => \&git_project_list,
+ "project_index" => \&git_project_index,
);
-$action = 'summary' if (!defined($action));
+if (defined $project) {
+ $action ||= 'summary';
+} else {
+ $action ||= 'project_list';
+}
if (!defined($actions{$action})) {
die_error(undef, "Unknown action");
}
+if ($action !~ m/^(opml|project_list|project_index)$/ &&
+ !$project) {
+ die_error(undef, "Project needed");
+}
$actions{$action}->();
exit;
## ======================================================================
+## action links
+
+sub href(%) {
+ my %params = @_;
+ my $href = $my_uri;
+
+ # XXX: Warning: If you touch this, check the search form for updating,
+ # too.
+
+ my @mapping = (
+ project => "p",
+ action => "a",
+ file_name => "f",
+ file_parent => "fp",
+ hash => "h",
+ hash_parent => "hp",
+ hash_base => "hb",
+ hash_parent_base => "hpb",
+ page => "pg",
+ order => "o",
+ searchtext => "s",
+ searchtype => "st",
+ );
+ my %mapping = @mapping;
+
+ $params{'project'} = $project unless exists $params{'project'};
+
+ my ($use_pathinfo) = gitweb_check_feature('pathinfo');
+ if ($use_pathinfo) {
+ # use PATH_INFO for project name
+ $href .= "/$params{'project'}" if defined $params{'project'};
+ delete $params{'project'};
+
+ # Summary just uses the project path URL
+ if (defined $params{'action'} && $params{'action'} eq 'summary') {
+ delete $params{'action'};
+ }
+ }
+
+ # now encode the parameters explicitly
+ my @result = ();
+ for (my $i = 0; $i < @mapping; $i += 2) {
+ my ($name, $symbol) = ($mapping[$i], $mapping[$i+1]);
+ if (defined $params{$name}) {
+ push @result, $symbol . "=" . esc_param($params{$name});
+ }
+ }
+ $href .= "?" . join(';', @result) if scalar @result;
+
+ return $href;
+}
+
+
+## ======================================================================
## validation, quoting/unquoting and escaping
-sub validate_input {
- my $input = shift;
+sub validate_pathname {
+ my $input = shift || return undef;
- if ($input =~ m/^[0-9a-fA-F]{40}$/) {
- return $input;
+ # no '.' or '..' as elements of path, i.e. no '.' nor '..'
+ # at the beginning, at the end, and between slashes.
+ # also this catches doubled slashes
+ if ($input =~ m!(^|/)(|\.|\.\.)(/|$)!) {
+ return undef;
}
- if ($input =~ m/(^|\/)(|\.|\.\.)($|\/)/) {
+ # no null characters
+ if ($input =~ m!\0!) {
return undef;
}
- if ($input =~ m/[^a-zA-Z0-9_\x80-\xff\ \t\.\/\-\+\#\~\%]/) {
+ return $input;
+}
+
+sub validate_refname {
+ my $input = shift || return undef;
+
+ # textual hashes are O.K.
+ if ($input =~ m/^[0-9a-fA-F]{40}$/) {
+ return $input;
+ }
+ # it must be correct pathname
+ $input = validate_pathname($input)
+ or return undef;
+ # restrictions on ref name according to git-check-ref-format
+ if ($input =~ m!(/\.|\.\.|[\000-\040\177 ~^:?*\[]|/$)!) {
return undef;
}
return $input;
}
+# very thin wrapper for decode("utf8", $str, Encode::FB_DEFAULT);
+sub to_utf8 {
+ my $str = shift;
+ return decode("utf8", $str, Encode::FB_DEFAULT);
+}
+
# quote unsafe chars, but keep the slash, even when it's not
# correct, but quoted slashes look too horrible in bookmarks
sub esc_param {
my $str = shift;
+ $str =~ s/([^A-Za-z0-9\-_.~()\/:@])/sprintf("%%%02X", ord($1))/eg;
+ $str =~ s/\+/%2B/g;
+ $str =~ s/ /\+/g;
+ return $str;
+}
+
+# quote unsafe chars in whole URL, so some charactrs cannot be quoted
+sub esc_url {
+ my $str = shift;
$str =~ s/([^A-Za-z0-9\-_.~();\/;?:@&=])/sprintf("%%%02X", ord($1))/eg;
$str =~ s/\+/%2B/g;
$str =~ s/ /\+/g;
@@ -206,20 +570,87 @@ sub esc_param {
}
# replace invalid utf8 character with SUBSTITUTION sequence
-sub esc_html {
+sub esc_html ($;%) {
my $str = shift;
- $str = decode("utf8", $str, Encode::FB_DEFAULT);
+ my %opts = @_;
+
+ $str = to_utf8($str);
$str = escapeHTML($str);
- $str =~ s/\014/^L/g; # escape FORM FEED (FF) character (e.g. in COPYING file)
+ if ($opts{'-nbsp'}) {
+ $str =~ s/ /&nbsp;/g;
+ }
+ $str =~ s|([[:cntrl:]])|(($1 ne "\t") ? quot_cec($1) : $1)|eg;
+ return $str;
+}
+
+# Make control characterss "printable".
+sub quot_cec {
+ my $cntrl = shift;
+ my %es = ( # character escape codes, aka escape sequences
+ "\t" => '\t', # tab (HT)
+ "\n" => '\n', # line feed (LF)
+ "\r" => '\r', # carrige return (CR)
+ "\f" => '\f', # form feed (FF)
+ "\b" => '\b', # backspace (BS)
+ "\a" => '\a', # alarm (bell) (BEL)
+ "\e" => '\e', # escape (ESC)
+ "\013" => '\v', # vertical tab (VT)
+ "\000" => '\0', # nul character (NUL)
+ );
+ my $chr = ( (exists $es{$cntrl})
+ ? $es{$cntrl}
+ : sprintf('\%03o', ord($cntrl)) );
+ return "<span class=\"cntrl\">$chr</span>";
+}
+
+# Alternatively use unicode control pictures codepoints.
+sub quot_upr {
+ my $cntrl = shift;
+ my $chr = sprintf('&#%04d;', 0x2400+ord($cntrl));
+ return "<span class=\"cntrl\">$chr</span>";
+}
+
+# quote control characters and escape filename to HTML
+sub esc_path {
+ my $str = shift;
+
+ $str = esc_html($str);
+ $str =~ s|([[:cntrl:]])|quot_cec($1)|eg;
return $str;
}
# git may return quoted and escaped filenames
sub unquote {
my $str = shift;
+
+ sub unq {
+ my $seq = shift;
+ my %es = ( # character escape codes, aka escape sequences
+ 't' => "\t", # tab (HT, TAB)
+ 'n' => "\n", # newline (NL)
+ 'r' => "\r", # return (CR)
+ 'f' => "\f", # form feed (FF)
+ 'b' => "\b", # backspace (BS)
+ 'a' => "\a", # alarm (bell) (BEL)
+ 'e' => "\e", # escape (ESC)
+ 'v' => "\013", # vertical tab (VT)
+ );
+
+ if ($seq =~ m/^[0-7]{1,3}$/) {
+ # octal char sequence
+ return chr(oct($seq));
+ } elsif (exists $es{$seq}) {
+ # C escape sequence, aka character escape code
+ return $es{$seq}
+ }
+ # quoted ordinary character
+ return $seq;
+ }
+
if ($str =~ m/^"(.*)"$/) {
+ # needs unquoting
$str = $1;
- $str =~ s/\\([0-7]{1,3})/chr(oct($1))/eg;
+ $str =~ s/\\([^0-7]|[0-7]{1,3})/unq($1)/eg;
}
return $str;
}
@@ -238,6 +669,12 @@ sub untabify {
return $line;
}
+sub project_in_list {
+ my $project = shift;
+ my @list = git_get_projects_list();
+ return @list && scalar(grep { $_->{'path'} eq $project } @list);
+}
+
## ----------------------------------------------------------------------
## HTML aware string manipulation
@@ -328,7 +765,13 @@ sub mode_str {
# convert file mode in octal to file type string
sub file_type {
- my $mode = oct shift;
+ my $mode = shift;
+
+ if ($mode !~ m/^[0-7]+$/) {
+ return $mode;
+ } else {
+ $mode = oct $mode;
+ }
if (S_ISDIR($mode & S_IFMT)) {
return "directory";
@@ -341,20 +784,47 @@ sub file_type {
}
}
+# convert file mode in octal to file type description string
+sub file_type_long {
+ my $mode = shift;
+
+ if ($mode !~ m/^[0-7]+$/) {
+ return $mode;
+ } else {
+ $mode = oct $mode;
+ }
+
+ if (S_ISDIR($mode & S_IFMT)) {
+ return "directory";
+ } elsif (S_ISLNK($mode)) {
+ return "symlink";
+ } elsif (S_ISREG($mode)) {
+ if ($mode & S_IXUSR) {
+ return "executable";
+ } else {
+ return "file";
+ };
+ } else {
+ return "unknown";
+ }
+}
+
+
## ----------------------------------------------------------------------
## functions returning short HTML fragments, or transforming HTML fragments
## which don't beling to other sections
-# format line of commit message or tag comment
+# format line of commit message.
sub format_log_line_html {
my $line = shift;
- $line = esc_html($line);
- $line =~ s/ /&nbsp;/g;
+ $line = esc_html($line, -nbsp=>1);
if ($line =~ m/([0-9a-fA-F]{40})/) {
my $hash_text = $1;
if (git_get_type($hash_text) eq "commit") {
- my $link = $cgi->a({-class => "text", -href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash_text")}, $hash_text);
+ my $link =
+ $cgi->a({-href => href(action=>"commit", hash=>$hash_text),
+ -class => "text"}, $hash_text);
$line =~ s/$hash_text/$link/;
}
}
@@ -362,34 +832,96 @@ sub format_log_line_html {
}
# format marker of refs pointing to given object
-sub git_get_referencing {
+sub format_ref_marker {
my ($refs, $id) = @_;
+ my $markers = '';
if (defined $refs->{$id}) {
- return ' <span class="tag">' . esc_html($refs->{$id}) . '</span>';
+ foreach my $ref (@{$refs->{$id}}) {
+ my ($type, $name) = qw();
+ # e.g. tags/v2.6.11 or heads/next
+ if ($ref =~ m!^(.*?)s?/(.*)$!) {
+ $type = $1;
+ $name = $2;
+ } else {
+ $type = "ref";
+ $name = $ref;
+ }
+
+ $markers .= " <span class=\"$type\">" . esc_html($name) . "</span>";
+ }
+ }
+
+ if ($markers) {
+ return ' <span class="refs">'. $markers . '</span>';
} else {
return "";
}
}
+# format, perhaps shortened and with markers, title line
+sub format_subject_html {
+ my ($long, $short, $href, $extra) = @_;
+ $extra = '' unless defined($extra);
+
+ if (length($short) < length($long)) {
+ return $cgi->a({-href => $href, -class => "list subject",
+ -title => to_utf8($long)},
+ esc_html($short) . $extra);
+ } else {
+ return $cgi->a({-href => $href, -class => "list subject"},
+ esc_html($long) . $extra);
+ }
+}
+
+sub format_diff_line {
+ my $line = shift;
+ my $char = substr($line, 0, 1);
+ my $diff_class = "";
+
+ chomp $line;
+
+ if ($char eq '+') {
+ $diff_class = " add";
+ } elsif ($char eq "-") {
+ $diff_class = " rem";
+ } elsif ($char eq "@") {
+ $diff_class = " chunk_header";
+ } elsif ($char eq "\\") {
+ $diff_class = " incomplete";
+ }
+ $line = untabify($line);
+ return "<div class=\"diff$diff_class\">" . esc_html($line, -nbsp=>1) . "</div>\n";
+}
+
## ----------------------------------------------------------------------
## git utility subroutines, invoking git commands
+# returns path to the core git executable and the --git-dir parameter as list
+sub git_cmd {
+ return $GIT, '--git-dir='.$git_dir;
+}
+
+# returns path to the core git executable and the --git-dir parameter as string
+sub git_cmd_str {
+ return join(' ', git_cmd());
+}
+
# get HEAD ref of given project as hash
-sub git_read_head {
+sub git_get_head_hash {
my $project = shift;
- my $oENV = $ENV{'GIT_DIR'};
+ my $o_git_dir = $git_dir;
my $retval = undef;
- $ENV{'GIT_DIR'} = "$projectroot/$project";
- if (open my $fd, "-|", $GIT, "rev-parse", "--verify", "HEAD") {
+ $git_dir = "$projectroot/$project";
+ if (open my $fd, "-|", git_cmd(), "rev-parse", "--verify", "HEAD") {
my $head = <$fd>;
close $fd;
if (defined $head && $head =~ /^([0-9a-fA-F]{40})$/) {
$retval = $1;
}
}
- if (defined $oENV) {
- $ENV{'GIT_DIR'} = $oENV;
+ if (defined $o_git_dir) {
+ $git_dir = $o_git_dir;
}
return $retval;
}
@@ -398,7 +930,7 @@ sub git_read_head {
sub git_get_type {
my $hash = shift;
- open my $fd, "-|", $GIT, "cat-file", '-t', $hash or return;
+ open my $fd, "-|", git_cmd(), "cat-file", '-t', $hash or return;
my $type = <$fd>;
close $fd or return;
chomp $type;
@@ -406,89 +938,108 @@ sub git_get_type {
}
sub git_get_project_config {
- my $key = shift;
+ my ($key, $type) = @_;
return unless ($key);
$key =~ s/^gitweb\.//;
return if ($key =~ m/\W/);
- my $val = qx($GIT repo-config --get gitweb.$key);
+ my @x = (git_cmd(), 'repo-config');
+ if (defined $type) { push @x, $type; }
+ push @x, "--get";
+ push @x, "gitweb.$key";
+ my $val = qx(@x);
+ chomp $val;
return ($val);
}
-sub git_get_project_config_bool {
- my $val = git_get_project_config (@_);
- if ($val and $val =~ m/true|yes|on/) {
- return (1);
- }
- return; # implicit false
-}
-
# get hash of given path at given ref
sub git_get_hash_by_path {
my $base = shift;
my $path = shift || return undef;
+ my $type = shift;
- my $tree = $base;
+ $path =~ s,/+$,,;
- open my $fd, "-|", $GIT, "ls-tree", $base, "--", $path
+ open my $fd, "-|", git_cmd(), "ls-tree", $base, "--", $path
or die_error(undef, "Open git-ls-tree failed");
my $line = <$fd>;
close $fd or return undef;
#'100644 blob 0fa3f3a66fb6a137f6ec2c19351ed4d807070ffa panic.c'
- $line =~ m/^([0-9]+) (.+) ([0-9a-fA-F]{40})\t(.+)$/;
+ $line =~ m/^([0-9]+) (.+) ([0-9a-fA-F]{40})\t/;
+ if (defined $type && $type ne $2) {
+ # type doesn't match
+ return undef;
+ }
return $3;
}
## ......................................................................
## git utility functions, directly accessing git repository
-# assumes that PATH is not symref
-sub git_read_hash {
+sub git_get_project_description {
my $path = shift;
- open my $fd, "$projectroot/$path" or return undef;
- my $head = <$fd>;
+ open my $fd, "$projectroot/$path/description" or return undef;
+ my $descr = <$fd>;
close $fd;
- chomp $head;
- if ($head =~ m/^[0-9a-fA-F]{40}$/) {
- return $head;
- }
+ chomp $descr;
+ return $descr;
}
-sub git_read_description {
+sub git_get_project_url_list {
my $path = shift;
- open my $fd, "$projectroot/$path/description" or return undef;
- my $descr = <$fd>;
+ open my $fd, "$projectroot/$path/cloneurl" or return;
+ my @git_project_url_list = map { chomp; $_ } <$fd>;
close $fd;
- chomp $descr;
- return $descr;
+
+ return wantarray ? @git_project_url_list : \@git_project_url_list;
}
-sub git_read_projects {
+sub git_get_projects_list {
+ my ($filter) = @_;
my @list;
+ $filter ||= '';
+ $filter =~ s/\.git$//;
+
if (-d $projects_list) {
# search in directory
- my $dir = $projects_list;
- opendir my ($dh), $dir or return undef;
- while (my $dir = readdir($dh)) {
- if (-e "$projectroot/$dir/HEAD") {
- my $pr = {
- path => $dir,
- };
- push @list, $pr
- }
- }
- closedir($dh);
+ my $dir = $projects_list . ($filter ? "/$filter" : '');
+ # remove the trailing "/"
+ $dir =~ s!/+$!!;
+ my $pfxlen = length("$dir");
+
+ my ($check_forks) = gitweb_check_feature('forks');
+
+ File::Find::find({
+ follow_fast => 1, # follow symbolic links
+ dangling_symlinks => 0, # ignore dangling symlinks, silently
+ wanted => sub {
+ # skip project-list toplevel, if we get it.
+ return if (m!^[/.]$!);
+ # only directories can be git repositories
+ return unless (-d $_);
+
+ my $subdir = substr($File::Find::name, $pfxlen + 1);
+ # we check related file in $projectroot
+ if ($check_forks and $subdir =~ m#/.#) {
+ $File::Find::prune = 1;
+ } elsif (check_export_ok("$projectroot/$filter/$subdir")) {
+ push @list, { path => ($filter ? "$filter/" : '') . $subdir };
+ $File::Find::prune = 1;
+ }
+ },
+ }, "$dir");
+
} elsif (-f $projects_list) {
# read from file(url-encoded):
# 'git%2Fgit.git Linus+Torvalds'
# 'libs%2Fklibc%2Fklibc.git H.+Peter+Anvin'
# 'linux%2Fhotplug%2Fudev.git Greg+Kroah-Hartman'
- open my ($fd), $projects_list or return undef;
+ open my ($fd), $projects_list or return;
while (my $line = <$fd>) {
chomp $line;
my ($path, $owner) = split ' ', $line;
@@ -497,10 +1048,21 @@ sub git_read_projects {
if (!defined $path) {
next;
}
- if (-e "$projectroot/$path/HEAD") {
+ if ($filter ne '') {
+ # looking for forks;
+ my $pfx = substr($path, 0, length($filter));
+ if ($pfx ne $filter) {
+ next;
+ }
+ my $sfx = substr($path, length($filter));
+ if ($sfx !~ /^\/.*\.git$/) {
+ next;
+ }
+ }
+ if (check_export_ok("$projectroot/$path")) {
my $pr = {
path => $path,
- owner => decode("utf8", $owner, Encode::FB_DEFAULT),
+ owner => to_utf8($owner),
};
push @list, $pr
}
@@ -511,21 +1073,70 @@ sub git_read_projects {
return @list;
}
-sub read_info_ref {
+sub git_get_project_owner {
+ my $project = shift;
+ my $owner;
+
+ return undef unless $project;
+
+ # read from file (url-encoded):
+ # 'git%2Fgit.git Linus+Torvalds'
+ # 'libs%2Fklibc%2Fklibc.git H.+Peter+Anvin'
+ # 'linux%2Fhotplug%2Fudev.git Greg+Kroah-Hartman'
+ if (-f $projects_list) {
+ open (my $fd , $projects_list);
+ while (my $line = <$fd>) {
+ chomp $line;
+ my ($pr, $ow) = split ' ', $line;
+ $pr = unescape($pr);
+ $ow = unescape($ow);
+ if ($pr eq $project) {
+ $owner = to_utf8($ow);
+ last;
+ }
+ }
+ close $fd;
+ }
+ if (!defined $owner) {
+ $owner = get_file_owner("$projectroot/$project");
+ }
+
+ return $owner;
+}
+
+sub git_get_last_activity {
+ my ($path) = @_;
+ my $fd;
+
+ $git_dir = "$projectroot/$path";
+ open($fd, "-|", git_cmd(), 'for-each-ref',
+ '--format=%(refname) %(committer)',
+ '--sort=-committerdate',
+ 'refs/heads') or return;
+ my $most_recent = <$fd>;
+ close $fd or return;
+ if ($most_recent =~ / (\d+) [-+][01]\d\d\d$/) {
+ my $timestamp = $1;
+ my $age = time - $timestamp;
+ return ($age, age_string($age));
+ }
+}
+
+sub git_get_references {
my $type = shift || "";
my %refs;
# 5dc01c595e6c6ec9ccda4f6f69c131c0dd945f8c refs/tags/v2.6.11
# c39ae07f393806ccf406ef966e9a15afc43cc36a refs/tags/v2.6.11^{}
- open my $fd, "$projectroot/$project/info/refs" or return;
+ open my $fd, "-|", $GIT, "peek-remote", "$projectroot/$project/"
+ or return;
+
while (my $line = <$fd>) {
chomp $line;
- # attention: for $type == "" it saves only last path part of ref name
- # e.g. from 'refs/heads/jn/gitweb' it would leave only 'gitweb'
- if ($line =~ m/^([0-9a-fA-F]{40})\t.*$type\/([^\^]+)/) {
+ if ($line =~ m/^([0-9a-fA-F]{40})\trefs\/($type\/?[^\^]+)/) {
if (defined $refs{$1}) {
- $refs{$1} .= " / $2";
+ push @{$refs{$1}}, $2;
} else {
- $refs{$1} = $2;
+ $refs{$1} = [ $2 ];
}
}
}
@@ -533,10 +1144,26 @@ sub read_info_ref {
return \%refs;
}
+sub git_get_rev_name_tags {
+ my $hash = shift || return undef;
+
+ open my $fd, "-|", git_cmd(), "name-rev", "--tags", $hash
+ or return;
+ my $name_rev = <$fd>;
+ close $fd;
+
+ if ($name_rev =~ m|^$hash tags/(.*)$|) {
+ return $1;
+ } else {
+ # catches also '$hash undefined' output
+ return undef;
+ }
+}
+
## ----------------------------------------------------------------------
## parse to hash functions
-sub date_str {
+sub parse_date {
my $epoch = shift;
my $tz = shift || "-0000";
@@ -549,8 +1176,10 @@ sub date_str {
$date{'mday'} = $mday;
$date{'day'} = $days[$wday];
$date{'month'} = $months[$mon];
- $date{'rfc2822'} = sprintf "%s, %d %s %4d %02d:%02d:%02d +0000", $days[$wday], $mday, $months[$mon], 1900+$year, $hour ,$min, $sec;
- $date{'mday-time'} = sprintf "%d %s %02d:%02d", $mday, $months[$mon], $hour ,$min;
+ $date{'rfc2822'} = sprintf "%s, %d %s %4d %02d:%02d:%02d +0000",
+ $days[$wday], $mday, $months[$mon], 1900+$year, $hour ,$min, $sec;
+ $date{'mday-time'} = sprintf "%d %s %02d:%02d",
+ $mday, $months[$mon], $hour ,$min;
$tz =~ m/^([+\-][0-9][0-9])([0-9][0-9])$/;
my $local = $epoch + ((int $1 + ($2/60)) * 3600);
@@ -558,15 +1187,18 @@ sub date_str {
$date{'hour_local'} = $hour;
$date{'minute_local'} = $min;
$date{'tz_local'} = $tz;
+ $date{'iso-tz'} = sprintf ("%04d-%02d-%02d %02d:%02d:%02d %s",
+ 1900+$year, $mon+1, $mday,
+ $hour, $min, $sec, $tz);
return %date;
}
-sub git_read_tag {
+sub parse_tag {
my $tag_id = shift;
my %tag;
my @comment;
- open my $fd, "-|", $GIT, "cat-file", "tag", $tag_id or return;
+ open my $fd, "-|", git_cmd(), "cat-file", "tag", $tag_id or return;
$tag{'id'} = $tag_id;
while (my $line = <$fd>) {
chomp $line;
@@ -596,7 +1228,7 @@ sub git_read_tag {
return %tag
}
-sub git_read_commit {
+sub parse_commit {
my $commit_id = shift;
my $commit_text = shift;
@@ -606,11 +1238,13 @@ sub git_read_commit {
if (defined $commit_text) {
@commit_lines = @$commit_text;
} else {
- $/ = "\0";
- open my $fd, "-|", $GIT, "rev-list", "--header", "--parents", "--max-count=1", $commit_id or return;
+ local $/ = "\0";
+ open my $fd, "-|", git_cmd(), "rev-list",
+ "--header", "--parents", "--max-count=1",
+ $commit_id, "--"
+ or return;
@commit_lines = split '\n', <$fd>;
close $fd or return;
- $/ = "\n";
pop @commit_lines;
}
my $header = shift @commit_lines;
@@ -670,6 +1304,9 @@ sub git_read_commit {
last;
}
}
+ if ($co{'title'} eq "") {
+ $co{'title'} = $co{'title_short'} = '(no commit message)';
+ }
# remove added spaces
foreach my $line (@commit_lines) {
$line =~ s/^ //;
@@ -690,65 +1327,183 @@ sub git_read_commit {
return %co;
}
+# parse ref from ref_file, given by ref_id, with given type
+sub parse_ref {
+ my $ref_file = shift;
+ my $ref_id = shift;
+ my $type = shift || git_get_type($ref_id);
+ my %ref_item;
+
+ $ref_item{'type'} = $type;
+ $ref_item{'id'} = $ref_id;
+ $ref_item{'epoch'} = 0;
+ $ref_item{'age'} = "unknown";
+ if ($type eq "tag") {
+ my %tag = parse_tag($ref_id);
+ $ref_item{'comment'} = $tag{'comment'};
+ if ($tag{'type'} eq "commit") {
+ my %co = parse_commit($tag{'object'});
+ $ref_item{'epoch'} = $co{'committer_epoch'};
+ $ref_item{'age'} = $co{'age_string'};
+ } elsif (defined($tag{'epoch'})) {
+ my $age = time - $tag{'epoch'};
+ $ref_item{'epoch'} = $tag{'epoch'};
+ $ref_item{'age'} = age_string($age);
+ }
+ $ref_item{'reftype'} = $tag{'type'};
+ $ref_item{'name'} = $tag{'name'};
+ $ref_item{'refid'} = $tag{'object'};
+ } elsif ($type eq "commit"){
+ my %co = parse_commit($ref_id);
+ $ref_item{'reftype'} = "commit";
+ $ref_item{'name'} = $ref_file;
+ $ref_item{'title'} = $co{'title'};
+ $ref_item{'refid'} = $ref_id;
+ $ref_item{'epoch'} = $co{'committer_epoch'};
+ $ref_item{'age'} = $co{'age_string'};
+ } else {
+ $ref_item{'reftype'} = $type;
+ $ref_item{'name'} = $ref_file;
+ $ref_item{'refid'} = $ref_id;
+ }
+
+ return %ref_item;
+}
+
+# parse line of git-diff-tree "raw" output
+sub parse_difftree_raw_line {
+ my $line = shift;
+ my %res;
+
+ # ':100644 100644 03b218260e99b78c6df0ed378e59ed9205ccc96d 3b93d5e7cc7f7dd4ebed13a5cc1a4ad976fc94d8 M ls-files.c'
+ # ':100644 100644 7f9281985086971d3877aca27704f2aaf9c448ce bc190ebc71bbd923f2b728e505408f5e54bd073a M rev-tree.c'
+ if ($line =~ m/^:([0-7]{6}) ([0-7]{6}) ([0-9a-fA-F]{40}) ([0-9a-fA-F]{40}) (.)([0-9]{0,3})\t(.*)$/) {
+ $res{'from_mode'} = $1;
+ $res{'to_mode'} = $2;
+ $res{'from_id'} = $3;
+ $res{'to_id'} = $4;
+ $res{'status'} = $5;
+ $res{'similarity'} = $6;
+ if ($res{'status'} eq 'R' || $res{'status'} eq 'C') { # renamed or copied
+ ($res{'from_file'}, $res{'to_file'}) = map { unquote($_) } split("\t", $7);
+ } else {
+ $res{'file'} = unquote($7);
+ }
+ }
+ # 'c512b523472485aef4fff9e57b229d9d243c967f'
+ elsif ($line =~ m/^([0-9a-fA-F]{40})$/) {
+ $res{'commit'} = $1;
+ }
+
+ return wantarray ? %res : \%res;
+}
+
+# parse line of git-ls-tree output
+sub parse_ls_tree_line ($;%) {
+ my $line = shift;
+ my %opts = @_;
+ my %res;
+
+ #'100644 blob 0fa3f3a66fb6a137f6ec2c19351ed4d807070ffa panic.c'
+ $line =~ m/^([0-9]+) (.+) ([0-9a-fA-F]{40})\t(.+)$/s;
+
+ $res{'mode'} = $1;
+ $res{'type'} = $2;
+ $res{'hash'} = $3;
+ if ($opts{'-z'}) {
+ $res{'name'} = $4;
+ } else {
+ $res{'name'} = unquote($4);
+ }
+
+ return wantarray ? %res : \%res;
+}
+
## ......................................................................
## parse to array of hashes functions
-sub git_read_refs {
- my $ref_dir = shift;
- my @reflist;
+sub git_get_heads_list {
+ my $limit = shift;
+ my @headslist;
- my @refs;
- my $pfxlen = length("$projectroot/$project/$ref_dir");
- File::Find::find(sub {
- return if (/^\./);
- if (-f $_) {
- push @refs, substr($File::Find::name, $pfxlen + 1);
+ open my $fd, '-|', git_cmd(), 'for-each-ref',
+ ($limit ? '--count='.($limit+1) : ()), '--sort=-committerdate',
+ '--format=%(objectname) %(refname) %(subject)%00%(committer)',
+ 'refs/heads'
+ or return;
+ while (my $line = <$fd>) {
+ my %ref_item;
+
+ chomp $line;
+ my ($refinfo, $committerinfo) = split(/\0/, $line);
+ my ($hash, $name, $title) = split(' ', $refinfo, 3);
+ my ($committer, $epoch, $tz) =
+ ($committerinfo =~ /^(.*) ([0-9]+) (.*)$/);
+ $name =~ s!^refs/heads/!!;
+
+ $ref_item{'name'} = $name;
+ $ref_item{'id'} = $hash;
+ $ref_item{'title'} = $title || '(no commit message)';
+ $ref_item{'epoch'} = $epoch;
+ if ($epoch) {
+ $ref_item{'age'} = age_string(time - $ref_item{'epoch'});
+ } else {
+ $ref_item{'age'} = "unknown";
}
- }, "$projectroot/$project/$ref_dir");
- foreach my $ref_file (@refs) {
- my $ref_id = git_read_hash("$project/$ref_dir/$ref_file");
- my $type = git_get_type($ref_id) || next;
+ push @headslist, \%ref_item;
+ }
+ close $fd;
+
+ return wantarray ? @headslist : \@headslist;
+}
+
+sub git_get_tags_list {
+ my $limit = shift;
+ my @tagslist;
+
+ open my $fd, '-|', git_cmd(), 'for-each-ref',
+ ($limit ? '--count='.($limit+1) : ()), '--sort=-creatordate',
+ '--format=%(objectname) %(objecttype) %(refname) '.
+ '%(*objectname) %(*objecttype) %(subject)%00%(creator)',
+ 'refs/tags'
+ or return;
+ while (my $line = <$fd>) {
my %ref_item;
- my %co;
+
+ chomp $line;
+ my ($refinfo, $creatorinfo) = split(/\0/, $line);
+ my ($id, $type, $name, $refid, $reftype, $title) = split(' ', $refinfo, 6);
+ my ($creator, $epoch, $tz) =
+ ($creatorinfo =~ /^(.*) ([0-9]+) (.*)$/);
+ $name =~ s!^refs/tags/!!;
+
$ref_item{'type'} = $type;
- $ref_item{'id'} = $ref_id;
- $ref_item{'epoch'} = 0;
- $ref_item{'age'} = "unknown";
+ $ref_item{'id'} = $id;
+ $ref_item{'name'} = $name;
if ($type eq "tag") {
- my %tag = git_read_tag($ref_id);
- $ref_item{'comment'} = $tag{'comment'};
- if ($tag{'type'} eq "commit") {
- %co = git_read_commit($tag{'object'});
- $ref_item{'epoch'} = $co{'committer_epoch'};
- $ref_item{'age'} = $co{'age_string'};
- } elsif (defined($tag{'epoch'})) {
- my $age = time - $tag{'epoch'};
- $ref_item{'epoch'} = $tag{'epoch'};
- $ref_item{'age'} = age_string($age);
- }
- $ref_item{'reftype'} = $tag{'type'};
- $ref_item{'name'} = $tag{'name'};
- $ref_item{'refid'} = $tag{'object'};
- } elsif ($type eq "commit"){
- %co = git_read_commit($ref_id);
- $ref_item{'reftype'} = "commit";
- $ref_item{'name'} = $ref_file;
- $ref_item{'title'} = $co{'title'};
- $ref_item{'refid'} = $ref_id;
- $ref_item{'epoch'} = $co{'committer_epoch'};
- $ref_item{'age'} = $co{'age_string'};
+ $ref_item{'subject'} = $title;
+ $ref_item{'reftype'} = $reftype;
+ $ref_item{'refid'} = $refid;
} else {
$ref_item{'reftype'} = $type;
- $ref_item{'name'} = $ref_file;
- $ref_item{'refid'} = $ref_id;
+ $ref_item{'refid'} = $id;
}
- push @reflist, \%ref_item;
+ if ($type eq "tag" || $type eq "commit") {
+ $ref_item{'epoch'} = $epoch;
+ if ($epoch) {
+ $ref_item{'age'} = age_string(time - $ref_item{'epoch'});
+ } else {
+ $ref_item{'age'} = "unknown";
+ }
+ }
+
+ push @tagslist, \%ref_item;
}
- # sort tags by age
- @reflist = sort {$b->{'epoch'} <=> $a->{'epoch'}} @reflist;
- return \@reflist;
+ close $fd;
+
+ return wantarray ? @tagslist : \@tagslist;
}
## ----------------------------------------------------------------------
@@ -764,7 +1519,7 @@ sub get_file_owner {
}
my $owner = $gcos;
$owner =~ s/[,;].*$//;
- return decode("utf8", $owner, Encode::FB_DEFAULT);
+ return to_utf8($owner);
}
## ......................................................................
@@ -778,6 +1533,7 @@ sub mimetype_guess_file {
my %mimemap;
open(MIME, $mimemap) or return undef;
while (<MIME>) {
+ next if m/^#/; # skip comments
my ($mime, $exts) = split(/\t+/);
if (defined $exts) {
my @exts = split(/\s+/, $exts);
@@ -788,7 +1544,7 @@ sub mimetype_guess_file {
}
close(MIME);
- $filename =~ /\.(.*?)$/;
+ $filename =~ /\.([^.]*)$/;
return $mimemap{$1};
}
@@ -799,14 +1555,17 @@ sub mimetype_guess {
if ($mimetypes_file) {
my $file = $mimetypes_file;
- #$file =~ m#^/# or $file = "$projectroot/$path/$file";
+ if ($file !~ m!^/!) { # if it is relative path
+ # it is relative to project
+ $file = "$projectroot/$project/$file";
+ }
$mime = mimetype_guess_file($filename, $file);
}
$mime ||= mimetype_guess_file($filename, '/etc/mime.types');
return $mime;
}
-sub git_blob_plain_mimetype {
+sub blob_mimetype {
my $fd = shift;
my $filename = shift;
@@ -841,13 +1600,13 @@ sub git_header_html {
my $status = shift || "200 OK";
my $expires = shift;
- my $title = "$site_name git";
+ my $title = "$site_name";
if (defined $project) {
$title .= " - $project";
if (defined $action) {
$title .= "/$action";
if (defined $file_name) {
- $title .= " - $file_name";
+ $title .= " - " . esc_path($file_name);
if ($action eq "tree" && $file_name !~ m|/$|) {
$title .= "/";
}
@@ -859,40 +1618,69 @@ sub git_header_html {
# 'application/xhtml+xml', otherwise send it as plain old 'text/html'.
# we have to do this because MSIE sometimes globs '*/*', pretending to
# support xhtml+xml but choking when it gets what it asked for.
- if (defined $cgi->http('HTTP_ACCEPT') && $cgi->http('HTTP_ACCEPT') =~ m/(,|;|\s|^)application\/xhtml\+xml(,|;|\s|$)/ && $cgi->Accept('application/xhtml+xml') != 0) {
+ if (defined $cgi->http('HTTP_ACCEPT') &&
+ $cgi->http('HTTP_ACCEPT') =~ m/(,|;|\s|^)application\/xhtml\+xml(,|;|\s|$)/ &&
+ $cgi->Accept('application/xhtml+xml') != 0) {
$content_type = 'application/xhtml+xml';
} else {
$content_type = 'text/html';
}
- print $cgi->header(-type=>$content_type, -charset => 'utf-8', -status=> $status, -expires => $expires);
+ print $cgi->header(-type=>$content_type, -charset => 'utf-8',
+ -status=> $status, -expires => $expires);
print <<EOF;
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US" lang="en-US">
-<!-- git web interface v$version, (C) 2005-2006, Kay Sievers <kay.sievers\@vrfy.org>, Christian Gierke -->
+<!-- git web interface version $version, (C) 2005-2006, Kay Sievers <kay.sievers\@vrfy.org>, Christian Gierke -->
<!-- git core binaries version $git_version -->
<head>
<meta http-equiv="content-type" content="$content_type; charset=utf-8"/>
+<meta name="generator" content="gitweb/$version git/$git_version"/>
<meta name="robots" content="index, nofollow"/>
<title>$title</title>
-<link rel="stylesheet" type="text/css" href="$stylesheet"/>
EOF
+# print out each stylesheet that exist
+ if (defined $stylesheet) {
+#provides backwards capability for those people who define style sheet in a config file
+ print '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'"/>'."\n";
+ } else {
+ foreach my $stylesheet (@stylesheets) {
+ next unless $stylesheet;
+ print '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'"/>'."\n";
+ }
+ }
if (defined $project) {
printf('<link rel="alternate" title="%s log" '.
'href="%s" type="application/rss+xml"/>'."\n",
- esc_param($project),
- esc_param("$my_uri?p=$project;a=rss"));
+ esc_param($project), href(action=>"rss"));
+ } else {
+ printf('<link rel="alternate" title="%s projects list" '.
+ 'href="%s" type="text/plain; charset=utf-8"/>'."\n",
+ $site_name, href(project=>undef, action=>"project_index"));
+ printf('<link rel="alternate" title="%s projects logs" '.
+ 'href="%s" type="text/x-opml"/>'."\n",
+ $site_name, href(project=>undef, action=>"opml"));
+ }
+ if (defined $favicon) {
+ print qq(<link rel="shortcut icon" href="$favicon" type="image/png"/>\n);
}
print "</head>\n" .
- "<body>\n" .
- "<div class=\"page_header\">\n" .
- "<a href=\"http://www.kernel.org/pub/software/scm/git/docs/\" title=\"git documentation\">" .
- "<img src=\"$logo\" width=\"72\" height=\"27\" alt=\"git\" style=\"float:right; border-width:0px;\"/>" .
- "</a>\n";
- print $cgi->a({-href => esc_param($home_link)}, "projects") . " / ";
+ "<body>\n";
+
+ if (-f $site_header) {
+ open (my $fd, $site_header);
+ print <$fd>;
+ close $fd;
+ }
+
+ print "<div class=\"page_header\">\n" .
+ $cgi->a({-href => esc_url($logo_url),
+ -title => $logo_label},
+ qq(<img src="$logo" width="72" height="27" alt="git" class="logo"/>));
+ print $cgi->a({-href => esc_url($home_link)}, $home_link_str) . " / ";
if (defined $project) {
- print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary")}, esc_html($project));
+ print $cgi->a({-href => href(action=>"summary")}, esc_html($project));
if (defined $action) {
print " / $action";
}
@@ -910,11 +1698,16 @@ EOF
}
$cgi->param("a", "search");
$cgi->param("h", $search_hash);
+ $cgi->param("p", $project);
print $cgi->startform(-method => "get", -action => $my_uri) .
"<div class=\"search\">\n" .
$cgi->hidden(-name => "p") . "\n" .
$cgi->hidden(-name => "a") . "\n" .
$cgi->hidden(-name => "h") . "\n" .
+ $cgi->popup_menu(-name => 'st', -default => 'commit',
+ -values => ['commit', 'author', 'committer', 'pickaxe']) .
+ $cgi->sup($cgi->a({-href => href(action=>"search_help")}, "?")) .
+ " search:\n",
$cgi->textfield(-name => "s", -value => $searchtext) . "\n" .
"</div>" .
$cgi->end_form() . "\n";
@@ -925,16 +1718,27 @@ EOF
sub git_footer_html {
print "<div class=\"page_footer\">\n";
if (defined $project) {
- my $descr = git_read_description($project);
+ my $descr = git_get_project_description($project);
if (defined $descr) {
print "<div class=\"page_footer_text\">" . esc_html($descr) . "</div>\n";
}
- print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=rss"), -class => "rss_logo"}, "RSS") . "\n";
+ print $cgi->a({-href => href(action=>"rss"),
+ -class => "rss_logo"}, "RSS") . "\n";
} else {
- print $cgi->a({-href => "$my_uri?" . esc_param("a=opml"), -class => "rss_logo"}, "OPML") . "\n";
+ print $cgi->a({-href => href(project=>undef, action=>"opml"),
+ -class => "rss_logo"}, "OPML") . " ";
+ print $cgi->a({-href => href(project=>undef, action=>"project_index"),
+ -class => "rss_logo"}, "TXT") . "\n";
}
- print "</div>\n" .
- "</body>\n" .
+ print "</div>\n" ;
+
+ if (-f $site_footer) {
+ open (my $fd, $site_footer);
+ print <$fd>;
+ close $fd;
+ }
+
+ print "</body>\n" .
"</html>";
}
@@ -943,11 +1747,13 @@ sub die_error {
my $error = shift || "Malformed query, file missing or permission denied";
git_header_html($status);
- print "<div class=\"page_body\">\n" .
- "<br/><br/>\n" .
- "$status - $error\n" .
- "<br/>\n" .
- "</div>\n";
+ print <<EOF;
+<div class="page_body">
+<br /><br />
+$status - $error
+<br />
+</div>
+EOF
git_footer_html();
exit;
}
@@ -955,7 +1761,7 @@ sub die_error {
## ----------------------------------------------------------------------
## functions printing or outputting HTML: navigation
-sub git_page_nav {
+sub git_print_page_nav {
my ($current, $suppress, $head, $treehead, $treebase, $extra) = @_;
$extra = '' if !defined $extra; # pager or formats
@@ -964,45 +1770,43 @@ sub git_page_nav {
@navs = grep { $_ ne $suppress } @navs;
}
- my %arg = map { $_, ''} @navs;
+ my %arg = map { $_ => {action=>$_} } @navs;
if (defined $head) {
for (qw(commit commitdiff)) {
- $arg{$_} = ";h=$head";
+ $arg{$_}{hash} = $head;
}
if ($current =~ m/^(tree | log | shortlog | commit | commitdiff | search)$/x) {
for (qw(shortlog log)) {
- $arg{$_} = ";h=$head";
+ $arg{$_}{hash} = $head;
}
}
}
- $arg{tree} .= ";h=$treehead" if defined $treehead;
- $arg{tree} .= ";hb=$treebase" if defined $treebase;
+ $arg{tree}{hash} = $treehead if defined $treehead;
+ $arg{tree}{hash_base} = $treebase if defined $treebase;
print "<div class=\"page_nav\">\n" .
(join " | ",
- map { $_ eq $current
- ? $_
- : $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$_$arg{$_}")}, "$_")
- }
- @navs);
+ map { $_ eq $current ?
+ $_ : $cgi->a({-href => href(%{$arg{$_}})}, "$_")
+ } @navs);
print "<br/>\n$extra<br/>\n" .
"</div>\n";
}
-sub git_get_paging_nav {
+sub format_paging_nav {
my ($action, $hash, $head, $page, $nrevs) = @_;
my $paging_nav;
if ($hash ne $head || $page) {
- $paging_nav .= $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$action")}, "HEAD");
+ $paging_nav .= $cgi->a({-href => href(action=>$action)}, "HEAD");
} else {
$paging_nav .= "HEAD";
}
if ($page > 0) {
$paging_nav .= " &sdot; " .
- $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$action;h=$hash;pg=" . ($page-1)),
+ $cgi->a({-href => href(action=>$action, hash=>$hash, page=>$page-1),
-accesskey => "p", -title => "Alt-p"}, "prev");
} else {
$paging_nav .= " &sdot; prev";
@@ -1010,7 +1814,7 @@ sub git_get_paging_nav {
if ($nrevs >= (100 * ($page+1)-1)) {
$paging_nav .= " &sdot; " .
- $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$action;h=$hash;pg=" . ($page+1)),
+ $cgi->a({-href => href(action=>$action, hash=>$hash, page=>$page+1),
-accesskey => "n", -title => "Alt-n"}, "next");
} else {
$paging_nav .= " &sdot; next";
@@ -1022,49 +1826,666 @@ sub git_get_paging_nav {
## ......................................................................
## functions printing or outputting HTML: div
-sub git_header_div {
+sub git_print_header_div {
my ($action, $title, $hash, $hash_base) = @_;
- my $rest = '';
+ my %args = ();
- $rest .= ";h=$hash" if $hash;
- $rest .= ";hb=$hash_base" if $hash_base;
+ $args{action} = $action;
+ $args{hash} = $hash if $hash;
+ $args{hash_base} = $hash_base if $hash_base;
print "<div class=\"header\">\n" .
- $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$action$rest"),
- -class => "title"}, $title ? $title : $action) . "\n" .
- "</div>\n";
+ $cgi->a({-href => href(%args), -class => "title"},
+ $title ? $title : $action) .
+ "\n</div>\n";
+}
+
+#sub git_print_authorship (\%) {
+sub git_print_authorship {
+ my $co = shift;
+
+ my %ad = parse_date($co->{'author_epoch'}, $co->{'author_tz'});
+ print "<div class=\"author_date\">" .
+ esc_html($co->{'author_name'}) .
+ " [$ad{'rfc2822'}";
+ if ($ad{'hour_local'} < 6) {
+ printf(" (<span class=\"atnight\">%02d:%02d</span> %s)",
+ $ad{'hour_local'}, $ad{'minute_local'}, $ad{'tz_local'});
+ } else {
+ printf(" (%02d:%02d %s)",
+ $ad{'hour_local'}, $ad{'minute_local'}, $ad{'tz_local'});
+ }
+ print "]</div>\n";
}
sub git_print_page_path {
my $name = shift;
my $type = shift;
+ my $hb = shift;
+
+
+ print "<div class=\"page_path\">";
+ print $cgi->a({-href => href(action=>"tree", hash_base=>$hb),
+ -title => 'tree root'}, "[$project]");
+ print " / ";
+ if (defined $name) {
+ my @dirname = split '/', $name;
+ my $basename = pop @dirname;
+ my $fullname = '';
+
+ foreach my $dir (@dirname) {
+ $fullname .= ($fullname ? '/' : '') . $dir;
+ print $cgi->a({-href => href(action=>"tree", file_name=>$fullname,
+ hash_base=>$hb),
+ -title => esc_html($fullname)}, esc_path($dir));
+ print " / ";
+ }
+ if (defined $type && $type eq 'blob') {
+ print $cgi->a({-href => href(action=>"blob_plain", file_name=>$file_name,
+ hash_base=>$hb),
+ -title => esc_html($name)}, esc_path($basename));
+ } elsif (defined $type && $type eq 'tree') {
+ print $cgi->a({-href => href(action=>"tree", file_name=>$file_name,
+ hash_base=>$hb),
+ -title => esc_html($name)}, esc_path($basename));
+ print " / ";
+ } else {
+ print esc_path($basename);
+ }
+ }
+ print "<br/></div>\n";
+}
- if (!defined $name) {
- print "<div class=\"page_path\"><b>/</b></div>\n";
- } elsif (defined $type && $type eq 'blob') {
- print "<div class=\"page_path\"><b>" .
- $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob_plain;f=$file_name")}, esc_html($name)) . "</b><br/></div>\n";
- } else {
- print "<div class=\"page_path\"><b>" . esc_html($name) . "</b><br/></div>\n";
+# sub git_print_log (\@;%) {
+sub git_print_log ($;%) {
+ my $log = shift;
+ my %opts = @_;
+
+ if ($opts{'-remove_title'}) {
+ # remove title, i.e. first line of log
+ shift @$log;
+ }
+ # remove leading empty lines
+ while (defined $log->[0] && $log->[0] eq "") {
+ shift @$log;
+ }
+
+ # print log
+ my $signoff = 0;
+ my $empty = 0;
+ foreach my $line (@$log) {
+ if ($line =~ m/^ *(signed[ \-]off[ \-]by[ :]|acked[ \-]by[ :]|cc[ :])/i) {
+ $signoff = 1;
+ $empty = 0;
+ if (! $opts{'-remove_signoff'}) {
+ print "<span class=\"signoff\">" . esc_html($line) . "</span><br/>\n";
+ next;
+ } else {
+ # remove signoff lines
+ next;
+ }
+ } else {
+ $signoff = 0;
+ }
+
+ # print only one empty line
+ # do not print empty line after signoff
+ if ($line eq "") {
+ next if ($empty || $signoff);
+ $empty = 1;
+ } else {
+ $empty = 0;
+ }
+
+ print format_log_line_html($line) . "<br/>\n";
+ }
+
+ if ($opts{'-final_empty_line'}) {
+ # end with single empty line
+ print "<br/>\n" unless $empty;
+ }
+}
+
+# print tree entry (row of git_tree), but without encompassing <tr> element
+sub git_print_tree_entry {
+ my ($t, $basedir, $hash_base, $have_blame) = @_;
+
+ my %base_key = ();
+ $base_key{hash_base} = $hash_base if defined $hash_base;
+
+ # The format of a table row is: mode list link. Where mode is
+ # the mode of the entry, list is the name of the entry, an href,
+ # and link is the action links of the entry.
+
+ print "<td class=\"mode\">" . mode_str($t->{'mode'}) . "</td>\n";
+ if ($t->{'type'} eq "blob") {
+ print "<td class=\"list\">" .
+ $cgi->a({-href => href(action=>"blob", hash=>$t->{'hash'},
+ file_name=>"$basedir$t->{'name'}", %base_key),
+ -class => "list"}, esc_path($t->{'name'})) . "</td>\n";
+ print "<td class=\"link\">";
+ print $cgi->a({-href => href(action=>"blob", hash=>$t->{'hash'},
+ file_name=>"$basedir$t->{'name'}", %base_key)},
+ "blob");
+ if ($have_blame) {
+ print " | " .
+ $cgi->a({-href => href(action=>"blame", hash=>$t->{'hash'},
+ file_name=>"$basedir$t->{'name'}", %base_key)},
+ "blame");
+ }
+ if (defined $hash_base) {
+ print " | " .
+ $cgi->a({-href => href(action=>"history", hash_base=>$hash_base,
+ hash=>$t->{'hash'}, file_name=>"$basedir$t->{'name'}")},
+ "history");
+ }
+ print " | " .
+ $cgi->a({-href => href(action=>"blob_plain", hash_base=>$hash_base,
+ file_name=>"$basedir$t->{'name'}")},
+ "raw");
+ print "</td>\n";
+
+ } elsif ($t->{'type'} eq "tree") {
+ print "<td class=\"list\">";
+ print $cgi->a({-href => href(action=>"tree", hash=>$t->{'hash'},
+ file_name=>"$basedir$t->{'name'}", %base_key)},
+ esc_path($t->{'name'}));
+ print "</td>\n";
+ print "<td class=\"link\">";
+ print $cgi->a({-href => href(action=>"tree", hash=>$t->{'hash'},
+ file_name=>"$basedir$t->{'name'}", %base_key)},
+ "tree");
+ if (defined $hash_base) {
+ print " | " .
+ $cgi->a({-href => href(action=>"history", hash_base=>$hash_base,
+ file_name=>"$basedir$t->{'name'}")},
+ "history");
+ }
+ print "</td>\n";
}
}
## ......................................................................
## functions printing large fragments of HTML
+sub git_difftree_body {
+ my ($difftree, $hash, $parent) = @_;
+ my ($have_blame) = gitweb_check_feature('blame');
+ print "<div class=\"list_head\">\n";
+ if ($#{$difftree} > 10) {
+ print(($#{$difftree} + 1) . " files changed:\n");
+ }
+ print "</div>\n";
+
+ print "<table class=\"diff_tree\">\n";
+ my $alternate = 1;
+ my $patchno = 0;
+ foreach my $line (@{$difftree}) {
+ my %diff = parse_difftree_raw_line($line);
+
+ if ($alternate) {
+ print "<tr class=\"dark\">\n";
+ } else {
+ print "<tr class=\"light\">\n";
+ }
+ $alternate ^= 1;
+
+ my ($to_mode_oct, $to_mode_str, $to_file_type);
+ my ($from_mode_oct, $from_mode_str, $from_file_type);
+ if ($diff{'to_mode'} ne ('0' x 6)) {
+ $to_mode_oct = oct $diff{'to_mode'};
+ if (S_ISREG($to_mode_oct)) { # only for regular file
+ $to_mode_str = sprintf("%04o", $to_mode_oct & 0777); # permission bits
+ }
+ $to_file_type = file_type($diff{'to_mode'});
+ }
+ if ($diff{'from_mode'} ne ('0' x 6)) {
+ $from_mode_oct = oct $diff{'from_mode'};
+ if (S_ISREG($to_mode_oct)) { # only for regular file
+ $from_mode_str = sprintf("%04o", $from_mode_oct & 0777); # permission bits
+ }
+ $from_file_type = file_type($diff{'from_mode'});
+ }
+
+ if ($diff{'status'} eq "A") { # created
+ my $mode_chng = "<span class=\"file_status new\">[new $to_file_type";
+ $mode_chng .= " with mode: $to_mode_str" if $to_mode_str;
+ $mode_chng .= "]</span>";
+ print "<td>";
+ print $cgi->a({-href => href(action=>"blob", hash=>$diff{'to_id'},
+ hash_base=>$hash, file_name=>$diff{'file'}),
+ -class => "list"}, esc_path($diff{'file'}));
+ print "</td>\n";
+ print "<td>$mode_chng</td>\n";
+ print "<td class=\"link\">";
+ if ($action eq 'commitdiff') {
+ # link to patch
+ $patchno++;
+ print $cgi->a({-href => "#patch$patchno"}, "patch");
+ }
+ print "</td>\n";
+
+ } elsif ($diff{'status'} eq "D") { # deleted
+ my $mode_chng = "<span class=\"file_status deleted\">[deleted $from_file_type]</span>";
+ print "<td>";
+ print $cgi->a({-href => href(action=>"blob", hash=>$diff{'from_id'},
+ hash_base=>$parent, file_name=>$diff{'file'}),
+ -class => "list"}, esc_path($diff{'file'}));
+ print "</td>\n";
+ print "<td>$mode_chng</td>\n";
+ print "<td class=\"link\">";
+ if ($action eq 'commitdiff') {
+ # link to patch
+ $patchno++;
+ print $cgi->a({-href => "#patch$patchno"}, "patch");
+ print " | ";
+ }
+ print $cgi->a({-href => href(action=>"blob", hash=>$diff{'from_id'},
+ hash_base=>$parent, file_name=>$diff{'file'})},
+ "blob") . " | ";
+ if ($have_blame) {
+ print $cgi->a({-href =>
+ href(action=>"blame",
+ hash_base=>$parent,
+ file_name=>$diff{'file'})},
+ "blame") . " | ";
+ }
+ print $cgi->a({-href => href(action=>"history", hash_base=>$parent,
+ file_name=>$diff{'file'})},
+ "history");
+ print "</td>\n";
+
+ } elsif ($diff{'status'} eq "M" || $diff{'status'} eq "T") { # modified, or type changed
+ my $mode_chnge = "";
+ if ($diff{'from_mode'} != $diff{'to_mode'}) {
+ $mode_chnge = "<span class=\"file_status mode_chnge\">[changed";
+ if ($from_file_type != $to_file_type) {
+ $mode_chnge .= " from $from_file_type to $to_file_type";
+ }
+ if (($from_mode_oct & 0777) != ($to_mode_oct & 0777)) {
+ if ($from_mode_str && $to_mode_str) {
+ $mode_chnge .= " mode: $from_mode_str->$to_mode_str";
+ } elsif ($to_mode_str) {
+ $mode_chnge .= " mode: $to_mode_str";
+ }
+ }
+ $mode_chnge .= "]</span>\n";
+ }
+ print "<td>";
+ print $cgi->a({-href => href(action=>"blob", hash=>$diff{'to_id'},
+ hash_base=>$hash, file_name=>$diff{'file'}),
+ -class => "list"}, esc_path($diff{'file'}));
+ print "</td>\n";
+ print "<td>$mode_chnge</td>\n";
+ print "<td class=\"link\">";
+ if ($action eq 'commitdiff') {
+ # link to patch
+ $patchno++;
+ print $cgi->a({-href => "#patch$patchno"}, "patch") .
+ " | ";
+ } elsif ($diff{'to_id'} ne $diff{'from_id'}) {
+ # "commit" view and modified file (not onlu mode changed)
+ print $cgi->a({-href => href(action=>"blobdiff",
+ hash=>$diff{'to_id'}, hash_parent=>$diff{'from_id'},
+ hash_base=>$hash, hash_parent_base=>$parent,
+ file_name=>$diff{'file'})},
+ "diff") .
+ " | ";
+ }
+ print $cgi->a({-href => href(action=>"blob", hash=>$diff{'to_id'},
+ hash_base=>$hash, file_name=>$diff{'file'})},
+ "blob") . " | ";
+ if ($have_blame) {
+ print $cgi->a({-href => href(action=>"blame",
+ hash_base=>$hash,
+ file_name=>$diff{'file'})},
+ "blame") . " | ";
+ }
+ print $cgi->a({-href => href(action=>"history", hash_base=>$hash,
+ file_name=>$diff{'file'})},
+ "history");
+ print "</td>\n";
+
+ } elsif ($diff{'status'} eq "R" || $diff{'status'} eq "C") { # renamed or copied
+ my %status_name = ('R' => 'moved', 'C' => 'copied');
+ my $nstatus = $status_name{$diff{'status'}};
+ my $mode_chng = "";
+ if ($diff{'from_mode'} != $diff{'to_mode'}) {
+ # mode also for directories, so we cannot use $to_mode_str
+ $mode_chng = sprintf(", mode: %04o", $to_mode_oct & 0777);
+ }
+ print "<td>" .
+ $cgi->a({-href => href(action=>"blob", hash_base=>$hash,
+ hash=>$diff{'to_id'}, file_name=>$diff{'to_file'}),
+ -class => "list"}, esc_path($diff{'to_file'})) . "</td>\n" .
+ "<td><span class=\"file_status $nstatus\">[$nstatus from " .
+ $cgi->a({-href => href(action=>"blob", hash_base=>$parent,
+ hash=>$diff{'from_id'}, file_name=>$diff{'from_file'}),
+ -class => "list"}, esc_path($diff{'from_file'})) .
+ " with " . (int $diff{'similarity'}) . "% similarity$mode_chng]</span></td>\n" .
+ "<td class=\"link\">";
+ if ($action eq 'commitdiff') {
+ # link to patch
+ $patchno++;
+ print $cgi->a({-href => "#patch$patchno"}, "patch") .
+ " | ";
+ } elsif ($diff{'to_id'} ne $diff{'from_id'}) {
+ # "commit" view and modified file (not only pure rename or copy)
+ print $cgi->a({-href => href(action=>"blobdiff",
+ hash=>$diff{'to_id'}, hash_parent=>$diff{'from_id'},
+ hash_base=>$hash, hash_parent_base=>$parent,
+ file_name=>$diff{'to_file'}, file_parent=>$diff{'from_file'})},
+ "diff") .
+ " | ";
+ }
+ print $cgi->a({-href => href(action=>"blob", hash=>$diff{'from_id'},
+ hash_base=>$parent, file_name=>$diff{'from_file'})},
+ "blob") . " | ";
+ if ($have_blame) {
+ print $cgi->a({-href => href(action=>"blame",
+ hash_base=>$hash,
+ file_name=>$diff{'to_file'})},
+ "blame") . " | ";
+ }
+ print $cgi->a({-href => href(action=>"history", hash_base=>$parent,
+ file_name=>$diff{'from_file'})},
+ "history");
+ print "</td>\n";
+
+ } # we should not encounter Unmerged (U) or Unknown (X) status
+ print "</tr>\n";
+ }
+ print "</table>\n";
+}
+
+sub git_patchset_body {
+ my ($fd, $difftree, $hash, $hash_parent) = @_;
+
+ my $patch_idx = 0;
+ my $in_header = 0;
+ my $patch_found = 0;
+ my $diffinfo;
+ my (%from, %to);
+
+ print "<div class=\"patchset\">\n";
+
+ LINE:
+ while (my $patch_line = <$fd>) {
+ chomp $patch_line;
+
+ if ($patch_line =~ m/^diff /) { # "git diff" header
+ # beginning of patch (in patchset)
+ if ($patch_found) {
+ # close extended header for previous empty patch
+ if ($in_header) {
+ print "</div>\n" # class="diff extended_header"
+ }
+ # close previous patch
+ print "</div>\n"; # class="patch"
+ } else {
+ # first patch in patchset
+ $patch_found = 1;
+ }
+ print "<div class=\"patch\" id=\"patch". ($patch_idx+1) ."\">\n";
+
+ # read and prepare patch information
+ if (ref($difftree->[$patch_idx]) eq "HASH") {
+ # pre-parsed (or generated by hand)
+ $diffinfo = $difftree->[$patch_idx];
+ } else {
+ $diffinfo = parse_difftree_raw_line($difftree->[$patch_idx]);
+ }
+ $from{'file'} = $diffinfo->{'from_file'} || $diffinfo->{'file'};
+ $to{'file'} = $diffinfo->{'to_file'} || $diffinfo->{'file'};
+ if ($diffinfo->{'status'} ne "A") { # not new (added) file
+ $from{'href'} = href(action=>"blob", hash_base=>$hash_parent,
+ hash=>$diffinfo->{'from_id'},
+ file_name=>$from{'file'});
+ }
+ if ($diffinfo->{'status'} ne "D") { # not deleted file
+ $to{'href'} = href(action=>"blob", hash_base=>$hash,
+ hash=>$diffinfo->{'to_id'},
+ file_name=>$to{'file'});
+ }
+ $patch_idx++;
+
+ # print "git diff" header
+ $patch_line =~ s!^(diff (.*?) )"?a/.*$!$1!;
+ if ($from{'href'}) {
+ $patch_line .= $cgi->a({-href => $from{'href'}, -class => "path"},
+ 'a/' . esc_path($from{'file'}));
+ } else { # file was added
+ $patch_line .= 'a/' . esc_path($from{'file'});
+ }
+ $patch_line .= ' ';
+ if ($to{'href'}) {
+ $patch_line .= $cgi->a({-href => $to{'href'}, -class => "path"},
+ 'b/' . esc_path($to{'file'}));
+ } else { # file was deleted
+ $patch_line .= 'b/' . esc_path($to{'file'});
+ }
+
+ print "<div class=\"diff header\">$patch_line</div>\n";
+ print "<div class=\"diff extended_header\">\n";
+ $in_header = 1;
+ next LINE;
+ }
+
+ if ($in_header) {
+ if ($patch_line !~ m/^---/) {
+ # match <path>
+ if ($patch_line =~ s!^((copy|rename) from ).*$!$1! && $from{'href'}) {
+ $patch_line .= $cgi->a({-href=>$from{'href'}, -class=>"path"},
+ esc_path($from{'file'}));
+ }
+ if ($patch_line =~ s!^((copy|rename) to ).*$!$1! && $to{'href'}) {
+ $patch_line = $cgi->a({-href=>$to{'href'}, -class=>"path"},
+ esc_path($to{'file'}));
+ }
+ # match <mode>
+ if ($patch_line =~ m/\s(\d{6})$/) {
+ $patch_line .= '<span class="info"> (' .
+ file_type_long($1) .
+ ')</span>';
+ }
+ # match <hash>
+ if ($patch_line =~ m/^index/) {
+ my ($from_link, $to_link);
+ if ($from{'href'}) {
+ $from_link = $cgi->a({-href=>$from{'href'}, -class=>"hash"},
+ substr($diffinfo->{'from_id'},0,7));
+ } else {
+ $from_link = '0' x 7;
+ }
+ if ($to{'href'}) {
+ $to_link = $cgi->a({-href=>$to{'href'}, -class=>"hash"},
+ substr($diffinfo->{'to_id'},0,7));
+ } else {
+ $to_link = '0' x 7;
+ }
+ my ($from_id, $to_id) = ($diffinfo->{'from_id'}, $diffinfo->{'to_id'});
+ $patch_line =~ s!$from_id\.\.$to_id!$from_link..$to_link!;
+ }
+ print $patch_line . "<br/>\n";
+
+ } else {
+ #$in_header && $patch_line =~ m/^---/;
+ print "</div>\n"; # class="diff extended_header"
+ $in_header = 0;
+
+ if ($from{'href'}) {
+ $patch_line = '--- a/' .
+ $cgi->a({-href=>$from{'href'}, -class=>"path"},
+ esc_path($from{'file'}));
+ }
+ print "<div class=\"diff from_file\">$patch_line</div>\n";
+
+ $patch_line = <$fd>;
+ chomp $patch_line;
+
+ #$patch_line =~ m/^+++/;
+ if ($to{'href'}) {
+ $patch_line = '+++ b/' .
+ $cgi->a({-href=>$to{'href'}, -class=>"path"},
+ esc_path($to{'file'}));
+ }
+ print "<div class=\"diff to_file\">$patch_line</div>\n";
+
+ }
+
+ next LINE;
+ }
+
+ print format_diff_line($patch_line);
+ }
+ print "</div>\n" if $in_header; # extended header
+
+ print "</div>\n" if $patch_found; # class="patch"
+
+ print "</div>\n"; # class="patchset"
+}
+
+# . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
+
+sub git_project_list_body {
+ my ($projlist, $order, $from, $to, $extra, $no_header) = @_;
+
+ my ($check_forks) = gitweb_check_feature('forks');
+
+ my @projects;
+ foreach my $pr (@$projlist) {
+ my (@aa) = git_get_last_activity($pr->{'path'});
+ unless (@aa) {
+ next;
+ }
+ ($pr->{'age'}, $pr->{'age_string'}) = @aa;
+ if (!defined $pr->{'descr'}) {
+ my $descr = git_get_project_description($pr->{'path'}) || "";
+ $pr->{'descr'} = chop_str($descr, 25, 5);
+ }
+ if (!defined $pr->{'owner'}) {
+ $pr->{'owner'} = get_file_owner("$projectroot/$pr->{'path'}") || "";
+ }
+ if ($check_forks) {
+ my $pname = $pr->{'path'};
+ if (($pname =~ s/\.git$//) &&
+ ($pname !~ /\/$/) &&
+ (-d "$projectroot/$pname")) {
+ $pr->{'forks'} = "-d $projectroot/$pname";
+ }
+ else {
+ $pr->{'forks'} = 0;
+ }
+ }
+ push @projects, $pr;
+ }
+
+ $order ||= "project";
+ $from = 0 unless defined $from;
+ $to = $#projects if (!defined $to || $#projects < $to);
+
+ print "<table class=\"project_list\">\n";
+ unless ($no_header) {
+ print "<tr>\n";
+ if ($check_forks) {
+ print "<th></th>\n";
+ }
+ if ($order eq "project") {
+ @projects = sort {$a->{'path'} cmp $b->{'path'}} @projects;
+ print "<th>Project</th>\n";
+ } else {
+ print "<th>" .
+ $cgi->a({-href => href(project=>undef, order=>'project'),
+ -class => "header"}, "Project") .
+ "</th>\n";
+ }
+ if ($order eq "descr") {
+ @projects = sort {$a->{'descr'} cmp $b->{'descr'}} @projects;
+ print "<th>Description</th>\n";
+ } else {
+ print "<th>" .
+ $cgi->a({-href => href(project=>undef, order=>'descr'),
+ -class => "header"}, "Description") .
+ "</th>\n";
+ }
+ if ($order eq "owner") {
+ @projects = sort {$a->{'owner'} cmp $b->{'owner'}} @projects;
+ print "<th>Owner</th>\n";
+ } else {
+ print "<th>" .
+ $cgi->a({-href => href(project=>undef, order=>'owner'),
+ -class => "header"}, "Owner") .
+ "</th>\n";
+ }
+ if ($order eq "age") {
+ @projects = sort {$a->{'age'} <=> $b->{'age'}} @projects;
+ print "<th>Last Change</th>\n";
+ } else {
+ print "<th>" .
+ $cgi->a({-href => href(project=>undef, order=>'age'),
+ -class => "header"}, "Last Change") .
+ "</th>\n";
+ }
+ print "<th></th>\n" .
+ "</tr>\n";
+ }
+ my $alternate = 1;
+ for (my $i = $from; $i <= $to; $i++) {
+ my $pr = $projects[$i];
+ if ($alternate) {
+ print "<tr class=\"dark\">\n";
+ } else {
+ print "<tr class=\"light\">\n";
+ }
+ $alternate ^= 1;
+ if ($check_forks) {
+ print "<td>";
+ if ($pr->{'forks'}) {
+ print "<!-- $pr->{'forks'} -->\n";
+ print $cgi->a({-href => href(project=>$pr->{'path'}, action=>"forks")}, "+");
+ }
+ print "</td>\n";
+ }
+ print "<td>" . $cgi->a({-href => href(project=>$pr->{'path'}, action=>"summary"),
+ -class => "list"}, esc_html($pr->{'path'})) . "</td>\n" .
+ "<td>" . esc_html($pr->{'descr'}) . "</td>\n" .
+ "<td><i>" . chop_str($pr->{'owner'}, 15) . "</i></td>\n";
+ print "<td class=\"". age_class($pr->{'age'}) . "\">" .
+ $pr->{'age_string'} . "</td>\n" .
+ "<td class=\"link\">" .
+ $cgi->a({-href => href(project=>$pr->{'path'}, action=>"summary")}, "summary") . " | " .
+ $cgi->a({-href => href(project=>$pr->{'path'}, action=>"shortlog")}, "shortlog") . " | " .
+ $cgi->a({-href => href(project=>$pr->{'path'}, action=>"log")}, "log") . " | " .
+ $cgi->a({-href => href(project=>$pr->{'path'}, action=>"tree")}, "tree") .
+ ($pr->{'forks'} ? " | " . $cgi->a({-href => href(project=>$pr->{'path'}, action=>"forks")}, "forks") : '') .
+ "</td>\n" .
+ "</tr>\n";
+ }
+ if (defined $extra) {
+ print "<tr>\n";
+ if ($check_forks) {
+ print "<td></td>\n";
+ }
+ print "<td colspan=\"5\">$extra</td>\n" .
+ "</tr>\n";
+ }
+ print "</table>\n";
+}
+
sub git_shortlog_body {
# uses global variable $project
my ($revlist, $from, $to, $refs, $extra) = @_;
+
$from = 0 unless defined $from;
$to = $#{$revlist} if (!defined $to || $#{$revlist} < $to);
print "<table class=\"shortlog\" cellspacing=\"0\">\n";
- my $alternate = 0;
+ my $alternate = 1;
for (my $i = $from; $i <= $to; $i++) {
my $commit = $revlist->[$i];
- #my $ref = defined $refs ? git_get_referencing($refs, $commit) : '';
- my $ref = git_get_referencing($refs, $commit);
- my %co = git_read_commit($commit);
+ #my $ref = defined $refs ? format_ref_marker($refs, $commit) : '';
+ my $ref = format_ref_marker($refs, $commit);
+ my %co = parse_commit($commit);
if ($alternate) {
print "<tr class=\"dark\">\n";
} else {
@@ -1075,20 +2496,81 @@ sub git_shortlog_body {
print "<td title=\"$co{'age_string_age'}\"><i>$co{'age_string_date'}</i></td>\n" .
"<td><i>" . esc_html(chop_str($co{'author_name'}, 10)) . "</i></td>\n" .
"<td>";
- if (length($co{'title_short'}) < length($co{'title'})) {
- print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$commit"),
- -class => "list", -title => "$co{'title'}"},
- "<b>" . esc_html($co{'title_short'}) . "$ref</b>");
+ print format_subject_html($co{'title'}, $co{'title_short'},
+ href(action=>"commit", hash=>$commit), $ref);
+ print "</td>\n" .
+ "<td class=\"link\">" .
+ $cgi->a({-href => href(action=>"commit", hash=>$commit)}, "commit") . " | " .
+ $cgi->a({-href => href(action=>"commitdiff", hash=>$commit)}, "commitdiff") . " | " .
+ $cgi->a({-href => href(action=>"tree", hash=>$commit, hash_base=>$commit)}, "tree");
+ if (gitweb_have_snapshot()) {
+ print " | " . $cgi->a({-href => href(action=>"snapshot", hash=>$commit)}, "snapshot");
+ }
+ print "</td>\n" .
+ "</tr>\n";
+ }
+ if (defined $extra) {
+ print "<tr>\n" .
+ "<td colspan=\"4\">$extra</td>\n" .
+ "</tr>\n";
+ }
+ print "</table>\n";
+}
+
+sub git_history_body {
+ # Warning: assumes constant type (blob or tree) during history
+ my ($revlist, $from, $to, $refs, $hash_base, $ftype, $extra) = @_;
+
+ $from = 0 unless defined $from;
+ $to = $#{$revlist} unless (defined $to && $to <= $#{$revlist});
+
+ print "<table class=\"history\" cellspacing=\"0\">\n";
+ my $alternate = 1;
+ for (my $i = $from; $i <= $to; $i++) {
+ if ($revlist->[$i] !~ m/^([0-9a-fA-F]{40})/) {
+ next;
+ }
+
+ my $commit = $1;
+ my %co = parse_commit($commit);
+ if (!%co) {
+ next;
+ }
+
+ my $ref = format_ref_marker($refs, $commit);
+
+ if ($alternate) {
+ print "<tr class=\"dark\">\n";
} else {
- print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$commit"),
- -class => "list"},
- "<b>" . esc_html($co{'title'}) . "$ref</b>");
+ print "<tr class=\"light\">\n";
}
+ $alternate ^= 1;
+ print "<td title=\"$co{'age_string_age'}\"><i>$co{'age_string_date'}</i></td>\n" .
+ # shortlog uses chop_str($co{'author_name'}, 10)
+ "<td><i>" . esc_html(chop_str($co{'author_name'}, 15, 3)) . "</i></td>\n" .
+ "<td>";
+ # originally git_history used chop_str($co{'title'}, 50)
+ print format_subject_html($co{'title'}, $co{'title_short'},
+ href(action=>"commit", hash=>$commit), $ref);
print "</td>\n" .
"<td class=\"link\">" .
- $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$commit")}, "commit") . " | " .
- $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$commit")}, "commitdiff") .
- "</td>\n" .
+ $cgi->a({-href => href(action=>$ftype, hash_base=>$commit, file_name=>$file_name)}, $ftype) . " | " .
+ $cgi->a({-href => href(action=>"commitdiff", hash=>$commit)}, "commitdiff");
+
+ if ($ftype eq 'blob') {
+ my $blob_current = git_get_hash_by_path($hash_base, $file_name);
+ my $blob_parent = git_get_hash_by_path($commit, $file_name);
+ if (defined $blob_current && defined $blob_parent &&
+ $blob_current ne $blob_parent) {
+ print " | " .
+ $cgi->a({-href => href(action=>"blobdiff",
+ hash=>$blob_current, hash_parent=>$blob_parent,
+ hash_base=>$hash_base, hash_parent_base=>$commit,
+ file_name=>$file_name)},
+ "diff to current");
+ }
+ }
+ print "</td>\n" .
"</tr>\n";
}
if (defined $extra) {
@@ -1106,12 +2588,11 @@ sub git_tags_body {
$to = $#{$taglist} if (!defined $to || $#{$taglist} < $to);
print "<table class=\"tags\" cellspacing=\"0\">\n";
- my $alternate = 0;
+ my $alternate = 1;
for (my $i = $from; $i <= $to; $i++) {
my $entry = $taglist->[$i];
my %tag = %$entry;
- my $comment_lines = $tag{'comment'};
- my $comment = shift @$comment_lines;
+ my $comment = $tag{'subject'};
my $comment_short;
if (defined $comment) {
$comment_short = chop_str($comment, 30, 5);
@@ -1124,34 +2605,29 @@ sub git_tags_body {
$alternate ^= 1;
print "<td><i>$tag{'age'}</i></td>\n" .
"<td>" .
- $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$tag{'reftype'};h=$tag{'refid'}"),
- -class => "list"}, "<b>" . esc_html($tag{'name'}) . "</b>") .
+ $cgi->a({-href => href(action=>$tag{'reftype'}, hash=>$tag{'refid'}),
+ -class => "list name"}, esc_html($tag{'name'})) .
"</td>\n" .
"<td>";
if (defined $comment) {
- if (length($comment_short) < length($comment)) {
- print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tag;h=$tag{'id'}"),
- -class => "list", -title => $comment}, $comment_short);
- } else {
- print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tag;h=$tag{'id'}"),
- -class => "list"}, $comment);
- }
+ print format_subject_html($comment, $comment_short,
+ href(action=>"tag", hash=>$tag{'id'}));
}
print "</td>\n" .
"<td class=\"selflink\">";
if ($tag{'type'} eq "tag") {
- print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tag;h=$tag{'id'}")}, "tag");
+ print $cgi->a({-href => href(action=>"tag", hash=>$tag{'id'})}, "tag");
} else {
print "&nbsp;";
}
print "</td>\n" .
"<td class=\"link\">" . " | " .
- $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$tag{'reftype'};h=$tag{'refid'}")}, $tag{'reftype'});
+ $cgi->a({-href => href(action=>$tag{'reftype'}, hash=>$tag{'refid'})}, $tag{'reftype'});
if ($tag{'reftype'} eq "commit") {
- print " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog;h=$tag{'name'}")}, "shortlog") .
- " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log;h=$tag{'refid'}")}, "log");
+ print " | " . $cgi->a({-href => href(action=>"shortlog", hash=>$tag{'name'})}, "shortlog") .
+ " | " . $cgi->a({-href => href(action=>"log", hash=>$tag{'name'})}, "log");
} elsif ($tag{'reftype'} eq "blob") {
- print " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob_plain;h=$tag{'refid'}")}, "raw");
+ print " | " . $cgi->a({-href => href(action=>"blob_plain", hash=>$tag{'refid'})}, "raw");
}
print "</td>\n" .
"</tr>";
@@ -1166,30 +2642,31 @@ sub git_tags_body {
sub git_heads_body {
# uses global variable $project
- my ($taglist, $head, $from, $to, $extra) = @_;
+ my ($headlist, $head, $from, $to, $extra) = @_;
$from = 0 unless defined $from;
- $to = $#{$taglist} if (!defined $to || $#{$taglist} < $to);
+ $to = $#{$headlist} if (!defined $to || $#{$headlist} < $to);
print "<table class=\"heads\" cellspacing=\"0\">\n";
- my $alternate = 0;
+ my $alternate = 1;
for (my $i = $from; $i <= $to; $i++) {
- my $entry = $taglist->[$i];
- my %tag = %$entry;
- my $curr = $tag{'id'} eq $head;
+ my $entry = $headlist->[$i];
+ my %ref = %$entry;
+ my $curr = $ref{'id'} eq $head;
if ($alternate) {
print "<tr class=\"dark\">\n";
} else {
print "<tr class=\"light\">\n";
}
$alternate ^= 1;
- print "<td><i>$tag{'age'}</i></td>\n" .
- ($tag{'id'} eq $head ? "<td class=\"current_head\">" : "<td>") .
- $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog;h=$tag{'name'}"),
- -class => "list"}, "<b>" . esc_html($tag{'name'}) . "</b>") .
+ print "<td><i>$ref{'age'}</i></td>\n" .
+ ($curr ? "<td class=\"current_head\">" : "<td>") .
+ $cgi->a({-href => href(action=>"shortlog", hash=>$ref{'name'}),
+ -class => "list name"},esc_html($ref{'name'})) .
"</td>\n" .
"<td class=\"link\">" .
- $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog;h=$tag{'name'}")}, "shortlog") . " | " .
- $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log;h=$tag{'name'}")}, "log") .
+ $cgi->a({-href => href(action=>"shortlog", hash=>$ref{'name'})}, "shortlog") . " | " .
+ $cgi->a({-href => href(action=>"log", hash=>$ref{'name'})}, "log") . " | " .
+ $cgi->a({-href => href(action=>"tree", hash=>$ref{'name'}, hash_base=>$ref{'name'})}, "tree") .
"</td>\n" .
"</tr>";
}
@@ -1201,77 +2678,6 @@ sub git_heads_body {
print "</table>\n";
}
-## ----------------------------------------------------------------------
-## functions printing large fragments, format as one of arguments
-
-sub git_diff_print {
- my $from = shift;
- my $from_name = shift;
- my $to = shift;
- my $to_name = shift;
- my $format = shift || "html";
-
- my $from_tmp = "/dev/null";
- my $to_tmp = "/dev/null";
- my $pid = $$;
-
- # create tmp from-file
- if (defined $from) {
- $from_tmp = "$git_temp/gitweb_" . $$ . "_from";
- open my $fd2, "> $from_tmp";
- open my $fd, "-|", $GIT, "cat-file", "blob", $from;
- my @file = <$fd>;
- print $fd2 @file;
- close $fd2;
- close $fd;
- }
-
- # create tmp to-file
- if (defined $to) {
- $to_tmp = "$git_temp/gitweb_" . $$ . "_to";
- open my $fd2, "> $to_tmp";
- open my $fd, "-|", $GIT, "cat-file", "blob", $to;
- my @file = <$fd>;
- print $fd2 @file;
- close $fd2;
- close $fd;
- }
-
- open my $fd, "-|", "/usr/bin/diff -u -p -L \'$from_name\' -L \'$to_name\' $from_tmp $to_tmp";
- if ($format eq "plain") {
- undef $/;
- print <$fd>;
- $/ = "\n";
- } else {
- while (my $line = <$fd>) {
- chomp $line;
- my $char = substr($line, 0, 1);
- my $diff_class = "";
- if ($char eq '+') {
- $diff_class = " add";
- } elsif ($char eq "-") {
- $diff_class = " rem";
- } elsif ($char eq "@") {
- $diff_class = " chunk_header";
- } elsif ($char eq "\\") {
- # skip errors
- next;
- }
- $line = untabify($line);
- print "<div class=\"diff$diff_class\">" . esc_html($line) . "</div>\n";
- }
- }
- close $fd;
-
- if (defined $from) {
- unlink($from_tmp);
- }
- if (defined $to) {
- unlink($to_tmp);
- }
-}
-
-
## ======================================================================
## ======================================================================
## actions
@@ -1282,31 +2688,10 @@ sub git_project_list {
die_error(undef, "Unknown order parameter");
}
- my @list = git_read_projects();
- my @projects;
+ my @list = git_get_projects_list();
if (!@list) {
die_error(undef, "No projects found");
}
- foreach my $pr (@list) {
- my $head = git_read_head($pr->{'path'});
- if (!defined $head) {
- next;
- }
- $ENV{'GIT_DIR'} = "$projectroot/$pr->{'path'}";
- my %co = git_read_commit($head);
- if (!%co) {
- next;
- }
- $pr->{'commit'} = \%co;
- if (!defined $pr->{'descr'}) {
- my $descr = git_read_description($pr->{'path'}) || "";
- $pr->{'descr'} = chop_str($descr, 25, 5);
- }
- if (!defined $pr->{'owner'}) {
- $pr->{'owner'} = get_file_owner("$projectroot/$pr->{'path'}") || "";
- }
- push @projects, $pr;
- }
git_header_html();
if (-f $home_text) {
@@ -1316,156 +2701,157 @@ sub git_project_list {
close $fd;
print "</div>\n";
}
- print "<table class=\"project_list\">\n" .
- "<tr>\n";
- $order ||= "project";
- if ($order eq "project") {
- @projects = sort {$a->{'path'} cmp $b->{'path'}} @projects;
- print "<th>Project</th>\n";
- } else {
- print "<th>" .
- $cgi->a({-href => "$my_uri?" . esc_param("o=project"),
- -class => "header"}, "Project") .
- "</th>\n";
- }
- if ($order eq "descr") {
- @projects = sort {$a->{'descr'} cmp $b->{'descr'}} @projects;
- print "<th>Description</th>\n";
- } else {
- print "<th>" .
- $cgi->a({-href => "$my_uri?" . esc_param("o=descr"),
- -class => "header"}, "Description") .
- "</th>\n";
- }
- if ($order eq "owner") {
- @projects = sort {$a->{'owner'} cmp $b->{'owner'}} @projects;
- print "<th>Owner</th>\n";
- } else {
- print "<th>" .
- $cgi->a({-href => "$my_uri?" . esc_param("o=owner"),
- -class => "header"}, "Owner") .
- "</th>\n";
- }
- if ($order eq "age") {
- @projects = sort {$a->{'commit'}{'age'} <=> $b->{'commit'}{'age'}} @projects;
- print "<th>Last Change</th>\n";
- } else {
- print "<th>" .
- $cgi->a({-href => "$my_uri?" . esc_param("o=age"),
- -class => "header"}, "Last Change") .
- "</th>\n";
+ git_project_list_body(\@list, $order);
+ git_footer_html();
+}
+
+sub git_forks {
+ my $order = $cgi->param('o');
+ if (defined $order && $order !~ m/project|descr|owner|age/) {
+ die_error(undef, "Unknown order parameter");
}
- print "<th></th>\n" .
- "</tr>\n";
- my $alternate = 0;
- foreach my $pr (@projects) {
- if ($alternate) {
- print "<tr class=\"dark\">\n";
- } else {
- print "<tr class=\"light\">\n";
- }
- $alternate ^= 1;
- print "<td>" . $cgi->a({-href => "$my_uri?" . esc_param("p=$pr->{'path'};a=summary"),
- -class => "list"}, esc_html($pr->{'path'})) . "</td>\n" .
- "<td>" . esc_html($pr->{'descr'}) . "</td>\n" .
- "<td><i>" . chop_str($pr->{'owner'}, 15) . "</i></td>\n";
- print "<td class=\"". age_class($pr->{'commit'}{'age'}) . "\">" .
- $pr->{'commit'}{'age_string'} . "</td>\n" .
- "<td class=\"link\">" .
- $cgi->a({-href => "$my_uri?" . esc_param("p=$pr->{'path'};a=summary")}, "summary") . " | " .
- $cgi->a({-href => "$my_uri?" . esc_param("p=$pr->{'path'};a=shortlog")}, "shortlog") . " | " .
- $cgi->a({-href => "$my_uri?" . esc_param("p=$pr->{'path'};a=log")}, "log") .
- "</td>\n" .
- "</tr>\n";
+
+ my @list = git_get_projects_list($project);
+ if (!@list) {
+ die_error(undef, "No forks found");
}
- print "</table>\n";
+
+ git_header_html();
+ git_print_page_nav('','');
+ git_print_header_div('summary', "$project forks");
+ git_project_list_body(\@list, $order);
git_footer_html();
}
-sub git_summary {
- my $descr = git_read_description($project) || "none";
- my $head = git_read_head($project);
- my %co = git_read_commit($head);
- my %cd = date_str($co{'committer_epoch'}, $co{'committer_tz'});
+sub git_project_index {
+ my @projects = git_get_projects_list($project);
- my $owner;
- if (-f $projects_list) {
- open (my $fd , $projects_list);
- while (my $line = <$fd>) {
- chomp $line;
- my ($pr, $ow) = split ' ', $line;
- $pr = unescape($pr);
- $ow = unescape($ow);
- if ($pr eq $project) {
- $owner = decode("utf8", $ow, Encode::FB_DEFAULT);
- last;
- }
+ print $cgi->header(
+ -type => 'text/plain',
+ -charset => 'utf-8',
+ -content_disposition => 'inline; filename="index.aux"');
+
+ foreach my $pr (@projects) {
+ if (!exists $pr->{'owner'}) {
+ $pr->{'owner'} = get_file_owner("$projectroot/$project");
}
- close $fd;
+
+ my ($path, $owner) = ($pr->{'path'}, $pr->{'owner'});
+ # quote as in CGI::Util::encode, but keep the slash, and use '+' for ' '
+ $path =~ s/([^a-zA-Z0-9_.\-\/ ])/sprintf("%%%02X", ord($1))/eg;
+ $owner =~ s/([^a-zA-Z0-9_.\-\/ ])/sprintf("%%%02X", ord($1))/eg;
+ $path =~ s/ /\+/g;
+ $owner =~ s/ /\+/g;
+
+ print "$path $owner\n";
}
- if (!defined $owner) {
- $owner = get_file_owner("$projectroot/$project");
+}
+
+sub git_summary {
+ my $descr = git_get_project_description($project) || "none";
+ my $head = git_get_head_hash($project);
+ my %co = parse_commit($head);
+ my %cd = parse_date($co{'committer_epoch'}, $co{'committer_tz'});
+
+ my $owner = git_get_project_owner($project);
+
+ my $refs = git_get_references();
+ my @taglist = git_get_tags_list(15);
+ my @headlist = git_get_heads_list(15);
+ my @forklist;
+ my ($check_forks) = gitweb_check_feature('forks');
+
+ if ($check_forks) {
+ @forklist = git_get_projects_list($project);
}
- my $refs = read_info_ref();
git_header_html();
- git_page_nav('summary','', $head);
+ git_print_page_nav('summary','', $head);
print "<div class=\"title\">&nbsp;</div>\n";
print "<table cellspacing=\"0\">\n" .
"<tr><td>description</td><td>" . esc_html($descr) . "</td></tr>\n" .
"<tr><td>owner</td><td>$owner</td></tr>\n" .
- "<tr><td>last change</td><td>$cd{'rfc2822'}</td></tr>\n" .
- "</table>\n";
+ "<tr><td>last change</td><td>$cd{'rfc2822'}</td></tr>\n";
+ # use per project git URL list in $projectroot/$project/cloneurl
+ # or make project git URL from git base URL and project name
+ my $url_tag = "URL";
+ my @url_list = git_get_project_url_list($project);
+ @url_list = map { "$_/$project" } @git_base_url_list unless @url_list;
+ foreach my $git_url (@url_list) {
+ next unless $git_url;
+ print "<tr><td>$url_tag</td><td>$git_url</td></tr>\n";
+ $url_tag = "";
+ }
+ print "</table>\n";
- open my $fd, "-|", $GIT, "rev-list", "--max-count=17", git_read_head($project)
+ if (-s "$projectroot/$project/README.html") {
+ if (open my $fd, "$projectroot/$project/README.html") {
+ print "<div class=\"title\">readme</div>\n";
+ print $_ while (<$fd>);
+ close $fd;
+ }
+ }
+
+ open my $fd, "-|", git_cmd(), "rev-list", "--max-count=17",
+ git_get_head_hash($project), "--"
or die_error(undef, "Open git-rev-list failed");
my @revlist = map { chomp; $_ } <$fd>;
close $fd;
- git_header_div('shortlog');
+ git_print_header_div('shortlog');
git_shortlog_body(\@revlist, 0, 15, $refs,
- $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog")}, "..."));
+ $cgi->a({-href => href(action=>"shortlog")}, "..."));
- my $taglist = git_read_refs("refs/tags");
- if (defined @$taglist) {
- git_header_div('tags');
- git_tags_body($taglist, 0, 15,
- $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tags")}, "..."));
+ if (@taglist) {
+ git_print_header_div('tags');
+ git_tags_body(\@taglist, 0, 15,
+ $cgi->a({-href => href(action=>"tags")}, "..."));
}
- my $headlist = git_read_refs("refs/heads");
- if (defined @$headlist) {
- git_header_div('heads');
- git_heads_body($headlist, $head, 0, 15,
- $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=heads")}, "..."));
+ if (@headlist) {
+ git_print_header_div('heads');
+ git_heads_body(\@headlist, $head, 0, 15,
+ $cgi->a({-href => href(action=>"heads")}, "..."));
+ }
+
+ if (@forklist) {
+ git_print_header_div('forks');
+ git_project_list_body(\@forklist, undef, 0, 15,
+ $cgi->a({-href => href(action=>"forks")}, "..."),
+ 'noheader');
}
git_footer_html();
}
sub git_tag {
- my $head = git_read_head($project);
+ my $head = git_get_head_hash($project);
git_header_html();
- git_page_nav('','', $head,undef,$head);
- my %tag = git_read_tag($hash);
- git_header_div('commit', esc_html($tag{'name'}), $hash);
+ git_print_page_nav('','', $head,undef,$head);
+ my %tag = parse_tag($hash);
+ git_print_header_div('commit', esc_html($tag{'name'}), $hash);
print "<div class=\"title_text\">\n" .
"<table cellspacing=\"0\">\n" .
"<tr>\n" .
"<td>object</td>\n" .
- "<td>" . $cgi->a({-class => "list", -href => "$my_uri?" . esc_param("p=$project;a=$tag{'type'};h=$tag{'object'}")}, $tag{'object'}) . "</td>\n" .
- "<td class=\"link\">" . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$tag{'type'};h=$tag{'object'}")}, $tag{'type'}) . "</td>\n" .
+ "<td>" . $cgi->a({-class => "list", -href => href(action=>$tag{'type'}, hash=>$tag{'object'})},
+ $tag{'object'}) . "</td>\n" .
+ "<td class=\"link\">" . $cgi->a({-href => href(action=>$tag{'type'}, hash=>$tag{'object'})},
+ $tag{'type'}) . "</td>\n" .
"</tr>\n";
if (defined($tag{'author'})) {
- my %ad = date_str($tag{'epoch'}, $tag{'tz'});
+ my %ad = parse_date($tag{'epoch'}, $tag{'tz'});
print "<tr><td>author</td><td>" . esc_html($tag{'author'}) . "</td></tr>\n";
- print "<tr><td></td><td>" . $ad{'rfc2822'} . sprintf(" (%02d:%02d %s)", $ad{'hour_local'}, $ad{'minute_local'}, $ad{'tz_local'}) . "</td></tr>\n";
+ print "<tr><td></td><td>" . $ad{'rfc2822'} .
+ sprintf(" (%02d:%02d %s)", $ad{'hour_local'}, $ad{'minute_local'}, $ad{'tz_local'}) .
+ "</td></tr>\n";
}
print "</table>\n\n" .
"</div>\n";
print "<div class=\"page_body\">";
my $comment = $tag{'comment'};
foreach my $line (@$comment) {
+ chomp($line);
print esc_html($line) . "<br/>\n";
}
print "</div>\n";
@@ -1475,11 +2861,15 @@ sub git_tag {
sub git_blame2 {
my $fd;
my $ftype;
- die_error(undef, "Permission denied") if (!git_get_project_config_bool ('blame'));
+
+ my ($have_blame) = gitweb_check_feature('blame');
+ if (!$have_blame) {
+ die_error('403 Permission denied', "Permission denied");
+ }
die_error('404 Not Found', "File name not defined") if (!$file_name);
- $hash_base ||= git_read_head($project);
+ $hash_base ||= git_get_head_hash($project);
die_error(undef, "Couldn't find base commit") unless ($hash_base);
- my %co = git_read_commit($hash_base)
+ my %co = parse_commit($hash_base)
or die_error(undef, "Reading commit failed");
if (!defined $hash) {
$hash = git_get_hash_by_path($hash_base, $file_name, "blob")
@@ -1489,69 +2879,119 @@ sub git_blame2 {
if ($ftype !~ "blob") {
die_error("400 Bad Request", "Object is not a blob");
}
- open ($fd, "-|", $GIT, "blame", '-l', $file_name, $hash_base)
+ open ($fd, "-|", git_cmd(), "blame", '-p', '--',
+ $file_name, $hash_base)
or die_error(undef, "Open git-blame failed");
git_header_html();
my $formats_nav =
- $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$hash;hb=$hash_base;f=$file_name")}, "blob") .
- " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blame;f=$file_name")}, "head");
- git_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav);
- git_header_div('commit', esc_html($co{'title'}), $hash_base);
- git_print_page_path($file_name, $ftype);
+ $cgi->a({-href => href(action=>"blob", hash=>$hash, hash_base=>$hash_base, file_name=>$file_name)},
+ "blob") .
+ " | " .
+ $cgi->a({-href => href(action=>"history", hash=>$hash, hash_base=>$hash_base, file_name=>$file_name)},
+ "history") .
+ " | " .
+ $cgi->a({-href => href(action=>"blame", file_name=>$file_name)},
+ "HEAD");
+ git_print_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav);
+ git_print_header_div('commit', esc_html($co{'title'}), $hash_base);
+ git_print_page_path($file_name, $ftype, $hash_base);
my @rev_color = (qw(light2 dark2));
my $num_colors = scalar(@rev_color);
my $current_color = 0;
my $last_rev;
- print "<div class=\"page_body\">\n";
- print "<table class=\"blame\">\n";
- print "<tr><th>Commit</th><th>Line</th><th>Data</th></tr>\n";
- while (<$fd>) {
- /^([0-9a-fA-F]{40}).*?(\d+)\)\s{1}(\s*.*)/;
- my $full_rev = $1;
+ print <<HTML;
+<div class="page_body">
+<table class="blame">
+<tr><th>Commit</th><th>Line</th><th>Data</th></tr>
+HTML
+ my %metainfo = ();
+ while (1) {
+ $_ = <$fd>;
+ last unless defined $_;
+ my ($full_rev, $orig_lineno, $lineno, $group_size) =
+ /^([0-9a-f]{40}) (\d+) (\d+)(?: (\d+))?$/;
+ if (!exists $metainfo{$full_rev}) {
+ $metainfo{$full_rev} = {};
+ }
+ my $meta = $metainfo{$full_rev};
+ while (<$fd>) {
+ last if (s/^\t//);
+ if (/^(\S+) (.*)$/) {
+ $meta->{$1} = $2;
+ }
+ }
+ my $data = $_;
+ chomp($data);
my $rev = substr($full_rev, 0, 8);
- my $lineno = $2;
- my $data = $3;
-
- if (!defined $last_rev) {
- $last_rev = $full_rev;
- } elsif ($last_rev ne $full_rev) {
- $last_rev = $full_rev;
+ my $author = $meta->{'author'};
+ my %date = parse_date($meta->{'author-time'},
+ $meta->{'author-tz'});
+ my $date = $date{'iso-tz'};
+ if ($group_size) {
$current_color = ++$current_color % $num_colors;
}
print "<tr class=\"$rev_color[$current_color]\">\n";
- print "<td class=\"sha1\">" .
- $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$full_rev;f=$file_name")}, esc_html($rev)) . "</td>\n";
- print "<td class=\"linenr\"><a id=\"l$lineno\" href=\"#l$lineno\" class=\"linenr\">" . esc_html($lineno) . "</a></td>\n";
+ if ($group_size) {
+ print "<td class=\"sha1\"";
+ print " title=\"". esc_html($author) . ", $date\"";
+ print " rowspan=\"$group_size\"" if ($group_size > 1);
+ print ">";
+ print $cgi->a({-href => href(action=>"commit",
+ hash=>$full_rev,
+ file_name=>$file_name)},
+ esc_html($rev));
+ print "</td>\n";
+ }
+ my $blamed = href(action => 'blame',
+ file_name => $meta->{'filename'},
+ hash_base => $full_rev);
+ print "<td class=\"linenr\">";
+ print $cgi->a({ -href => "$blamed#l$orig_lineno",
+ -id => "l$lineno",
+ -class => "linenr" },
+ esc_html($lineno));
+ print "</td>";
print "<td class=\"pre\">" . esc_html($data) . "</td>\n";
print "</tr>\n";
}
print "</table>\n";
print "</div>";
- close $fd or print "Reading blob failed\n";
+ close $fd
+ or print "Reading blob failed\n";
git_footer_html();
}
sub git_blame {
my $fd;
- die_error('403 Permission denied', "Permission denied") if (!git_get_project_config_bool ('blame'));
+
+ my ($have_blame) = gitweb_check_feature('blame');
+ if (!$have_blame) {
+ die_error('403 Permission denied', "Permission denied");
+ }
die_error('404 Not Found', "File name not defined") if (!$file_name);
- $hash_base ||= git_read_head($project);
+ $hash_base ||= git_get_head_hash($project);
die_error(undef, "Couldn't find base commit") unless ($hash_base);
- my %co = git_read_commit($hash_base)
+ my %co = parse_commit($hash_base)
or die_error(undef, "Reading commit failed");
if (!defined $hash) {
$hash = git_get_hash_by_path($hash_base, $file_name, "blob")
or die_error(undef, "Error lookup file");
}
- open ($fd, "-|", $GIT, "annotate", '-l', '-t', '-r', $file_name, $hash_base)
+ open ($fd, "-|", git_cmd(), "annotate", '-l', '-t', '-r', $file_name, $hash_base)
or die_error(undef, "Open git-annotate failed");
git_header_html();
my $formats_nav =
- $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$hash;hb=$hash_base;f=$file_name")}, "blob") .
- " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blame;f=$file_name")}, "head");
- git_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav);
- git_header_div('commit', esc_html($co{'title'}), $hash_base);
- git_print_page_path($file_name, 'blob');
+ $cgi->a({-href => href(action=>"blob", hash=>$hash, hash_base=>$hash_base, file_name=>$file_name)},
+ "blob") .
+ " | " .
+ $cgi->a({-href => href(action=>"history", hash=>$hash, hash_base=>$hash_base, file_name=>$file_name)},
+ "history") .
+ " | " .
+ $cgi->a({-href => href(action=>"blame", file_name=>$file_name)},
+ "HEAD");
+ git_print_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav);
+ git_print_header_div('commit', esc_html($co{'title'}), $hash_base);
+ git_print_page_path($file_name, 'blob', $hash_base);
print "<div class=\"page_body\">\n";
print <<HTML;
<table class="blame">
@@ -1580,7 +3020,7 @@ HTML
chomp $line;
$line_class_num = ($line_class_num + 1) % $line_class_len;
- if ($line =~ m/^([0-9a-fA-F]{40})\t\(\s*([^\t]+)\t(\d+) \+\d\d\d\d\t(\d+)\)(.*)$/) {
+ if ($line =~ m/^([0-9a-fA-F]{40})\t\(\s*([^\t]+)\t(\d+) [+-]\d\d\d\d\t(\d+)\)(.*)$/) {
$long_rev = $1;
$author = $2;
$time = $3;
@@ -1603,7 +3043,7 @@ HTML
print <<HTML;
<tr class="$line_class[$line_class_num]">
- <td class="sha1"><a href="$my_uri?${\esc_param ("p=$project;a=commit;h=$long_rev")}" class="text">$short_rev..</a></td>
+ <td class="sha1"><a href="${\href (action=>"commit", hash=>$long_rev)}" class="text">$short_rev..</a></td>
<td class="$age_class">$age_str</td>
<td>$author</td>
<td class="linenr"><a id="$lineno" href="#$lineno" class="linenr">$lineno</a></td>
@@ -1612,52 +3052,59 @@ HTML
HTML
} # while (my $line = <$fd>)
print "</table>\n\n";
- close $fd or print "Reading blob failed.\n";
+ close $fd
+ or print "Reading blob failed.\n";
print "</div>";
git_footer_html();
}
sub git_tags {
- my $head = git_read_head($project);
+ my $head = git_get_head_hash($project);
git_header_html();
- git_page_nav('','', $head,undef,$head);
- git_header_div('summary', $project);
+ git_print_page_nav('','', $head,undef,$head);
+ git_print_header_div('summary', $project);
- my $taglist = git_read_refs("refs/tags");
- if (defined @$taglist) {
- git_tags_body($taglist);
+ my @tagslist = git_get_tags_list();
+ if (@tagslist) {
+ git_tags_body(\@tagslist);
}
git_footer_html();
}
sub git_heads {
- my $head = git_read_head($project);
+ my $head = git_get_head_hash($project);
git_header_html();
- git_page_nav('','', $head,undef,$head);
- git_header_div('summary', $project);
+ git_print_page_nav('','', $head,undef,$head);
+ git_print_header_div('summary', $project);
- my $taglist = git_read_refs("refs/heads");
- if (defined @$taglist) {
- git_heads_body($taglist, $head);
+ my @headslist = git_get_heads_list();
+ if (@headslist) {
+ git_heads_body(\@headslist, $head);
}
git_footer_html();
}
sub git_blob_plain {
+ my $expires;
+
if (!defined $hash) {
if (defined $file_name) {
- my $base = $hash_base || git_read_head($project);
+ my $base = $hash_base || git_get_head_hash($project);
$hash = git_get_hash_by_path($base, $file_name, "blob")
or die_error(undef, "Error lookup file");
} else {
die_error(undef, "No file name defined");
}
+ } elsif ($hash =~ m/^[0-9a-fA-F]{40}$/) {
+ # blobs defined by non-textual hash id's can be cached
+ $expires = "+1d";
}
+
my $type = shift;
- open my $fd, "-|", $GIT, "cat-file", "blob", $hash
+ open my $fd, "-|", git_cmd(), "cat-file", "blob", $hash
or die_error(undef, "Couldn't cat $file_name, $hash");
- $type ||= git_blob_plain_mimetype($fd, $file_name);
+ $type ||= blob_mimetype($fd, $file_name);
# save as filename, even when no $file_name is given
my $save_as = "$hash";
@@ -1667,7 +3114,10 @@ sub git_blob_plain {
$save_as .= '.txt';
}
- print $cgi->header(-type => "$type", '-content-disposition' => "inline; filename=\"$save_as\"");
+ print $cgi->header(
+ -type => "$type",
+ -expires=>$expires,
+ -content_disposition => 'inline; filename="' . "$save_as" . '"');
undef $/;
binmode STDOUT, ':raw';
print <$fd>;
@@ -1677,132 +3127,175 @@ sub git_blob_plain {
}
sub git_blob {
+ my $expires;
+
if (!defined $hash) {
if (defined $file_name) {
- my $base = $hash_base || git_read_head($project);
+ my $base = $hash_base || git_get_head_hash($project);
$hash = git_get_hash_by_path($base, $file_name, "blob")
or die_error(undef, "Error lookup file");
} else {
die_error(undef, "No file name defined");
}
+ } elsif ($hash =~ m/^[0-9a-fA-F]{40}$/) {
+ # blobs defined by non-textual hash id's can be cached
+ $expires = "+1d";
}
- my $have_blame = git_get_project_config_bool ('blame');
- open my $fd, "-|", $GIT, "cat-file", "blob", $hash
+
+ my ($have_blame) = gitweb_check_feature('blame');
+ open my $fd, "-|", git_cmd(), "cat-file", "blob", $hash
or die_error(undef, "Couldn't cat $file_name, $hash");
- my $mimetype = git_blob_plain_mimetype($fd, $file_name);
+ my $mimetype = blob_mimetype($fd, $file_name);
if ($mimetype !~ m/^text\//) {
close $fd;
return git_blob_plain($mimetype);
}
- git_header_html();
+ git_header_html(undef, $expires);
my $formats_nav = '';
- if (defined $hash_base && (my %co = git_read_commit($hash_base))) {
+ if (defined $hash_base && (my %co = parse_commit($hash_base))) {
if (defined $file_name) {
if ($have_blame) {
- $formats_nav .= $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blame;h=$hash;hb=$hash_base;f=$file_name")}, "blame") . " | ";
+ $formats_nav .=
+ $cgi->a({-href => href(action=>"blame", hash_base=>$hash_base,
+ hash=>$hash, file_name=>$file_name)},
+ "blame") .
+ " | ";
}
$formats_nav .=
- $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob_plain;h=$hash;f=$file_name")}, "plain") .
- " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;hb=HEAD;f=$file_name")}, "head");
+ $cgi->a({-href => href(action=>"history", hash_base=>$hash_base,
+ hash=>$hash, file_name=>$file_name)},
+ "history") .
+ " | " .
+ $cgi->a({-href => href(action=>"blob_plain",
+ hash=>$hash, file_name=>$file_name)},
+ "raw") .
+ " | " .
+ $cgi->a({-href => href(action=>"blob",
+ hash_base=>"HEAD", file_name=>$file_name)},
+ "HEAD");
} else {
- $formats_nav .= $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob_plain;h=$hash")}, "plain");
+ $formats_nav .=
+ $cgi->a({-href => href(action=>"blob_plain", hash=>$hash)}, "raw");
}
- git_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav);
- git_header_div('commit', esc_html($co{'title'}), $hash_base);
+ git_print_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav);
+ git_print_header_div('commit', esc_html($co{'title'}), $hash_base);
} else {
print "<div class=\"page_nav\">\n" .
"<br/><br/></div>\n" .
"<div class=\"title\">$hash</div>\n";
}
- git_print_page_path($file_name, "blob");
+ git_print_page_path($file_name, "blob", $hash_base);
print "<div class=\"page_body\">\n";
my $nr;
while (my $line = <$fd>) {
chomp $line;
$nr++;
$line = untabify($line);
- printf "<div class=\"pre\"><a id=\"l%i\" href=\"#l%i\" class=\"linenr\">%4i</a> %s</div>\n", $nr, $nr, $nr, esc_html($line);
+ printf "<div class=\"pre\"><a id=\"l%i\" href=\"#l%i\" class=\"linenr\">%4i</a> %s</div>\n",
+ $nr, $nr, $nr, esc_html($line, -nbsp=>1);
}
- close $fd or print "Reading blob failed.\n";
+ close $fd
+ or print "Reading blob failed.\n";
print "</div>";
git_footer_html();
}
sub git_tree {
+ my $have_snapshot = gitweb_have_snapshot();
+
+ if (!defined $hash_base) {
+ $hash_base = "HEAD";
+ }
if (!defined $hash) {
- $hash = git_read_head($project);
if (defined $file_name) {
- my $base = $hash_base || $hash;
- $hash = git_get_hash_by_path($base, $file_name, "tree");
- }
- if (!defined $hash_base) {
- $hash_base = $hash;
+ $hash = git_get_hash_by_path($hash_base, $file_name, "tree");
+ } else {
+ $hash = $hash_base;
}
}
$/ = "\0";
- open my $fd, "-|", $GIT, "ls-tree", '-z', $hash
+ open my $fd, "-|", git_cmd(), "ls-tree", '-z', $hash
or die_error(undef, "Open git-ls-tree failed");
my @entries = map { chomp; $_ } <$fd>;
close $fd or die_error(undef, "Reading tree failed");
$/ = "\n";
- my $refs = read_info_ref();
- my $ref = git_get_referencing($refs, $hash_base);
+ my $refs = git_get_references();
+ my $ref = format_ref_marker($refs, $hash_base);
git_header_html();
- my $base_key = "";
- my $base = "";
- my $have_blame = git_get_project_config_bool ('blame');
- if (defined $hash_base && (my %co = git_read_commit($hash_base))) {
- $base_key = ";hb=$hash_base";
- git_page_nav('tree','', $hash_base);
- git_header_div('commit', esc_html($co{'title'}) . $ref, $hash_base);
+ my $basedir = '';
+ my ($have_blame) = gitweb_check_feature('blame');
+ if (defined $hash_base && (my %co = parse_commit($hash_base))) {
+ my @views_nav = ();
+ if (defined $file_name) {
+ push @views_nav,
+ $cgi->a({-href => href(action=>"history", hash_base=>$hash_base,
+ hash=>$hash, file_name=>$file_name)},
+ "history"),
+ $cgi->a({-href => href(action=>"tree",
+ hash_base=>"HEAD", file_name=>$file_name)},
+ "HEAD"),
+ }
+ if ($have_snapshot) {
+ # FIXME: Should be available when we have no hash base as well.
+ push @views_nav,
+ $cgi->a({-href => href(action=>"snapshot", hash=>$hash)},
+ "snapshot");
+ }
+ git_print_page_nav('tree','', $hash_base, undef, undef, join(' | ', @views_nav));
+ git_print_header_div('commit', esc_html($co{'title'}) . $ref, $hash_base);
} else {
+ undef $hash_base;
print "<div class=\"page_nav\">\n";
print "<br/><br/></div>\n";
print "<div class=\"title\">$hash</div>\n";
}
if (defined $file_name) {
- $base = esc_html("$file_name/");
+ $basedir = $file_name;
+ if ($basedir ne '' && substr($basedir, -1) ne '/') {
+ $basedir .= '/';
+ }
}
- git_print_page_path($file_name, 'tree');
+ git_print_page_path($file_name, 'tree', $hash_base);
print "<div class=\"page_body\">\n";
print "<table cellspacing=\"0\">\n";
- my $alternate = 0;
- foreach my $line (@entries) {
- #'100644 blob 0fa3f3a66fb6a137f6ec2c19351ed4d807070ffa panic.c'
- $line =~ m/^([0-9]+) (.+) ([0-9a-fA-F]{40})\t(.+)$/;
- my $t_mode = $1;
- my $t_type = $2;
- my $t_hash = $3;
- my $t_name = validate_input($4);
+ my $alternate = 1;
+ # '..' (top directory) link if possible
+ if (defined $hash_base &&
+ defined $file_name && $file_name =~ m![^/]+$!) {
if ($alternate) {
print "<tr class=\"dark\">\n";
} else {
print "<tr class=\"light\">\n";
}
$alternate ^= 1;
- print "<td class=\"mode\">" . mode_str($t_mode) . "</td>\n";
- if ($t_type eq "blob") {
- print "<td class=\"list\">" .
- $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$t_hash$base_key;f=$base$t_name"), -class => "list"}, esc_html($t_name)) .
- "</td>\n" .
- "<td class=\"link\">" .
- $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$t_hash$base_key;f=$base$t_name")}, "blob");
- if ($have_blame) {
- print " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blame;h=$t_hash$base_key;f=$base$t_name")}, "blame");
- }
- print " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=history;h=$t_hash;hb=$hash_base;f=$base$t_name")}, "history") .
- " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob_plain;h=$t_hash;f=$base$t_name")}, "raw") .
- "</td>\n";
- } elsif ($t_type eq "tree") {
- print "<td class=\"list\">" .
- $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;h=$t_hash$base_key;f=$base$t_name")}, esc_html($t_name)) .
- "</td>\n" .
- "<td class=\"link\">" .
- $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;h=$t_hash$base_key;f=$base$t_name")}, "tree") .
- " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=history;hb=$hash_base;f=$base$t_name")}, "history") .
- "</td>\n";
+
+ my $up = $file_name;
+ $up =~ s!/?[^/]+$!!;
+ undef $up unless $up;
+ # based on git_print_tree_entry
+ print '<td class="mode">' . mode_str('040000') . "</td>\n";
+ print '<td class="list">';
+ print $cgi->a({-href => href(action=>"tree", hash_base=>$hash_base,
+ file_name=>$up)},
+ "..");
+ print "</td>\n";
+ print "<td class=\"link\"></td>\n";
+
+ print "</tr>\n";
+ }
+ foreach my $line (@entries) {
+ my %t = parse_ls_tree_line($line, -z => 1);
+
+ if ($alternate) {
+ print "<tr class=\"dark\">\n";
+ } else {
+ print "<tr class=\"light\">\n";
}
+ $alternate ^= 1;
+
+ git_print_tree_entry(\%t, $basedir, $hash_base, $have_blame);
+
print "</tr>\n";
}
print "</table>\n" .
@@ -1810,89 +3303,108 @@ sub git_tree {
git_footer_html();
}
+sub git_snapshot {
+ my ($ctype, $suffix, $command) = gitweb_check_feature('snapshot');
+ my $have_snapshot = (defined $ctype && defined $suffix);
+ if (!$have_snapshot) {
+ die_error('403 Permission denied', "Permission denied");
+ }
+
+ if (!defined $hash) {
+ $hash = git_get_head_hash($project);
+ }
+
+ my $filename = basename($project) . "-$hash.tar.$suffix";
+
+ print $cgi->header(
+ -type => 'application/x-tar',
+ -content_encoding => $ctype,
+ -content_disposition => 'inline; filename="' . "$filename" . '"',
+ -status => '200 OK');
+
+ my $git = git_cmd_str();
+ my $name = $project;
+ $name =~ s/\047/\047\\\047\047/g;
+ open my $fd, "-|",
+ "$git archive --format=tar --prefix=\'$name\'/ $hash | $command"
+ or die_error(undef, "Execute git-tar-tree failed.");
+ binmode STDOUT, ':raw';
+ print <$fd>;
+ binmode STDOUT, ':utf8'; # as set at the beginning of gitweb.cgi
+ close $fd;
+
+}
+
sub git_log {
- my $head = git_read_head($project);
+ my $head = git_get_head_hash($project);
if (!defined $hash) {
$hash = $head;
}
if (!defined $page) {
$page = 0;
}
- my $refs = read_info_ref();
+ my $refs = git_get_references();
my $limit = sprintf("--max-count=%i", (100 * ($page+1)));
- open my $fd, "-|", $GIT, "rev-list", $limit, $hash
+ open my $fd, "-|", git_cmd(), "rev-list", $limit, $hash, "--"
or die_error(undef, "Open git-rev-list failed");
my @revlist = map { chomp; $_ } <$fd>;
close $fd;
- my $paging_nav = git_get_paging_nav('log', $hash, $head, $page, $#revlist);
+ my $paging_nav = format_paging_nav('log', $hash, $head, $page, $#revlist);
git_header_html();
- git_page_nav('log','', $hash,undef,undef, $paging_nav);
+ git_print_page_nav('log','', $hash,undef,undef, $paging_nav);
if (!@revlist) {
- my %co = git_read_commit($hash);
+ my %co = parse_commit($hash);
- git_header_div('summary', $project);
+ git_print_header_div('summary', $project);
print "<div class=\"page_body\"> Last change $co{'age_string'}.<br/><br/></div>\n";
}
for (my $i = ($page * 100); $i <= $#revlist; $i++) {
my $commit = $revlist[$i];
- my $ref = git_get_referencing($refs, $commit);
- my %co = git_read_commit($commit);
+ my $ref = format_ref_marker($refs, $commit);
+ my %co = parse_commit($commit);
next if !%co;
- my %ad = date_str($co{'author_epoch'});
- git_header_div('commit',
+ my %ad = parse_date($co{'author_epoch'});
+ git_print_header_div('commit',
"<span class=\"age\">$co{'age_string'}</span>" .
esc_html($co{'title'}) . $ref,
$commit);
print "<div class=\"title_text\">\n" .
"<div class=\"log_link\">\n" .
- $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$commit")}, "commit") .
- " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$commit")}, "commitdiff") .
+ $cgi->a({-href => href(action=>"commit", hash=>$commit)}, "commit") .
+ " | " .
+ $cgi->a({-href => href(action=>"commitdiff", hash=>$commit)}, "commitdiff") .
+ " | " .
+ $cgi->a({-href => href(action=>"tree", hash=>$commit, hash_base=>$commit)}, "tree") .
"<br/>\n" .
"</div>\n" .
"<i>" . esc_html($co{'author_name'}) . " [$ad{'rfc2822'}]</i><br/>\n" .
- "</div>\n" .
- "<div class=\"log_body\">\n";
- my $comment = $co{'comment'};
- my $empty = 0;
- foreach my $line (@$comment) {
- if ($line =~ m/^ *(signed[ \-]off[ \-]by[ :]|acked[ \-]by[ :]|cc[ :])/i) {
- next;
- }
- if ($line eq "") {
- if ($empty) {
- next;
- }
- $empty = 1;
- } else {
- $empty = 0;
- }
- print format_log_line_html($line) . "<br/>\n";
- }
- if (!$empty) {
- print "<br/>\n";
- }
+ "</div>\n";
+
+ print "<div class=\"log_body\">\n";
+ git_print_log($co{'comment'}, -final_empty_line=> 1);
print "</div>\n";
}
git_footer_html();
}
sub git_commit {
- my %co = git_read_commit($hash);
+ my %co = parse_commit($hash);
if (!%co) {
die_error(undef, "Unknown commit object");
}
- my %ad = date_str($co{'author_epoch'}, $co{'author_tz'});
- my %cd = date_str($co{'committer_epoch'}, $co{'committer_tz'});
+ my %ad = parse_date($co{'author_epoch'}, $co{'author_tz'});
+ my %cd = parse_date($co{'committer_epoch'}, $co{'committer_tz'});
my $parent = $co{'parent'};
if (!defined $parent) {
$parent = "--root";
}
- open my $fd, "-|", $GIT, "diff-tree", '-r', '-M', $parent, $hash
+ open my $fd, "-|", git_cmd(), "diff-tree", '-r', "--no-commit-id",
+ @diff_opts, $parent, $hash, "--"
or die_error(undef, "Open git-diff-tree failed");
my @difftree = map { chomp; $_ } <$fd>;
close $fd or die_error(undef, "Reading git-diff-tree failed");
@@ -1902,22 +3414,26 @@ sub git_commit {
if ($hash =~ m/^[0-9a-fA-F]{40}$/) {
$expires = "+1d";
}
- my $refs = read_info_ref();
- my $ref = git_get_referencing($refs, $co{'id'});
- my $formats_nav = '';
+ my $refs = git_get_references();
+ my $ref = format_ref_marker($refs, $co{'id'});
+
+ my $have_snapshot = gitweb_have_snapshot();
+
+ my @views_nav = ();
if (defined $file_name && defined $co{'parent'}) {
- my $parent = $co{'parent'};
- $formats_nav .= $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blame;hb=$parent;f=$file_name")}, "blame");
+ push @views_nav,
+ $cgi->a({-href => href(action=>"blame", hash_parent=>$parent, file_name=>$file_name)},
+ "blame");
}
git_header_html(undef, $expires);
- git_page_nav('commit', defined $co{'parent'} ? '' : 'commitdiff',
- $hash, $co{'tree'}, $hash,
- $formats_nav);
+ git_print_page_nav('commit', '',
+ $hash, $co{'tree'}, $hash,
+ join (' | ', @views_nav));
if (defined $co{'parent'}) {
- git_header_div('commitdiff', esc_html($co{'title'}) . $ref, $hash);
+ git_print_header_div('commitdiff', esc_html($co{'title'}) . $ref, $hash);
} else {
- git_header_div('tree', esc_html($co{'title'}) . $ref, $co{'tree'}, $hash);
+ git_print_header_div('tree', esc_html($co{'title'}) . $ref, $co{'tree'}, $hash);
}
print "<div class=\"title_text\">\n" .
"<table cellspacing=\"0\">\n";
@@ -1925,403 +3441,448 @@ sub git_commit {
"<tr>" .
"<td></td><td> $ad{'rfc2822'}";
if ($ad{'hour_local'} < 6) {
- printf(" (<span class=\"atnight\">%02d:%02d</span> %s)", $ad{'hour_local'}, $ad{'minute_local'}, $ad{'tz_local'});
+ printf(" (<span class=\"atnight\">%02d:%02d</span> %s)",
+ $ad{'hour_local'}, $ad{'minute_local'}, $ad{'tz_local'});
} else {
- printf(" (%02d:%02d %s)", $ad{'hour_local'}, $ad{'minute_local'}, $ad{'tz_local'});
+ printf(" (%02d:%02d %s)",
+ $ad{'hour_local'}, $ad{'minute_local'}, $ad{'tz_local'});
}
print "</td>" .
"</tr>\n";
print "<tr><td>committer</td><td>" . esc_html($co{'committer'}) . "</td></tr>\n";
- print "<tr><td></td><td> $cd{'rfc2822'}" . sprintf(" (%02d:%02d %s)", $cd{'hour_local'}, $cd{'minute_local'}, $cd{'tz_local'}) . "</td></tr>\n";
+ print "<tr><td></td><td> $cd{'rfc2822'}" .
+ sprintf(" (%02d:%02d %s)", $cd{'hour_local'}, $cd{'minute_local'}, $cd{'tz_local'}) .
+ "</td></tr>\n";
print "<tr><td>commit</td><td class=\"sha1\">$co{'id'}</td></tr>\n";
print "<tr>" .
"<td>tree</td>" .
"<td class=\"sha1\">" .
- $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;h=$co{'tree'};hb=$hash"), class => "list"}, $co{'tree'}) .
- "</td>" .
- "<td class=\"link\">" . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;h=$co{'tree'};hb=$hash")}, "tree") .
+ $cgi->a({-href => href(action=>"tree", hash=>$co{'tree'}, hash_base=>$hash),
+ class => "list"}, $co{'tree'}) .
"</td>" .
+ "<td class=\"link\">" .
+ $cgi->a({-href => href(action=>"tree", hash=>$co{'tree'}, hash_base=>$hash)},
+ "tree");
+ if ($have_snapshot) {
+ print " | " .
+ $cgi->a({-href => href(action=>"snapshot", hash=>$hash)}, "snapshot");
+ }
+ print "</td>" .
"</tr>\n";
my $parents = $co{'parents'};
foreach my $par (@$parents) {
print "<tr>" .
"<td>parent</td>" .
- "<td class=\"sha1\">" . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$par"), class => "list"}, $par) . "</td>" .
+ "<td class=\"sha1\">" .
+ $cgi->a({-href => href(action=>"commit", hash=>$par),
+ class => "list"}, $par) .
+ "</td>" .
"<td class=\"link\">" .
- $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$par")}, "commit") .
- " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$hash;hp=$par")}, "commitdiff") .
+ $cgi->a({-href => href(action=>"commit", hash=>$par)}, "commit") .
+ " | " .
+ $cgi->a({-href => href(action=>"commitdiff", hash=>$hash, hash_parent=>$par)}, "diff") .
"</td>" .
"</tr>\n";
}
print "</table>".
"</div>\n";
+
print "<div class=\"page_body\">\n";
- my $comment = $co{'comment'};
- my $empty = 0;
- my $signed = 0;
- foreach my $line (@$comment) {
- # print only one empty line
- if ($line eq "") {
- if ($empty || $signed) {
- next;
- }
- $empty = 1;
+ git_print_log($co{'comment'});
+ print "</div>\n";
+
+ git_difftree_body(\@difftree, $hash, $parent);
+
+ git_footer_html();
+}
+
+sub git_blobdiff {
+ my $format = shift || 'html';
+
+ my $fd;
+ my @difftree;
+ my %diffinfo;
+ my $expires;
+
+ # preparing $fd and %diffinfo for git_patchset_body
+ # new style URI
+ if (defined $hash_base && defined $hash_parent_base) {
+ if (defined $file_name) {
+ # read raw output
+ open $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts,
+ $hash_parent_base, $hash_base,
+ "--", $file_name
+ or die_error(undef, "Open git-diff-tree failed");
+ @difftree = map { chomp; $_ } <$fd>;
+ close $fd
+ or die_error(undef, "Reading git-diff-tree failed");
+ @difftree
+ or die_error('404 Not Found', "Blob diff not found");
+
+ } elsif (defined $hash &&
+ $hash =~ /[0-9a-fA-F]{40}/) {
+ # try to find filename from $hash
+
+ # read filtered raw output
+ open $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts,
+ $hash_parent_base, $hash_base, "--"
+ or die_error(undef, "Open git-diff-tree failed");
+ @difftree =
+ # ':100644 100644 03b21826... 3b93d5e7... M ls-files.c'
+ # $hash == to_id
+ grep { /^:[0-7]{6} [0-7]{6} [0-9a-fA-F]{40} $hash/ }
+ map { chomp; $_ } <$fd>;
+ close $fd
+ or die_error(undef, "Reading git-diff-tree failed");
+ @difftree
+ or die_error('404 Not Found', "Blob diff not found");
+
} else {
- $empty = 0;
+ die_error('404 Not Found', "Missing one of the blob diff parameters");
}
- if ($line =~ m/^ *(signed[ \-]off[ \-]by[ :]|acked[ \-]by[ :]|cc[ :])/i) {
- $signed = 1;
- print "<span class=\"signoff\">" . esc_html($line) . "</span><br/>\n";
- } else {
- $signed = 0;
- print format_log_line_html($line) . "<br/>\n";
+
+ if (@difftree > 1) {
+ die_error('404 Not Found', "Ambiguous blob diff specification");
}
+
+ %diffinfo = parse_difftree_raw_line($difftree[0]);
+ $file_parent ||= $diffinfo{'from_file'} || $file_name || $diffinfo{'file'};
+ $file_name ||= $diffinfo{'to_file'} || $diffinfo{'file'};
+
+ $hash_parent ||= $diffinfo{'from_id'};
+ $hash ||= $diffinfo{'to_id'};
+
+ # non-textual hash id's can be cached
+ if ($hash_base =~ m/^[0-9a-fA-F]{40}$/ &&
+ $hash_parent_base =~ m/^[0-9a-fA-F]{40}$/) {
+ $expires = '+1d';
+ }
+
+ # open patch output
+ open $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts,
+ '-p', $hash_parent_base, $hash_base,
+ "--", $file_name
+ or die_error(undef, "Open git-diff-tree failed");
}
- print "</div>\n";
- print "<div class=\"list_head\">\n";
- if ($#difftree > 10) {
- print(($#difftree + 1) . " files changed:\n");
- }
- print "</div>\n";
- print "<table class=\"diff_tree\">\n";
- my $alternate = 0;
- foreach my $line (@difftree) {
- # ':100644 100644 03b218260e99b78c6df0ed378e59ed9205ccc96d 3b93d5e7cc7f7dd4ebed13a5cc1a4ad976fc94d8 M ls-files.c'
- # ':100644 100644 7f9281985086971d3877aca27704f2aaf9c448ce bc190ebc71bbd923f2b728e505408f5e54bd073a M rev-tree.c'
- if ($line !~ m/^:([0-7]{6}) ([0-7]{6}) ([0-9a-fA-F]{40}) ([0-9a-fA-F]{40}) (.)([0-9]{0,3})\t(.*)$/) {
- next;
+
+ # old/legacy style URI
+ if (!%diffinfo && # if new style URI failed
+ defined $hash && defined $hash_parent) {
+ # fake git-diff-tree raw output
+ $diffinfo{'from_mode'} = $diffinfo{'to_mode'} = "blob";
+ $diffinfo{'from_id'} = $hash_parent;
+ $diffinfo{'to_id'} = $hash;
+ if (defined $file_name) {
+ if (defined $file_parent) {
+ $diffinfo{'status'} = '2';
+ $diffinfo{'from_file'} = $file_parent;
+ $diffinfo{'to_file'} = $file_name;
+ } else { # assume not renamed
+ $diffinfo{'status'} = '1';
+ $diffinfo{'from_file'} = $file_name;
+ $diffinfo{'to_file'} = $file_name;
+ }
+ } else { # no filename given
+ $diffinfo{'status'} = '2';
+ $diffinfo{'from_file'} = $hash_parent;
+ $diffinfo{'to_file'} = $hash;
}
- my $from_mode = $1;
- my $to_mode = $2;
- my $from_id = $3;
- my $to_id = $4;
- my $status = $5;
- my $similarity = $6;
- my $file = validate_input(unquote($7));
- if ($alternate) {
- print "<tr class=\"dark\">\n";
+
+ # non-textual hash id's can be cached
+ if ($hash =~ m/^[0-9a-fA-F]{40}$/ &&
+ $hash_parent =~ m/^[0-9a-fA-F]{40}$/) {
+ $expires = '+1d';
+ }
+
+ # open patch output
+ open $fd, "-|", git_cmd(), "diff", '-p', @diff_opts,
+ $hash_parent, $hash, "--"
+ or die_error(undef, "Open git-diff failed");
+ } else {
+ die_error('404 Not Found', "Missing one of the blob diff parameters")
+ unless %diffinfo;
+ }
+
+ # header
+ if ($format eq 'html') {
+ my $formats_nav =
+ $cgi->a({-href => href(action=>"blobdiff_plain",
+ hash=>$hash, hash_parent=>$hash_parent,
+ hash_base=>$hash_base, hash_parent_base=>$hash_parent_base,
+ file_name=>$file_name, file_parent=>$file_parent)},
+ "raw");
+ git_header_html(undef, $expires);
+ if (defined $hash_base && (my %co = parse_commit($hash_base))) {
+ git_print_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav);
+ git_print_header_div('commit', esc_html($co{'title'}), $hash_base);
} else {
- print "<tr class=\"light\">\n";
+ print "<div class=\"page_nav\"><br/>$formats_nav<br/></div>\n";
+ print "<div class=\"title\">$hash vs $hash_parent</div>\n";
}
- $alternate ^= 1;
- if ($status eq "A") {
- my $mode_chng = "";
- if (S_ISREG(oct $to_mode)) {
- $mode_chng = sprintf(" with mode: %04o", (oct $to_mode) & 0777);
- }
- print "<td>" .
- $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$to_id;hb=$hash;f=$file"), -class => "list"}, esc_html($file)) . "</td>\n" .
- "<td><span class=\"file_status new\">[new " . file_type($to_mode) . "$mode_chng]</span></td>\n" .
- "<td class=\"link\">" . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$to_id;hb=$hash;f=$file")}, "blob") . "</td>\n";
- } elsif ($status eq "D") {
- print "<td>" .
- $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$from_id;hb=$parent;f=$file"), -class => "list"}, esc_html($file)) . "</td>\n" .
- "<td><span class=\"file_status deleted\">[deleted " . file_type($from_mode). "]</span></td>\n" .
- "<td class=\"link\">" .
- $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$from_id;hb=$parent;f=$file")}, "blob") .
- " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=history;hb=$parent;f=$file")}, "history") .
- "</td>\n"
- } elsif ($status eq "M" || $status eq "T") {
- my $mode_chnge = "";
- if ($from_mode != $to_mode) {
- $mode_chnge = " <span class=\"file_status mode_chnge\">[changed";
- if (((oct $from_mode) & S_IFMT) != ((oct $to_mode) & S_IFMT)) {
- $mode_chnge .= " from " . file_type($from_mode) . " to " . file_type($to_mode);
- }
- if (((oct $from_mode) & 0777) != ((oct $to_mode) & 0777)) {
- if (S_ISREG($from_mode) && S_ISREG($to_mode)) {
- $mode_chnge .= sprintf(" mode: %04o->%04o", (oct $from_mode) & 0777, (oct $to_mode) & 0777);
- } elsif (S_ISREG($to_mode)) {
- $mode_chnge .= sprintf(" mode: %04o", (oct $to_mode) & 0777);
- }
- }
- $mode_chnge .= "]</span>\n";
- }
- print "<td>";
- if ($to_id ne $from_id) {
- print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blobdiff;h=$to_id;hp=$from_id;hb=$hash;f=$file"), -class => "list"}, esc_html($file));
- } else {
- print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$to_id;hb=$hash;f=$file"), -class => "list"}, esc_html($file));
- }
- print "</td>\n" .
- "<td>$mode_chnge</td>\n" .
- "<td class=\"link\">";
- print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$to_id;hb=$hash;f=$file")}, "blob");
- if ($to_id ne $from_id) {
- print " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blobdiff;h=$to_id;hp=$from_id;hb=$hash;f=$file")}, "diff");
- }
- print " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=history;hb=$hash;f=$file")}, "history") . "\n";
- print "</td>\n";
- } elsif ($status eq "R") {
- my ($from_file, $to_file) = split "\t", $file;
- my $mode_chng = "";
- if ($from_mode != $to_mode) {
- $mode_chng = sprintf(", mode: %04o", (oct $to_mode) & 0777);
- }
- print "<td>" .
- $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$to_id;hb=$hash;f=$to_file"), -class => "list"}, esc_html($to_file)) . "</td>\n" .
- "<td><span class=\"file_status moved\">[moved from " .
- $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$from_id;hb=$parent;f=$from_file"), -class => "list"}, esc_html($from_file)) .
- " with " . (int $similarity) . "% similarity$mode_chng]</span></td>\n" .
- "<td class=\"link\">" .
- $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$to_id;hb=$hash;f=$to_file")}, "blob");
- if ($to_id ne $from_id) {
- print " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blobdiff;h=$to_id;hp=$from_id;hb=$hash;f=$to_file")}, "diff");
- }
- print "</td>\n";
+ if (defined $file_name) {
+ git_print_page_path($file_name, "blob", $hash_base);
+ } else {
+ print "<div class=\"page_path\"></div>\n";
}
- print "</tr>\n";
+
+ } elsif ($format eq 'plain') {
+ print $cgi->header(
+ -type => 'text/plain',
+ -charset => 'utf-8',
+ -expires => $expires,
+ -content_disposition => 'inline; filename="' . "$file_name" . '.patch"');
+
+ print "X-Git-Url: " . $cgi->self_url() . "\n\n";
+
+ } else {
+ die_error(undef, "Unknown blobdiff format");
}
- print "</table>\n";
- git_footer_html();
-}
-sub git_blobdiff {
- mkdir($git_temp, 0700);
- git_header_html();
- if (defined $hash_base && (my %co = git_read_commit($hash_base))) {
- my $formats_nav =
- $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blobdiff_plain;h=$hash;hp=$hash_parent")}, "plain");
- git_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav);
- git_header_div('commit', esc_html($co{'title'}), $hash_base);
+ # patch
+ if ($format eq 'html') {
+ print "<div class=\"page_body\">\n";
+
+ git_patchset_body($fd, [ \%diffinfo ], $hash_base, $hash_parent_base);
+ close $fd;
+
+ print "</div>\n"; # class="page_body"
+ git_footer_html();
+
} else {
- print "<div class=\"page_nav\">\n" .
- "<br/><br/></div>\n" .
- "<div class=\"title\">$hash vs $hash_parent</div>\n";
- }
- git_print_page_path($file_name, "blob");
- print "<div class=\"page_body\">\n" .
- "<div class=\"diff_info\">blob:" .
- $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$hash_parent;hb=$hash_base;f=$file_name")}, $hash_parent) .
- " -> blob:" .
- $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$hash;hb=$hash_base;f=$file_name")}, $hash) .
- "</div>\n";
- git_diff_print($hash_parent, $file_name || $hash_parent, $hash, $file_name || $hash);
- print "</div>";
- git_footer_html();
+ while (my $line = <$fd>) {
+ $line =~ s!a/($hash|$hash_parent)!'a/'.esc_path($diffinfo{'from_file'})!eg;
+ $line =~ s!b/($hash|$hash_parent)!'b/'.esc_path($diffinfo{'to_file'})!eg;
+
+ print $line;
+
+ last if $line =~ m!^\+\+\+!;
+ }
+ local $/ = undef;
+ print <$fd>;
+ close $fd;
+ }
}
sub git_blobdiff_plain {
- mkdir($git_temp, 0700);
- print $cgi->header(-type => "text/plain", -charset => 'utf-8');
- git_diff_print($hash_parent, $file_name || $hash_parent, $hash, $file_name || $hash, "plain");
+ git_blobdiff('plain');
}
sub git_commitdiff {
- mkdir($git_temp, 0700);
- my %co = git_read_commit($hash);
+ my $format = shift || 'html';
+ my %co = parse_commit($hash);
if (!%co) {
die_error(undef, "Unknown commit object");
}
- if (!defined $hash_parent) {
- $hash_parent = $co{'parent'} || '--root';
- }
- open my $fd, "-|", $GIT, "diff-tree", '-r', $hash_parent, $hash
- or die_error(undef, "Open git-diff-tree failed");
- my @difftree = map { chomp; $_ } <$fd>;
- close $fd or die_error(undef, "Reading git-diff-tree failed");
- # non-textual hash id's can be cached
- my $expires;
- if ($hash =~ m/^[0-9a-fA-F]{40}$/) {
- $expires = "+1d";
- }
- my $refs = read_info_ref();
- my $ref = git_get_referencing($refs, $co{'id'});
- my $formats_nav =
- $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff_plain;h=$hash;hp=$hash_parent")}, "plain");
- git_header_html(undef, $expires);
- git_page_nav('commitdiff','', $hash,$co{'tree'},$hash, $formats_nav);
- git_header_div('commit', esc_html($co{'title'}) . $ref, $hash);
- print "<div class=\"page_body\">\n";
- my $comment = $co{'comment'};
- my $empty = 0;
- my $signed = 0;
- my @log = @$comment;
- # remove first and empty lines after that
- shift @log;
- while (defined $log[0] && $log[0] eq "") {
- shift @log;
- }
- foreach my $line (@log) {
- if ($line =~ m/^ *(signed[ \-]off[ \-]by[ :]|acked[ \-]by[ :]|cc[ :])/i) {
- next;
- }
- if ($line eq "") {
- if ($empty) {
- next;
+ # we need to prepare $formats_nav before any parameter munging
+ my $formats_nav;
+ if ($format eq 'html') {
+ $formats_nav =
+ $cgi->a({-href => href(action=>"commitdiff_plain",
+ hash=>$hash, hash_parent=>$hash_parent)},
+ "raw");
+
+ if (defined $hash_parent) {
+ # commitdiff with two commits given
+ my $hash_parent_short = $hash_parent;
+ if ($hash_parent =~ m/^[0-9a-fA-F]{40}$/) {
+ $hash_parent_short = substr($hash_parent, 0, 7);
}
- $empty = 1;
+ $formats_nav .=
+ ' (from: ' .
+ $cgi->a({-href => href(action=>"commitdiff",
+ hash=>$hash_parent)},
+ esc_html($hash_parent_short)) .
+ ')';
+ } elsif (!$co{'parent'}) {
+ # --root commitdiff
+ $formats_nav .= ' (initial)';
+ } elsif (scalar @{$co{'parents'}} == 1) {
+ # single parent commit
+ $formats_nav .=
+ ' (parent: ' .
+ $cgi->a({-href => href(action=>"commitdiff",
+ hash=>$co{'parent'})},
+ esc_html(substr($co{'parent'}, 0, 7))) .
+ ')';
} else {
- $empty = 0;
- }
- print format_log_line_html($line) . "<br/>\n";
- }
- print "<br/>\n";
- foreach my $line (@difftree) {
- # ':100644 100644 03b218260e99b78c6df0ed378e59ed9205ccc96d 3b93d5e7cc7f7dd4ebed13a5cc1a4ad976fc94d8 M ls-files.c'
- # ':100644 100644 7f9281985086971d3877aca27704f2aaf9c448ce bc190ebc71bbd923f2b728e505408f5e54bd073a M rev-tree.c'
- if ($line !~ m/^:([0-7]{6}) ([0-7]{6}) ([0-9a-fA-F]{40}) ([0-9a-fA-F]{40}) (.)\t(.*)$/) {
- next;
- }
- my $from_mode = $1;
- my $to_mode = $2;
- my $from_id = $3;
- my $to_id = $4;
- my $status = $5;
- my $file = validate_input(unquote($6));
- if ($status eq "A") {
- print "<div class=\"diff_info\">" . file_type($to_mode) . ":" .
- $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$to_id;hb=$hash;f=$file")}, $to_id) . "(new)" .
- "</div>\n";
- git_diff_print(undef, "/dev/null", $to_id, "b/$file");
- } elsif ($status eq "D") {
- print "<div class=\"diff_info\">" . file_type($from_mode) . ":" .
- $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$from_id;hb=$hash_parent;f=$file")}, $from_id) . "(deleted)" .
- "</div>\n";
- git_diff_print($from_id, "a/$file", undef, "/dev/null");
- } elsif ($status eq "M") {
- if ($from_id ne $to_id) {
- print "<div class=\"diff_info\">" .
- file_type($from_mode) . ":" .
- $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$from_id;hb=$hash_parent;f=$file")}, $from_id) .
- " -> " .
- file_type($to_mode) . ":" .
- $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$to_id;hb=$hash;f=$file")}, $to_id);
- print "</div>\n";
- git_diff_print($from_id, "a/$file", $to_id, "b/$file");
- }
+ # merge commit
+ $formats_nav .=
+ ' (merge: ' .
+ join(' ', map {
+ $cgi->a({-href => href(action=>"commitdiff",
+ hash=>$_)},
+ esc_html(substr($_, 0, 7)));
+ } @{$co{'parents'}} ) .
+ ')';
}
}
- print "<br/>\n" .
- "</div>";
- git_footer_html();
-}
-sub git_commitdiff_plain {
- mkdir($git_temp, 0700);
- my %co = git_read_commit($hash);
- if (!%co) {
- die_error(undef, "Unknown commit object");
- }
if (!defined $hash_parent) {
$hash_parent = $co{'parent'} || '--root';
}
- open my $fd, "-|", $GIT, "diff-tree", '-r', $hash_parent, $hash
- or die_error(undef, "Open git-diff-tree failed");
- my @difftree = map { chomp; $_ } <$fd>;
- close $fd or die_error(undef, "Reading diff-tree failed");
- # try to figure out the next tag after this commit
- my $tagname;
- my $refs = read_info_ref("tags");
- open $fd, "-|", $GIT, "rev-list", "HEAD";
- my @commits = map { chomp; $_ } <$fd>;
- close $fd;
- foreach my $commit (@commits) {
- if (defined $refs->{$commit}) {
- $tagname = $refs->{$commit}
- }
- if ($commit eq $hash) {
- last;
+ # read commitdiff
+ my $fd;
+ my @difftree;
+ if ($format eq 'html') {
+ open $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts,
+ "--no-commit-id", "--patch-with-raw", "--full-index",
+ $hash_parent, $hash, "--"
+ or die_error(undef, "Open git-diff-tree failed");
+
+ while (chomp(my $line = <$fd>)) {
+ # empty line ends raw part of diff-tree output
+ last unless $line;
+ push @difftree, $line;
}
- }
- print $cgi->header(-type => "text/plain", -charset => 'utf-8', '-content-disposition' => "inline; filename=\"git-$hash.patch\"");
- my %ad = date_str($co{'author_epoch'}, $co{'author_tz'});
- my $comment = $co{'comment'};
- print "From: $co{'author'}\n" .
- "Date: $ad{'rfc2822'} ($ad{'tz_local'})\n".
- "Subject: $co{'title'}\n";
- if (defined $tagname) {
- print "X-Git-Tag: $tagname\n";
+ } elsif ($format eq 'plain') {
+ open $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts,
+ '-p', $hash_parent, $hash, "--"
+ or die_error(undef, "Open git-diff-tree failed");
+
+ } else {
+ die_error(undef, "Unknown commitdiff format");
}
- print "X-Git-Url: $my_url?p=$project;a=commitdiff;h=$hash\n" .
- "\n";
- foreach my $line (@$comment) {;
- print "$line\n";
+ # non-textual hash id's can be cached
+ my $expires;
+ if ($hash =~ m/^[0-9a-fA-F]{40}$/) {
+ $expires = "+1d";
}
- print "---\n\n";
- foreach my $line (@difftree) {
- if ($line !~ m/^:([0-7]{6}) ([0-7]{6}) ([0-9a-fA-F]{40}) ([0-9a-fA-F]{40}) (.)\t(.*)$/) {
- next;
+ # write commit message
+ if ($format eq 'html') {
+ my $refs = git_get_references();
+ my $ref = format_ref_marker($refs, $co{'id'});
+
+ git_header_html(undef, $expires);
+ git_print_page_nav('commitdiff','', $hash,$co{'tree'},$hash, $formats_nav);
+ git_print_header_div('commit', esc_html($co{'title'}) . $ref, $hash);
+ git_print_authorship(\%co);
+ print "<div class=\"page_body\">\n";
+ if (@{$co{'comment'}} > 1) {
+ print "<div class=\"log\">\n";
+ git_print_log($co{'comment'}, -final_empty_line=> 1, -remove_title => 1);
+ print "</div>\n"; # class="log"
}
- my $from_id = $3;
- my $to_id = $4;
- my $status = $5;
- my $file = $6;
- if ($status eq "A") {
- git_diff_print(undef, "/dev/null", $to_id, "b/$file", "plain");
- } elsif ($status eq "D") {
- git_diff_print($from_id, "a/$file", undef, "/dev/null", "plain");
- } elsif ($status eq "M") {
- git_diff_print($from_id, "a/$file", $to_id, "b/$file", "plain");
+
+ } elsif ($format eq 'plain') {
+ my $refs = git_get_references("tags");
+ my $tagname = git_get_rev_name_tags($hash);
+ my $filename = basename($project) . "-$hash.patch";
+
+ print $cgi->header(
+ -type => 'text/plain',
+ -charset => 'utf-8',
+ -expires => $expires,
+ -content_disposition => 'inline; filename="' . "$filename" . '"');
+ my %ad = parse_date($co{'author_epoch'}, $co{'author_tz'});
+ print <<TEXT;
+From: $co{'author'}
+Date: $ad{'rfc2822'} ($ad{'tz_local'})
+Subject: $co{'title'}
+TEXT
+ print "X-Git-Tag: $tagname\n" if $tagname;
+ print "X-Git-Url: " . $cgi->self_url() . "\n\n";
+
+ foreach my $line (@{$co{'comment'}}) {
+ print "$line\n";
}
+ print "---\n\n";
+ }
+
+ # write patch
+ if ($format eq 'html') {
+ git_difftree_body(\@difftree, $hash, $hash_parent);
+ print "<br/>\n";
+
+ git_patchset_body($fd, \@difftree, $hash, $hash_parent);
+ close $fd;
+ print "</div>\n"; # class="page_body"
+ git_footer_html();
+
+ } elsif ($format eq 'plain') {
+ local $/ = undef;
+ print <$fd>;
+ close $fd
+ or print "Reading git-diff-tree failed\n";
}
}
+sub git_commitdiff_plain {
+ git_commitdiff('plain');
+}
+
sub git_history {
if (!defined $hash_base) {
- $hash_base = git_read_head($project);
+ $hash_base = git_get_head_hash($project);
+ }
+ if (!defined $page) {
+ $page = 0;
}
my $ftype;
- my %co = git_read_commit($hash_base);
+ my %co = parse_commit($hash_base);
if (!%co) {
die_error(undef, "Unknown commit object");
}
- my $refs = read_info_ref();
- git_header_html();
- git_page_nav('','', $hash_base,$co{'tree'},$hash_base);
- git_header_div('commit', esc_html($co{'title'}), $hash_base);
+
+ my $refs = git_get_references();
+ my $limit = sprintf("--max-count=%i", (100 * ($page+1)));
+
if (!defined $hash && defined $file_name) {
$hash = git_get_hash_by_path($hash_base, $file_name);
}
if (defined $hash) {
$ftype = git_get_type($hash);
}
- git_print_page_path($file_name, $ftype);
open my $fd, "-|",
- $GIT, "rev-list", "--full-history", $hash_base, "--", $file_name;
- print "<table cellspacing=\"0\">\n";
- my $alternate = 0;
- while (my $line = <$fd>) {
- if ($line =~ m/^([0-9a-fA-F]{40})/){
- my $commit = $1;
- my %co = git_read_commit($commit);
- if (!%co) {
- next;
- }
- my $ref = git_get_referencing($refs, $commit);
- if ($alternate) {
- print "<tr class=\"dark\">\n";
- } else {
- print "<tr class=\"light\">\n";
- }
- $alternate ^= 1;
- print "<td title=\"$co{'age_string_age'}\"><i>$co{'age_string_date'}</i></td>\n" .
- "<td><i>" . esc_html(chop_str($co{'author_name'}, 15, 3)) . "</i></td>\n" .
- "<td>" . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$commit"), -class => "list"}, "<b>" .
- esc_html(chop_str($co{'title'}, 50)) . "$ref</b>") . "</td>\n" .
- "<td class=\"link\">" .
- $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$commit")}, "commit") .
- " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$commit")}, "commitdiff") .
- " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$ftype;hb=$commit;f=$file_name")}, $ftype);
- my $blob = git_get_hash_by_path($hash_base, $file_name);
- my $blob_parent = git_get_hash_by_path($commit, $file_name);
- if (defined $blob && defined $blob_parent && $blob ne $blob_parent) {
- print " | " .
- $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blobdiff;h=$blob;hp=$blob_parent;hb=$commit;f=$file_name")},
- "diff to current");
- }
- print "</td>\n" .
- "</tr>\n";
- }
+ git_cmd(), "rev-list", $limit, "--full-history", $hash_base, "--", $file_name
+ or die_error(undef, "Open git-rev-list-failed");
+ my @revlist = map { chomp; $_ } <$fd>;
+ close $fd
+ or die_error(undef, "Reading git-rev-list failed");
+
+ my $paging_nav = '';
+ if ($page > 0) {
+ $paging_nav .=
+ $cgi->a({-href => href(action=>"history", hash=>$hash, hash_base=>$hash_base,
+ file_name=>$file_name)},
+ "first");
+ $paging_nav .= " &sdot; " .
+ $cgi->a({-href => href(action=>"history", hash=>$hash, hash_base=>$hash_base,
+ file_name=>$file_name, page=>$page-1),
+ -accesskey => "p", -title => "Alt-p"}, "prev");
+ } else {
+ $paging_nav .= "first";
+ $paging_nav .= " &sdot; prev";
}
- print "</table>\n";
- close $fd;
+ if ($#revlist >= (100 * ($page+1)-1)) {
+ $paging_nav .= " &sdot; " .
+ $cgi->a({-href => href(action=>"history", hash=>$hash, hash_base=>$hash_base,
+ file_name=>$file_name, page=>$page+1),
+ -accesskey => "n", -title => "Alt-n"}, "next");
+ } else {
+ $paging_nav .= " &sdot; next";
+ }
+ my $next_link = '';
+ if ($#revlist >= (100 * ($page+1)-1)) {
+ $next_link =
+ $cgi->a({-href => href(action=>"history", hash=>$hash, hash_base=>$hash_base,
+ file_name=>$file_name, page=>$page+1),
+ -title => "Alt-n"}, "next");
+ }
+
+ git_header_html();
+ git_print_page_nav('history','', $hash_base,$co{'tree'},$hash_base, $paging_nav);
+ git_print_header_div('commit', esc_html($co{'title'}), $hash_base);
+ git_print_page_path($file_name, $ftype, $hash_base);
+
+ git_history_body(\@revlist, ($page * 100), $#revlist,
+ $refs, $hash_base, $ftype, $next_link);
+
git_footer_html();
}
@@ -2330,47 +3891,46 @@ sub git_search {
die_error(undef, "Text field empty");
}
if (!defined $hash) {
- $hash = git_read_head($project);
+ $hash = git_get_head_hash($project);
}
- my %co = git_read_commit($hash);
+ my %co = parse_commit($hash);
if (!%co) {
die_error(undef, "Unknown commit object");
}
- # pickaxe may take all resources of your box and run for several minutes
- # with every query - so decide by yourself how public you make this feature :)
- my $commit_search = 1;
- my $author_search = 0;
- my $committer_search = 0;
- my $pickaxe_search = 0;
- if ($searchtext =~ s/^author\\://i) {
- $author_search = 1;
- } elsif ($searchtext =~ s/^committer\\://i) {
- $committer_search = 1;
- } elsif ($searchtext =~ s/^pickaxe\\://i) {
- $commit_search = 0;
- $pickaxe_search = 1;
+
+ $searchtype ||= 'commit';
+ if ($searchtype eq 'pickaxe') {
+ # pickaxe may take all resources of your box and run for several minutes
+ # with every query - so decide by yourself how public you make this feature
+ my ($have_pickaxe) = gitweb_check_feature('pickaxe');
+ if (!$have_pickaxe) {
+ die_error('403 Permission denied', "Permission denied");
+ }
}
+
git_header_html();
- git_page_nav('','', $hash,$co{'tree'},$hash);
- git_header_div('commit', esc_html($co{'title'}), $hash);
+ git_print_page_nav('','', $hash,$co{'tree'},$hash);
+ git_print_header_div('commit', esc_html($co{'title'}), $hash);
print "<table cellspacing=\"0\">\n";
- my $alternate = 0;
- if ($commit_search) {
+ my $alternate = 1;
+ if ($searchtype eq 'commit' or $searchtype eq 'author' or $searchtype eq 'committer') {
$/ = "\0";
- open my $fd, "-|", $GIT, "rev-list", "--header", "--parents", $hash or next;
+ open my $fd, "-|", git_cmd(), "rev-list",
+ "--header", "--parents", $hash, "--"
+ or next;
while (my $commit_text = <$fd>) {
if (!grep m/$searchtext/i, $commit_text) {
next;
}
- if ($author_search && !grep m/\nauthor .*$searchtext/i, $commit_text) {
+ if ($searchtype eq 'author' && !grep m/\nauthor .*$searchtext/i, $commit_text) {
next;
}
- if ($committer_search && !grep m/\ncommitter .*$searchtext/i, $commit_text) {
+ if ($searchtype eq 'committer' && !grep m/\ncommitter .*$searchtext/i, $commit_text) {
next;
}
my @commit_lines = split "\n", $commit_text;
- my %co = git_read_commit(undef, \@commit_lines);
+ my %co = parse_commit(undef, \@commit_lines);
if (!%co) {
next;
}
@@ -2383,7 +3943,8 @@ sub git_search {
print "<td title=\"$co{'age_string_age'}\"><i>$co{'age_string_date'}</i></td>\n" .
"<td><i>" . esc_html(chop_str($co{'author_name'}, 15, 5)) . "</i></td>\n" .
"<td>" .
- $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$co{'id'}"), -class => "list"}, "<b>" . esc_html(chop_str($co{'title'}, 50)) . "</b><br/>");
+ $cgi->a({-href => href(action=>"commit", hash=>$co{'id'}), -class => "list subject"},
+ esc_html(chop_str($co{'title'}, 50)) . "<br/>");
my $comment = $co{'comment'};
foreach my $line (@$comment) {
if ($line =~ m/^(.*)($searchtext)(.*)$/i) {
@@ -2398,17 +3959,20 @@ sub git_search {
}
print "</td>\n" .
"<td class=\"link\">" .
- $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$co{'id'}")}, "commit") .
- " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;h=$co{'tree'};hb=$co{'id'}")}, "tree");
+ $cgi->a({-href => href(action=>"commit", hash=>$co{'id'})}, "commit") .
+ " | " .
+ $cgi->a({-href => href(action=>"tree", hash=>$co{'tree'}, hash_base=>$co{'id'})}, "tree");
print "</td>\n" .
"</tr>\n";
}
close $fd;
}
- if ($pickaxe_search) {
+ if ($searchtype eq 'pickaxe') {
$/ = "\n";
- open my $fd, "-|", "$GIT rev-list $hash | $GIT diff-tree -r --stdin -S\'$searchtext\'";
+ my $git_command = git_cmd_str();
+ open my $fd, "-|", "$git_command rev-list $hash | " .
+ "$git_command diff-tree -r --stdin -S\'$searchtext\'";
undef %co;
my @files;
while (my $line = <$fd>) {
@@ -2436,22 +4000,26 @@ sub git_search {
print "<td title=\"$co{'age_string_age'}\"><i>$co{'age_string_date'}</i></td>\n" .
"<td><i>" . esc_html(chop_str($co{'author_name'}, 15, 5)) . "</i></td>\n" .
"<td>" .
- $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$co{'id'}"), -class => "list"}, "<b>" .
- esc_html(chop_str($co{'title'}, 50)) . "</b><br/>");
+ $cgi->a({-href => href(action=>"commit", hash=>$co{'id'}),
+ -class => "list subject"},
+ esc_html(chop_str($co{'title'}, 50)) . "<br/>");
while (my $setref = shift @files) {
my %set = %$setref;
- print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$set{'id'};hb=$co{'id'};f=$set{'file'}"), class => "list"},
- "<span class=\"match\">" . esc_html($set{'file'}) . "</span>") .
+ print $cgi->a({-href => href(action=>"blob", hash_base=>$co{'id'},
+ hash=>$set{'id'}, file_name=>$set{'file'}),
+ -class => "list"},
+ "<span class=\"match\">" . esc_path($set{'file'}) . "</span>") .
"<br/>\n";
}
print "</td>\n" .
"<td class=\"link\">" .
- $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$co{'id'}")}, "commit") .
- " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;h=$co{'tree'};hb=$co{'id'}")}, "tree");
+ $cgi->a({-href => href(action=>"commit", hash=>$co{'id'})}, "commit") .
+ " | " .
+ $cgi->a({-href => href(action=>"tree", hash=>$co{'tree'}, hash_base=>$co{'id'})}, "tree");
print "</td>\n" .
"</tr>\n";
}
- %co = git_read_commit($1);
+ %co = parse_commit($1);
}
}
close $fd;
@@ -2460,34 +4028,59 @@ sub git_search {
git_footer_html();
}
+sub git_search_help {
+ git_header_html();
+ git_print_page_nav('','', $hash,$hash,$hash);
+ print <<EOT;
+<dl>
+<dt><b>commit</b></dt>
+<dd>The commit messages and authorship information will be scanned for the given string.</dd>
+<dt><b>author</b></dt>
+<dd>Name and e-mail of the change author and date of birth of the patch will be scanned for the given string.</dd>
+<dt><b>committer</b></dt>
+<dd>Name and e-mail of the committer and date of commit will be scanned for the given string.</dd>
+EOT
+ my ($have_pickaxe) = gitweb_check_feature('pickaxe');
+ if ($have_pickaxe) {
+ print <<EOT;
+<dt><b>pickaxe</b></dt>
+<dd>All commits that caused the string to appear or disappear from any file (changes that
+added, removed or "modified" the string) will be listed. This search can take a while and
+takes a lot of strain on the server, so please use it wisely.</dd>
+EOT
+ }
+ print "</dl>\n";
+ git_footer_html();
+}
+
sub git_shortlog {
- my $head = git_read_head($project);
+ my $head = git_get_head_hash($project);
if (!defined $hash) {
$hash = $head;
}
if (!defined $page) {
$page = 0;
}
- my $refs = read_info_ref();
+ my $refs = git_get_references();
my $limit = sprintf("--max-count=%i", (100 * ($page+1)));
- open my $fd, "-|", $GIT, "rev-list", $limit, $hash
+ open my $fd, "-|", git_cmd(), "rev-list", $limit, $hash, "--"
or die_error(undef, "Open git-rev-list failed");
my @revlist = map { chomp; $_ } <$fd>;
close $fd;
- my $paging_nav = git_get_paging_nav('shortlog', $hash, $head, $page, $#revlist);
+ my $paging_nav = format_paging_nav('shortlog', $hash, $head, $page, $#revlist);
my $next_link = '';
if ($#revlist >= (100 * ($page+1)-1)) {
$next_link =
- $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog;h=$hash;pg=" . ($page+1)),
+ $cgi->a({-href => href(action=>"shortlog", hash=>$hash, page=>$page+1),
-title => "Alt-n"}, "next");
}
git_header_html();
- git_page_nav('shortlog','', $hash,$hash,$hash, $paging_nav);
- git_header_div('summary', $project);
+ git_print_page_nav('shortlog','', $hash,$hash,$hash, $paging_nav);
+ git_print_header_div('summary', $project);
git_shortlog_body(\@revlist, ($page * 100), $#revlist, $refs, $next_link);
@@ -2499,30 +4092,36 @@ sub git_shortlog {
sub git_rss {
# http://www.notestips.com/80256B3A007F2692/1/NAMO5P9UPQ
- open my $fd, "-|", $GIT, "rev-list", "--max-count=150", git_read_head($project)
+ open my $fd, "-|", git_cmd(), "rev-list", "--max-count=150",
+ git_get_head_hash($project), "--"
or die_error(undef, "Open git-rev-list failed");
my @revlist = map { chomp; $_ } <$fd>;
close $fd or die_error(undef, "Reading git-rev-list failed");
print $cgi->header(-type => 'text/xml', -charset => 'utf-8');
- print "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n".
- "<rss version=\"2.0\" xmlns:content=\"http://purl.org/rss/1.0/modules/content/\">\n";
- print "<channel>\n";
- print "<title>$project</title>\n".
- "<link>" . esc_html("$my_url?p=$project;a=summary") . "</link>\n".
- "<description>$project log</description>\n".
- "<language>en</language>\n";
+ print <<XML;
+<?xml version="1.0" encoding="utf-8"?>
+<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/">
+<channel>
+<title>$project $my_uri $my_url</title>
+<link>${\esc_html("$my_url?p=$project;a=summary")}</link>
+<description>$project log</description>
+<language>en</language>
+XML
for (my $i = 0; $i <= $#revlist; $i++) {
my $commit = $revlist[$i];
- my %co = git_read_commit($commit);
+ my %co = parse_commit($commit);
# we read 150, we always show 30 and the ones more recent than 48 hours
if (($i >= 20) && ((time - $co{'committer_epoch'}) > 48*60*60)) {
last;
}
- my %cd = date_str($co{'committer_epoch'});
- open $fd, "-|", $GIT, "diff-tree", '-r', $co{'parent'}, $co{'id'} or next;
+ my %cd = parse_date($co{'committer_epoch'});
+ open $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts,
+ $co{'parent'}, $co{'id'}, "--"
+ or next;
my @difftree = map { chomp; $_ } <$fd>;
- close $fd or next;
+ close $fd
+ or next;
print "<item>\n" .
"<title>" .
sprintf("%d %s %02d:%02d", $cd{'mday'}, $cd{'month'}, $cd{'hour'}, $cd{'minute'}) . " - " . esc_html($co{'title'}) .
@@ -2536,7 +4135,7 @@ sub git_rss {
"<![CDATA[\n";
my $comment = $co{'comment'};
foreach my $line (@$comment) {
- $line = decode("utf8", $line, Encode::FB_DEFAULT);
+ $line = to_utf8($line);
print "$line<br/>\n";
}
print "<br/>\n";
@@ -2544,8 +4143,8 @@ sub git_rss {
if (!($line =~ m/^:([0-7]{6}) ([0-7]{6}) ([0-9a-fA-F]{40}) ([0-9a-fA-F]{40}) (.)([0-9]{0,3})\t(.*)$/)) {
next;
}
- my $file = validate_input(unquote($7));
- $file = decode("utf8", $file, Encode::FB_DEFAULT);
+ my $file = esc_path(unquote($7));
+ $file = to_utf8($file);
print "$file<br/>\n";
}
print "]]>\n" .
@@ -2556,25 +4155,27 @@ sub git_rss {
}
sub git_opml {
- my @list = git_read_projects();
+ my @list = git_get_projects_list();
print $cgi->header(-type => 'text/xml', -charset => 'utf-8');
- print "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n".
- "<opml version=\"1.0\">\n".
- "<head>".
- " <title>$site_name Git OPML Export</title>\n".
- "</head>\n".
- "<body>\n".
- "<outline text=\"git RSS feeds\">\n";
+ print <<XML;
+<?xml version="1.0" encoding="utf-8"?>
+<opml version="1.0">
+<head>
+ <title>$site_name OPML Export</title>
+</head>
+<body>
+<outline text="git RSS feeds">
+XML
foreach my $pr (@list) {
my %proj = %$pr;
- my $head = git_read_head($proj{'path'});
+ my $head = git_get_head_hash($proj{'path'});
if (!defined $head) {
next;
}
- $ENV{'GIT_DIR'} = "$projectroot/$proj{'path'}";
- my %co = git_read_commit($head);
+ $git_dir = "$projectroot/$proj{'path'}";
+ my %co = parse_commit($head);
if (!%co) {
next;
}
@@ -2584,7 +4185,9 @@ sub git_opml {
my $html = "$my_url?p=$proj{'path'};a=summary";
print "<outline type=\"rss\" text=\"$path\" title=\"$path\" xmlUrl=\"$rss\" htmlUrl=\"$html\"/>\n";
}
- print "</outline>\n".
- "</body>\n".
- "</opml>\n";
+ print <<XML;
+</outline>
+</body>
+</opml>
+XML
}
diff --git a/grep.c b/grep.c
new file mode 100644
index 0000000000..0fc078ec0a
--- /dev/null
+++ b/grep.c
@@ -0,0 +1,576 @@
+#include "cache.h"
+#include <regex.h>
+#include "grep.h"
+
+void append_grep_pattern(struct grep_opt *opt, const char *pat,
+ const char *origin, int no, enum grep_pat_token t)
+{
+ struct grep_pat *p = xcalloc(1, sizeof(*p));
+ p->pattern = pat;
+ p->origin = origin;
+ p->no = no;
+ p->token = t;
+ *opt->pattern_tail = p;
+ opt->pattern_tail = &p->next;
+ p->next = NULL;
+}
+
+static void compile_regexp(struct grep_pat *p, struct grep_opt *opt)
+{
+ int err = regcomp(&p->regexp, p->pattern, opt->regflags);
+ if (err) {
+ char errbuf[1024];
+ char where[1024];
+ if (p->no)
+ sprintf(where, "In '%s' at %d, ",
+ p->origin, p->no);
+ else if (p->origin)
+ sprintf(where, "%s, ", p->origin);
+ else
+ where[0] = 0;
+ regerror(err, &p->regexp, errbuf, 1024);
+ regfree(&p->regexp);
+ die("%s'%s': %s", where, p->pattern, errbuf);
+ }
+}
+
+static struct grep_expr *compile_pattern_or(struct grep_pat **);
+static struct grep_expr *compile_pattern_atom(struct grep_pat **list)
+{
+ struct grep_pat *p;
+ struct grep_expr *x;
+
+ p = *list;
+ switch (p->token) {
+ case GREP_PATTERN: /* atom */
+ case GREP_PATTERN_HEAD:
+ case GREP_PATTERN_BODY:
+ x = xcalloc(1, sizeof (struct grep_expr));
+ x->node = GREP_NODE_ATOM;
+ x->u.atom = p;
+ *list = p->next;
+ return x;
+ case GREP_OPEN_PAREN:
+ *list = p->next;
+ x = compile_pattern_or(list);
+ if (!x)
+ return NULL;
+ if (!*list || (*list)->token != GREP_CLOSE_PAREN)
+ die("unmatched parenthesis");
+ *list = (*list)->next;
+ return x;
+ default:
+ return NULL;
+ }
+}
+
+static struct grep_expr *compile_pattern_not(struct grep_pat **list)
+{
+ struct grep_pat *p;
+ struct grep_expr *x;
+
+ p = *list;
+ switch (p->token) {
+ case GREP_NOT:
+ if (!p->next)
+ die("--not not followed by pattern expression");
+ *list = p->next;
+ x = xcalloc(1, sizeof (struct grep_expr));
+ x->node = GREP_NODE_NOT;
+ x->u.unary = compile_pattern_not(list);
+ if (!x->u.unary)
+ die("--not followed by non pattern expression");
+ return x;
+ default:
+ return compile_pattern_atom(list);
+ }
+}
+
+static struct grep_expr *compile_pattern_and(struct grep_pat **list)
+{
+ struct grep_pat *p;
+ struct grep_expr *x, *y, *z;
+
+ x = compile_pattern_not(list);
+ p = *list;
+ if (p && p->token == GREP_AND) {
+ if (!p->next)
+ die("--and not followed by pattern expression");
+ *list = p->next;
+ y = compile_pattern_and(list);
+ if (!y)
+ die("--and not followed by pattern expression");
+ z = xcalloc(1, sizeof (struct grep_expr));
+ z->node = GREP_NODE_AND;
+ z->u.binary.left = x;
+ z->u.binary.right = y;
+ return z;
+ }
+ return x;
+}
+
+static struct grep_expr *compile_pattern_or(struct grep_pat **list)
+{
+ struct grep_pat *p;
+ struct grep_expr *x, *y, *z;
+
+ x = compile_pattern_and(list);
+ p = *list;
+ if (x && p && p->token != GREP_CLOSE_PAREN) {
+ y = compile_pattern_or(list);
+ if (!y)
+ die("not a pattern expression %s", p->pattern);
+ z = xcalloc(1, sizeof (struct grep_expr));
+ z->node = GREP_NODE_OR;
+ z->u.binary.left = x;
+ z->u.binary.right = y;
+ return z;
+ }
+ return x;
+}
+
+static struct grep_expr *compile_pattern_expr(struct grep_pat **list)
+{
+ return compile_pattern_or(list);
+}
+
+void compile_grep_patterns(struct grep_opt *opt)
+{
+ struct grep_pat *p;
+
+ if (opt->all_match)
+ opt->extended = 1;
+
+ for (p = opt->pattern_list; p; p = p->next) {
+ switch (p->token) {
+ case GREP_PATTERN: /* atom */
+ case GREP_PATTERN_HEAD:
+ case GREP_PATTERN_BODY:
+ if (!opt->fixed)
+ compile_regexp(p, opt);
+ break;
+ default:
+ opt->extended = 1;
+ break;
+ }
+ }
+
+ if (!opt->extended)
+ return;
+
+ /* Then bundle them up in an expression.
+ * A classic recursive descent parser would do.
+ */
+ p = opt->pattern_list;
+ opt->pattern_expression = compile_pattern_expr(&p);
+ if (p)
+ die("incomplete pattern expression: %s", p->pattern);
+}
+
+static void free_pattern_expr(struct grep_expr *x)
+{
+ switch (x->node) {
+ case GREP_NODE_ATOM:
+ break;
+ case GREP_NODE_NOT:
+ free_pattern_expr(x->u.unary);
+ break;
+ case GREP_NODE_AND:
+ case GREP_NODE_OR:
+ free_pattern_expr(x->u.binary.left);
+ free_pattern_expr(x->u.binary.right);
+ break;
+ }
+ free(x);
+}
+
+void free_grep_patterns(struct grep_opt *opt)
+{
+ struct grep_pat *p, *n;
+
+ for (p = opt->pattern_list; p; p = n) {
+ n = p->next;
+ switch (p->token) {
+ case GREP_PATTERN: /* atom */
+ case GREP_PATTERN_HEAD:
+ case GREP_PATTERN_BODY:
+ regfree(&p->regexp);
+ break;
+ default:
+ break;
+ }
+ free(p);
+ }
+
+ if (!opt->extended)
+ return;
+ free_pattern_expr(opt->pattern_expression);
+}
+
+static char *end_of_line(char *cp, unsigned long *left)
+{
+ unsigned long l = *left;
+ while (l && *cp != '\n') {
+ l--;
+ cp++;
+ }
+ *left = l;
+ return cp;
+}
+
+static int word_char(char ch)
+{
+ return isalnum(ch) || ch == '_';
+}
+
+static void show_line(struct grep_opt *opt, const char *bol, const char *eol,
+ const char *name, unsigned lno, char sign)
+{
+ if (opt->pathname)
+ printf("%s%c", name, sign);
+ if (opt->linenum)
+ printf("%d%c", lno, sign);
+ printf("%.*s\n", (int)(eol-bol), bol);
+}
+
+/*
+ * NEEDSWORK: share code with diff.c
+ */
+#define FIRST_FEW_BYTES 8000
+static int buffer_is_binary(const char *ptr, unsigned long size)
+{
+ if (FIRST_FEW_BYTES < size)
+ size = FIRST_FEW_BYTES;
+ return !!memchr(ptr, 0, size);
+}
+
+static int fixmatch(const char *pattern, char *line, regmatch_t *match)
+{
+ char *hit = strstr(line, pattern);
+ if (!hit) {
+ match->rm_so = match->rm_eo = -1;
+ return REG_NOMATCH;
+ }
+ else {
+ match->rm_so = hit - line;
+ match->rm_eo = match->rm_so + strlen(pattern);
+ return 0;
+ }
+}
+
+static int match_one_pattern(struct grep_opt *opt, struct grep_pat *p, char *bol, char *eol, enum grep_context ctx)
+{
+ int hit = 0;
+ int at_true_bol = 1;
+ regmatch_t pmatch[10];
+
+ if ((p->token != GREP_PATTERN) &&
+ ((p->token == GREP_PATTERN_HEAD) != (ctx == GREP_CONTEXT_HEAD)))
+ return 0;
+
+ again:
+ if (!opt->fixed) {
+ regex_t *exp = &p->regexp;
+ hit = !regexec(exp, bol, ARRAY_SIZE(pmatch),
+ pmatch, 0);
+ }
+ else {
+ hit = !fixmatch(p->pattern, bol, pmatch);
+ }
+
+ if (hit && opt->word_regexp) {
+ if ((pmatch[0].rm_so < 0) ||
+ (eol - bol) <= pmatch[0].rm_so ||
+ (pmatch[0].rm_eo < 0) ||
+ (eol - bol) < pmatch[0].rm_eo)
+ die("regexp returned nonsense");
+
+ /* Match beginning must be either beginning of the
+ * line, or at word boundary (i.e. the last char must
+ * not be a word char). Similarly, match end must be
+ * either end of the line, or at word boundary
+ * (i.e. the next char must not be a word char).
+ */
+ if ( ((pmatch[0].rm_so == 0 && at_true_bol) ||
+ !word_char(bol[pmatch[0].rm_so-1])) &&
+ ((pmatch[0].rm_eo == (eol-bol)) ||
+ !word_char(bol[pmatch[0].rm_eo])) )
+ ;
+ else
+ hit = 0;
+
+ if (!hit && pmatch[0].rm_so + bol + 1 < eol) {
+ /* There could be more than one match on the
+ * line, and the first match might not be
+ * strict word match. But later ones could be!
+ */
+ bol = pmatch[0].rm_so + bol + 1;
+ at_true_bol = 0;
+ goto again;
+ }
+ }
+ return hit;
+}
+
+static int match_expr_eval(struct grep_opt *o,
+ struct grep_expr *x,
+ char *bol, char *eol,
+ enum grep_context ctx,
+ int collect_hits)
+{
+ int h = 0;
+
+ switch (x->node) {
+ case GREP_NODE_ATOM:
+ h = match_one_pattern(o, x->u.atom, bol, eol, ctx);
+ break;
+ case GREP_NODE_NOT:
+ h = !match_expr_eval(o, x->u.unary, bol, eol, ctx, 0);
+ break;
+ case GREP_NODE_AND:
+ if (!collect_hits)
+ return (match_expr_eval(o, x->u.binary.left,
+ bol, eol, ctx, 0) &&
+ match_expr_eval(o, x->u.binary.right,
+ bol, eol, ctx, 0));
+ h = match_expr_eval(o, x->u.binary.left, bol, eol, ctx, 0);
+ h &= match_expr_eval(o, x->u.binary.right, bol, eol, ctx, 0);
+ break;
+ case GREP_NODE_OR:
+ if (!collect_hits)
+ return (match_expr_eval(o, x->u.binary.left,
+ bol, eol, ctx, 0) ||
+ match_expr_eval(o, x->u.binary.right,
+ bol, eol, ctx, 0));
+ h = match_expr_eval(o, x->u.binary.left, bol, eol, ctx, 0);
+ x->u.binary.left->hit |= h;
+ h |= match_expr_eval(o, x->u.binary.right, bol, eol, ctx, 1);
+ break;
+ default:
+ die("Unexpected node type (internal error) %d\n", x->node);
+ }
+ if (collect_hits)
+ x->hit |= h;
+ return h;
+}
+
+static int match_expr(struct grep_opt *opt, char *bol, char *eol,
+ enum grep_context ctx, int collect_hits)
+{
+ struct grep_expr *x = opt->pattern_expression;
+ return match_expr_eval(opt, x, bol, eol, ctx, collect_hits);
+}
+
+static int match_line(struct grep_opt *opt, char *bol, char *eol,
+ enum grep_context ctx, int collect_hits)
+{
+ struct grep_pat *p;
+ if (opt->extended)
+ return match_expr(opt, bol, eol, ctx, collect_hits);
+
+ /* we do not call with collect_hits without being extended */
+ for (p = opt->pattern_list; p; p = p->next) {
+ if (match_one_pattern(opt, p, bol, eol, ctx))
+ return 1;
+ }
+ return 0;
+}
+
+static int grep_buffer_1(struct grep_opt *opt, const char *name,
+ char *buf, unsigned long size, int collect_hits)
+{
+ char *bol = buf;
+ unsigned long left = size;
+ unsigned lno = 1;
+ struct pre_context_line {
+ char *bol;
+ char *eol;
+ } *prev = NULL, *pcl;
+ unsigned last_hit = 0;
+ unsigned last_shown = 0;
+ int binary_match_only = 0;
+ const char *hunk_mark = "";
+ unsigned count = 0;
+ enum grep_context ctx = GREP_CONTEXT_HEAD;
+
+ if (buffer_is_binary(buf, size)) {
+ switch (opt->binary) {
+ case GREP_BINARY_DEFAULT:
+ binary_match_only = 1;
+ break;
+ case GREP_BINARY_NOMATCH:
+ return 0; /* Assume unmatch */
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (opt->pre_context)
+ prev = xcalloc(opt->pre_context, sizeof(*prev));
+ if (opt->pre_context || opt->post_context)
+ hunk_mark = "--\n";
+
+ while (left) {
+ char *eol, ch;
+ int hit;
+
+ eol = end_of_line(bol, &left);
+ ch = *eol;
+ *eol = 0;
+
+ if ((ctx == GREP_CONTEXT_HEAD) && (eol == bol))
+ ctx = GREP_CONTEXT_BODY;
+
+ hit = match_line(opt, bol, eol, ctx, collect_hits);
+ *eol = ch;
+
+ if (collect_hits)
+ goto next_line;
+
+ /* "grep -v -e foo -e bla" should list lines
+ * that do not have either, so inversion should
+ * be done outside.
+ */
+ if (opt->invert)
+ hit = !hit;
+ if (opt->unmatch_name_only) {
+ if (hit)
+ return 0;
+ goto next_line;
+ }
+ if (hit) {
+ count++;
+ if (opt->status_only)
+ return 1;
+ if (binary_match_only) {
+ printf("Binary file %s matches\n", name);
+ return 1;
+ }
+ if (opt->name_only) {
+ printf("%s\n", name);
+ return 1;
+ }
+ /* Hit at this line. If we haven't shown the
+ * pre-context lines, we would need to show them.
+ * When asked to do "count", this still show
+ * the context which is nonsense, but the user
+ * deserves to get that ;-).
+ */
+ if (opt->pre_context) {
+ unsigned from;
+ if (opt->pre_context < lno)
+ from = lno - opt->pre_context;
+ else
+ from = 1;
+ if (from <= last_shown)
+ from = last_shown + 1;
+ if (last_shown && from != last_shown + 1)
+ printf(hunk_mark);
+ while (from < lno) {
+ pcl = &prev[lno-from-1];
+ show_line(opt, pcl->bol, pcl->eol,
+ name, from, '-');
+ from++;
+ }
+ last_shown = lno-1;
+ }
+ if (last_shown && lno != last_shown + 1)
+ printf(hunk_mark);
+ if (!opt->count)
+ show_line(opt, bol, eol, name, lno, ':');
+ last_shown = last_hit = lno;
+ }
+ else if (last_hit &&
+ lno <= last_hit + opt->post_context) {
+ /* If the last hit is within the post context,
+ * we need to show this line.
+ */
+ if (last_shown && lno != last_shown + 1)
+ printf(hunk_mark);
+ show_line(opt, bol, eol, name, lno, '-');
+ last_shown = lno;
+ }
+ if (opt->pre_context) {
+ memmove(prev+1, prev,
+ (opt->pre_context-1) * sizeof(*prev));
+ prev->bol = bol;
+ prev->eol = eol;
+ }
+
+ next_line:
+ bol = eol + 1;
+ if (!left)
+ break;
+ left--;
+ lno++;
+ }
+
+ free(prev);
+ if (collect_hits)
+ return 0;
+
+ if (opt->status_only)
+ return 0;
+ if (opt->unmatch_name_only) {
+ /* We did not see any hit, so we want to show this */
+ printf("%s\n", name);
+ return 1;
+ }
+
+ /* NEEDSWORK:
+ * The real "grep -c foo *.c" gives many "bar.c:0" lines,
+ * which feels mostly useless but sometimes useful. Maybe
+ * make it another option? For now suppress them.
+ */
+ if (opt->count && count)
+ printf("%s:%u\n", name, count);
+ return !!last_hit;
+}
+
+static void clr_hit_marker(struct grep_expr *x)
+{
+ /* All-hit markers are meaningful only at the very top level
+ * OR node.
+ */
+ while (1) {
+ x->hit = 0;
+ if (x->node != GREP_NODE_OR)
+ return;
+ x->u.binary.left->hit = 0;
+ x = x->u.binary.right;
+ }
+}
+
+static int chk_hit_marker(struct grep_expr *x)
+{
+ /* Top level nodes have hit markers. See if they all are hits */
+ while (1) {
+ if (x->node != GREP_NODE_OR)
+ return x->hit;
+ if (!x->u.binary.left->hit)
+ return 0;
+ x = x->u.binary.right;
+ }
+}
+
+int grep_buffer(struct grep_opt *opt, const char *name, char *buf, unsigned long size)
+{
+ /*
+ * we do not have to do the two-pass grep when we do not check
+ * buffer-wide "all-match".
+ */
+ if (!opt->all_match)
+ return grep_buffer_1(opt, name, buf, size, 0);
+
+ /* Otherwise the toplevel "or" terms hit a bit differently.
+ * We first clear hit markers from them.
+ */
+ clr_hit_marker(opt->pattern_expression);
+ grep_buffer_1(opt, name, buf, size, 1);
+
+ if (!chk_hit_marker(opt->pattern_expression))
+ return 0;
+
+ return grep_buffer_1(opt, name, buf, size, 0);
+}
diff --git a/grep.h b/grep.h
new file mode 100644
index 0000000000..d252dd25f8
--- /dev/null
+++ b/grep.h
@@ -0,0 +1,81 @@
+#ifndef GREP_H
+#define GREP_H
+
+enum grep_pat_token {
+ GREP_PATTERN,
+ GREP_PATTERN_HEAD,
+ GREP_PATTERN_BODY,
+ GREP_AND,
+ GREP_OPEN_PAREN,
+ GREP_CLOSE_PAREN,
+ GREP_NOT,
+ GREP_OR,
+};
+
+enum grep_context {
+ GREP_CONTEXT_HEAD,
+ GREP_CONTEXT_BODY,
+};
+
+struct grep_pat {
+ struct grep_pat *next;
+ const char *origin;
+ int no;
+ enum grep_pat_token token;
+ const char *pattern;
+ regex_t regexp;
+};
+
+enum grep_expr_node {
+ GREP_NODE_ATOM,
+ GREP_NODE_NOT,
+ GREP_NODE_AND,
+ GREP_NODE_OR,
+};
+
+struct grep_expr {
+ enum grep_expr_node node;
+ unsigned hit;
+ union {
+ struct grep_pat *atom;
+ struct grep_expr *unary;
+ struct {
+ struct grep_expr *left;
+ struct grep_expr *right;
+ } binary;
+ } u;
+};
+
+struct grep_opt {
+ struct grep_pat *pattern_list;
+ struct grep_pat **pattern_tail;
+ struct grep_expr *pattern_expression;
+ int prefix_length;
+ regex_t regexp;
+ unsigned linenum:1;
+ unsigned invert:1;
+ unsigned status_only:1;
+ unsigned name_only:1;
+ unsigned unmatch_name_only:1;
+ unsigned count:1;
+ unsigned word_regexp:1;
+ unsigned fixed:1;
+ unsigned all_match:1;
+#define GREP_BINARY_DEFAULT 0
+#define GREP_BINARY_NOMATCH 1
+#define GREP_BINARY_TEXT 2
+ unsigned binary:2;
+ unsigned extended:1;
+ unsigned relative:1;
+ unsigned pathname:1;
+ int regflags;
+ unsigned pre_context;
+ unsigned post_context;
+};
+
+extern void append_grep_pattern(struct grep_opt *opt, const char *pat, const char *origin, int no, enum grep_pat_token t);
+extern void compile_grep_patterns(struct grep_opt *opt);
+extern void free_grep_patterns(struct grep_opt *opt);
+extern int grep_buffer(struct grep_opt *opt, const char *name, char *buf, unsigned long size);
+
+#endif
diff --git a/help.c b/help.c
index 6484cb9df2..0824c25226 100644
--- a/help.c
+++ b/help.c
@@ -14,7 +14,7 @@
static int term_columns(void)
{
char *col_string = getenv("COLUMNS");
- int n_cols = 0;
+ int n_cols;
if (col_string && (n_cols = atoi(col_string)) > 0)
return n_cols;
@@ -184,7 +184,7 @@ static void show_man_page(const char *git_cmd)
page = git_cmd;
else {
int page_len = strlen(git_cmd) + 4;
- char *p = malloc(page_len + 1);
+ char *p = xmalloc(page_len + 1);
strcpy(p, "git-");
strcpy(p + 4, git_cmd);
p[page_len] = 0;
diff --git a/http-fetch.c b/http-fetch.c
index de5fc44e66..396552da02 100644
--- a/http-fetch.c
+++ b/http-fetch.c
@@ -4,42 +4,13 @@
#include "fetch.h"
#include "http.h"
-#ifndef NO_EXPAT
-#include <expat.h>
-
-/* Definitions for DAV requests */
-#define DAV_PROPFIND "PROPFIND"
-#define DAV_PROPFIND_RESP ".multistatus.response"
-#define DAV_PROPFIND_NAME ".multistatus.response.href"
-#define DAV_PROPFIND_COLLECTION ".multistatus.response.propstat.prop.resourcetype.collection"
-#define PROPFIND_ALL_REQUEST "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n<D:propfind xmlns:D=\"DAV:\">\n<D:allprop/>\n</D:propfind>"
-
-/* Definitions for processing XML DAV responses */
-#ifndef XML_STATUS_OK
-enum XML_Status {
- XML_STATUS_OK = 1,
- XML_STATUS_ERROR = 0
-};
-#define XML_STATUS_OK 1
-#define XML_STATUS_ERROR 0
-#endif
-
-/* 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)
-#endif
-
#define PREV_BUF_SIZE 4096
#define RANGE_HEADER_SIZE 30
-static int commits_on_stdin = 0;
+static int commits_on_stdin;
static int got_alternates = -1;
-static int corrupt_object_found = 0;
+static int corrupt_object_found;
static struct curl_slist *no_pragma_header;
@@ -52,7 +23,7 @@ struct alt_base
struct alt_base *next;
};
-static struct alt_base *alt = NULL;
+static struct alt_base *alt;
enum object_request_state {
WAITING,
@@ -90,31 +61,7 @@ struct alternates_request {
int http_specific;
};
-#ifndef NO_EXPAT
-struct xml_ctx
-{
- char *name;
- int len;
- char *cdata;
- void (*userFunc)(struct xml_ctx *ctx, int tag_closed);
- void *userData;
-};
-
-struct remote_ls_ctx
-{
- struct alt_base *repo;
- char *path;
- void (*userFunc)(struct remote_ls_ctx *ls);
- void *userData;
- int flags;
- char *dentry_name;
- int dentry_flags;
- int rc;
- struct remote_ls_ctx *parent;
-};
-#endif
-
-static struct object_request *object_queue_head = NULL;
+static struct object_request *object_queue_head;
static size_t fwrite_sha1_file(void *ptr, size_t eltsize, size_t nmemb,
void *data)
@@ -144,6 +91,19 @@ static size_t fwrite_sha1_file(void *ptr, size_t eltsize, size_t nmemb,
return size;
}
+static int missing__target(int code, int result)
+{
+ return /* file:// URL -- do we ever use one??? */
+ (result == CURLE_FILE_COULDNT_READ_FILE) ||
+ /* http:// and https:// URL */
+ (code == 404 && result == CURLE_HTTP_RETURNED_ERROR) ||
+ /* ftp:// URL */
+ (code == 550 && result == CURLE_FTP_COULDNT_RETR_FILE)
+ ;
+}
+
+#define missing_target(a) missing__target((a)->http_code, (a)->curl_result)
+
static void fetch_alternates(const char *base);
static void process_object_response(void *callback_data);
@@ -301,7 +261,7 @@ static void finish_object_request(struct object_request *obj_req)
unlink(obj_req->tmpfile);
return;
}
- if (memcmp(obj_req->sha1, obj_req->real_sha1, 20)) {
+ if (hashcmp(obj_req->sha1, obj_req->real_sha1)) {
unlink(obj_req->tmpfile);
return;
}
@@ -323,8 +283,7 @@ static void process_object_response(void *callback_data)
obj_req->state = COMPLETE;
/* Use alternates if necessary */
- if (obj_req->http_code == 404 ||
- obj_req->curl_result == CURLE_FILE_COULDNT_READ_FILE) {
+ if (missing_target(obj_req)) {
fetch_alternates(alt->base);
if (obj_req->repo->next != NULL) {
obj_req->repo =
@@ -393,7 +352,7 @@ void prefetch(unsigned char *sha1)
char *filename = sha1_file_name(sha1);
newreq = xmalloc(sizeof(*newreq));
- memcpy(newreq->sha1, sha1, 20);
+ hashcpy(newreq->sha1, sha1);
newreq->repo = alt;
newreq->url = NULL;
newreq->local = -1;
@@ -537,8 +496,7 @@ static void process_alternates_response(void *callback_data)
return;
}
} else if (slot->curl_result != CURLE_OK) {
- if (slot->http_code != 404 &&
- slot->curl_result != CURLE_FILE_COULDNT_READ_FILE) {
+ if (!missing_target(slot)) {
got_alternates = -1;
return;
}
@@ -559,9 +517,36 @@ static void process_alternates_response(void *callback_data)
char *target = NULL;
char *path;
if (data[i] == '/') {
- serverlen = strchr(base + 8, '/') - base;
- okay = 1;
+ /* This counts
+ * http://git.host/pub/scm/linux.git/
+ * -----------here^
+ * so memcpy(dst, base, serverlen) will
+ * copy up to "...git.host".
+ */
+ const char *colon_ss = strstr(base,"://");
+ if (colon_ss) {
+ serverlen = (strchr(colon_ss + 3, '/')
+ - base);
+ okay = 1;
+ }
} else if (!memcmp(data + i, "../", 3)) {
+ /* Relative URL; chop the corresponding
+ * number of subpath from base (and ../
+ * from data), and concatenate the result.
+ *
+ * The code first drops ../ from data, and
+ * then drops one ../ from data and one path
+ * from base. IOW, one extra ../ is dropped
+ * from data than path is dropped from base.
+ *
+ * This is not wrong. The alternate in
+ * http://git.host/pub/scm/linux.git/
+ * to borrow from
+ * http://git.host/pub/scm/linus.git/
+ * is ../../linus.git/objects/. You need
+ * two ../../ to borrow from your direct
+ * neighbour.
+ */
i += 3;
serverlen = strlen(base);
while (i + 2 < posn &&
@@ -583,11 +568,13 @@ static void process_alternates_response(void *callback_data)
okay = 1;
}
}
- /* skip 'objects' at end */
+ /* skip "objects\n" at end */
if (okay) {
target = xmalloc(serverlen + posn - i - 6);
- strlcpy(target, base, serverlen);
- strlcpy(target + serverlen, data + i, posn - i - 6);
+ memcpy(target, base, serverlen);
+ memcpy(target + serverlen, data + i,
+ posn - i - 7);
+ target[serverlen + posn - i - 7] = 0;
if (get_verbosely)
fprintf(stderr,
"Also look at %s\n", target);
@@ -674,209 +661,6 @@ static void fetch_alternates(const char *base)
free(url);
}
-#ifndef NO_EXPAT
-static void
-xml_start_tag(void *userData, const char *name, const char **atts)
-{
- struct xml_ctx *ctx = (struct xml_ctx *)userData;
- const char *c = strchr(name, ':');
- int new_len;
-
- if (c == NULL)
- c = name;
- else
- c++;
-
- new_len = strlen(ctx->name) + strlen(c) + 2;
-
- if (new_len > ctx->len) {
- ctx->name = xrealloc(ctx->name, new_len);
- ctx->len = new_len;
- }
- strcat(ctx->name, ".");
- strcat(ctx->name, c);
-
- if (ctx->cdata) {
- free(ctx->cdata);
- ctx->cdata = NULL;
- }
-
- ctx->userFunc(ctx, 0);
-}
-
-static void
-xml_end_tag(void *userData, const char *name)
-{
- struct xml_ctx *ctx = (struct xml_ctx *)userData;
- const char *c = strchr(name, ':');
- char *ep;
-
- ctx->userFunc(ctx, 1);
-
- if (c == NULL)
- c = name;
- else
- c++;
-
- ep = ctx->name + strlen(ctx->name) - strlen(c) - 1;
- *ep = 0;
-}
-
-static void
-xml_cdata(void *userData, const XML_Char *s, int len)
-{
- struct xml_ctx *ctx = (struct xml_ctx *)userData;
- if (ctx->cdata)
- free(ctx->cdata);
- ctx->cdata = xmalloc(len + 1);
- strlcpy(ctx->cdata, s, len + 1);
-}
-
-static int remote_ls(struct alt_base *repo, const char *path, int flags,
- void (*userFunc)(struct remote_ls_ctx *ls),
- void *userData);
-
-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) {
- ls->rc = remote_ls(ls->repo,
- 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) -
- ls->repo->path_len + 1);
- strcpy(ls->dentry_name, ctx->cdata + ls->repo->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 int remote_ls(struct alt_base *repo, const char *path, int flags,
- void (*userFunc)(struct remote_ls_ctx *ls),
- void *userData)
-{
- char *url = xmalloc(strlen(repo->base) + 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.repo = repo;
- ls.path = strdup(path);
- ls.dentry_name = NULL;
- ls.dentry_flags = 0;
- ls.userData = userData;
- ls.userFunc = userFunc;
- ls.rc = 0;
-
- sprintf(url, "%s%s", repo->base, 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) {
- ls.rc = error("XML error: %s",
- XML_ErrorString(
- XML_GetErrorCode(parser)));
- }
- } else {
- ls.rc = -1;
- }
- } else {
- ls.rc = error("Unable to start PROPFIND request");
- }
-
- free(ls.path);
- free(url);
- free(out_data);
- free(in_buffer.buffer);
- curl_slist_free_all(dav_headers);
-
- return ls.rc;
-}
-
-static void process_ls_pack(struct remote_ls_ctx *ls)
-{
- unsigned char sha1[20];
-
- if (strlen(ls->dentry_name) == 63 &&
- !strncmp(ls->dentry_name, "objects/pack/pack-", 18) &&
- has_extension(ls->dentry_name, ".pack")) {
- get_sha1_hex(ls->dentry_name + 18, sha1);
- setup_index(ls->repo, sha1);
- }
-}
-#endif
-
static int fetch_indices(struct alt_base *repo)
{
unsigned char sha1[20];
@@ -899,12 +683,6 @@ static int fetch_indices(struct alt_base *repo)
if (get_verbosely)
fprintf(stderr, "Getting pack list for %s\n", repo->base);
-#ifndef NO_EXPAT
- if (remote_ls(repo, "objects/pack/", PROCESS_FILES,
- process_ls_pack, NULL) == 0)
- return 0;
-#endif
-
url = xmalloc(strlen(repo->base) + 21);
sprintf(url, "%s/objects/info/packs", repo->base);
@@ -917,8 +695,7 @@ static int fetch_indices(struct alt_base *repo)
if (start_active_slot(slot)) {
run_active_slot(slot);
if (results.curl_result != CURLE_OK) {
- if (results.http_code == 404 ||
- results.curl_result == CURLE_FILE_COULDNT_READ_FILE) {
+ if (missing_target(&results)) {
repo->got_indices = 1;
free(buffer.buffer);
return 0;
@@ -1070,7 +847,7 @@ static int fetch_object(struct alt_base *repo, unsigned char *sha1)
int ret = 0;
struct object_request *obj_req = object_queue_head;
- while (obj_req != NULL && memcmp(obj_req->sha1, sha1, 20))
+ while (obj_req != NULL && hashcmp(obj_req->sha1, sha1))
obj_req = obj_req->next;
if (obj_req == NULL)
return error("Couldn't find request for %s in the queue", hex);
@@ -1099,8 +876,7 @@ static int fetch_object(struct alt_base *repo, unsigned char *sha1)
ret = error("Request for %s aborted", hex);
} else if (obj_req->curl_result != CURLE_OK &&
obj_req->http_code != 416) {
- if (obj_req->http_code == 404 ||
- obj_req->curl_result == CURLE_FILE_COULDNT_READ_FILE)
+ if (missing_target(obj_req))
ret = -1; /* Be silent, it is probably in a pack. */
else
ret = error("%s (curl_result = %d, http_code = %ld, sha1 = %s)",
@@ -1109,7 +885,7 @@ static int fetch_object(struct alt_base *repo, unsigned char *sha1)
} else if (obj_req->zret != Z_STREAM_END) {
corrupt_object_found++;
ret = error("File %s (%s) corrupt", hex, obj_req->url);
- } else if (memcmp(obj_req->sha1, obj_req->real_sha1, 20)) {
+ } else if (hashcmp(obj_req->sha1, obj_req->real_sha1)) {
ret = error("File %s has bad hash", hex);
} else if (obj_req->rename < 0) {
ret = error("unable to write sha1 filename %s",
diff --git a/http-push.c b/http-push.c
index 0359ae5b6f..ecefdfd4f8 100644
--- a/http-push.c
+++ b/http-push.c
@@ -70,18 +70,18 @@ enum XML_Status {
/* We allow "recursive" symbolic refs. Only within reason, though */
#define MAXDEPTH 5
-static int pushing = 0;
-static int aborted = 0;
+static int pushing;
+static int aborted;
static signed char remote_dir_exists[256];
static struct curl_slist *no_pragma_header;
static struct curl_slist *default_headers;
-static int push_verbosely = 0;
-static int push_all = 0;
-static int force_all = 0;
+static int push_verbosely;
+static int push_all;
+static int force_all;
-static struct object_list *objects = NULL;
+static struct object_list *objects;
struct repo
{
@@ -94,7 +94,7 @@ struct repo
struct remote_lock *locks;
};
-static struct repo *remote = NULL;
+static struct repo *remote;
enum transfer_state {
NEED_FETCH,
@@ -134,7 +134,7 @@ struct transfer_request
struct transfer_request *next;
};
-static struct transfer_request *request_queue_head = NULL;
+static struct transfer_request *request_queue_head;
struct xml_ctx
{
@@ -745,7 +745,7 @@ static void finish_request(struct transfer_request *request)
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)) {
+ } else if (hashcmp(request->obj->sha1, request->real_sha1)) {
unlink(request->tmpfile);
} else {
request->rename =
@@ -1238,10 +1238,8 @@ xml_start_tag(void *userData, const char *name, const char **atts)
strcat(ctx->name, ".");
strcat(ctx->name, c);
- if (ctx->cdata) {
- free(ctx->cdata);
- ctx->cdata = NULL;
- }
+ free(ctx->cdata);
+ ctx->cdata = NULL;
ctx->userFunc(ctx, 0);
}
@@ -1268,8 +1266,7 @@ static void
xml_cdata(void *userData, const XML_Char *s, int len)
{
struct xml_ctx *ctx = (struct xml_ctx *)userData;
- if (ctx->cdata)
- free(ctx->cdata);
+ free(ctx->cdata);
ctx->cdata = xmalloc(len + 1);
strlcpy(ctx->cdata, s, len + 1);
}
@@ -1518,9 +1515,7 @@ static void handle_remote_ls_ctx(struct xml_ctx *ctx, int tag_closed)
ls->dentry_flags |= IS_DIR;
}
} else if (!strcmp(ctx->name, DAV_PROPFIND_RESP)) {
- if (ls->dentry_name) {
- free(ls->dentry_name);
- }
+ free(ls->dentry_name);
ls->dentry_name = NULL;
ls->dentry_flags = 0;
}
@@ -1544,7 +1539,7 @@ static void remote_ls(const char *path, int flags,
struct remote_ls_ctx ls;
ls.flags = flags;
- ls.path = strdup(path);
+ ls.path = xstrdup(path);
ls.dentry_name = NULL;
ls.dentry_flags = 0;
ls.userData = userData;
@@ -1700,7 +1695,7 @@ static int locking_available(void)
return lock_flags;
}
-struct object_list **add_one_object(struct object *obj, struct object_list **p)
+static struct object_list **add_one_object(struct object *obj, struct object_list **p)
{
struct object_list *entry = xmalloc(sizeof(struct object_list));
entry->item = obj;
@@ -1743,7 +1738,7 @@ static struct object_list **process_tree(struct tree *tree,
die("bad tree object %s", sha1_to_hex(obj->sha1));
obj->flags |= SEEN;
- name = strdup(name);
+ name = xstrdup(name);
p = add_one_object(obj, p);
me.up = path;
me.elem = name;
@@ -1869,12 +1864,12 @@ static int update_remote(unsigned char *sha1, struct remote_lock *lock)
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)
+static int one_local_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
{
struct ref *ref;
int len = strlen(refname) + 1;
ref = xcalloc(1, sizeof(*ref) + len);
- memcpy(ref->new_sha1, sha1, 20);
+ hashcpy(ref->new_sha1, sha1);
memcpy(ref->name, refname, len);
*local_tail = ref;
local_tail = &ref->next;
@@ -1909,7 +1904,7 @@ static void one_remote_ref(char *refname)
}
ref = xcalloc(1, sizeof(*ref) + len);
- memcpy(ref->old_sha1, remote_sha1, 20);
+ hashcpy(ref->old_sha1, remote_sha1);
memcpy(ref->name, refname, len);
*remote_tail = ref;
remote_tail = &ref->next;
@@ -1918,7 +1913,7 @@ static void one_remote_ref(char *refname)
static void get_local_heads(void)
{
local_tail = &local_refs;
- for_each_ref(one_local_ref);
+ for_each_ref(one_local_ref, NULL);
}
static void get_dav_remote_heads(void)
@@ -2164,7 +2159,7 @@ static void fetch_symref(const char *path, char **symref, unsigned char *sha1)
if (*symref != NULL)
free(*symref);
*symref = NULL;
- memset(sha1, 0, 20);
+ hashclr(sha1);
if (buffer.posn == 0)
return;
@@ -2186,10 +2181,7 @@ static int verify_merge_base(unsigned char *head_sha1, unsigned char *branch_sha
struct commit *branch = lookup_commit(branch_sha1);
struct commit_list *merge_bases = get_merge_bases(head, branch, 1);
- if (merge_bases && !merge_bases->next && merge_bases->item == branch)
- return 1;
-
- return 0;
+ return (merge_bases && !merge_bases->next && merge_bases->item == branch);
}
static int delete_remote_branch(char *pattern, int force)
@@ -2419,7 +2411,7 @@ int main(int argc, char **argv)
if (!ref->peer_ref)
continue;
- if (!memcmp(ref->old_sha1, ref->peer_ref->new_sha1, 20)) {
+ if (!hashcmp(ref->old_sha1, ref->peer_ref->new_sha1)) {
if (push_verbosely || 1)
fprintf(stderr, "'%s': up-to-date\n", ref->name);
continue;
@@ -2448,7 +2440,7 @@ int main(int argc, char **argv)
continue;
}
}
- memcpy(ref->new_sha1, ref->peer_ref->new_sha1, 20);
+ hashcpy(ref->new_sha1, ref->peer_ref->new_sha1);
if (is_zero_sha1(ref->new_sha1)) {
error("cannot happen anymore");
rc = -3;
@@ -2475,7 +2467,7 @@ int main(int argc, char **argv)
/* Set up revision info for this refspec */
commit_argc = 3;
- new_sha1_hex = strdup(sha1_to_hex(ref->new_sha1));
+ new_sha1_hex = xstrdup(sha1_to_hex(ref->new_sha1));
old_sha1_hex = NULL;
commit_argv[1] = "--objects";
commit_argv[2] = new_sha1_hex;
diff --git a/http.c b/http.c
index 6c1937b676..576740feff 100644
--- a/http.c
+++ b/http.c
@@ -23,6 +23,7 @@ char *ssl_capath = NULL;
char *ssl_cainfo = NULL;
long curl_low_speed_limit = -1;
long curl_low_speed_time = -1;
+int curl_ftp_no_epsv = 0;
struct curl_slist *pragma_header;
@@ -155,6 +156,11 @@ static int http_options(const char *var, const char *value)
return 0;
}
+ if (!strcmp("http.noepsv", var)) {
+ curl_ftp_no_epsv = git_config_bool(var, value);
+ return 0;
+ }
+
/* Fall back on the default ones */
return git_default_config(var, value);
}
@@ -196,6 +202,9 @@ static CURL* get_curl_handle(void)
curl_easy_setopt(result, CURLOPT_USERAGENT, GIT_USER_AGENT);
+ if (curl_ftp_no_epsv)
+ curl_easy_setopt(result, CURLOPT_FTP_USE_EPSV, 0);
+
return result;
}
@@ -251,6 +260,9 @@ void http_init(void)
max_requests = DEFAULT_MAX_REQUESTS;
#endif
+ if (getenv("GIT_CURL_FTP_NO_EPSV"))
+ curl_ftp_no_epsv = 1;
+
#ifndef NO_CURL_EASY_DUPHANDLE
curl_default = get_curl_handle();
#endif
diff --git a/http.h b/http.h
index 9ca16acec2..6e12e41688 100644
--- a/http.h
+++ b/http.h
@@ -22,6 +22,10 @@
#define NO_CURL_EASY_DUPHANDLE
#endif
+#if LIBCURL_VERSION_NUM < 0x070a03
+#define CURLE_HTTP_RETURNED_ERROR CURLE_HTTP_NOT_FOUND
+#endif
+
struct slot_results
{
CURLcode curl_result;
diff --git a/imap-send.c b/imap-send.c
index 65c71c602d..a6a65680ee 100644
--- a/imap-send.c
+++ b/imap-send.c
@@ -110,7 +110,6 @@ 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, ... );
@@ -273,7 +272,7 @@ buffer_gets( buffer_t * b, char **s )
n = b->bytes - start;
if (n)
- memcpy( b->buf, b->buf + start, n );
+ memmove(b->buf, b->buf + start, n);
b->offset -= start;
b->bytes = n;
start = 0;
@@ -372,21 +371,6 @@ free_generic_messages( message_t *msgs )
}
static int
-git_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;
@@ -399,15 +383,6 @@ nfsnprintf( char *buf, int blen, const char *fmt, ... )
return ret;
}
-static int
-nfvasprintf( char **str, const char *fmt, va_list va )
-{
- int ret = git_vasprintf( str, fmt, va );
- if (ret < 0)
- die( "Fatal: Out of memory\n");
- return ret;
-}
-
static struct {
unsigned char i, j, s[256];
} rs;
@@ -1032,7 +1007,7 @@ imap_open_store( imap_server_conf_t *srvc )
* getpass() returns a pointer to a static buffer. make a copy
* for long term storage.
*/
- srvc->pass = strdup( arg );
+ srvc->pass = xstrdup( arg );
}
if (CAP(NOLOGIN)) {
fprintf( stderr, "Skipping account %s@%s, server forbids LOGIN\n", srvc->user, srvc->host );
@@ -1251,6 +1226,14 @@ split_msg( msg_data_t *all_msgs, msg_data_t *msg, int *ofs )
if (msg->len < 5 || strncmp( data, "From ", 5 ))
return 0;
+ p = strchr( data, '\n' );
+ if (p) {
+ p = &p[1];
+ msg->len -= p-data;
+ *ofs += p-data;
+ data = p;
+ }
+
p = strstr( data, "\nFrom " );
if (p)
msg->len = &p[1] - data;
@@ -1288,7 +1271,7 @@ git_imap_config(const char *key, const char *val)
key += sizeof imap_key - 1;
if (!strcmp( "folder", key )) {
- imap_folder = strdup( val );
+ imap_folder = xstrdup( val );
} else if (!strcmp( "host", key )) {
{
if (!strncmp( "imap:", val, 5 ))
@@ -1298,16 +1281,16 @@ git_imap_config(const char *key, const char *val)
}
if (!strncmp( "//", val, 2 ))
val += 2;
- server.host = strdup( val );
+ server.host = xstrdup( val );
}
else if (!strcmp( "user", key ))
- server.user = strdup( val );
+ server.user = xstrdup( val );
else if (!strcmp( "pass", key ))
- server.pass = strdup( val );
+ server.pass = xstrdup( val );
else if (!strcmp( "port", key ))
server.port = git_config_int( key, val );
else if (!strcmp( "tunnel", key ))
- server.tunnel = strdup( val );
+ server.tunnel = xstrdup( val );
return 0;
}
diff --git a/index-pack.c b/index-pack.c
index b20659c259..8331d99a62 100644
--- a/index-pack.c
+++ b/index-pack.c
@@ -6,84 +6,173 @@
#include "commit.h"
#include "tag.h"
#include "tree.h"
+#include <sys/time.h>
+#include <signal.h>
static const char index_pack_usage[] =
-"git-index-pack [-o index-file] pack-file";
+"git-index-pack [-v] [-o <index-file>] [{ ---keep | --keep=<msg> }] { <pack-file> | --stdin [--fix-thin] [<pack-file>] }";
struct object_entry
{
unsigned long offset;
+ unsigned long size;
+ unsigned int hdr_size;
enum object_type type;
enum object_type real_type;
unsigned char sha1[20];
};
+union delta_base {
+ unsigned char sha1[20];
+ unsigned long offset;
+};
+
+/*
+ * Even if sizeof(union delta_base) == 24 on 64-bit archs, we really want
+ * to memcmp() only the first 20 bytes.
+ */
+#define UNION_BASE_SZ 20
+
struct delta_entry
{
- struct object_entry *obj;
- unsigned char base_sha1[20];
+ union delta_base base;
+ int obj_no;
};
-static const char *pack_name;
-static unsigned char *pack_base;
-static unsigned long pack_size;
static struct object_entry *objects;
static struct delta_entry *deltas;
static int nr_objects;
static int nr_deltas;
+static int nr_resolved_deltas;
+
+static int from_stdin;
+static int verbose;
+
+static volatile sig_atomic_t progress_update;
-static void open_pack_file(void)
+static void progress_interval(int signum)
{
- int fd;
- struct stat st;
+ progress_update = 1;
+}
- fd = open(pack_name, O_RDONLY);
- if (fd < 0)
- die("cannot open packfile '%s': %s", pack_name,
- strerror(errno));
- if (fstat(fd, &st)) {
- int err = errno;
- close(fd);
- die("cannot fstat packfile '%s': %s", pack_name,
- strerror(err));
+static void setup_progress_signal(void)
+{
+ struct sigaction sa;
+ struct itimerval v;
+
+ memset(&sa, 0, sizeof(sa));
+ sa.sa_handler = progress_interval;
+ sigemptyset(&sa.sa_mask);
+ sa.sa_flags = SA_RESTART;
+ sigaction(SIGALRM, &sa, NULL);
+
+ v.it_interval.tv_sec = 1;
+ v.it_interval.tv_usec = 0;
+ v.it_value = v.it_interval;
+ setitimer(ITIMER_REAL, &v, NULL);
+
+}
+
+static unsigned display_progress(unsigned n, unsigned total, unsigned last_pc)
+{
+ unsigned percent = n * 100 / total;
+ if (percent != last_pc || progress_update) {
+ fprintf(stderr, "%4u%% (%u/%u) done\r", percent, n, total);
+ progress_update = 0;
}
- pack_size = st.st_size;
- pack_base = mmap(NULL, pack_size, PROT_READ, MAP_PRIVATE, fd, 0);
- if (pack_base == MAP_FAILED) {
- int err = errno;
- close(fd);
- die("cannot mmap packfile '%s': %s", pack_name,
- strerror(err));
+ return percent;
+}
+
+/* We always read in 4kB chunks. */
+static unsigned char input_buffer[4096];
+static unsigned long input_offset, input_len, consumed_bytes;
+static SHA_CTX input_ctx;
+static int input_fd, output_fd, mmap_fd;
+
+/* Discard current buffer used content. */
+static void flush(void)
+{
+ if (input_offset) {
+ if (output_fd >= 0)
+ write_or_die(output_fd, input_buffer, input_offset);
+ SHA1_Update(&input_ctx, input_buffer, input_offset);
+ memcpy(input_buffer, input_buffer + input_offset, input_len);
+ input_offset = 0;
}
- close(fd);
}
-static void parse_pack_header(void)
+/*
+ * Make sure at least "min" bytes are available in the buffer, and
+ * return the pointer to the buffer.
+ */
+static void *fill(int min)
{
- const struct pack_header *hdr;
- unsigned char sha1[20];
- SHA_CTX ctx;
+ if (min <= input_len)
+ return input_buffer + input_offset;
+ if (min > sizeof(input_buffer))
+ die("cannot fill %d bytes", min);
+ flush();
+ do {
+ int ret = xread(input_fd, input_buffer + input_len,
+ sizeof(input_buffer) - input_len);
+ if (ret <= 0) {
+ if (!ret)
+ die("early EOF");
+ die("read error on input: %s", strerror(errno));
+ }
+ input_len += ret;
+ } while (input_len < min);
+ return input_buffer;
+}
+
+static void use(int bytes)
+{
+ if (bytes > input_len)
+ die("used more bytes than were available");
+ input_len -= bytes;
+ input_offset += bytes;
+ consumed_bytes += bytes;
+}
+
+static const char *open_pack_file(const char *pack_name)
+{
+ if (from_stdin) {
+ input_fd = 0;
+ if (!pack_name) {
+ static char tmpfile[PATH_MAX];
+ snprintf(tmpfile, sizeof(tmpfile),
+ "%s/pack_XXXXXX", get_object_directory());
+ output_fd = mkstemp(tmpfile);
+ pack_name = xstrdup(tmpfile);
+ } else
+ output_fd = open(pack_name, O_CREAT|O_EXCL|O_RDWR, 0600);
+ if (output_fd < 0)
+ die("unable to create %s: %s\n", pack_name, strerror(errno));
+ mmap_fd = output_fd;
+ } else {
+ input_fd = open(pack_name, O_RDONLY);
+ if (input_fd < 0)
+ die("cannot open packfile '%s': %s",
+ pack_name, strerror(errno));
+ output_fd = -1;
+ mmap_fd = input_fd;
+ }
+ SHA1_Init(&input_ctx);
+ return pack_name;
+}
- /* Ensure there are enough bytes for the header and final SHA1 */
- if (pack_size < sizeof(struct pack_header) + 20)
- die("packfile '%s' is too small", pack_name);
+static void parse_pack_header(void)
+{
+ struct pack_header *hdr = fill(sizeof(struct pack_header));
/* Header consistency check */
- hdr = (void *)pack_base;
if (hdr->hdr_signature != htonl(PACK_SIGNATURE))
- die("packfile '%s' signature mismatch", pack_name);
+ die("pack signature mismatch");
if (!pack_version_ok(hdr->hdr_version))
- die("packfile '%s' version %d unsupported",
- pack_name, ntohl(hdr->hdr_version));
+ die("pack version %d unsupported", ntohl(hdr->hdr_version));
nr_objects = ntohl(hdr->hdr_entries);
-
- /* Check packfile integrity */
- SHA1_Init(&ctx);
- SHA1_Update(&ctx, pack_base, pack_size - 20);
- SHA1_Final(sha1, &ctx);
- if (memcmp(sha1, pack_base + pack_size - 20, 20))
- die("packfile '%s' SHA1 mismatch", pack_name);
+ use(sizeof(struct pack_header));
}
static void bad_object(unsigned long offset, const char *format,
@@ -97,90 +186,124 @@ static void bad_object(unsigned long offset, const char *format, ...)
va_start(params, format);
vsnprintf(buf, sizeof(buf), format, params);
va_end(params);
- die("packfile '%s': bad object at offset %lu: %s",
- pack_name, offset, buf);
+ die("pack has bad object at offset %lu: %s", offset, buf);
}
-static void *unpack_entry_data(unsigned long offset,
- unsigned long *current_pos, unsigned long size)
+static void *unpack_entry_data(unsigned long offset, unsigned long size)
{
- unsigned long pack_limit = pack_size - 20;
- unsigned long pos = *current_pos;
z_stream stream;
void *buf = xmalloc(size);
memset(&stream, 0, sizeof(stream));
stream.next_out = buf;
stream.avail_out = size;
- stream.next_in = pack_base + pos;
- stream.avail_in = pack_limit - pos;
+ stream.next_in = fill(1);
+ stream.avail_in = input_len;
inflateInit(&stream);
for (;;) {
int ret = inflate(&stream, 0);
- if (ret == Z_STREAM_END)
+ use(input_len - stream.avail_in);
+ if (stream.total_out == size && ret == Z_STREAM_END)
break;
if (ret != Z_OK)
bad_object(offset, "inflate returned %d", ret);
+ stream.next_in = fill(1);
+ stream.avail_in = input_len;
}
inflateEnd(&stream);
- if (stream.total_out != size)
- bad_object(offset, "size mismatch (expected %lu, got %lu)",
- size, stream.total_out);
- *current_pos = pack_limit - stream.avail_in;
return buf;
}
-static void *unpack_raw_entry(unsigned long offset,
- enum object_type *obj_type,
- unsigned long *obj_size,
- unsigned char *delta_base,
- unsigned long *next_obj_offset)
+static void *unpack_raw_entry(struct object_entry *obj, union delta_base *delta_base)
{
- unsigned long pack_limit = pack_size - 20;
- unsigned long pos = offset;
- unsigned char c;
- unsigned long size;
+ unsigned char *p, c;
+ unsigned long size, base_offset;
unsigned shift;
- enum object_type type;
- void *data;
- c = pack_base[pos++];
- type = (c >> 4) & 7;
+ obj->offset = consumed_bytes;
+
+ p = fill(1);
+ c = *p;
+ use(1);
+ obj->type = (c >> 4) & 7;
size = (c & 15);
shift = 4;
while (c & 0x80) {
- if (pos >= pack_limit)
- bad_object(offset, "object extends past end of pack");
- c = pack_base[pos++];
+ p = fill(1);
+ c = *p;
+ use(1);
size += (c & 0x7fUL) << shift;
shift += 7;
}
+ obj->size = size;
- switch (type) {
- case OBJ_DELTA:
- if (pos + 20 >= pack_limit)
- bad_object(offset, "object extends past end of pack");
- memcpy(delta_base, pack_base + pos, 20);
- pos += 20;
- /* fallthru */
+ switch (obj->type) {
+ case OBJ_REF_DELTA:
+ hashcpy(delta_base->sha1, fill(20));
+ use(20);
+ break;
+ case OBJ_OFS_DELTA:
+ memset(delta_base, 0, sizeof(*delta_base));
+ p = fill(1);
+ c = *p;
+ use(1);
+ base_offset = c & 127;
+ while (c & 128) {
+ base_offset += 1;
+ if (!base_offset || base_offset & ~(~0UL >> 7))
+ bad_object(obj->offset, "offset value overflow for delta base object");
+ p = fill(1);
+ c = *p;
+ use(1);
+ base_offset = (base_offset << 7) + (c & 127);
+ }
+ delta_base->offset = obj->offset - base_offset;
+ if (delta_base->offset >= obj->offset)
+ bad_object(obj->offset, "delta base offset is out of bound");
+ break;
case OBJ_COMMIT:
case OBJ_TREE:
case OBJ_BLOB:
case OBJ_TAG:
- data = unpack_entry_data(offset, &pos, size);
break;
default:
- bad_object(offset, "bad object type %d", type);
+ bad_object(obj->offset, "bad object type %d", obj->type);
}
+ obj->hdr_size = consumed_bytes - obj->offset;
- *obj_type = type;
- *obj_size = size;
- *next_obj_offset = pos;
+ return unpack_entry_data(obj->offset, obj->size);
+}
+
+static void *get_data_from_pack(struct object_entry *obj)
+{
+ unsigned long from = obj[0].offset + obj[0].hdr_size;
+ unsigned long len = obj[1].offset - from;
+ unsigned pg_offset = from % getpagesize();
+ unsigned char *map, *data;
+ z_stream stream;
+ int st;
+
+ map = mmap(NULL, len + pg_offset, PROT_READ, MAP_PRIVATE,
+ mmap_fd, from - pg_offset);
+ if (map == MAP_FAILED)
+ die("cannot mmap pack file: %s", strerror(errno));
+ data = xmalloc(obj->size);
+ memset(&stream, 0, sizeof(stream));
+ stream.next_out = data;
+ stream.avail_out = obj->size;
+ stream.next_in = map + pg_offset;
+ stream.avail_in = len;
+ inflateInit(&stream);
+ while ((st = inflate(&stream, Z_FINISH)) == Z_OK);
+ inflateEnd(&stream);
+ if (st != Z_STREAM_END || stream.total_out != obj->size)
+ die("serious inflate inconsistency");
+ munmap(map, len + pg_offset);
return data;
}
-static int find_delta(const unsigned char *base_sha1)
+static int find_delta(const union delta_base *base)
{
int first = 0, last = nr_deltas;
@@ -189,7 +312,7 @@ static int find_delta(const unsigned char *base_sha1)
struct delta_entry *delta = &deltas[next];
int cmp;
- cmp = memcmp(base_sha1, delta->base_sha1, 20);
+ cmp = memcmp(base, &delta->base, UNION_BASE_SZ);
if (!cmp)
return next;
if (cmp < 0) {
@@ -201,18 +324,18 @@ static int find_delta(const unsigned char *base_sha1)
return -first-1;
}
-static int find_deltas_based_on_sha1(const unsigned char *base_sha1,
- int *first_index, int *last_index)
+static int find_delta_children(const union delta_base *base,
+ int *first_index, int *last_index)
{
- int first = find_delta(base_sha1);
+ int first = find_delta(base);
int last = first;
int end = nr_deltas - 1;
if (first < 0)
return -1;
- while (first > 0 && !memcmp(deltas[first-1].base_sha1, base_sha1, 20))
+ while (first > 0 && !memcmp(&deltas[first - 1].base, base, UNION_BASE_SZ))
--first;
- while (last < end && !memcmp(deltas[last+1].base_sha1, base_sha1, 20))
+ while (last < end && !memcmp(&deltas[last + 1].base, base, UNION_BASE_SZ))
++last;
*first_index = first;
*last_index = last;
@@ -244,33 +367,46 @@ static void sha1_object(const void *data, unsigned long size,
SHA1_Final(sha1, &ctx);
}
-static void resolve_delta(struct delta_entry *delta, void *base_data,
+static void resolve_delta(struct object_entry *delta_obj, void *base_data,
unsigned long base_size, enum object_type type)
{
- struct object_entry *obj = delta->obj;
void *delta_data;
unsigned long delta_size;
void *result;
unsigned long result_size;
- enum object_type delta_type;
- unsigned char base_sha1[20];
- unsigned long next_obj_offset;
+ union delta_base delta_base;
int j, first, last;
- obj->real_type = type;
- delta_data = unpack_raw_entry(obj->offset, &delta_type,
- &delta_size, base_sha1,
- &next_obj_offset);
+ delta_obj->real_type = type;
+ delta_data = get_data_from_pack(delta_obj);
+ delta_size = delta_obj->size;
result = patch_delta(base_data, base_size, delta_data, delta_size,
&result_size);
free(delta_data);
if (!result)
- bad_object(obj->offset, "failed to apply delta");
- sha1_object(result, result_size, type, obj->sha1);
- if (!find_deltas_based_on_sha1(obj->sha1, &first, &last)) {
- for (j = first; j <= last; j++)
- resolve_delta(&deltas[j], result, result_size, type);
+ bad_object(delta_obj->offset, "failed to apply delta");
+ sha1_object(result, result_size, type, delta_obj->sha1);
+ nr_resolved_deltas++;
+
+ hashcpy(delta_base.sha1, delta_obj->sha1);
+ if (!find_delta_children(&delta_base, &first, &last)) {
+ for (j = first; j <= last; j++) {
+ struct object_entry *child = objects + deltas[j].obj_no;
+ if (child->real_type == OBJ_REF_DELTA)
+ resolve_delta(child, result, result_size, type);
+ }
+ }
+
+ memset(&delta_base, 0, sizeof(delta_base));
+ delta_base.offset = delta_obj->offset;
+ if (!find_delta_children(&delta_base, &first, &last)) {
+ for (j = first; j <= last; j++) {
+ struct object_entry *child = objects + deltas[j].obj_no;
+ if (child->real_type == OBJ_OFS_DELTA)
+ resolve_delta(child, result, result_size, type);
+ }
}
+
free(result);
}
@@ -278,41 +414,60 @@ static int compare_delta_entry(const void *a, const void *b)
{
const struct delta_entry *delta_a = a;
const struct delta_entry *delta_b = b;
- return memcmp(delta_a->base_sha1, delta_b->base_sha1, 20);
+ return memcmp(&delta_a->base, &delta_b->base, UNION_BASE_SZ);
}
-static void parse_pack_objects(void)
+/* Parse all objects and return the pack content SHA1 hash */
+static void parse_pack_objects(unsigned char *sha1)
{
- int i;
- unsigned long offset = sizeof(struct pack_header);
- unsigned char base_sha1[20];
+ int i, percent = -1;
+ struct delta_entry *delta = deltas;
void *data;
- unsigned long data_size;
+ struct stat st;
/*
* First pass:
* - find locations of all objects;
* - calculate SHA1 of all non-delta objects;
- * - remember base SHA1 for all deltas.
+ * - remember base (SHA1 or offset) for all deltas.
*/
+ if (verbose)
+ fprintf(stderr, "Indexing %d objects.\n", nr_objects);
for (i = 0; i < nr_objects; i++) {
struct object_entry *obj = &objects[i];
- obj->offset = offset;
- data = unpack_raw_entry(offset, &obj->type, &data_size,
- base_sha1, &offset);
+ data = unpack_raw_entry(obj, &delta->base);
obj->real_type = obj->type;
- if (obj->type == OBJ_DELTA) {
- struct delta_entry *delta = &deltas[nr_deltas++];
- delta->obj = obj;
- memcpy(delta->base_sha1, base_sha1, 20);
+ if (obj->type == OBJ_REF_DELTA || obj->type == OBJ_OFS_DELTA) {
+ nr_deltas++;
+ delta->obj_no = i;
+ delta++;
} else
- sha1_object(data, data_size, obj->type, obj->sha1);
+ sha1_object(data, obj->size, obj->type, obj->sha1);
free(data);
+ if (verbose)
+ percent = display_progress(i+1, nr_objects, percent);
}
- if (offset != pack_size - 20)
- die("packfile '%s' has junk at the end", pack_name);
-
- /* Sort deltas by base SHA1 for fast searching */
+ objects[i].offset = consumed_bytes;
+ if (verbose)
+ fputc('\n', stderr);
+
+ /* Check pack integrity */
+ flush();
+ SHA1_Final(sha1, &input_ctx);
+ if (hashcmp(fill(20), sha1))
+ die("pack is corrupted (SHA1 mismatch)");
+ use(20);
+
+ /* If input_fd is a file, we should have reached its end now. */
+ if (fstat(input_fd, &st))
+ die("cannot fstat packfile: %s", strerror(errno));
+ if (S_ISREG(st.st_mode) && st.st_size != consumed_bytes)
+ die("pack has junk at the end");
+
+ if (!nr_deltas)
+ return;
+
+ /* Sort deltas by base SHA1/offset for fast searching */
qsort(deltas, nr_deltas, sizeof(struct delta_entry),
compare_delta_entry);
@@ -324,41 +479,208 @@ static void parse_pack_objects(void)
* recursively checking if the resulting object is used as a base
* for some more deltas.
*/
+ if (verbose)
+ fprintf(stderr, "Resolving %d deltas.\n", nr_deltas);
for (i = 0; i < nr_objects; i++) {
struct object_entry *obj = &objects[i];
- int j, first, last;
+ union delta_base base;
+ int j, ref, ref_first, ref_last, ofs, ofs_first, ofs_last;
- if (obj->type == OBJ_DELTA)
+ if (obj->type == OBJ_REF_DELTA || obj->type == OBJ_OFS_DELTA)
continue;
- if (find_deltas_based_on_sha1(obj->sha1, &first, &last))
+ hashcpy(base.sha1, obj->sha1);
+ ref = !find_delta_children(&base, &ref_first, &ref_last);
+ memset(&base, 0, sizeof(base));
+ base.offset = obj->offset;
+ ofs = !find_delta_children(&base, &ofs_first, &ofs_last);
+ if (!ref && !ofs)
continue;
- data = unpack_raw_entry(obj->offset, &obj->type, &data_size,
- base_sha1, &offset);
- for (j = first; j <= last; j++)
- resolve_delta(&deltas[j], data, data_size, obj->type);
+ data = get_data_from_pack(obj);
+ if (ref)
+ for (j = ref_first; j <= ref_last; j++) {
+ struct object_entry *child = objects + deltas[j].obj_no;
+ if (child->real_type == OBJ_REF_DELTA)
+ resolve_delta(child, data,
+ obj->size, obj->type);
+ }
+ if (ofs)
+ for (j = ofs_first; j <= ofs_last; j++) {
+ struct object_entry *child = objects + deltas[j].obj_no;
+ if (child->real_type == OBJ_OFS_DELTA)
+ resolve_delta(child, data,
+ obj->size, obj->type);
+ }
free(data);
+ if (verbose)
+ percent = display_progress(nr_resolved_deltas,
+ nr_deltas, percent);
}
+ if (verbose && nr_resolved_deltas == nr_deltas)
+ fputc('\n', stderr);
+}
+
+static int write_compressed(int fd, void *in, unsigned int size)
+{
+ z_stream stream;
+ unsigned long maxsize;
+ void *out;
+
+ memset(&stream, 0, sizeof(stream));
+ deflateInit(&stream, zlib_compression_level);
+ maxsize = deflateBound(&stream, size);
+ out = xmalloc(maxsize);
+
+ /* Compress it */
+ stream.next_in = in;
+ stream.avail_in = size;
+ stream.next_out = out;
+ stream.avail_out = maxsize;
+ while (deflate(&stream, Z_FINISH) == Z_OK);
+ deflateEnd(&stream);
+
+ size = stream.total_out;
+ write_or_die(fd, out, size);
+ free(out);
+ return size;
+}
+
+static void append_obj_to_pack(void *buf,
+ unsigned long size, enum object_type type)
+{
+ struct object_entry *obj = &objects[nr_objects++];
+ unsigned char header[10];
+ unsigned long s = size;
+ int n = 0;
+ unsigned char c = (type << 4) | (s & 15);
+ s >>= 4;
+ while (s) {
+ header[n++] = c | 0x80;
+ c = s & 0x7f;
+ s >>= 7;
+ }
+ header[n++] = c;
+ write_or_die(output_fd, header, n);
+ obj[1].offset = obj[0].offset + n;
+ obj[1].offset += write_compressed(output_fd, buf, size);
+ sha1_object(buf, size, type, obj->sha1);
+}
+
+static int delta_pos_compare(const void *_a, const void *_b)
+{
+ struct delta_entry *a = *(struct delta_entry **)_a;
+ struct delta_entry *b = *(struct delta_entry **)_b;
+ return a->obj_no - b->obj_no;
+}
+
+static void fix_unresolved_deltas(int nr_unresolved)
+{
+ struct delta_entry **sorted_by_pos;
+ int i, n = 0, percent = -1;
- /* Check for unresolved deltas */
+ /*
+ * Since many unresolved deltas may well be themselves base objects
+ * for more unresolved deltas, we really want to include the
+ * smallest number of base objects that would cover as much delta
+ * as possible by picking the
+ * trunc deltas first, allowing for other deltas to resolve without
+ * additional base objects. Since most base objects are to be found
+ * before deltas depending on them, a good heuristic is to start
+ * resolving deltas in the same order as their position in the pack.
+ */
+ sorted_by_pos = xmalloc(nr_unresolved * sizeof(*sorted_by_pos));
for (i = 0; i < nr_deltas; i++) {
- if (deltas[i].obj->real_type == OBJ_DELTA)
- die("packfile '%s' has unresolved deltas", pack_name);
+ if (objects[deltas[i].obj_no].real_type != OBJ_REF_DELTA)
+ continue;
+ sorted_by_pos[n++] = &deltas[i];
+ }
+ qsort(sorted_by_pos, n, sizeof(*sorted_by_pos), delta_pos_compare);
+
+ for (i = 0; i < n; i++) {
+ struct delta_entry *d = sorted_by_pos[i];
+ void *data;
+ unsigned long size;
+ char type[10];
+ enum object_type obj_type;
+ int j, first, last;
+
+ if (objects[d->obj_no].real_type != OBJ_REF_DELTA)
+ continue;
+ data = read_sha1_file(d->base.sha1, type, &size);
+ if (!data)
+ continue;
+ if (!strcmp(type, blob_type)) obj_type = OBJ_BLOB;
+ else if (!strcmp(type, tree_type)) obj_type = OBJ_TREE;
+ else if (!strcmp(type, commit_type)) obj_type = OBJ_COMMIT;
+ else if (!strcmp(type, tag_type)) obj_type = OBJ_TAG;
+ else die("base object %s is of type '%s'",
+ sha1_to_hex(d->base.sha1), type);
+
+ find_delta_children(&d->base, &first, &last);
+ for (j = first; j <= last; j++) {
+ struct object_entry *child = objects + deltas[j].obj_no;
+ if (child->real_type == OBJ_REF_DELTA)
+ resolve_delta(child, data, size, obj_type);
+ }
+
+ append_obj_to_pack(data, size, obj_type);
+ free(data);
+ if (verbose)
+ percent = display_progress(nr_resolved_deltas,
+ nr_deltas, percent);
}
+ free(sorted_by_pos);
+ if (verbose)
+ fputc('\n', stderr);
+}
+
+static void readjust_pack_header_and_sha1(unsigned char *sha1)
+{
+ struct pack_header hdr;
+ SHA_CTX ctx;
+ int size;
+
+ /* Rewrite pack header with updated object number */
+ if (lseek(output_fd, 0, SEEK_SET) != 0)
+ die("cannot seek back: %s", strerror(errno));
+ if (xread(output_fd, &hdr, sizeof(hdr)) != sizeof(hdr))
+ die("cannot read pack header back: %s", strerror(errno));
+ hdr.hdr_entries = htonl(nr_objects);
+ if (lseek(output_fd, 0, SEEK_SET) != 0)
+ die("cannot seek back: %s", strerror(errno));
+ write_or_die(output_fd, &hdr, sizeof(hdr));
+ if (lseek(output_fd, 0, SEEK_SET) != 0)
+ die("cannot seek back: %s", strerror(errno));
+
+ /* Recompute and store the new pack's SHA1 */
+ SHA1_Init(&ctx);
+ do {
+ unsigned char *buf[4096];
+ size = xread(output_fd, buf, sizeof(buf));
+ if (size < 0)
+ die("cannot read pack data back: %s", strerror(errno));
+ SHA1_Update(&ctx, buf, size);
+ } while (size > 0);
+ SHA1_Final(sha1, &ctx);
+ write_or_die(output_fd, sha1, 20);
}
static int sha1_compare(const void *_a, const void *_b)
{
struct object_entry *a = *(struct object_entry **)_a;
struct object_entry *b = *(struct object_entry **)_b;
- return memcmp(a->sha1, b->sha1, 20);
+ return hashcmp(a->sha1, b->sha1);
}
-static void write_index_file(const char *index_name, unsigned char *sha1)
+/*
+ * On entry *sha1 contains the pack content SHA1 hash, on exit it is
+ * the SHA1 hash of sorted object names.
+ */
+static const char *write_index_file(const char *index_name, unsigned char *sha1)
{
struct sha1file *f;
struct object_entry **sorted_by_sha, **list, **last;
unsigned int array[256];
- int i;
+ int i, fd;
SHA_CTX ctx;
if (nr_objects) {
@@ -375,8 +697,19 @@ static void write_index_file(const char *index_name, unsigned char *sha1)
else
sorted_by_sha = list = last = NULL;
- unlink(index_name);
- f = sha1create("%s", index_name);
+ if (!index_name) {
+ static char tmpfile[PATH_MAX];
+ snprintf(tmpfile, sizeof(tmpfile),
+ "%s/index_XXXXXX", get_object_directory());
+ fd = mkstemp(tmpfile);
+ index_name = xstrdup(tmpfile);
+ } else {
+ unlink(index_name);
+ fd = open(index_name, O_CREAT|O_EXCL|O_WRONLY, 0600);
+ }
+ if (fd < 0)
+ die("unable to create %s: %s", index_name, strerror(errno));
+ f = sha1fd(fd, index_name);
/*
* Write the first-level table (the list is sorted,
@@ -412,24 +745,132 @@ static void write_index_file(const char *index_name, unsigned char *sha1)
sha1write(f, obj->sha1, 20);
SHA1_Update(&ctx, obj->sha1, 20);
}
- sha1write(f, pack_base + pack_size - 20, 20);
+ sha1write(f, sha1, 20);
sha1close(f, NULL, 1);
free(sorted_by_sha);
SHA1_Final(sha1, &ctx);
+ return index_name;
+}
+
+static void final(const char *final_pack_name, const char *curr_pack_name,
+ const char *final_index_name, const char *curr_index_name,
+ const char *keep_name, const char *keep_msg,
+ unsigned char *sha1)
+{
+ char *report = "pack";
+ char name[PATH_MAX];
+ int err;
+
+ if (!from_stdin) {
+ close(input_fd);
+ } else {
+ err = close(output_fd);
+ if (err)
+ die("error while closing pack file: %s", strerror(errno));
+ chmod(curr_pack_name, 0444);
+ }
+
+ if (keep_msg) {
+ int keep_fd, keep_msg_len = strlen(keep_msg);
+ if (!keep_name) {
+ snprintf(name, sizeof(name), "%s/pack/pack-%s.keep",
+ get_object_directory(), sha1_to_hex(sha1));
+ keep_name = name;
+ }
+ keep_fd = open(keep_name, O_RDWR|O_CREAT|O_EXCL, 0600);
+ if (keep_fd < 0) {
+ if (errno != EEXIST)
+ die("cannot write keep file");
+ } else {
+ if (keep_msg_len > 0) {
+ write_or_die(keep_fd, keep_msg, keep_msg_len);
+ write_or_die(keep_fd, "\n", 1);
+ }
+ close(keep_fd);
+ report = "keep";
+ }
+ }
+
+ if (final_pack_name != curr_pack_name) {
+ if (!final_pack_name) {
+ snprintf(name, sizeof(name), "%s/pack/pack-%s.pack",
+ get_object_directory(), sha1_to_hex(sha1));
+ final_pack_name = name;
+ }
+ if (move_temp_to_file(curr_pack_name, final_pack_name))
+ die("cannot store pack file");
+ }
+
+ chmod(curr_index_name, 0444);
+ if (final_index_name != curr_index_name) {
+ if (!final_index_name) {
+ snprintf(name, sizeof(name), "%s/pack/pack-%s.idx",
+ get_object_directory(), sha1_to_hex(sha1));
+ final_index_name = name;
+ }
+ if (move_temp_to_file(curr_index_name, final_index_name))
+ die("cannot store index file");
+ }
+
+ if (!from_stdin) {
+ printf("%s\n", sha1_to_hex(sha1));
+ } else {
+ char buf[48];
+ int len = snprintf(buf, sizeof(buf), "%s\t%s\n",
+ report, sha1_to_hex(sha1));
+ xwrite(1, buf, len);
+
+ /*
+ * Let's just mimic git-unpack-objects here and write
+ * the last part of the input buffer to stdout.
+ */
+ while (input_len) {
+ err = xwrite(1, input_buffer + input_offset, input_len);
+ if (err <= 0)
+ break;
+ input_len -= err;
+ input_offset += err;
+ }
+ }
}
int main(int argc, char **argv)
{
- int i;
- char *index_name = NULL;
- char *index_name_buf = NULL;
+ int i, fix_thin_pack = 0;
+ const char *curr_pack, *pack_name = NULL;
+ const char *curr_index, *index_name = NULL;
+ const char *keep_name = NULL, *keep_msg = NULL;
+ char *index_name_buf = NULL, *keep_name_buf = NULL;
unsigned char sha1[20];
for (i = 1; i < argc; i++) {
const char *arg = argv[i];
if (*arg == '-') {
- if (!strcmp(arg, "-o")) {
+ if (!strcmp(arg, "--stdin")) {
+ from_stdin = 1;
+ } else if (!strcmp(arg, "--fix-thin")) {
+ fix_thin_pack = 1;
+ } else if (!strcmp(arg, "--keep")) {
+ keep_msg = "";
+ } else if (!strncmp(arg, "--keep=", 7)) {
+ keep_msg = arg + 7;
+ } else if (!strncmp(arg, "--pack_header=", 14)) {
+ struct pack_header *hdr;
+ char *c;
+
+ hdr = (struct pack_header *)input_buffer;
+ hdr->hdr_signature = htonl(PACK_SIGNATURE);
+ hdr->hdr_version = htonl(strtoul(arg + 14, &c, 10));
+ if (*c != ',')
+ die("bad %s", arg);
+ hdr->hdr_entries = htonl(strtoul(c + 1, &c, 10));
+ if (*c)
+ die("bad %s", arg);
+ input_len = sizeof(*hdr);
+ } else if (!strcmp(arg, "-v")) {
+ verbose = 1;
+ } else if (!strcmp(arg, "-o")) {
if (index_name || (i+1) >= argc)
usage(index_pack_usage);
index_name = argv[++i];
@@ -443,9 +884,11 @@ int main(int argc, char **argv)
pack_name = arg;
}
- if (!pack_name)
+ if (!pack_name && !from_stdin)
usage(index_pack_usage);
- if (!index_name) {
+ if (fix_thin_pack && !from_stdin)
+ die("--fix-thin cannot be used without --stdin");
+ if (!index_name && pack_name) {
int len = strlen(pack_name);
if (!has_extension(pack_name, ".pack"))
die("packfile name '%s' does not end with '.pack'",
@@ -455,18 +898,55 @@ int main(int argc, char **argv)
strcpy(index_name_buf + len - 5, ".idx");
index_name = index_name_buf;
}
+ if (keep_msg && !keep_name && pack_name) {
+ int len = strlen(pack_name);
+ if (!has_extension(pack_name, ".pack"))
+ die("packfile name '%s' does not end with '.pack'",
+ pack_name);
+ keep_name_buf = xmalloc(len);
+ memcpy(keep_name_buf, pack_name, len - 5);
+ strcpy(keep_name_buf + len - 5, ".keep");
+ keep_name = keep_name_buf;
+ }
- open_pack_file();
+ curr_pack = open_pack_file(pack_name);
parse_pack_header();
- objects = xcalloc(nr_objects, sizeof(struct object_entry));
- deltas = xcalloc(nr_objects, sizeof(struct delta_entry));
- parse_pack_objects();
+ objects = xmalloc((nr_objects + 1) * sizeof(struct object_entry));
+ deltas = xmalloc(nr_objects * sizeof(struct delta_entry));
+ if (verbose)
+ setup_progress_signal();
+ parse_pack_objects(sha1);
+ if (nr_deltas != nr_resolved_deltas) {
+ if (fix_thin_pack) {
+ int nr_unresolved = nr_deltas - nr_resolved_deltas;
+ int nr_objects_initial = nr_objects;
+ if (nr_unresolved <= 0)
+ die("confusion beyond insanity");
+ objects = xrealloc(objects,
+ (nr_objects + nr_unresolved + 1)
+ * sizeof(*objects));
+ fix_unresolved_deltas(nr_unresolved);
+ if (verbose)
+ fprintf(stderr, "%d objects were added to complete this thin pack.\n",
+ nr_objects - nr_objects_initial);
+ readjust_pack_header_and_sha1(sha1);
+ }
+ if (nr_deltas != nr_resolved_deltas)
+ die("pack has %d unresolved deltas",
+ nr_deltas - nr_resolved_deltas);
+ } else {
+ /* Flush remaining pack final 20-byte SHA1. */
+ flush();
+ }
free(deltas);
- write_index_file(index_name, sha1);
+ curr_index = write_index_file(index_name, sha1);
+ final(pack_name, curr_pack,
+ index_name, curr_index,
+ keep_name, keep_msg,
+ sha1);
free(objects);
free(index_name_buf);
-
- printf("%s\n", sha1_to_hex(sha1));
+ free(keep_name_buf);
return 0;
}
diff --git a/interpolate.c b/interpolate.c
new file mode 100644
index 0000000000..5d9d1889f0
--- /dev/null
+++ b/interpolate.c
@@ -0,0 +1,108 @@
+/*
+ * Copyright 2006 Jon Loeliger
+ */
+
+#include <string.h>
+
+#include "git-compat-util.h"
+#include "interpolate.h"
+
+
+void interp_set_entry(struct interp *table, int slot, const char *value)
+{
+ char *oldval = table[slot].value;
+ char *newval = NULL;
+
+ if (oldval)
+ free(oldval);
+
+ if (value)
+ newval = xstrdup(value);
+
+ table[slot].value = newval;
+}
+
+
+void interp_clear_table(struct interp *table, int ninterps)
+{
+ int i;
+
+ for (i = 0; i < ninterps; i++) {
+ interp_set_entry(table, i, NULL);
+ }
+}
+
+
+/*
+ * Convert a NUL-terminated string in buffer orig
+ * into the supplied buffer, result, whose length is reslen,
+ * performing substitutions on %-named sub-strings from
+ * the table, interps, with ninterps entries.
+ *
+ * Example interps:
+ * {
+ * { "%H", "example.org"},
+ * { "%port", "123"},
+ * { "%%", "%"},
+ * }
+ *
+ * Returns 1 on a successful substitution pass that fits in result,
+ * Returns 0 on a failed or overflowing substitution pass.
+ */
+
+int interpolate(char *result, int reslen,
+ const char *orig,
+ const struct interp *interps, int ninterps)
+{
+ const char *src = orig;
+ char *dest = result;
+ int newlen = 0;
+ char *name, *value;
+ int namelen, valuelen;
+ int i;
+ char c;
+
+ memset(result, 0, reslen);
+
+ while ((c = *src) && newlen < reslen - 1) {
+ if (c == '%') {
+ /* Try to match an interpolation string. */
+ for (i = 0; i < ninterps; i++) {
+ name = interps[i].name;
+ namelen = strlen(name);
+ if (strncmp(src, name, namelen) == 0) {
+ break;
+ }
+ }
+
+ /* Check for valid interpolation. */
+ if (i < ninterps) {
+ value = interps[i].value;
+ valuelen = strlen(value);
+
+ if (newlen + valuelen < reslen - 1) {
+ /* Substitute. */
+ strncpy(dest, value, valuelen);
+ newlen += valuelen;
+ dest += valuelen;
+ src += namelen;
+ } else {
+ /* Something's not fitting. */
+ return 0;
+ }
+
+ } else {
+ /* Skip bogus interpolation. */
+ *dest++ = *src++;
+ newlen++;
+ }
+
+ } else {
+ /* Straight copy one non-interpolation character. */
+ *dest++ = *src++;
+ newlen++;
+ }
+ }
+
+ return newlen < reslen - 1;
+}
diff --git a/interpolate.h b/interpolate.h
new file mode 100644
index 0000000000..190a180b58
--- /dev/null
+++ b/interpolate.h
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2006 Jon Loeliger
+ */
+
+#ifndef INTERPOLATE_H
+#define INTERPOLATE_H
+
+/*
+ * Convert a NUL-terminated string in buffer orig,
+ * performing substitutions on %-named sub-strings from
+ * the interpretation table.
+ */
+
+struct interp {
+ char *name;
+ char *value;
+};
+
+extern void interp_set_entry(struct interp *table, int slot, const char *value);
+extern void interp_clear_table(struct interp *table, int ninterps);
+
+extern int interpolate(char *result, int reslen,
+ const char *orig,
+ const struct interp *interps, int ninterps);
+
+#endif /* INTERPOLATE_H */
diff --git a/list-objects.c b/list-objects.c
new file mode 100644
index 0000000000..f1fa21c397
--- /dev/null
+++ b/list-objects.c
@@ -0,0 +1,140 @@
+#include "cache.h"
+#include "tag.h"
+#include "commit.h"
+#include "tree.h"
+#include "blob.h"
+#include "diff.h"
+#include "tree-walk.h"
+#include "revision.h"
+#include "list-objects.h"
+
+static void process_blob(struct rev_info *revs,
+ struct blob *blob,
+ struct object_array *p,
+ struct name_path *path,
+ const char *name)
+{
+ struct object *obj = &blob->object;
+
+ if (!revs->blob_objects)
+ return;
+ if (obj->flags & (UNINTERESTING | SEEN))
+ return;
+ obj->flags |= SEEN;
+ name = xstrdup(name);
+ add_object(obj, p, path, name);
+}
+
+static void process_tree(struct rev_info *revs,
+ struct tree *tree,
+ struct object_array *p,
+ struct name_path *path,
+ const char *name)
+{
+ struct object *obj = &tree->object;
+ struct tree_desc desc;
+ struct name_entry entry;
+ struct name_path me;
+
+ if (!revs->tree_objects)
+ return;
+ if (obj->flags & (UNINTERESTING | SEEN))
+ return;
+ if (parse_tree(tree) < 0)
+ die("bad tree object %s", sha1_to_hex(obj->sha1));
+ obj->flags |= SEEN;
+ name = xstrdup(name);
+ add_object(obj, p, path, name);
+ me.up = path;
+ me.elem = name;
+ me.elem_len = strlen(name);
+
+ desc.buf = tree->buffer;
+ desc.size = tree->size;
+
+ while (tree_entry(&desc, &entry)) {
+ if (S_ISDIR(entry.mode))
+ process_tree(revs,
+ lookup_tree(entry.sha1),
+ p, &me, entry.path);
+ else
+ process_blob(revs,
+ lookup_blob(entry.sha1),
+ p, &me, entry.path);
+ }
+ free(tree->buffer);
+ tree->buffer = NULL;
+}
+
+static void mark_edge_parents_uninteresting(struct commit *commit,
+ struct rev_info *revs,
+ show_edge_fn show_edge)
+{
+ 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);
+ if (revs->edge_hint && !(parent->object.flags & SHOWN)) {
+ parent->object.flags |= SHOWN;
+ show_edge(parent);
+ }
+ }
+}
+
+void mark_edges_uninteresting(struct commit_list *list,
+ struct rev_info *revs,
+ show_edge_fn show_edge)
+{
+ 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, revs, show_edge);
+ }
+}
+
+void traverse_commit_list(struct rev_info *revs,
+ void (*show_commit)(struct commit *),
+ void (*show_object)(struct object_array_entry *))
+{
+ int i;
+ struct commit *commit;
+ struct object_array objects = { 0, 0, NULL };
+
+ while ((commit = get_revision(revs)) != NULL) {
+ process_tree(revs, commit->tree, &objects, NULL, "");
+ show_commit(commit);
+ }
+ for (i = 0; i < revs->pending.nr; i++) {
+ struct object_array_entry *pending = revs->pending.objects + i;
+ struct object *obj = pending->item;
+ const char *name = pending->name;
+ if (obj->flags & (UNINTERESTING | SEEN))
+ continue;
+ if (obj->type == OBJ_TAG) {
+ obj->flags |= SEEN;
+ add_object_array(obj, name, &objects);
+ continue;
+ }
+ if (obj->type == OBJ_TREE) {
+ process_tree(revs, (struct tree *)obj, &objects,
+ NULL, name);
+ continue;
+ }
+ if (obj->type == OBJ_BLOB) {
+ process_blob(revs, (struct blob *)obj, &objects,
+ NULL, name);
+ continue;
+ }
+ die("unknown pending object %s (%s)",
+ sha1_to_hex(obj->sha1), name);
+ }
+ for (i = 0; i < objects.nr; i++)
+ show_object(&objects.objects[i]);
+}
diff --git a/list-objects.h b/list-objects.h
new file mode 100644
index 0000000000..0f41391ecc
--- /dev/null
+++ b/list-objects.h
@@ -0,0 +1,12 @@
+#ifndef LIST_OBJECTS_H
+#define LIST_OBJECTS_H
+
+typedef void (*show_commit_fn)(struct commit *);
+typedef void (*show_object_fn)(struct object_array_entry *);
+typedef void (*show_edge_fn)(struct commit *);
+
+void traverse_commit_list(struct rev_info *revs, show_commit_fn, show_object_fn);
+
+void mark_edges_uninteresting(struct commit_list *, struct rev_info *, show_edge_fn);
+
+#endif
diff --git a/local-fetch.c b/local-fetch.c
index 7d01845d39..7b6875cce6 100644
--- a/local-fetch.c
+++ b/local-fetch.c
@@ -5,10 +5,10 @@
#include "commit.h"
#include "fetch.h"
-static int use_link = 0;
-static int use_symlink = 0;
+static int use_link;
+static int use_symlink;
static int use_filecopy = 1;
-static int commits_on_stdin = 0;
+static int commits_on_stdin;
static const char *path; /* "Remote" git repository */
@@ -16,7 +16,7 @@ void prefetch(unsigned char *sha1)
{
}
-static struct packed_git *packs = NULL;
+static struct packed_git *packs;
static void setup_index(unsigned char *sha1)
{
diff --git a/log-tree.c b/log-tree.c
index 05ede0c175..8787df5cc6 100644
--- a/log-tree.c
+++ b/log-tree.c
@@ -12,28 +12,86 @@ static void show_parents(struct commit *commit, int abbrev)
}
}
+/*
+ * Search for "^[-A-Za-z]+: [^@]+@" pattern. It usually matches
+ * Signed-off-by: and Acked-by: lines.
+ */
+static int detect_any_signoff(char *letter, int size)
+{
+ char ch, *cp;
+ int seen_colon = 0;
+ int seen_at = 0;
+ int seen_name = 0;
+ int seen_head = 0;
+
+ cp = letter + size;
+ while (letter <= --cp && (ch = *cp) == '\n')
+ continue;
+
+ while (letter <= cp) {
+ ch = *cp--;
+ if (ch == '\n')
+ break;
+
+ if (!seen_at) {
+ if (ch == '@')
+ seen_at = 1;
+ continue;
+ }
+ if (!seen_colon) {
+ if (ch == '@')
+ return 0;
+ else if (ch == ':')
+ seen_colon = 1;
+ else
+ seen_name = 1;
+ continue;
+ }
+ if (('A' <= ch && ch <= 'Z') ||
+ ('a' <= ch && ch <= 'z') ||
+ ch == '-') {
+ seen_head = 1;
+ continue;
+ }
+ /* no empty last line doesn't match */
+ return 0;
+ }
+ return seen_head && seen_name;
+}
+
static int append_signoff(char *buf, int buf_sz, int at, const char *signoff)
{
- int signoff_len = strlen(signoff);
static const char signed_off_by[] = "Signed-off-by: ";
+ int signoff_len = strlen(signoff);
+ int has_signoff = 0;
char *cp = buf;
/* Do we have enough space to add it? */
- if (buf_sz - at <= strlen(signed_off_by) + signoff_len + 2)
+ if (buf_sz - at <= strlen(signed_off_by) + signoff_len + 3)
return at;
/* First see if we already have the sign-off by the signer */
- while (1) {
- cp = strstr(cp, signed_off_by);
- if (!cp)
- break;
+ while ((cp = strstr(cp, signed_off_by))) {
+
+ has_signoff = 1;
+
cp += strlen(signed_off_by);
- if ((cp + signoff_len < buf + at) &&
- !strncmp(cp, signoff, signoff_len) &&
- isspace(cp[signoff_len]))
- return at; /* we already have him */
+ if (cp + signoff_len >= buf + at)
+ break;
+ if (strncmp(cp, signoff, signoff_len))
+ continue;
+ if (!isspace(cp[signoff_len]))
+ continue;
+ /* we already have him */
+ return at;
}
+ if (!has_signoff)
+ has_signoff = detect_any_signoff(buf, at);
+
+ if (!has_signoff)
+ buf[at++] = '\n';
+
strcpy(buf + at, signed_off_by);
at += strlen(signed_off_by);
strcpy(buf + at, signoff);
@@ -152,7 +210,9 @@ void show_log(struct rev_info *opt, const char *sep)
/*
* And then the pretty-printed message itself
*/
- len = pretty_print_commit(opt->commit_format, commit, ~0u, this_header, sizeof(this_header), abbrev, subject, extra_headers);
+ len = pretty_print_commit(opt->commit_format, commit, ~0u, this_header,
+ sizeof(this_header), abbrev, subject,
+ extra_headers, opt->relative_date);
if (opt->add_signoff)
len = append_signoff(this_header, sizeof(this_header), len,
@@ -192,26 +252,6 @@ int log_tree_diff_flush(struct rev_info *opt)
return 1;
}
-static int diff_root_tree(struct rev_info *opt,
- const unsigned char *new, const char *base)
-{
- int retval;
- void *tree;
- struct tree_desc empty, real;
-
- tree = read_object_with_reference(new, tree_type, &real.size, NULL);
- if (!tree)
- die("unable to read root tree (%s)", sha1_to_hex(new));
- real.buf = tree;
-
- empty.buf = "";
- empty.size = 0;
- retval = diff_tree(&empty, &real, base, &opt->diffopt);
- free(tree);
- log_tree_diff_flush(opt);
- return retval;
-}
-
static int do_diff_combined(struct rev_info *opt, struct commit *commit)
{
unsigned const char *sha1 = commit->object.sha1;
@@ -237,8 +277,10 @@ static int log_tree_diff(struct rev_info *opt, struct commit *commit, struct log
/* Root commit? */
parents = commit->parents;
if (!parents) {
- if (opt->show_root_diff)
- diff_root_tree(opt, sha1, "");
+ if (opt->show_root_diff) {
+ diff_root_tree_sha1(sha1, "", &opt->diffopt);
+ log_tree_diff_flush(opt);
+ }
return !opt->loginfo;
}
diff --git a/merge-base.c b/merge-base.c
index 59f723f404..009caf804b 100644
--- a/merge-base.c
+++ b/merge-base.c
@@ -2,7 +2,7 @@
#include "cache.h"
#include "commit.h"
-static int show_all = 0;
+static int show_all;
static int merge_base(struct commit *rev1, struct commit *rev2)
{
diff --git a/merge-file.c b/merge-file.c
index f32c653825..fc9b148993 100644
--- a/merge-file.c
+++ b/merge-file.c
@@ -21,7 +21,7 @@ static const char *write_temp_file(mmfile_t *f)
fd = mkstemp(filename);
if (fd < 0)
return NULL;
- filename = strdup(filename);
+ filename = xstrdup(filename);
if (f->size != xwrite(fd, f->ptr, f->size)) {
rm_temp_file(filename);
return NULL;
diff --git a/merge-index.c b/merge-index.c
index 0498a6f45e..646d090c58 100644
--- a/merge-index.c
+++ b/merge-index.c
@@ -4,14 +4,15 @@
#include "cache.h"
-static const char *pgm = NULL;
+static const char *pgm;
static const char *arguments[8];
static int one_shot, quiet;
static int err;
static void run_program(void)
{
- int pid = fork(), status;
+ pid_t pid = fork();
+ int status;
if (pid < 0)
die("unable to fork");
diff --git a/merge-recursive.c b/merge-recursive.c
new file mode 100644
index 0000000000..cd2cc77bf4
--- /dev/null
+++ b/merge-recursive.c
@@ -0,0 +1,1352 @@
+/*
+ * Recursive Merge algorithm stolen from git-merge-recursive.py by
+ * Fredrik Kuivinen.
+ * The thieves were Alex Riesen and Johannes Schindelin, in June/July 2006
+ */
+#include <stdarg.h>
+#include <string.h>
+#include <assert.h>
+#include <sys/wait.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <time.h>
+#include "cache.h"
+#include "cache-tree.h"
+#include "commit.h"
+#include "blob.h"
+#include "tree-walk.h"
+#include "diff.h"
+#include "diffcore.h"
+#include "run-command.h"
+#include "tag.h"
+#include "unpack-trees.h"
+#include "path-list.h"
+
+/*
+ * A virtual commit has
+ * - (const char *)commit->util set to the name, and
+ * - *(int *)commit->object.sha1 set to the virtual id.
+ */
+
+static unsigned commit_list_count(const struct commit_list *l)
+{
+ unsigned c = 0;
+ for (; l; l = l->next )
+ c++;
+ return c;
+}
+
+static struct commit *make_virtual_commit(struct tree *tree, const char *comment)
+{
+ struct commit *commit = xcalloc(1, sizeof(struct commit));
+ static unsigned virtual_id = 1;
+ commit->tree = tree;
+ commit->util = (void*)comment;
+ *(int*)commit->object.sha1 = virtual_id++;
+ /* avoid warnings */
+ commit->object.parsed = 1;
+ return commit;
+}
+
+/*
+ * Since we use get_tree_entry(), which does not put the read object into
+ * the object pool, we cannot rely on a == b.
+ */
+static int sha_eq(const unsigned char *a, const unsigned char *b)
+{
+ if (!a && !b)
+ return 2;
+ return a && b && hashcmp(a, b) == 0;
+}
+
+/*
+ * Since we want to write the index eventually, we cannot reuse the index
+ * for these (temporary) data.
+ */
+struct stage_data
+{
+ struct
+ {
+ unsigned mode;
+ unsigned char sha[20];
+ } stages[4];
+ unsigned processed:1;
+};
+
+static struct path_list current_file_set = {NULL, 0, 0, 1};
+static struct path_list current_directory_set = {NULL, 0, 0, 1};
+
+static int output_indent = 0;
+
+static void output(const char *fmt, ...)
+{
+ va_list args;
+ int i;
+ for (i = output_indent; i--;)
+ fputs(" ", stdout);
+ va_start(args, fmt);
+ vfprintf(stdout, fmt, args);
+ va_end(args);
+ fputc('\n', stdout);
+}
+
+static void output_commit_title(struct commit *commit)
+{
+ int i;
+ for (i = output_indent; i--;)
+ fputs(" ", stdout);
+ if (commit->util)
+ printf("virtual %s\n", (char *)commit->util);
+ else {
+ printf("%s ", find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV));
+ if (parse_commit(commit) != 0)
+ printf("(bad commit)\n");
+ else {
+ const char *s;
+ int len;
+ for (s = commit->buffer; *s; s++)
+ if (*s == '\n' && s[1] == '\n') {
+ s += 2;
+ break;
+ }
+ for (len = 0; s[len] && '\n' != s[len]; len++)
+ ; /* do nothing */
+ printf("%.*s\n", len, s);
+ }
+ }
+}
+
+static const char *current_index_file = NULL;
+static const char *original_index_file;
+static const char *temporary_index_file;
+static int cache_dirty = 0;
+
+static int flush_cache(void)
+{
+ /* flush temporary index */
+ struct lock_file *lock = xcalloc(1, sizeof(struct lock_file));
+ int fd = hold_lock_file_for_update(lock, current_index_file, 1);
+ if (write_cache(fd, active_cache, active_nr) ||
+ close(fd) || commit_lock_file(lock))
+ die ("unable to write %s", current_index_file);
+ discard_cache();
+ cache_dirty = 0;
+ return 0;
+}
+
+static void setup_index(int temp)
+{
+ current_index_file = temp ? temporary_index_file: original_index_file;
+ if (cache_dirty) {
+ discard_cache();
+ cache_dirty = 0;
+ }
+ unlink(temporary_index_file);
+ discard_cache();
+}
+
+static struct cache_entry *make_cache_entry(unsigned int mode,
+ const unsigned char *sha1, const char *path, int stage, int refresh)
+{
+ int size, len;
+ struct cache_entry *ce;
+
+ if (!verify_path(path))
+ return NULL;
+
+ len = strlen(path);
+ size = cache_entry_size(len);
+ ce = xcalloc(1, size);
+
+ hashcpy(ce->sha1, sha1);
+ memcpy(ce->name, path, len);
+ ce->ce_flags = create_ce_flags(len, stage);
+ ce->ce_mode = create_ce_mode(mode);
+
+ if (refresh)
+ return refresh_cache_entry(ce, 0);
+
+ return ce;
+}
+
+static int add_cacheinfo(unsigned int mode, const unsigned char *sha1,
+ const char *path, int stage, int refresh, int options)
+{
+ struct cache_entry *ce;
+ if (!cache_dirty)
+ read_cache_from(current_index_file);
+ cache_dirty++;
+ ce = make_cache_entry(mode, sha1 ? sha1 : null_sha1, path, stage, refresh);
+ if (!ce)
+ return error("cache_addinfo failed: %s", strerror(cache_errno));
+ return add_cache_entry(ce, options);
+}
+
+/*
+ * This is a global variable which is used in a number of places but
+ * only written to in the 'merge' function.
+ *
+ * index_only == 1 => Don't leave any non-stage 0 entries in the cache and
+ * don't update the working directory.
+ * 0 => Leave unmerged entries in the cache and update
+ * the working directory.
+ */
+static int index_only = 0;
+
+static int git_read_tree(struct tree *tree)
+{
+ int rc;
+ struct object_list *trees = NULL;
+ struct unpack_trees_options opts;
+
+ if (cache_dirty)
+ die("read-tree with dirty cache");
+
+ memset(&opts, 0, sizeof(opts));
+ object_list_append(&tree->object, &trees);
+ rc = unpack_trees(trees, &opts);
+ cache_tree_free(&active_cache_tree);
+
+ if (rc == 0)
+ cache_dirty = 1;
+
+ return rc;
+}
+
+static int git_merge_trees(int index_only,
+ struct tree *common,
+ struct tree *head,
+ struct tree *merge)
+{
+ int rc;
+ struct object_list *trees = NULL;
+ struct unpack_trees_options opts;
+
+ if (!cache_dirty) {
+ read_cache_from(current_index_file);
+ cache_dirty = 1;
+ }
+
+ memset(&opts, 0, sizeof(opts));
+ if (index_only)
+ opts.index_only = 1;
+ else
+ opts.update = 1;
+ opts.merge = 1;
+ opts.head_idx = 2;
+ opts.fn = threeway_merge;
+
+ object_list_append(&common->object, &trees);
+ object_list_append(&head->object, &trees);
+ object_list_append(&merge->object, &trees);
+
+ rc = unpack_trees(trees, &opts);
+ cache_tree_free(&active_cache_tree);
+
+ cache_dirty = 1;
+
+ return rc;
+}
+
+static struct tree *git_write_tree(void)
+{
+ struct tree *result = NULL;
+
+ if (cache_dirty) {
+ unsigned i;
+ for (i = 0; i < active_nr; i++) {
+ struct cache_entry *ce = active_cache[i];
+ if (ce_stage(ce))
+ return NULL;
+ }
+ } else
+ read_cache_from(current_index_file);
+
+ if (!active_cache_tree)
+ active_cache_tree = cache_tree();
+
+ if (!cache_tree_fully_valid(active_cache_tree) &&
+ cache_tree_update(active_cache_tree,
+ active_cache, active_nr, 0, 0) < 0)
+ die("error building trees");
+
+ result = lookup_tree(active_cache_tree->sha1);
+
+ flush_cache();
+ cache_dirty = 0;
+
+ return result;
+}
+
+static int save_files_dirs(const unsigned char *sha1,
+ const char *base, int baselen, const char *path,
+ unsigned int mode, int stage)
+{
+ int len = strlen(path);
+ char *newpath = xmalloc(baselen + len + 1);
+ memcpy(newpath, base, baselen);
+ memcpy(newpath + baselen, path, len);
+ newpath[baselen + len] = '\0';
+
+ if (S_ISDIR(mode))
+ path_list_insert(newpath, &current_directory_set);
+ else
+ path_list_insert(newpath, &current_file_set);
+ free(newpath);
+
+ return READ_TREE_RECURSIVE;
+}
+
+static int get_files_dirs(struct tree *tree)
+{
+ int n;
+ if (read_tree_recursive(tree, "", 0, 0, NULL, save_files_dirs) != 0)
+ return 0;
+ n = current_file_set.nr + current_directory_set.nr;
+ return n;
+}
+
+/*
+ * Returns a index_entry instance which doesn't have to correspond to
+ * a real cache entry in Git's index.
+ */
+static struct stage_data *insert_stage_data(const char *path,
+ struct tree *o, struct tree *a, struct tree *b,
+ struct path_list *entries)
+{
+ struct path_list_item *item;
+ struct stage_data *e = xcalloc(1, sizeof(struct stage_data));
+ get_tree_entry(o->object.sha1, path,
+ e->stages[1].sha, &e->stages[1].mode);
+ get_tree_entry(a->object.sha1, path,
+ e->stages[2].sha, &e->stages[2].mode);
+ get_tree_entry(b->object.sha1, path,
+ e->stages[3].sha, &e->stages[3].mode);
+ item = path_list_insert(path, entries);
+ item->util = e;
+ return e;
+}
+
+/*
+ * Create a dictionary mapping file names to stage_data objects. The
+ * dictionary contains one entry for every path with a non-zero stage entry.
+ */
+static struct path_list *get_unmerged(void)
+{
+ struct path_list *unmerged = xcalloc(1, sizeof(struct path_list));
+ int i;
+
+ unmerged->strdup_paths = 1;
+ if (!cache_dirty) {
+ read_cache_from(current_index_file);
+ cache_dirty++;
+ }
+ for (i = 0; i < active_nr; i++) {
+ struct path_list_item *item;
+ struct stage_data *e;
+ struct cache_entry *ce = active_cache[i];
+ if (!ce_stage(ce))
+ continue;
+
+ item = path_list_lookup(ce->name, unmerged);
+ if (!item) {
+ item = path_list_insert(ce->name, unmerged);
+ item->util = xcalloc(1, sizeof(struct stage_data));
+ }
+ e = item->util;
+ e->stages[ce_stage(ce)].mode = ntohl(ce->ce_mode);
+ hashcpy(e->stages[ce_stage(ce)].sha, ce->sha1);
+ }
+
+ return unmerged;
+}
+
+struct rename
+{
+ struct diff_filepair *pair;
+ struct stage_data *src_entry;
+ struct stage_data *dst_entry;
+ unsigned processed:1;
+};
+
+/*
+ * Get information of all renames which occured between 'o_tree' and
+ * 'tree'. We need the three trees in the merge ('o_tree', 'a_tree' and
+ * 'b_tree') to be able to associate the correct cache entries with
+ * the rename information. 'tree' is always equal to either a_tree or b_tree.
+ */
+static struct path_list *get_renames(struct tree *tree,
+ struct tree *o_tree,
+ struct tree *a_tree,
+ struct tree *b_tree,
+ struct path_list *entries)
+{
+ int i;
+ struct path_list *renames;
+ struct diff_options opts;
+
+ renames = xcalloc(1, sizeof(struct path_list));
+ diff_setup(&opts);
+ opts.recursive = 1;
+ opts.detect_rename = DIFF_DETECT_RENAME;
+ opts.output_format = DIFF_FORMAT_NO_OUTPUT;
+ if (diff_setup_done(&opts) < 0)
+ die("diff setup failed");
+ diff_tree_sha1(o_tree->object.sha1, tree->object.sha1, "", &opts);
+ diffcore_std(&opts);
+ for (i = 0; i < diff_queued_diff.nr; ++i) {
+ struct path_list_item *item;
+ struct rename *re;
+ struct diff_filepair *pair = diff_queued_diff.queue[i];
+ if (pair->status != 'R') {
+ diff_free_filepair(pair);
+ continue;
+ }
+ re = xmalloc(sizeof(*re));
+ re->processed = 0;
+ re->pair = pair;
+ item = path_list_lookup(re->pair->one->path, entries);
+ if (!item)
+ re->src_entry = insert_stage_data(re->pair->one->path,
+ o_tree, a_tree, b_tree, entries);
+ else
+ re->src_entry = item->util;
+
+ item = path_list_lookup(re->pair->two->path, entries);
+ if (!item)
+ re->dst_entry = insert_stage_data(re->pair->two->path,
+ o_tree, a_tree, b_tree, entries);
+ else
+ re->dst_entry = item->util;
+ item = path_list_insert(pair->one->path, renames);
+ item->util = re;
+ }
+ opts.output_format = DIFF_FORMAT_NO_OUTPUT;
+ diff_queued_diff.nr = 0;
+ diff_flush(&opts);
+ return renames;
+}
+
+static int update_stages(const char *path, struct diff_filespec *o,
+ struct diff_filespec *a, struct diff_filespec *b,
+ int clear)
+{
+ int options = ADD_CACHE_OK_TO_ADD | ADD_CACHE_OK_TO_REPLACE;
+ if (clear)
+ if (remove_file_from_cache(path))
+ return -1;
+ if (o)
+ if (add_cacheinfo(o->mode, o->sha1, path, 1, 0, options))
+ return -1;
+ if (a)
+ if (add_cacheinfo(a->mode, a->sha1, path, 2, 0, options))
+ return -1;
+ if (b)
+ if (add_cacheinfo(b->mode, b->sha1, path, 3, 0, options))
+ return -1;
+ return 0;
+}
+
+static int remove_path(const char *name)
+{
+ int ret, len;
+ char *slash, *dirs;
+
+ ret = unlink(name);
+ if (ret)
+ return ret;
+ len = strlen(name);
+ dirs = xmalloc(len+1);
+ memcpy(dirs, name, len);
+ dirs[len] = '\0';
+ while ((slash = strrchr(name, '/'))) {
+ *slash = '\0';
+ len = slash - name;
+ if (rmdir(name) != 0)
+ break;
+ }
+ free(dirs);
+ return ret;
+}
+
+static int remove_file(int clean, const char *path, int no_wd)
+{
+ int update_cache = index_only || clean;
+ int update_working_directory = !index_only && !no_wd;
+
+ if (update_cache) {
+ if (!cache_dirty)
+ read_cache_from(current_index_file);
+ cache_dirty++;
+ if (remove_file_from_cache(path))
+ return -1;
+ }
+ if (update_working_directory) {
+ unlink(path);
+ if (errno != ENOENT || errno != EISDIR)
+ return -1;
+ remove_path(path);
+ }
+ return 0;
+}
+
+static char *unique_path(const char *path, const char *branch)
+{
+ char *newpath = xmalloc(strlen(path) + 1 + strlen(branch) + 8 + 1);
+ int suffix = 0;
+ struct stat st;
+ char *p = newpath + strlen(path);
+ strcpy(newpath, path);
+ *(p++) = '~';
+ strcpy(p, branch);
+ for (; *p; ++p)
+ if ('/' == *p)
+ *p = '_';
+ while (path_list_has_path(&current_file_set, newpath) ||
+ path_list_has_path(&current_directory_set, newpath) ||
+ lstat(newpath, &st) == 0)
+ sprintf(p, "_%d", suffix++);
+
+ path_list_insert(newpath, &current_file_set);
+ return newpath;
+}
+
+static int mkdir_p(const char *path, unsigned long mode)
+{
+ /* path points to cache entries, so xstrdup before messing with it */
+ char *buf = xstrdup(path);
+ int result = safe_create_leading_directories(buf);
+ free(buf);
+ return result;
+}
+
+static void flush_buffer(int fd, const char *buf, unsigned long size)
+{
+ while (size > 0) {
+ long ret = xwrite(fd, buf, size);
+ if (ret < 0) {
+ /* Ignore epipe */
+ if (errno == EPIPE)
+ break;
+ die("merge-recursive: %s", strerror(errno));
+ } else if (!ret) {
+ die("merge-recursive: disk full?");
+ }
+ size -= ret;
+ buf += ret;
+ }
+}
+
+static void update_file_flags(const unsigned char *sha,
+ unsigned mode,
+ const char *path,
+ int update_cache,
+ int update_wd)
+{
+ if (index_only)
+ update_wd = 0;
+
+ if (update_wd) {
+ char type[20];
+ void *buf;
+ unsigned long size;
+
+ buf = read_sha1_file(sha, type, &size);
+ if (!buf)
+ die("cannot read object %s '%s'", sha1_to_hex(sha), path);
+ if (strcmp(type, blob_type) != 0)
+ die("blob expected for %s '%s'", sha1_to_hex(sha), path);
+
+ if (S_ISREG(mode)) {
+ int fd;
+ if (mkdir_p(path, 0777))
+ die("failed to create path %s: %s", path, strerror(errno));
+ unlink(path);
+ if (mode & 0100)
+ mode = 0777;
+ else
+ mode = 0666;
+ fd = open(path, O_WRONLY | O_TRUNC | O_CREAT, mode);
+ if (fd < 0)
+ die("failed to open %s: %s", path, strerror(errno));
+ flush_buffer(fd, buf, size);
+ close(fd);
+ } else if (S_ISLNK(mode)) {
+ char *lnk = xmalloc(size + 1);
+ memcpy(lnk, buf, size);
+ lnk[size] = '\0';
+ mkdir_p(path, 0777);
+ unlink(lnk);
+ symlink(lnk, path);
+ } else
+ die("do not know what to do with %06o %s '%s'",
+ mode, sha1_to_hex(sha), path);
+ }
+ if (update_cache)
+ add_cacheinfo(mode, sha, path, 0, update_wd, ADD_CACHE_OK_TO_ADD);
+}
+
+static void update_file(int clean,
+ const unsigned char *sha,
+ unsigned mode,
+ const char *path)
+{
+ update_file_flags(sha, mode, path, index_only || clean, !index_only);
+}
+
+/* Low level file merging, update and removal */
+
+struct merge_file_info
+{
+ unsigned char sha[20];
+ unsigned mode;
+ unsigned clean:1,
+ merge:1;
+};
+
+static char *git_unpack_file(const unsigned char *sha1, char *path)
+{
+ void *buf;
+ char type[20];
+ unsigned long size;
+ int fd;
+
+ buf = read_sha1_file(sha1, type, &size);
+ if (!buf || strcmp(type, blob_type))
+ die("unable to read blob object %s", sha1_to_hex(sha1));
+
+ strcpy(path, ".merge_file_XXXXXX");
+ fd = mkstemp(path);
+ if (fd < 0)
+ die("unable to create temp-file");
+ flush_buffer(fd, buf, size);
+ close(fd);
+ return path;
+}
+
+static struct merge_file_info merge_file(struct diff_filespec *o,
+ struct diff_filespec *a, struct diff_filespec *b,
+ const char *branch1, const char *branch2)
+{
+ struct merge_file_info result;
+ result.merge = 0;
+ result.clean = 1;
+
+ if ((S_IFMT & a->mode) != (S_IFMT & b->mode)) {
+ result.clean = 0;
+ if (S_ISREG(a->mode)) {
+ result.mode = a->mode;
+ hashcpy(result.sha, a->sha1);
+ } else {
+ result.mode = b->mode;
+ hashcpy(result.sha, b->sha1);
+ }
+ } else {
+ if (!sha_eq(a->sha1, o->sha1) && !sha_eq(b->sha1, o->sha1))
+ result.merge = 1;
+
+ result.mode = a->mode == o->mode ? b->mode: a->mode;
+
+ if (sha_eq(a->sha1, o->sha1))
+ hashcpy(result.sha, b->sha1);
+ else if (sha_eq(b->sha1, o->sha1))
+ hashcpy(result.sha, a->sha1);
+ else if (S_ISREG(a->mode)) {
+ int code = 1, fd;
+ struct stat st;
+ char orig[PATH_MAX];
+ char src1[PATH_MAX];
+ char src2[PATH_MAX];
+ const char *argv[] = {
+ "merge", "-L", NULL, "-L", NULL, "-L", NULL,
+ NULL, NULL, NULL,
+ NULL
+ };
+ char *la, *lb, *lo;
+
+ git_unpack_file(o->sha1, orig);
+ git_unpack_file(a->sha1, src1);
+ git_unpack_file(b->sha1, src2);
+
+ argv[2] = la = xstrdup(mkpath("%s/%s", branch1, a->path));
+ argv[6] = lb = xstrdup(mkpath("%s/%s", branch2, b->path));
+ argv[4] = lo = xstrdup(mkpath("orig/%s", o->path));
+ argv[7] = src1;
+ argv[8] = orig;
+ argv[9] = src2,
+
+ code = run_command_v(10, argv);
+
+ free(la);
+ free(lb);
+ free(lo);
+ if (code && code < -256) {
+ die("Failed to execute 'merge'. merge(1) is used as the "
+ "file-level merge tool. Is 'merge' in your path?");
+ }
+ fd = open(src1, O_RDONLY);
+ if (fd < 0 || fstat(fd, &st) < 0 ||
+ index_fd(result.sha, fd, &st, 1,
+ "blob"))
+ die("Unable to add %s to database", src1);
+
+ unlink(orig);
+ unlink(src1);
+ unlink(src2);
+
+ result.clean = WEXITSTATUS(code) == 0;
+ } else {
+ if (!(S_ISLNK(a->mode) || S_ISLNK(b->mode)))
+ die("cannot merge modes?");
+
+ hashcpy(result.sha, a->sha1);
+
+ if (!sha_eq(a->sha1, b->sha1))
+ result.clean = 0;
+ }
+ }
+
+ return result;
+}
+
+static void conflict_rename_rename(struct rename *ren1,
+ const char *branch1,
+ struct rename *ren2,
+ const char *branch2)
+{
+ char *del[2];
+ int delp = 0;
+ const char *ren1_dst = ren1->pair->two->path;
+ const char *ren2_dst = ren2->pair->two->path;
+ const char *dst_name1 = ren1_dst;
+ const char *dst_name2 = ren2_dst;
+ if (path_list_has_path(&current_directory_set, ren1_dst)) {
+ dst_name1 = del[delp++] = unique_path(ren1_dst, branch1);
+ output("%s is a directory in %s adding as %s instead",
+ ren1_dst, branch2, dst_name1);
+ remove_file(0, ren1_dst, 0);
+ }
+ if (path_list_has_path(&current_directory_set, ren2_dst)) {
+ dst_name2 = del[delp++] = unique_path(ren2_dst, branch2);
+ output("%s is a directory in %s adding as %s instead",
+ ren2_dst, branch1, dst_name2);
+ remove_file(0, ren2_dst, 0);
+ }
+ update_stages(dst_name1, NULL, ren1->pair->two, NULL, 1);
+ update_stages(dst_name2, NULL, NULL, ren2->pair->two, 1);
+ while (delp--)
+ free(del[delp]);
+}
+
+static void conflict_rename_dir(struct rename *ren1,
+ const char *branch1)
+{
+ char *new_path = unique_path(ren1->pair->two->path, branch1);
+ output("Renaming %s to %s instead", ren1->pair->one->path, new_path);
+ remove_file(0, ren1->pair->two->path, 0);
+ update_file(0, ren1->pair->two->sha1, ren1->pair->two->mode, new_path);
+ free(new_path);
+}
+
+static void conflict_rename_rename_2(struct rename *ren1,
+ const char *branch1,
+ struct rename *ren2,
+ const char *branch2)
+{
+ char *new_path1 = unique_path(ren1->pair->two->path, branch1);
+ char *new_path2 = unique_path(ren2->pair->two->path, branch2);
+ output("Renaming %s to %s and %s to %s instead",
+ ren1->pair->one->path, new_path1,
+ ren2->pair->one->path, new_path2);
+ remove_file(0, ren1->pair->two->path, 0);
+ update_file(0, ren1->pair->two->sha1, ren1->pair->two->mode, new_path1);
+ update_file(0, ren2->pair->two->sha1, ren2->pair->two->mode, new_path2);
+ free(new_path2);
+ free(new_path1);
+}
+
+static int process_renames(struct path_list *a_renames,
+ struct path_list *b_renames,
+ const char *a_branch,
+ const char *b_branch)
+{
+ int clean_merge = 1, i, j;
+ struct path_list a_by_dst = {NULL, 0, 0, 0}, b_by_dst = {NULL, 0, 0, 0};
+ const struct rename *sre;
+
+ for (i = 0; i < a_renames->nr; i++) {
+ sre = a_renames->items[i].util;
+ path_list_insert(sre->pair->two->path, &a_by_dst)->util
+ = sre->dst_entry;
+ }
+ for (i = 0; i < b_renames->nr; i++) {
+ sre = b_renames->items[i].util;
+ path_list_insert(sre->pair->two->path, &b_by_dst)->util
+ = sre->dst_entry;
+ }
+
+ for (i = 0, j = 0; i < a_renames->nr || j < b_renames->nr;) {
+ int compare;
+ char *src;
+ struct path_list *renames1, *renames2, *renames2Dst;
+ struct rename *ren1 = NULL, *ren2 = NULL;
+ const char *branch1, *branch2;
+ const char *ren1_src, *ren1_dst;
+
+ if (i >= a_renames->nr) {
+ compare = 1;
+ ren2 = b_renames->items[j++].util;
+ } else if (j >= b_renames->nr) {
+ compare = -1;
+ ren1 = a_renames->items[i++].util;
+ } else {
+ compare = strcmp(a_renames->items[i].path,
+ b_renames->items[j].path);
+ if (compare <= 0)
+ ren1 = a_renames->items[i++].util;
+ if (compare >= 0)
+ ren2 = b_renames->items[j++].util;
+ }
+
+ /* TODO: refactor, so that 1/2 are not needed */
+ if (ren1) {
+ renames1 = a_renames;
+ renames2 = b_renames;
+ renames2Dst = &b_by_dst;
+ branch1 = a_branch;
+ branch2 = b_branch;
+ } else {
+ struct rename *tmp;
+ renames1 = b_renames;
+ renames2 = a_renames;
+ renames2Dst = &a_by_dst;
+ branch1 = b_branch;
+ branch2 = a_branch;
+ tmp = ren2;
+ ren2 = ren1;
+ ren1 = tmp;
+ }
+ src = ren1->pair->one->path;
+
+ ren1->dst_entry->processed = 1;
+ ren1->src_entry->processed = 1;
+
+ if (ren1->processed)
+ continue;
+ ren1->processed = 1;
+
+ ren1_src = ren1->pair->one->path;
+ ren1_dst = ren1->pair->two->path;
+
+ if (ren2) {
+ const char *ren2_src = ren2->pair->one->path;
+ const char *ren2_dst = ren2->pair->two->path;
+ /* Renamed in 1 and renamed in 2 */
+ if (strcmp(ren1_src, ren2_src) != 0)
+ die("ren1.src != ren2.src");
+ ren2->dst_entry->processed = 1;
+ ren2->processed = 1;
+ if (strcmp(ren1_dst, ren2_dst) != 0) {
+ clean_merge = 0;
+ output("CONFLICT (rename/rename): "
+ "Rename %s->%s in branch %s "
+ "rename %s->%s in %s",
+ src, ren1_dst, branch1,
+ src, ren2_dst, branch2);
+ conflict_rename_rename(ren1, branch1, ren2, branch2);
+ } else {
+ struct merge_file_info mfi;
+ remove_file(1, ren1_src, 1);
+ mfi = merge_file(ren1->pair->one,
+ ren1->pair->two,
+ ren2->pair->two,
+ branch1,
+ branch2);
+ if (mfi.merge || !mfi.clean)
+ output("Renaming %s->%s", src, ren1_dst);
+
+ if (mfi.merge)
+ output("Auto-merging %s", ren1_dst);
+
+ if (!mfi.clean) {
+ output("CONFLICT (content): merge conflict in %s",
+ ren1_dst);
+ clean_merge = 0;
+
+ if (!index_only)
+ update_stages(ren1_dst,
+ ren1->pair->one,
+ ren1->pair->two,
+ ren2->pair->two,
+ 1 /* clear */);
+ }
+ update_file(mfi.clean, mfi.sha, mfi.mode, ren1_dst);
+ }
+ } else {
+ /* Renamed in 1, maybe changed in 2 */
+ struct path_list_item *item;
+ /* we only use sha1 and mode of these */
+ struct diff_filespec src_other, dst_other;
+ int try_merge, stage = a_renames == renames1 ? 3: 2;
+
+ remove_file(1, ren1_src, 1);
+
+ hashcpy(src_other.sha1, ren1->src_entry->stages[stage].sha);
+ src_other.mode = ren1->src_entry->stages[stage].mode;
+ hashcpy(dst_other.sha1, ren1->dst_entry->stages[stage].sha);
+ dst_other.mode = ren1->dst_entry->stages[stage].mode;
+
+ try_merge = 0;
+
+ if (path_list_has_path(&current_directory_set, ren1_dst)) {
+ clean_merge = 0;
+ output("CONFLICT (rename/directory): Rename %s->%s in %s "
+ " directory %s added in %s",
+ ren1_src, ren1_dst, branch1,
+ ren1_dst, branch2);
+ conflict_rename_dir(ren1, branch1);
+ } else if (sha_eq(src_other.sha1, null_sha1)) {
+ clean_merge = 0;
+ output("CONFLICT (rename/delete): Rename %s->%s in %s "
+ "and deleted in %s",
+ ren1_src, ren1_dst, branch1,
+ branch2);
+ update_file(0, ren1->pair->two->sha1, ren1->pair->two->mode, ren1_dst);
+ } else if (!sha_eq(dst_other.sha1, null_sha1)) {
+ const char *new_path;
+ clean_merge = 0;
+ try_merge = 1;
+ output("CONFLICT (rename/add): Rename %s->%s in %s. "
+ "%s added in %s",
+ ren1_src, ren1_dst, branch1,
+ ren1_dst, branch2);
+ new_path = unique_path(ren1_dst, branch2);
+ output("Adding as %s instead", new_path);
+ update_file(0, dst_other.sha1, dst_other.mode, new_path);
+ } else if ((item = path_list_lookup(ren1_dst, renames2Dst))) {
+ ren2 = item->util;
+ clean_merge = 0;
+ ren2->processed = 1;
+ output("CONFLICT (rename/rename): Rename %s->%s in %s. "
+ "Rename %s->%s in %s",
+ ren1_src, ren1_dst, branch1,
+ ren2->pair->one->path, ren2->pair->two->path, branch2);
+ conflict_rename_rename_2(ren1, branch1, ren2, branch2);
+ } else
+ try_merge = 1;
+
+ if (try_merge) {
+ struct diff_filespec *o, *a, *b;
+ struct merge_file_info mfi;
+ src_other.path = (char *)ren1_src;
+
+ o = ren1->pair->one;
+ if (a_renames == renames1) {
+ a = ren1->pair->two;
+ b = &src_other;
+ } else {
+ b = ren1->pair->two;
+ a = &src_other;
+ }
+ mfi = merge_file(o, a, b,
+ a_branch, b_branch);
+
+ if (mfi.merge || !mfi.clean)
+ output("Renaming %s => %s", ren1_src, ren1_dst);
+ if (mfi.merge)
+ output("Auto-merging %s", ren1_dst);
+ if (!mfi.clean) {
+ output("CONFLICT (rename/modify): Merge conflict in %s",
+ ren1_dst);
+ clean_merge = 0;
+
+ if (!index_only)
+ update_stages(ren1_dst,
+ o, a, b, 1);
+ }
+ update_file(mfi.clean, mfi.sha, mfi.mode, ren1_dst);
+ }
+ }
+ }
+ path_list_clear(&a_by_dst, 0);
+ path_list_clear(&b_by_dst, 0);
+
+ if (cache_dirty)
+ flush_cache();
+ return clean_merge;
+}
+
+static unsigned char *has_sha(const unsigned char *sha)
+{
+ return is_null_sha1(sha) ? NULL: (unsigned char *)sha;
+}
+
+/* Per entry merge function */
+static int process_entry(const char *path, struct stage_data *entry,
+ const char *branch1,
+ const char *branch2)
+{
+ /*
+ printf("processing entry, clean cache: %s\n", index_only ? "yes": "no");
+ print_index_entry("\tpath: ", entry);
+ */
+ int clean_merge = 1;
+ unsigned char *o_sha = has_sha(entry->stages[1].sha);
+ unsigned char *a_sha = has_sha(entry->stages[2].sha);
+ unsigned char *b_sha = has_sha(entry->stages[3].sha);
+ unsigned o_mode = entry->stages[1].mode;
+ unsigned a_mode = entry->stages[2].mode;
+ unsigned b_mode = entry->stages[3].mode;
+
+ if (o_sha && (!a_sha || !b_sha)) {
+ /* Case A: Deleted in one */
+ if ((!a_sha && !b_sha) ||
+ (sha_eq(a_sha, o_sha) && !b_sha) ||
+ (!a_sha && sha_eq(b_sha, o_sha))) {
+ /* Deleted in both or deleted in one and
+ * unchanged in the other */
+ if (a_sha)
+ output("Removing %s", path);
+ /* do not touch working file if it did not exist */
+ remove_file(1, path, !a_sha);
+ } else {
+ /* Deleted in one and changed in the other */
+ clean_merge = 0;
+ if (!a_sha) {
+ output("CONFLICT (delete/modify): %s deleted in %s "
+ "and modified in %s. Version %s of %s left in tree.",
+ path, branch1,
+ branch2, branch2, path);
+ update_file(0, b_sha, b_mode, path);
+ } else {
+ output("CONFLICT (delete/modify): %s deleted in %s "
+ "and modified in %s. Version %s of %s left in tree.",
+ path, branch2,
+ branch1, branch1, path);
+ update_file(0, a_sha, a_mode, path);
+ }
+ }
+
+ } else if ((!o_sha && a_sha && !b_sha) ||
+ (!o_sha && !a_sha && b_sha)) {
+ /* Case B: Added in one. */
+ const char *add_branch;
+ const char *other_branch;
+ unsigned mode;
+ const unsigned char *sha;
+ const char *conf;
+
+ if (a_sha) {
+ add_branch = branch1;
+ other_branch = branch2;
+ mode = a_mode;
+ sha = a_sha;
+ conf = "file/directory";
+ } else {
+ add_branch = branch2;
+ other_branch = branch1;
+ mode = b_mode;
+ sha = b_sha;
+ conf = "directory/file";
+ }
+ if (path_list_has_path(&current_directory_set, path)) {
+ const char *new_path = unique_path(path, add_branch);
+ clean_merge = 0;
+ output("CONFLICT (%s): There is a directory with name %s in %s. "
+ "Adding %s as %s",
+ conf, path, other_branch, path, new_path);
+ remove_file(0, path, 0);
+ update_file(0, sha, mode, new_path);
+ } else {
+ output("Adding %s", path);
+ update_file(1, sha, mode, path);
+ }
+ } else if (!o_sha && a_sha && b_sha) {
+ /* Case C: Added in both (check for same permissions). */
+ if (sha_eq(a_sha, b_sha)) {
+ if (a_mode != b_mode) {
+ clean_merge = 0;
+ output("CONFLICT: File %s added identically in both branches, "
+ "but permissions conflict %06o->%06o",
+ path, a_mode, b_mode);
+ output("CONFLICT: adding with permission: %06o", a_mode);
+ update_file(0, a_sha, a_mode, path);
+ } else {
+ /* This case is handled by git-read-tree */
+ assert(0 && "This case must be handled by git-read-tree");
+ }
+ } else {
+ const char *new_path1, *new_path2;
+ clean_merge = 0;
+ new_path1 = unique_path(path, branch1);
+ new_path2 = unique_path(path, branch2);
+ output("CONFLICT (add/add): File %s added non-identically "
+ "in both branches. Adding as %s and %s instead.",
+ path, new_path1, new_path2);
+ remove_file(0, path, 0);
+ update_file(0, a_sha, a_mode, new_path1);
+ update_file(0, b_sha, b_mode, new_path2);
+ }
+
+ } else if (o_sha && a_sha && b_sha) {
+ /* case D: Modified in both, but differently. */
+ struct merge_file_info mfi;
+ struct diff_filespec o, a, b;
+
+ output("Auto-merging %s", path);
+ o.path = a.path = b.path = (char *)path;
+ hashcpy(o.sha1, o_sha);
+ o.mode = o_mode;
+ hashcpy(a.sha1, a_sha);
+ a.mode = a_mode;
+ hashcpy(b.sha1, b_sha);
+ b.mode = b_mode;
+
+ mfi = merge_file(&o, &a, &b,
+ branch1, branch2);
+
+ if (mfi.clean)
+ update_file(1, mfi.sha, mfi.mode, path);
+ else {
+ clean_merge = 0;
+ output("CONFLICT (content): Merge conflict in %s", path);
+
+ if (index_only)
+ update_file(0, mfi.sha, mfi.mode, path);
+ else
+ update_file_flags(mfi.sha, mfi.mode, path,
+ 0 /* update_cache */, 1 /* update_working_directory */);
+ }
+ } else
+ die("Fatal merge failure, shouldn't happen.");
+
+ if (cache_dirty)
+ flush_cache();
+
+ return clean_merge;
+}
+
+static int merge_trees(struct tree *head,
+ struct tree *merge,
+ struct tree *common,
+ const char *branch1,
+ const char *branch2,
+ struct tree **result)
+{
+ int code, clean;
+ if (sha_eq(common->object.sha1, merge->object.sha1)) {
+ output("Already uptodate!");
+ *result = head;
+ return 1;
+ }
+
+ code = git_merge_trees(index_only, common, head, merge);
+
+ if (code != 0)
+ die("merging of trees %s and %s failed",
+ sha1_to_hex(head->object.sha1),
+ sha1_to_hex(merge->object.sha1));
+
+ *result = git_write_tree();
+
+ if (!*result) {
+ struct path_list *entries, *re_head, *re_merge;
+ int i;
+ path_list_clear(&current_file_set, 1);
+ path_list_clear(&current_directory_set, 1);
+ get_files_dirs(head);
+ get_files_dirs(merge);
+
+ entries = get_unmerged();
+ re_head = get_renames(head, common, head, merge, entries);
+ re_merge = get_renames(merge, common, head, merge, entries);
+ clean = process_renames(re_head, re_merge,
+ branch1, branch2);
+ for (i = 0; i < entries->nr; i++) {
+ const char *path = entries->items[i].path;
+ struct stage_data *e = entries->items[i].util;
+ if (e->processed)
+ continue;
+ if (!process_entry(path, e, branch1, branch2))
+ clean = 0;
+ }
+
+ path_list_clear(re_merge, 0);
+ path_list_clear(re_head, 0);
+ path_list_clear(entries, 1);
+
+ if (clean || index_only)
+ *result = git_write_tree();
+ else
+ *result = NULL;
+ } else {
+ clean = 1;
+ printf("merging of trees %s and %s resulted in %s\n",
+ sha1_to_hex(head->object.sha1),
+ sha1_to_hex(merge->object.sha1),
+ sha1_to_hex((*result)->object.sha1));
+ }
+
+ return clean;
+}
+
+static struct commit_list *reverse_commit_list(struct commit_list *list)
+{
+ struct commit_list *next = NULL, *current, *backup;
+ for (current = list; current; current = backup) {
+ backup = current->next;
+ current->next = next;
+ next = current;
+ }
+ return next;
+}
+
+/*
+ * Merge the commits h1 and h2, return the resulting virtual
+ * commit object and a flag indicating the cleaness of the merge.
+ */
+static int merge(struct commit *h1,
+ struct commit *h2,
+ const char *branch1,
+ const char *branch2,
+ int call_depth /* =0 */,
+ struct commit *ancestor /* =None */,
+ struct commit **result)
+{
+ struct commit_list *ca = NULL, *iter;
+ struct commit *merged_common_ancestors;
+ struct tree *mrtree;
+ int clean;
+
+ output("Merging:");
+ output_commit_title(h1);
+ output_commit_title(h2);
+
+ if (ancestor)
+ commit_list_insert(ancestor, &ca);
+ else
+ ca = reverse_commit_list(get_merge_bases(h1, h2, 1));
+
+ output("found %u common ancestor(s):", commit_list_count(ca));
+ for (iter = ca; iter; iter = iter->next)
+ output_commit_title(iter->item);
+
+ merged_common_ancestors = pop_commit(&ca);
+ if (merged_common_ancestors == NULL) {
+ /* if there is no common ancestor, make an empty tree */
+ struct tree *tree = xcalloc(1, sizeof(struct tree));
+
+ tree->object.parsed = 1;
+ tree->object.type = OBJ_TREE;
+ hash_sha1_file(NULL, 0, tree_type, tree->object.sha1);
+ merged_common_ancestors = make_virtual_commit(tree, "ancestor");
+ }
+
+ for (iter = ca; iter; iter = iter->next) {
+ output_indent = call_depth + 1;
+ /*
+ * When the merge fails, the result contains files
+ * with conflict markers. The cleanness flag is
+ * ignored, it was never acutally used, as result of
+ * merge_trees has always overwritten it: the commited
+ * "conflicts" were already resolved.
+ */
+ merge(merged_common_ancestors, iter->item,
+ "Temporary merge branch 1",
+ "Temporary merge branch 2",
+ call_depth + 1,
+ NULL,
+ &merged_common_ancestors);
+ output_indent = call_depth;
+
+ if (!merged_common_ancestors)
+ die("merge returned no commit");
+ }
+
+ if (call_depth == 0) {
+ setup_index(0 /* $GIT_DIR/index */);
+ index_only = 0;
+ } else {
+ setup_index(1 /* temporary index */);
+ git_read_tree(h1->tree);
+ index_only = 1;
+ }
+
+ clean = merge_trees(h1->tree, h2->tree, merged_common_ancestors->tree,
+ branch1, branch2, &mrtree);
+
+ if (!ancestor && (clean || index_only)) {
+ *result = make_virtual_commit(mrtree, "merged tree");
+ commit_list_insert(h1, &(*result)->parents);
+ commit_list_insert(h2, &(*result)->parents->next);
+ } else
+ *result = NULL;
+
+ return clean;
+}
+
+static struct commit *get_ref(const char *ref)
+{
+ unsigned char sha1[20];
+ struct object *object;
+
+ if (get_sha1(ref, sha1))
+ die("Could not resolve ref '%s'", ref);
+ object = deref_tag(parse_object(sha1), ref, strlen(ref));
+ if (object->type != OBJ_COMMIT)
+ return NULL;
+ if (parse_commit((struct commit *)object))
+ die("Could not parse commit '%s'", sha1_to_hex(object->sha1));
+ return (struct commit *)object;
+}
+
+int main(int argc, char *argv[])
+{
+ static const char *bases[2];
+ static unsigned bases_count = 0;
+ int i, clean;
+ const char *branch1, *branch2;
+ struct commit *result, *h1, *h2;
+
+ git_config(git_default_config); /* core.filemode */
+ original_index_file = getenv("GIT_INDEX_FILE");
+
+ if (!original_index_file)
+ original_index_file = xstrdup(git_path("index"));
+
+ temporary_index_file = xstrdup(git_path("mrg-rcrsv-tmp-idx"));
+
+ if (argc < 4)
+ die("Usage: %s <base>... -- <head> <remote> ...\n", argv[0]);
+
+ for (i = 1; i < argc; ++i) {
+ if (!strcmp(argv[i], "--"))
+ break;
+ if (bases_count < sizeof(bases)/sizeof(*bases))
+ bases[bases_count++] = argv[i];
+ }
+ if (argc - i != 3) /* "--" "<head>" "<remote>" */
+ die("Not handling anything other than two heads merge.");
+
+ branch1 = argv[++i];
+ branch2 = argv[++i];
+ printf("Merging %s with %s\n", branch1, branch2);
+
+ h1 = get_ref(branch1);
+ h2 = get_ref(branch2);
+
+ if (bases_count == 1) {
+ struct commit *ancestor = get_ref(bases[0]);
+ clean = merge(h1, h2, branch1, branch2, 0, ancestor, &result);
+ } else
+ clean = merge(h1, h2, branch1, branch2, 0, NULL, &result);
+
+ if (cache_dirty)
+ flush_cache();
+
+ return clean ? 0: 1;
+}
+
+/*
+vim: sw=8 noet
+*/
diff --git a/merge-tree.c b/merge-tree.c
index 7cf00be6d5..692ede0e3d 100644
--- a/merge-tree.c
+++ b/merge-tree.c
@@ -152,7 +152,7 @@ static int same_entry(struct name_entry *a, struct name_entry *b)
{
return a->sha1 &&
b->sha1 &&
- !memcmp(a->sha1, b->sha1, 20) &&
+ !hashcmp(a->sha1, b->sha1) &&
a->mode == b->mode;
}
@@ -177,7 +177,7 @@ static void resolve(const char *base, struct name_entry *branch1, struct name_en
if (!branch1)
return;
- path = strdup(mkpath("%s%s", base, result->path));
+ path = xstrdup(mkpath("%s%s", base, result->path));
orig = create_entry(2, branch1->mode, branch1->sha1, path);
final = create_entry(0, result->mode, result->sha1, path);
@@ -233,7 +233,7 @@ static struct merge_list *link_entry(unsigned stage, const char *base, struct na
if (entry)
path = entry->path;
else
- path = strdup(mkpath("%s%s", base, n->path));
+ path = xstrdup(mkpath("%s%s", base, n->path));
link = create_entry(stage, n->mode, n->sha1, path);
link->link = entry;
return link;
@@ -337,9 +337,11 @@ int main(int argc, char **argv)
struct tree_desc t[3];
void *buf1, *buf2, *buf3;
- if (argc < 4)
+ if (argc != 4)
usage(merge_tree_usage);
+ setup_git_directory();
+
buf1 = get_tree_descriptor(t+0, argv[1]);
buf2 = get_tree_descriptor(t+1, argv[2]);
buf3 = get_tree_descriptor(t+2, argv[3]);
diff --git a/mktag.c b/mktag.c
index be23e589fb..3448a5dde7 100644
--- a/mktag.c
+++ b/mktag.c
@@ -119,7 +119,7 @@ static int verify_tag(char *buffer, unsigned long size)
int main(int argc, char **argv)
{
unsigned long size = 4096;
- char *buffer = malloc(size);
+ char *buffer = xmalloc(size);
unsigned char result_sha1[20];
if (argc != 1)
diff --git a/mktree.c b/mktree.c
index 9a6f0d2f6b..56205d1e00 100644
--- a/mktree.c
+++ b/mktree.c
@@ -30,7 +30,7 @@ static void append_to_tree(unsigned mode, unsigned char *sha1, char *path)
ent = entries[used++] = xmalloc(sizeof(**entries) + len + 1);
ent->mode = mode;
ent->len = len;
- memcpy(ent->sha1, sha1, 20);
+ hashcpy(ent->sha1, sha1);
memcpy(ent->name, path, len+1);
}
@@ -49,7 +49,6 @@ static void write_tree(unsigned char *sha1)
int i;
qsort(entries, used, sizeof(*entries), ent_compare);
- size = 100;
for (size = i = 0; i < used; i++)
size += 32 + entries[i]->len;
buffer = xmalloc(size);
@@ -65,7 +64,7 @@ static void write_tree(unsigned char *sha1)
offset += sprintf(buffer + offset, "%o ", ent->mode);
offset += sprintf(buffer + offset, "%s", ent->name);
buffer[offset++] = 0;
- memcpy(buffer + offset, ent->sha1, 20);
+ hashcpy((unsigned char*)buffer + offset, ent->sha1);
offset += 20;
}
write_sha1_file(buffer, offset, tree_type, sha1);
diff --git a/object-refs.c b/object-refs.c
index b1b8065851..98ea10005a 100644
--- a/object-refs.c
+++ b/object-refs.c
@@ -30,7 +30,7 @@ static void grow_refs_hash(void)
int new_hash_size = (refs_hash_size + 1000) * 3 / 2;
struct object_refs **new_hash;
- new_hash = calloc(new_hash_size, sizeof(struct object_refs *));
+ new_hash = xcalloc(new_hash_size, sizeof(struct object_refs *));
for (i = 0; i < refs_hash_size; i++) {
struct object_refs *ref = refs_hash[i];
if (!ref)
@@ -55,9 +55,13 @@ static void add_object_refs(struct object *obj, struct object_refs *ref)
struct object_refs *lookup_object_refs(struct object *obj)
{
- int j = hash_obj(obj, refs_hash_size);
struct object_refs *ref;
+ int j;
+ /* nothing to lookup */
+ if (!refs_hash_size)
+ return NULL;
+ j = hash_obj(obj, refs_hash_size);
while ((ref = refs_hash[j]) != NULL) {
if (ref->base == obj)
break;
@@ -125,9 +129,6 @@ void mark_reachable(struct object *obj, unsigned int mask)
if (!track_object_refs)
die("cannot do reachability with object refs turned off");
- /* nothing to lookup */
- if (!refs_hash_size)
- return;
/* If we've been here already, don't bother */
if (obj->flags & mask)
return;
diff --git a/object.c b/object.c
index b5d8ed467d..de244e2063 100644
--- a/object.c
+++ b/object.c
@@ -58,7 +58,7 @@ struct object *lookup_object(const unsigned char *sha1)
i = hashtable_index(sha1);
while ((obj = obj_hash[i]) != NULL) {
- if (!memcmp(sha1, obj->sha1, 20))
+ if (!hashcmp(sha1, obj->sha1))
break;
i++;
if (i == obj_hash_size)
@@ -73,7 +73,7 @@ static void grow_object_hash(void)
int new_hash_size = obj_hash_size < 32 ? 32 : 2 * obj_hash_size;
struct object **new_hash;
- new_hash = calloc(new_hash_size, sizeof(struct object *));
+ new_hash = xcalloc(new_hash_size, sizeof(struct object *));
for (i = 0; i < obj_hash_size; i++) {
struct object *obj = obj_hash[i];
if (!obj)
@@ -91,7 +91,7 @@ void created_object(const unsigned char *sha1, struct object *obj)
obj->used = 0;
obj->type = OBJ_NONE;
obj->flags = 0;
- memcpy(obj->sha1, sha1, 20);
+ hashcpy(obj->sha1, sha1);
if (obj_hash_size - 1 <= nr_objs * 2)
grow_object_hash();
@@ -138,42 +138,56 @@ struct object *lookup_unknown_object(const unsigned char *sha1)
return obj;
}
+struct object *parse_object_buffer(const unsigned char *sha1, const char *type, unsigned long size, void *buffer, int *eaten_p)
+{
+ struct object *obj;
+ int eaten = 0;
+
+ if (!strcmp(type, blob_type)) {
+ struct blob *blob = lookup_blob(sha1);
+ parse_blob_buffer(blob, buffer, size);
+ obj = &blob->object;
+ } else if (!strcmp(type, tree_type)) {
+ struct tree *tree = lookup_tree(sha1);
+ obj = &tree->object;
+ if (!tree->object.parsed) {
+ parse_tree_buffer(tree, buffer, size);
+ eaten = 1;
+ }
+ } else if (!strcmp(type, commit_type)) {
+ struct commit *commit = lookup_commit(sha1);
+ parse_commit_buffer(commit, buffer, size);
+ if (!commit->buffer) {
+ commit->buffer = buffer;
+ eaten = 1;
+ }
+ obj = &commit->object;
+ } else if (!strcmp(type, tag_type)) {
+ struct tag *tag = lookup_tag(sha1);
+ parse_tag_buffer(tag, buffer, size);
+ obj = &tag->object;
+ } else {
+ obj = NULL;
+ }
+ *eaten_p = eaten;
+ return obj;
+}
+
struct object *parse_object(const unsigned char *sha1)
{
unsigned long size;
char type[20];
+ int eaten;
void *buffer = read_sha1_file(sha1, type, &size);
+
if (buffer) {
struct object *obj;
if (check_sha1_signature(sha1, buffer, size, type) < 0)
printf("sha1 mismatch %s\n", sha1_to_hex(sha1));
- if (!strcmp(type, blob_type)) {
- struct blob *blob = lookup_blob(sha1);
- parse_blob_buffer(blob, buffer, size);
- obj = &blob->object;
- } else if (!strcmp(type, tree_type)) {
- struct tree *tree = lookup_tree(sha1);
- obj = &tree->object;
- if (!tree->object.parsed) {
- parse_tree_buffer(tree, buffer, size);
- buffer = NULL;
- }
- } else if (!strcmp(type, commit_type)) {
- struct commit *commit = lookup_commit(sha1);
- parse_commit_buffer(commit, buffer, size);
- if (!commit->buffer) {
- commit->buffer = buffer;
- buffer = NULL;
- }
- obj = &commit->object;
- } else if (!strcmp(type, tag_type)) {
- struct tag *tag = lookup_tag(sha1);
- parse_tag_buffer(tag, buffer, size);
- obj = &tag->object;
- } else {
- obj = NULL;
- }
- free(buffer);
+
+ obj = parse_object_buffer(sha1, type, size, buffer, &eaten);
+ if (!eaten)
+ free(buffer);
return obj;
}
return NULL;
diff --git a/object.h b/object.h
index 733faac4cc..caee733cde 100644
--- a/object.h
+++ b/object.h
@@ -27,17 +27,6 @@ struct object_array {
/*
* The object type is stored in 3 bits.
*/
-enum object_type {
- OBJ_NONE = 0,
- OBJ_COMMIT = 1,
- OBJ_TREE = 2,
- OBJ_BLOB = 3,
- OBJ_TAG = 4,
- /* 5/6 for future expansion */
- OBJ_DELTA = 7,
- OBJ_BAD,
-};
-
struct object {
unsigned parsed : 1;
unsigned used : 1;
@@ -70,6 +59,12 @@ void created_object(const unsigned char *sha1, struct object *obj);
/** Returns the object, having parsed it to find out what it is. **/
struct object *parse_object(const unsigned char *sha1);
+/* Given the result of read_sha1_file(), returns the object after
+ * parsing it. eaten_p indicates if the object has a borrowed copy
+ * of buffer and the caller should not free() it.
+ */
+struct object *parse_object_buffer(const unsigned char *sha1, const char *type, unsigned long size, void *buffer, int *eaten_p);
+
/** Returns the object, with potentially excess memory allocated. **/
struct object *lookup_unknown_object(const unsigned char *sha1);
diff --git a/pack-check.c b/pack-check.c
index 3a62e1b7e4..c0caaee093 100644
--- a/pack-check.c
+++ b/pack-check.c
@@ -29,10 +29,10 @@ static int verify_packfile(struct packed_git *p)
pack_base = p->pack_base;
SHA1_Update(&ctx, pack_base, pack_size - 20);
SHA1_Final(sha1, &ctx);
- if (memcmp(sha1, (char *) pack_base + pack_size - 20, 20))
+ if (hashcmp(sha1, (unsigned char *)pack_base + pack_size - 20))
return error("Packfile %s SHA1 mismatch with itself",
p->pack_name);
- if (memcmp(sha1, (char *) index_base + index_size - 40, 20))
+ if (hashcmp(sha1, (unsigned char *)index_base + index_size - 40))
return error("Packfile %s SHA1 mismatch with idx",
p->pack_name);
@@ -42,16 +42,16 @@ static int verify_packfile(struct packed_git *p)
*/
for (i = err = 0; i < nr_objects; i++) {
unsigned char sha1[20];
- struct pack_entry e;
void *data;
char type[20];
- unsigned long size;
+ unsigned long size, offset;
if (nth_packed_object_sha1(p, i, sha1))
die("internal error pack-check nth-packed-object");
- if (!find_pack_entry_one(sha1, &e, p))
+ offset = find_pack_entry_one(sha1, p);
+ if (!offset)
die("internal error pack-check find-pack-entry-one");
- data = unpack_entry_gently(&e, type, &size);
+ data = unpack_entry_gently(p, offset, type, &size);
if (!data) {
err = error("cannot unpack %s from %s",
sha1_to_hex(sha1), p->pack_name);
@@ -84,25 +84,26 @@ static void show_pack_info(struct packed_git *p)
for (i = 0; i < nr_objects; i++) {
unsigned char sha1[20], base_sha1[20];
- struct pack_entry e;
char type[20];
unsigned long size;
unsigned long store_size;
+ unsigned long offset;
unsigned int delta_chain_length;
if (nth_packed_object_sha1(p, i, sha1))
die("internal error pack-check nth-packed-object");
- if (!find_pack_entry_one(sha1, &e, p))
+ offset = find_pack_entry_one(sha1, p);
+ if (!offset)
die("internal error pack-check find-pack-entry-one");
- packed_object_info_detail(&e, type, &size, &store_size,
+ packed_object_info_detail(p, offset, type, &size, &store_size,
&delta_chain_length,
base_sha1);
printf("%s ", sha1_to_hex(sha1));
if (!delta_chain_length)
- printf("%-6s %lu %u\n", type, size, e.offset);
+ printf("%-6s %lu %lu\n", type, size, offset);
else {
- printf("%-6s %lu %u %u %s\n", type, size, e.offset,
+ printf("%-6s %lu %lu %u %s\n", type, size, offset,
delta_chain_length, sha1_to_hex(base_sha1));
if (delta_chain_length < MAX_CHAIN)
chain_histogram[delta_chain_length]++;
@@ -135,7 +136,7 @@ int verify_pack(struct packed_git *p, int verbose)
SHA1_Init(&ctx);
SHA1_Update(&ctx, index_base, index_size - 20);
SHA1_Final(sha1, &ctx);
- if (memcmp(sha1, (char *) index_base + index_size - 20, 20))
+ if (hashcmp(sha1, (unsigned char *)index_base + index_size - 20))
ret = error("Packfile index for %s SHA1 mismatch",
p->pack_name);
diff --git a/pack-redundant.c b/pack-redundant.c
index 41fb960569..edb5524fc4 100644
--- a/pack-redundant.c
+++ b/pack-redundant.c
@@ -13,7 +13,7 @@
static const char pack_redundant_usage[] =
"git-pack-redundant [ --verbose ] [ --alt-odb ] < --all | <.pack filename> ...>";
-static int load_all_packs = 0, verbose = 0, alt_odb = 0;
+static int load_all_packs, verbose, alt_odb;
struct llist_item {
struct llist_item *next;
@@ -37,7 +37,7 @@ struct pll {
struct pack_list *pl;
};
-static struct llist_item *free_nodes = NULL;
+static struct llist_item *free_nodes;
static inline void llist_item_put(struct llist_item *item)
{
@@ -139,7 +139,7 @@ static inline struct llist_item *llist_insert_sorted_unique(struct llist *list,
l = (hint == NULL) ? list->front : hint;
while (l) {
- int cmp = memcmp(l->sha1, sha1, 20);
+ int cmp = hashcmp(l->sha1, sha1);
if (cmp > 0) { /* we insert before this entry */
return llist_insert(list, prev, sha1);
}
@@ -162,7 +162,7 @@ redo_from_start:
l = (hint == NULL) ? list->front : hint;
prev = NULL;
while (l) {
- int cmp = memcmp(l->sha1, sha1, 20);
+ int cmp = hashcmp(l->sha1, sha1);
if (cmp > 0) /* not in list, since sorted */
return prev;
if(!cmp) { /* found */
@@ -256,7 +256,7 @@ static void cmp_two_packs(struct pack_list *p1, struct pack_list *p2)
while (p1_off <= p1->pack->index_size - 3 * 20 &&
p2_off <= p2->pack->index_size - 3 * 20)
{
- int cmp = memcmp(p1_base + p1_off, p2_base + p2_off, 20);
+ int cmp = hashcmp(p1_base + p1_off, p2_base + p2_off);
/* cmp ~ p1 - p2 */
if (cmp == 0) {
p1_hint = llist_sorted_remove(p1->unique_objects,
@@ -351,16 +351,16 @@ static size_t sizeof_union(struct packed_git *p1, struct packed_git *p2)
{
size_t ret = 0;
int p1_off, p2_off;
- char *p1_base, *p2_base;
+ unsigned char *p1_base, *p2_base;
p1_off = p2_off = 256 * 4 + 4;
- p1_base = (char *)p1->index_base;
- p2_base = (char *)p2->index_base;
+ p1_base = (unsigned char *)p1->index_base;
+ p2_base = (unsigned char *)p2->index_base;
while (p1_off <= p1->index_size - 3 * 20 &&
p2_off <= p2->index_size - 3 * 20)
{
- int cmp = memcmp(p1_base + p1_off, p2_base + p2_off, 20);
+ int cmp = hashcmp(p1_base + p1_off, p2_base + p2_off);
/* cmp ~ p1 - p2 */
if (cmp == 0) {
ret++;
diff --git a/pack.h b/pack.h
index eb07b033ae..4814800f28 100644
--- a/pack.h
+++ b/pack.h
@@ -16,7 +16,4 @@ struct pack_header {
};
extern int verify_pack(struct packed_git *, int);
-extern int check_reuse_pack_delta(struct packed_git *, unsigned long,
- unsigned char *, unsigned long *,
- enum object_type *);
#endif
diff --git a/pager.c b/pager.c
index dcb398da8e..4587fbbdb5 100644
--- a/pager.c
+++ b/pager.c
@@ -50,7 +50,7 @@ void setup_pager(void)
close(fd[0]);
close(fd[1]);
- setenv("LESS", "-RS", 0);
+ setenv("LESS", "FRSX", 0);
run_pager(pager);
die("unable to execute pager '%s'", pager);
exit(255);
diff --git a/patch-id.c b/patch-id.c
index 3b4c80f764..086d2d9c68 100644
--- a/patch-id.c
+++ b/patch-id.c
@@ -47,7 +47,7 @@ static void generate_id_list(void)
if (!get_sha1_hex(p, n)) {
flush_current_id(patchlen, sha1, &ctx);
- memcpy(sha1, n, 20);
+ hashcpy(sha1, n);
patchlen = 0;
continue;
}
diff --git a/path-list.c b/path-list.c
index f15a10de37..f8800f8e66 100644
--- a/path-list.c
+++ b/path-list.c
@@ -45,7 +45,7 @@ static int add_entry(struct path_list *list, const char *path)
(list->nr - index)
* sizeof(struct path_list_item));
list->items[index].path = list->strdup_paths ?
- strdup(path) : (char *)path;
+ xstrdup(path) : (char *)path;
list->items[index].util = NULL;
list->nr++;
@@ -57,7 +57,7 @@ struct path_list_item *path_list_insert(const char *path, struct path_list *list
int index = add_entry(list, path);
if (index < 0)
- index = 1 - index;
+ index = -1 - index;
return list->items + index;
}
@@ -85,8 +85,7 @@ void path_list_clear(struct path_list *list, int free_items)
for (i = 0; i < list->nr; i++) {
if (list->strdup_paths)
free(list->items[i].path);
- if (list->items[i].util)
- free(list->items[i].util);
+ free(list->items[i].util);
}
free(list->items);
}
diff --git a/path.c b/path.c
index db8905f3c3..d2c076d7cb 100644
--- a/path.c
+++ b/path.c
@@ -13,9 +13,15 @@
#include "cache.h"
#include <pwd.h>
-static char pathname[PATH_MAX];
static char bad_path[] = "/bad-path/";
+static char *get_pathname(void)
+{
+ static char pathname_array[4][PATH_MAX];
+ static int index;
+ return pathname_array[3 & ++index];
+}
+
static char *cleanup_path(char *path)
{
/* Clean it up */
@@ -31,6 +37,7 @@ char *mkpath(const char *fmt, ...)
{
va_list args;
unsigned len;
+ char *pathname = get_pathname();
va_start(args, fmt);
len = vsnprintf(pathname, PATH_MAX, fmt, args);
@@ -43,6 +50,7 @@ char *mkpath(const char *fmt, ...)
char *git_path(const char *fmt, ...)
{
const char *git_dir = get_git_dir();
+ char *pathname = get_pathname();
va_list args;
unsigned len;
@@ -271,7 +279,7 @@ int adjust_shared_perm(const char *path)
: 0));
if (S_ISDIR(mode))
mode |= S_ISGID;
- if (chmod(path, mode) < 0)
+ if ((mode & st.st_mode) != mode && chmod(path, mode) < 0)
return -2;
return 0;
}
diff --git a/peek-remote.c b/peek-remote.c
index 2b30980b04..353da002b4 100644
--- a/peek-remote.c
+++ b/peek-remote.c
@@ -1,7 +1,6 @@
#include "cache.h"
#include "refs.h"
#include "pkt-line.h"
-#include <sys/wait.h>
static const char peek_remote_usage[] =
"git-peek-remote [--exec=upload-pack] [host:]directory";
@@ -67,6 +66,6 @@ int main(int argc, char **argv)
ret = peek_remote(fd, flags);
close(fd[0]);
close(fd[1]);
- finish_connect(pid);
- return ret;
+ ret |= finish_connect(pid);
+ return !!ret;
}
diff --git a/perl/.gitignore b/perl/.gitignore
new file mode 100644
index 0000000000..e990caeea7
--- /dev/null
+++ b/perl/.gitignore
@@ -0,0 +1,4 @@
+Makefile
+blib
+blibdirs
+pm_to_blib
diff --git a/perl/Git.pm b/perl/Git.pm
new file mode 100644
index 0000000000..2b26b65bfb
--- /dev/null
+++ b/perl/Git.pm
@@ -0,0 +1,837 @@
+=head1 NAME
+
+Git - Perl interface to the Git version control system
+
+=cut
+
+
+package Git;
+
+use strict;
+
+
+BEGIN {
+
+our ($VERSION, @ISA, @EXPORT, @EXPORT_OK);
+
+# Totally unstable API.
+$VERSION = '0.01';
+
+
+=head1 SYNOPSIS
+
+ use Git;
+
+ my $version = Git::command_oneline('version');
+
+ git_cmd_try { Git::command_noisy('update-server-info') }
+ '%s failed w/ code %d';
+
+ my $repo = Git->repository (Directory => '/srv/git/cogito.git');
+
+
+ my @revs = $repo->command('rev-list', '--since=last monday', '--all');
+
+ my ($fh, $c) = $repo->command_output_pipe('rev-list', '--since=last monday', '--all');
+ my $lastrev = <$fh>; chomp $lastrev;
+ $repo->command_close_pipe($fh, $c);
+
+ my $lastrev = $repo->command_oneline( [ 'rev-list', '--all' ],
+ STDERR => 0 );
+
+=cut
+
+
+require Exporter;
+
+@ISA = qw(Exporter);
+
+@EXPORT = qw(git_cmd_try);
+
+# Methods which can be called as standalone functions as well:
+@EXPORT_OK = qw(command command_oneline command_noisy
+ command_output_pipe command_input_pipe command_close_pipe
+ version exec_path hash_object git_cmd_try);
+
+
+=head1 DESCRIPTION
+
+This module provides Perl scripts easy way to interface the Git version control
+system. The modules have an easy and well-tested way to call arbitrary Git
+commands; in the future, the interface will also provide specialized methods
+for doing easily operations which are not totally trivial to do over
+the generic command interface.
+
+While some commands can be executed outside of any context (e.g. 'version'
+or 'init-db'), most operations require a repository context, which in practice
+means getting an instance of the Git object using the repository() constructor.
+(In the future, we will also get a new_repository() constructor.) All commands
+called as methods of the object are then executed in the context of the
+repository.
+
+Part of the "repository state" is also information about path to the attached
+working copy (unless you work with a bare repository). You can also navigate
+inside of the working copy using the C<wc_chdir()> method. (Note that
+the repository object is self-contained and will not change working directory
+of your process.)
+
+TODO: In the future, we might also do
+
+ my $remoterepo = $repo->remote_repository (Name => 'cogito', Branch => 'master');
+ $remoterepo ||= Git->remote_repository ('http://git.or.cz/cogito.git/');
+ my @refs = $remoterepo->refs();
+
+Currently, the module merely wraps calls to external Git tools. In the future,
+it will provide a much faster way to interact with Git by linking directly
+to libgit. This should be completely opaque to the user, though (performance
+increate nonwithstanding).
+
+=cut
+
+
+use Carp qw(carp croak); # but croak is bad - throw instead
+use Error qw(:try);
+use Cwd qw(abs_path);
+
+}
+
+
+=head1 CONSTRUCTORS
+
+=over 4
+
+=item repository ( OPTIONS )
+
+=item repository ( DIRECTORY )
+
+=item repository ()
+
+Construct a new repository object.
+C<OPTIONS> are passed in a hash like fashion, using key and value pairs.
+Possible options are:
+
+B<Repository> - Path to the Git repository.
+
+B<WorkingCopy> - Path to the associated working copy; not strictly required
+as many commands will happily crunch on a bare repository.
+
+B<WorkingSubdir> - Subdirectory in the working copy to work inside.
+Just left undefined if you do not want to limit the scope of operations.
+
+B<Directory> - Path to the Git working directory in its usual setup.
+The C<.git> directory is searched in the directory and all the parent
+directories; if found, C<WorkingCopy> is set to the directory containing
+it and C<Repository> to the C<.git> directory itself. If no C<.git>
+directory was found, the C<Directory> is assumed to be a bare repository,
+C<Repository> is set to point at it and C<WorkingCopy> is left undefined.
+If the C<$GIT_DIR> environment variable is set, things behave as expected
+as well.
+
+You should not use both C<Directory> and either of C<Repository> and
+C<WorkingCopy> - the results of that are undefined.
+
+Alternatively, a directory path may be passed as a single scalar argument
+to the constructor; it is equivalent to setting only the C<Directory> option
+field.
+
+Calling the constructor with no options whatsoever is equivalent to
+calling it with C<< Directory => '.' >>. In general, if you are building
+a standard porcelain command, simply doing C<< Git->repository() >> should
+do the right thing and setup the object to reflect exactly where the user
+is right now.
+
+=cut
+
+sub repository {
+ my $class = shift;
+ my @args = @_;
+ my %opts = ();
+ my $self;
+
+ if (defined $args[0]) {
+ if ($#args % 2 != 1) {
+ # Not a hash.
+ $#args == 0 or throw Error::Simple("bad usage");
+ %opts = ( Directory => $args[0] );
+ } else {
+ %opts = @args;
+ }
+ }
+
+ if (not defined $opts{Repository} and not defined $opts{WorkingCopy}) {
+ $opts{Directory} ||= '.';
+ }
+
+ if ($opts{Directory}) {
+ -d $opts{Directory} or throw Error::Simple("Directory not found: $!");
+
+ my $search = Git->repository(WorkingCopy => $opts{Directory});
+ my $dir;
+ try {
+ $dir = $search->command_oneline(['rev-parse', '--git-dir'],
+ STDERR => 0);
+ } catch Git::Error::Command with {
+ $dir = undef;
+ };
+
+ if ($dir) {
+ $dir =~ m#^/# or $dir = $opts{Directory} . '/' . $dir;
+ $opts{Repository} = $dir;
+
+ # If --git-dir went ok, this shouldn't die either.
+ my $prefix = $search->command_oneline('rev-parse', '--show-prefix');
+ $dir = abs_path($opts{Directory}) . '/';
+ if ($prefix) {
+ if (substr($dir, -length($prefix)) ne $prefix) {
+ throw Error::Simple("rev-parse confused me - $dir does not have trailing $prefix");
+ }
+ substr($dir, -length($prefix)) = '';
+ }
+ $opts{WorkingCopy} = $dir;
+ $opts{WorkingSubdir} = $prefix;
+
+ } else {
+ # A bare repository? Let's see...
+ $dir = $opts{Directory};
+
+ unless (-d "$dir/refs" and -d "$dir/objects" and -e "$dir/HEAD") {
+ # Mimick git-rev-parse --git-dir error message:
+ throw Error::Simple('fatal: Not a git repository');
+ }
+ my $search = Git->repository(Repository => $dir);
+ try {
+ $search->command('symbolic-ref', 'HEAD');
+ } catch Git::Error::Command with {
+ # Mimick git-rev-parse --git-dir error message:
+ throw Error::Simple('fatal: Not a git repository');
+ }
+
+ $opts{Repository} = abs_path($dir);
+ }
+
+ delete $opts{Directory};
+ }
+
+ $self = { opts => \%opts };
+ bless $self, $class;
+}
+
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item command ( COMMAND [, ARGUMENTS... ] )
+
+=item command ( [ COMMAND, ARGUMENTS... ], { Opt => Val ... } )
+
+Execute the given Git C<COMMAND> (specify it without the 'git-'
+prefix), optionally with the specified extra C<ARGUMENTS>.
+
+The second more elaborate form can be used if you want to further adjust
+the command execution. Currently, only one option is supported:
+
+B<STDERR> - How to deal with the command's error output. By default (C<undef>)
+it is delivered to the caller's C<STDERR>. A false value (0 or '') will cause
+it to be thrown away. If you want to process it, you can get it in a filehandle
+you specify, but you must be extremely careful; if the error output is not
+very short and you want to read it in the same process as where you called
+C<command()>, you are set up for a nice deadlock!
+
+The method can be called without any instance or on a specified Git repository
+(in that case the command will be run in the repository context).
+
+In scalar context, it returns all the command output in a single string
+(verbatim).
+
+In array context, it returns an array containing lines printed to the
+command's stdout (without trailing newlines).
+
+In both cases, the command's stdin and stderr are the same as the caller's.
+
+=cut
+
+sub command {
+ my ($fh, $ctx) = command_output_pipe(@_);
+
+ if (not defined wantarray) {
+ # Nothing to pepper the possible exception with.
+ _cmd_close($fh, $ctx);
+
+ } elsif (not wantarray) {
+ local $/;
+ my $text = <$fh>;
+ try {
+ _cmd_close($fh, $ctx);
+ } catch Git::Error::Command with {
+ # Pepper with the output:
+ my $E = shift;
+ $E->{'-outputref'} = \$text;
+ throw $E;
+ };
+ return $text;
+
+ } else {
+ my @lines = <$fh>;
+ chomp @lines;
+ try {
+ _cmd_close($fh, $ctx);
+ } catch Git::Error::Command with {
+ my $E = shift;
+ $E->{'-outputref'} = \@lines;
+ throw $E;
+ };
+ return @lines;
+ }
+}
+
+
+=item command_oneline ( COMMAND [, ARGUMENTS... ] )
+
+=item command_oneline ( [ COMMAND, ARGUMENTS... ], { Opt => Val ... } )
+
+Execute the given C<COMMAND> in the same way as command()
+does but always return a scalar string containing the first line
+of the command's standard output.
+
+=cut
+
+sub command_oneline {
+ my ($fh, $ctx) = command_output_pipe(@_);
+
+ my $line = <$fh>;
+ defined $line and chomp $line;
+ try {
+ _cmd_close($fh, $ctx);
+ } catch Git::Error::Command with {
+ # Pepper with the output:
+ my $E = shift;
+ $E->{'-outputref'} = \$line;
+ throw $E;
+ };
+ return $line;
+}
+
+
+=item command_output_pipe ( COMMAND [, ARGUMENTS... ] )
+
+=item command_output_pipe ( [ COMMAND, ARGUMENTS... ], { Opt => Val ... } )
+
+Execute the given C<COMMAND> in the same way as command()
+does but return a pipe filehandle from which the command output can be
+read.
+
+The function can return C<($pipe, $ctx)> in array context.
+See C<command_close_pipe()> for details.
+
+=cut
+
+sub command_output_pipe {
+ _command_common_pipe('-|', @_);
+}
+
+
+=item command_input_pipe ( COMMAND [, ARGUMENTS... ] )
+
+=item command_input_pipe ( [ COMMAND, ARGUMENTS... ], { Opt => Val ... } )
+
+Execute the given C<COMMAND> in the same way as command_output_pipe()
+does but return an input pipe filehandle instead; the command output
+is not captured.
+
+The function can return C<($pipe, $ctx)> in array context.
+See C<command_close_pipe()> for details.
+
+=cut
+
+sub command_input_pipe {
+ _command_common_pipe('|-', @_);
+}
+
+
+=item command_close_pipe ( PIPE [, CTX ] )
+
+Close the C<PIPE> as returned from C<command_*_pipe()>, checking
+whether the command finished successfuly. The optional C<CTX> argument
+is required if you want to see the command name in the error message,
+and it is the second value returned by C<command_*_pipe()> when
+called in array context. The call idiom is:
+
+ my ($fh, $ctx) = $r->command_output_pipe('status');
+ while (<$fh>) { ... }
+ $r->command_close_pipe($fh, $ctx);
+
+Note that you should not rely on whatever actually is in C<CTX>;
+currently it is simply the command name but in future the context might
+have more complicated structure.
+
+=cut
+
+sub command_close_pipe {
+ my ($self, $fh, $ctx) = _maybe_self(@_);
+ $ctx ||= '<unknown>';
+ _cmd_close($fh, $ctx);
+}
+
+
+=item command_noisy ( COMMAND [, ARGUMENTS... ] )
+
+Execute the given C<COMMAND> in the same way as command() does but do not
+capture the command output - the standard output is not redirected and goes
+to the standard output of the caller application.
+
+While the method is called command_noisy(), you might want to as well use
+it for the most silent Git commands which you know will never pollute your
+stdout but you want to avoid the overhead of the pipe setup when calling them.
+
+The function returns only after the command has finished running.
+
+=cut
+
+sub command_noisy {
+ my ($self, $cmd, @args) = _maybe_self(@_);
+ _check_valid_cmd($cmd);
+
+ my $pid = fork;
+ if (not defined $pid) {
+ throw Error::Simple("fork failed: $!");
+ } elsif ($pid == 0) {
+ _cmd_exec($self, $cmd, @args);
+ }
+ if (waitpid($pid, 0) > 0 and $?>>8 != 0) {
+ throw Git::Error::Command(join(' ', $cmd, @args), $? >> 8);
+ }
+}
+
+
+=item version ()
+
+Return the Git version in use.
+
+=cut
+
+sub version {
+ my $verstr = command_oneline('--version');
+ $verstr =~ s/^git version //;
+ $verstr;
+}
+
+
+=item exec_path ()
+
+Return path to the Git sub-command executables (the same as
+C<git --exec-path>). Useful mostly only internally.
+
+=cut
+
+sub exec_path { command_oneline('--exec-path') }
+
+
+=item repo_path ()
+
+Return path to the git repository. Must be called on a repository instance.
+
+=cut
+
+sub repo_path { $_[0]->{opts}->{Repository} }
+
+
+=item wc_path ()
+
+Return path to the working copy. Must be called on a repository instance.
+
+=cut
+
+sub wc_path { $_[0]->{opts}->{WorkingCopy} }
+
+
+=item wc_subdir ()
+
+Return path to the subdirectory inside of a working copy. Must be called
+on a repository instance.
+
+=cut
+
+sub wc_subdir { $_[0]->{opts}->{WorkingSubdir} ||= '' }
+
+
+=item wc_chdir ( SUBDIR )
+
+Change the working copy subdirectory to work within. The C<SUBDIR> is
+relative to the working copy root directory (not the current subdirectory).
+Must be called on a repository instance attached to a working copy
+and the directory must exist.
+
+=cut
+
+sub wc_chdir {
+ my ($self, $subdir) = @_;
+ $self->wc_path()
+ or throw Error::Simple("bare repository");
+
+ -d $self->wc_path().'/'.$subdir
+ or throw Error::Simple("subdir not found: $!");
+ # Of course we will not "hold" the subdirectory so anyone
+ # can delete it now and we will never know. But at least we tried.
+
+ $self->{opts}->{WorkingSubdir} = $subdir;
+}
+
+
+=item config ( VARIABLE )
+
+Retrieve the configuration C<VARIABLE> in the same manner as C<repo-config>
+does. In scalar context requires the variable to be set only one time
+(exception is thrown otherwise), in array context returns allows the
+variable to be set multiple times and returns all the values.
+
+Must be called on a repository instance.
+
+This currently wraps command('repo-config') so it is not so fast.
+
+=cut
+
+sub config {
+ my ($self, $var) = @_;
+ $self->repo_path()
+ or throw Error::Simple("not a repository");
+
+ try {
+ if (wantarray) {
+ return $self->command('repo-config', '--get-all', $var);
+ } else {
+ return $self->command_oneline('repo-config', '--get', $var);
+ }
+ } catch Git::Error::Command with {
+ my $E = shift;
+ if ($E->value() == 1) {
+ # Key not found.
+ return undef;
+ } else {
+ throw $E;
+ }
+ };
+}
+
+
+=item ident ( TYPE | IDENTSTR )
+
+=item ident_person ( TYPE | IDENTSTR | IDENTARRAY )
+
+This suite of functions retrieves and parses ident information, as stored
+in the commit and tag objects or produced by C<var GIT_type_IDENT> (thus
+C<TYPE> can be either I<author> or I<committer>; case is insignificant).
+
+The C<ident> method retrieves the ident information from C<git-var>
+and either returns it as a scalar string or as an array with the fields parsed.
+Alternatively, it can take a prepared ident string (e.g. from the commit
+object) and just parse it.
+
+C<ident_person> returns the person part of the ident - name and email;
+it can take the same arguments as C<ident> or the array returned by C<ident>.
+
+The synopsis is like:
+
+ my ($name, $email, $time_tz) = ident('author');
+ "$name <$email>" eq ident_person('author');
+ "$name <$email>" eq ident_person($name);
+ $time_tz =~ /^\d+ [+-]\d{4}$/;
+
+Both methods must be called on a repository instance.
+
+=cut
+
+sub ident {
+ my ($self, $type) = @_;
+ my $identstr;
+ if (lc $type eq lc 'committer' or lc $type eq lc 'author') {
+ $identstr = $self->command_oneline('var', 'GIT_'.uc($type).'_IDENT');
+ } else {
+ $identstr = $type;
+ }
+ if (wantarray) {
+ return $identstr =~ /^(.*) <(.*)> (\d+ [+-]\d{4})$/;
+ } else {
+ return $identstr;
+ }
+}
+
+sub ident_person {
+ my ($self, @ident) = @_;
+ $#ident == 0 and @ident = $self->ident($ident[0]);
+ return "$ident[0] <$ident[1]>";
+}
+
+
+=item hash_object ( TYPE, FILENAME )
+
+Compute the SHA1 object id of the given C<FILENAME> (or data waiting in
+C<FILEHANDLE>) considering it is of the C<TYPE> object type (C<blob>,
+C<commit>, C<tree>).
+
+The method can be called without any instance or on a specified Git repository,
+it makes zero difference.
+
+The function returns the SHA1 hash.
+
+=cut
+
+# TODO: Support for passing FILEHANDLE instead of FILENAME
+sub hash_object {
+ my ($self, $type, $file) = _maybe_self(@_);
+ command_oneline('hash-object', '-t', $type, $file);
+}
+
+
+
+=back
+
+=head1 ERROR HANDLING
+
+All functions are supposed to throw Perl exceptions in case of errors.
+See the L<Error> module on how to catch those. Most exceptions are mere
+L<Error::Simple> instances.
+
+However, the C<command()>, C<command_oneline()> and C<command_noisy()>
+functions suite can throw C<Git::Error::Command> exceptions as well: those are
+thrown when the external command returns an error code and contain the error
+code as well as access to the captured command's output. The exception class
+provides the usual C<stringify> and C<value> (command's exit code) methods and
+in addition also a C<cmd_output> method that returns either an array or a
+string with the captured command output (depending on the original function
+call context; C<command_noisy()> returns C<undef>) and $<cmdline> which
+returns the command and its arguments (but without proper quoting).
+
+Note that the C<command_*_pipe()> functions cannot throw this exception since
+it has no idea whether the command failed or not. You will only find out
+at the time you C<close> the pipe; if you want to have that automated,
+use C<command_close_pipe()>, which can throw the exception.
+
+=cut
+
+{
+ package Git::Error::Command;
+
+ @Git::Error::Command::ISA = qw(Error);
+
+ sub new {
+ my $self = shift;
+ my $cmdline = '' . shift;
+ my $value = 0 + shift;
+ my $outputref = shift;
+ my(@args) = ();
+
+ local $Error::Depth = $Error::Depth + 1;
+
+ push(@args, '-cmdline', $cmdline);
+ push(@args, '-value', $value);
+ push(@args, '-outputref', $outputref);
+
+ $self->SUPER::new(-text => 'command returned error', @args);
+ }
+
+ sub stringify {
+ my $self = shift;
+ my $text = $self->SUPER::stringify;
+ $self->cmdline() . ': ' . $text . ': ' . $self->value() . "\n";
+ }
+
+ sub cmdline {
+ my $self = shift;
+ $self->{'-cmdline'};
+ }
+
+ sub cmd_output {
+ my $self = shift;
+ my $ref = $self->{'-outputref'};
+ defined $ref or undef;
+ if (ref $ref eq 'ARRAY') {
+ return @$ref;
+ } else { # SCALAR
+ return $$ref;
+ }
+ }
+}
+
+=over 4
+
+=item git_cmd_try { CODE } ERRMSG
+
+This magical statement will automatically catch any C<Git::Error::Command>
+exceptions thrown by C<CODE> and make your program die with C<ERRMSG>
+on its lips; the message will have %s substituted for the command line
+and %d for the exit status. This statement is useful mostly for producing
+more user-friendly error messages.
+
+In case of no exception caught the statement returns C<CODE>'s return value.
+
+Note that this is the only auto-exported function.
+
+=cut
+
+sub git_cmd_try(&$) {
+ my ($code, $errmsg) = @_;
+ my @result;
+ my $err;
+ my $array = wantarray;
+ try {
+ if ($array) {
+ @result = &$code;
+ } else {
+ $result[0] = &$code;
+ }
+ } catch Git::Error::Command with {
+ my $E = shift;
+ $err = $errmsg;
+ $err =~ s/\%s/$E->cmdline()/ge;
+ $err =~ s/\%d/$E->value()/ge;
+ # We can't croak here since Error.pm would mangle
+ # that to Error::Simple.
+ };
+ $err and croak $err;
+ return $array ? @result : $result[0];
+}
+
+
+=back
+
+=head1 COPYRIGHT
+
+Copyright 2006 by Petr Baudis E<lt>pasky@suse.czE<gt>.
+
+This module is free software; it may be used, copied, modified
+and distributed under the terms of the GNU General Public Licence,
+either version 2, or (at your option) any later version.
+
+=cut
+
+
+# Take raw method argument list and return ($obj, @args) in case
+# the method was called upon an instance and (undef, @args) if
+# it was called directly.
+sub _maybe_self {
+ # This breaks inheritance. Oh well.
+ ref $_[0] eq 'Git' ? @_ : (undef, @_);
+}
+
+# Check if the command id is something reasonable.
+sub _check_valid_cmd {
+ my ($cmd) = @_;
+ $cmd =~ /^[a-z0-9A-Z_-]+$/ or throw Error::Simple("bad command: $cmd");
+}
+
+# Common backend for the pipe creators.
+sub _command_common_pipe {
+ my $direction = shift;
+ my ($self, @p) = _maybe_self(@_);
+ my (%opts, $cmd, @args);
+ if (ref $p[0]) {
+ ($cmd, @args) = @{shift @p};
+ %opts = ref $p[0] ? %{$p[0]} : @p;
+ } else {
+ ($cmd, @args) = @p;
+ }
+ _check_valid_cmd($cmd);
+
+ my $fh;
+ if ($^O eq '##INSERT_ACTIVESTATE_STRING_HERE##') {
+ # ActiveState Perl
+ #defined $opts{STDERR} and
+ # warn 'ignoring STDERR option - running w/ ActiveState';
+ $direction eq '-|' or
+ die 'input pipe for ActiveState not implemented';
+ tie ($fh, 'Git::activestate_pipe', $cmd, @args);
+
+ } else {
+ my $pid = open($fh, $direction);
+ if (not defined $pid) {
+ throw Error::Simple("open failed: $!");
+ } elsif ($pid == 0) {
+ if (defined $opts{STDERR}) {
+ close STDERR;
+ }
+ if ($opts{STDERR}) {
+ open (STDERR, '>&', $opts{STDERR})
+ or die "dup failed: $!";
+ }
+ _cmd_exec($self, $cmd, @args);
+ }
+ }
+ return wantarray ? ($fh, join(' ', $cmd, @args)) : $fh;
+}
+
+# When already in the subprocess, set up the appropriate state
+# for the given repository and execute the git command.
+sub _cmd_exec {
+ my ($self, @args) = @_;
+ if ($self) {
+ $self->repo_path() and $ENV{'GIT_DIR'} = $self->repo_path();
+ $self->wc_path() and chdir($self->wc_path());
+ $self->wc_subdir() and chdir($self->wc_subdir());
+ }
+ _execv_git_cmd(@args);
+ die "exec failed: $!";
+}
+
+# Execute the given Git command ($_[0]) with arguments ($_[1..])
+# by searching for it at proper places.
+sub _execv_git_cmd { exec('git', @_); }
+
+# Close pipe to a subprocess.
+sub _cmd_close {
+ my ($fh, $ctx) = @_;
+ if (not close $fh) {
+ if ($!) {
+ # It's just close, no point in fatalities
+ carp "error closing pipe: $!";
+ } elsif ($? >> 8) {
+ # The caller should pepper this.
+ throw Git::Error::Command($ctx, $? >> 8);
+ }
+ # else we might e.g. closed a live stream; the command
+ # dying of SIGPIPE would drive us here.
+ }
+}
+
+
+sub DESTROY { }
+
+
+# Pipe implementation for ActiveState Perl.
+
+package Git::activestate_pipe;
+use strict;
+
+sub TIEHANDLE {
+ my ($class, @params) = @_;
+ # FIXME: This is probably horrible idea and the thing will explode
+ # at the moment you give it arguments that require some quoting,
+ # but I have no ActiveState clue... --pasky
+ my $cmdline = join " ", @params;
+ my @data = qx{$cmdline};
+ bless { i => 0, data => \@data }, $class;
+}
+
+sub READLINE {
+ my $self = shift;
+ if ($self->{i} >= scalar @{$self->{data}}) {
+ return undef;
+ }
+ return $self->{'data'}->[ $self->{i}++ ];
+}
+
+sub CLOSE {
+ my $self = shift;
+ delete $self->{data};
+ delete $self->{i};
+}
+
+sub EOF {
+ my $self = shift;
+ return ($self->{i} >= scalar @{$self->{data}});
+}
+
+
+1; # Famous last words
diff --git a/perl/Makefile.PL b/perl/Makefile.PL
new file mode 100644
index 0000000000..de73235e4c
--- /dev/null
+++ b/perl/Makefile.PL
@@ -0,0 +1,28 @@
+use ExtUtils::MakeMaker;
+
+sub MY::postamble {
+ return <<'MAKE_FRAG';
+instlibdir:
+ @echo '$(INSTALLSITELIB)'
+
+MAKE_FRAG
+}
+
+my %pm = ('Git.pm' => '$(INST_LIBDIR)/Git.pm');
+
+# We come with our own bundled Error.pm. It's not in the set of default
+# Perl modules so install it if it's not available on the system yet.
+eval { require Error };
+if ($@) {
+ $pm{'private-Error.pm'} = '$(INST_LIBDIR)/Error.pm';
+}
+
+my %extra;
+$extra{DESTDIR} = $ENV{DESTDIR} if $ENV{DESTDIR};
+
+WriteMakefile(
+ NAME => 'Git',
+ VERSION_FROM => 'Git.pm',
+ PM => \%pm,
+ %extra
+);
diff --git a/perl/private-Error.pm b/perl/private-Error.pm
new file mode 100644
index 0000000000..8fff86699f
--- /dev/null
+++ b/perl/private-Error.pm
@@ -0,0 +1,827 @@
+# Error.pm
+#
+# Copyright (c) 1997-8 Graham Barr <gbarr@ti.com>. All rights reserved.
+# This program is free software; you can redistribute it and/or
+# modify it under the same terms as Perl itself.
+#
+# Based on my original Error.pm, and Exceptions.pm by Peter Seibel
+# <peter@weblogic.com> and adapted by Jesse Glick <jglick@sig.bsh.com>.
+#
+# but modified ***significantly***
+
+package Error;
+
+use strict;
+use vars qw($VERSION);
+use 5.004;
+
+$VERSION = "0.15009";
+
+use overload (
+ '""' => 'stringify',
+ '0+' => 'value',
+ 'bool' => sub { return 1; },
+ 'fallback' => 1
+);
+
+$Error::Depth = 0; # Depth to pass to caller()
+$Error::Debug = 0; # Generate verbose stack traces
+@Error::STACK = (); # Clause stack for try
+$Error::THROWN = undef; # last error thrown, a workaround until die $ref works
+
+my $LAST; # Last error created
+my %ERROR; # Last error associated with package
+
+sub throw_Error_Simple
+{
+ my $args = shift;
+ return Error::Simple->new($args->{'text'});
+}
+
+$Error::ObjectifyCallback = \&throw_Error_Simple;
+
+
+# Exported subs are defined in Error::subs
+
+sub import {
+ shift;
+ local $Exporter::ExportLevel = $Exporter::ExportLevel + 1;
+ Error::subs->import(@_);
+}
+
+# I really want to use last for the name of this method, but it is a keyword
+# which prevent the syntax last Error
+
+sub prior {
+ shift; # ignore
+
+ return $LAST unless @_;
+
+ my $pkg = shift;
+ return exists $ERROR{$pkg} ? $ERROR{$pkg} : undef
+ unless ref($pkg);
+
+ my $obj = $pkg;
+ my $err = undef;
+ if($obj->isa('HASH')) {
+ $err = $obj->{'__Error__'}
+ if exists $obj->{'__Error__'};
+ }
+ elsif($obj->isa('GLOB')) {
+ $err = ${*$obj}{'__Error__'}
+ if exists ${*$obj}{'__Error__'};
+ }
+
+ $err;
+}
+
+sub flush {
+ shift; #ignore
+
+ unless (@_) {
+ $LAST = undef;
+ return;
+ }
+
+ my $pkg = shift;
+ return unless ref($pkg);
+
+ undef $ERROR{$pkg} if defined $ERROR{$pkg};
+}
+
+# Return as much information as possible about where the error
+# happened. The -stacktrace element only exists if $Error::DEBUG
+# was set when the error was created
+
+sub stacktrace {
+ my $self = shift;
+
+ return $self->{'-stacktrace'}
+ if exists $self->{'-stacktrace'};
+
+ my $text = exists $self->{'-text'} ? $self->{'-text'} : "Died";
+
+ $text .= sprintf(" at %s line %d.\n", $self->file, $self->line)
+ unless($text =~ /\n$/s);
+
+ $text;
+}
+
+# Allow error propagation, ie
+#
+# $ber->encode(...) or
+# return Error->prior($ber)->associate($ldap);
+
+sub associate {
+ my $err = shift;
+ my $obj = shift;
+
+ return unless ref($obj);
+
+ if($obj->isa('HASH')) {
+ $obj->{'__Error__'} = $err;
+ }
+ elsif($obj->isa('GLOB')) {
+ ${*$obj}{'__Error__'} = $err;
+ }
+ $obj = ref($obj);
+ $ERROR{ ref($obj) } = $err;
+
+ return;
+}
+
+sub new {
+ my $self = shift;
+ my($pkg,$file,$line) = caller($Error::Depth);
+
+ my $err = bless {
+ '-package' => $pkg,
+ '-file' => $file,
+ '-line' => $line,
+ @_
+ }, $self;
+
+ $err->associate($err->{'-object'})
+ if(exists $err->{'-object'});
+
+ # To always create a stacktrace would be very inefficient, so
+ # we only do it if $Error::Debug is set
+
+ if($Error::Debug) {
+ require Carp;
+ local $Carp::CarpLevel = $Error::Depth;
+ my $text = defined($err->{'-text'}) ? $err->{'-text'} : "Error";
+ my $trace = Carp::longmess($text);
+ # Remove try calls from the trace
+ $trace =~ s/(\n\s+\S+__ANON__[^\n]+)?\n\s+eval[^\n]+\n\s+Error::subs::try[^\n]+(?=\n)//sog;
+ $trace =~ s/(\n\s+\S+__ANON__[^\n]+)?\n\s+eval[^\n]+\n\s+Error::subs::run_clauses[^\n]+\n\s+Error::subs::try[^\n]+(?=\n)//sog;
+ $err->{'-stacktrace'} = $trace
+ }
+
+ $@ = $LAST = $ERROR{$pkg} = $err;
+}
+
+# Throw an error. this contains some very gory code.
+
+sub throw {
+ my $self = shift;
+ local $Error::Depth = $Error::Depth + 1;
+
+ # if we are not rethrow-ing then create the object to throw
+ $self = $self->new(@_) unless ref($self);
+
+ die $Error::THROWN = $self;
+}
+
+# syntactic sugar for
+#
+# die with Error( ... );
+
+sub with {
+ my $self = shift;
+ local $Error::Depth = $Error::Depth + 1;
+
+ $self->new(@_);
+}
+
+# syntactic sugar for
+#
+# record Error( ... ) and return;
+
+sub record {
+ my $self = shift;
+ local $Error::Depth = $Error::Depth + 1;
+
+ $self->new(@_);
+}
+
+# catch clause for
+#
+# try { ... } catch CLASS with { ... }
+
+sub catch {
+ my $pkg = shift;
+ my $code = shift;
+ my $clauses = shift || {};
+ my $catch = $clauses->{'catch'} ||= [];
+
+ unshift @$catch, $pkg, $code;
+
+ $clauses;
+}
+
+# Object query methods
+
+sub object {
+ my $self = shift;
+ exists $self->{'-object'} ? $self->{'-object'} : undef;
+}
+
+sub file {
+ my $self = shift;
+ exists $self->{'-file'} ? $self->{'-file'} : undef;
+}
+
+sub line {
+ my $self = shift;
+ exists $self->{'-line'} ? $self->{'-line'} : undef;
+}
+
+sub text {
+ my $self = shift;
+ exists $self->{'-text'} ? $self->{'-text'} : undef;
+}
+
+# overload methods
+
+sub stringify {
+ my $self = shift;
+ defined $self->{'-text'} ? $self->{'-text'} : "Died";
+}
+
+sub value {
+ my $self = shift;
+ exists $self->{'-value'} ? $self->{'-value'} : undef;
+}
+
+package Error::Simple;
+
+@Error::Simple::ISA = qw(Error);
+
+sub new {
+ my $self = shift;
+ my $text = "" . shift;
+ my $value = shift;
+ my(@args) = ();
+
+ local $Error::Depth = $Error::Depth + 1;
+
+ @args = ( -file => $1, -line => $2)
+ if($text =~ s/\s+at\s+(\S+)\s+line\s+(\d+)(?:,\s*<[^>]*>\s+line\s+\d+)?\.?\n?$//s);
+ push(@args, '-value', 0 + $value)
+ if defined($value);
+
+ $self->SUPER::new(-text => $text, @args);
+}
+
+sub stringify {
+ my $self = shift;
+ my $text = $self->SUPER::stringify;
+ $text .= sprintf(" at %s line %d.\n", $self->file, $self->line)
+ unless($text =~ /\n$/s);
+ $text;
+}
+
+##########################################################################
+##########################################################################
+
+# Inspired by code from Jesse Glick <jglick@sig.bsh.com> and
+# Peter Seibel <peter@weblogic.com>
+
+package Error::subs;
+
+use Exporter ();
+use vars qw(@EXPORT_OK @ISA %EXPORT_TAGS);
+
+@EXPORT_OK = qw(try with finally except otherwise);
+%EXPORT_TAGS = (try => \@EXPORT_OK);
+
+@ISA = qw(Exporter);
+
+
+sub blessed {
+ my $item = shift;
+ local $@; # don't kill an outer $@
+ ref $item and eval { $item->can('can') };
+}
+
+
+sub run_clauses ($$$\@) {
+ my($clauses,$err,$wantarray,$result) = @_;
+ my $code = undef;
+
+ $err = $Error::ObjectifyCallback->({'text' =>$err}) unless ref($err);
+
+ CATCH: {
+
+ # catch
+ my $catch;
+ if(defined($catch = $clauses->{'catch'})) {
+ my $i = 0;
+
+ CATCHLOOP:
+ for( ; $i < @$catch ; $i += 2) {
+ my $pkg = $catch->[$i];
+ unless(defined $pkg) {
+ #except
+ splice(@$catch,$i,2,$catch->[$i+1]->());
+ $i -= 2;
+ next CATCHLOOP;
+ }
+ elsif(blessed($err) && $err->isa($pkg)) {
+ $code = $catch->[$i+1];
+ while(1) {
+ my $more = 0;
+ local($Error::THROWN);
+ my $ok = eval {
+ if($wantarray) {
+ @{$result} = $code->($err,\$more);
+ }
+ elsif(defined($wantarray)) {
+ @{$result} = ();
+ $result->[0] = $code->($err,\$more);
+ }
+ else {
+ $code->($err,\$more);
+ }
+ 1;
+ };
+ if( $ok ) {
+ next CATCHLOOP if $more;
+ undef $err;
+ }
+ else {
+ $err = defined($Error::THROWN)
+ ? $Error::THROWN : $@;
+ $err = $Error::ObjectifyCallback->({'text' =>$err})
+ unless ref($err);
+ }
+ last CATCH;
+ };
+ }
+ }
+ }
+
+ # otherwise
+ my $owise;
+ if(defined($owise = $clauses->{'otherwise'})) {
+ my $code = $clauses->{'otherwise'};
+ my $more = 0;
+ my $ok = eval {
+ if($wantarray) {
+ @{$result} = $code->($err,\$more);
+ }
+ elsif(defined($wantarray)) {
+ @{$result} = ();
+ $result->[0] = $code->($err,\$more);
+ }
+ else {
+ $code->($err,\$more);
+ }
+ 1;
+ };
+ if( $ok ) {
+ undef $err;
+ }
+ else {
+ $err = defined($Error::THROWN)
+ ? $Error::THROWN : $@;
+
+ $err = $Error::ObjectifyCallback->({'text' =>$err})
+ unless ref($err);
+ }
+ }
+ }
+ $err;
+}
+
+sub try (&;$) {
+ my $try = shift;
+ my $clauses = @_ ? shift : {};
+ my $ok = 0;
+ my $err = undef;
+ my @result = ();
+
+ unshift @Error::STACK, $clauses;
+
+ my $wantarray = wantarray();
+
+ do {
+ local $Error::THROWN = undef;
+ local $@ = undef;
+
+ $ok = eval {
+ if($wantarray) {
+ @result = $try->();
+ }
+ elsif(defined $wantarray) {
+ $result[0] = $try->();
+ }
+ else {
+ $try->();
+ }
+ 1;
+ };
+
+ $err = defined($Error::THROWN) ? $Error::THROWN : $@
+ unless $ok;
+ };
+
+ shift @Error::STACK;
+
+ $err = run_clauses($clauses,$err,wantarray,@result)
+ unless($ok);
+
+ $clauses->{'finally'}->()
+ if(defined($clauses->{'finally'}));
+
+ if (defined($err))
+ {
+ if (blessed($err) && $err->can('throw'))
+ {
+ throw $err;
+ }
+ else
+ {
+ die $err;
+ }
+ }
+
+ wantarray ? @result : $result[0];
+}
+
+# Each clause adds a sub to the list of clauses. The finally clause is
+# always the last, and the otherwise clause is always added just before
+# the finally clause.
+#
+# All clauses, except the finally clause, add a sub which takes one argument
+# this argument will be the error being thrown. The sub will return a code ref
+# if that clause can handle that error, otherwise undef is returned.
+#
+# The otherwise clause adds a sub which unconditionally returns the users
+# code reference, this is why it is forced to be last.
+#
+# The catch clause is defined in Error.pm, as the syntax causes it to
+# be called as a method
+
+sub with (&;$) {
+ @_
+}
+
+sub finally (&) {
+ my $code = shift;
+ my $clauses = { 'finally' => $code };
+ $clauses;
+}
+
+# The except clause is a block which returns a hashref or a list of
+# key-value pairs, where the keys are the classes and the values are subs.
+
+sub except (&;$) {
+ my $code = shift;
+ my $clauses = shift || {};
+ my $catch = $clauses->{'catch'} ||= [];
+
+ my $sub = sub {
+ my $ref;
+ my(@array) = $code->($_[0]);
+ if(@array == 1 && ref($array[0])) {
+ $ref = $array[0];
+ $ref = [ %$ref ]
+ if(UNIVERSAL::isa($ref,'HASH'));
+ }
+ else {
+ $ref = \@array;
+ }
+ @$ref
+ };
+
+ unshift @{$catch}, undef, $sub;
+
+ $clauses;
+}
+
+sub otherwise (&;$) {
+ my $code = shift;
+ my $clauses = shift || {};
+
+ if(exists $clauses->{'otherwise'}) {
+ require Carp;
+ Carp::croak("Multiple otherwise clauses");
+ }
+
+ $clauses->{'otherwise'} = $code;
+
+ $clauses;
+}
+
+1;
+__END__
+
+=head1 NAME
+
+Error - Error/exception handling in an OO-ish way
+
+=head1 SYNOPSIS
+
+ use Error qw(:try);
+
+ throw Error::Simple( "A simple error");
+
+ sub xyz {
+ ...
+ record Error::Simple("A simple error")
+ and return;
+ }
+
+ unlink($file) or throw Error::Simple("$file: $!",$!);
+
+ try {
+ do_some_stuff();
+ die "error!" if $condition;
+ throw Error::Simple -text => "Oops!" if $other_condition;
+ }
+ catch Error::IO with {
+ my $E = shift;
+ print STDERR "File ", $E->{'-file'}, " had a problem\n";
+ }
+ except {
+ my $E = shift;
+ my $general_handler=sub {send_message $E->{-description}};
+ return {
+ UserException1 => $general_handler,
+ UserException2 => $general_handler
+ };
+ }
+ otherwise {
+ print STDERR "Well I don't know what to say\n";
+ }
+ finally {
+ close_the_garage_door_already(); # Should be reliable
+ }; # Don't forget the trailing ; or you might be surprised
+
+=head1 DESCRIPTION
+
+The C<Error> package provides two interfaces. Firstly C<Error> provides
+a procedural interface to exception handling. Secondly C<Error> is a
+base class for errors/exceptions that can either be thrown, for
+subsequent catch, or can simply be recorded.
+
+Errors in the class C<Error> should not be thrown directly, but the
+user should throw errors from a sub-class of C<Error>.
+
+=head1 PROCEDURAL INTERFACE
+
+C<Error> exports subroutines to perform exception handling. These will
+be exported if the C<:try> tag is used in the C<use> line.
+
+=over 4
+
+=item try BLOCK CLAUSES
+
+C<try> is the main subroutine called by the user. All other subroutines
+exported are clauses to the try subroutine.
+
+The BLOCK will be evaluated and, if no error is throw, try will return
+the result of the block.
+
+C<CLAUSES> are the subroutines below, which describe what to do in the
+event of an error being thrown within BLOCK.
+
+=item catch CLASS with BLOCK
+
+This clauses will cause all errors that satisfy C<$err-E<gt>isa(CLASS)>
+to be caught and handled by evaluating C<BLOCK>.
+
+C<BLOCK> will be passed two arguments. The first will be the error
+being thrown. The second is a reference to a scalar variable. If this
+variable is set by the catch block then, on return from the catch
+block, try will continue processing as if the catch block was never
+found.
+
+To propagate the error the catch block may call C<$err-E<gt>throw>
+
+If the scalar reference by the second argument is not set, and the
+error is not thrown. Then the current try block will return with the
+result from the catch block.
+
+=item except BLOCK
+
+When C<try> is looking for a handler, if an except clause is found
+C<BLOCK> is evaluated. The return value from this block should be a
+HASHREF or a list of key-value pairs, where the keys are class names
+and the values are CODE references for the handler of errors of that
+type.
+
+=item otherwise BLOCK
+
+Catch any error by executing the code in C<BLOCK>
+
+When evaluated C<BLOCK> will be passed one argument, which will be the
+error being processed.
+
+Only one otherwise block may be specified per try block
+
+=item finally BLOCK
+
+Execute the code in C<BLOCK> either after the code in the try block has
+successfully completed, or if the try block throws an error then
+C<BLOCK> will be executed after the handler has completed.
+
+If the handler throws an error then the error will be caught, the
+finally block will be executed and the error will be re-thrown.
+
+Only one finally block may be specified per try block
+
+=back
+
+=head1 CLASS INTERFACE
+
+=head2 CONSTRUCTORS
+
+The C<Error> object is implemented as a HASH. This HASH is initialized
+with the arguments that are passed to it's constructor. The elements
+that are used by, or are retrievable by the C<Error> class are listed
+below, other classes may add to these.
+
+ -file
+ -line
+ -text
+ -value
+ -object
+
+If C<-file> or C<-line> are not specified in the constructor arguments
+then these will be initialized with the file name and line number where
+the constructor was called from.
+
+If the error is associated with an object then the object should be
+passed as the C<-object> argument. This will allow the C<Error> package
+to associate the error with the object.
+
+The C<Error> package remembers the last error created, and also the
+last error associated with a package. This could either be the last
+error created by a sub in that package, or the last error which passed
+an object blessed into that package as the C<-object> argument.
+
+=over 4
+
+=item throw ( [ ARGS ] )
+
+Create a new C<Error> object and throw an error, which will be caught
+by a surrounding C<try> block, if there is one. Otherwise it will cause
+the program to exit.
+
+C<throw> may also be called on an existing error to re-throw it.
+
+=item with ( [ ARGS ] )
+
+Create a new C<Error> object and returns it. This is defined for
+syntactic sugar, eg
+
+ die with Some::Error ( ... );
+
+=item record ( [ ARGS ] )
+
+Create a new C<Error> object and returns it. This is defined for
+syntactic sugar, eg
+
+ record Some::Error ( ... )
+ and return;
+
+=back
+
+=head2 STATIC METHODS
+
+=over 4
+
+=item prior ( [ PACKAGE ] )
+
+Return the last error created, or the last error associated with
+C<PACKAGE>
+
+=item flush ( [ PACKAGE ] )
+
+Flush the last error created, or the last error associated with
+C<PACKAGE>.It is necessary to clear the error stack before exiting the
+package or uncaught errors generated using C<record> will be reported.
+
+ $Error->flush;
+
+=cut
+
+=back
+
+=head2 OBJECT METHODS
+
+=over 4
+
+=item stacktrace
+
+If the variable C<$Error::Debug> was non-zero when the error was
+created, then C<stacktrace> returns a string created by calling
+C<Carp::longmess>. If the variable was zero the C<stacktrace> returns
+the text of the error appended with the filename and line number of
+where the error was created, providing the text does not end with a
+newline.
+
+=item object
+
+The object this error was associated with
+
+=item file
+
+The file where the constructor of this error was called from
+
+=item line
+
+The line where the constructor of this error was called from
+
+=item text
+
+The text of the error
+
+=back
+
+=head2 OVERLOAD METHODS
+
+=over 4
+
+=item stringify
+
+A method that converts the object into a string. This method may simply
+return the same as the C<text> method, or it may append more
+information. For example the file name and line number.
+
+By default this method returns the C<-text> argument that was passed to
+the constructor, or the string C<"Died"> if none was given.
+
+=item value
+
+A method that will return a value that can be associated with the
+error. For example if an error was created due to the failure of a
+system call, then this may return the numeric value of C<$!> at the
+time.
+
+By default this method returns the C<-value> argument that was passed
+to the constructor.
+
+=back
+
+=head1 PRE-DEFINED ERROR CLASSES
+
+=over 4
+
+=item Error::Simple
+
+This class can be used to hold simple error strings and values. It's
+constructor takes two arguments. The first is a text value, the second
+is a numeric value. These values are what will be returned by the
+overload methods.
+
+If the text value ends with C<at file line 1> as $@ strings do, then
+this infomation will be used to set the C<-file> and C<-line> arguments
+of the error object.
+
+This class is used internally if an eval'd block die's with an error
+that is a plain string. (Unless C<$Error::ObjectifyCallback> is modified)
+
+=back
+
+=head1 $Error::ObjectifyCallback
+
+This variable holds a reference to a subroutine that converts errors that
+are plain strings to objects. It is used by Error.pm to convert textual
+errors to objects, and can be overrided by the user.
+
+It accepts a single argument which is a hash reference to named parameters.
+Currently the only named parameter passed is C<'text'> which is the text
+of the error, but others may be available in the future.
+
+For example the following code will cause Error.pm to throw objects of the
+class MyError::Bar by default:
+
+ sub throw_MyError_Bar
+ {
+ my $args = shift;
+ my $err = MyError::Bar->new();
+ $err->{'MyBarText'} = $args->{'text'};
+ return $err;
+ }
+
+ {
+ local $Error::ObjectifyCallback = \&throw_MyError_Bar;
+
+ # Error handling here.
+ }
+
+=head1 KNOWN BUGS
+
+None, but that does not mean there are not any.
+
+=head1 AUTHORS
+
+Graham Barr <gbarr@pobox.com>
+
+The code that inspired me to write this was originally written by
+Peter Seibel <peter@weblogic.com> and adapted by Jesse Glick
+<jglick@sig.bsh.com>.
+
+=head1 MAINTAINER
+
+Shlomi Fish <shlomif@iglu.org.il>
+
+=head1 PAST MAINTAINERS
+
+Arun Kumar U <u_arunkumar@yahoo.com>
+
+=cut
diff --git a/quote.c b/quote.c
index e220dcc280..a418a0f803 100644
--- a/quote.c
+++ b/quote.c
@@ -74,6 +74,67 @@ char *sq_quote(const char *src)
return buf;
}
+char *sq_quote_argv(const char** argv, int count)
+{
+ char *buf, *to;
+ int i;
+ size_t len = 0;
+
+ /* Count argv if needed. */
+ if (count < 0) {
+ for (count = 0; argv[count]; count++)
+ ; /* just counting */
+ }
+
+ /* Special case: no argv. */
+ if (!count)
+ return xcalloc(1,1);
+
+ /* Get destination buffer length. */
+ for (i = 0; i < count; i++)
+ len += sq_quote_buf(NULL, 0, argv[i]) + 1;
+
+ /* Alloc destination buffer. */
+ to = buf = xmalloc(len + 1);
+
+ /* Copy into destination buffer. */
+ for (i = 0; i < count; ++i) {
+ *to++ = ' ';
+ to += sq_quote_buf(to, len, argv[i]);
+ }
+
+ return buf;
+}
+
+/*
+ * Append a string to a string buffer, with or without shell quoting.
+ * Return true if the buffer overflowed.
+ */
+int add_to_string(char **ptrp, int *sizep, const char *str, int quote)
+{
+ char *p = *ptrp;
+ int size = *sizep;
+ int oc;
+ int err = 0;
+
+ if (quote)
+ oc = sq_quote_buf(p, size, str);
+ else {
+ oc = strlen(str);
+ memcpy(p, str, (size <= oc) ? size - 1 : oc);
+ }
+
+ if (size <= oc) {
+ err = 1;
+ oc = size - 1;
+ }
+
+ *ptrp += oc;
+ **ptrp = '\0';
+ *sizep -= oc;
+ return err;
+}
+
char *sq_dequote(char *arg)
{
char *dst = arg;
@@ -148,7 +209,7 @@ static int quote_c_style_counted(const char *name, int namelen,
if (!ch)
break;
if ((ch < ' ') || (ch == '"') || (ch == '\\') ||
- (ch == 0177)) {
+ (ch >= 0177)) {
needquote = 1;
switch (ch) {
case '\a': EMITQ(); ch = 'a'; break;
@@ -288,3 +349,41 @@ void write_name_quoted(const char *prefix, int prefix_len,
else
goto no_quote;
}
+
+/* quoting as a string literal for other languages */
+
+void perl_quote_print(FILE *stream, const char *src)
+{
+ const char sq = '\'';
+ const char bq = '\\';
+ char c;
+
+ fputc(sq, stream);
+ while ((c = *src++)) {
+ if (c == sq || c == bq)
+ fputc(bq, stream);
+ fputc(c, stream);
+ }
+ fputc(sq, stream);
+}
+
+void python_quote_print(FILE *stream, const char *src)
+{
+ const char sq = '\'';
+ const char bq = '\\';
+ const char nl = '\n';
+ char c;
+
+ fputc(sq, stream);
+ while ((c = *src++)) {
+ if (c == nl) {
+ fputc(bq, stream);
+ fputc('n', stream);
+ continue;
+ }
+ if (c == sq || c == bq)
+ fputc(bq, stream);
+ fputc(c, stream);
+ }
+ fputc(sq, stream);
+}
diff --git a/quote.h b/quote.h
index fc5481e78a..b55e699750 100644
--- a/quote.h
+++ b/quote.h
@@ -31,6 +31,13 @@
extern char *sq_quote(const char *src);
extern void sq_quote_print(FILE *stream, const char *src);
extern size_t sq_quote_buf(char *dst, size_t n, const char *src);
+extern char *sq_quote_argv(const char** argv, int count);
+
+/*
+ * Append a string to a string buffer, with or without shell quoting.
+ * Return true if the buffer overflowed.
+ */
+extern int add_to_string(char **ptrp, int *sizep, const char *str, int quote);
/* This unwraps what sq_quote() produces in place, but returns
* NULL if the input does not look like what sq_quote would have
@@ -45,4 +52,8 @@ extern char *unquote_c_style(const char *quoted, const char **endp);
extern void write_name_quoted(const char *prefix, int prefix_len,
const char *name, int quote, FILE *out);
+/* quoting as a string literal for other languages */
+extern void perl_quote_print(FILE *stream, const char *src);
+extern void python_quote_print(FILE *stream, const char *src);
+
#endif
diff --git a/read-cache.c b/read-cache.c
index c923a32707..eae4745d28 100644
--- a/read-cache.c
+++ b/read-cache.c
@@ -5,7 +5,6 @@
*/
#include "cache.h"
#include "cache-tree.h"
-#include <time.h>
/* Index extensions.
*
@@ -19,16 +18,16 @@
#define CACHE_EXT(s) ( (s[0]<<24)|(s[1]<<16)|(s[2]<<8)|(s[3]) )
#define CACHE_EXT_TREE 0x54524545 /* "TREE" */
-struct cache_entry **active_cache = NULL;
+struct cache_entry **active_cache;
static time_t index_file_timestamp;
-unsigned int active_nr = 0, active_alloc = 0, active_cache_changed = 0;
+unsigned int active_nr, active_alloc, active_cache_changed;
-struct cache_tree *active_cache_tree = NULL;
+struct cache_tree *active_cache_tree;
-int cache_errno = 0;
+int cache_errno;
-static void *cache_mmap = NULL;
-static size_t cache_mmap_size = 0;
+static void *cache_mmap;
+static size_t cache_mmap_size;
/*
* This only updates the "non-critical" parts of the directory
@@ -61,7 +60,7 @@ static int ce_compare_data(struct cache_entry *ce, struct stat *st)
if (fd >= 0) {
unsigned char sha1[20];
if (!index_fd(sha1, fd, st, 0, NULL))
- match = memcmp(sha1, ce->sha1, 20);
+ match = hashcmp(sha1, ce->sha1);
/* index_fd() closed the file descriptor already */
}
return match;
@@ -170,9 +169,11 @@ static int ce_match_stat_basic(struct cache_entry *ce, struct stat *st)
return changed;
}
-int ce_match_stat(struct cache_entry *ce, struct stat *st, int ignore_valid)
+int ce_match_stat(struct cache_entry *ce, struct stat *st, int options)
{
unsigned int changed;
+ int ignore_valid = options & 01;
+ int assume_racy_is_modified = options & 02;
/*
* If it's marked as always valid in the index, it's
@@ -201,8 +202,12 @@ int ce_match_stat(struct cache_entry *ce, struct stat *st, int ignore_valid)
*/
if (!changed &&
index_file_timestamp &&
- index_file_timestamp <= ntohl(ce->ce_mtime.sec))
- changed |= ce_modified_check_fs(ce, st);
+ index_file_timestamp <= ntohl(ce->ce_mtime.sec)) {
+ if (assume_racy_is_modified)
+ changed |= DATA_CHANGED;
+ else
+ changed |= ce_modified_check_fs(ce, st);
+ }
return changed;
}
@@ -342,11 +347,13 @@ int add_file_to_index(const char *path, int verbose)
ce->ce_mode = create_ce_mode(st.st_mode);
if (!trust_executable_bit) {
/* If there is an existing entry, pick the mode bits
- * from it.
+ * from it, otherwise assume unexecutable.
*/
int pos = cache_name_pos(path, namelen);
if (pos >= 0)
ce->ce_mode = active_cache[pos]->ce_mode;
+ else if (S_ISREG(st.st_mode))
+ ce->ce_mode = create_ce_mode(S_IFREG | 0666);
}
if (index_path(ce->sha1, path, &st, 1))
@@ -739,7 +746,7 @@ static int verify_hdr(struct cache_header *hdr, unsigned long size)
SHA1_Init(&c);
SHA1_Update(&c, hdr, size - 20);
SHA1_Final(sha1, &c);
- if (memcmp(sha1, (char *) hdr + size - 20, 20))
+ if (hashcmp(sha1, (unsigned char *)hdr + size - 20))
return error("bad index file sha1 signature");
return 0;
}
@@ -837,6 +844,23 @@ unmap:
die("index file corrupt");
}
+int discard_cache(void)
+{
+ int ret;
+
+ active_nr = active_cache_changed = 0;
+ index_file_timestamp = 0;
+ cache_tree_free(&active_cache_tree);
+ if (cache_mmap == NULL)
+ return 0;
+ ret = munmap(cache_mmap, cache_mmap_size);
+ cache_mmap = NULL;
+ cache_mmap_size = 0;
+
+ /* no need to throw away allocated active_cache */
+ return ret;
+}
+
#define WRITE_BUFFER_SIZE 8192
static unsigned char write_buffer[WRITE_BUFFER_SIZE];
static unsigned long write_buffer_len;
@@ -880,10 +904,8 @@ static int write_index_ext_header(SHA_CTX *context, int fd,
{
ext = htonl(ext);
sz = htonl(sz);
- if ((ce_write(context, fd, &ext, 4) < 0) ||
- (ce_write(context, fd, &sz, 4) < 0))
- return -1;
- return 0;
+ return ((ce_write(context, fd, &ext, 4) < 0) ||
+ (ce_write(context, fd, &sz, 4) < 0)) ? -1 : 0;
}
static int ce_flush(SHA_CTX *context, int fd)
@@ -905,9 +927,7 @@ static int ce_flush(SHA_CTX *context, int fd)
/* Append the SHA1 signature at the end */
SHA1_Final(write_buffer + left, context);
left += 20;
- if (write(fd, write_buffer, left) != left)
- return -1;
- return 0;
+ return (write(fd, write_buffer, left) != left) ? -1 : 0;
}
static void ce_smudge_racily_clean_entry(struct cache_entry *ce)
@@ -958,9 +978,7 @@ int write_cache(int newfd, struct cache_entry **cache, int entries)
{
SHA_CTX c;
struct cache_header hdr;
- int i, removed, recent;
- struct stat st;
- time_t now;
+ int i, removed;
for (i = removed = 0; i < entries; i++)
if (!cache[i]->ce_mode)
@@ -998,57 +1016,5 @@ int write_cache(int newfd, struct cache_entry **cache, int entries)
return -1;
}
}
-
- /*
- * To prevent later ce_match_stat() from always falling into
- * check_fs(), if we have too many entries that can trigger
- * racily clean check, we are better off delaying the return.
- * We arbitrarily say if more than 20 paths or 25% of total
- * paths are very new, we delay the return until the index
- * file gets a new timestamp.
- *
- * NOTE! NOTE! NOTE!
- *
- * This assumes that nobody is touching the working tree while
- * we are updating the index.
- */
-
- /* Make sure that the new index file has st_mtime
- * that is current enough -- ce_write() batches the data
- * so it might not have written anything yet.
- */
- ce_write_flush(&c, newfd);
-
- now = fstat(newfd, &st) ? 0 : st.st_mtime;
- if (now) {
- recent = 0;
- for (i = 0; i < entries; i++) {
- struct cache_entry *ce = cache[i];
- time_t entry_time = (time_t) ntohl(ce->ce_mtime.sec);
- if (!ce->ce_mode)
- continue;
- if (now && now <= entry_time)
- recent++;
- }
- if (20 < recent && entries <= recent * 4) {
-#if 0
- fprintf(stderr, "entries %d\n", entries);
- fprintf(stderr, "recent %d\n", recent);
- fprintf(stderr, "now %lu\n", now);
-#endif
- while (!fstat(newfd, &st) && st.st_mtime <= now) {
- struct timespec rq, rm;
- off_t where = lseek(newfd, 0, SEEK_CUR);
- rq.tv_sec = 0;
- rq.tv_nsec = 250000000;
- nanosleep(&rq, &rm);
- if ((where == (off_t) -1) ||
- (write(newfd, "", 1) != 1) ||
- (lseek(newfd, -1, SEEK_CUR) != where) ||
- ftruncate(newfd, where))
- break;
- }
- }
- }
return ce_flush(&c, newfd);
}
diff --git a/receive-pack.c b/receive-pack.c
index 93929b5371..d56898c9b2 100644
--- a/receive-pack.c
+++ b/receive-pack.c
@@ -1,19 +1,42 @@
#include "cache.h"
+#include "pack.h"
#include "refs.h"
#include "pkt-line.h"
#include "run-command.h"
+#include "exec_cmd.h"
+#include "commit.h"
+#include "object.h"
#include <sys/wait.h>
static const char receive_pack_usage[] = "git-receive-pack <git-dir>";
-static const char *unpacker[] = { "unpack-objects", NULL };
-
-static int report_status = 0;
+static int deny_non_fast_forwards = 0;
+static int unpack_limit = 5000;
+static int report_status;
static char capabilities[] = "report-status";
-static int capabilities_sent = 0;
+static int capabilities_sent;
+
+static int receive_pack_config(const char *var, const char *value)
+{
+ git_default_config(var, value);
+
+ if (strcmp(var, "receive.denynonfastforwards") == 0)
+ {
+ deny_non_fast_forwards = git_config_bool(var, value);
+ return 0;
+ }
+
+ if (strcmp(var, "receive.unpacklimit") == 0)
+ {
+ unpack_limit = git_config_int(var, value);
+ return 0;
+ }
-static int show_ref(const char *path, const unsigned char *sha1)
+ return 0;
+}
+
+static int show_ref(const char *path, const unsigned char *sha1, int flag, void *cb_data)
{
if (capabilities_sent)
packet_write(1, "%s %s\n", sha1_to_hex(sha1), path);
@@ -26,9 +49,9 @@ static int show_ref(const char *path, const unsigned char *sha1)
static void write_head_info(void)
{
- for_each_ref(show_ref);
+ for_each_ref(show_ref, NULL);
if (!capabilities_sent)
- show_ref("capabilities^{}", null_sha1);
+ show_ref("capabilities^{}", null_sha1, 0, NULL);
}
@@ -40,35 +63,7 @@ struct command {
char ref_name[FLEX_ARRAY]; /* more */
};
-static struct command *commands = NULL;
-
-static int is_all_zeroes(const char *hex)
-{
- int i;
- for (i = 0; i < 40; i++)
- if (*hex++ != '0')
- return 0;
- return 1;
-}
-
-static int verify_old_ref(const char *name, char *hex_contents)
-{
- int fd, ret;
- char buffer[60];
-
- if (is_all_zeroes(hex_contents))
- return 0;
- fd = open(name, O_RDONLY);
- if (fd < 0)
- return -1;
- ret = read(fd, buffer, 40);
- close(fd);
- if (ret != 40)
- return -1;
- if (memcmp(buffer, hex_contents, 40))
- return -1;
- return 0;
-}
+static struct command *commands;
static char update_hook[] = "hooks/update";
@@ -106,8 +101,8 @@ static int update(struct command *cmd)
const char *name = cmd->ref_name;
unsigned char *old_sha1 = cmd->old_sha1;
unsigned char *new_sha1 = cmd->new_sha1;
- char new_hex[60], *old_hex, *lock_name;
- int newfd, namelen, written;
+ char new_hex[41], old_hex[41];
+ struct ref_lock *lock;
cmd->error_string = NULL;
if (!strncmp(name, "refs/", 5) && check_ref_format(name + 5)) {
@@ -116,59 +111,42 @@ static int update(struct command *cmd)
name);
}
- namelen = strlen(name);
- lock_name = xmalloc(namelen + 10);
- memcpy(lock_name, name, namelen);
- memcpy(lock_name + namelen, ".lock", 6);
-
strcpy(new_hex, sha1_to_hex(new_sha1));
- old_hex = sha1_to_hex(old_sha1);
+ strcpy(old_hex, sha1_to_hex(old_sha1));
if (!has_sha1_file(new_sha1)) {
cmd->error_string = "bad pack";
return error("unpack should have generated %s, "
"but I can't find it!", new_hex);
}
- safe_create_leading_directories(lock_name);
-
- newfd = open(lock_name, O_CREAT | O_EXCL | O_WRONLY, 0666);
- if (newfd < 0) {
- cmd->error_string = "can't lock";
- return error("unable to create %s (%s)",
- lock_name, strerror(errno));
- }
-
- /* Write the ref with an ending '\n' */
- new_hex[40] = '\n';
- new_hex[41] = 0;
- written = write(newfd, new_hex, 41);
- /* Remove the '\n' again */
- new_hex[40] = 0;
-
- close(newfd);
- if (written != 41) {
- unlink(lock_name);
- cmd->error_string = "can't write";
- return error("unable to write %s", lock_name);
- }
- if (verify_old_ref(name, old_hex) < 0) {
- unlink(lock_name);
- cmd->error_string = "raced";
- return error("%s changed during push", name);
+ if (deny_non_fast_forwards && !is_null_sha1(old_sha1)) {
+ struct commit *old_commit, *new_commit;
+ struct commit_list *bases, *ent;
+
+ old_commit = (struct commit *)parse_object(old_sha1);
+ new_commit = (struct commit *)parse_object(new_sha1);
+ bases = get_merge_bases(old_commit, new_commit, 1);
+ for (ent = bases; ent; ent = ent->next)
+ if (!hashcmp(old_sha1, ent->item->object.sha1))
+ break;
+ free_commit_list(bases);
+ if (!ent)
+ return error("denying non-fast forward;"
+ " you should pull first");
}
if (run_update_hook(name, old_hex, new_hex)) {
- unlink(lock_name);
cmd->error_string = "hook declined";
return error("hook declined to update %s", name);
}
- else if (rename(lock_name, name) < 0) {
- unlink(lock_name);
- cmd->error_string = "can't rename";
- return error("unable to replace %s", name);
- }
- else {
- fprintf(stderr, "%s: %s -> %s\n", name, old_hex, new_hex);
- return 0;
+
+ lock = lock_any_ref_for_update(name, old_sha1);
+ if (!lock) {
+ cmd->error_string = "failed to lock";
+ return error("failed to lock %s", name);
}
+ write_ref_sha1(lock, new_sha1, "push");
+
+ fprintf(stderr, "%s: %s -> %s\n", name, old_hex, new_hex);
+ return 0;
}
static char update_post_hook[] = "hooks/post-update";
@@ -247,8 +225,8 @@ static void read_head_info(void)
report_status = 1;
}
cmd = xmalloc(sizeof(struct command) + len - 80);
- memcpy(cmd->old_sha1, old_sha1, 20);
- memcpy(cmd->new_sha1, new_sha1, 20);
+ hashcpy(cmd->old_sha1, old_sha1);
+ hashcpy(cmd->new_sha1, new_sha1);
memcpy(cmd->ref_name, line + 82, len - 81);
cmd->error_string = "n/a (unpacker error)";
cmd->next = NULL;
@@ -257,29 +235,127 @@ static void read_head_info(void)
}
}
-static const char *unpack(int *error_code)
+static const char *parse_pack_header(struct pack_header *hdr)
{
- int code = run_command_v_opt(1, unpacker, RUN_GIT_CMD);
+ char *c = (char*)hdr;
+ ssize_t remaining = sizeof(struct pack_header);
+ do {
+ ssize_t r = xread(0, c, remaining);
+ if (r <= 0)
+ return "eof before pack header was fully read";
+ remaining -= r;
+ c += r;
+ } while (remaining > 0);
+ if (hdr->hdr_signature != htonl(PACK_SIGNATURE))
+ return "protocol error (pack signature mismatch detected)";
+ if (!pack_version_ok(hdr->hdr_version))
+ return "protocol error (pack version unsupported)";
+ return NULL;
+}
- *error_code = 0;
- switch (code) {
- case 0:
- return NULL;
- case -ERR_RUN_COMMAND_FORK:
- return "unpack fork failed";
- case -ERR_RUN_COMMAND_EXEC:
- return "unpack execute failed";
- case -ERR_RUN_COMMAND_WAITPID:
- return "waitpid failed";
- case -ERR_RUN_COMMAND_WAITPID_WRONG_PID:
- return "waitpid is confused";
- case -ERR_RUN_COMMAND_WAITPID_SIGNAL:
- return "unpacker died of signal";
- case -ERR_RUN_COMMAND_WAITPID_NOEXIT:
- return "unpacker died strangely";
- default:
- *error_code = -code;
- return "unpacker exited with error code";
+static const char *pack_lockfile;
+
+static const char *unpack(void)
+{
+ struct pack_header hdr;
+ const char *hdr_err;
+ char hdr_arg[38];
+
+ hdr_err = parse_pack_header(&hdr);
+ if (hdr_err)
+ return hdr_err;
+ snprintf(hdr_arg, sizeof(hdr_arg), "--pack_header=%u,%u",
+ ntohl(hdr.hdr_version), ntohl(hdr.hdr_entries));
+
+ if (ntohl(hdr.hdr_entries) < unpack_limit) {
+ int code;
+ const char *unpacker[3];
+ unpacker[0] = "unpack-objects";
+ unpacker[1] = hdr_arg;
+ unpacker[2] = NULL;
+ code = run_command_v_opt(1, unpacker, RUN_GIT_CMD);
+ switch (code) {
+ case 0:
+ return NULL;
+ case -ERR_RUN_COMMAND_FORK:
+ return "unpack fork failed";
+ case -ERR_RUN_COMMAND_EXEC:
+ return "unpack execute failed";
+ case -ERR_RUN_COMMAND_WAITPID:
+ return "waitpid failed";
+ case -ERR_RUN_COMMAND_WAITPID_WRONG_PID:
+ return "waitpid is confused";
+ case -ERR_RUN_COMMAND_WAITPID_SIGNAL:
+ return "unpacker died of signal";
+ case -ERR_RUN_COMMAND_WAITPID_NOEXIT:
+ return "unpacker died strangely";
+ default:
+ return "unpacker exited with error code";
+ }
+ } else {
+ const char *keeper[6];
+ int fd[2], s, len, status;
+ pid_t pid;
+ char keep_arg[256];
+ char packname[46];
+
+ s = sprintf(keep_arg, "--keep=receive-pack %i on ", getpid());
+ if (gethostname(keep_arg + s, sizeof(keep_arg) - s))
+ strcpy(keep_arg + s, "localhost");
+
+ keeper[0] = "index-pack";
+ keeper[1] = "--stdin";
+ keeper[2] = "--fix-thin";
+ keeper[3] = hdr_arg;
+ keeper[4] = keep_arg;
+ keeper[5] = NULL;
+
+ if (pipe(fd) < 0)
+ return "index-pack pipe failed";
+ pid = fork();
+ if (pid < 0)
+ return "index-pack fork failed";
+ if (!pid) {
+ dup2(fd[1], 1);
+ close(fd[1]);
+ close(fd[0]);
+ execv_git_cmd(keeper);
+ die("execv of index-pack failed");
+ }
+ close(fd[1]);
+
+ /*
+ * The first thing we expects from index-pack's output
+ * is "pack\t%40s\n" or "keep\t%40s\n" (46 bytes) where
+ * %40s is the newly created pack SHA1 name. In the "keep"
+ * case, we need it to remove the corresponding .keep file
+ * later on. If we don't get that then tough luck with it.
+ */
+ for (len = 0;
+ len < 46 && (s = xread(fd[0], packname+len, 46-len)) > 0;
+ len += s);
+ close(fd[0]);
+ if (len == 46 && packname[45] == '\n' &&
+ memcmp(packname, "keep\t", 5) == 0) {
+ char path[PATH_MAX];
+ packname[45] = 0;
+ snprintf(path, sizeof(path), "%s/pack/pack-%s.keep",
+ get_object_directory(), packname + 5);
+ pack_lockfile = xstrdup(path);
+ }
+
+ /* Then wrap our index-pack process. */
+ while (waitpid(pid, &status, 0) < 0)
+ if (errno != EINTR)
+ return "waitpid failed";
+ if (WIFEXITED(status)) {
+ int code = WEXITSTATUS(status);
+ if (code)
+ return "index-pack exited with error code";
+ reprepare_packed_git();
+ return NULL;
+ }
+ return "index-pack abnormal exit";
}
}
@@ -319,9 +395,12 @@ int main(int argc, char **argv)
if (!dir)
usage(receive_pack_usage);
- if(!enter_repo(dir, 0))
+ if (!enter_repo(dir, 0))
die("'%s': unable to chdir or not a git archive", dir);
+ setup_ident();
+ git_config(receive_pack_config);
+
write_head_info();
/* EOF */
@@ -329,10 +408,11 @@ int main(int argc, char **argv)
read_head_info();
if (commands) {
- int code;
- const char *unpack_status = unpack(&code);
+ const char *unpack_status = unpack();
if (!unpack_status)
execute_commands();
+ if (pack_lockfile)
+ unlink(pack_lockfile);
if (report_status)
report(unpack_status);
}
diff --git a/refs.c b/refs.c
index 86ef916614..0e156c5dee 100644
--- a/refs.c
+++ b/refs.c
@@ -3,15 +3,193 @@
#include <errno.h>
+struct ref_list {
+ struct ref_list *next;
+ unsigned char flag; /* ISSYMREF? ISPACKED? */
+ unsigned char sha1[20];
+ char name[FLEX_ARRAY];
+};
+
+static const char *parse_ref_line(char *line, unsigned char *sha1)
+{
+ /*
+ * 42: the answer to everything.
+ *
+ * In this case, it happens to be the answer to
+ * 40 (length of sha1 hex representation)
+ * +1 (space in between hex and name)
+ * +1 (newline at the end of the line)
+ */
+ int len = strlen(line) - 42;
+
+ if (len <= 0)
+ return NULL;
+ if (get_sha1_hex(line, sha1) < 0)
+ return NULL;
+ if (!isspace(line[40]))
+ return NULL;
+ line += 41;
+ if (isspace(*line))
+ return NULL;
+ if (line[len] != '\n')
+ return NULL;
+ line[len] = 0;
+ return line;
+}
+
+static struct ref_list *add_ref(const char *name, const unsigned char *sha1,
+ int flag, struct ref_list *list)
+{
+ int len;
+ struct ref_list **p = &list, *entry;
+
+ /* Find the place to insert the ref into.. */
+ while ((entry = *p) != NULL) {
+ int cmp = strcmp(entry->name, name);
+ if (cmp > 0)
+ break;
+
+ /* Same as existing entry? */
+ if (!cmp)
+ return list;
+ p = &entry->next;
+ }
+
+ /* Allocate it and add it in.. */
+ len = strlen(name) + 1;
+ entry = xmalloc(sizeof(struct ref_list) + len);
+ hashcpy(entry->sha1, sha1);
+ memcpy(entry->name, name, len);
+ entry->flag = flag;
+ entry->next = *p;
+ *p = entry;
+ return list;
+}
+
+/*
+ * Future: need to be in "struct repository"
+ * when doing a full libification.
+ */
+struct cached_refs {
+ char did_loose;
+ char did_packed;
+ struct ref_list *loose;
+ struct ref_list *packed;
+} cached_refs;
+
+static void free_ref_list(struct ref_list *list)
+{
+ struct ref_list *next;
+ for ( ; list; list = next) {
+ next = list->next;
+ free(list);
+ }
+}
+
+static void invalidate_cached_refs(void)
+{
+ struct cached_refs *ca = &cached_refs;
+
+ if (ca->did_loose && ca->loose)
+ free_ref_list(ca->loose);
+ if (ca->did_packed && ca->packed)
+ free_ref_list(ca->packed);
+ ca->loose = ca->packed = NULL;
+ ca->did_loose = ca->did_packed = 0;
+}
+
+static struct ref_list *get_packed_refs(void)
+{
+ if (!cached_refs.did_packed) {
+ struct ref_list *refs = NULL;
+ FILE *f = fopen(git_path("packed-refs"), "r");
+ if (f) {
+ struct ref_list *list = NULL;
+ char refline[PATH_MAX];
+ while (fgets(refline, sizeof(refline), f)) {
+ unsigned char sha1[20];
+ const char *name = parse_ref_line(refline, sha1);
+ if (!name)
+ continue;
+ list = add_ref(name, sha1, REF_ISPACKED, list);
+ }
+ fclose(f);
+ refs = list;
+ }
+ cached_refs.packed = refs;
+ cached_refs.did_packed = 1;
+ }
+ return cached_refs.packed;
+}
+
+static struct ref_list *get_ref_dir(const char *base, struct ref_list *list)
+{
+ DIR *dir = opendir(git_path("%s", base));
+
+ if (dir) {
+ struct dirent *de;
+ int baselen = strlen(base);
+ char *ref = xmalloc(baselen + 257);
+
+ memcpy(ref, base, baselen);
+ if (baselen && base[baselen-1] != '/')
+ ref[baselen++] = '/';
+
+ while ((de = readdir(dir)) != NULL) {
+ unsigned char sha1[20];
+ struct stat st;
+ int flag;
+ int namelen;
+
+ if (de->d_name[0] == '.')
+ continue;
+ namelen = strlen(de->d_name);
+ if (namelen > 255)
+ continue;
+ if (has_extension(de->d_name, ".lock"))
+ continue;
+ memcpy(ref + baselen, de->d_name, namelen+1);
+ if (stat(git_path("%s", ref), &st) < 0)
+ continue;
+ if (S_ISDIR(st.st_mode)) {
+ list = get_ref_dir(ref, list);
+ continue;
+ }
+ if (!resolve_ref(ref, sha1, 1, &flag)) {
+ error("%s points nowhere!", ref);
+ continue;
+ }
+ list = add_ref(ref, sha1, flag, list);
+ }
+ free(ref);
+ closedir(dir);
+ }
+ return list;
+}
+
+static struct ref_list *get_loose_refs(void)
+{
+ if (!cached_refs.did_loose) {
+ cached_refs.loose = get_ref_dir("refs", NULL);
+ cached_refs.did_loose = 1;
+ }
+ return cached_refs.loose;
+}
+
/* We allow "recursive" symbolic refs. Only within reason, though */
#define MAXDEPTH 5
-const char *resolve_ref(const char *path, unsigned char *sha1, int reading)
+const char *resolve_ref(const char *ref, unsigned char *sha1, int reading, int *flag)
{
int depth = MAXDEPTH, len;
char buffer[256];
+ static char ref_buffer[256];
+
+ if (flag)
+ *flag = 0;
for (;;) {
+ const char *path = git_path("%s", ref);
struct stat st;
char *buf;
int fd;
@@ -27,21 +205,41 @@ const char *resolve_ref(const char *path, unsigned char *sha1, int reading)
* reading.
*/
if (lstat(path, &st) < 0) {
+ struct ref_list *list = get_packed_refs();
+ while (list) {
+ if (!strcmp(ref, list->name)) {
+ hashcpy(sha1, list->sha1);
+ if (flag)
+ *flag |= REF_ISPACKED;
+ return ref;
+ }
+ list = list->next;
+ }
if (reading || errno != ENOENT)
return NULL;
- memset(sha1, 0, 20);
- return path;
+ hashclr(sha1);
+ return ref;
}
/* Follow "normalized" - ie "refs/.." symlinks by hand */
if (S_ISLNK(st.st_mode)) {
len = readlink(path, buffer, sizeof(buffer)-1);
if (len >= 5 && !memcmp("refs/", buffer, 5)) {
- path = git_path("%.*s", len, buffer);
+ buffer[len] = 0;
+ strcpy(ref_buffer, buffer);
+ ref = ref_buffer;
+ if (flag)
+ *flag |= REF_ISSYMREF;
continue;
}
}
+ /* Is it a directory? */
+ if (S_ISDIR(st.st_mode)) {
+ errno = EISDIR;
+ return NULL;
+ }
+
/*
* Anything else, just open it and try to use it as
* a ref
@@ -62,19 +260,24 @@ const char *resolve_ref(const char *path, unsigned char *sha1, int reading)
while (len && isspace(*buf))
buf++, len--;
while (len && isspace(buf[len-1]))
- buf[--len] = 0;
- path = git_path("%.*s", len, buf);
+ len--;
+ buf[len] = 0;
+ memcpy(ref_buffer, buf, len + 1);
+ ref = ref_buffer;
+ if (flag)
+ *flag |= REF_ISSYMREF;
}
if (len < 40 || get_sha1_hex(buffer, sha1))
return NULL;
- return path;
+ return ref;
}
-int create_symref(const char *git_HEAD, const char *refs_heads_master)
+int create_symref(const char *ref_target, const char *refs_heads_master)
{
const char *lockpath;
char ref[1000];
int fd, len, written;
+ const char *git_HEAD = git_path("%s", ref_target);
#ifndef NO_SYMLINK_HEAD
if (prefer_symlink_refs) {
@@ -112,104 +315,101 @@ int create_symref(const char *git_HEAD, const char *refs_heads_master)
return 0;
}
-int read_ref(const char *filename, unsigned char *sha1)
+int read_ref(const char *ref, unsigned char *sha1)
{
- if (resolve_ref(filename, sha1, 1))
+ if (resolve_ref(ref, sha1, 1, NULL))
return 0;
return -1;
}
-static int do_for_each_ref(const char *base, int (*fn)(const char *path, const unsigned char *sha1), int trim)
+static int do_one_ref(const char *base, each_ref_fn fn, int trim,
+ void *cb_data, struct ref_list *entry)
{
- int retval = 0;
- DIR *dir = opendir(git_path("%s", base));
-
- if (dir) {
- struct dirent *de;
- int baselen = strlen(base);
- char *path = xmalloc(baselen + 257);
+ if (strncmp(base, entry->name, trim))
+ return 0;
+ if (is_null_sha1(entry->sha1))
+ return 0;
+ if (!has_sha1_file(entry->sha1)) {
+ error("%s does not point to a valid object!", entry->name);
+ return 0;
+ }
+ return fn(entry->name + trim, entry->sha1, entry->flag, cb_data);
+}
- if (!strncmp(base, "./", 2)) {
- base += 2;
- baselen -= 2;
+static int do_for_each_ref(const char *base, each_ref_fn fn, int trim,
+ void *cb_data)
+{
+ int retval;
+ struct ref_list *packed = get_packed_refs();
+ struct ref_list *loose = get_loose_refs();
+
+ while (packed && loose) {
+ struct ref_list *entry;
+ int cmp = strcmp(packed->name, loose->name);
+ if (!cmp) {
+ packed = packed->next;
+ continue;
}
- memcpy(path, base, baselen);
- if (baselen && base[baselen-1] != '/')
- path[baselen++] = '/';
-
- while ((de = readdir(dir)) != NULL) {
- unsigned char sha1[20];
- struct stat st;
- int namelen;
-
- if (de->d_name[0] == '.')
- continue;
- namelen = strlen(de->d_name);
- if (namelen > 255)
- continue;
- if (has_extension(de->d_name, ".lock"))
- continue;
- memcpy(path + baselen, de->d_name, namelen+1);
- if (stat(git_path("%s", path), &st) < 0)
- continue;
- if (S_ISDIR(st.st_mode)) {
- retval = do_for_each_ref(path, fn, trim);
- if (retval)
- break;
- continue;
- }
- if (read_ref(git_path("%s", path), sha1) < 0) {
- error("%s points nowhere!", path);
- continue;
- }
- if (!has_sha1_file(sha1)) {
- error("%s does not point to a valid "
- "commit object!", path);
- continue;
- }
- retval = fn(path + trim, sha1);
- if (retval)
- break;
+ if (cmp > 0) {
+ entry = loose;
+ loose = loose->next;
+ } else {
+ entry = packed;
+ packed = packed->next;
}
- free(path);
- closedir(dir);
+ retval = do_one_ref(base, fn, trim, cb_data, entry);
+ if (retval)
+ return retval;
+ }
+
+ for (packed = packed ? packed : loose; packed; packed = packed->next) {
+ retval = do_one_ref(base, fn, trim, cb_data, packed);
+ if (retval)
+ return retval;
}
- return retval;
+ return 0;
}
-int head_ref(int (*fn)(const char *path, const unsigned char *sha1))
+int head_ref(each_ref_fn fn, void *cb_data)
{
unsigned char sha1[20];
- if (!read_ref(git_path("HEAD"), sha1))
- return fn("HEAD", sha1);
+ int flag;
+
+ if (resolve_ref("HEAD", sha1, 1, &flag))
+ return fn("HEAD", sha1, flag, cb_data);
return 0;
}
-int for_each_ref(int (*fn)(const char *path, const unsigned char *sha1))
+int for_each_ref(each_ref_fn fn, void *cb_data)
{
- return do_for_each_ref("refs", fn, 0);
+ return do_for_each_ref("refs/", fn, 0, cb_data);
}
-int for_each_tag_ref(int (*fn)(const char *path, const unsigned char *sha1))
+int for_each_tag_ref(each_ref_fn fn, void *cb_data)
{
- return do_for_each_ref("refs/tags", fn, 10);
+ return do_for_each_ref("refs/tags/", fn, 10, cb_data);
}
-int for_each_branch_ref(int (*fn)(const char *path, const unsigned char *sha1))
+int for_each_branch_ref(each_ref_fn fn, void *cb_data)
{
- return do_for_each_ref("refs/heads", fn, 11);
+ return do_for_each_ref("refs/heads/", fn, 11, cb_data);
}
-int for_each_remote_ref(int (*fn)(const char *path, const unsigned char *sha1))
+int for_each_remote_ref(each_ref_fn fn, void *cb_data)
{
- return do_for_each_ref("refs/remotes", fn, 13);
+ return do_for_each_ref("refs/remotes/", fn, 13, cb_data);
}
+/* NEEDSWORK: This is only used by ssh-upload and it should go; the
+ * caller should do resolve_ref or read_ref like everybody else. Or
+ * maybe everybody else should use get_ref_sha1() instead of doing
+ * read_ref().
+ */
int get_ref_sha1(const char *ref, unsigned char *sha1)
{
if (check_ref_format(ref))
return -1;
- return read_ref(git_path("refs/%s", ref), sha1);
+ return read_ref(mkpath("refs/%s", ref), sha1);
}
/*
@@ -267,22 +467,13 @@ int check_ref_format(const char *ref)
static struct ref_lock *verify_lock(struct ref_lock *lock,
const unsigned char *old_sha1, int mustexist)
{
- char buf[40];
- int nr, fd = open(lock->ref_file, O_RDONLY);
- if (fd < 0 && (mustexist || errno != ENOENT)) {
- error("Can't verify ref %s", lock->ref_file);
- unlock_ref(lock);
- return NULL;
- }
- nr = read(fd, buf, 40);
- close(fd);
- if (nr != 40 || get_sha1_hex(buf, lock->old_sha1) < 0) {
- error("Can't verify ref %s", lock->ref_file);
+ if (!resolve_ref(lock->ref_name, lock->old_sha1, mustexist, NULL)) {
+ error("Can't verify ref %s", lock->ref_name);
unlock_ref(lock);
return NULL;
}
- if (memcmp(lock->old_sha1, old_sha1, 20)) {
- error("Ref %s is at %s but expected %s", lock->ref_file,
+ if (hashcmp(lock->old_sha1, old_sha1)) {
+ error("Ref %s is at %s but expected %s", lock->ref_name,
sha1_to_hex(lock->old_sha1), sha1_to_hex(old_sha1));
unlock_ref(lock);
return NULL;
@@ -290,54 +481,223 @@ static struct ref_lock *verify_lock(struct ref_lock *lock,
return lock;
}
-static struct ref_lock *lock_ref_sha1_basic(const char *path,
- int plen,
- const unsigned char *old_sha1, int mustexist)
+static int remove_empty_dir_recursive(char *path, int len)
+{
+ DIR *dir = opendir(path);
+ struct dirent *e;
+ int ret = 0;
+
+ if (!dir)
+ return -1;
+ if (path[len-1] != '/')
+ path[len++] = '/';
+ while ((e = readdir(dir)) != NULL) {
+ struct stat st;
+ int namlen;
+ if ((e->d_name[0] == '.') &&
+ ((e->d_name[1] == 0) ||
+ ((e->d_name[1] == '.') && e->d_name[2] == 0)))
+ continue; /* "." and ".." */
+
+ namlen = strlen(e->d_name);
+ if ((len + namlen < PATH_MAX) &&
+ strcpy(path + len, e->d_name) &&
+ !lstat(path, &st) &&
+ S_ISDIR(st.st_mode) &&
+ !remove_empty_dir_recursive(path, len + namlen))
+ continue; /* happy */
+
+ /* path too long, stat fails, or non-directory still exists */
+ ret = -1;
+ break;
+ }
+ closedir(dir);
+ if (!ret) {
+ path[len] = 0;
+ ret = rmdir(path);
+ }
+ return ret;
+}
+
+static int remove_empty_directories(char *file)
{
- const char *orig_path = path;
+ /* we want to create a file but there is a directory there;
+ * if that is an empty directory (or a directory that contains
+ * only empty directories), remove them.
+ */
+ char path[PATH_MAX];
+ int len = strlen(file);
+
+ if (len >= PATH_MAX) /* path too long ;-) */
+ return -1;
+ strcpy(path, file);
+ return remove_empty_dir_recursive(path, len);
+}
+
+static struct ref_lock *lock_ref_sha1_basic(const char *ref, const unsigned char *old_sha1, int *flag)
+{
+ char *ref_file;
+ const char *orig_ref = ref;
struct ref_lock *lock;
struct stat st;
+ int last_errno = 0;
+ int mustexist = (old_sha1 && !is_null_sha1(old_sha1));
lock = xcalloc(1, sizeof(struct ref_lock));
lock->lock_fd = -1;
- plen = strlen(path) - plen;
- path = resolve_ref(path, lock->old_sha1, mustexist);
- if (!path) {
- int last_errno = errno;
+ ref = resolve_ref(ref, lock->old_sha1, mustexist, flag);
+ if (!ref && errno == EISDIR) {
+ /* we are trying to lock foo but we used to
+ * have foo/bar which now does not exist;
+ * it is normal for the empty directory 'foo'
+ * to remain.
+ */
+ ref_file = git_path("%s", orig_ref);
+ if (remove_empty_directories(ref_file)) {
+ last_errno = errno;
+ error("there are still refs under '%s'", orig_ref);
+ goto error_return;
+ }
+ ref = resolve_ref(orig_ref, lock->old_sha1, mustexist, flag);
+ }
+ if (!ref) {
+ last_errno = errno;
error("unable to resolve reference %s: %s",
- orig_path, strerror(errno));
- unlock_ref(lock);
- errno = last_errno;
- return NULL;
+ orig_ref, strerror(errno));
+ goto error_return;
}
+ if (is_null_sha1(lock->old_sha1)) {
+ /* The ref did not exist and we are creating it.
+ * Make sure there is no existing ref that is packed
+ * whose name begins with our refname, nor a ref whose
+ * name is a proper prefix of our refname.
+ */
+ int namlen = strlen(ref); /* e.g. 'foo/bar' */
+ struct ref_list *list = get_packed_refs();
+ while (list) {
+ /* list->name could be 'foo' or 'foo/bar/baz' */
+ int len = strlen(list->name);
+ int cmplen = (namlen < len) ? namlen : len;
+ const char *lead = (namlen < len) ? list->name : ref;
+
+ if (!strncmp(ref, list->name, cmplen) &&
+ lead[cmplen] == '/') {
+ error("'%s' exists; cannot create '%s'",
+ list->name, ref);
+ goto error_return;
+ }
+ list = list->next;
+ }
+ }
+
lock->lk = xcalloc(1, sizeof(struct lock_file));
- lock->ref_file = strdup(path);
- lock->log_file = strdup(git_path("logs/%s", lock->ref_file + plen));
- lock->force_write = lstat(lock->ref_file, &st) && errno == ENOENT;
+ lock->ref_name = xstrdup(ref);
+ lock->log_file = xstrdup(git_path("logs/%s", ref));
+ ref_file = git_path("%s", ref);
+ lock->force_write = lstat(ref_file, &st) && errno == ENOENT;
- if (safe_create_leading_directories(lock->ref_file))
- die("unable to create directory for %s", lock->ref_file);
- lock->lock_fd = hold_lock_file_for_update(lock->lk, lock->ref_file, 1);
+ if (safe_create_leading_directories(ref_file)) {
+ last_errno = errno;
+ error("unable to create directory for %s", ref_file);
+ goto error_return;
+ }
+ lock->lock_fd = hold_lock_file_for_update(lock->lk, ref_file, 1);
return old_sha1 ? verify_lock(lock, old_sha1, mustexist) : lock;
+
+ error_return:
+ unlock_ref(lock);
+ errno = last_errno;
+ return NULL;
}
-struct ref_lock *lock_ref_sha1(const char *ref,
- const unsigned char *old_sha1, int mustexist)
+struct ref_lock *lock_ref_sha1(const char *ref, const unsigned char *old_sha1)
{
+ char refpath[PATH_MAX];
if (check_ref_format(ref))
return NULL;
- return lock_ref_sha1_basic(git_path("refs/%s", ref),
- 5 + strlen(ref), old_sha1, mustexist);
+ strcpy(refpath, mkpath("refs/%s", ref));
+ return lock_ref_sha1_basic(refpath, old_sha1, NULL);
}
-struct ref_lock *lock_any_ref_for_update(const char *ref,
- const unsigned char *old_sha1, int mustexist)
+struct ref_lock *lock_any_ref_for_update(const char *ref, const unsigned char *old_sha1)
+{
+ return lock_ref_sha1_basic(ref, old_sha1, NULL);
+}
+
+static struct lock_file packlock;
+
+static int repack_without_ref(const char *refname)
{
- return lock_ref_sha1_basic(git_path("%s", ref),
- strlen(ref), old_sha1, mustexist);
+ struct ref_list *list, *packed_ref_list;
+ int fd;
+ int found = 0;
+
+ packed_ref_list = get_packed_refs();
+ for (list = packed_ref_list; list; list = list->next) {
+ if (!strcmp(refname, list->name)) {
+ found = 1;
+ break;
+ }
+ }
+ if (!found)
+ return 0;
+ memset(&packlock, 0, sizeof(packlock));
+ fd = hold_lock_file_for_update(&packlock, git_path("packed-refs"), 0);
+ if (fd < 0)
+ return error("cannot delete '%s' from packed refs", refname);
+
+ for (list = packed_ref_list; list; list = list->next) {
+ char line[PATH_MAX + 100];
+ int len;
+
+ if (!strcmp(refname, list->name))
+ continue;
+ len = snprintf(line, sizeof(line), "%s %s\n",
+ sha1_to_hex(list->sha1), list->name);
+ /* this should not happen but just being defensive */
+ if (len > sizeof(line))
+ die("too long a refname '%s'", list->name);
+ write_or_die(fd, line, len);
+ }
+ return commit_lock_file(&packlock);
+}
+
+int delete_ref(const char *refname, unsigned char *sha1)
+{
+ struct ref_lock *lock;
+ int err, i, ret = 0, flag = 0;
+
+ lock = lock_ref_sha1_basic(refname, sha1, &flag);
+ if (!lock)
+ return 1;
+ if (!(flag & REF_ISPACKED)) {
+ /* loose */
+ i = strlen(lock->lk->filename) - 5; /* .lock */
+ lock->lk->filename[i] = 0;
+ err = unlink(lock->lk->filename);
+ if (err) {
+ ret = 1;
+ error("unlink(%s) failed: %s",
+ lock->lk->filename, strerror(errno));
+ }
+ lock->lk->filename[i] = '.';
+ }
+ /* removing the loose one could have resurrected an earlier
+ * packed one. Also, if it was not loose we need to repack
+ * without it.
+ */
+ ret |= repack_without_ref(refname);
+
+ err = unlink(lock->log_file);
+ if (err && errno != ENOENT)
+ fprintf(stderr, "warning: unlink(%s) failed: %s",
+ lock->log_file, strerror(errno));
+ invalidate_cached_refs();
+ unlock_ref(lock);
+ return ret;
}
void unlock_ref(struct ref_lock *lock)
@@ -348,10 +708,8 @@ void unlock_ref(struct ref_lock *lock)
if (lock->lk)
rollback_lock_file(lock->lk);
}
- if (lock->ref_file)
- free(lock->ref_file);
- if (lock->log_file)
- free(lock->log_file);
+ free(lock->ref_name);
+ free(lock->log_file);
free(lock);
}
@@ -363,7 +721,8 @@ static int log_ref_write(struct ref_lock *lock,
char *logrec;
const char *committer;
- if (log_all_ref_updates) {
+ if (log_all_ref_updates &&
+ !strncmp(lock->ref_name, "refs/heads/", 11)) {
if (safe_create_leading_directories(lock->log_file) < 0)
return error("unable to create directory for %s",
lock->log_file);
@@ -372,10 +731,20 @@ static int log_ref_write(struct ref_lock *lock,
logfd = open(lock->log_file, oflags, 0666);
if (logfd < 0) {
- if (!log_all_ref_updates && errno == ENOENT)
+ if (!(oflags & O_CREAT) && errno == ENOENT)
return 0;
- return error("Unable to append to %s: %s",
- lock->log_file, strerror(errno));
+
+ if ((oflags & O_CREAT) && errno == EISDIR) {
+ if (remove_empty_directories(lock->log_file)) {
+ return error("There are still logs under '%s'",
+ lock->log_file);
+ }
+ logfd = open(lock->log_file, oflags, 0666);
+ }
+
+ if (logfd < 0)
+ return error("Unable to append to %s: %s",
+ lock->log_file, strerror(errno));
}
committer = git_committer_info(1);
@@ -411,7 +780,7 @@ int write_ref_sha1(struct ref_lock *lock,
if (!lock)
return -1;
- if (!lock->force_write && !memcmp(lock->old_sha1, sha1, 20)) {
+ if (!lock->force_write && !hashcmp(lock->old_sha1, sha1)) {
unlock_ref(lock);
return 0;
}
@@ -422,12 +791,13 @@ int write_ref_sha1(struct ref_lock *lock,
unlock_ref(lock);
return -1;
}
+ invalidate_cached_refs();
if (log_ref_write(lock, sha1, logmsg) < 0) {
unlock_ref(lock);
return -1;
}
if (commit_lock_file(lock->lk)) {
- error("Couldn't set %s", lock->ref_file);
+ error("Couldn't set %s", lock->ref_name);
unlock_ref(lock);
return -1;
}
@@ -436,7 +806,7 @@ int write_ref_sha1(struct ref_lock *lock,
return 0;
}
-int read_ref_at(const char *ref, unsigned long at_time, unsigned char *sha1)
+int read_ref_at(const char *ref, unsigned long at_time, int cnt, unsigned char *sha1)
{
const char *logfile, *logdata, *logend, *rec, *lastgt, *lastrec;
char *tz_c;
@@ -469,13 +839,13 @@ int read_ref_at(const char *ref, unsigned long at_time, unsigned char *sha1)
if (!lastgt)
die("Log %s is corrupt.", logfile);
date = strtoul(lastgt + 1, &tz_c, 10);
- if (date <= at_time) {
+ if (date <= at_time || cnt == 0) {
if (lastrec) {
if (get_sha1_hex(lastrec, logged_sha1))
die("Log %s is corrupt.", logfile);
if (get_sha1_hex(rec + 41, sha1))
die("Log %s is corrupt.", logfile);
- if (memcmp(logged_sha1, sha1, 20)) {
+ if (hashcmp(logged_sha1, sha1)) {
tz = strtoul(tz_c, NULL, 10);
fprintf(stderr,
"warning: Log %s has gap after %s.\n",
@@ -489,7 +859,7 @@ int read_ref_at(const char *ref, unsigned long at_time, unsigned char *sha1)
else {
if (get_sha1_hex(rec + 41, logged_sha1))
die("Log %s is corrupt.", logfile);
- if (memcmp(logged_sha1, sha1, 20)) {
+ if (hashcmp(logged_sha1, sha1)) {
tz = strtoul(tz_c, NULL, 10);
fprintf(stderr,
"warning: Log %s unexpectedly ended on %s.\n",
@@ -500,6 +870,8 @@ int read_ref_at(const char *ref, unsigned long at_time, unsigned char *sha1)
return 0;
}
lastrec = rec;
+ if (cnt > 0)
+ cnt--;
}
rec = logdata;
diff --git a/refs.h b/refs.h
index 553155c04a..a57d43726a 100644
--- a/refs.h
+++ b/refs.h
@@ -2,7 +2,7 @@
#define REFS_H
struct ref_lock {
- char *ref_file;
+ char *ref_name;
char *log_file;
struct lock_file *lk;
unsigned char old_sha1[20];
@@ -14,20 +14,23 @@ struct ref_lock {
* Calls the specified function for each ref file until it returns nonzero,
* and returns the value
*/
-extern int head_ref(int (*fn)(const char *path, const unsigned char *sha1));
-extern int for_each_ref(int (*fn)(const char *path, const unsigned char *sha1));
-extern int for_each_tag_ref(int (*fn)(const char *path, const unsigned char *sha1));
-extern int for_each_branch_ref(int (*fn)(const char *path, const unsigned char *sha1));
-extern int for_each_remote_ref(int (*fn)(const char *path, const unsigned char *sha1));
+#define REF_ISSYMREF 01
+#define REF_ISPACKED 02
+typedef int each_ref_fn(const char *refname, const unsigned char *sha1, int flags, void *cb_data);
+extern int head_ref(each_ref_fn, void *);
+extern int for_each_ref(each_ref_fn, void *);
+extern int for_each_tag_ref(each_ref_fn, void *);
+extern int for_each_branch_ref(each_ref_fn, void *);
+extern int for_each_remote_ref(each_ref_fn, void *);
/** Reads the refs file specified into sha1 **/
extern int get_ref_sha1(const char *ref, unsigned char *sha1);
/** Locks a "refs/" ref returning the lock on success and NULL on failure. **/
-extern struct ref_lock *lock_ref_sha1(const char *ref, const unsigned char *old_sha1, int mustexist);
+extern struct ref_lock *lock_ref_sha1(const char *ref, const unsigned char *old_sha1);
/** Locks any ref (for 'HEAD' type refs). */
-extern struct ref_lock *lock_any_ref_for_update(const char *ref, const unsigned char *old_sha1, int mustexist);
+extern struct ref_lock *lock_any_ref_for_update(const char *ref, const unsigned char *old_sha1);
/** Release any lock taken but not written. **/
extern void unlock_ref(struct ref_lock *lock);
@@ -36,7 +39,7 @@ extern void unlock_ref(struct ref_lock *lock);
extern int write_ref_sha1(struct ref_lock *lock, const unsigned char *sha1, const char *msg);
/** Reads log for the value of ref during at_time. **/
-extern int read_ref_at(const char *ref, unsigned long at_time, unsigned char *sha1);
+extern int read_ref_at(const char *ref, unsigned long at_time, int cnt, unsigned char *sha1);
/** Returns 0 if target has the right format for a ref. **/
extern int check_ref_format(const char *target);
diff --git a/revision.c b/revision.c
index 5a91d06b98..993bb668a2 100644
--- a/revision.c
+++ b/revision.c
@@ -6,6 +6,8 @@
#include "diff.h"
#include "refs.h"
#include "revision.h"
+#include <regex.h>
+#include "grep.h"
static char *path_name(struct name_path *path, const char *name)
{
@@ -416,8 +418,6 @@ static void limit_list(struct rev_info *revs)
if (revs->max_age != -1 && (commit->date < revs->max_age))
obj->flags |= UNINTERESTING;
- if (revs->unpacked && has_sha1_pack(obj->sha1))
- obj->flags |= UNINTERESTING;
add_parents_to_list(revs, commit, &list);
if (obj->flags & UNINTERESTING) {
mark_parents_uninteresting(commit);
@@ -465,7 +465,7 @@ static void limit_list(struct rev_info *revs)
static int all_flags;
static struct rev_info *all_revs;
-static int handle_one_ref(const char *path, const unsigned char *sha1)
+static int handle_one_ref(const char *path, const unsigned char *sha1, int flag, void *cb_data)
{
struct object *object = get_reference(all_revs, path, sha1, all_flags);
add_pending_object(all_revs, object, "");
@@ -476,7 +476,7 @@ static void handle_all(struct rev_info *revs, unsigned flags)
{
all_revs = revs;
all_flags = flags;
- for_each_ref(handle_one_ref);
+ for_each_ref(handle_one_ref, NULL);
}
static int add_parents_only(struct rev_info *revs, const char *arg, int flags)
@@ -496,7 +496,7 @@ static int add_parents_only(struct rev_info *revs, const char *arg, int flags)
it = get_reference(revs, arg, sha1, 0);
if (it->type != OBJ_TAG)
break;
- memcpy(sha1, ((struct tag*)it)->tagged->sha1, 20);
+ hashcpy(sha1, ((struct tag*)it)->tagged->sha1);
}
if (it->type != OBJ_COMMIT)
return 0;
@@ -592,6 +592,138 @@ static void prepare_show_merge(struct rev_info *revs)
revs->prune_data = prune;
}
+int handle_revision_arg(const char *arg, struct rev_info *revs,
+ int flags,
+ int cant_be_filename)
+{
+ char *dotdot;
+ struct object *object;
+ unsigned char sha1[20];
+ int local_flags;
+
+ dotdot = strstr(arg, "..");
+ if (dotdot) {
+ unsigned char from_sha1[20];
+ const char *next = dotdot + 2;
+ const char *this = arg;
+ int symmetric = *next == '.';
+ unsigned int flags_exclude = flags ^ UNINTERESTING;
+
+ *dotdot = 0;
+ next += symmetric;
+
+ if (!*next)
+ next = "HEAD";
+ if (dotdot == arg)
+ this = "HEAD";
+ if (!get_sha1(this, from_sha1) &&
+ !get_sha1(next, sha1)) {
+ struct commit *a, *b;
+ struct commit_list *exclude;
+
+ a = lookup_commit_reference(from_sha1);
+ b = lookup_commit_reference(sha1);
+ if (!a || !b) {
+ die(symmetric ?
+ "Invalid symmetric difference expression %s...%s" :
+ "Invalid revision range %s..%s",
+ arg, next);
+ }
+
+ if (!cant_be_filename) {
+ *dotdot = '.';
+ verify_non_filename(revs->prefix, arg);
+ }
+
+ if (symmetric) {
+ exclude = get_merge_bases(a, b, 1);
+ add_pending_commit_list(revs, exclude,
+ flags_exclude);
+ free_commit_list(exclude);
+ a->object.flags |= flags;
+ } else
+ a->object.flags |= flags_exclude;
+ b->object.flags |= flags;
+ add_pending_object(revs, &a->object, this);
+ add_pending_object(revs, &b->object, next);
+ return 0;
+ }
+ *dotdot = '.';
+ }
+ dotdot = strstr(arg, "^@");
+ if (dotdot && !dotdot[2]) {
+ *dotdot = 0;
+ if (add_parents_only(revs, arg, flags))
+ return 0;
+ *dotdot = '^';
+ }
+ dotdot = strstr(arg, "^!");
+ if (dotdot && !dotdot[2]) {
+ *dotdot = 0;
+ if (!add_parents_only(revs, arg, flags ^ UNINTERESTING))
+ *dotdot = '^';
+ }
+
+ local_flags = 0;
+ if (*arg == '^') {
+ local_flags = UNINTERESTING;
+ arg++;
+ }
+ if (get_sha1(arg, sha1))
+ return -1;
+ if (!cant_be_filename)
+ verify_non_filename(revs->prefix, arg);
+ object = get_reference(revs, arg, sha1, flags ^ local_flags);
+ add_pending_object(revs, object, arg);
+ return 0;
+}
+
+static void add_grep(struct rev_info *revs, const char *ptn, enum grep_pat_token what)
+{
+ if (!revs->grep_filter) {
+ struct grep_opt *opt = xcalloc(1, sizeof(*opt));
+ opt->status_only = 1;
+ opt->pattern_tail = &(opt->pattern_list);
+ opt->regflags = REG_NEWLINE;
+ revs->grep_filter = opt;
+ }
+ append_grep_pattern(revs->grep_filter, ptn,
+ "command line", 0, what);
+}
+
+static void add_header_grep(struct rev_info *revs, const char *field, const char *pattern)
+{
+ char *pat;
+ const char *prefix;
+ int patlen, fldlen;
+
+ fldlen = strlen(field);
+ patlen = strlen(pattern);
+ pat = xmalloc(patlen + fldlen + 10);
+ prefix = ".*";
+ if (*pattern == '^') {
+ prefix = "";
+ pattern++;
+ }
+ sprintf(pat, "^%s %s%s", field, prefix, pattern);
+ add_grep(revs, pat, GREP_PATTERN_HEAD);
+}
+
+static void add_message_grep(struct rev_info *revs, const char *pattern)
+{
+ add_grep(revs, pattern, GREP_PATTERN_BODY);
+}
+
+static void add_ignore_packed(struct rev_info *revs, const char *name)
+{
+ int num = ++revs->num_ignore_packed;
+
+ revs->ignore_packed = xrealloc(revs->ignore_packed,
+ sizeof(const char **) * (num + 1));
+ revs->ignore_packed[num-1] = name;
+ revs->ignore_packed[num] = NULL;
+}
+
/*
* Parse revision information, filling in the "rev_info" structure,
* and removing the used arguments from the argument list.
@@ -604,6 +736,7 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch
int i, flags, seen_dashdash, show_merge;
const char **unrecognized = argv + 1;
int left = 1;
+ int all_match = 0;
/* First, search for "--" */
seen_dashdash = 0;
@@ -620,12 +753,7 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch
flags = show_merge = 0;
for (i = 1; i < argc; i++) {
- struct object *object;
const char *arg = argv[i];
- unsigned char sha1[20];
- char *dotdot;
- int local_flags;
-
if (*arg == '-') {
int opts;
if (!strncmp(arg, "--max-count=", 12)) {
@@ -737,6 +865,14 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch
}
if (!strcmp(arg, "--unpacked")) {
revs->unpacked = 1;
+ free(revs->ignore_packed);
+ revs->ignore_packed = NULL;
+ revs->num_ignore_packed = 0;
+ continue;
+ }
+ if (!strncmp(arg, "--unpacked=", 11)) {
+ revs->unpacked = 1;
+ add_ignore_packed(revs, arg+11);
continue;
}
if (!strcmp(arg, "-r")) {
@@ -816,6 +952,31 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch
revs->simplify_history = 0;
continue;
}
+ if (!strcmp(arg, "--relative-date")) {
+ revs->relative_date = 1;
+ continue;
+ }
+
+ /*
+ * Grepping the commit log
+ */
+ if (!strncmp(arg, "--author=", 9)) {
+ add_header_grep(revs, "author", arg+9);
+ continue;
+ }
+ if (!strncmp(arg, "--committer=", 12)) {
+ add_header_grep(revs, "committer", arg+12);
+ continue;
+ }
+ if (!strncmp(arg, "--grep=", 7)) {
+ add_message_grep(revs, arg+7);
+ continue;
+ }
+ if (!strcmp(arg, "--all-match")) {
+ all_match = 1;
+ continue;
+ }
+
opts = diff_opt_parse(&revs->diffopt, argv+i, argc-i);
if (opts > 0) {
revs->diff = 1;
@@ -826,71 +987,10 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch
left++;
continue;
}
- dotdot = strstr(arg, "..");
- if (dotdot) {
- unsigned char from_sha1[20];
- const char *next = dotdot + 2;
- const char *this = arg;
- int symmetric = *next == '.';
- unsigned int flags_exclude = flags ^ UNINTERESTING;
-
- *dotdot = 0;
- next += symmetric;
-
- if (!*next)
- next = "HEAD";
- if (dotdot == arg)
- this = "HEAD";
- if (!get_sha1(this, from_sha1) &&
- !get_sha1(next, sha1)) {
- struct commit *a, *b;
- struct commit_list *exclude;
-
- a = lookup_commit_reference(from_sha1);
- b = lookup_commit_reference(sha1);
- if (!a || !b) {
- die(symmetric ?
- "Invalid symmetric difference expression %s...%s" :
- "Invalid revision range %s..%s",
- arg, next);
- }
-
- if (!seen_dashdash) {
- *dotdot = '.';
- verify_non_filename(revs->prefix, arg);
- }
-
- if (symmetric) {
- exclude = get_merge_bases(a, b, 1);
- add_pending_commit_list(revs, exclude,
- flags_exclude);
- free_commit_list(exclude);
- a->object.flags |= flags;
- } else
- a->object.flags |= flags_exclude;
- b->object.flags |= flags;
- add_pending_object(revs, &a->object, this);
- add_pending_object(revs, &b->object, next);
- continue;
- }
- *dotdot = '.';
- }
- dotdot = strstr(arg, "^@");
- if (dotdot && !dotdot[2]) {
- *dotdot = 0;
- if (add_parents_only(revs, arg, flags))
- continue;
- *dotdot = '^';
- }
- local_flags = 0;
- if (*arg == '^') {
- local_flags = UNINTERESTING;
- arg++;
- }
- if (get_sha1(arg, sha1)) {
- int j;
- if (seen_dashdash || local_flags)
+ if (handle_revision_arg(arg, revs, flags, seen_dashdash)) {
+ int j;
+ if (seen_dashdash || *arg == '^')
die("bad revision '%s'", arg);
/* If we didn't have a "--":
@@ -902,14 +1002,12 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch
for (j = i; j < argc; j++)
verify_filename(revs->prefix, argv[j]);
- revs->prune_data = get_pathspec(revs->prefix, argv + i);
+ revs->prune_data = get_pathspec(revs->prefix,
+ argv + i);
break;
}
- if (!seen_dashdash)
- verify_non_filename(revs->prefix, arg);
- object = get_reference(revs, arg, sha1, flags ^ local_flags);
- add_pending_object(revs, object, arg);
}
+
if (show_merge)
prepare_show_merge(revs);
if (def && !revs->pending.nr) {
@@ -921,7 +1019,7 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch
add_pending_object(revs, object, def);
}
- if (revs->topo_order || revs->unpacked)
+ if (revs->topo_order)
revs->limited = 1;
if (revs->prune_data) {
@@ -939,6 +1037,11 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch
if (diff_setup_done(&revs->diffopt) < 0)
die("diff_setup_done failed");
+ if (revs->grep_filter) {
+ revs->grep_filter->all_match = all_match;
+ compile_grep_patterns(revs->grep_filter);
+ }
+
return left;
}
@@ -1011,6 +1114,15 @@ static void mark_boundary_to_show(struct commit *commit)
}
}
+static int commit_match(struct commit *commit, struct rev_info *opt)
+{
+ if (!opt->grep_filter)
+ return 1;
+ return grep_buffer(opt->grep_filter,
+ NULL, /* we say nothing, not even filename */
+ commit->buffer, strlen(commit->buffer));
+}
+
struct commit *get_revision(struct rev_info *revs)
{
struct commit_list *list = revs->commits;
@@ -1041,16 +1153,18 @@ struct commit *get_revision(struct rev_info *revs)
* that we'd otherwise have done in limit_list().
*/
if (!revs->limited) {
- if ((revs->unpacked &&
- has_sha1_pack(commit->object.sha1)) ||
- (revs->max_age != -1 &&
- (commit->date < revs->max_age)))
+ if (revs->max_age != -1 &&
+ (commit->date < revs->max_age))
continue;
add_parents_to_list(revs, commit, &revs->commits);
}
if (commit->object.flags & SHOWN)
continue;
+ if (revs->unpacked && has_sha1_pack(commit->object.sha1,
+ revs->ignore_packed))
+ continue;
+
/* We want to show boundary commits only when their
* children are shown. When path-limiter is in effect,
* rewrite_parents() drops some commits from getting shown,
@@ -1070,6 +1184,8 @@ struct commit *get_revision(struct rev_info *revs)
if (revs->no_merges &&
commit->parents && commit->parents->next)
continue;
+ if (!commit_match(commit, revs))
+ continue;
if (revs->prune_fn && revs->dense) {
/* Commit without changes? */
if (!(commit->object.flags & TREECHANGE)) {
diff --git a/revision.h b/revision.h
index 0c3b8d9905..3adab9590a 100644
--- a/revision.h
+++ b/revision.h
@@ -38,7 +38,7 @@ struct rev_info {
blob_objects:1,
edge_hint:1,
limited:1,
- unpacked:1,
+ unpacked:1, /* see also ignore_packed below */
boundary:1,
parents:1;
@@ -55,7 +55,12 @@ struct rev_info {
/* Format info */
unsigned int shown_one:1,
- abbrev_commit:1;
+ abbrev_commit:1,
+ relative_date:1;
+
+ const char **ignore_packed; /* pretend objects in these are unpacked */
+ int num_ignore_packed;
+
unsigned int abbrev;
enum cmit_fmt commit_format;
struct log_info *loginfo;
@@ -66,6 +71,9 @@ struct rev_info {
const char *add_signoff;
const char *extra_headers;
+ /* Filter by commit log message */
+ struct grep_opt *grep_filter;
+
/* special limits */
int max_count;
unsigned long max_age;
@@ -89,6 +97,8 @@ extern int rev_compare_tree(struct rev_info *, struct tree *t1, struct tree *t2)
extern void init_revisions(struct rev_info *revs, const char *prefix);
extern int setup_revisions(int argc, const char **argv, struct rev_info *revs, const char *def);
+extern int handle_revision_arg(const char *arg, struct rev_info *revs,int flags,int cant_be_filename);
+
extern void prepare_revision_walk(struct rev_info *revs);
extern struct commit *get_revision(struct rev_info *revs);
diff --git a/rsh.c b/rsh.c
index 07166addd9..f34409e1bc 100644
--- a/rsh.c
+++ b/rsh.c
@@ -8,36 +8,7 @@
#define COMMAND_SIZE 4096
-/*
- * Append a string to a string buffer, with or without shell quoting.
- * Return true if the buffer overflowed.
- */
-static int add_to_string(char **ptrp, int *sizep, const char *str, int quote)
-{
- char *p = *ptrp;
- int size = *sizep;
- int oc;
- int err = 0;
-
- if ( quote ) {
- oc = sq_quote_buf(p, size, str);
- } else {
- oc = strlen(str);
- memcpy(p, str, (oc >= size) ? size-1 : oc);
- }
-
- if ( oc >= size ) {
- err = 1;
- oc = size-1;
- }
-
- *ptrp += oc;
- **ptrp = '\0';
- *sizep -= oc;
- return err;
-}
-
-int setup_connection(int *fd_in, int *fd_out, const char *remote_prog,
+int setup_connection(int *fd_in, int *fd_out, const char *remote_prog,
char *url, int rmt_argc, char **rmt_argv)
{
char *host;
diff --git a/run-command.c b/run-command.c
index ca67ee9333..61908682b9 100644
--- a/run-command.c
+++ b/run-command.c
@@ -25,15 +25,15 @@ int run_command_v_opt(int argc, const char **argv, int flags)
}
for (;;) {
int status, code;
- int retval = waitpid(pid, &status, 0);
+ pid_t waiting = waitpid(pid, &status, 0);
- if (retval < 0) {
+ if (waiting < 0) {
if (errno == EINTR)
continue;
- error("waitpid failed (%s)", strerror(retval));
+ error("waitpid failed (%s)", strerror(errno));
return -ERR_RUN_COMMAND_WAITPID;
}
- if (retval != pid)
+ if (waiting != pid)
return -ERR_RUN_COMMAND_WAITPID_WRONG_PID;
if (WIFSIGNALED(status))
return -ERR_RUN_COMMAND_WAITPID_SIGNAL;
diff --git a/send-pack.c b/send-pack.c
index 10bc8bc359..447666665b 100644
--- a/send-pack.c
+++ b/send-pack.c
@@ -9,10 +9,10 @@ static const char send_pack_usage[] =
"git-send-pack [--all] [--exec=git-receive-pack] <remote> [<head>...]\n"
" --all and explicit <head> specification are mutually exclusive.";
static const char *exec = "git-receive-pack";
-static int verbose = 0;
-static int send_all = 0;
-static int force_update = 0;
-static int use_thin_pack = 0;
+static int verbose;
+static int send_all;
+static int force_update;
+static int use_thin_pack;
static int is_zero_sha1(const unsigned char *sha1)
{
@@ -29,6 +29,7 @@ static void exec_pack_objects(void)
{
static const char *args[] = {
"pack-objects",
+ "--all-progress",
"--stdout",
NULL
};
@@ -38,9 +39,8 @@ static void exec_pack_objects(void)
static void exec_rev_list(struct ref *refs)
{
- struct ref *ref;
- static const char *args[1000];
- int i = 0, j;
+ static const char *args[4];
+ int i = 0;
args[i++] = "rev-list"; /* 0 */
if (use_thin_pack) /* 1 */
@@ -48,43 +48,16 @@ static void exec_rev_list(struct ref *refs)
else
args[i++] = "--objects";
- /* First send the ones we care about most */
- for (ref = refs; ref; ref = ref->next) {
- if (900 < i)
- die("git-rev-list environment overflow");
- if (!is_zero_sha1(ref->new_sha1)) {
- char *buf = malloc(100);
- args[i++] = buf;
- snprintf(buf, 50, "%s", sha1_to_hex(ref->new_sha1));
- buf += 50;
- if (!is_zero_sha1(ref->old_sha1) &&
- has_sha1_file(ref->old_sha1)) {
- args[i++] = buf;
- snprintf(buf, 50, "^%s",
- sha1_to_hex(ref->old_sha1));
- }
- }
- }
+ args[i++] = "--stdin";
- /* Then a handful of the remainder
- * NEEDSWORK: we would be better off if used the newer ones first.
- */
- for (ref = refs, j = i + 16;
- i < 900 && i < j && ref;
- ref = ref->next) {
- if (is_zero_sha1(ref->new_sha1) &&
- !is_zero_sha1(ref->old_sha1) &&
- has_sha1_file(ref->old_sha1)) {
- char *buf = malloc(42);
- args[i++] = buf;
- snprintf(buf, 42, "^%s", sha1_to_hex(ref->old_sha1));
- }
- }
args[i] = NULL;
execv_git_cmd(args);
die("git-rev-list exec failed (%s)", strerror(errno));
}
+/*
+ * Run "rev-list --stdin | pack-objects" pipe.
+ */
static void rev_list(int fd, struct ref *refs)
{
int pipe_fd[2];
@@ -94,6 +67,9 @@ static void rev_list(int fd, struct ref *refs)
die("rev-list setup: pipe failed");
pack_objects_pid = fork();
if (!pack_objects_pid) {
+ /* The child becomes pack-objects; reads from pipe
+ * and writes to the original fd
+ */
dup2(pipe_fd[0], 0);
dup2(fd, 1);
close(pipe_fd[0]);
@@ -104,6 +80,8 @@ static void rev_list(int fd, struct ref *refs)
}
if (pack_objects_pid < 0)
die("pack-objects fork failed");
+
+ /* We become rev-list --stdin; output goes to pipe. */
dup2(pipe_fd[1], 1);
close(pipe_fd[0]);
close(pipe_fd[1]);
@@ -111,13 +89,71 @@ static void rev_list(int fd, struct ref *refs)
exec_rev_list(refs);
}
-static int pack_objects(int fd, struct ref *refs)
+/*
+ * Create "rev-list --stdin | pack-objects" pipe and feed
+ * the refs into the pipeline.
+ */
+static void rev_list_generate(int fd, struct ref *refs)
+{
+ int pipe_fd[2];
+ pid_t rev_list_generate_pid;
+
+ if (pipe(pipe_fd) < 0)
+ die("rev-list-generate setup: pipe failed");
+ rev_list_generate_pid = fork();
+ if (!rev_list_generate_pid) {
+ /* The child becomes the "rev-list | pack-objects"
+ * pipeline. It takes input from us, and its output
+ * goes to fd.
+ */
+ dup2(pipe_fd[0], 0);
+ dup2(fd, 1);
+ close(pipe_fd[0]);
+ close(pipe_fd[1]);
+ close(fd);
+ rev_list(fd, refs);
+ die("rev-list setup failed");
+ }
+ if (rev_list_generate_pid < 0)
+ die("rev-list-generate fork failed");
+
+ /* We feed the rev parameters to them. We do not write into
+ * fd nor read from the pipe.
+ */
+ close(pipe_fd[0]);
+ close(fd);
+ while (refs) {
+ char buf[42];
+
+ if (!is_null_sha1(refs->old_sha1) &&
+ has_sha1_file(refs->old_sha1)) {
+ memcpy(buf + 1, sha1_to_hex(refs->old_sha1), 40);
+ buf[0] = '^';
+ buf[41] = '\n';
+ write(pipe_fd[1], buf, 42);
+ }
+ if (!is_null_sha1(refs->new_sha1)) {
+ memcpy(buf, sha1_to_hex(refs->new_sha1), 40);
+ buf[40] = '\n';
+ write(pipe_fd[1], buf, 41);
+ }
+ refs = refs->next;
+ }
+ close(pipe_fd[1]);
+ // waitpid(rev_list_generate_pid);
+ exit(0);
+}
+
+/*
+ * Make a pack stream and spit it out into file descriptor fd
+ */
+static void pack_objects(int fd, struct ref *refs)
{
pid_t rev_list_pid;
rev_list_pid = fork();
if (!rev_list_pid) {
- rev_list(fd, refs);
+ rev_list_generate(fd, refs);
die("rev-list setup failed");
}
if (rev_list_pid < 0)
@@ -126,7 +162,6 @@ static int pack_objects(int fd, struct ref *refs)
* We don't wait for the rev-list pipeline in the parent:
* we end up waiting for the other end instead
*/
- return 0;
}
static void unmark_and_free(struct commit_list *list, unsigned int mark)
@@ -181,12 +216,12 @@ static int ref_newer(const unsigned char *new_sha1,
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)
+static int one_local_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
{
struct ref *ref;
int len = strlen(refname) + 1;
ref = xcalloc(1, sizeof(*ref) + len);
- memcpy(ref->new_sha1, sha1, 20);
+ hashcpy(ref->new_sha1, sha1);
memcpy(ref->name, refname, len);
*local_tail = ref;
local_tail = &ref->next;
@@ -196,7 +231,7 @@ static int one_local_ref(const char *refname, const unsigned char *sha1)
static void get_local_heads(void)
{
local_tail = &local_refs;
- for_each_ref(one_local_ref);
+ for_each_ref(one_local_ref, NULL);
}
static int receive_status(int in)
@@ -266,7 +301,7 @@ static int send_pack(int in, int out, int nr_refspec, char **refspec)
char old_hex[60], *new_hex;
if (!ref->peer_ref)
continue;
- if (!memcmp(ref->old_sha1, ref->peer_ref->new_sha1, 20)) {
+ if (!hashcmp(ref->old_sha1, ref->peer_ref->new_sha1)) {
if (verbose)
fprintf(stderr, "'%s': up-to-date\n", ref->name);
continue;
@@ -311,7 +346,7 @@ static int send_pack(int in, int out, int nr_refspec, char **refspec)
continue;
}
}
- memcpy(ref->new_sha1, ref->peer_ref->new_sha1, 20);
+ hashcpy(ref->new_sha1, ref->peer_ref->new_sha1);
if (is_zero_sha1(ref->new_sha1)) {
error("cannot happen anymore");
ret = -3;
@@ -409,6 +444,6 @@ int main(int argc, char **argv)
ret = send_pack(fd[0], fd[1], nr_heads, heads);
close(fd[0]);
close(fd[1]);
- finish_connect(pid);
- return ret;
+ ret |= finish_connect(pid);
+ return !!ret;
}
diff --git a/server-info.c b/server-info.c
index 7df628f2b2..6cd38be329 100644
--- a/server-info.c
+++ b/server-info.c
@@ -7,7 +7,7 @@
/* refs */
static FILE *info_ref_fp;
-static int add_info_ref(const char *path, const unsigned char *sha1)
+static int add_info_ref(const char *path, const unsigned char *sha1, int flag, void *cb_data)
{
struct object *o = parse_object(sha1);
@@ -23,7 +23,7 @@ static int add_info_ref(const char *path, const unsigned char *sha1)
static int update_info_refs(int force)
{
- char *path0 = strdup(git_path("info/refs"));
+ char *path0 = xstrdup(git_path("info/refs"));
int len = strlen(path0);
char *path1 = xmalloc(len + 2);
@@ -34,7 +34,7 @@ static int update_info_refs(int force)
info_ref_fp = fopen(path1, "w");
if (!info_ref_fp)
return error("unable to update %s", path0);
- for_each_ref(add_info_ref);
+ for_each_ref(add_info_ref, NULL);
fclose(info_ref_fp);
rename(path1, path0);
free(path0);
diff --git a/sha1_file.c b/sha1_file.c
index 842a6f3ae8..09456d23f8 100644
--- a/sha1_file.c
+++ b/sha1_file.c
@@ -22,20 +22,44 @@
#endif
#endif
-const unsigned char null_sha1[20] = { 0, };
+const unsigned char null_sha1[20];
static unsigned int sha1_file_open_flag = O_NOATIME;
-static unsigned hexval(char c)
-{
- if (c >= '0' && c <= '9')
- return c - '0';
- if (c >= 'a' && c <= 'f')
- return c - 'a' + 10;
- if (c >= 'A' && c <= 'F')
- return c - 'A' + 10;
- return ~0;
-}
+signed char hexval_table[256] = {
+ -1, -1, -1, -1, -1, -1, -1, -1, /* 00-07 */
+ -1, -1, -1, -1, -1, -1, -1, -1, /* 08-0f */
+ -1, -1, -1, -1, -1, -1, -1, -1, /* 10-17 */
+ -1, -1, -1, -1, -1, -1, -1, -1, /* 18-1f */
+ -1, -1, -1, -1, -1, -1, -1, -1, /* 20-27 */
+ -1, -1, -1, -1, -1, -1, -1, -1, /* 28-2f */
+ 0, 1, 2, 3, 4, 5, 6, 7, /* 30-37 */
+ 8, 9, -1, -1, -1, -1, -1, -1, /* 38-3f */
+ -1, 10, 11, 12, 13, 14, 15, -1, /* 40-47 */
+ -1, -1, -1, -1, -1, -1, -1, -1, /* 48-4f */
+ -1, -1, -1, -1, -1, -1, -1, -1, /* 50-57 */
+ -1, -1, -1, -1, -1, -1, -1, -1, /* 58-5f */
+ -1, 10, 11, 12, 13, 14, 15, -1, /* 60-67 */
+ -1, -1, -1, -1, -1, -1, -1, -1, /* 68-67 */
+ -1, -1, -1, -1, -1, -1, -1, -1, /* 70-77 */
+ -1, -1, -1, -1, -1, -1, -1, -1, /* 78-7f */
+ -1, -1, -1, -1, -1, -1, -1, -1, /* 80-87 */
+ -1, -1, -1, -1, -1, -1, -1, -1, /* 88-8f */
+ -1, -1, -1, -1, -1, -1, -1, -1, /* 90-97 */
+ -1, -1, -1, -1, -1, -1, -1, -1, /* 98-9f */
+ -1, -1, -1, -1, -1, -1, -1, -1, /* a0-a7 */
+ -1, -1, -1, -1, -1, -1, -1, -1, /* a8-af */
+ -1, -1, -1, -1, -1, -1, -1, -1, /* b0-b7 */
+ -1, -1, -1, -1, -1, -1, -1, -1, /* b8-bf */
+ -1, -1, -1, -1, -1, -1, -1, -1, /* c0-c7 */
+ -1, -1, -1, -1, -1, -1, -1, -1, /* c8-cf */
+ -1, -1, -1, -1, -1, -1, -1, -1, /* d0-d7 */
+ -1, -1, -1, -1, -1, -1, -1, -1, /* d8-df */
+ -1, -1, -1, -1, -1, -1, -1, -1, /* e0-e7 */
+ -1, -1, -1, -1, -1, -1, -1, -1, /* e8-ef */
+ -1, -1, -1, -1, -1, -1, -1, -1, /* f0-f7 */
+ -1, -1, -1, -1, -1, -1, -1, -1, /* f8-ff */
+};
int get_sha1_hex(const char *hex, unsigned char *sha1)
{
@@ -115,7 +139,7 @@ static void fill_sha1_path(char *pathbuf, const unsigned char *sha1)
/*
* NOTE! This returns a statically allocated buffer, so you have to be
- * careful about using it. Do a "strdup()" if you need to save the
+ * careful about using it. Do a "xstrdup()" if you need to save the
* filename.
*
* Also note that this returns the location for creating. Reading
@@ -463,6 +487,7 @@ int use_packed_git(struct packed_git *p)
int fd;
struct stat st;
void *map;
+ struct pack_header *hdr;
pack_mapped += p->pack_size;
while (PACK_MAX_SZ < pack_mapped && unuse_one_packed_git())
@@ -482,13 +507,24 @@ int use_packed_git(struct packed_git *p)
die("packfile %s cannot be mapped.", p->pack_name);
p->pack_base = map;
+ /* Check if we understand this pack file. If we don't we're
+ * likely too old to handle it.
+ */
+ hdr = map;
+ if (hdr->hdr_signature != htonl(PACK_SIGNATURE))
+ die("packfile %s isn't actually a pack.", p->pack_name);
+ if (!pack_version_ok(hdr->hdr_version))
+ die("packfile %s is version %i and not supported"
+ " (try upgrading GIT to a newer version)",
+ p->pack_name, ntohl(hdr->hdr_version));
+
/* Check if the pack file matches with the index file.
* this is cheap.
*/
- if (memcmp((char*)(p->index_base) + p->index_size - 40,
- (char *) p->pack_base + p->pack_size - 20,
- 20)) {
-
+ if (hashcmp((unsigned char *)(p->index_base) +
+ p->index_size - 40,
+ (unsigned char *)p->pack_base +
+ p->pack_size - 20)) {
die("packfile %s does not match index.", p->pack_name);
}
}
@@ -528,7 +564,7 @@ struct packed_git *add_packed_git(char *path, int path_len, int local)
p->pack_use_cnt = 0;
p->pack_local = local;
if ((path_len > 44) && !get_sha1_hex(path + path_len - 44, sha1))
- memcpy(p->sha1, sha1, 20);
+ hashcpy(p->sha1, sha1);
return p;
}
@@ -559,7 +595,7 @@ struct packed_git *parse_pack_index_file(const unsigned char *sha1, char *idx_pa
p->pack_base = NULL;
p->pack_last_used = 0;
p->pack_use_cnt = 0;
- memcpy(p->sha1, sha1, 20);
+ hashcpy(p->sha1, sha1);
return p;
}
@@ -627,7 +663,7 @@ void prepare_packed_git(void)
prepare_packed_git_run_once = 1;
}
-static void reprepare_packed_git(void)
+void reprepare_packed_git(void)
{
prepare_packed_git_run_once = 0;
prepare_packed_git();
@@ -635,15 +671,9 @@ static void reprepare_packed_git(void)
int check_sha1_signature(const unsigned char *sha1, void *map, unsigned long size, const char *type)
{
- char header[100];
unsigned char real_sha1[20];
- SHA_CTX c;
-
- SHA1_Init(&c);
- SHA1_Update(&c, header, 1+sprintf(header, "%s %lu", type, size));
- SHA1_Update(&c, map, size);
- SHA1_Final(real_sha1, &c);
- return memcmp(sha1, real_sha1, 20) ? -1 : 0;
+ hash_sha1_file(map, size, type, real_sha1);
+ return hashcmp(sha1, real_sha1) ? -1 : 0;
}
void *map_sha1_file(const unsigned char *sha1, unsigned long *size)
@@ -699,17 +729,39 @@ int legacy_loose_object(unsigned char *map)
return 0;
}
-static int unpack_sha1_header(z_stream *stream, unsigned char *map, unsigned long mapsize, void *buffer, unsigned long bufsiz)
+unsigned long unpack_object_header_gently(const unsigned char *buf, unsigned long len, enum object_type *type, unsigned long *sizep)
{
+ unsigned shift;
unsigned char c;
- unsigned int bits;
unsigned long size;
- static const char *typename[8] = {
- NULL, /* OBJ_EXT */
- "commit", "tree", "blob", "tag",
- NULL, NULL, NULL
+ unsigned long used = 0;
+
+ c = buf[used++];
+ *type = (c >> 4) & 7;
+ size = c & 15;
+ shift = 4;
+ while (c & 0x80) {
+ if (len <= used)
+ return 0;
+ if (sizeof(long) * 8 <= shift)
+ return 0;
+ c = buf[used++];
+ size += (c & 0x7f) << shift;
+ shift += 7;
+ }
+ *sizep = size;
+ return used;
+}
+
+static int unpack_sha1_header(z_stream *stream, unsigned char *map, unsigned long mapsize, void *buffer, unsigned long bufsiz)
+{
+ unsigned long size, used;
+ static const char valid_loose_object_type[8] = {
+ 0, /* OBJ_EXT */
+ 1, 1, 1, 1, /* "commit", "tree", "blob", "tag" */
+ 0, /* "delta" and others are invalid in a loose object */
};
- const char *type;
+ enum object_type type;
/* Get the data stream */
memset(stream, 0, sizeof(*stream));
@@ -723,22 +775,11 @@ static int unpack_sha1_header(z_stream *stream, unsigned char *map, unsigned lon
return inflate(stream, 0);
}
- c = *map++;
- mapsize--;
- type = typename[(c >> 4) & 7];
- if (!type)
+ used = unpack_object_header_gently(map, mapsize, &type, &size);
+ if (!used || !valid_loose_object_type[type])
return -1;
-
- bits = 4;
- size = c & 0xf;
- while ((c & 0x80)) {
- if (bits >= 8*sizeof(long))
- return -1;
- c = *map++;
- size += (c & 0x7f) << bits;
- bits += 7;
- mapsize--;
- }
+ map += used;
+ mapsize -= used;
/* Set up the stream for the rest.. */
stream->next_in = map;
@@ -746,7 +787,8 @@ static int unpack_sha1_header(z_stream *stream, unsigned char *map, unsigned lon
inflateInit(stream);
/* And generate the fake traditional header */
- stream->total_out = 1 + snprintf(buffer, bufsiz, "%s %lu", type, size);
+ stream->total_out = 1 + snprintf(buffer, bufsiz, "%s %lu",
+ type_names[type], size);
return 0;
}
@@ -835,47 +877,81 @@ void * unpack_sha1_file(void *map, unsigned long mapsize, char *type, unsigned l
return unpack_sha1_rest(&stream, hdr, *size);
}
+static unsigned long get_delta_base(struct packed_git *p,
+ unsigned long offset,
+ enum object_type kind,
+ unsigned long delta_obj_offset,
+ unsigned long *base_obj_offset)
+{
+ unsigned char *base_info = (unsigned char *) p->pack_base + offset;
+ unsigned long base_offset;
+
+ /* there must be at least 20 bytes left regardless of delta type */
+ if (p->pack_size <= offset + 20)
+ die("truncated pack file");
+
+ if (kind == OBJ_OFS_DELTA) {
+ unsigned used = 0;
+ unsigned char c = base_info[used++];
+ base_offset = c & 127;
+ while (c & 128) {
+ base_offset += 1;
+ if (!base_offset || base_offset & ~(~0UL >> 7))
+ die("offset value overflow for delta base object");
+ c = base_info[used++];
+ base_offset = (base_offset << 7) + (c & 127);
+ }
+ base_offset = delta_obj_offset - base_offset;
+ if (base_offset >= delta_obj_offset)
+ die("delta base offset out of bound");
+ offset += used;
+ } else if (kind == OBJ_REF_DELTA) {
+ /* The base entry _must_ be in the same pack */
+ base_offset = find_pack_entry_one(base_info, p);
+ if (!base_offset)
+ die("failed to find delta-pack base object %s",
+ sha1_to_hex(base_info));
+ offset += 20;
+ } else
+ die("I am totally screwed");
+ *base_obj_offset = base_offset;
+ return offset;
+}
+
/* forward declaration for a mutually recursive function */
-static int packed_object_info(struct pack_entry *entry,
+static int packed_object_info(struct packed_git *p, unsigned long offset,
char *type, unsigned long *sizep);
-static int packed_delta_info(unsigned char *base_sha1,
- unsigned long delta_size,
- unsigned long left,
+static int packed_delta_info(struct packed_git *p,
+ unsigned long offset,
+ enum object_type kind,
+ unsigned long obj_offset,
char *type,
- unsigned long *sizep,
- struct packed_git *p)
+ unsigned long *sizep)
{
- struct pack_entry base_ent;
-
- if (left < 20)
- die("truncated pack file");
+ unsigned long base_offset;
- /* The base entry _must_ be in the same pack */
- if (!find_pack_entry_one(base_sha1, &base_ent, p))
- die("failed to find delta-pack base object %s",
- sha1_to_hex(base_sha1));
+ offset = get_delta_base(p, offset, kind, obj_offset, &base_offset);
/* We choose to only get the type of the base object and
* ignore potentially corrupt pack file that expects the delta
* based on a base with a wrong size. This saves tons of
* inflate() calls.
*/
-
- if (packed_object_info(&base_ent, type, NULL))
+ if (packed_object_info(p, base_offset, type, NULL))
die("cannot get info for delta-pack base");
if (sizep) {
const unsigned char *data;
- unsigned char delta_head[64];
+ unsigned char delta_head[20];
unsigned long result_size;
z_stream stream;
int st;
memset(&stream, 0, sizeof(stream));
- data = stream.next_in = base_sha1 + 20;
- stream.avail_in = left - 20;
+ stream.next_in = (unsigned char *) p->pack_base + offset;
+ stream.avail_in = p->pack_size - offset;
stream.next_out = delta_head;
stream.avail_out = sizeof(delta_head);
@@ -904,136 +980,87 @@ static int packed_delta_info(unsigned char *base_sha1,
static unsigned long unpack_object_header(struct packed_git *p, unsigned long offset,
enum object_type *type, unsigned long *sizep)
{
- unsigned shift;
- unsigned char *pack, c;
- unsigned long size;
+ unsigned long used;
- if (offset >= p->pack_size)
+ if (p->pack_size <= offset)
die("object offset outside of pack file");
- pack = (unsigned char *) p->pack_base + offset;
- c = *pack++;
- offset++;
- *type = (c >> 4) & 7;
- size = c & 15;
- shift = 4;
- while (c & 0x80) {
- if (offset >= p->pack_size)
- die("object offset outside of pack file");
- c = *pack++;
- offset++;
- size += (c & 0x7f) << shift;
- shift += 7;
- }
- *sizep = size;
- return offset;
-}
-
-int check_reuse_pack_delta(struct packed_git *p, unsigned long offset,
- unsigned char *base, unsigned long *sizep,
- enum object_type *kindp)
-{
- unsigned long ptr;
- int status = -1;
+ used = unpack_object_header_gently((unsigned char *)p->pack_base +
+ offset,
+ p->pack_size - offset, type, sizep);
+ if (!used)
+ die("object offset outside of pack file");
- use_packed_git(p);
- ptr = offset;
- ptr = unpack_object_header(p, ptr, kindp, sizep);
- if (*kindp != OBJ_DELTA)
- goto done;
- memcpy(base, (char *) p->pack_base + ptr, 20);
- status = 0;
- done:
- unuse_packed_git(p);
- return status;
+ return offset + used;
}
-void packed_object_info_detail(struct pack_entry *e,
+void packed_object_info_detail(struct packed_git *p,
+ unsigned long offset,
char *type,
unsigned long *size,
unsigned long *store_size,
unsigned int *delta_chain_length,
unsigned char *base_sha1)
{
- struct packed_git *p = e->p;
- unsigned long offset;
- unsigned char *pack;
+ unsigned long obj_offset, val;
+ unsigned char *next_sha1;
enum object_type kind;
- offset = unpack_object_header(p, e->offset, &kind, size);
- pack = (unsigned char *) p->pack_base + offset;
- if (kind != OBJ_DELTA)
- *delta_chain_length = 0;
- else {
- unsigned int chain_length = 0;
- if (p->pack_size <= offset + 20)
- die("pack file %s records an incomplete delta base",
- p->pack_name);
- memcpy(base_sha1, pack, 20);
- do {
- struct pack_entry base_ent;
- unsigned long junk;
-
- find_pack_entry_one(pack, &base_ent, p);
- offset = unpack_object_header(p, base_ent.offset,
- &kind, &junk);
- pack = (unsigned char *) p->pack_base + offset;
- chain_length++;
- } while (kind == OBJ_DELTA);
- *delta_chain_length = chain_length;
- }
- switch (kind) {
- case OBJ_COMMIT:
- strcpy(type, commit_type);
- break;
- case OBJ_TREE:
- strcpy(type, tree_type);
- break;
- case OBJ_BLOB:
- strcpy(type, blob_type);
- break;
- case OBJ_TAG:
- strcpy(type, tag_type);
- break;
- default:
- die("corrupted pack file %s containing object of kind %d",
- p->pack_name, kind);
+ *delta_chain_length = 0;
+ obj_offset = offset;
+ offset = unpack_object_header(p, offset, &kind, size);
+
+ for (;;) {
+ switch (kind) {
+ default:
+ die("corrupted pack file %s containing object of kind %d",
+ p->pack_name, kind);
+ case OBJ_COMMIT:
+ case OBJ_TREE:
+ case OBJ_BLOB:
+ case OBJ_TAG:
+ strcpy(type, type_names[kind]);
+ *store_size = 0; /* notyet */
+ return;
+ case OBJ_OFS_DELTA:
+ get_delta_base(p, offset, kind, obj_offset, &offset);
+ if (*delta_chain_length == 0) {
+ /* TODO: find base_sha1 as pointed by offset */
+ }
+ break;
+ case OBJ_REF_DELTA:
+ if (p->pack_size <= offset + 20)
+ die("pack file %s records an incomplete delta base",
+ p->pack_name);
+ next_sha1 = (unsigned char *) p->pack_base + offset;
+ if (*delta_chain_length == 0)
+ hashcpy(base_sha1, next_sha1);
+ offset = find_pack_entry_one(next_sha1, p);
+ break;
+ }
+ obj_offset = offset;
+ offset = unpack_object_header(p, offset, &kind, &val);
+ (*delta_chain_length)++;
}
- *store_size = 0; /* notyet */
}
-static int packed_object_info(struct pack_entry *entry,
+static int packed_object_info(struct packed_git *p, unsigned long offset,
char *type, unsigned long *sizep)
{
- struct packed_git *p = entry->p;
- unsigned long offset, size, left;
- unsigned char *pack;
+ unsigned long size, obj_offset = offset;
enum object_type kind;
- int retval;
-
- if (use_packed_git(p))
- die("cannot map packed file");
- offset = unpack_object_header(p, entry->offset, &kind, &size);
- pack = (unsigned char *) p->pack_base + offset;
- left = p->pack_size - offset;
+ offset = unpack_object_header(p, offset, &kind, &size);
switch (kind) {
- case OBJ_DELTA:
- retval = packed_delta_info(pack, size, left, type, sizep, p);
- unuse_packed_git(p);
- return retval;
+ case OBJ_OFS_DELTA:
+ case OBJ_REF_DELTA:
+ return packed_delta_info(p, offset, kind, obj_offset, type, sizep);
case OBJ_COMMIT:
- strcpy(type, commit_type);
- break;
case OBJ_TREE:
- strcpy(type, tree_type);
- break;
case OBJ_BLOB:
- strcpy(type, blob_type);
- break;
case OBJ_TAG:
- strcpy(type, tag_type);
+ strcpy(type, type_names[kind]);
break;
default:
die("corrupted pack file %s containing object of kind %d",
@@ -1041,69 +1068,12 @@ static int packed_object_info(struct pack_entry *entry,
}
if (sizep)
*sizep = size;
- unuse_packed_git(p);
return 0;
}
-/* forward declaration for a mutually recursive function */
-static void *unpack_entry(struct pack_entry *, char *, unsigned long *);
-
-static void *unpack_delta_entry(unsigned char *base_sha1,
- unsigned long delta_size,
- unsigned long left,
- char *type,
- unsigned long *sizep,
- struct packed_git *p)
-{
- struct pack_entry base_ent;
- void *data, *delta_data, *result, *base;
- unsigned long data_size, result_size, base_size;
- z_stream stream;
- int st;
-
- if (left < 20)
- die("truncated pack file");
-
- /* The base entry _must_ be in the same pack */
- if (!find_pack_entry_one(base_sha1, &base_ent, p))
- die("failed to find delta-pack base object %s",
- sha1_to_hex(base_sha1));
- base = unpack_entry_gently(&base_ent, type, &base_size);
- if (!base)
- die("failed to read delta-pack base object %s",
- sha1_to_hex(base_sha1));
-
- data = base_sha1 + 20;
- data_size = left - 20;
- delta_data = xmalloc(delta_size);
-
- memset(&stream, 0, sizeof(stream));
-
- stream.next_in = data;
- stream.avail_in = data_size;
- stream.next_out = delta_data;
- stream.avail_out = delta_size;
-
- inflateInit(&stream);
- st = inflate(&stream, Z_FINISH);
- inflateEnd(&stream);
- if ((st != Z_STREAM_END) || stream.total_out != delta_size)
- die("delta data unpack failed");
-
- result = patch_delta(base, base_size,
- delta_data, delta_size,
- &result_size);
- if (!result)
- die("failed to apply delta");
- free(delta_data);
- free(base);
- *sizep = result_size;
- return result;
-}
-
-static void *unpack_non_delta_entry(unsigned char *data,
- unsigned long size,
- unsigned long left)
+static void *unpack_compressed_entry(struct packed_git *p,
+ unsigned long offset,
+ unsigned long size)
{
int st;
z_stream stream;
@@ -1112,8 +1082,8 @@ static void *unpack_non_delta_entry(unsigned char *data,
buffer = xmalloc(size + 1);
buffer[size] = 0;
memset(&stream, 0, sizeof(stream));
- stream.next_in = data;
- stream.avail_in = left;
+ stream.next_in = (unsigned char*)p->pack_base + offset;
+ stream.avail_in = p->pack_size - offset;
stream.next_out = buffer;
stream.avail_out = size;
@@ -1128,6 +1098,35 @@ static void *unpack_non_delta_entry(unsigned char *data,
return buffer;
}
+static void *unpack_delta_entry(struct packed_git *p,
+ unsigned long offset,
+ unsigned long delta_size,
+ enum object_type kind,
+ unsigned long obj_offset,
+ char *type,
+ unsigned long *sizep)
+{
+ void *delta_data, *result, *base;
+ unsigned long result_size, base_size, base_offset;
+
+ offset = get_delta_base(p, offset, kind, obj_offset, &base_offset);
+ base = unpack_entry_gently(p, base_offset, type, &base_size);
+ if (!base)
+ die("failed to read delta base object at %lu from %s",
+ base_offset, p->pack_name);
+
+ delta_data = unpack_compressed_entry(p, offset, delta_size);
+ result = patch_delta(base, base_size,
+ delta_data, delta_size,
+ &result_size);
+ if (!result)
+ die("failed to apply delta");
+ free(delta_data);
+ free(base);
+ *sizep = result_size;
+ return result;
+}
+
static void *unpack_entry(struct pack_entry *entry,
char *type, unsigned long *sizep)
{
@@ -1136,7 +1135,7 @@ static void *unpack_entry(struct pack_entry *entry,
if (use_packed_git(p))
die("cannot map packed file");
- retval = unpack_entry_gently(entry, type, sizep);
+ retval = unpack_entry_gently(p, entry->offset, type, sizep);
unuse_packed_git(p);
if (!retval)
die("corrupted pack file %s", p->pack_name);
@@ -1144,40 +1143,27 @@ static void *unpack_entry(struct pack_entry *entry,
}
/* The caller is responsible for use_packed_git()/unuse_packed_git() pair */
-void *unpack_entry_gently(struct pack_entry *entry,
+void *unpack_entry_gently(struct packed_git *p, unsigned long offset,
char *type, unsigned long *sizep)
{
- struct packed_git *p = entry->p;
- unsigned long offset, size, left;
- unsigned char *pack;
+ unsigned long size, obj_offset = offset;
enum object_type kind;
- void *retval;
- offset = unpack_object_header(p, entry->offset, &kind, &size);
- pack = (unsigned char *) p->pack_base + offset;
- left = p->pack_size - offset;
+ offset = unpack_object_header(p, offset, &kind, &size);
switch (kind) {
- case OBJ_DELTA:
- retval = unpack_delta_entry(pack, size, left, type, sizep, p);
- return retval;
+ case OBJ_OFS_DELTA:
+ case OBJ_REF_DELTA:
+ return unpack_delta_entry(p, offset, size, kind, obj_offset, type, sizep);
case OBJ_COMMIT:
- strcpy(type, commit_type);
- break;
case OBJ_TREE:
- strcpy(type, tree_type);
- break;
case OBJ_BLOB:
- strcpy(type, blob_type);
- break;
case OBJ_TAG:
- strcpy(type, tag_type);
- break;
+ strcpy(type, type_names[kind]);
+ *sizep = size;
+ return unpack_compressed_entry(p, offset, size);
default:
return NULL;
}
- *sizep = size;
- retval = unpack_non_delta_entry(pack, size, left);
- return retval;
}
int num_packed_objects(const struct packed_git *p)
@@ -1192,12 +1178,12 @@ int nth_packed_object_sha1(const struct packed_git *p, int n,
void *index = p->index_base + 256;
if (n < 0 || num_packed_objects(p) <= n)
return -1;
- memcpy(sha1, (char *) index + (24 * n) + 4, 20);
+ hashcpy(sha1, (unsigned char *) index + (24 * n) + 4);
return 0;
}
-int find_pack_entry_one(const unsigned char *sha1,
- struct pack_entry *e, struct packed_git *p)
+unsigned long find_pack_entry_one(const unsigned char *sha1,
+ struct packed_git *p)
{
unsigned int *level1_ofs = p->index_base;
int hi = ntohl(level1_ofs[*sha1]);
@@ -1206,13 +1192,9 @@ int find_pack_entry_one(const unsigned char *sha1,
do {
int mi = (lo + hi) / 2;
- int cmp = memcmp((char *) index + (24 * mi) + 4, sha1, 20);
- if (!cmp) {
- e->offset = ntohl(*((unsigned int *) ((char *) index + (24 * mi))));
- memcpy(e->sha1, sha1, 20);
- e->p = p;
- return 1;
- }
+ int cmp = hashcmp((unsigned char *)index + (24 * mi) + 4, sha1);
+ if (!cmp)
+ return ntohl(*((unsigned int *) ((char *) index + (24 * mi))));
if (cmp > 0)
hi = mi;
else
@@ -1221,14 +1203,47 @@ int find_pack_entry_one(const unsigned char *sha1,
return 0;
}
-static int find_pack_entry(const unsigned char *sha1, struct pack_entry *e)
+static int matches_pack_name(struct packed_git *p, const char *ig)
+{
+ const char *last_c, *c;
+
+ if (!strcmp(p->pack_name, ig))
+ return 0;
+
+ for (c = p->pack_name, last_c = c; *c;)
+ if (*c == '/')
+ last_c = ++c;
+ else
+ ++c;
+ if (!strcmp(last_c, ig))
+ return 0;
+
+ return 1;
+}
+
+static int find_pack_entry(const unsigned char *sha1, struct pack_entry *e, const char **ignore_packed)
{
struct packed_git *p;
+ unsigned long offset;
+
prepare_packed_git();
for (p = packed_git; p; p = p->next) {
- if (find_pack_entry_one(sha1, e, p))
+ if (ignore_packed) {
+ const char **ig;
+ for (ig = ignore_packed; *ig; ig++)
+ if (!matches_pack_name(p, *ig))
+ break;
+ if (*ig)
+ continue;
+ }
+ offset = find_pack_entry_one(sha1, p);
+ if (offset) {
+ e->offset = offset;
+ e->p = p;
+ hashcpy(e->sha1, sha1);
return 1;
+ }
}
return 0;
}
@@ -1237,10 +1252,9 @@ struct packed_git *find_sha1_pack(const unsigned char *sha1,
struct packed_git *packs)
{
struct packed_git *p;
- struct pack_entry e;
for (p = packs; p; p = p->next) {
- if (find_pack_entry_one(sha1, &e, p))
+ if (find_pack_entry_one(sha1, p))
return p;
}
return NULL;
@@ -1259,12 +1273,16 @@ int sha1_object_info(const unsigned char *sha1, char *type, unsigned long *sizep
if (!map) {
struct pack_entry e;
- if (find_pack_entry(sha1, &e))
- return packed_object_info(&e, type, sizep);
- reprepare_packed_git();
- if (find_pack_entry(sha1, &e))
- return packed_object_info(&e, type, sizep);
- return error("unable to find %s", sha1_to_hex(sha1));
+ if (!find_pack_entry(sha1, &e, NULL)) {
+ reprepare_packed_git();
+ if (!find_pack_entry(sha1, &e, NULL))
+ return error("unable to find %s", sha1_to_hex(sha1));
+ }
+ if (use_packed_git(e.p))
+ die("cannot map packed file");
+ status = packed_object_info(e.p, e.offset, type, sizep);
+ unuse_packed_git(e.p);
+ return status;
}
if (unpack_sha1_header(&stream, map, mapsize, hdr, sizeof(hdr)) < 0)
status = error("unable to unpack %s header",
@@ -1285,7 +1303,7 @@ static void *read_packed_sha1(const unsigned char *sha1, char *type, unsigned lo
{
struct pack_entry e;
- if (!find_pack_entry(sha1, &e)) {
+ if (!find_pack_entry(sha1, &e, NULL)) {
error("cannot read sha1_file for %s", sha1_to_hex(sha1));
return NULL;
}
@@ -1298,7 +1316,7 @@ void * read_sha1_file(const unsigned char *sha1, char *type, unsigned long *size
void *map, *buf;
struct pack_entry e;
- if (find_pack_entry(sha1, &e))
+ if (find_pack_entry(sha1, &e, NULL))
return read_packed_sha1(sha1, type, size);
map = map_sha1_file(sha1, &mapsize);
if (map) {
@@ -1307,7 +1325,7 @@ void * read_sha1_file(const unsigned char *sha1, char *type, unsigned long *size
return buf;
}
reprepare_packed_git();
- if (find_pack_entry(sha1, &e))
+ if (find_pack_entry(sha1, &e, NULL))
return read_packed_sha1(sha1, type, size);
return NULL;
}
@@ -1322,7 +1340,7 @@ void *read_object_with_reference(const unsigned char *sha1,
unsigned long isize;
unsigned char actual_sha1[20];
- memcpy(actual_sha1, sha1, 20);
+ hashcpy(actual_sha1, sha1);
while (1) {
int ref_length = -1;
const char *ref_type = NULL;
@@ -1333,7 +1351,7 @@ void *read_object_with_reference(const unsigned char *sha1,
if (!strcmp(type, required_type)) {
*size = isize;
if (actual_sha1_return)
- memcpy(actual_sha1_return, actual_sha1, 20);
+ hashcpy(actual_sha1_return, actual_sha1);
return buffer;
}
/* Handle references */
@@ -1358,12 +1376,9 @@ void *read_object_with_reference(const unsigned char *sha1,
}
}
-char *write_sha1_file_prepare(void *buf,
- unsigned long len,
- const char *type,
- unsigned char *sha1,
- unsigned char *hdr,
- int *hdrlen)
+static void write_sha1_file_prepare(void *buf, unsigned long len,
+ const char *type, unsigned char *sha1,
+ unsigned char *hdr, int *hdrlen)
{
SHA_CTX c;
@@ -1375,8 +1390,6 @@ char *write_sha1_file_prepare(void *buf,
SHA1_Update(&c, hdr, *hdrlen);
SHA1_Update(&c, buf, len);
SHA1_Final(sha1, &c);
-
- return sha1_file_name(sha1);
}
/*
@@ -1385,7 +1398,7 @@ char *write_sha1_file_prepare(void *buf,
*
* Returns the errno on failure, 0 on success.
*/
-static int link_temp_to_file(const char *tmpfile, char *filename)
+static int link_temp_to_file(const char *tmpfile, const char *filename)
{
int ret;
char *dir;
@@ -1404,9 +1417,10 @@ static int link_temp_to_file(const char *tmpfile, char *filename)
dir = strrchr(filename, '/');
if (dir) {
*dir = 0;
- mkdir(filename, 0777);
- if (adjust_shared_perm(filename))
+ if (!mkdir(filename, 0777) && adjust_shared_perm(filename)) {
+ *dir = '/';
return -2;
+ }
*dir = '/';
if (!link(tmpfile, filename))
return 0;
@@ -1418,7 +1432,7 @@ static int link_temp_to_file(const char *tmpfile, char *filename)
/*
* Move the just written object into its final resting place
*/
-int move_temp_to_file(const char *tmpfile, char *filename)
+int move_temp_to_file(const char *tmpfile, const char *filename)
{
int ret = link_temp_to_file(tmpfile, filename);
@@ -1441,8 +1455,7 @@ int move_temp_to_file(const char *tmpfile, char *filename)
unlink(tmpfile);
if (ret) {
if (ret != EEXIST) {
- fprintf(stderr, "unable to write sha1 filename %s: %s\n", filename, strerror(ret));
- return -1;
+ return error("unable to write sha1 filename %s: %s\n", filename, strerror(ret));
}
/* FIXME!!! Collision check here ? */
}
@@ -1512,6 +1525,15 @@ static void setup_object_header(z_stream *stream, const char *type, unsigned lon
stream->avail_out -= hdr;
}
+int hash_sha1_file(void *buf, unsigned long len, const char *type,
+ unsigned char *sha1)
+{
+ unsigned char hdr[50];
+ int hdrlen;
+ write_sha1_file_prepare(buf, len, type, sha1, hdr, &hdrlen);
+ return 0;
+}
+
int write_sha1_file(void *buf, unsigned long len, const char *type, unsigned char *returnsha1)
{
int size;
@@ -1526,9 +1548,10 @@ int write_sha1_file(void *buf, unsigned long len, const char *type, unsigned cha
/* Normally if we have it in the pack then we do not bother writing
* it out into .git/objects/??/?{38} file.
*/
- filename = write_sha1_file_prepare(buf, len, type, sha1, hdr, &hdrlen);
+ write_sha1_file_prepare(buf, len, type, sha1, hdr, &hdrlen);
+ filename = sha1_file_name(sha1);
if (returnsha1)
- memcpy(returnsha1, sha1, 20);
+ hashcpy(returnsha1, sha1);
if (has_sha1_file(sha1))
return 0;
fd = open(filename, O_RDONLY);
@@ -1542,16 +1565,17 @@ int write_sha1_file(void *buf, unsigned long len, const char *type, unsigned cha
}
if (errno != ENOENT) {
- fprintf(stderr, "sha1 file %s: %s\n", filename, strerror(errno));
- return -1;
+ return error("sha1 file %s: %s\n", filename, strerror(errno));
}
snprintf(tmpfile, sizeof(tmpfile), "%s/obj_XXXXXX", get_object_directory());
fd = mkstemp(tmpfile);
if (fd < 0) {
- fprintf(stderr, "unable to create temporary sha1 filename %s: %s\n", tmpfile, strerror(errno));
- return -1;
+ if (errno == EPERM)
+ return error("insufficient permission for adding an object to repository database %s\n", get_object_directory());
+ else
+ return error("unable to create temporary sha1 filename %s: %s\n", tmpfile, strerror(errno));
}
/* Set it up */
@@ -1666,9 +1690,12 @@ int write_sha1_from_fd(const unsigned char *sha1, int fd, char *buffer,
snprintf(tmpfile, sizeof(tmpfile), "%s/obj_XXXXXX", get_object_directory());
local = mkstemp(tmpfile);
- if (local < 0)
- return error("Couldn't open %s for %s",
- tmpfile, sha1_to_hex(sha1));
+ if (local < 0) {
+ if (errno == EPERM)
+ return error("insufficient permission for adding an object to repository database %s\n", get_object_directory());
+ else
+ return error("unable to create temporary sha1 filename %s: %s\n", tmpfile, strerror(errno));
+ }
memset(&stream, 0, sizeof(stream));
@@ -1715,7 +1742,7 @@ int write_sha1_from_fd(const unsigned char *sha1, int fd, char *buffer,
unlink(tmpfile);
return error("File %s corrupted", sha1_to_hex(sha1));
}
- if (memcmp(sha1, real_sha1, 20)) {
+ if (hashcmp(sha1, real_sha1)) {
unlink(tmpfile);
return error("File %s has bad hash", sha1_to_hex(sha1));
}
@@ -1739,10 +1766,10 @@ int has_pack_file(const unsigned char *sha1)
return 1;
}
-int has_sha1_pack(const unsigned char *sha1)
+int has_sha1_pack(const unsigned char *sha1, const char **ignore_packed)
{
struct pack_entry e;
- return find_pack_entry(sha1, &e);
+ return find_pack_entry(sha1, &e, ignore_packed);
}
int has_sha1_file(const unsigned char *sha1)
@@ -1750,7 +1777,7 @@ int has_sha1_file(const unsigned char *sha1)
struct stat st;
struct pack_entry e;
- if (find_pack_entry(sha1, &e))
+ if (find_pack_entry(sha1, &e, NULL))
return 1;
return find_sha1_file(sha1, &st) ? 1 : 0;
}
@@ -1777,7 +1804,7 @@ int read_pipe(int fd, char** return_buf, unsigned long* return_size)
off += iret;
if (off == size) {
size *= 2;
- buf = realloc(buf, size);
+ buf = xrealloc(buf, size);
}
}
} while (iret > 0);
@@ -1793,10 +1820,8 @@ int read_pipe(int fd, char** return_buf, unsigned long* return_size)
int index_pipe(unsigned char *sha1, int fd, const char *type, int write_object)
{
unsigned long size = 4096;
- char *buf = malloc(size);
+ char *buf = xmalloc(size);
int ret;
- unsigned char hdr[50];
- int hdrlen;
if (read_pipe(fd, &buf, &size)) {
free(buf);
@@ -1807,10 +1832,8 @@ int index_pipe(unsigned char *sha1, int fd, const char *type, int write_object)
type = blob_type;
if (write_object)
ret = write_sha1_file(buf, size, type, sha1);
- else {
- write_sha1_file_prepare(buf, size, type, sha1, hdr, &hdrlen);
- ret = 0;
- }
+ else
+ ret = hash_sha1_file(buf, size, type, sha1);
free(buf);
return ret;
}
@@ -1820,8 +1843,6 @@ int index_fd(unsigned char *sha1, int fd, struct stat *st, int write_object, con
unsigned long size = st->st_size;
void *buf;
int ret;
- unsigned char hdr[50];
- int hdrlen;
buf = "";
if (size)
@@ -1834,10 +1855,8 @@ int index_fd(unsigned char *sha1, int fd, struct stat *st, int write_object, con
type = blob_type;
if (write_object)
ret = write_sha1_file(buf, size, type, sha1);
- else {
- write_sha1_file_prepare(buf, size, type, sha1, hdr, &hdrlen);
- ret = 0;
- }
+ else
+ ret = hash_sha1_file(buf, size, type, sha1);
if (size)
munmap(buf, size);
return ret;
@@ -1866,12 +1885,9 @@ int index_path(unsigned char *sha1, const char *path, struct stat *st, int write
return error("readlink(\"%s\"): %s", path,
errstr);
}
- if (!write_object) {
- unsigned char hdr[50];
- int hdrlen;
- write_sha1_file_prepare(target, st->st_size, blob_type,
- sha1, hdr, &hdrlen);
- } else if (write_sha1_file(target, st->st_size, blob_type, sha1))
+ if (!write_object)
+ hash_sha1_file(target, st->st_size, blob_type, sha1);
+ else if (write_sha1_file(target, st->st_size, blob_type, sha1))
return error("%s: failed to insert into database",
path);
free(target);
diff --git a/sha1_name.c b/sha1_name.c
index c5a05faeb6..6d7cd78381 100644
--- a/sha1_name.c
+++ b/sha1_name.c
@@ -84,7 +84,7 @@ static int find_short_packed_object(int len, const unsigned char *match, unsigne
int cmp;
nth_packed_object_sha1(p, mid, now);
- cmp = memcmp(match, now, 20);
+ cmp = hashcmp(match, now);
if (!cmp) {
first = mid;
break;
@@ -103,10 +103,10 @@ static int find_short_packed_object(int len, const unsigned char *match, unsigne
!match_sha(len, match, next)) {
/* unique within this pack */
if (!found) {
- memcpy(found_sha1, now, 20);
+ hashcpy(found_sha1, now);
found++;
}
- else if (memcmp(found_sha1, now, 20)) {
+ else if (hashcmp(found_sha1, now)) {
found = 2;
break;
}
@@ -120,7 +120,7 @@ static int find_short_packed_object(int len, const unsigned char *match, unsigne
}
}
if (found == 1)
- memcpy(sha1, found_sha1, 20);
+ hashcpy(sha1, found_sha1);
return found;
}
@@ -140,13 +140,13 @@ static int find_unique_short_object(int len, char *canonical,
if (1 < has_unpacked || 1 < has_packed)
return SHORT_NAME_AMBIGUOUS;
if (has_unpacked != has_packed) {
- memcpy(sha1, (has_packed ? packed_sha1 : unpacked_sha1), 20);
+ hashcpy(sha1, (has_packed ? packed_sha1 : unpacked_sha1));
return 0;
}
/* Both have unique ones -- do they match? */
- if (memcmp(packed_sha1, unpacked_sha1, 20))
+ if (hashcmp(packed_sha1, unpacked_sha1))
return SHORT_NAME_AMBIGUOUS;
- memcpy(sha1, packed_sha1, 20);
+ hashcpy(sha1, packed_sha1);
return 0;
}
@@ -157,9 +157,9 @@ static int get_short_sha1(const char *name, int len, unsigned char *sha1,
char canonical[40];
unsigned char res[20];
- if (len < MINIMUM_ABBREV)
+ if (len < MINIMUM_ABBREV || len > 40)
return -1;
- memset(res, 0, 20);
+ hashclr(res);
memset(canonical, 'x', 40);
for (i = 0; i < len ;i++) {
unsigned char c = name[i];
@@ -191,7 +191,7 @@ const char *find_unique_abbrev(const unsigned char *sha1, int len)
int status, is_null;
static char hex[41];
- is_null = !memcmp(sha1, null_sha1, 20);
+ is_null = is_null_sha1(sha1);
memcpy(hex, sha1_to_hex(sha1), 40);
if (len == 40 || !len)
return hex;
@@ -247,26 +247,25 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1)
NULL
};
static const char *warning = "warning: refname '%.*s' is ambiguous.\n";
- const char **p, *pathname;
- char *real_path = NULL;
- int refs_found = 0, am;
- unsigned long at_time = (unsigned long)-1;
+ const char **p, *ref;
+ char *real_ref = NULL;
+ int refs_found = 0;
+ int at, reflog_len;
unsigned char *this_result;
unsigned char sha1_from_ref[20];
if (len == 40 && !get_sha1_hex(str, sha1))
return 0;
- /* At a given period of time? "@{2 hours ago}" */
- for (am = 1; am < len - 1; am++) {
- if (str[am] == '@' && str[am+1] == '{' && str[len-1] == '}') {
- int date_len = len - am - 3;
- char *date_spec = xmalloc(date_len + 1);
- strlcpy(date_spec, str + am + 2, date_len + 1);
- at_time = approxidate(date_spec);
- free(date_spec);
- len = am;
- break;
+ /* basic@{time or number} format to query ref-log */
+ reflog_len = at = 0;
+ if (str[len-1] == '}') {
+ for (at = 1; at < len - 1; at++) {
+ if (str[at] == '@' && str[at+1] == '{') {
+ reflog_len = (len-1) - (at+2);
+ len = at;
+ break;
+ }
}
}
@@ -276,10 +275,10 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1)
for (p = fmt; *p; p++) {
this_result = refs_found ? sha1_from_ref : sha1;
- pathname = resolve_ref(git_path(*p, len, str), this_result, 1);
- if (pathname) {
+ ref = resolve_ref(mkpath(*p, len, str), this_result, 1, NULL);
+ if (ref) {
if (!refs_found++)
- real_path = strdup(pathname);
+ real_ref = xstrdup(ref);
if (!warn_ambiguous_refs)
break;
}
@@ -291,14 +290,25 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1)
if (warn_ambiguous_refs && refs_found > 1)
fprintf(stderr, warning, len, str);
- if (at_time != (unsigned long)-1) {
- read_ref_at(
- real_path + strlen(git_path(".")) - 1,
- at_time,
- sha1);
+ if (reflog_len) {
+ /* Is it asking for N-th entry, or approxidate? */
+ int nth, i;
+ unsigned long at_time;
+ for (i = nth = 0; 0 <= nth && i < reflog_len; i++) {
+ char ch = str[at+2+i];
+ if ('0' <= ch && ch <= '9')
+ nth = nth * 10 + ch - '0';
+ else
+ nth = -1;
+ }
+ if (0 <= nth)
+ at_time = 0;
+ else
+ at_time = approxidate(str + at + 2);
+ read_ref_at(real_ref, at_time, nth, sha1);
}
- free(real_path);
+ free(real_ref);
return 0;
}
@@ -320,13 +330,13 @@ static int get_parent(const char *name, int len,
if (parse_commit(commit))
return -1;
if (!idx) {
- memcpy(result, commit->object.sha1, 20);
+ hashcpy(result, commit->object.sha1);
return 0;
}
p = commit->parents;
while (p) {
if (!--idx) {
- memcpy(result, p->item->object.sha1, 20);
+ hashcpy(result, p->item->object.sha1);
return 0;
}
p = p->next;
@@ -347,9 +357,9 @@ static int get_nth_ancestor(const char *name, int len,
if (!commit || parse_commit(commit) || !commit->parents)
return -1;
- memcpy(sha1, commit->parents->item->object.sha1, 20);
+ hashcpy(sha1, commit->parents->item->object.sha1);
}
- memcpy(result, sha1, 20);
+ hashcpy(result, sha1);
return 0;
}
@@ -401,7 +411,7 @@ static int peel_onion(const char *name, int len, unsigned char *sha1)
o = deref_tag(o, name, sp - name - 2);
if (!o || (!o->parsed && !parse_object(o->sha1)))
return -1;
- memcpy(sha1, o->sha1, 20);
+ hashcpy(sha1, o->sha1);
}
else {
/* At this point, the syntax look correct, so
@@ -413,7 +423,7 @@ static int peel_onion(const char *name, int len, unsigned char *sha1)
if (!o || (!o->parsed && !parse_object(o->sha1)))
return -1;
if (o->type == expected_type) {
- memcpy(sha1, o->sha1, 20);
+ hashcpy(sha1, o->sha1);
return 0;
}
if (o->type == OBJ_TAG)
@@ -431,6 +441,26 @@ static int peel_onion(const char *name, int len, unsigned char *sha1)
return 0;
}
+static int get_describe_name(const char *name, int len, unsigned char *sha1)
+{
+ const char *cp;
+
+ for (cp = name + len - 1; name + 2 <= cp; cp--) {
+ char ch = *cp;
+ if (hexval(ch) & ~0377) {
+ /* We must be looking at g in "SOMETHING-g"
+ * for it to be describe output.
+ */
+ if (ch == 'g' && cp[-1] == '-') {
+ cp++;
+ len -= cp - name;
+ return get_short_sha1(cp, len, sha1, 1);
+ }
+ }
+ }
+ return -1;
+}
+
static int get_sha1_1(const char *name, int len, unsigned char *sha1)
{
int ret, has_suffix;
@@ -472,6 +502,12 @@ static int get_sha1_1(const char *name, int len, unsigned char *sha1)
ret = get_sha1_basic(name, len, sha1);
if (!ret)
return 0;
+
+ /* It could be describe output that is "SOMETHING-gXXXX" */
+ ret = get_describe_name(name, len, sha1);
+ if (!ret)
+ return 0;
+
return get_short_sha1(name, len, sha1, 0);
}
@@ -520,7 +556,7 @@ int get_sha1(const char *name, unsigned char *sha1)
memcmp(ce->name, cp, namelen))
break;
if (ce_stage(ce) == stage) {
- memcpy(sha1, ce->sha1, 20);
+ hashcpy(sha1, ce->sha1);
return 0;
}
pos++;
diff --git a/show-index.c b/show-index.c
index c21d660b62..a30a2de5d1 100644
--- a/show-index.c
+++ b/show-index.c
@@ -8,7 +8,7 @@ int main(int argc, char **argv)
static unsigned int top_index[256];
if (fread(top_index, sizeof(top_index), 1, stdin) != 1)
- die("unable to read idex");
+ die("unable to read index");
nr = 0;
for (i = 0; i < 256; i++) {
unsigned n = ntohl(top_index[i]);
diff --git a/sideband.c b/sideband.c
new file mode 100644
index 0000000000..277fa3c10d
--- /dev/null
+++ b/sideband.c
@@ -0,0 +1,78 @@
+#include "pkt-line.h"
+#include "sideband.h"
+
+/*
+ * Receive multiplexed output stream over git native protocol.
+ * in_stream is the input stream from the remote, which carries data
+ * in pkt_line format with band designator. Demultiplex it into out
+ * and err and return error appropriately. Band #1 carries the
+ * primary payload. Things coming over band #2 is not necessarily
+ * error; they are usually informative message on the standard error
+ * stream, aka "verbose"). A message over band #3 is a signal that
+ * the remote died unexpectedly. A flush() concludes the stream.
+ */
+int recv_sideband(const char *me, int in_stream, int out, int err)
+{
+ char buf[7 + LARGE_PACKET_MAX + 1];
+ strcpy(buf, "remote:");
+ while (1) {
+ int band, len;
+ len = packet_read_line(in_stream, buf+7, LARGE_PACKET_MAX);
+ if (len == 0)
+ break;
+ if (len < 1) {
+ len = sprintf(buf, "%s: protocol error: no band designator\n", me);
+ safe_write(err, buf, len);
+ return SIDEBAND_PROTOCOL_ERROR;
+ }
+ band = buf[7] & 0xff;
+ len--;
+ switch (band) {
+ case 3:
+ buf[7] = ' ';
+ buf[8+len] = '\n';
+ safe_write(err, buf, 8+len+1);
+ return SIDEBAND_REMOTE_ERROR;
+ case 2:
+ buf[7] = ' ';
+ safe_write(err, buf, 8+len);
+ continue;
+ case 1:
+ safe_write(out, buf+8, len);
+ continue;
+ default:
+ len = sprintf(buf,
+ "%s: protocol error: bad band #%d\n",
+ me, band);
+ safe_write(err, buf, len);
+ return SIDEBAND_PROTOCOL_ERROR;
+ }
+ }
+ return 0;
+}
+
+/*
+ * fd is connected to the remote side; send the sideband data
+ * over multiplexed packet stream.
+ */
+ssize_t send_sideband(int fd, int band, const char *data, ssize_t sz, int packet_max)
+{
+ ssize_t ssz = sz;
+ const char *p = data;
+
+ while (sz) {
+ unsigned n;
+ char hdr[5];
+
+ n = sz;
+ if (packet_max - 5 < n)
+ n = packet_max - 5;
+ sprintf(hdr, "%04x", n + 5);
+ hdr[4] = band;
+ safe_write(fd, hdr, 5);
+ safe_write(fd, p, n);
+ p += n;
+ sz -= n;
+ }
+ return ssz;
+}
diff --git a/sideband.h b/sideband.h
new file mode 100644
index 0000000000..a84b6917c7
--- /dev/null
+++ b/sideband.h
@@ -0,0 +1,13 @@
+#ifndef SIDEBAND_H
+#define SIDEBAND_H
+
+#define SIDEBAND_PROTOCOL_ERROR -2
+#define SIDEBAND_REMOTE_ERROR -1
+
+#define DEFAULT_PACKET_MAX 1000
+#define LARGE_PACKET_MAX 65520
+
+int recv_sideband(const char *me, int in_stream, int out, int err);
+ssize_t send_sideband(int fd, int band, const char *data, ssize_t sz, int packet_max);
+
+#endif
diff --git a/ssh-fetch.c b/ssh-fetch.c
index c7d8fa80e4..b006c5c980 100644
--- a/ssh-fetch.c
+++ b/ssh-fetch.c
@@ -17,7 +17,7 @@
static int fd_in;
static int fd_out;
-static unsigned char remote_version = 0;
+static unsigned char remote_version;
static unsigned char local_version = 1;
static ssize_t force_write(int fd, void *buffer, size_t length)
@@ -36,9 +36,9 @@ static ssize_t force_write(int fd, void *buffer, size_t length)
return ret;
}
-static int prefetches = 0;
+static int prefetches;
-static struct object_list *in_transit = NULL;
+static struct object_list *in_transit;
static struct object_list **end_of_transit = &in_transit;
void prefetch(unsigned char *sha1)
@@ -59,7 +59,7 @@ void prefetch(unsigned char *sha1)
}
static char conn_buf[4096];
-static size_t conn_buf_posn = 0;
+static size_t conn_buf_posn;
int fetch(unsigned char *sha1)
{
@@ -67,7 +67,7 @@ int fetch(unsigned char *sha1)
signed char remote;
struct object_list *temp;
- if (memcmp(sha1, in_transit->item->sha1, 20)) {
+ if (hashcmp(sha1, in_transit->item->sha1)) {
/* we must have already fetched it to clean the queue */
return has_sha1_file(sha1) ? 0 : -1;
}
diff --git a/ssh-upload.c b/ssh-upload.c
index 2da66618fc..20b15eab57 100644
--- a/ssh-upload.c
+++ b/ssh-upload.c
@@ -15,9 +15,9 @@
#include <string.h>
static unsigned char local_version = 1;
-static unsigned char remote_version = 0;
+static unsigned char remote_version;
-static int verbose = 0;
+static int verbose;
static int serve_object(int fd_in, int fd_out) {
ssize_t size;
diff --git a/t/annotate-tests.sh b/t/annotate-tests.sh
index 8baf2fef69..b5ceba4acf 100644
--- a/t/annotate-tests.sh
+++ b/t/annotate-tests.sh
@@ -4,6 +4,7 @@
check_count () {
head=
case "$1" in -h) head="$2"; shift; shift ;; esac
+ echo "$PROG file $head" >&4
$PROG file $head >.result || return 1
cat .result | perl -e '
my %expect = (@ARGV);
diff --git a/t/t0000-basic.sh b/t/t0000-basic.sh
index 2c9bbb59b0..6aff0b808c 100755
--- a/t/t0000-basic.sh
+++ b/t/t0000-basic.sh
@@ -209,6 +209,28 @@ test_expect_success \
'validate object ID for a known tree.' \
'test "$ptree" = 3c5e5399f3a333eddecce7a9b9465b63f65f51e2'
+cat >badobjects <<EOF
+100644 blob 1000000000000000000000000000000000000000 dir/file1
+100644 blob 2000000000000000000000000000000000000000 dir/file2
+100644 blob 3000000000000000000000000000000000000000 dir/file3
+100644 blob 4000000000000000000000000000000000000000 dir/file4
+100644 blob 5000000000000000000000000000000000000000 dir/file5
+EOF
+
+rm .git/index
+test_expect_success \
+ 'put invalid objects into the index.' \
+ 'git-update-index --index-info < badobjects'
+
+test_expect_failure \
+ 'writing this tree without --missing-ok.' \
+ 'git-write-tree'
+
+test_expect_success \
+ 'writing this tree with --missing-ok.' \
+ 'git-write-tree --missing-ok'
+
+
################################################################
rm .git/index
test_expect_success \
diff --git a/t/t1004-read-tree-m-u-wf.sh b/t/t1004-read-tree-m-u-wf.sh
new file mode 100755
index 0000000000..018fbea450
--- /dev/null
+++ b/t/t1004-read-tree-m-u-wf.sh
@@ -0,0 +1,53 @@
+#!/bin/sh
+
+test_description='read-tree -m -u checks working tree files'
+
+. ./test-lib.sh
+
+# two-tree test
+
+test_expect_success 'two-way setup' '
+
+ echo >file1 file one &&
+ echo >file2 file two &&
+ git update-index --add file1 file2 &&
+ git commit -m initial &&
+
+ git branch side &&
+ git tag -f branch-point &&
+
+ echo file2 is not tracked on the master anymore &&
+ rm -f file2 &&
+ git update-index --remove file2 &&
+ git commit -a -m "master removes file2"
+'
+
+test_expect_success 'two-way not clobbering' '
+
+ echo >file2 master creates untracked file2 &&
+ if err=`git read-tree -m -u master side 2>&1`
+ then
+ echo should have complained
+ false
+ else
+ echo "happy to see $err"
+ fi
+'
+
+# three-tree test
+
+test_expect_success 'three-way not complaining' '
+
+ rm -f file2 &&
+ git checkout side &&
+ echo >file3 file three &&
+ git update-index --add file3 &&
+ git commit -a -m "side adds file3" &&
+
+ git checkout master &&
+ echo >file2 file two is untracked on the master side &&
+
+ git-read-tree -m -u branch-point master side
+'
+
+test_done
diff --git a/t/t1200-tutorial.sh b/t/t1200-tutorial.sh
index c7db20e7f3..0272dd4293 100755
--- a/t/t1200-tutorial.sh
+++ b/t/t1200-tutorial.sh
@@ -3,7 +3,7 @@
# Copyright (c) 2005 Johannes Schindelin
#
-test_description='Test git-rev-parse with different parent options'
+test_description='A simple turial in the form of a test case'
. ./test-lib.sh
diff --git a/t/t1400-update-ref.sh b/t/t1400-update-ref.sh
index ddc80bbeae..6a917f2ff4 100755
--- a/t/t1400-update-ref.sh
+++ b/t/t1400-update-ref.sh
@@ -19,51 +19,48 @@ n=$n_dir/fixes
test_expect_success \
"create $m" \
- 'git-update-ref $m $A &&
- test $A = $(cat .git/$m)'
+ "git-update-ref $m $A &&
+ test $A"' = $(cat .git/'"$m"')'
test_expect_success \
"create $m" \
- 'git-update-ref $m $B $A &&
- test $B = $(cat .git/$m)'
+ "git-update-ref $m $B $A &&
+ test $B"' = $(cat .git/'"$m"')'
rm -f .git/$m
test_expect_success \
"fail to create $n" \
- 'touch .git/$n_dir
- git-update-ref $n $A >out 2>err
- test $? = 1 &&
- test "" = "$(cat out)" &&
- grep "error: unable to resolve reference" err &&
- grep $n err'
+ "touch .git/$n_dir
+ git-update-ref $n $A >out 2>err"'
+ test $? != 0'
rm -f .git/$n_dir out err
test_expect_success \
"create $m (by HEAD)" \
- 'git-update-ref HEAD $A &&
- test $A = $(cat .git/$m)'
+ "git-update-ref HEAD $A &&
+ test $A"' = $(cat .git/'"$m"')'
test_expect_success \
"create $m (by HEAD)" \
- 'git-update-ref HEAD $B $A &&
- test $B = $(cat .git/$m)'
+ "git-update-ref HEAD $B $A &&
+ test $B"' = $(cat .git/'"$m"')'
rm -f .git/$m
test_expect_failure \
'(not) create HEAD with old sha1' \
- 'git-update-ref HEAD $A $B'
+ "git-update-ref HEAD $A $B"
test_expect_failure \
"(not) prior created .git/$m" \
- 'test -f .git/$m'
+ "test -f .git/$m"
rm -f .git/$m
test_expect_success \
"create HEAD" \
- 'git-update-ref HEAD $A'
+ "git-update-ref HEAD $A"
test_expect_failure \
'(not) change HEAD with wrong SHA1' \
- 'git-update-ref HEAD $B $Z'
+ "git-update-ref HEAD $B $Z"
test_expect_failure \
"(not) changed .git/$m" \
- 'test $B = $(cat .git/$m)'
+ "test $B"' = $(cat .git/'"$m"')'
rm -f .git/$m
mkdir -p .git/logs/refs/heads
@@ -71,18 +68,18 @@ touch .git/logs/refs/heads/master
test_expect_success \
"create $m (logged by touch)" \
'GIT_COMMITTER_DATE="2005-05-26 23:30" \
- git-update-ref HEAD $A -m "Initial Creation" &&
- test $A = $(cat .git/$m)'
+ git-update-ref HEAD '"$A"' -m "Initial Creation" &&
+ test '"$A"' = $(cat .git/'"$m"')'
test_expect_success \
"update $m (logged by touch)" \
'GIT_COMMITTER_DATE="2005-05-26 23:31" \
- git-update-ref HEAD $B $A -m "Switch" &&
- test $B = $(cat .git/$m)'
+ git-update-ref HEAD'" $B $A "'-m "Switch" &&
+ test '"$B"' = $(cat .git/'"$m"')'
test_expect_success \
"set $m (logged by touch)" \
'GIT_COMMITTER_DATE="2005-05-26 23:41" \
- git-update-ref HEAD $A &&
- test $A = $(cat .git/$m)'
+ git-update-ref HEAD'" $A &&
+ test $A"' = $(cat .git/'"$m"')'
cat >expect <<EOF
$Z $A $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150200 +0000 Initial Creation
@@ -91,7 +88,7 @@ $B $A $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150860 +0000
EOF
test_expect_success \
"verifying $m's log" \
- 'diff expect .git/logs/$m'
+ "diff expect .git/logs/$m"
rm -rf .git/$m .git/logs expect
test_expect_success \
@@ -102,18 +99,18 @@ test_expect_success \
test_expect_success \
"create $m (logged by config)" \
'GIT_COMMITTER_DATE="2005-05-26 23:32" \
- git-update-ref HEAD $A -m "Initial Creation" &&
- test $A = $(cat .git/$m)'
+ git-update-ref HEAD'" $A "'-m "Initial Creation" &&
+ test '"$A"' = $(cat .git/'"$m"')'
test_expect_success \
"update $m (logged by config)" \
'GIT_COMMITTER_DATE="2005-05-26 23:33" \
- git-update-ref HEAD $B $A -m "Switch" &&
- test $B = $(cat .git/$m)'
+ git-update-ref HEAD'" $B $A "'-m "Switch" &&
+ test '"$B"' = $(cat .git/'"$m"')'
test_expect_success \
"set $m (logged by config)" \
'GIT_COMMITTER_DATE="2005-05-26 23:43" \
- git-update-ref HEAD $A &&
- test $A = $(cat .git/$m)'
+ git-update-ref HEAD '"$A &&
+ test $A"' = $(cat .git/'"$m"')'
cat >expect <<EOF
$Z $A $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150320 +0000 Initial Creation
@@ -140,50 +137,50 @@ test_expect_success \
'Query "master@{May 25 2005}" (before history)' \
'rm -f o e
git-rev-parse --verify "master@{May 25 2005}" >o 2>e &&
- test $C = $(cat o) &&
- test "warning: Log .git/logs/$m only goes back to $ed." = "$(cat e)"'
+ test '"$C"' = $(cat o) &&
+ test "warning: Log .git/logs/'"$m only goes back to $ed"'." = "$(cat e)"'
test_expect_success \
"Query master@{2005-05-25} (before history)" \
'rm -f o e
git-rev-parse --verify master@{2005-05-25} >o 2>e &&
- test $C = $(cat o) &&
- echo test "warning: Log .git/logs/$m only goes back to $ed." = "$(cat e)"'
+ test '"$C"' = $(cat o) &&
+ echo test "warning: Log .git/logs/'"$m only goes back to $ed"'." = "$(cat e)"'
test_expect_success \
'Query "master@{May 26 2005 23:31:59}" (1 second before history)' \
'rm -f o e
git-rev-parse --verify "master@{May 26 2005 23:31:59}" >o 2>e &&
- test $C = $(cat o) &&
- test "warning: Log .git/logs/$m only goes back to $ed." = "$(cat e)"'
+ test '"$C"' = $(cat o) &&
+ test "warning: Log .git/logs/'"$m only goes back to $ed"'." = "$(cat e)"'
test_expect_success \
'Query "master@{May 26 2005 23:32:00}" (exactly history start)' \
'rm -f o e
git-rev-parse --verify "master@{May 26 2005 23:32:00}" >o 2>e &&
- test $A = $(cat o) &&
+ test '"$A"' = $(cat o) &&
test "" = "$(cat e)"'
test_expect_success \
'Query "master@{2005-05-26 23:33:01}" (middle of history with gap)' \
'rm -f o e
git-rev-parse --verify "master@{2005-05-26 23:33:01}" >o 2>e &&
- test $B = $(cat o) &&
- test "warning: Log .git/logs/$m has gap after $gd." = "$(cat e)"'
+ test '"$B"' = $(cat o) &&
+ test "warning: Log .git/logs/'"$m has gap after $gd"'." = "$(cat e)"'
test_expect_success \
'Query "master@{2005-05-26 23:38:00}" (middle of history)' \
'rm -f o e
git-rev-parse --verify "master@{2005-05-26 23:38:00}" >o 2>e &&
- test $Z = $(cat o) &&
+ test '"$Z"' = $(cat o) &&
test "" = "$(cat e)"'
test_expect_success \
'Query "master@{2005-05-26 23:43:00}" (exact end of history)' \
'rm -f o e
git-rev-parse --verify "master@{2005-05-26 23:43:00}" >o 2>e &&
- test $E = $(cat o) &&
+ test '"$E"' = $(cat o) &&
test "" = "$(cat e)"'
test_expect_success \
'Query "master@{2005-05-28}" (past end of history)' \
'rm -f o e
git-rev-parse --verify "master@{2005-05-28}" >o 2>e &&
- test $D = $(cat o) &&
- test "warning: Log .git/logs/$m unexpectedly ended on $ld." = "$(cat e)"'
+ test '"$D"' = $(cat o) &&
+ test "warning: Log .git/logs/'"$m unexpectedly ended on $ld"'." = "$(cat e)"'
rm -f .git/$m .git/logs/$m expect
@@ -221,7 +218,7 @@ $h_FIXED $h_MERGED $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117151100 +0000 c
EOF
test_expect_success \
'git-commit logged updates' \
- 'diff expect .git/logs/$m'
+ "diff expect .git/logs/$m"
unset h_TEST h_OTHER h_FIXED h_MERGED
test_expect_success \
diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh
index 5b04efc89d..acb54b6a07 100755
--- a/t/t3200-branch.sh
+++ b/t/t3200-branch.sh
@@ -17,13 +17,10 @@ test_expect_success \
git-commit -m "Initial commit." &&
HEAD=$(git-rev-parse --verify HEAD)'
-test_expect_success \
- 'git branch --help should return success now.' \
- 'git-branch --help'
-
test_expect_failure \
'git branch --help should not have created a bogus branch' \
- 'test -f .git/refs/heads/--help'
+ 'git-branch --help </dev/null >/dev/null 2>/dev/null || :
+ test -f .git/refs/heads/--help'
test_expect_success \
'git branch abc should create a branch' \
@@ -34,7 +31,7 @@ test_expect_success \
'git-branch a/b/c && test -f .git/refs/heads/a/b/c'
cat >expect <<EOF
-0000000000000000000000000000000000000000 $HEAD $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150200 +0000 branch: Created from HEAD
+0000000000000000000000000000000000000000 $HEAD $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150200 +0000 branch: Created from master
EOF
test_expect_success \
'git branch -l d/e/f should create a branch and a log' \
@@ -61,4 +58,16 @@ test_expect_success \
test -f .git/logs/refs/heads/g/h/i &&
diff expect .git/logs/refs/heads/g/h/i'
+test_expect_success \
+ 'git branch j/k should work after branch j has been deleted' \
+ 'git-branch j &&
+ git-branch -d j &&
+ git-branch j/k'
+
+test_expect_success \
+ 'git branch l should work after branch l/m has been deleted' \
+ 'git-branch l/m &&
+ git-branch -d l/m &&
+ git-branch l'
+
test_done
diff --git a/t/t3210-pack-refs.sh b/t/t3210-pack-refs.sh
new file mode 100755
index 0000000000..b1e9f2eed2
--- /dev/null
+++ b/t/t3210-pack-refs.sh
@@ -0,0 +1,99 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Amos Waterland
+# Copyright (c) 2006 Christian Couder
+#
+
+test_description='git pack-refs should not change the branch semantic
+
+This test runs git pack-refs and git show-ref and checks that the branch
+semantic is still the same.
+'
+. ./test-lib.sh
+
+echo '[core] logallrefupdates = true' >>.git/config
+
+test_expect_success \
+ 'prepare a trivial repository' \
+ 'echo Hello > A &&
+ git-update-index --add A &&
+ git-commit -m "Initial commit." &&
+ HEAD=$(git-rev-parse --verify HEAD)'
+
+SHA1=
+
+test_expect_success \
+ 'see if git show-ref works as expected' \
+ 'git-branch a &&
+ SHA1=`cat .git/refs/heads/a` &&
+ echo "$SHA1 refs/heads/a" >expect &&
+ git-show-ref a >result &&
+ diff expect result'
+
+test_expect_success \
+ 'see if a branch still exists when packed' \
+ 'git-branch b &&
+ git-pack-refs --all &&
+ rm .git/refs/heads/b &&
+ echo "$SHA1 refs/heads/b" >expect &&
+ git-show-ref b >result &&
+ diff expect result'
+
+test_expect_failure \
+ 'git branch c/d should barf if branch c exists' \
+ 'git-branch c &&
+ git-pack-refs --all &&
+ rm .git/refs/heads/c &&
+ git-branch c/d'
+
+test_expect_success \
+ 'see if a branch still exists after git pack-refs --prune' \
+ 'git-branch e &&
+ git-pack-refs --all --prune &&
+ echo "$SHA1 refs/heads/e" >expect &&
+ git-show-ref e >result &&
+ diff expect result'
+
+test_expect_failure \
+ 'see if git pack-refs --prune remove ref files' \
+ 'git-branch f &&
+ git-pack-refs --all --prune &&
+ ls .git/refs/heads/f'
+
+test_expect_success \
+ 'git branch g should work when git branch g/h has been deleted' \
+ 'git-branch g/h &&
+ git-pack-refs --all --prune &&
+ git-branch -d g/h &&
+ git-branch g &&
+ git-pack-refs --all &&
+ git-branch -d g'
+
+test_expect_failure \
+ 'git branch i/j/k should barf if branch i exists' \
+ 'git-branch i &&
+ git-pack-refs --all --prune &&
+ git-branch i/j/k'
+
+test_expect_success \
+ 'test git branch k after branch k/l/m and k/lm have been deleted' \
+ 'git-branch k/l &&
+ git-branch k/lm &&
+ git-branch -d k/l &&
+ git-branch k/l/m &&
+ git-branch -d k/l/m &&
+ git-branch -d k/lm &&
+ git-branch k'
+
+test_expect_success \
+ 'test git branch n after some branch deletion and pruning' \
+ 'git-branch n/o &&
+ git-branch n/op &&
+ git-branch -d n/o &&
+ git-branch n/o/p &&
+ git-branch -d n/op &&
+ git-pack-refs --all --prune &&
+ git-branch -d n/o/p &&
+ git-branch n'
+
+test_done
diff --git a/t/t3401-rebase-partial.sh b/t/t3401-rebase-partial.sh
index 360a67060e..8b19d3ccea 100755
--- a/t/t3401-rebase-partial.sh
+++ b/t/t3401-rebase-partial.sh
@@ -52,13 +52,10 @@ test_expect_success \
'rebase topic branch against new master and check git-am did not get halted' \
'git-rebase master && test ! -d .dotest'
-if test -z "$no_python"
-then
- test_expect_success \
+test_expect_success \
'rebase --merge topic branch that was partially merged upstream' \
'git-checkout -f my-topic-branch-merge &&
git-rebase --merge master-merge &&
test ! -d .git/.dotest-merge'
-fi
test_done
diff --git a/t/t3402-rebase-merge.sh b/t/t3402-rebase-merge.sh
index d34c6cf6f3..0779aaa9ab 100755
--- a/t/t3402-rebase-merge.sh
+++ b/t/t3402-rebase-merge.sh
@@ -7,12 +7,6 @@ test_description='git rebase --merge test'
. ./test-lib.sh
-if test "$no_python"; then
- echo "Skipping: no python => no recursive merge"
- test_done
- exit 0
-fi
-
T="A quick brown fox
jumps over the lazy dog."
for i in 1 2 3 4 5 6 7 8 9 10
diff --git a/t/t3403-rebase-skip.sh b/t/t3403-rebase-skip.sh
index 8ab63c5276..977c498f00 100755
--- a/t/t3403-rebase-skip.sh
+++ b/t/t3403-rebase-skip.sh
@@ -10,12 +10,6 @@ test_description='git rebase --merge --skip tests'
# we assume the default git-am -3 --skip strategy is tested independently
# and always works :)
-if test "$no_python"; then
- echo "Skipping: no python => no recursive merge"
- test_done
- exit 0
-fi
-
test_expect_success setup '
echo hello > hello &&
git add hello &&
@@ -37,7 +31,9 @@ test_expect_success setup '
git branch skip-merge skip-reference
'
-test_expect_failure 'rebase with git am -3 (default)' 'git rebase master'
+test_expect_failure 'rebase with git am -3 (default)' '
+ git rebase master
+'
test_expect_success 'rebase --skip with am -3' '
git reset --hard HEAD &&
diff --git a/t/t3700-add.sh b/t/t3700-add.sh
index 6cd05c3d90..c09c53f20b 100755
--- a/t/t3700-add.sh
+++ b/t/t3700-add.sh
@@ -19,4 +19,36 @@ test_expect_success \
'Test that "git-add -- -q" works' \
'touch -- -q && git-add -- -q'
+test_expect_success \
+ 'git-add: Test that executable bit is not used if core.filemode=0' \
+ 'git repo-config core.filemode 0 &&
+ echo foo >xfoo1 &&
+ chmod 755 xfoo1 &&
+ git-add xfoo1 &&
+ case "`git-ls-files --stage xfoo1`" in
+ 100644" "*xfoo1) echo ok;;
+ *) echo fail; git-ls-files --stage xfoo1; (exit 1);;
+ esac'
+
+test_expect_success \
+ 'git-update-index --add: Test that executable bit is not used...' \
+ 'git repo-config core.filemode 0 &&
+ echo foo >xfoo2 &&
+ chmod 755 xfoo2 &&
+ git-update-index --add xfoo2 &&
+ case "`git-ls-files --stage xfoo2`" in
+ 100644" "*xfoo2) echo ok;;
+ *) echo fail; git-ls-files --stage xfoo2; (exit 1);;
+ esac'
+
+test_expect_success \
+ 'git-update-index --add: Test that executable bit is not used...' \
+ 'git repo-config core.filemode 0 &&
+ ln -s xfoo2 xfoo3 &&
+ git-update-index --add xfoo3 &&
+ case "`git-ls-files --stage xfoo3`" in
+ 120000" "*xfoo3) echo ok;;
+ *) echo fail; git-ls-files --stage xfoo3; (exit 1);;
+ esac'
+
test_done
diff --git a/t/t4013/diff.diff-tree_--pretty_--root_--summary_initial b/t/t4013/diff.diff-tree_--pretty_--root_--summary_initial
index ea48205537..58e5f74aea 100644
--- a/t/t4013/diff.diff-tree_--pretty_--root_--summary_initial
+++ b/t/t4013/diff.diff-tree_--pretty_--root_--summary_initial
@@ -5,7 +5,7 @@ Date: Mon Jun 26 00:00:00 2006 +0000
Initial
- create mode 040000 dir
+ create mode 100644 dir/sub
create mode 100644 file0
create mode 100644 file2
$
diff --git a/t/t4015-diff-whitespace.sh b/t/t4015-diff-whitespace.sh
new file mode 100755
index 0000000000..1bc5b7a412
--- /dev/null
+++ b/t/t4015-diff-whitespace.sh
@@ -0,0 +1,122 @@
+#!/bin/sh
+#
+# Copyright (c) 2006 Johannes E. Schindelin
+#
+
+test_description='Test special whitespace in diff engine.
+
+'
+. ./test-lib.sh
+. ../diff-lib.sh
+
+# Ray Lehtiniemi's example
+
+cat << EOF > x
+do {
+ nothing;
+} while (0);
+EOF
+
+git-update-index --add x
+
+cat << EOF > x
+do
+{
+ nothing;
+}
+while (0);
+EOF
+
+cat << EOF > expect
+diff --git a/x b/x
+index adf3937..6edc172 100644
+--- a/x
++++ b/x
+@@ -1,3 +1,5 @@
+-do {
++do
++{
+ nothing;
+-} while (0);
++}
++while (0);
+EOF
+
+git-diff > out
+test_expect_success "Ray's example without options" 'diff -u expect out'
+
+git-diff -w > out
+test_expect_success "Ray's example with -w" 'diff -u expect out'
+
+git-diff -b > out
+test_expect_success "Ray's example with -b" 'diff -u expect out'
+
+tr 'Q' '\015' << EOF > x
+whitespace at beginning
+whitespace change
+whitespace in the middle
+whitespace at end
+unchanged line
+CR at endQ
+EOF
+
+git-update-index x
+
+cat << EOF > x
+ whitespace at beginning
+whitespace change
+white space in the middle
+whitespace at end
+unchanged line
+CR at end
+EOF
+
+tr 'Q' '\015' << EOF > expect
+diff --git a/x b/x
+index d99af23..8b32fb5 100644
+--- a/x
++++ b/x
+@@ -1,6 +1,6 @@
+-whitespace at beginning
+-whitespace change
+-whitespace in the middle
+-whitespace at end
++ whitespace at beginning
++whitespace change
++white space in the middle
++whitespace at end
+ unchanged line
+-CR at endQ
++CR at end
+EOF
+git-diff > out
+test_expect_success 'another test, without options' 'diff -u expect out'
+
+cat << EOF > expect
+diff --git a/x b/x
+index d99af23..8b32fb5 100644
+EOF
+git-diff -w > out
+test_expect_success 'another test, with -w' 'diff -u expect out'
+
+tr 'Q' '\015' << EOF > expect
+diff --git a/x b/x
+index d99af23..8b32fb5 100644
+--- a/x
++++ b/x
+@@ -1,6 +1,6 @@
+-whitespace at beginning
++ whitespace at beginning
+ whitespace change
+-whitespace in the middle
+-whitespace at end
++white space in the middle
++whitespace at end
+ unchanged line
+-CR at endQ
++CR at end
+EOF
+git-diff -b > out
+test_expect_success 'another test, with -b' 'diff -u expect out'
+
+test_done
diff --git a/t/t4103-apply-binary.sh b/t/t4103-apply-binary.sh
index ff052699a2..e2b1124c78 100755
--- a/t/t4103-apply-binary.sh
+++ b/t/t4103-apply-binary.sh
@@ -94,11 +94,11 @@ test_expect_failure 'apply binary diff (copy) -- should fail.' \
'do_reset
git-apply --index C.diff'
-test_expect_failure 'apply binary diff without replacement -- should fail.' \
+test_expect_success 'apply binary diff without replacement.' \
'do_reset
git-apply BF.diff'
-test_expect_failure 'apply binary diff without replacement (copy) -- should fail.' \
+test_expect_success 'apply binary diff without replacement (copy).' \
'do_reset
git-apply CF.diff'
diff --git a/t/t4104-apply-boundary.sh b/t/t4104-apply-boundary.sh
new file mode 100755
index 0000000000..2ff800c23f
--- /dev/null
+++ b/t/t4104-apply-boundary.sh
@@ -0,0 +1,115 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+
+test_description='git-apply boundary tests
+
+'
+. ./test-lib.sh
+
+L="c d e f g h i j k l m n o p q r s t u v w x"
+
+test_expect_success setup '
+ for i in b '"$L"' y
+ do
+ echo $i
+ done >victim &&
+ cat victim >original &&
+ git update-index --add victim &&
+
+ : add to the head
+ for i in a b '"$L"' y
+ do
+ echo $i
+ done >victim &&
+ cat victim >add-a-expect &&
+ git diff victim >add-a-patch.with &&
+ git diff --unified=0 >add-a-patch.without &&
+
+ : modify at the head
+ for i in a '"$L"' y
+ do
+ echo $i
+ done >victim &&
+ cat victim >mod-a-expect &&
+ git diff victim >mod-a-patch.with &&
+ git diff --unified=0 >mod-a-patch.without &&
+
+ : remove from the head
+ for i in '"$L"' y
+ do
+ echo $i
+ done >victim &&
+ cat victim >del-a-expect &&
+ git diff victim >del-a-patch.with
+ git diff --unified=0 >del-a-patch.without &&
+
+ : add to the tail
+ for i in b '"$L"' y z
+ do
+ echo $i
+ done >victim &&
+ cat victim >add-z-expect &&
+ git diff victim >add-z-patch.with &&
+ git diff --unified=0 >add-z-patch.without &&
+
+ : modify at the tail
+ for i in a '"$L"' y
+ do
+ echo $i
+ done >victim &&
+ cat victim >mod-z-expect &&
+ git diff victim >mod-z-patch.with &&
+ git diff --unified=0 >mod-z-patch.without &&
+
+ : remove from the tail
+ for i in b '"$L"'
+ do
+ echo $i
+ done >victim &&
+ cat victim >del-z-expect &&
+ git diff victim >del-z-patch.with
+ git diff --unified=0 >del-z-patch.without &&
+
+ : done
+'
+
+for with in with without
+do
+ case "$with" in
+ with) u= ;;
+ without) u='--unidiff-zero ' ;;
+ esac
+ for kind in add-a add-z mod-a mod-z del-a del-z
+ do
+ test_expect_success "apply $kind-patch $with context" '
+ cat original >victim &&
+ git update-index victim &&
+ git apply --index '"$u$kind-patch.$with"' || {
+ cat '"$kind-patch.$with"'
+ (exit 1)
+ } &&
+ diff -u '"$kind"'-expect victim
+ '
+ done
+done
+
+for kind in add-a add-z mod-a mod-z del-a del-z
+do
+ rm -f $kind-ng.without
+ sed -e "s/^diff --git /diff /" \
+ -e '/^index /d' \
+ <$kind-patch.without >$kind-ng.without
+ test_expect_success "apply non-git $kind-patch without context" '
+ cat original >victim &&
+ git update-index victim &&
+ git apply --unidiff-zero --index '"$kind-ng.without"' || {
+ cat '"$kind-ng.without"'
+ (exit 1)
+ } &&
+ diff -u '"$kind"'-expect victim
+ '
+done
+
+test_done
diff --git a/t/t4116-apply-reverse.sh b/t/t4116-apply-reverse.sh
new file mode 100755
index 0000000000..74f5c2a575
--- /dev/null
+++ b/t/t4116-apply-reverse.sh
@@ -0,0 +1,85 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+
+test_description='git-apply in reverse
+
+'
+
+. ./test-lib.sh
+
+test_expect_success setup '
+
+ for i in a b c d e f g h i j k l m n; do echo $i; done >file1 &&
+ tr "[ijk]" '\''[\0\1\2]'\'' <file1 >file2 &&
+
+ git add file1 file2 &&
+ git commit -m initial &&
+ git tag initial &&
+
+ for i in a b c g h i J K L m o n p q; do echo $i; done >file1 &&
+ tr "[mon]" '\''[\0\1\2]'\'' <file1 >file2 &&
+
+ git commit -a -m second &&
+ git tag second &&
+
+ git diff --binary initial second >patch
+
+'
+
+test_expect_success 'apply in forward' '
+
+ T0=`git rev-parse "second^{tree}"` &&
+ git reset --hard initial &&
+ git apply --index --binary patch &&
+ T1=`git write-tree` &&
+ test "$T0" = "$T1"
+'
+
+test_expect_success 'apply in reverse' '
+
+ git reset --hard second &&
+ git apply --reverse --binary --index patch &&
+ git diff >diff &&
+ diff -u /dev/null diff
+
+'
+
+test_expect_success 'setup separate repository lacking postimage' '
+
+ git tar-tree initial initial | tar xf - &&
+ (
+ cd initial && git init-db && git add .
+ ) &&
+
+ git tar-tree second second | tar xf - &&
+ (
+ cd second && git init-db && git add .
+ )
+
+'
+
+test_expect_success 'apply in forward without postimage' '
+
+ T0=`git rev-parse "second^{tree}"` &&
+ (
+ cd initial &&
+ git apply --index --binary ../patch &&
+ T1=`git write-tree` &&
+ test "$T0" = "$T1"
+ )
+'
+
+test_expect_success 'apply in reverse without postimage' '
+
+ T0=`git rev-parse "initial^{tree}"` &&
+ (
+ cd second &&
+ git apply --index --binary --reverse ../patch &&
+ T1=`git write-tree` &&
+ test "$T0" = "$T1"
+ )
+'
+
+test_done
diff --git a/t/t4117-apply-reject.sh b/t/t4117-apply-reject.sh
new file mode 100755
index 0000000000..b4de075a3e
--- /dev/null
+++ b/t/t4117-apply-reject.sh
@@ -0,0 +1,157 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+
+test_description='git-apply with rejects
+
+'
+
+. ./test-lib.sh
+
+test_expect_success setup '
+ for i in 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
+ do
+ echo $i
+ done >file1 &&
+ cat file1 >saved.file1 &&
+ git update-index --add file1 &&
+ git commit -m initial &&
+
+ for i in 1 2 A B 4 5 6 7 8 9 10 11 12 C 13 14 15 16 17 18 19 20 D 21
+ do
+ echo $i
+ done >file1 &&
+ git diff >patch.1 &&
+ cat file1 >clean &&
+
+ for i in 1 E 2 3 4 5 6 7 8 9 10 11 12 C 13 14 15 16 17 18 19 20 F 21
+ do
+ echo $i
+ done >expected &&
+
+ mv file1 file2 &&
+ git update-index --add --remove file1 file2 &&
+ git diff -M HEAD >patch.2 &&
+
+ rm -f file1 file2 &&
+ mv saved.file1 file1 &&
+ git update-index --add --remove file1 file2 &&
+
+ for i in 1 E 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 F 21
+ do
+ echo $i
+ done >file1 &&
+
+ cat file1 >saved.file1
+'
+
+test_expect_success 'apply without --reject should fail' '
+
+ if git apply patch.1
+ then
+ echo "Eh? Why?"
+ exit 1
+ fi
+
+ diff -u file1 saved.file1
+'
+
+test_expect_success 'apply without --reject should fail' '
+
+ if git apply --verbose patch.1
+ then
+ echo "Eh? Why?"
+ exit 1
+ fi
+
+ diff -u file1 saved.file1
+'
+
+test_expect_success 'apply with --reject should fail but update the file' '
+
+ cat saved.file1 >file1 &&
+ rm -f file1.rej file2.rej &&
+
+ if git apply --reject patch.1
+ then
+ echo "succeeds with --reject?"
+ exit 1
+ fi
+
+ diff -u file1 expected &&
+
+ cat file1.rej &&
+
+ if test -f file2.rej
+ then
+ echo "file2 should not have been touched"
+ exit 1
+ fi
+'
+
+test_expect_success 'apply with --reject should fail but update the file' '
+
+ cat saved.file1 >file1 &&
+ rm -f file1.rej file2.rej file2 &&
+
+ if git apply --reject patch.2 >rejects
+ then
+ echo "succeeds with --reject?"
+ exit 1
+ fi
+
+ test -f file1 && {
+ echo "file1 still exists?"
+ exit 1
+ }
+ diff -u file2 expected &&
+
+ cat file2.rej &&
+
+ if test -f file1.rej
+ then
+ echo "file2 should not have been touched"
+ exit 1
+ fi
+
+'
+
+test_expect_success 'the same test with --verbose' '
+
+ cat saved.file1 >file1 &&
+ rm -f file1.rej file2.rej file2 &&
+
+ if git apply --reject --verbose patch.2 >rejects
+ then
+ echo "succeeds with --reject?"
+ exit 1
+ fi
+
+ test -f file1 && {
+ echo "file1 still exists?"
+ exit 1
+ }
+ diff -u file2 expected &&
+
+ cat file2.rej &&
+
+ if test -f file1.rej
+ then
+ echo "file2 should not have been touched"
+ exit 1
+ fi
+
+'
+
+test_expect_success 'apply cleanly with --verbose' '
+
+ git cat-file -p HEAD:file1 >file1 &&
+ rm -f file?.rej file2 &&
+
+ git apply --verbose patch.1 &&
+
+ diff -u file1 clean
+'
+
+test_done
diff --git a/t/t4118-apply-empty-context.sh b/t/t4118-apply-empty-context.sh
new file mode 100755
index 0000000000..7309422fe5
--- /dev/null
+++ b/t/t4118-apply-empty-context.sh
@@ -0,0 +1,55 @@
+#!/bin/sh
+#
+# Copyright (c) 2006 Junio C Hamano
+#
+
+test_description='git-apply with new style GNU diff with empty context
+
+'
+
+. ./test-lib.sh
+
+test_expect_success setup '
+ {
+ echo; echo;
+ echo A; echo B; echo C;
+ echo;
+ } >file1 &&
+ cat file1 >file1.orig &&
+ {
+ cat file1 &&
+ echo Q | tr -d "\\012"
+ } >file2 &&
+ cat file2 >file2.orig
+ git add file1 file2 &&
+ sed -e "/^B/d" <file1.orig >file1 &&
+ sed -e "/^B/d" <file2.orig >file2 &&
+ cat file1 >file1.mods &&
+ cat file2 >file2.mods &&
+ git diff |
+ sed -e "s/^ \$//" >diff.output
+'
+
+test_expect_success 'apply --numstat' '
+
+ git apply --numstat diff.output >actual &&
+ {
+ echo "0 1 file1" &&
+ echo "0 1 file2"
+ } >expect &&
+ diff -u expect actual
+
+'
+
+test_expect_success 'apply --apply' '
+
+ cat file1.orig >file1 &&
+ cat file2.orig >file2 &&
+ git update-index file1 file2 &&
+ git apply --index diff.output &&
+ diff -u file1.mods file1 &&
+ diff -u file2.mods file2
+'
+
+test_done
+
diff --git a/t/t5000-tar-tree.sh b/t/t5000-tar-tree.sh
index 278eb66701..cf08e9279c 100755
--- a/t/t5000-tar-tree.sh
+++ b/t/t5000-tar-tree.sh
@@ -26,6 +26,7 @@ commit id embedding:
. ./test-lib.sh
TAR=${TAR:-tar}
+UNZIP=${UNZIP:-unzip}
test_expect_success \
'populate workdir' \
@@ -95,4 +96,38 @@ test_expect_success \
'validate file contents with prefix' \
'diff -r a c/prefix/a'
+test_expect_success \
+ 'git-archive --format=zip' \
+ 'git-archive --format=zip HEAD >d.zip'
+
+test_expect_success \
+ 'extract ZIP archive' \
+ '(mkdir d && cd d && $UNZIP ../d.zip)'
+
+test_expect_success \
+ 'validate filenames' \
+ '(cd d/a && find .) | sort >d.lst &&
+ diff a.lst d.lst'
+
+test_expect_success \
+ 'validate file contents' \
+ 'diff -r a d/a'
+
+test_expect_success \
+ 'git-archive --format=zip with prefix' \
+ 'git-archive --format=zip --prefix=prefix/ HEAD >e.zip'
+
+test_expect_success \
+ 'extract ZIP archive with prefix' \
+ '(mkdir e && cd e && $UNZIP ../e.zip)'
+
+test_expect_success \
+ 'validate filenames with prefix' \
+ '(cd e/prefix/a && find .) | sort >e.lst &&
+ diff a.lst e.lst'
+
+test_expect_success \
+ 'validate file contents with prefix' \
+ 'diff -r a e/prefix/a'
+
test_done
diff --git a/t/t5400-send-pack.sh b/t/t5400-send-pack.sh
index f3694ac3c7..8afb899717 100755
--- a/t/t5400-send-pack.sh
+++ b/t/t5400-send-pack.sh
@@ -64,4 +64,18 @@ test_expect_success \
cmp victim/.git/refs/heads/master .git/refs/heads/master
'
+unset GIT_CONFIG GIT_CONFIG_LOCAL
+HOME=`pwd`/no-such-directory
+export HOME ;# this way we force the victim/.git/config to be used.
+
+test_expect_success \
+ 'pushing with --force should be denied with denyNonFastforwards' '
+ cd victim &&
+ git-repo-config receive.denyNonFastforwards true &&
+ cd .. &&
+ git-update-ref refs/heads/master master^ &&
+ git-send-pack --force ./victim/.git/ master &&
+ ! diff -u .git/refs/heads/master victim/.git/refs/heads/master
+'
+
test_done
diff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh
new file mode 100755
index 0000000000..a11ab0ad41
--- /dev/null
+++ b/t/t5510-fetch.sh
@@ -0,0 +1,85 @@
+#!/bin/sh
+# Copyright (c) 2006, Junio C Hamano.
+
+test_description='Per branch config variables affects "git fetch".
+
+'
+
+. ./test-lib.sh
+
+D=`pwd`
+
+test_expect_success setup '
+ echo >file original &&
+ git add file &&
+ git commit -a -m original'
+
+test_expect_success "clone and setup child repos" '
+ git clone . one &&
+ cd one &&
+ echo >file updated by one &&
+ git commit -a -m "updated by one" &&
+ cd .. &&
+ git clone . two &&
+ cd two &&
+ git repo-config branch.master.remote one &&
+ {
+ echo "URL: ../one/.git/"
+ echo "Pull: refs/heads/master:refs/heads/one"
+ } >.git/remotes/one
+ cd .. &&
+ git clone . three &&
+ cd three &&
+ git repo-config branch.master.remote two &&
+ git repo-config branch.master.merge refs/heads/one &&
+ {
+ echo "URL: ../two/.git/"
+ echo "Pull: refs/heads/master:refs/heads/two"
+ echo "Pull: refs/heads/one:refs/heads/one"
+ } >.git/remotes/two
+'
+
+test_expect_success "fetch test" '
+ cd "$D" &&
+ echo >file updated by origin &&
+ git commit -a -m "updated by origin" &&
+ cd two &&
+ git fetch &&
+ test -f .git/refs/heads/one &&
+ mine=`git rev-parse refs/heads/one` &&
+ his=`cd ../one && git rev-parse refs/heads/master` &&
+ test "z$mine" = "z$his"
+'
+
+test_expect_success "fetch test for-merge" '
+ cd "$D" &&
+ cd three &&
+ git fetch &&
+ test -f .git/refs/heads/two &&
+ test -f .git/refs/heads/one &&
+ master_in_two=`cd ../two && git rev-parse master` &&
+ one_in_two=`cd ../two && git rev-parse one` &&
+ {
+ echo "$master_in_two not-for-merge"
+ echo "$one_in_two "
+ } >expected &&
+ cut -f -2 .git/FETCH_HEAD >actual &&
+ diff expected actual'
+
+test_expect_success 'fetch following tags' '
+
+ cd "$D" &&
+ git tag -a -m 'annotated' anno HEAD &&
+ git tag light HEAD &&
+
+ mkdir four &&
+ cd four &&
+ git init-db &&
+
+ git fetch .. :track &&
+ git show-ref --verify refs/tags/anno &&
+ git show-ref --verify refs/tags/light
+
+'
+
+test_done
diff --git a/t/t5520-pull.sh b/t/t5520-pull.sh
new file mode 100755
index 0000000000..f841574573
--- /dev/null
+++ b/t/t5520-pull.sh
@@ -0,0 +1,33 @@
+#!/bin/sh
+
+test_description='pulling into void'
+
+. ./test-lib.sh
+
+D=`pwd`
+
+test_expect_success setup '
+
+ echo file >file &&
+ git add file &&
+ git commit -a -m original
+
+'
+
+test_expect_success 'pulling into void' '
+ mkdir cloned &&
+ cd cloned &&
+ git init-db &&
+ git pull ..
+'
+
+cd "$D"
+
+test_expect_success 'checking the results' '
+ test -f file &&
+ test -f cloned/file &&
+ diff file cloned/file
+'
+
+test_done
+
diff --git a/t/t5600-clone-fail-cleanup.sh b/t/t5600-clone-fail-cleanup.sh
index 0c6a363be9..041be04f5c 100755
--- a/t/t5600-clone-fail-cleanup.sh
+++ b/t/t5600-clone-fail-cleanup.sh
@@ -25,6 +25,12 @@ test_create_repo foo
# clone doesn't like it if there is no HEAD. Is that a bug?
(cd foo && touch file && git add file && git commit -m 'add file' >/dev/null 2>&1)
+# source repository given to git-clone should be relative to the
+# current path not to the target dir
+test_expect_failure \
+ 'clone of non-existent (relative to $PWD) source should fail' \
+ 'git-clone ../foo baz'
+
test_expect_success \
'clone should work now that source exists' \
'git-clone foo bar'
diff --git a/t/t5710-info-alternate.sh b/t/t5710-info-alternate.sh
index 2e1b48a89b..b9f6d96363 100755
--- a/t/t5710-info-alternate.sh
+++ b/t/t5710-info-alternate.sh
@@ -58,6 +58,8 @@ test_expect_failure 'creating too deep nesting' \
git clone -l -s D E &&
git clone -l -s E F &&
git clone -l -s F G &&
+git clone -l -s G H &&
+cd H &&
test_valid_repo'
cd "$base_dir"
diff --git a/t/t6001-rev-list-graft.sh b/t/t6001-rev-list-graft.sh
new file mode 100755
index 0000000000..b2131cdacd
--- /dev/null
+++ b/t/t6001-rev-list-graft.sh
@@ -0,0 +1,113 @@
+#!/bin/sh
+
+test_description='Revision traversal vs grafts and path limiter'
+
+. ./test-lib.sh
+
+test_expect_success setup '
+ mkdir subdir &&
+ echo >fileA fileA &&
+ echo >subdir/fileB fileB &&
+ git add fileA subdir/fileB &&
+ git commit -a -m "Initial in one history." &&
+ A0=`git rev-parse --verify HEAD` &&
+
+ echo >fileA fileA modified &&
+ git commit -a -m "Second in one history." &&
+ A1=`git rev-parse --verify HEAD` &&
+
+ echo >subdir/fileB fileB modified &&
+ git commit -a -m "Third in one history." &&
+ A2=`git rev-parse --verify HEAD` &&
+
+ rm -f .git/refs/heads/master .git/index &&
+
+ echo >fileA fileA again &&
+ echo >subdir/fileB fileB again &&
+ git add fileA subdir/fileB &&
+ git commit -a -m "Initial in alternate history." &&
+ B0=`git rev-parse --verify HEAD` &&
+
+ echo >fileA fileA modified in alternate history &&
+ git commit -a -m "Second in alternate history." &&
+ B1=`git rev-parse --verify HEAD` &&
+
+ echo >subdir/fileB fileB modified in alternate history &&
+ git commit -a -m "Third in alternate history." &&
+ B2=`git rev-parse --verify HEAD` &&
+ : done
+'
+
+check () {
+ type=$1
+ shift
+
+ arg=
+ which=arg
+ rm -f test.expect
+ for a
+ do
+ if test "z$a" = z--
+ then
+ which=expect
+ child=
+ continue
+ fi
+ if test "$which" = arg
+ then
+ arg="$arg$a "
+ continue
+ fi
+ if test "$type" = basic
+ then
+ echo "$a"
+ else
+ if test "z$child" != z
+ then
+ echo "$child $a"
+ fi
+ child="$a"
+ fi
+ done >test.expect
+ if test "$type" != basic && test "z$child" != z
+ then
+ echo >>test.expect $child
+ fi
+ if test $type = basic
+ then
+ git rev-list $arg >test.actual
+ elif test $type = parents
+ then
+ git rev-list --parents $arg >test.actual
+ elif test $type = parents-raw
+ then
+ git rev-list --parents --pretty=raw $arg |
+ sed -n -e 's/^commit //p' >test.actual
+ fi
+ diff test.expect test.actual
+}
+
+for type in basic parents parents-raw
+do
+ test_expect_success 'without grafts' "
+ rm -f .git/info/grafts
+ check $type $B2 -- $B2 $B1 $B0
+ "
+
+ test_expect_success 'with grafts' "
+ echo '$B0 $A2' >.git/info/grafts
+ check $type $B2 -- $B2 $B1 $B0 $A2 $A1 $A0
+ "
+
+ test_expect_success 'without grafts, with pathlimit' "
+ rm -f .git/info/grafts
+ check $type $B2 subdir -- $B2 $B0
+ "
+
+ test_expect_success 'with grafts, with pathlimit' "
+ echo '$B0 $A2' >.git/info/grafts
+ check $type $B2 subdir -- $B2 $B0 $A2 $A0
+ "
+
+done
+test_done
diff --git a/t/t6021-merge-criss-cross.sh b/t/t6021-merge-criss-cross.sh
index 8f7366da8d..499cafb882 100755
--- a/t/t6021-merge-criss-cross.sh
+++ b/t/t6021-merge-criss-cross.sh
@@ -10,12 +10,6 @@
test_description='Test criss-cross merge'
. ./test-lib.sh
-if test "$no_python"; then
- echo "Skipping: no python => no recursive merge"
- test_done
- exit 0
-fi
-
test_expect_success 'prepare repository' \
'echo "1
2
diff --git a/t/t6022-merge-rename.sh b/t/t6022-merge-rename.sh
index 5ac25647ab..b608e202c1 100755
--- a/t/t6022-merge-rename.sh
+++ b/t/t6022-merge-rename.sh
@@ -3,12 +3,6 @@
test_description='Merge-recursive merging renames'
. ./test-lib.sh
-if test "$no_python"; then
- echo "Skipping: no python => no recursive merge"
- test_done
- exit 0
-fi
-
test_expect_success setup \
'
cat >A <<\EOF &&
@@ -48,15 +42,20 @@ O OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO
EOF
git add A M &&
-git commit -m initial &&
+git commit -m "initial has A and M" &&
git branch white &&
git branch red &&
git branch blue &&
+git branch yellow &&
sed -e "/^g /s/.*/g : master changes a line/" <A >A+ &&
mv A+ A &&
git commit -a -m "master updates A" &&
+git checkout yellow &&
+rm -f M &&
+git commit -a -m "yellow removes M" &&
+
git checkout white &&
sed -e "/^g /s/.*/g : white changes a line/" <A >B &&
sed -e "/^G /s/.*/G : colored branch changes a line/" <M >N &&
@@ -85,27 +84,27 @@ test_expect_success 'pull renaming branch into unrenaming one' \
git show-branch
git pull . white && {
echo "BAD: should have conflicted"
- exit 1
+ return 1
}
git ls-files -s
test "$(git ls-files -u B | wc -l)" -eq 3 || {
echo "BAD: should have left stages for B"
- exit 1
+ return 1
}
test "$(git ls-files -s N | wc -l)" -eq 1 || {
echo "BAD: should have merged N"
- exit 1
+ return 1
}
sed -ne "/^g/{
p
q
}" B | grep master || {
echo "BAD: should have listed our change first"
- exit 1
+ return 1
}
test "$(git diff white N | wc -l)" -eq 0 || {
echo "BAD: should have taken colored branch"
- exit 1
+ return 1
}
'
@@ -116,26 +115,26 @@ test_expect_success 'pull renaming branch into another renaming one' \
git checkout red
git pull . white && {
echo "BAD: should have conflicted"
- exit 1
+ return 1
}
test "$(git ls-files -u B | wc -l)" -eq 3 || {
echo "BAD: should have left stages"
- exit 1
+ return 1
}
test "$(git ls-files -s N | wc -l)" -eq 1 || {
echo "BAD: should have merged N"
- exit 1
+ return 1
}
sed -ne "/^g/{
p
q
}" B | grep red || {
echo "BAD: should have listed our change first"
- exit 1
+ return 1
}
test "$(git diff white N | wc -l)" -eq 0 || {
echo "BAD: should have taken colored branch"
- exit 1
+ return 1
}
'
@@ -145,26 +144,26 @@ test_expect_success 'pull unrenaming branch into renaming one' \
git show-branch
git pull . master && {
echo "BAD: should have conflicted"
- exit 1
+ return 1
}
test "$(git ls-files -u B | wc -l)" -eq 3 || {
echo "BAD: should have left stages"
- exit 1
+ return 1
}
test "$(git ls-files -s N | wc -l)" -eq 1 || {
echo "BAD: should have merged N"
- exit 1
+ return 1
}
sed -ne "/^g/{
p
q
}" B | grep red || {
echo "BAD: should have listed our change first"
- exit 1
+ return 1
}
test "$(git diff white N | wc -l)" -eq 0 || {
echo "BAD: should have taken colored branch"
- exit 1
+ return 1
}
'
@@ -174,35 +173,149 @@ test_expect_success 'pull conflicting renames' \
git show-branch
git pull . blue && {
echo "BAD: should have conflicted"
- exit 1
+ return 1
}
test "$(git ls-files -u A | wc -l)" -eq 1 || {
echo "BAD: should have left a stage"
- exit 1
+ return 1
}
test "$(git ls-files -u B | wc -l)" -eq 1 || {
echo "BAD: should have left a stage"
- exit 1
+ return 1
}
test "$(git ls-files -u C | wc -l)" -eq 1 || {
echo "BAD: should have left a stage"
- exit 1
+ return 1
}
test "$(git ls-files -s N | wc -l)" -eq 1 || {
echo "BAD: should have merged N"
- exit 1
+ return 1
}
sed -ne "/^g/{
p
q
}" B | grep red || {
echo "BAD: should have listed our change first"
- exit 1
+ return 1
}
test "$(git diff white N | wc -l)" -eq 0 || {
echo "BAD: should have taken colored branch"
- exit 1
+ return 1
+ }
+'
+
+test_expect_success 'interference with untracked working tree file' '
+
+ git reset --hard
+ git show-branch
+ echo >A this file should not matter
+ git pull . white && {
+ echo "BAD: should have conflicted"
+ return 1
+ }
+ test -f A || {
+ echo "BAD: should have left A intact"
+ return 1
+ }
+'
+
+test_expect_success 'interference with untracked working tree file' '
+
+ git reset --hard
+ git checkout white
+ git show-branch
+ rm -f A
+ echo >A this file should not matter
+ git pull . red && {
+ echo "BAD: should have conflicted"
+ return 1
+ }
+ test -f A || {
+ echo "BAD: should have left A intact"
+ return 1
+ }
+'
+
+test_expect_success 'interference with untracked working tree file' '
+
+ git reset --hard
+ rm -f A M
+ git checkout -f master
+ git tag -f anchor
+ git show-branch
+ git pull . yellow || {
+ echo "BAD: should have cleanly merged"
+ return 1
+ }
+ test -f M && {
+ echo "BAD: should have removed M"
+ return 1
+ }
+ git reset --hard anchor
+'
+
+test_expect_success 'updated working tree file should prevent the merge' '
+
+ git reset --hard
+ rm -f A M
+ git checkout -f master
+ git tag -f anchor
+ git show-branch
+ echo >>M one line addition
+ cat M >M.saved
+ git pull . yellow && {
+ echo "BAD: should have complained"
+ return 1
+ }
+ diff M M.saved || {
+ echo "BAD: should have left M intact"
+ return 1
+ }
+ rm -f M.saved
+'
+
+test_expect_success 'updated working tree file should prevent the merge' '
+
+ git reset --hard
+ rm -f A M
+ git checkout -f master
+ git tag -f anchor
+ git show-branch
+ echo >>M one line addition
+ cat M >M.saved
+ git update-index M
+ git pull . yellow && {
+ echo "BAD: should have complained"
+ return 1
+ }
+ diff M M.saved || {
+ echo "BAD: should have left M intact"
+ return 1
+ }
+ rm -f M.saved
+'
+
+test_expect_success 'interference with untracked working tree file' '
+
+ git reset --hard
+ rm -f A M
+ git checkout -f yellow
+ git tag -f anchor
+ git show-branch
+ echo >M this file should not matter
+ git pull . master || {
+ echo "BAD: should have cleanly merged"
+ return 1
+ }
+ test -f M || {
+ echo "BAD: should have left M intact"
+ return 1
+ }
+ git ls-files -s | grep M && {
+ echo "BAD: M must be untracked in the result"
+ return 1
}
+ git reset --hard anchor
'
test_done
diff --git a/t/t7001-mv.sh b/t/t7001-mv.sh
index 900ca93cde..23a1eff3bb 100755
--- a/t/t7001-mv.sh
+++ b/t/t7001-mv.sh
@@ -60,6 +60,10 @@ test_expect_success \
grep -E "^R100.+path0/README.+path2/README"'
test_expect_success \
+ 'succeed when source is a prefix of destination' \
+ 'git-mv path2/COPYING path2/COPYING-renamed'
+
+test_expect_success \
'moving whole subdirectory into subdirectory' \
'git-mv path2 path1'
@@ -78,4 +82,27 @@ test_expect_failure \
'do not move directory over existing directory' \
'mkdir path0 && mkdir path0/path2 && git-mv path2 path0'
+test_expect_success \
+ 'move into "."' \
+ 'git-mv path1/path2/ .'
+
+test_expect_success "Michael Cassar's test case" '
+ rm -fr .git papers partA &&
+ git init-db &&
+ mkdir -p papers/unsorted papers/all-papers partA &&
+ echo a > papers/unsorted/Thesis.pdf &&
+ echo b > partA/outline.txt &&
+ echo c > papers/unsorted/_another &&
+ git add papers partA &&
+ T1=`git write-tree` &&
+
+ git mv papers/unsorted/Thesis.pdf papers/all-papers/moo-blah.pdf &&
+
+ T=`git write-tree` &&
+ git ls-tree -r $T | grep partA/outline.txt || {
+ git ls-tree -r $T
+ (exit 1)
+ }
+'
+
test_done
diff --git a/t/t7201-co.sh b/t/t7201-co.sh
index b64e8b7d77..085d4a096b 100755
--- a/t/t7201-co.sh
+++ b/t/t7201-co.sh
@@ -31,6 +31,15 @@ test_expect_success setup '
git checkout master
'
+test_expect_success "checkout from non-existing branch" '
+
+ git checkout -b delete-me master &&
+ rm .git/refs/heads/delete-me &&
+ test refs/heads/delete-me = "$(git symbolic-ref HEAD)" &&
+ git checkout master &&
+ test refs/heads/master = "$(git symbolic-ref HEAD)"
+'
+
test_expect_success "checkout with dirty tree without -m" '
fill 0 1 2 3 4 5 >one &&
diff --git a/t/t9105-git-svn-commit-diff.sh b/t/t9105-git-svn-commit-diff.sh
index f994b72f80..746c8277d0 100755
--- a/t/t9105-git-svn-commit-diff.sh
+++ b/t/t9105-git-svn-commit-diff.sh
@@ -33,7 +33,7 @@ prev=`git rev-parse --verify HEAD^1`
test_expect_success 'test the commit-diff command' "
test -n '$prev' && test -n '$head' &&
- git-svn commit-diff '$prev' '$head' '$svnrepo' &&
+ git-svn commit-diff -r1 '$prev' '$head' '$svnrepo' &&
svn co $svnrepo wc &&
cmp readme wc/readme
"
diff --git a/t/t9106-git-svn-commit-diff-clobber.sh b/t/t9106-git-svn-commit-diff-clobber.sh
new file mode 100755
index 0000000000..58698b3f29
--- /dev/null
+++ b/t/t9106-git-svn-commit-diff-clobber.sh
@@ -0,0 +1,74 @@
+#!/bin/sh
+#
+# Copyright (c) 2006 Eric Wong
+test_description='git-svn commit-diff clobber'
+. ./lib-git-svn.sh
+
+if test -n "$GIT_SVN_NO_LIB" && test "$GIT_SVN_NO_LIB" -ne 0
+then
+ echo 'Skipping: commit-diff clobber needs SVN libraries'
+ test_done
+ exit 0
+fi
+
+test_expect_success 'initialize repo' "
+ mkdir import &&
+ cd import &&
+ echo initial > file &&
+ svn import -m 'initial' . $svnrepo &&
+ cd .. &&
+ echo initial > file &&
+ git update-index --add file &&
+ git commit -a -m 'initial'
+ "
+test_expect_success 'commit change from svn side' "
+ svn co $svnrepo t.svn &&
+ cd t.svn &&
+ echo second line from svn >> file &&
+ svn commit -m 'second line from svn' &&
+ cd .. &&
+ rm -rf t.svn
+ "
+
+test_expect_failure 'commit conflicting change from git' "
+ echo second line from git >> file &&
+ git commit -a -m 'second line from git' &&
+ git-svn commit-diff -r1 HEAD~1 HEAD $svnrepo
+ " || true
+
+test_expect_success 'commit complementing change from git' "
+ git reset --hard HEAD~1 &&
+ echo second line from svn >> file &&
+ git commit -a -m 'second line from svn' &&
+ echo third line from git >> file &&
+ git commit -a -m 'third line from git' &&
+ git-svn commit-diff -r2 HEAD~1 HEAD $svnrepo
+ "
+
+test_expect_failure 'dcommit fails to commit because of conflict' "
+ git-svn init $svnrepo &&
+ git-svn fetch &&
+ git reset --hard refs/remotes/git-svn &&
+ svn co $svnrepo t.svn &&
+ cd t.svn &&
+ echo fourth line from svn >> file &&
+ svn commit -m 'fourth line from svn' &&
+ cd .. &&
+ rm -rf t.svn &&
+ echo 'fourth line from git' >> file &&
+ git commit -a -m 'fourth line from git' &&
+ git-svn dcommit
+ " || true
+
+test_expect_success 'dcommit does the svn equivalent of an index merge' "
+ git reset --hard refs/remotes/git-svn &&
+ echo 'index merge' > file2 &&
+ git update-index --add file2 &&
+ git commit -a -m 'index merge' &&
+ echo 'more changes' >> file2 &&
+ git update-index file2 &&
+ git commit -a -m 'more changes' &&
+ git-svn dcommit
+ "
+
+test_done
diff --git a/t/t9200-git-cvsexportcommit.sh b/t/t9200-git-cvsexportcommit.sh
new file mode 100755
index 0000000000..6e566d4409
--- /dev/null
+++ b/t/t9200-git-cvsexportcommit.sh
@@ -0,0 +1,145 @@
+#!/bin/bash
+#
+# Copyright (c) Robin Rosenberg
+#
+test_description='CVS export comit. '
+
+. ./test-lib.sh
+
+cvs >/dev/null 2>&1
+if test $? -ne 1
+then
+ test_expect_success 'skipping git-cvsexportcommit tests, cvs not found' :
+ test_done
+ exit
+fi
+
+export CVSROOT=$(pwd)/cvsroot
+export CVSWORK=$(pwd)/cvswork
+rm -rf "$CVSROOT" "$CVSWORK"
+mkdir "$CVSROOT" &&
+cvs init &&
+cvs -Q co -d "$CVSWORK" . &&
+export GIT_DIR=$(pwd)/.git &&
+echo >empty &&
+git add empty &&
+git commit -a -m "Initial" 2>/dev/null ||
+exit 1
+
+test_expect_success \
+ 'New file' \
+ 'mkdir A B C D E F &&
+ echo hello1 >A/newfile1.txt &&
+ echo hello2 >B/newfile2.txt &&
+ cp ../test9200a.png C/newfile3.png &&
+ cp ../test9200a.png D/newfile4.png &&
+ git add A/newfile1.txt &&
+ git add B/newfile2.txt &&
+ git add C/newfile3.png &&
+ git add D/newfile4.png &&
+ git commit -a -m "Test: New file" &&
+ id=$(git rev-list --max-count=1 HEAD) &&
+ (cd "$CVSWORK" &&
+ git cvsexportcommit -c $id &&
+ test "$(echo $(sort A/CVS/Entries|cut -d/ -f2,3,5))" = "newfile1.txt/1.1/" &&
+ test "$(echo $(sort B/CVS/Entries|cut -d/ -f2,3,5))" = "newfile2.txt/1.1/" &&
+ test "$(echo $(sort C/CVS/Entries|cut -d/ -f2,3,5))" = "newfile3.png/1.1/-kb" &&
+ test "$(echo $(sort D/CVS/Entries|cut -d/ -f2,3,5))" = "newfile4.png/1.1/-kb" &&
+ diff A/newfile1.txt ../A/newfile1.txt &&
+ diff B/newfile2.txt ../B/newfile2.txt &&
+ diff C/newfile3.png ../C/newfile3.png &&
+ diff D/newfile4.png ../D/newfile4.png
+ )'
+
+test_expect_success \
+ 'Remove two files, add two and update two' \
+ 'echo Hello1 >>A/newfile1.txt &&
+ rm -f B/newfile2.txt &&
+ rm -f C/newfile3.png &&
+ echo Hello5 >E/newfile5.txt &&
+ cp ../test9200b.png D/newfile4.png &&
+ cp ../test9200a.png F/newfile6.png &&
+ git add E/newfile5.txt &&
+ git add F/newfile6.png &&
+ git commit -a -m "Test: Remove, add and update" &&
+ id=$(git rev-list --max-count=1 HEAD) &&
+ (cd "$CVSWORK" &&
+ git cvsexportcommit -c $id &&
+ test "$(echo $(sort A/CVS/Entries|cut -d/ -f2,3,5))" = "newfile1.txt/1.2/" &&
+ test "$(echo $(sort B/CVS/Entries|cut -d/ -f2,3,5))" = "" &&
+ test "$(echo $(sort C/CVS/Entries|cut -d/ -f2,3,5))" = "" &&
+ test "$(echo $(sort D/CVS/Entries|cut -d/ -f2,3,5))" = "newfile4.png/1.2/-kb" &&
+ test "$(echo $(sort E/CVS/Entries|cut -d/ -f2,3,5))" = "newfile5.txt/1.1/" &&
+ test "$(echo $(sort F/CVS/Entries|cut -d/ -f2,3,5))" = "newfile6.png/1.1/-kb" &&
+ diff A/newfile1.txt ../A/newfile1.txt &&
+ diff D/newfile4.png ../D/newfile4.png &&
+ diff E/newfile5.txt ../E/newfile5.txt &&
+ diff F/newfile6.png ../F/newfile6.png
+ )'
+
+# Should fail (but only on the git-cvsexportcommit stage)
+test_expect_success \
+ 'Fail to change binary more than one generation old' \
+ 'cat F/newfile6.png >>D/newfile4.png &&
+ git commit -a -m "generatiion 1" &&
+ cat F/newfile6.png >>D/newfile4.png &&
+ git commit -a -m "generation 2" &&
+ id=$(git rev-list --max-count=1 HEAD) &&
+ (cd "$CVSWORK" &&
+ ! git cvsexportcommit -c $id
+ )'
+
+# Should fail, but only on the git-cvsexportcommit stage
+test_expect_success \
+ 'Fail to remove binary file more than one generation old' \
+ 'git reset --hard HEAD^ &&
+ cat F/newfile6.png >>D/newfile4.png &&
+ git commit -a -m "generation 2 (again)" &&
+ rm -f D/newfile4.png &&
+ git commit -a -m "generation 3" &&
+ id=$(git rev-list --max-count=1 HEAD) &&
+ (cd "$CVSWORK" &&
+ ! git cvsexportcommit -c $id
+ )'
+
+# We reuse the state from two tests back here
+
+# This test is here because a patch for only binary files will
+# fail with gnu patch, so cvsexportcommit must handle that.
+test_expect_success \
+ 'Remove only binary files' \
+ 'git reset --hard HEAD^^^ &&
+ rm -f D/newfile4.png &&
+ git commit -a -m "test: remove only a binary file" &&
+ id=$(git rev-list --max-count=1 HEAD) &&
+ (cd "$CVSWORK" &&
+ git cvsexportcommit -c $id &&
+ test "$(echo $(sort A/CVS/Entries|cut -d/ -f2,3,5))" = "newfile1.txt/1.2/" &&
+ test "$(echo $(sort B/CVS/Entries|cut -d/ -f2,3,5))" = "" &&
+ test "$(echo $(sort C/CVS/Entries|cut -d/ -f2,3,5))" = "" &&
+ test "$(echo $(sort D/CVS/Entries|cut -d/ -f2,3,5))" = "" &&
+ test "$(echo $(sort E/CVS/Entries|cut -d/ -f2,3,5))" = "newfile5.txt/1.1/" &&
+ test "$(echo $(sort F/CVS/Entries|cut -d/ -f2,3,5))" = "newfile6.png/1.1/-kb" &&
+ diff A/newfile1.txt ../A/newfile1.txt &&
+ diff E/newfile5.txt ../E/newfile5.txt &&
+ diff F/newfile6.png ../F/newfile6.png
+ )'
+
+test_expect_success \
+ 'Remove only a text file' \
+ 'rm -f A/newfile1.txt &&
+ git commit -a -m "test: remove only a binary file" &&
+ id=$(git rev-list --max-count=1 HEAD) &&
+ (cd "$CVSWORK" &&
+ git cvsexportcommit -c $id &&
+ test "$(echo $(sort A/CVS/Entries|cut -d/ -f2,3,5))" = "" &&
+ test "$(echo $(sort B/CVS/Entries|cut -d/ -f2,3,5))" = "" &&
+ test "$(echo $(sort C/CVS/Entries|cut -d/ -f2,3,5))" = "" &&
+ test "$(echo $(sort D/CVS/Entries|cut -d/ -f2,3,5))" = "" &&
+ test "$(echo $(sort E/CVS/Entries|cut -d/ -f2,3,5))" = "newfile5.txt/1.1/" &&
+ test "$(echo $(sort F/CVS/Entries|cut -d/ -f2,3,5))" = "newfile6.png/1.1/-kb" &&
+ diff E/newfile5.txt ../E/newfile5.txt &&
+ diff F/newfile6.png ../F/newfile6.png
+ )'
+
+test_done
diff --git a/t/test-lib.sh b/t/test-lib.sh
index 470a909891..3895f16709 100755
--- a/t/test-lib.sh
+++ b/t/test-lib.sh
@@ -28,13 +28,21 @@ unset GIT_DIR
unset GIT_EXTERNAL_DIFF
unset GIT_INDEX_FILE
unset GIT_OBJECT_DIRECTORY
-unset GIT_TRACE
unset SHA1_FILE_DIRECTORIES
unset SHA1_FILE_DIRECTORY
export GIT_AUTHOR_EMAIL GIT_AUTHOR_NAME
export GIT_COMMITTER_EMAIL GIT_COMMITTER_NAME
export EDITOR VISUAL
+case $(echo $GIT_TRACE |tr "[A-Z]" "[a-z]") in
+ 1|2|true)
+ echo "* warning: Some tests will not work if GIT_TRACE" \
+ "is set as to trace on STDERR ! *"
+ echo "* warning: Please set GIT_TRACE to something" \
+ "other than 1, 2 or true ! *"
+ ;;
+esac
+
# Each test should start with something like this, after copyright notices:
#
# test_description='Description of this test...
@@ -121,12 +129,13 @@ test_expect_failure () {
error "bug in the test script: not 2 parameters to test-expect-failure"
say >&3 "expecting failure: $2"
test_run_ "$2"
- if [ "$?" = 0 -a "$eval_ret" != 0 ]
+ if [ "$?" = 0 -a "$eval_ret" != 0 -a "$eval_ret" -lt 129 ]
then
test_ok_ "$1"
else
test_failure_ "$@"
fi
+ echo >&3 ""
}
test_expect_success () {
@@ -140,6 +149,7 @@ test_expect_success () {
else
test_failure_ "$@"
fi
+ echo >&3 ""
}
test_expect_code () {
@@ -153,6 +163,7 @@ test_expect_code () {
else
test_failure_ "$@"
fi
+ echo >&3 ""
}
# Most tests can use the created repository, but some amy need to create more.
@@ -196,20 +207,23 @@ test_done () {
# t/ subdirectory and are run in trash subdirectory.
PATH=$(pwd)/..:$PATH
GIT_EXEC_PATH=$(pwd)/..
-export PATH GIT_EXEC_PATH
+HOME=$(pwd)/trash
+export PATH GIT_EXEC_PATH HOME
# Similarly use ../compat/subprocess.py if our python does not
# have subprocess.py on its own.
PYTHON=`sed -e '1{
s/^#!//
q
-}' ../git-merge-recursive` || {
+}' ../git-merge-recursive-old` || {
error "You haven't built things yet, have you?"
}
"$PYTHON" -c 'import subprocess' 2>/dev/null || {
PYTHONPATH=$(pwd)/../compat
export PYTHONPATH
}
+GITPERLLIB=$(pwd)/../perl/blib/lib:$(pwd)/../perl/blib/arch/auto/Git
+export GITPERLLIB
test -d ../templates/blt || {
error "You haven't built things yet, have you?"
}
diff --git a/t/test9200a.png b/t/test9200a.png
new file mode 100644
index 0000000000..7b181d15ce
--- /dev/null
+++ b/t/test9200a.png
Binary files differ
diff --git a/t/test9200b.png b/t/test9200b.png
new file mode 100644
index 0000000000..ac22ccbd3e
--- /dev/null
+++ b/t/test9200b.png
Binary files differ
diff --git a/templates/hooks--commit-msg b/templates/hooks--commit-msg
index 643822d235..0b906caa98 100644
--- a/templates/hooks--commit-msg
+++ b/templates/hooks--commit-msg
@@ -11,4 +11,8 @@
# This example catches duplicate Signed-off-by lines.
test "" = "$(grep '^Signed-off-by: ' "$1" |
- sort | uniq -c | sed -e '/^[ ]*1 /d')"
+ sort | uniq -c | sed -e '/^[ ]*1[ ]/d')" || {
+ echo >&2 Duplicate Signed-off-by lines.
+ exit 1
+}
+
diff --git a/trace.c b/trace.c
new file mode 100644
index 0000000000..495e5ed92a
--- /dev/null
+++ b/trace.c
@@ -0,0 +1,150 @@
+/*
+ * GIT - The information manager from hell
+ *
+ * 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
+ * Copyright (C) 2006 Christian Couder
+ *
+ * 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 "quote.h"
+
+/* Stolen from "imap-send.c". */
+static int git_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;
+}
+
+/* Stolen from "imap-send.c". */
+int nfvasprintf(char **str, const char *fmt, va_list va)
+{
+ int ret = git_vasprintf(str, fmt, va);
+ if (ret < 0)
+ die("Fatal: Out of memory\n");
+ return ret;
+}
+
+/* Get a trace file descriptor from GIT_TRACE env variable. */
+static int get_trace_fd(int *need_close)
+{
+ char *trace = getenv("GIT_TRACE");
+
+ if (!trace || !strcmp(trace, "") ||
+ !strcmp(trace, "0") || !strcasecmp(trace, "false"))
+ return 0;
+ if (!strcmp(trace, "1") || !strcasecmp(trace, "true"))
+ return STDERR_FILENO;
+ if (strlen(trace) == 1 && isdigit(*trace))
+ return atoi(trace);
+ if (*trace == '/') {
+ int fd = open(trace, O_WRONLY | O_APPEND | O_CREAT, 0666);
+ if (fd == -1) {
+ fprintf(stderr,
+ "Could not open '%s' for tracing: %s\n"
+ "Defaulting to tracing on stderr...\n",
+ trace, strerror(errno));
+ return STDERR_FILENO;
+ }
+ *need_close = 1;
+ return fd;
+ }
+
+ fprintf(stderr, "What does '%s' for GIT_TRACE means ?\n", trace);
+ fprintf(stderr, "If you want to trace into a file, "
+ "then please set GIT_TRACE to an absolute pathname "
+ "(starting with /).\n");
+ fprintf(stderr, "Defaulting to tracing on stderr...\n");
+
+ return STDERR_FILENO;
+}
+
+static const char err_msg[] = "Could not trace into fd given by "
+ "GIT_TRACE environment variable";
+
+void trace_printf(const char *format, ...)
+{
+ char *trace_str;
+ va_list rest;
+ int need_close = 0;
+ int fd = get_trace_fd(&need_close);
+
+ if (!fd)
+ return;
+
+ va_start(rest, format);
+ nfvasprintf(&trace_str, format, rest);
+ va_end(rest);
+
+ write_or_whine(fd, trace_str, strlen(trace_str), err_msg);
+
+ free(trace_str);
+
+ if (need_close)
+ close(fd);
+}
+
+void trace_argv_printf(const char **argv, int count, const char *format, ...)
+{
+ char *argv_str, *format_str, *trace_str;
+ size_t argv_len, format_len, trace_len;
+ va_list rest;
+ int need_close = 0;
+ int fd = get_trace_fd(&need_close);
+
+ if (!fd)
+ return;
+
+ /* Get the argv string. */
+ argv_str = sq_quote_argv(argv, count);
+ argv_len = strlen(argv_str);
+
+ /* Get the formated string. */
+ va_start(rest, format);
+ nfvasprintf(&format_str, format, rest);
+ va_end(rest);
+
+ /* Allocate buffer for trace string. */
+ format_len = strlen(format_str);
+ trace_len = argv_len + format_len + 1; /* + 1 for \n */
+ trace_str = xmalloc(trace_len + 1);
+
+ /* Copy everything into the trace string. */
+ strncpy(trace_str, format_str, format_len);
+ strncpy(trace_str + format_len, argv_str, argv_len);
+ strcpy(trace_str + trace_len - 1, "\n");
+
+ write_or_whine(fd, trace_str, trace_len, err_msg);
+
+ free(argv_str);
+ free(format_str);
+ free(trace_str);
+
+ if (need_close)
+ close(fd);
+}
diff --git a/tree-diff.c b/tree-diff.c
index 1cdf8aa908..37d235e06e 100644
--- a/tree-diff.c
+++ b/tree-diff.c
@@ -15,7 +15,8 @@ static char *malloc_base(const char *base, const char *path, int pathlen)
return newbase;
}
-static int show_entry(struct diff_options *opt, const char *prefix, struct tree_desc *desc, const char *base);
+static void show_entry(struct diff_options *opt, const char *prefix, struct tree_desc *desc,
+ const char *base);
static int compare_tree_entry(struct tree_desc *t1, struct tree_desc *t2, const char *base, struct diff_options *opt)
{
@@ -38,8 +39,7 @@ static int compare_tree_entry(struct tree_desc *t1, struct tree_desc *t2, const
show_entry(opt, "+", t2, base);
return 1;
}
- if (!opt->find_copies_harder &&
- !memcmp(sha1, sha2, 20) && mode1 == mode2)
+ if (!opt->find_copies_harder && !hashcmp(sha1, sha2) && mode1 == mode2)
return 0;
/*
@@ -131,7 +131,8 @@ static void show_tree(struct diff_options *opt, const char *prefix, struct tree_
}
/* A file entry went away or appeared */
-static int show_entry(struct diff_options *opt, const char *prefix, struct tree_desc *desc, const char *base)
+static void show_entry(struct diff_options *opt, const char *prefix, struct tree_desc *desc,
+ const char *base)
{
unsigned mode;
const char *path;
@@ -152,11 +153,9 @@ static int show_entry(struct diff_options *opt, const char *prefix, struct tree_
free(tree);
free(newbase);
- return 0;
+ } else {
+ opt->add_remove(opt, prefix[0], mode, sha1, base, path);
}
-
- opt->add_remove(opt, prefix[0], mode, sha1, base, path);
- return 0;
}
int diff_tree(struct tree_desc *t1, struct tree_desc *t2, const char *base, struct diff_options *opt)
@@ -216,6 +215,24 @@ int diff_tree_sha1(const unsigned char *old, const unsigned char *new, const cha
return retval;
}
+int diff_root_tree_sha1(const unsigned char *new, const char *base, struct diff_options *opt)
+{
+ int retval;
+ void *tree;
+ struct tree_desc empty, real;
+
+ tree = read_object_with_reference(new, tree_type, &real.size, NULL);
+ if (!tree)
+ die("unable to read root tree (%s)", sha1_to_hex(new));
+ real.buf = tree;
+
+ empty.size = 0;
+ empty.buf = "";
+ retval = diff_tree(&empty, &real, base, opt);
+ free(tree);
+ return retval;
+}
+
static int count_paths(const char **paths)
{
int i = 0;
diff --git a/tree-walk.c b/tree-walk.c
index 3f83e98f3a..14cc5aea6c 100644
--- a/tree-walk.c
+++ b/tree-walk.c
@@ -179,7 +179,7 @@ static int find_tree_entry(struct tree_desc *t, const char *name, unsigned char
if (cmp < 0)
break;
if (entrylen == namelen) {
- memcpy(result, sha1, 20);
+ hashcpy(result, sha1);
return 0;
}
if (name[entrylen] != '/')
@@ -187,7 +187,7 @@ static int find_tree_entry(struct tree_desc *t, const char *name, unsigned char
if (!S_ISDIR(*mode))
break;
if (++entrylen == namelen) {
- memcpy(result, sha1, 20);
+ hashcpy(result, sha1);
return 0;
}
return get_tree_entry(sha1, name + entrylen, result, mode);
diff --git a/tree.c b/tree.c
index a6032e35ec..ea386e5066 100644
--- a/tree.c
+++ b/tree.c
@@ -25,7 +25,7 @@ static int read_one_entry(const unsigned char *sha1, const char *base, int basel
ce->ce_flags = create_ce_flags(baselen + len, stage);
memcpy(ce->name, base, baselen);
memcpy(ce->name + baselen, pathname, len+1);
- memcpy(ce->sha1, sha1, 20);
+ hashcpy(ce->sha1, sha1);
return add_cache_entry(ce, ADD_CACHE_OK_TO_ADD|ADD_CACHE_SKIP_DFCHECK);
}
@@ -144,7 +144,7 @@ struct tree *lookup_tree(const unsigned char *sha1)
return (struct tree *) obj;
}
-static int track_tree_refs(struct tree *item)
+static void track_tree_refs(struct tree *item)
{
int n_refs = 0, i;
struct object_refs *refs;
@@ -174,7 +174,6 @@ static int track_tree_refs(struct tree *item)
refs->ref[i++] = obj;
}
set_object_refs(&item->object, refs);
- return 0;
}
int parse_tree_buffer(struct tree *item, void *buffer, unsigned long size)
diff --git a/unpack-trees.c b/unpack-trees.c
index a20639be70..7cfd628d8e 100644
--- a/unpack-trees.c
+++ b/unpack-trees.c
@@ -200,7 +200,7 @@ static int unpack_trees_rec(struct tree_entry_list **posns, int len,
any_files = 1;
- memcpy(ce->sha1, posns[i]->sha1, 20);
+ hashcpy(ce->sha1, posns[i]->sha1);
src[i + o->merge] = ce;
subposns[i] = df_conflict_list;
posns[i] = posns[i]->next;
@@ -278,7 +278,7 @@ static void unlink_entry(char *name)
}
}
-static volatile int progress_update = 0;
+static volatile sig_atomic_t progress_update;
static void progress_interval(int signum)
{
@@ -417,7 +417,7 @@ static int same(struct cache_entry *a, struct cache_entry *b)
if (!a && !b)
return 1;
return a->ce_mode == b->ce_mode &&
- !memcmp(a->sha1, b->sha1, 20);
+ !hashcmp(a->sha1, b->sha1);
}
@@ -642,7 +642,7 @@ int threeway_merge(struct cache_entry **stages,
(remote_deleted && head && head_match)) {
if (index)
return deleted_entry(index, index, o);
- else if (path)
+ else if (path && !head_deleted)
verify_absent(path, "removed", o);
return 0;
}
@@ -661,8 +661,6 @@ int threeway_merge(struct cache_entry **stages,
if (index) {
verify_uptodate(index, o);
}
- else if (path)
- verify_absent(path, "overwritten", o);
o->nontrivial_merge = 1;
diff --git a/upload-pack.c b/upload-pack.c
index e8f4be373c..4572fff07c 100644
--- a/upload-pack.c
+++ b/upload-pack.c
@@ -4,6 +4,7 @@
#include "cache.h"
#include "refs.h"
#include "pkt-line.h"
+#include "sideband.h"
#include "tag.h"
#include "object.h"
#include "commit.h"
@@ -18,14 +19,17 @@ static const char upload_pack_usage[] = "git-upload-pack [--strict] [--timeout=n
#define COMMON_KNOWN (1u << 14)
#define REACHABLE (1u << 15)
-static unsigned long oldest_have = 0;
+static unsigned long oldest_have;
-static int multi_ack = 0, nr_our_refs = 0;
-static int use_thin_pack = 0;
+static int multi_ack, nr_our_refs;
+static int use_thin_pack, use_ofs_delta;
static struct object_array have_obj;
static struct object_array want_obj;
-static unsigned int timeout = 0;
-static int use_sideband = 0;
+static unsigned int timeout;
+/* 0 for no sideband,
+ * otherwise maximum packet size (up to 65520 bytes).
+ */
+static int use_sideband;
static void reset_timeout(void)
{
@@ -39,45 +43,18 @@ static int strip(char *line, int len)
return len;
}
-#define PACKET_MAX 1000
static ssize_t send_client_data(int fd, const char *data, ssize_t sz)
{
- ssize_t ssz;
- const char *p;
-
- if (!data) {
- if (!use_sideband)
- return 0;
- packet_flush(1);
- }
-
- if (!use_sideband) {
- if (fd == 3)
- /* emergency quit */
- fd = 2;
- if (fd == 2) {
- xwrite(fd, data, sz);
- return sz;
- }
- return safe_write(fd, data, sz);
+ if (use_sideband)
+ return send_sideband(1, fd, data, sz, use_sideband);
+ if (fd == 3)
+ /* emergency quit */
+ fd = 2;
+ if (fd == 2) {
+ xwrite(fd, data, sz);
+ return sz;
}
- p = data;
- ssz = sz;
- while (sz) {
- unsigned n;
- char hdr[5];
-
- n = sz;
- if (PACKET_MAX - 5 < n)
- n = PACKET_MAX - 5;
- sprintf(hdr, "%04x", n + 5);
- hdr[4] = fd;
- safe_write(1, hdr, 5);
- safe_write(1, p, n);
- p += n;
- sz -= n;
- }
- return ssz;
+ return safe_write(fd, data, sz);
}
static void create_pack_file(void)
@@ -166,7 +143,9 @@ static void create_pack_file(void)
close(pu_pipe[1]);
close(pe_pipe[0]);
close(pe_pipe[1]);
- execl_git_cmd("pack-objects", "--stdout", "--progress", NULL);
+ execl_git_cmd("pack-objects", "--stdout", "--progress",
+ use_ofs_delta ? "--delta-base-offset" : NULL,
+ NULL);
kill(pid_rev_list, SIGKILL);
die("git-upload-pack: unable to exec git-pack-objects");
}
@@ -314,7 +293,8 @@ static void create_pack_file(void)
goto fail;
fprintf(stderr, "flushed.\n");
}
- send_client_data(1, NULL, 0);
+ if (use_sideband)
+ packet_flush(1);
return;
}
fail:
@@ -498,8 +478,12 @@ static void receive_needs(void)
multi_ack = 1;
if (strstr(line+45, "thin-pack"))
use_thin_pack = 1;
- if (strstr(line+45, "side-band"))
- use_sideband = 1;
+ if (strstr(line+45, "ofs-delta"))
+ use_ofs_delta = 1;
+ if (strstr(line+45, "side-band-64k"))
+ use_sideband = LARGE_PACKET_MAX;
+ else if (strstr(line+45, "side-band"))
+ use_sideband = DEFAULT_PACKET_MAX;
/* We have sent all our refs already, and the other end
* should have chosen out of them; otherwise they are
@@ -519,9 +503,9 @@ static void receive_needs(void)
}
}
-static int send_ref(const char *refname, const unsigned char *sha1)
+static int send_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
{
- static const char *capabilities = "multi_ack thin-pack side-band";
+ static const char *capabilities = "multi_ack thin-pack side-band side-band-64k ofs-delta";
struct object *o = parse_object(sha1);
if (!o)
@@ -544,18 +528,17 @@ static int send_ref(const char *refname, const unsigned char *sha1)
return 0;
}
-static int upload_pack(void)
+static void upload_pack(void)
{
reset_timeout();
- head_ref(send_ref);
- for_each_ref(send_ref);
+ head_ref(send_ref, NULL);
+ for_each_ref(send_ref, NULL);
packet_flush(1);
receive_needs();
- if (!want_obj.nr)
- return 0;
- get_common_commits();
- create_pack_file();
- return 0;
+ if (want_obj.nr) {
+ get_common_commits();
+ create_pack_file();
+ }
}
int main(int argc, char **argv)
diff --git a/write_or_die.c b/write_or_die.c
new file mode 100644
index 0000000000..bfe4eeb649
--- /dev/null
+++ b/write_or_die.c
@@ -0,0 +1,45 @@
+#include "cache.h"
+
+void write_or_die(int fd, const void *buf, size_t count)
+{
+ const char *p = buf;
+ ssize_t written;
+
+ while (count > 0) {
+ written = xwrite(fd, p, count);
+ if (written == 0)
+ die("disk full?");
+ else if (written < 0) {
+ if (errno == EPIPE)
+ exit(0);
+ die("write error (%s)", strerror(errno));
+ }
+ count -= written;
+ p += written;
+ }
+}
+
+int write_or_whine(int fd, const void *buf, size_t count, const char *msg)
+{
+ const char *p = buf;
+ ssize_t written;
+
+ while (count > 0) {
+ written = xwrite(fd, p, count);
+ if (written == 0) {
+ fprintf(stderr, "%s: disk full?\n", msg);
+ return 0;
+ }
+ else if (written < 0) {
+ if (errno == EPIPE)
+ exit(0);
+ fprintf(stderr, "%s: write error (%s)\n",
+ msg, strerror(errno));
+ return 0;
+ }
+ count -= written;
+ p += written;
+ }
+
+ return 1;
+}
diff --git a/wt-status.c b/wt-status.c
new file mode 100644
index 0000000000..de1be5bc66
--- /dev/null
+++ b/wt-status.c
@@ -0,0 +1,309 @@
+#include "wt-status.h"
+#include "color.h"
+#include "cache.h"
+#include "object.h"
+#include "dir.h"
+#include "commit.h"
+#include "diff.h"
+#include "revision.h"
+#include "diffcore.h"
+
+int wt_status_use_color = 0;
+static char wt_status_colors[][COLOR_MAXLEN] = {
+ "", /* WT_STATUS_HEADER: normal */
+ "\033[32m", /* WT_STATUS_UPDATED: green */
+ "\033[31m", /* WT_STATUS_CHANGED: red */
+ "\033[31m", /* WT_STATUS_UNTRACKED: red */
+};
+
+static int parse_status_slot(const char *var, int offset)
+{
+ if (!strcasecmp(var+offset, "header"))
+ return WT_STATUS_HEADER;
+ if (!strcasecmp(var+offset, "updated"))
+ return WT_STATUS_UPDATED;
+ if (!strcasecmp(var+offset, "changed"))
+ return WT_STATUS_CHANGED;
+ if (!strcasecmp(var+offset, "untracked"))
+ return WT_STATUS_UNTRACKED;
+ die("bad config variable '%s'", var);
+}
+
+static const char* color(int slot)
+{
+ return wt_status_use_color ? wt_status_colors[slot] : "";
+}
+
+void wt_status_prepare(struct wt_status *s)
+{
+ unsigned char sha1[20];
+ const char *head;
+
+ s->is_initial = get_sha1("HEAD", sha1) ? 1 : 0;
+
+ head = resolve_ref("HEAD", sha1, 0, NULL);
+ s->branch = head ? xstrdup(head) : NULL;
+
+ s->reference = "HEAD";
+ s->amend = 0;
+ s->verbose = 0;
+ s->commitable = 0;
+ s->untracked = 0;
+}
+
+static void wt_status_print_header(const char *main, const char *sub)
+{
+ const char *c = color(WT_STATUS_HEADER);
+ color_printf_ln(c, "# %s:", main);
+ color_printf_ln(c, "# (%s)", sub);
+ color_printf_ln(c, "#");
+}
+
+static void wt_status_print_trailer(void)
+{
+ color_printf_ln(color(WT_STATUS_HEADER), "#");
+}
+
+static const char *quote_crlf(const char *in, char *buf, size_t sz)
+{
+ const char *scan;
+ char *out;
+ const char *ret = in;
+
+ for (scan = in, out = buf; *scan; scan++) {
+ int ch = *scan;
+ int quoted;
+
+ switch (ch) {
+ case '\n':
+ quoted = 'n';
+ break;
+ case '\r':
+ quoted = 'r';
+ break;
+ default:
+ *out++ = ch;
+ continue;
+ }
+ *out++ = '\\';
+ *out++ = quoted;
+ ret = buf;
+ }
+ *out = '\0';
+ return ret;
+}
+
+static void wt_status_print_filepair(int t, struct diff_filepair *p)
+{
+ const char *c = color(t);
+ const char *one, *two;
+ char onebuf[PATH_MAX], twobuf[PATH_MAX];
+
+ one = quote_crlf(p->one->path, onebuf, sizeof(onebuf));
+ two = quote_crlf(p->two->path, twobuf, sizeof(twobuf));
+
+ color_printf(color(WT_STATUS_HEADER), "#\t");
+ switch (p->status) {
+ case DIFF_STATUS_ADDED:
+ color_printf(c, "new file: %s", one);
+ break;
+ case DIFF_STATUS_COPIED:
+ color_printf(c, "copied: %s -> %s", one, two);
+ break;
+ case DIFF_STATUS_DELETED:
+ color_printf(c, "deleted: %s", one);
+ break;
+ case DIFF_STATUS_MODIFIED:
+ color_printf(c, "modified: %s", one);
+ break;
+ case DIFF_STATUS_RENAMED:
+ color_printf(c, "renamed: %s -> %s", one, two);
+ break;
+ case DIFF_STATUS_TYPE_CHANGED:
+ color_printf(c, "typechange: %s", one);
+ break;
+ case DIFF_STATUS_UNKNOWN:
+ color_printf(c, "unknown: %s", one);
+ break;
+ case DIFF_STATUS_UNMERGED:
+ color_printf(c, "unmerged: %s", one);
+ break;
+ default:
+ die("bug: unhandled diff status %c", p->status);
+ }
+ printf("\n");
+}
+
+static void wt_status_print_updated_cb(struct diff_queue_struct *q,
+ struct diff_options *options,
+ void *data)
+{
+ struct wt_status *s = data;
+ int shown_header = 0;
+ int i;
+ for (i = 0; i < q->nr; i++) {
+ if (q->queue[i]->status == 'U')
+ continue;
+ if (!shown_header) {
+ wt_status_print_header("Updated but not checked in",
+ "will commit");
+ s->commitable = 1;
+ shown_header = 1;
+ }
+ wt_status_print_filepair(WT_STATUS_UPDATED, q->queue[i]);
+ }
+ if (shown_header)
+ wt_status_print_trailer();
+}
+
+static void wt_status_print_changed_cb(struct diff_queue_struct *q,
+ struct diff_options *options,
+ void *data)
+{
+ int i;
+ if (q->nr)
+ wt_status_print_header("Changed but not updated",
+ "use git-update-index to mark for commit");
+ for (i = 0; i < q->nr; i++)
+ wt_status_print_filepair(WT_STATUS_CHANGED, q->queue[i]);
+ if (q->nr)
+ wt_status_print_trailer();
+}
+
+void wt_status_print_initial(struct wt_status *s)
+{
+ int i;
+ char buf[PATH_MAX];
+
+ read_cache();
+ if (active_nr) {
+ s->commitable = 1;
+ wt_status_print_header("Updated but not checked in",
+ "will commit");
+ }
+ for (i = 0; i < active_nr; i++) {
+ color_printf(color(WT_STATUS_HEADER), "#\t");
+ color_printf_ln(color(WT_STATUS_UPDATED), "new file: %s",
+ quote_crlf(active_cache[i]->name,
+ buf, sizeof(buf)));
+ }
+ if (active_nr)
+ wt_status_print_trailer();
+}
+
+static void wt_status_print_updated(struct wt_status *s)
+{
+ struct rev_info rev;
+ init_revisions(&rev, NULL);
+ setup_revisions(0, NULL, &rev, s->reference);
+ rev.diffopt.output_format |= DIFF_FORMAT_CALLBACK;
+ rev.diffopt.format_callback = wt_status_print_updated_cb;
+ rev.diffopt.format_callback_data = s;
+ rev.diffopt.detect_rename = 1;
+ run_diff_index(&rev, 1);
+}
+
+static void wt_status_print_changed(struct wt_status *s)
+{
+ struct rev_info rev;
+ init_revisions(&rev, "");
+ setup_revisions(0, NULL, &rev, NULL);
+ rev.diffopt.output_format |= DIFF_FORMAT_CALLBACK;
+ rev.diffopt.format_callback = wt_status_print_changed_cb;
+ rev.diffopt.format_callback_data = s;
+ run_diff_files(&rev, 0);
+}
+
+static void wt_status_print_untracked(const struct wt_status *s)
+{
+ struct dir_struct dir;
+ const char *x;
+ int i;
+ int shown_header = 0;
+
+ memset(&dir, 0, sizeof(dir));
+
+ dir.exclude_per_dir = ".gitignore";
+ if (!s->untracked) {
+ dir.show_other_directories = 1;
+ dir.hide_empty_directories = 1;
+ }
+ x = git_path("info/exclude");
+ if (file_exists(x))
+ add_excludes_from_file(&dir, x);
+
+ read_directory(&dir, ".", "", 0);
+ for(i = 0; i < dir.nr; i++) {
+ /* check for matching entry, which is unmerged; lifted from
+ * builtin-ls-files:show_other_files */
+ struct dir_entry *ent = dir.entries[i];
+ int pos = cache_name_pos(ent->name, ent->len);
+ struct cache_entry *ce;
+ if (0 <= pos)
+ die("bug in wt_status_print_untracked");
+ pos = -pos - 1;
+ if (pos < active_nr) {
+ ce = active_cache[pos];
+ if (ce_namelen(ce) == ent->len &&
+ !memcmp(ce->name, ent->name, ent->len))
+ continue;
+ }
+ if (!shown_header) {
+ wt_status_print_header("Untracked files",
+ "use \"git add\" to add to commit");
+ shown_header = 1;
+ }
+ color_printf(color(WT_STATUS_HEADER), "#\t");
+ color_printf_ln(color(WT_STATUS_UNTRACKED), "%.*s",
+ ent->len, ent->name);
+ }
+}
+
+static void wt_status_print_verbose(struct wt_status *s)
+{
+ struct rev_info rev;
+ init_revisions(&rev, NULL);
+ setup_revisions(0, NULL, &rev, s->reference);
+ rev.diffopt.output_format |= DIFF_FORMAT_PATCH;
+ rev.diffopt.detect_rename = 1;
+ run_diff_index(&rev, 1);
+}
+
+void wt_status_print(struct wt_status *s)
+{
+ if (s->branch && strcmp(s->branch, "refs/heads/master"))
+ color_printf_ln(color(WT_STATUS_HEADER),
+ "# On branch %s", s->branch);
+
+ if (s->is_initial) {
+ color_printf_ln(color(WT_STATUS_HEADER), "#");
+ color_printf_ln(color(WT_STATUS_HEADER), "# Initial commit");
+ color_printf_ln(color(WT_STATUS_HEADER), "#");
+ wt_status_print_initial(s);
+ }
+ else {
+ wt_status_print_updated(s);
+ discard_cache();
+ }
+
+ wt_status_print_changed(s);
+ wt_status_print_untracked(s);
+
+ if (s->verbose && !s->is_initial)
+ wt_status_print_verbose(s);
+ if (!s->commitable)
+ printf("%s\n", s->amend ? "# No changes" : "nothing to commit");
+}
+
+int git_status_config(const char *k, const char *v)
+{
+ if (!strcmp(k, "status.color")) {
+ wt_status_use_color = git_config_colorbool(k, v);
+ return 0;
+ }
+ if (!strncmp(k, "status.color.", 13)) {
+ int slot = parse_status_slot(k, 13);
+ color_parse(v, k, wt_status_colors[slot]);
+ }
+ return git_default_config(k, v);
+}
diff --git a/wt-status.h b/wt-status.h
new file mode 100644
index 0000000000..0a5a5b7ba9
--- /dev/null
+++ b/wt-status.h
@@ -0,0 +1,25 @@
+#ifndef STATUS_H
+#define STATUS_H
+
+enum color_wt_status {
+ WT_STATUS_HEADER,
+ WT_STATUS_UPDATED,
+ WT_STATUS_CHANGED,
+ WT_STATUS_UNTRACKED,
+};
+
+struct wt_status {
+ int is_initial;
+ char *branch;
+ const char *reference;
+ int commitable;
+ int verbose;
+ int amend;
+ int untracked;
+};
+
+int git_status_config(const char *var, const char *value);
+void wt_status_prepare(struct wt_status *s);
+void wt_status_print(struct wt_status *s);
+
+#endif /* STATUS_H */
diff --git a/xdiff-interface.c b/xdiff-interface.c
index 6a82da73b6..08602f5221 100644
--- a/xdiff-interface.c
+++ b/xdiff-interface.c
@@ -69,9 +69,9 @@ int xdiff_outf(void *priv_, mmbuffer_t *mb, int nbuf)
for (i = 0; i < nbuf; i++) {
if (mb[i].ptr[mb[i].size-1] != '\n') {
/* Incomplete line */
- priv->remainder = realloc(priv->remainder,
- priv->remainder_size +
- mb[i].size);
+ priv->remainder = xrealloc(priv->remainder,
+ priv->remainder_size +
+ mb[i].size);
memcpy(priv->remainder + priv->remainder_size,
mb[i].ptr, mb[i].size);
priv->remainder_size += mb[i].size;
@@ -83,9 +83,9 @@ int xdiff_outf(void *priv_, mmbuffer_t *mb, int nbuf)
consume_one(priv, mb[i].ptr, mb[i].size);
continue;
}
- priv->remainder = realloc(priv->remainder,
- priv->remainder_size +
- mb[i].size);
+ priv->remainder = xrealloc(priv->remainder,
+ priv->remainder_size +
+ mb[i].size);
memcpy(priv->remainder + priv->remainder_size,
mb[i].ptr, mb[i].size);
consume_one(priv, priv->remainder,
diff --git a/xdiff/xemit.c b/xdiff/xemit.c
index 714c563547..e291dc7608 100644
--- a/xdiff/xemit.c
+++ b/xdiff/xemit.c
@@ -86,11 +86,10 @@ static void xdl_find_func(xdfile_t *xf, long i, char *buf, long sz, long *ll) {
if (len > 0 &&
(isalpha((unsigned char)*rec) || /* identifier? */
*rec == '_' || /* also identifier? */
- *rec == '(' || /* lisp defun? */
- *rec == '#')) { /* #define? */
+ *rec == '$')) { /* mysterious GNU diff's invention */
if (len > sz)
len = sz;
- if (len && rec[len - 1] == '\n')
+ while (0 < len && isspace((unsigned char)rec[len - 1]))
len--;
memcpy(buf, rec, len);
*ll = len;
@@ -119,7 +118,7 @@ int xdl_emit_diff(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb,
xdemitconf_t const *xecfg) {
long s1, s2, e1, e2, lctx;
xdchange_t *xch, *xche;
- char funcbuf[40];
+ char funcbuf[80];
long funclen = 0;
if (xecfg->flags & XDL_EMIT_COMMON)
diff --git a/xdiff/xmacros.h b/xdiff/xmacros.h
index 4c2fde80c1..e2cd2023b3 100644
--- a/xdiff/xmacros.h
+++ b/xdiff/xmacros.h
@@ -24,14 +24,15 @@
#define XMACROS_H
-#define GR_PRIME 0x9e370001UL
#define XDL_MIN(a, b) ((a) < (b) ? (a): (b))
#define XDL_MAX(a, b) ((a) > (b) ? (a): (b))
#define XDL_ABS(v) ((v) >= 0 ? (v): -(v))
#define XDL_ISDIGIT(c) ((c) >= '0' && (c) <= '9')
-#define XDL_HASHLONG(v, b) (((unsigned long)(v) * GR_PRIME) >> ((CHAR_BIT * sizeof(unsigned long)) - (b)))
+#define XDL_ADDBITS(v,b) ((v) + ((v) >> (b)))
+#define XDL_MASKBITS(b) ((1UL << (b)) - 1)
+#define XDL_HASHLONG(v,b) (XDL_ADDBITS((unsigned long)(v), b) & XDL_MASKBITS(b))
#define XDL_PTRFREE(p) do { if (p) { xdl_free(p); (p) = NULL; } } while (0)
#define XDL_LE32_PUT(p, v) \
do { \
diff --git a/xdiff/xutils.c b/xdiff/xutils.c
index f7bdd395ad..9e4bb47ee9 100644
--- a/xdiff/xutils.c
+++ b/xdiff/xutils.c
@@ -191,36 +191,30 @@ int xdl_recmatch(const char *l1, long s1, const char *l2, long s2, long flags)
int i1, i2;
if (flags & XDF_IGNORE_WHITESPACE) {
- for (i1 = i2 = 0; i1 < s1 && i2 < s2; i1++, i2++) {
+ for (i1 = i2 = 0; i1 < s1 && i2 < s2; ) {
if (isspace(l1[i1]))
while (isspace(l1[i1]) && i1 < s1)
i1++;
- else if (isspace(l2[i2]))
+ if (isspace(l2[i2]))
while (isspace(l2[i2]) && i2 < s2)
i2++;
- else if (l1[i1] != l2[i2])
- return l2[i2] - l1[i1];
+ if (i1 < s1 && i2 < s2 && l1[i1++] != l2[i2++])
+ return 0;
}
- if (i1 >= s1)
- return 1;
- else if (i2 >= s2)
- return -1;
+ return (i1 >= s1 && i2 >= s2);
} else if (flags & XDF_IGNORE_WHITESPACE_CHANGE) {
- for (i1 = i2 = 0; i1 < s1 && i2 < s2; i1++, i2++) {
+ for (i1 = i2 = 0; i1 < s1 && i2 < s2; ) {
if (isspace(l1[i1])) {
if (!isspace(l2[i2]))
- return -1;
+ return 0;
while (isspace(l1[i1]) && i1 < s1)
i1++;
while (isspace(l2[i2]) && i2 < s2)
i2++;
- } else if (l1[i1] != l2[i2])
- return l2[i2] - l1[i1];
+ } else if (l1[i1++] != l2[i2++])
+ return 0;
}
- if (i1 >= s1)
- return 1;
- else if (i2 >= s2)
- return -1;
+ return (i1 >= s1 && i2 >= s2);
} else
return s1 == s2 && !memcmp(l1, l2, s1);
@@ -233,7 +227,8 @@ unsigned long xdl_hash_record(char const **data, char const *top, long flags) {
for (; ptr < top && *ptr != '\n'; ptr++) {
if (isspace(*ptr) && (flags & XDF_WHITESPACE_FLAGS)) {
- while (ptr < top && isspace(*ptr) && ptr[1] != '\n')
+ while (ptr + 1 < top && isspace(ptr[1])
+ && ptr[1] != '\n')
ptr++;
if (flags & XDF_IGNORE_WHITESPACE_CHANGE) {
ha += (ha << 5);