summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore7
-rw-r--r--Documentation/Makefile4
-rw-r--r--Documentation/config.txt34
-rw-r--r--Documentation/core-tutorial.txt2
-rw-r--r--Documentation/cvs-migration.txt2
-rw-r--r--Documentation/diff-options.txt7
-rw-r--r--Documentation/git-apply.txt69
-rw-r--r--Documentation/git-archive.txt113
-rw-r--r--Documentation/git-blame.txt29
-rw-r--r--Documentation/git-daemon.txt135
-rw-r--r--Documentation/git-grep.txt9
-rw-r--r--Documentation/git-init-db.txt4
-rw-r--r--Documentation/git-ls-remote.txt18
-rw-r--r--Documentation/git-pack-objects.txt21
-rw-r--r--Documentation/git-receive-pack.txt2
-rw-r--r--Documentation/git-repack.txt12
-rw-r--r--Documentation/git-rev-list.txt266
-rw-r--r--Documentation/git-svn.txt260
-rw-r--r--Documentation/git-tar-tree.txt3
-rw-r--r--Documentation/git-unpack-objects.txt8
-rw-r--r--Documentation/git-update-index.txt4
-rw-r--r--Documentation/git-upload-archive.txt37
-rw-r--r--Documentation/git-upload-tar.txt39
-rw-r--r--Documentation/git.txt31
-rw-r--r--Documentation/gitk.txt69
-rw-r--r--Documentation/hooks.txt56
-rw-r--r--Documentation/technical/racy-git.txt193
-rw-r--r--Documentation/tutorial-2.txt2
-rw-r--r--Makefile53
-rw-r--r--archive-tar.c325
-rw-r--r--archive-zip.c333
-rw-r--r--archive.h47
-rw-r--r--blame.c14
-rw-r--r--builtin-add.c1
-rw-r--r--builtin-apply.c482
-rw-r--r--builtin-archive.c263
-rw-r--r--builtin-cat-file.c31
-rw-r--r--builtin-checkout-index.c2
-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.c39
-rw-r--r--builtin-grep.c593
-rw-r--r--builtin-init-db.c1
-rw-r--r--builtin-log.c7
-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.c133
-rw-r--r--builtin-name-rev.c4
-rw-r--r--builtin-pack-objects.c419
-rw-r--r--builtin-prune-packed.c2
-rw-r--r--builtin-prune.c4
-rw-r--r--builtin-push.c28
-rw-r--r--builtin-read-tree.c4
-rw-r--r--builtin-repo-config.c32
-rw-r--r--builtin-rev-list.c171
-rw-r--r--builtin-rev-parse.c10
-rw-r--r--builtin-rm.c2
-rw-r--r--builtin-runstatus.c36
-rw-r--r--builtin-show-branch.c18
-rw-r--r--builtin-symbolic-ref.c4
-rw-r--r--builtin-tar-tree.c439
-rw-r--r--builtin-unpack-objects.c73
-rw-r--r--builtin-update-index.c18
-rw-r--r--builtin-update-ref.c2
-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.h6
-rw-r--r--cache-tree.c6
-rw-r--r--cache.h59
-rw-r--r--check-racy.c28
-rw-r--r--color.c176
-rw-r--r--color.h12
-rw-r--r--combine-diff.c18
-rw-r--r--commit.c40
-rw-r--r--commit.h2
-rw-r--r--compat/inet_pton.c220
-rw-r--r--config.c18
-rw-r--r--config.mak.in1
-rw-r--r--configure.ac35
-rw-r--r--connect.c59
-rwxr-xr-xcontrib/completion/git-completion.bash324
-rw-r--r--contrib/emacs/vc-git.el2
-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.c8
-rw-r--r--csum-file.c2
-rw-r--r--daemon.c431
-rw-r--r--date.c132
-rw-r--r--describe.c14
-rw-r--r--diff-delta.c12
-rw-r--r--diff-lib.c32
-rw-r--r--diff.c523
-rw-r--r--diff.h12
-rw-r--r--diffcore-break.c2
-rw-r--r--diffcore-rename.c2
-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.c20
-rw-r--r--exec_cmd.c20
-rw-r--r--fetch-clone.c35
-rw-r--r--fetch-pack.c24
-rw-r--r--fetch.c9
-rw-r--r--fsck-objects.c39
-rwxr-xr-xgenerate-cmdlist.sh1
-rwxr-xr-xgit-am.sh23
-rwxr-xr-xgit-branch.sh10
-rwxr-xr-xgit-checkout.sh9
-rwxr-xr-xgit-cherry.sh3
-rwxr-xr-xgit-clone.sh6
-rwxr-xr-xgit-commit.sh107
-rw-r--r--git-compat-util.h18
-rwxr-xr-xgit-cvsexportcommit.perl2
-rwxr-xr-xgit-fetch.sh20
-rwxr-xr-xgit-ls-remote.sh6
-rwxr-xr-xgit-merge-recursive-old.py (renamed from git-merge-recursive.py)0
-rwxr-xr-xgit-merge.sh5
-rwxr-xr-xgit-parse-remote.sh43
-rwxr-xr-xgit-rebase.sh6
-rwxr-xr-xgit-repack.sh25
-rwxr-xr-xgit-reset.sh3
-rwxr-xr-xgit-resolve.sh4
-rwxr-xr-xgit-send-email.perl2
-rwxr-xr-xgit-svn.perl46
-rwxr-xr-xgit-svnimport.perl35
-rw-r--r--git.c47
-rwxr-xr-xgitk682
-rw-r--r--gitweb/git-favicon.pngbin0 -> 164 bytes
-rw-r--r--gitweb/gitweb.css31
-rwxr-xr-xgitweb/gitweb.perl2358
-rw-r--r--grep.c498
-rw-r--r--grep.h79
-rw-r--r--help.c4
-rw-r--r--http-fetch.c95
-rw-r--r--http-push.c49
-rw-r--r--http.c12
-rw-r--r--http.h4
-rw-r--r--imap-send.c37
-rw-r--r--index-pack.c16
-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.c82
-rw-r--r--merge-base.c2
-rw-r--r--merge-file.c2
-rw-r--r--merge-index.c5
-rw-r--r--merge-recursive.c1354
-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.c6
-rw-r--r--object.h11
-rw-r--r--pack-check.c25
-rw-r--r--pack-redundant.c18
-rw-r--r--pack.h2
-rw-r--r--patch-id.c2
-rw-r--r--path-list.c5
-rw-r--r--path.c10
-rw-r--r--peek-remote.c5
-rw-r--r--quote.c61
-rw-r--r--quote.h7
-rw-r--r--read-cache.c106
-rw-r--r--receive-pack.c28
-rw-r--r--refs.c20
-rw-r--r--revision.c258
-rw-r--r--revision.h14
-rw-r--r--rsh.c31
-rw-r--r--run-command.c8
-rw-r--r--send-pack.c123
-rw-r--r--server-info.c2
-rw-r--r--setup.c2
-rw-r--r--sha1_file.c507
-rw-r--r--sha1_name.c60
-rw-r--r--sideband.c74
-rw-r--r--sideband.h13
-rw-r--r--ssh-fetch.c10
-rw-r--r--ssh-upload.c4
-rwxr-xr-xt/t1400-update-ref.sh86
-rwxr-xr-xt/t3200-branch.sh12
-rwxr-xr-xt/t3403-rebase-skip.sh4
-rwxr-xr-xt/t3700-add.sh22
-rwxr-xr-xt/t4103-apply-binary.sh4
-rwxr-xr-xt/t4104-apply-boundary.sh115
-rwxr-xr-xt/t4116-apply-reverse.sh47
-rwxr-xr-xt/t4117-apply-reject.sh157
-rwxr-xr-xt/t5400-send-pack.sh14
-rwxr-xr-xt/t5510-fetch.sh69
-rwxr-xr-xt/t5710-info-alternate.sh2
-rwxr-xr-xt/t6001-rev-list-graft.sh113
-rwxr-xr-xt/t7001-mv.sh8
-rwxr-xr-xt/t7201-co.sh9
-rwxr-xr-xt/test-lib.sh12
-rw-r--r--trace.c149
-rw-r--r--tree-diff.c3
-rw-r--r--tree-walk.c4
-rw-r--r--tree.c2
-rw-r--r--unpack-trees.c6
-rw-r--r--upload-pack.c90
-rw-r--r--write_or_die.c45
-rw-r--r--wt-status.c276
-rw-r--r--wt-status.h25
-rw-r--r--xdiff-interface.c12
211 files changed, 12249 insertions, 4306 deletions
diff --git a/.gitignore b/.gitignore
index 55cd984..25eb463 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
@@ -62,7 +63,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
@@ -93,6 +96,7 @@ git-rev-list
git-rev-parse
git-revert
git-rm
+git-runstatus
git-send-email
git-send-pack
git-sh-setup
@@ -117,8 +121,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 +143,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 0d9ffb4..c00f5f6 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/config.txt b/Documentation/config.txt
index ce722a2..84e3891 100644
--- a/Documentation/config.txt
+++ b/Documentation/config.txt
@@ -119,6 +119,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 +202,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
@@ -225,6 +238,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 +280,10 @@ whatchanged.difftree::
imap::
The configuration variables in the 'imap' section are described
in gitlink:git-imap-send[1].
+
+receive.denyNonFastforwads::
+ 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 1185897..47505aa 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 d2b0bd3..6812683 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-options.txt b/Documentation/diff-options.txt
index b5d9763..7b7b9e8 100644
--- a/Documentation/diff-options.txt
+++ b/Documentation/diff-options.txt
@@ -10,8 +10,11 @@
--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.
--summary::
Output a condensed summary of extended header information
diff --git a/Documentation/git-apply.txt b/Documentation/git-apply.txt
index 2ff7494..d9137c7 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.
+--inacurate-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 0000000..031fcd5
--- /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 bfed945..e1f8944 100644
--- a/Documentation/git-blame.txt
+++ b/Documentation/git-blame.txt
@@ -3,21 +3,38 @@ 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]
+'git-blame' [-c] [-l] [-t] [-S <revs-file>] [--] <file> [<rev>]
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.
+
+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, --long::
Show long rev (Default: off).
@@ -26,7 +43,7 @@ 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].
-h, --help::
Show help message.
diff --git a/Documentation/git-daemon.txt b/Documentation/git-daemon.txt
index 0f7d274..d562232 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,12 @@ 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.
OPTIONS
-------
@@ -45,6 +52,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 +69,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 +118,101 @@ 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`.
+
+EXAMPLES
+--------
+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
+ --syslog --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
+ --syslog --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-grep.txt b/Documentation/git-grep.txt
index 7545dd9..d8af4d9 100644
--- a/Documentation/git-grep.txt
+++ b/Documentation/git-grep.txt
@@ -11,7 +11,7 @@ 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]
@@ -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
diff --git a/Documentation/git-init-db.txt b/Documentation/git-init-db.txt
index 63cd5da..ca7d09d 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-ls-remote.txt b/Documentation/git-ls-remote.txt
index ae4c1a2..c8a4c5a 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 4991f88..d4661dd 100644
--- a/Documentation/git-pack-objects.txt
+++ b/Documentation/git-pack-objects.txt
@@ -11,7 +11,7 @@ SYNOPSIS
[verse]
'git-pack-objects' [-q] [--no-reuse-delta] [--non-empty]
[--local] [--incremental] [--window=N] [--depth=N]
- {--stdout | base-name} < object-list
+ [--revs [--unpacked | --all]*] [--stdout | base-name] < object-list
DESCRIPTION
@@ -56,6 +56,24 @@ base-name::
Write the pack contents (what would have been written to
.pack file) out to the standard output.
+--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 specifed to be
+ included.
+
--window and --depth::
These two options affects how the objects contained in
the pack are stored using delta compression. The
@@ -103,6 +121,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-receive-pack.txt b/Documentation/git-receive-pack.txt
index f9457d4..0dfadc2 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 9516227..49f7e0a 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,16 @@ OPTIONS
Do not update the server information with
`git update-server-info`.
+--window=[N], --depth=[N]::
+ These two options affects 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.
+
+
Author
------
Written by Linus Torvalds <torvalds@osdl.org>
diff --git a/Documentation/git-rev-list.txt b/Documentation/git-rev-list.txt
index dd9fff1..00a95e2 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,258 @@ 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]
+
+--pretty[='<format>']::
+
+ Pretty print the contents of the commit logs in a given format,
+ where '<format>' can be one of 'raw', 'medium', 'short', 'full',
+ and 'oneline'. When left out the format default to 'medium'.
+
+--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-svn.txt b/Documentation/git-svn.txt
index 7d86809..1cfa3e3 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,38 @@ 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.
+'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.
- 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.
- See '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.
-commit::
+'commit'::
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 +71,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 +81,182 @@ 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.
+--
+
OPTIONS
-------
+--
+
-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.
- This can allow you to make partial mirrors when running fetch.
+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.
-::
--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 'commit' command.
- repo-config key: svn.rmdir
+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
-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 '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.
- 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.
+Both of these are only used with the 'commit' command.
- 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.authors-file
+
+-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::
- repo-config key: svn.authors-file
+This is only used with the 'dcommit' command.
+
+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.
+
+--
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.
+
+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.
- 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.
+Otherwise, do not enable this flag unless you know what you're
+doing.
- Versions of svn that do not support --ignore-externals are
- automatically detected and this flag will be automatically
- enabled for them.
+repo-config key: svn.noignoreexternals
- Otherwise, do not enable this flag unless you know what you're
- doing.
+--ignore-nodate::
+Only used with the 'fetch' command.
- repo-config key: svn.noignoreexternals
+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
~~~~~~~~~~~~~~
@@ -212,12 +274,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 +302,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.
@@ -241,6 +318,7 @@ 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,11 +329,15 @@ 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
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -310,6 +392,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 1e1c7fa..74a6fdd 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 c20b38b..ff6184b 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 3ae6e74..41bb7e1 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.
diff --git a/Documentation/git-upload-archive.txt b/Documentation/git-upload-archive.txt
new file mode 100644
index 0000000..388bb53
--- /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 394af62..0000000
--- 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 3de5fa9..2135b65 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
-----------
@@ -246,10 +247,6 @@ 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 +266,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 +302,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.
@@ -482,13 +485,6 @@ gitlink:git-stripspace[1]::
Filter out empty lines.
-Commands not yet documented
----------------------------
-
-gitlink:gitk[1]::
- The gitk repository browser.
-
-
Configuration Mechanism
-----------------------
@@ -637,9 +633,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 cb482bf..f1aeb07 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/hooks.txt b/Documentation/hooks.txt
index 898b4aa..517f49b 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/technical/racy-git.txt b/Documentation/technical/racy-git.txt
new file mode 100644
index 0000000..7597d04
--- /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 2f4fe12..42b6e7d 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/Makefile b/Makefile
index 1875965..dd33158 100644
--- a/Makefile
+++ b/Makefile
@@ -81,8 +81,6 @@ 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.
@@ -126,10 +124,14 @@ 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
export prefix bindir gitexecdir template_dir GIT_PYTHON_DIR
@@ -176,7 +178,7 @@ SCRIPT_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)) \
@@ -200,7 +202,12 @@ 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-blame$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 \
@@ -231,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
@@ -243,17 +250,22 @@ 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)
+ 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-apply.o \
+ builtin-archive.o \
builtin-cat-file.o \
builtin-checkout-index.o \
builtin-check-ref-format.o \
@@ -283,6 +295,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 \
@@ -290,7 +303,7 @@ 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
@@ -337,10 +350,12 @@ ifeq ($(uname_S),SunOS)
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
@@ -493,7 +508,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
@@ -514,6 +529,9 @@ 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
BASIC_CFLAGS += -DNO_ICONV
@@ -568,7 +586,8 @@ export prefix TAR INSTALL DESTDIR SHELL_PATH template_dir
### Build rules
-all: $(ALL_PROGRAMS) $(BUILT_INS) git$X gitk gitweb/gitweb.cgi
+all: $(ALL_PROGRAMS) $(BUILT_INS) git$X gitk gitweb/gitweb.cgi \
+ git-merge-recur$X
all: perl/Makefile
$(MAKE) -C perl
@@ -584,6 +603,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 $@
@@ -645,10 +667,14 @@ gitweb/gitweb.cgi: gitweb/gitweb.perl
-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' \
$< >$@+
chmod +x $@+
mv $@+ $@
@@ -892,7 +918,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" || \
diff --git a/archive-tar.c b/archive-tar.c
new file mode 100644
index 0000000..ff0f6e2
--- /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 0000000..3ffdad6
--- /dev/null
+++ b/archive-zip.c
@@ -0,0 +1,333 @@
+/*
+ * 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];
+};
+
+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];
+};
+
+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];
+};
+
+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 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, Z_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;
+ result = READ_TREE_RECURSIVE;
+ out = NULL;
+ uncompressed_size = 0;
+ compressed_size = 0;
+ } else if (S_ISREG(mode)) {
+ method = zlib_compression_level == 0 ? 0 : 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 = sizeof(struct zip_dir_header) + 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, 0);
+ copy_le16(dirent.version, 20);
+ 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, 0);
+ copy_le32(dirent.offset, zip_offset);
+ memcpy(zip_dir + zip_dir_offset, &dirent, sizeof(struct zip_dir_header));
+ zip_dir_offset += sizeof(struct zip_dir_header);
+ memcpy(zip_dir + zip_dir_offset, path, pathlen);
+ zip_dir_offset += pathlen;
+ zip_dir_entries++;
+
+ copy_le32(header.magic, 0x04034b50);
+ copy_le16(header.version, 20);
+ 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, sizeof(struct zip_local_header));
+ zip_offset += sizeof(struct zip_local_header);
+ 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, sizeof(struct zip_dir_trailer));
+ 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 0000000..16dcdb8
--- /dev/null
+++ b/archive.h
@@ -0,0 +1,47 @@
+#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 struct archiver archivers[];
+
+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
index 54a43d5..8cfd5d9 100644
--- a/blame.c
+++ b/blame.c
@@ -56,9 +56,9 @@ struct patch {
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;
+static int num_get_patch;
+static int num_commits;
+static int patch_time;
struct blame_diff_state {
struct xdiff_emit_state xm;
@@ -165,7 +165,7 @@ static int get_blob_sha1(struct tree *t, const char *pathname,
blame_file = pathname;
pathspec[0] = pathname;
pathspec[1] = NULL;
- memset(blob_sha1, 0, sizeof(blob_sha1));
+ hashclr(blob_sha1);
read_tree_recursive(t, "", 0, 0, pathspec, get_blob_sha1_internal);
for (i = 0; i < 20; i++) {
@@ -176,7 +176,7 @@ static int get_blob_sha1(struct tree *t, const char *pathname,
if (i == 20)
return -1;
- memcpy(sha1, blob_sha1, 20);
+ hashcpy(sha1, blob_sha1);
return 0;
}
@@ -191,7 +191,7 @@ static int get_blob_sha1_internal(const unsigned char *sha1, const char *base,
strcmp(blame_file + baselen, pathname))
return -1;
- memcpy(blob_sha1, sha1, 20);
+ hashcpy(blob_sha1, sha1);
return -1;
}
@@ -617,7 +617,7 @@ static void simplify_commit(struct rev_info *revs, struct commit *commit)
if (new_name) {
struct util_info* putil = get_util(p);
if (!putil->pathname)
- putil->pathname = strdup(new_name);
+ putil->pathname = xstrdup(new_name);
} else {
*pp = parent->next;
continue;
diff --git a/builtin-add.c b/builtin-add.c
index 0cb9c81..febb75e 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-apply.c b/builtin-apply.c
index 4573c9a..de5f855 100644
--- a/builtin-apply.c
+++ b/builtin-apply.c
@@ -27,23 +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 apply_in_reverse = 0;
-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 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,
@@ -51,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)
{
@@ -122,6 +124,7 @@ struct fragment {
unsigned long newpos, newlines;
const char *patch;
int size;
+ int rejected;
struct fragment *next;
};
@@ -138,6 +141,7 @@ 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;
+ int rejected;
unsigned long deflate_origlen;
int lines_added, lines_deleted;
int score;
@@ -150,6 +154,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)
@@ -606,9 +628,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':
@@ -834,12 +854,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)
{
@@ -856,31 +918,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);
@@ -902,25 +947,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;
@@ -953,12 +981,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)) {
@@ -969,9 +1003,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;
@@ -980,6 +1016,46 @@ 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;
+ if (patch->is_delete < 0 && !newlines)
+ patch->is_delete = 1;
+ }
+
+ 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;
}
@@ -1063,8 +1139,12 @@ static struct fragment *parse_binary_hunk(char **buf_p,
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.
*/
@@ -1107,8 +1187,7 @@ static struct fragment *parse_binary_hunk(char **buf_p,
return frag;
corrupt:
- if (data)
- free(data);
+ free(data);
*status_p = -1;
error("corrupt binary patch at line %d: %.*s",
linenr-1, llen-1, buffer);
@@ -1205,14 +1284,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);
}
@@ -1305,8 +1382,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)
@@ -1444,22 +1520,68 @@ 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;
}
- memcpy(output, patch + 1, plen);
+
+ 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;
+ }
+
+ 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 inaccurate_eof)
@@ -1537,14 +1659,25 @@ static int apply_one_fragment(struct buffer_desc *desc, struct fragment *frag, i
/*
* 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)
@@ -1557,8 +1690,10 @@ static int apply_one_fragment(struct buffer_desc *desc, struct fragment *frag, i
/* 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) {
@@ -1568,7 +1703,9 @@ static int apply_one_fragment(struct buffer_desc *desc, struct fragment *frag, i
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;
@@ -1618,7 +1755,7 @@ static int apply_binary_fragment(struct buffer_desc *desc, struct patch *patch)
"without the reverse hunk to '%s'",
patch->new_name
? patch->new_name : patch->old_name);
- fragment = fragment;
+ fragment = fragment->next;
}
data = (void*) fragment->patch;
switch (fragment->binary_patch_method) {
@@ -1649,11 +1786,6 @@ static int apply_binary(struct buffer_desc *desc, struct patch *patch)
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.
*/
@@ -1684,7 +1816,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;
@@ -1717,7 +1849,7 @@ static int apply_binary(struct buffer_desc *desc, struct patch *patch)
write_sha1_file_prepare(desc->buffer, desc->size, blob_type,
sha1, hdr, &hdrlen);
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;
@@ -1732,9 +1864,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->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;
@@ -1770,8 +1905,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)
@@ -1781,7 +1917,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;
@@ -1796,6 +1932,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;
@@ -1852,7 +1989,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
@@ -1865,7 +2002,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)
@@ -1882,7 +2020,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;
@@ -1901,24 +2039,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)
@@ -1934,7 +2071,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).",
@@ -2224,23 +2361,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;
@@ -2315,11 +2528,13 @@ static int apply_patch(int fd, const char *filename, int inaccurate_eof)
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);
@@ -2340,7 +2555,7 @@ static int apply_patch(int fd, const char *filename, int inaccurate_eof)
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);
@@ -2352,6 +2567,7 @@ int cmd_apply(int argc, const char **argv, const char *prefix)
int i;
int read_stdin = 1;
int inaccurate_eof = 0;
+ int errs = 0;
const char *whitespace_option = NULL;
@@ -2361,7 +2577,7 @@ int cmd_apply(int argc, const char **argv, const char *prefix)
int fd;
if (!strcmp(arg, "-")) {
- apply_patch(0, "<stdin>", inaccurate_eof);
+ errs |= apply_patch(0, "<stdin>", inaccurate_eof);
read_stdin = 0;
continue;
}
@@ -2387,8 +2603,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;
@@ -2442,6 +2657,18 @@ int cmd_apply(int argc, const char **argv, const char *prefix)
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")) {
inaccurate_eof = 1;
continue;
@@ -2462,18 +2689,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, inaccurate_eof);
+ errs |= apply_patch(fd, arg, inaccurate_eof);
close(fd);
}
set_default_whitespace_mode(whitespace_option);
if (read_stdin)
- apply_patch(0, "<stdin>", 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");
}
@@ -2501,5 +2729,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 0000000..6dabdee
--- /dev/null
+++ b/builtin-archive.c
@@ -0,0 +1,263 @@
+/*
+ * 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...]";
+
+struct archiver archivers[] = {
+ {
+ .name = "tar",
+ .write_archive = write_tar_archive,
+ },
+ {
+ .name = "zip",
+ .write_archive = write_zip_archive,
+ .parse_extra = 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, buf, sizeof(buf));
+ 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)) {
+ memcpy(ar, &archivers[i], sizeof(struct archiver));
+ 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);
+
+ setlinebuf(stderr);
+
+ 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-cat-file.c b/builtin-cat-file.c
index df009ad..6c16bfa 100644
--- a/builtin-cat-file.c
+++ b/builtin-cat-file.c
@@ -9,23 +9,6 @@
#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 void pprint_tag(const unsigned char *sha1, const char *buf, unsigned long size)
{
/* the parser in tag.c is useless here. */
@@ -42,7 +25,7 @@ static void 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 void 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,7 +73,7 @@ static void pprint_tag(const unsigned char *sha1, const char *buf, unsigned long
* remainder as is.
*/
if (cp < endp)
- flush_buffer(cp, endp - cp);
+ write_or_die(1, cp, endp - cp);
}
int cmd_cat_file(int argc, const char **argv, const char *prefix)
@@ -162,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 6b55f93..b097c88 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;
diff --git a/builtin-commit-tree.c b/builtin-commit-tree.c
index 9c98796..e2e690a 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 1d3729a..73c5982 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 5960e08..70bb898 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 82afce7..a659020 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 485ede7..c407c03 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);
}
@@ -278,7 +277,7 @@ 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"));
+ head = xstrdup(git_path("HEAD"));
current_branch = resolve_ref(head, head_sha1, 1);
current_branch += strlen(head) - 4;
free((char *)head);
diff --git a/builtin-grep.c b/builtin-grep.c
index 3ec99b7..4205e5d 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,556 +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;
- 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)
-{
- 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;
@@ -751,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)
@@ -872,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;
}
@@ -971,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;
@@ -1030,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) ||
@@ -1108,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++;
@@ -1117,27 +575,32 @@ 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("-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;
@@ -1159,8 +622,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 {
@@ -1177,8 +640,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++) {
@@ -1233,5 +695,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 5085018..c3ed1ce 100644
--- a/builtin-init-db.c
+++ b/builtin-init-db.c
@@ -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 691cf3a..9d1ceae 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;
@@ -348,6 +348,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 +384,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;
diff --git a/builtin-ls-files.c b/builtin-ls-files.c
index 11386c4..ad8c41e 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 261147f..201defd 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 24a4fc6..b8d7dbc 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 a731f8d..4d21d88 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);
}
diff --git a/builtin-name-rev.c b/builtin-name-rev.c
index 571bba4..52886b6 100644
--- a/builtin-name-rev.c
+++ b/builtin-name-rev.c
@@ -75,7 +75,7 @@ copy_data:
}
}
-static int tags_only = 0;
+static int tags_only;
static int name_ref(const char *path, const unsigned char *sha1)
{
@@ -100,7 +100,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;
}
diff --git a/builtin-pack-objects.c b/builtin-pack-objects.c
index 2f99212..96c069a 100644
--- a/builtin-pack-objects.c
+++ b/builtin-pack-objects.c
@@ -9,10 +9,13 @@
#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] [--no-reuse-delta] [--non-empty] [--local] [--incremental] [--window=N] [--depth=N] [--revs [--unpacked | --all]*] [--stdout | base-name] <ref-list | <object-list]";
struct object_entry {
unsigned char sha1[20];
@@ -53,18 +56,20 @@ 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 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,8 +77,8 @@ 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
@@ -90,15 +95,15 @@ struct pack_revindex {
struct packed_git *p;
unsigned long *revindex;
} *pack_revindex = NULL;
-static int pack_revindex_hashsz = 0;
+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)
{
@@ -242,6 +247,82 @@ static int encode_header(enum object_type type, unsigned long size, unsigned cha
return n;
}
+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;
+}
+
+/*
+ * we are going to reuse the existing pack entry data. make
+ * sure it is not corrupt.
+ */
+static int revalidate_pack_entry(struct object_entry *entry, unsigned char *data, unsigned long len)
+{
+ enum object_type type;
+ unsigned long size, used;
+
+ if (pack_to_stdout)
+ return 0;
+
+ /* the caller has already called use_packed_git() for us,
+ * so it is safe to access the pack data from mmapped location.
+ * make sure the entry inflates correctly.
+ */
+ used = unpack_object_header_gently(data, len, &type, &size);
+ if (!used)
+ return -1;
+ if (type == OBJ_DELTA)
+ used += 20; /* skip base object name */
+ data += used;
+ len -= used;
+ return check_inflate(data, len, entry->size);
+}
+
+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)
{
@@ -276,6 +357,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 +370,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));
@@ -319,6 +403,9 @@ static unsigned long write_object(struct sha1file *f,
datalen = find_packed_object_size(p, entry->in_pack_offset);
buf = (char *) p->pack_base + entry->in_pack_offset;
+
+ if (revalidate_pack_entry(entry, buf, datalen))
+ die("corrupt delta in pack %s", sha1_to_hex(entry->sha1));
sha1write(f, buf, datalen);
unuse_packed_git(p);
hdrlen = 0; /* not really */
@@ -441,7 +528,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 +597,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 +621,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 +694,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 +736,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 +842,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 +871,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 +889,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;
}
@@ -931,7 +1021,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 +1253,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 +1333,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 +1439,99 @@ 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("--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;
+ }
+ if (!strcmp("--revs", arg)) {
+ use_internal_rev_list = 1;
+ continue;
}
- if (base_name)
- usage(pack_usage);
- base_name = arg;
+ 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 +1539,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,7 +1562,7 @@ 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)
diff --git a/builtin-prune-packed.c b/builtin-prune-packed.c
index d3dd94d..960db49 100644
--- a/builtin-prune-packed.c
+++ b/builtin-prune-packed.c
@@ -19,7 +19,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)
diff --git a/builtin-prune.c b/builtin-prune.c
index 89ec7f1..6228c79 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;
diff --git a/builtin-push.c b/builtin-push.c
index c09ff2f..c43f256 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;
+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)
{
@@ -33,7 +33,7 @@ static int expand_one_ref(const char *ref, const unsigned char *sha1)
ref += 5;
if (!strncmp(ref, "tags/", 5))
- add_refspec(strdup(ref));
+ add_refspec(xstrdup(ref));
return 0;
}
@@ -100,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)
@@ -125,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;
}
@@ -232,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;
@@ -248,10 +248,10 @@ 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)
+ 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:
@@ -262,7 +262,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;
diff --git a/builtin-read-tree.c b/builtin-read-tree.c
index 8da8acb..c1867d2 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 1d93739..f60cee1 100644
--- a/builtin-repo-config.c
+++ b/builtin-repo-config.c
@@ -5,14 +5,14 @@
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;
+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;
@@ -122,10 +122,8 @@ static int get_value(const char* key_, const char* regex_)
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;
}
diff --git a/builtin-rev-list.c b/builtin-rev-list.c
index 0dee173..fb7fc92 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 aca4a36..fd3ccc8 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,
diff --git a/builtin-rm.c b/builtin-rm.c
index 593d867..33d04bd 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 0000000..303c556
--- /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]";
+
+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 2a1b848..578c9fa 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))
@@ -364,7 +364,7 @@ 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;
}
@@ -378,7 +378,7 @@ 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);
}
@@ -442,7 +442,7 @@ static int rev_is_head(char *head_path, int headlen, char *name,
{
int namelen;
if ((!head_path[0]) ||
- (head_sha1 && sha1 && memcmp(head_sha1, sha1, 20)))
+ (head_sha1 && sha1 && hashcmp(head_sha1, sha1)))
return 0;
namelen = strlen(name);
if ((headlen < namelen) ||
@@ -521,7 +521,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;
}
diff --git a/builtin-symbolic-ref.c b/builtin-symbolic-ref.c
index b4ec6f2..1d3a5e2 100644
--- a/builtin-symbolic-ref.c
+++ b/builtin-symbolic-ref.c
@@ -7,7 +7,7 @@ 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_HEAD = xstrdup(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 */
@@ -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(xstrdup(git_path("%s", argv[1])), argv[2]);
break;
default:
usage(git_symbolic_ref_usage);
diff --git a/builtin-tar-tree.c b/builtin-tar-tree.c
index 215892b..4d4cfec 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 63f4b8e..4f96bca 100644
--- a/builtin-unpack-objects.c
+++ b/builtin-unpack-objects.c
@@ -10,12 +10,12 @@
#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;
static SHA_CTX ctx;
/*
@@ -26,8 +26,6 @@ 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) {
@@ -73,8 +71,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;
}
@@ -95,7 +100,7 @@ static void add_delta_to_list(unsigned char *base_sha1, void *delta, unsigned lo
{
struct delta_info *info = xmalloc(sizeof(*info));
- memcpy(info->base_sha1, base_sha1, 20);
+ hashcpy(info->base_sha1, base_sha1);
info->size = size;
info->delta = delta;
info->next = delta_list;
@@ -112,9 +117,9 @@ static void write_object(void *buf, unsigned long size, const char *type)
added_object(sha1, 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(const char *type,
+ void *base, unsigned long base_size,
+ void *delta, unsigned long delta_size)
{
void *result;
unsigned long result_size;
@@ -127,7 +132,6 @@ static int resolve_delta(const char *type,
free(delta);
write_object(result, result_size, type);
free(result);
- return 0;
}
static void added_object(unsigned char *sha1, const char *type, void *data, unsigned long size)
@@ -136,7 +140,7 @@ static void added_object(unsigned char *sha1, const char *type, void *data, unsi
struct delta_info *info;
while ((info = *p) != NULL) {
- if (!memcmp(info->base_sha1, sha1, 20)) {
+ if (!hashcmp(info->base_sha1, sha1)) {
*p = info->next;
p = &delta_list;
resolve_delta(type, data, size, info->delta, info->size);
@@ -147,7 +151,7 @@ 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)
{
void *buf = get_data(size);
const char *type;
@@ -159,39 +163,42 @@ 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)
+ if (!dry_run && buf)
write_object(buf, size, type);
free(buf);
- return 0;
}
-static int unpack_delta_entry(unsigned long delta_size)
+static void unpack_delta_entry(unsigned long delta_size)
{
void *delta_data, *base;
unsigned long base_size;
char type[20];
unsigned char base_sha1[20];
- int result;
- memcpy(base_sha1, fill(20), 20);
+ hashcpy(base_sha1, fill(20));
use(20);
delta_data = get_data(delta_size);
- if (dry_run) {
+ if (dry_run || !delta_data) {
free(delta_data);
- return 0;
+ return;
}
if (!has_sha1_file(base_sha1)) {
add_delta_to_list(base_sha1, delta_data, delta_size);
- return 0;
+ 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(type, base, base_size, delta_data, delta_size);
free(base);
- return result;
}
static void unpack_one(unsigned nr, unsigned total)
@@ -238,7 +245,11 @@ static void unpack_one(unsigned nr, unsigned total)
unpack_delta_entry(size);
return;
default:
- die("bad object type %d", type);
+ error("bad object type %d", type);
+ has_errors = 1;
+ if (recover)
+ return;
+ exit(1);
}
}
@@ -282,6 +293,10 @@ int cmd_unpack_objects(int argc, const char **argv, const char *prefix)
quiet = 1;
continue;
}
+ if (!strcmp(arg, "-r")) {
+ recover = 1;
+ continue;
+ }
usage(unpack_usage);
}
@@ -292,7 +307,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 +323,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 d2556f3..a3c0a45 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 force to 644.
*/
int pos = cache_name_pos(path, namelen);
if (0 <= pos)
ce->ce_mode = active_cache[pos]->ce_mode;
+ else
+ ce->ce_mode = create_ce_mode(S_IFREG | 0644);
}
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);
@@ -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 5bd7182..90a3da5 100644
--- a/builtin-update-ref.c
+++ b/builtin-update-ref.c
@@ -44,7 +44,7 @@ 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);
+ hashclr(oldsha1);
if (oldval && get_sha1(oldval, oldsha1))
die("%s: not a valid old SHA1", oldval);
diff --git a/builtin-upload-archive.c b/builtin-upload-archive.c
new file mode 100644
index 0000000..45c92e1
--- /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 7b401bb..0000000
--- 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 ca06149..50670dc 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 ade58c4..f9fa9ff 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[];
@@ -15,6 +14,7 @@ extern int write_tree(unsigned char *sha1, int missing_ok, const char *prefix);
extern int cmd_add(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_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);
@@ -47,6 +47,7 @@ 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_runstatus(int argc, const char **argv, const char *prefix);
extern int cmd_show_branch(int argc, const char **argv, const char *prefix);
extern int cmd_show(int argc, const char **argv, const char *prefix);
extern int cmd_stripspace(int argc, const char **argv, const char *prefix);
@@ -55,6 +56,7 @@ 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);
diff --git a/cache-tree.c b/cache-tree.c
index d9f7e1e..323c68a 100644
--- a/cache-tree.c
+++ b/cache-tree.c
@@ -335,7 +335,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
@@ -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 af77402..97debd0 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 */
@@ -187,6 +188,7 @@ extern int prefer_symlink_refs;
extern int log_all_ref_updates;
extern int warn_ambiguous_refs;
extern int shared_repository;
+extern int deny_non_fast_forwards;
extern const char *apply_default_whitespace;
extern int zlib_compression_level;
@@ -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);
@@ -240,9 +258,9 @@ extern int check_sha1_signature(const unsigned char *sha1, void *buf, unsigned l
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 +268,23 @@ 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/6 for future expansion */
+ OBJ_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
@@ -270,7 +305,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 +366,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,
@@ -355,9 +390,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,6 +416,8 @@ extern char git_default_name[MAX_GITNAME];
extern char git_commit_encoding[MAX_ENCODING_LENGTH];
extern int copy_fd(int ifd, int ofd);
+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);
/* Finish off pack transfer receiving end */
extern int receive_unpack_pack(int fd[2], const char *me, int quiet, int);
@@ -405,4 +443,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-racy.c b/check-racy.c
new file mode 100644
index 0000000..d6a08b4
--- /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 0000000..d8c8399
--- /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 0000000..88bb8ff
--- /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 4c6bfed..46d9121 100644
--- a/combine-diff.c
+++ b/combine-diff.c
@@ -31,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;
@@ -56,8 +56,7 @@ static struct combine_diff_path *intersect_paths(struct combine_diff_path *curr,
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;
@@ -94,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);
@@ -611,7 +610,7 @@ static void show_patch_diff(struct combine_diff_path *elem, int num_parent,
struct sline *sline; /* survived lines */
int mode_differs = 0;
int i, show_hunks;
- int working_tree_file = !memcmp(elem->sha1, null_sha1, 20);
+ int working_tree_file = is_null_sha1(elem->sha1);
int abbrev = opt->full_index ? 40 : DEFAULT_ABBREV;
mmfile_t result_file;
@@ -688,8 +687,8 @@ static void 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;
}
@@ -927,6 +926,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 77f0ca1..5b6e082 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' */
@@ -557,7 +560,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 +652,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 +735,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 779ed82..fc13de9 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 0000000..5704e0d
--- /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/config.c b/config.c
index 82b3562..e8f0caf 100644
--- a/config.c
+++ b/config.c
@@ -350,19 +350,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 +545,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 +565,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];
@@ -634,7 +633,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 +733,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 fecae80..1cafa19 100644
--- a/config.mak.in
+++ b/config.mak.in
@@ -38,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 8192826..cff5722 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])],
@@ -100,7 +99,6 @@ AC_PROG_CC([cc gcc])
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).
@@ -204,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],
@@ -260,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.
@@ -329,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 4422a0d..c55a20a 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;
@@ -208,7 +208,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 +318,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 +493,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 +599,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 +668,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 +679,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 +704,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 +754,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 0000000..d9cb17d
--- /dev/null
+++ b/contrib/completion/git-completion.bash
@@ -0,0 +1,324 @@
+#
+# 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
+#
+
+__git_refs ()
+{
+ local cmd i is_hash=y
+ if [ -d "$1" ]; then
+ cmd=git-peek-remote
+ else
+ cmd=git-ls-remote
+ fi
+ for i in $($cmd "$1" 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
+ if [ -d "$1" ]; then
+ cmd=git-peek-remote
+ else
+ cmd=git-ls-remote
+ fi
+ for i in $($cmd "$1" 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 REVERTGLOB=$(shopt -p nullglob)
+ shopt -s nullglob
+ for i in .git/remotes/*; do
+ echo ${i#.git/remotes/}
+ done
+ $REVERTGLOB
+}
+
+__git_complete_file ()
+{
+ local cur="${COMP_WORDS[COMP_CWORD]}"
+ case "$cur" in
+ ?*:*)
+ local pfx ls ref="$(echo "$cur" | sed 's,:.*$,,')"
+ cur="$(echo "$cur" | sed 's,^.*:,,')"
+ case "$cur" in
+ ?*/*)
+ pfx="$(echo "$cur" | sed 's,/[^/]*$,,')"
+ cur="$(echo "$cur" | sed 's,^.*/,,')"
+ ls="$ref:$pfx"
+ pfx="$pfx/"
+ ;;
+ *)
+ ls="$ref"
+ ;;
+ esac
+ COMPREPLY=($(compgen -P "$pfx" \
+ -W "$(git-ls-tree "$ls" \
+ | sed '/^100... blob /s,^.* ,,
+ /^040000 tree /{
+ s,^.* ,,
+ s,$,/,
+ }
+ s/^.* //')" \
+ -- "$cur"))
+ ;;
+ *)
+ COMPREPLY=($(compgen -W "$(__git_refs .)" -- "$cur"))
+ ;;
+ esac
+}
+
+_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=$(echo "$cur" | sed 's/^.*://')
+ 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 cur="${COMP_WORDS[COMP_CWORD]}"
+ case "$cur" in
+ *..*)
+ local pfx=$(echo "$cur" | sed 's/\.\..*$/../')
+ cur=$(echo "$cur" | sed 's/^.*\.\.//')
+ 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=$(echo "$cur" | sed 's/^.*://')
+ COMPREPLY=($(compgen -W "$(__git_refs "$remote")" -- "$cur"))
+ ;;
+ *)
+ COMPREPLY=($(compgen -W "$(__git_refs2 .)" -- "$cur"))
+ ;;
+ esac
+ ;;
+ esac
+}
+
+_git_show ()
+{
+ local cur="${COMP_WORDS[COMP_CWORD]}"
+ COMPREPLY=($(compgen -W "$(__git_refs .)" -- "$cur"))
+}
+
+_git ()
+{
+ if [ $COMP_CWORD = 1 ]; then
+ COMPREPLY=($(compgen \
+ -W "--version $(git help -a|egrep '^ ')" \
+ -- "${COMP_WORDS[COMP_CWORD]}"))
+ else
+ case "${COMP_WORDS[1]}" 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 ;;
+ pull) _git_pull ;;
+ push) _git_push ;;
+ show) _git_show ;;
+ show-branch) _git_log ;;
+ whatchanged) _git_log ;;
+ *) COMPREPLY=() ;;
+ esac
+ fi
+}
+
+_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_show git-show
+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.
+#
+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-whatchanged.exe
diff --git a/contrib/emacs/vc-git.el b/contrib/emacs/vc-git.el
index 3f6ed69..4a8f790 100644
--- a/contrib/emacs/vc-git.el
+++ b/contrib/emacs/vc-git.el
@@ -54,7 +54,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"))))))))
diff --git a/contrib/gitview/gitview.txt b/contrib/gitview/gitview.txt
index 6924df2..77c29de 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 0000000..9e7881f
--- /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 0000000..a9de09f
--- /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 168771e..631678b 100644
--- a/convert-objects.c
+++ b/convert-objects.c
@@ -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 e227889..b7174c6 100644
--- a/csum-file.c
+++ b/csum-file.c
@@ -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 810837f..ad84928 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,141 @@ 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;
+ 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] = "";
@@ -310,8 +547,32 @@ static int execute(struct sockaddr *addr)
if (len && line[len-1] == '\n')
line[--len] = 0;
- if (!strncmp("git-upload-pack ", line, 16))
- return upload(line+16);
+ /*
+ * 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 (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 +594,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 +765,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 +819,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 +834,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 +963,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 +1000,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 +1050,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 +1075,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 +1110,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 +1166,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 66be23a..1825922 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 324ca89..ab192f8 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) {
@@ -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 7da9205..51df460 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;
@@ -308,8 +308,8 @@ create_delta(const struct delta_index *index,
continue;
if (ref_size > top - src)
ref_size = top - src;
- if (ref_size > 0x10000)
- ref_size = 0x10000;
+ if (ref_size > 0xffffff)
+ ref_size = 0xffffff;
if (ref_size <= msize)
break;
while (ref_size-- && *src++ == *ref)
@@ -318,6 +318,8 @@ create_delta(const struct delta_index *index,
/* this is our best match so far */
msize = ref - entry->ptr;
moff = entry->ptr - ref_data;
+ if (msize >= 0x10000)
+ break; /* this is good enough */
}
}
@@ -381,6 +383,8 @@ create_delta(const struct delta_index *index,
if (msize & 0xff) { out[outpos++] = msize; i |= 0x10; }
msize >>= 8;
if (msize & 0xff) { out[outpos++] = msize; i |= 0x20; }
+ msize >>= 8;
+ if (msize & 0xff) { out[outpos++] = msize; i |= 0x40; }
*op = i;
}
@@ -392,7 +396,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 116b5a9..fc69fb9 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 2327e60..fb82432 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,32 @@ 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);
}
struct checkdiff_t {
@@ -838,7 +859,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 +870,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,6 +918,13 @@ 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)
{
@@ -1101,8 +1128,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);
}
}
@@ -1140,7 +1167,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
@@ -1168,7 +1195,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) {
@@ -1194,7 +1221,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;
}
@@ -1516,7 +1543,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)
@@ -1579,9 +1606,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),
@@ -1795,8 +1828,33 @@ 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 (!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"))
@@ -1812,7 +1870,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;
@@ -2098,7 +2156,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. */
@@ -2237,7 +2295,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 {
@@ -2554,7 +2612,7 @@ void diff_flush(struct diff_options *options)
if (check_pair_status(p))
diff_flush_stat(p, options, &diffstat);
}
- show_stats(&diffstat);
+ show_stats(&diffstat, options);
separator++;
}
@@ -2581,6 +2639,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 b007240..b48c991 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,6 +21,9 @@ 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
@@ -35,6 +39,8 @@ typedef void (*add_remove_fn_t)(struct diff_options *options,
*/
#define DIFF_FORMAT_NO_OUTPUT 0x0080
+#define DIFF_FORMAT_CALLBACK 0x0100
+
struct diff_options {
const char *filter;
const char *orderfile;
@@ -63,11 +69,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 +89,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);
diff --git a/diffcore-break.c b/diffcore-break.c
index ed0e14c..acb18db 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 0ec488a..ef23901 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;
diff --git a/dir.c b/dir.c
index 092d077..96389b3 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 56a1b7f..313f8ab 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 1ccaf51..1f73f1e 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 793724f..b2ea0ef 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 87162b2..63b1d15 100644
--- a/environment.c
+++ b/environment.c
@@ -13,20 +13,22 @@ 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;
+int deny_non_fast_forwards = 0;
+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 +48,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 62f51fc..5d6a124 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
index 5e84c46..b632ca0 100644
--- a/fetch-clone.c
+++ b/fetch-clone.c
@@ -1,6 +1,7 @@
#include "cache.h"
#include "exec_cmd.h"
#include "pkt-line.h"
+#include "sideband.h"
#include <sys/wait.h>
#include <sys/time.h>
@@ -44,9 +45,8 @@ static int finish_pack(const char *pack_tmp_name, const char *me)
for (;;) {
int status, code;
- int retval = waitpid(pid, &status, 0);
- if (retval < 0) {
+ if (waitpid(pid, &status, 0) < 0) {
if (errno == EINTR)
continue;
error("waitpid failed (%s)", strerror(errno));
@@ -115,36 +115,13 @@ static pid_t setup_sideband(int sideband, const char *me, int fd[2], int xd[2])
die("%s: unable to fork off sideband demultiplexer", me);
if (!side_pid) {
/* subprocess */
+ char buf[LARGE_PACKET_MAX];
+
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));
- }
- }
+ if (recv_sideband(me, xd[0], fd[1], 2, buf, sizeof(buf)))
+ exit(1);
exit(0);
}
close(xd[0]);
diff --git a/fetch-pack.c b/fetch-pack.c
index b7824db..e8708aa 100644
--- a/fetch-pack.c
+++ b/fetch-pack.c
@@ -24,8 +24,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)
{
@@ -166,10 +166,11 @@ 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\n",
sha1_to_hex(remote),
(multi_ack ? " multi_ack" : ""),
- (use_sideband ? " side-band" : ""),
+ (use_sideband == 2 ? " side-band-64k" : ""),
+ (use_sideband == 1 ? " side-band" : ""),
(use_thin_pack ? " thin-pack" : ""));
else
packet_write(fd[1], "want %s\n", sha1_to_hex(remote));
@@ -250,7 +251,7 @@ 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)
{
@@ -404,7 +405,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,
@@ -426,7 +427,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;
@@ -519,7 +525,7 @@ int main(int argc, char **argv)
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 +540,5 @@ int main(int argc, char **argv)
}
}
- return ret;
+ return !!ret;
}
diff --git a/fetch.c b/fetch.c
index aeb6bf2..34df8d3 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));
@@ -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;
@@ -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 4ba3377..4d994f3 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();
@@ -400,7 +400,7 @@ static void fsck_dir(int i, char *path)
closedir(dir);
}
-static int default_refs = 0;
+static int default_refs;
static int fsck_handle_ref(const char *refname, const unsigned char *sha1)
{
@@ -425,8 +425,23 @@ 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");
+
+ /*
+ * 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)
@@ -443,7 +458,7 @@ 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_HEAD = xstrdup(git_path("HEAD"));
const char *git_refs_heads_master = resolve_ref(git_HEAD, sha1, 1);
int pfxlen = strlen(git_HEAD) - 4; /* strip .../.git/ part */
@@ -452,7 +467,7 @@ static int fsck_head_link(void)
if (strncmp(git_refs_heads_master + pfxlen, "refs/heads/", 11))
return error("HEAD points to something strange (%s)",
git_refs_heads_master + pfxlen);
- if (!memcmp(null_sha1, sha1, 20))
+ 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 ec1eda2..5450918 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 d0af786..afe322b 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-branch.sh b/git-branch.sh
index e0501ec..4f31903 100755
--- a/git-branch.sh
+++ b/git-branch.sh
@@ -112,6 +112,16 @@ rev=$(git-rev-parse --verify "$head") || exit
git-check-ref-format "heads/$branchname" ||
die "we do not like '$branchname' as a branch name."
+if [ -d "$GIT_DIR/refs/heads/$branchname" ]
+then
+ for refdir in `cd "$GIT_DIR" && \
+ find "refs/heads/$branchname" -type d | sort -r`
+ do
+ rmdir "$GIT_DIR/$refdir" || \
+ die "Could not delete '$refdir', there may still be a ref there."
+ done
+fi
+
if [ -e "$GIT_DIR/refs/heads/$branchname" ]
then
if test '' = "$force"
diff --git a/git-checkout.sh b/git-checkout.sh
index 580a9e8..dd47724 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=
@@ -139,6 +139,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
index f0e8831..8832573 100755
--- a/git-cherry.sh
+++ b/git-cherry.sh
@@ -51,9 +51,6 @@ 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
diff --git a/git-clone.sh b/git-clone.sh
index 7060bda..3998c55 100755
--- a/git-clone.sh
+++ b/git-clone.sh
@@ -31,6 +31,10 @@ clone_dumb_http () {
cd "$2" &&
clone_tmp="$GIT_DIR/clone-tmp" &&
mkdir -p "$clone_tmp" || exit 1
+ 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" || {
echo >&2 "Cannot get remote repository information.
Perhaps git-update-server-info needs to be run there?"
@@ -298,7 +302,7 @@ 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"
diff --git a/git-commit.sh b/git-commit.sh
index 4cf3fab..5a4c659 100755
--- a/git-commit.sh
+++ b/git-commit.sh
@@ -60,26 +60,6 @@ report () {
}
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
@@ -96,85 +76,14 @@ run_status () {
export GIT_INDEX_FILE
fi
- case "$branch" in
- refs/heads/master) ;;
- *) echo "# On branch $branch" ;;
- 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
- )
+ case "$status_only" in
+ t) color= ;;
+ *) color=--nocolor ;;
+ esac
+ git-runstatus ${color} \
+ ${verbose:+--verbose} \
+ ${amend:+--amend} \
+ ${untracked_files:+--untracked}
}
trap '
diff --git a/git-compat-util.h b/git-compat-util.h
index b2e18954..0272d04 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 99b3dc3..5e23851 100755
--- a/git-cvsexportcommit.perl
+++ b/git-cvsexportcommit.perl
@@ -135,7 +135,7 @@ 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];
}
}
diff --git a/git-fetch.sh b/git-fetch.sh
index c2eebee..f1522bd 100755
--- a/git-fetch.sh
+++ b/git-fetch.sh
@@ -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"
@@ -258,6 +257,7 @@ fi
fetch_main () {
reflist="$1"
refs=
+ rref=
for ref in $reflist
do
@@ -286,10 +286,14 @@ fetch_main () {
# There are transports that can fetch only one head at a time...
case "$remote" 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
+ noepsv_opt="--disable-epsv"
+ fi
max_depth=5
depth=0
head="ref: $remote_name"
@@ -301,7 +305,7 @@ 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 ||
@@ -350,7 +354,7 @@ fetch_main () {
done
case "$remote" in
- http://* | https://* | rsync://* )
+ http://* | https://* | ftp://* | rsync://* )
;; # we are already done.
*)
( : subshell because we muck with IFS
diff --git a/git-ls-remote.sh b/git-ls-remote.sh
index 2fdcaf7..0f88953 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-recursive.py b/git-merge-recursive-old.py
index 4039435..4039435 100755
--- a/git-merge-recursive.py
+++ b/git-merge-recursive-old.py
diff --git a/git-merge.sh b/git-merge.sh
index a9cfafb..5b34b4d 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() {
diff --git a/git-parse-remote.sh b/git-parse-remote.sh
index 187f088..c325ef7 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-rebase.sh b/git-rebase.sh
index 7d3a5d0..a7373c0 100755
--- a/git-rebase.sh
+++ b/git-rebase.sh
@@ -292,11 +292,11 @@ then
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 9da92fb..f2c9071 100755
--- a/git-repack.sh
+++ b/git-repack.sh
@@ -4,6 +4,7 @@
#
USAGE='[-a] [-d] [-f] [-l] [-n] [-q]'
+SUBDIRECTORY_OK='Yes'
. git-sh-setup
no_update_info= all_into_one= remove_redundant=
@@ -24,29 +25,27 @@ do
shift
done
-rm -f .tmp-pack-*
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=
+ args=
# Redundancy check in all-into-one case is trivial.
- existing=`cd "$PACKDIR" && \
+ existing=`test -d "$PACKDIR" && cd "$PACKDIR" && \
find . -type f \( -name '*.pack' -o -name '*.idx' \) -print`
;;
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 +63,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."
diff --git a/git-reset.sh b/git-reset.sh
index 36fc8ce..3133b5b 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 a7bc680..729ec65 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
diff --git a/git-send-email.perl b/git-send-email.perl
index 1e2777c..4a20310 100755
--- a/git-send-email.perl
+++ b/git-send-email.perl
@@ -488,7 +488,7 @@ 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",
diff --git a/git-svn.perl b/git-svn.perl
index 0d58bb9..f5c7d46 100755
--- a/git-svn.perl
+++ b/git-svn.perl
@@ -51,7 +51,8 @@ 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);
my (@_branch_from, %tree_map, %users, %rusers, %equiv);
my ($_svn_co_url_revs, $_svn_pg_peg_revs);
my @repo_path_split_cache;
@@ -64,6 +65,7 @@ my %fc_opts = ( 'no-ignore-externals' => \$_no_ignore_ext,
'repack:i' => \$_repack,
'no-metadata' => \$_no_metadata,
'quiet|q' => \$_q,
+ 'ignore-nodate' => \$_ignore_nodate,
'repack-flags|repack-args|repack-opts=s' => \$_repack_flags);
my ($_trunk, $_tags, $_branches);
@@ -118,6 +120,11 @@ my %cmd = (
{ 'message|m=s' => \$_message,
'file|F=s' => \$_file,
%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 } ],
);
my $cmd;
@@ -500,6 +507,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,6 +517,8 @@ 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,
@@ -557,6 +568,33 @@ 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"));
+ foreach my $d (reverse @refs) {
+ if ($_dry_run) {
+ print "diff-tree $d~1 $d\n";
+ } else {
+ commit_diff("$d~1", $d);
+ }
+ }
+ 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 --hard/;
+ }
+ 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();
@@ -782,6 +820,7 @@ sub commit_diff {
} else {
$ed->close_edit;
}
+ $_message = $_file = undef;
}
########################### utility functions #########################
@@ -1208,6 +1247,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;
@@ -1696,6 +1736,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 +2172,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];
}
diff --git a/git-svnimport.perl b/git-svnimport.perl
index 26dc454..988514e 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);
sub usage() {
print STDERR <<END;
@@ -39,12 +39,12 @@ 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] [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:Suv") or usage();
usage if $opt_h;
my $tag_name = $opt_t || "tags";
@@ -531,21 +531,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;
@@ -772,8 +785,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 +838,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>\n") and
close($out)
or die "Cannot create tag object $dest: $!\n";
diff --git a/git.c b/git.c
index 5da7787..ae80e78 100644
--- a/git.c
+++ b/git.c
@@ -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;
@@ -226,11 +220,12 @@ static void handle_internal_command(int argc, const char **argv, char **envp)
} commands[] = {
{ "add", cmd_add, RUN_SETUP },
{ "apply", cmd_apply },
+ { "archive", cmd_archive },
{ "cat-file", cmd_cat_file, RUN_SETUP },
{ "checkout-index", cmd_checkout_index, RUN_SETUP },
{ "check-ref-format", cmd_check_ref_format },
{ "commit-tree", cmd_commit_tree, RUN_SETUP },
- { "count-objects", cmd_count_objects },
+ { "count-objects", cmd_count_objects, RUN_SETUP },
{ "diff", cmd_diff, RUN_SETUP },
{ "diff-files", cmd_diff_files, RUN_SETUP },
{ "diff-index", cmd_diff_index, RUN_SETUP },
@@ -258,6 +253,7 @@ 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 },
@@ -266,7 +262,7 @@ static void handle_internal_command(int argc, const char **argv, char **envp)
{ "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 },
@@ -291,16 +287,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 +295,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/gitk b/gitk
index a92ab00..ebbeac6 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"
}
@@ -4463,6 +4608,7 @@ proc redisplay {} {
drawvisible
if {[info exists selectedline]} {
selectline $selectedline 0
+ allcanvs yview moveto [lindex $span 0]
}
}
@@ -4930,6 +5076,7 @@ proc domktag {} {
set tagids($tag) $id
lappend idtags($id) $tag
redrawtags $id
+ addedtag $id
}
proc redrawtags {id} {
@@ -5020,10 +5167,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 +5408,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 +5468,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 +5476,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 +5487,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 +5509,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 +5706,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/git-favicon.png b/gitweb/git-favicon.png
new file mode 100644
index 0000000..de637c0
--- /dev/null
+++ b/gitweb/git-favicon.png
Binary files differ
diff --git a/gitweb/gitweb.css b/gitweb/gitweb.css
index 9013895..eb9fc38 100644
--- a/gitweb/gitweb.css
+++ b/gitweb/gitweb.css
@@ -42,6 +42,7 @@ div.page_nav a:visited {
div.page_path {
padding: 8px;
+ font-weight: bold;
border: solid #d9d8d1;
border-width: 0px 0px 1px;
}
@@ -115,13 +116,23 @@ 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;
- font-weight: bold;
color: #000000;
}
-table.tags a.list {
+a.subject, a.name {
+ font-weight: bold;
+}
+
+table.tags a.subject {
font-weight: normal;
}
@@ -269,10 +280,22 @@ td.mode {
font-family: monospace;
}
+div.diff a.list {
+ text-decoration: none;
+}
+
+div.diff a.list:hover {
+ text-decoration: underline;
+}
+
+div.diff.to_file a.list,
+div.diff.to_file,
div.diff.add {
color: #008800;
}
+div.diff.from_file a.list,
+div.diff.from_file,
div.diff.rem {
color: #cc0000;
}
@@ -281,6 +304,10 @@ div.diff.chunk_header {
color: #990099;
}
+div.diff.incomplete {
+ color: #cccccc;
+}
+
div.diff_info {
font-family: monospace;
color: #000099;
diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl
index b5b89de..44991b1 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,11 +31,8 @@ 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++";
@@ -50,10 +48,23 @@ our $home_text = "++GITWEB_HOMETEXT++";
our $stylesheet = "++GITWEB_CSS++";
# URI of GIT logo
our $logo = "++GITWEB_LOGO++";
+# URI of GIT favicon, assumed to be image/png type
+our $favicon = "++GITWEB_FAVICON++";
# 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 = ("++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;
@@ -62,16 +73,130 @@ 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
+
+ 'blame' => {
+ 'sub' => \&feature_blame,
+ 'override' => 0,
+ 'default' => [0]},
+
+ 'snapshot' => {
+ 'sub' => \&feature_snapshot,
+ 'override' => 0,
+ # => [content-encoding, suffix, program]
+ 'default' => ['x-gzip', 'gz', 'gzip']},
+
+ 'pickaxe' => {
+ 'sub' => \&feature_pickaxe,
+ 'override' => 0,
+ 'default' => [1]},
+);
+
+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; }
+ return $sub->(@defaults);
+}
+
+# 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;
+
+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];
+}
+
+# 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
+
+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;
+}
+
+# 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;
+
+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]);
+}
+
+# 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
@@ -80,65 +205,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") ||
+ !(-e "$projectroot/$project/HEAD") ||
+ ($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");
}
}
@@ -151,6 +279,55 @@ if (defined $searchtext) {
$searchtext = quotemeta $searchtext;
}
+# 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 && !-e "$projectroot/$project/HEAD") {
+ $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,
@@ -171,28 +348,93 @@ my %actions = (
"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 @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",
+ );
+ my %mapping = @mapping;
+
+ $params{'project'} = $project unless exists $params{'project'};
+
+ 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});
+ }
+ }
+ return "$my_uri?" . join(';', @result);
+}
+
+
+## ======================================================================
## 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;
@@ -202,6 +444,15 @@ sub validate_input {
# 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;
@@ -241,6 +492,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
@@ -331,7 +588,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";
@@ -357,7 +620,9 @@ sub format_log_line_html {
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/;
}
}
@@ -394,38 +659,67 @@ sub format_ref_marker {
# format, perhaps shortened and with markers, title line
sub format_subject_html {
- my ($long, $short, $query, $extra) = @_;
+ my ($long, $short, $href, $extra) = @_;
$extra = '' unless defined($extra);
if (length($short) < length($long)) {
- return $cgi->a({-href => "$my_uri?" . esc_param($query),
- -class => "list", -title => $long},
+ return $cgi->a({-href => $href, -class => "list subject",
+ -title => decode("utf8", $long, Encode::FB_DEFAULT)},
esc_html($short) . $extra);
} else {
- return $cgi->a({-href => "$my_uri?" . esc_param($query),
- -class => "list"},
+ 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) . "</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_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;
}
@@ -434,7 +728,7 @@ sub git_get_head_hash {
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;
@@ -442,57 +736,46 @@ 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(.+)$/;
+ 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_get_hash_by_ref {
- my $path = shift;
-
- open my $fd, "$projectroot/$path" or return undef;
- my $head = <$fd>;
- close $fd;
- chomp $head;
- if ($head =~ m/^[0-9a-fA-F]{40}$/) {
- return $head;
- }
-}
-
sub git_get_project_description {
my $path = shift;
@@ -503,28 +786,49 @@ sub git_get_project_description {
return $descr;
}
+sub git_get_project_url_list {
+ my $path = shift;
+
+ open my $fd, "$projectroot/$path/cloneurl" or return;
+ my @git_project_url_list = map { chomp; $_ } <$fd>;
+ close $fd;
+
+ return wantarray ? @git_project_url_list : \@git_project_url_list;
+}
+
sub git_get_projects_list {
my @list;
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 $pfxlen = length("$dir");
+
+ 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 (-e "$projectroot/$subdir/HEAD" && (!$export_ok ||
+ -e "$projectroot/$subdir/$export_ok")) {
+ push @list, { path => $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;
@@ -533,7 +837,8 @@ sub git_get_projects_list {
if (!defined $path) {
next;
}
- if (-e "$projectroot/$path/HEAD") {
+ if (-e "$projectroot/$path/HEAD" && (!$export_ok ||
+ -e "$projectroot/$path/$export_ok")) {
my $pr = {
path => $path,
owner => decode("utf8", $owner, Encode::FB_DEFAULT),
@@ -581,16 +886,10 @@ sub git_get_project_owner {
sub git_get_references {
my $type = shift || "";
my %refs;
- my $fd;
# 5dc01c595e6c6ec9ccda4f6f69c131c0dd945f8c refs/tags/v2.6.11
# c39ae07f393806ccf406ef966e9a15afc43cc36a refs/tags/v2.6.11^{}
- if (-f "$projectroot/$project/info/refs") {
- open $fd, "$projectroot/$project/info/refs"
- or return;
- } else {
- open $fd, "-|", $GIT, "ls-remote", "."
- or return;
- }
+ open my $fd, "-|", $GIT, "peek-remote", "$projectroot/$project/"
+ or return;
while (my $line = <$fd>) {
chomp $line;
@@ -606,6 +905,22 @@ sub git_get_references {
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
@@ -622,8 +937,10 @@ sub parse_date {
$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);
@@ -639,7 +956,7 @@ sub parse_tag {
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;
@@ -680,7 +997,8 @@ sub parse_commit {
@commit_lines = @$commit_text;
} else {
$/ = "\0";
- open my $fd, "-|", $GIT, "rev-list", "--header", "--parents", "--max-count=1", $commit_id or return;
+ 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";
@@ -806,32 +1124,99 @@ sub parse_ref {
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(.+)$/;
+
+ $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_get_refs_list {
- my $ref_dir = shift;
+ my $type = shift || "";
+ my %refs;
my @reflist;
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, "peek-remote", "$projectroot/$project/"
+ or return;
+ while (my $line = <$fd>) {
+ chomp $line;
+ if ($line =~ m/^([0-9a-fA-F]{40})\trefs\/($type\/?([^\^]+))(\^\{\})?$/) {
+ if (defined $refs{$1}) {
+ push @{$refs{$1}}, $2;
+ } else {
+ $refs{$1} = [ $2 ];
+ }
+
+ if (! $4) { # unpeeled, direct reference
+ push @refs, { hash => $1, name => $3 }; # without type
+ } elsif ($3 eq $refs[-1]{'name'}) {
+ # most likely a tag is followed by its peeled
+ # (deref) one, and when that happens we know the
+ # previous one was of type 'tag'.
+ $refs[-1]{'type'} = "tag";
+ }
}
- }, "$projectroot/$project/$ref_dir");
+ }
+ close $fd;
+
+ foreach my $ref (@refs) {
+ my $ref_file = $ref->{'name'};
+ my $ref_id = $ref->{'hash'};
- foreach my $ref_file (@refs) {
- my $ref_id = git_get_hash_by_ref("$project/$ref_dir/$ref_file");
- my $type = git_get_type($ref_id) || next;
+ my $type = $ref->{'type'} || git_get_type($ref_id) || next;
my %ref_item = parse_ref($ref_file, $ref_id, $type);
push @reflist, \%ref_item;
}
# sort refs by age
@reflist = sort {$b->{'epoch'} <=> $a->{'epoch'}} @reflist;
- return \@reflist;
+ return (\@reflist, \%refs);
}
## ----------------------------------------------------------------------
@@ -872,7 +1257,7 @@ sub mimetype_guess_file {
}
close(MIME);
- $filename =~ /\.(.*?)$/;
+ $filename =~ /\.([^.]*)$/;
return $mimemap{$1};
}
@@ -934,7 +1319,7 @@ sub git_header_html {
if (defined $action) {
$title .= "/$action";
if (defined $file_name) {
- $title .= " - $file_name";
+ $title .= " - " . esc_html($file_name);
if ($action eq "tree" && $file_name !~ m|/$|) {
$title .= "/";
}
@@ -946,20 +1331,24 @@ 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"/>
@@ -967,8 +1356,17 @@ EOF
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" .
@@ -977,9 +1375,9 @@ EOF
"<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)}, $home_link_str) . " / ";
+ 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";
}
@@ -1016,9 +1414,13 @@ sub git_footer_html {
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" .
@@ -1030,11 +1432,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;
}
@@ -1051,27 +1455,25 @@ sub git_print_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";
}
@@ -1082,14 +1484,14 @@ sub format_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";
@@ -1097,7 +1499,7 @@ sub format_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";
@@ -1111,28 +1513,183 @@ sub format_paging_nav {
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;
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";
+ print "<div class=\"page_path\">/</div>\n";
} else {
- print "<div class=\"page_path\"><b>" . esc_html($name) . "</b><br/></div>\n";
+ my @dirname = split '/', $name;
+ my $basename = pop @dirname;
+ my $fullname = '';
+
+ print "<div class=\"page_path\">";
+ print $cgi->a({-href => href(action=>"tree", hash_base=>$hb),
+ -title => 'tree root'}, "[$project]");
+ print " / ";
+ foreach my $dir (@dirname) {
+ $fullname .= ($fullname ? '/' : '') . $dir;
+ print $cgi->a({-href => href(action=>"tree", file_name=>$fullname,
+ hash_base=>$hb),
+ -title => $fullname}, esc_html($dir));
+ print " / ";
+ }
+ if (defined $type && $type eq 'blob') {
+ print $cgi->a({-href => href(action=>"blob_plain", file_name=>$file_name,
+ hash_base=>$hb),
+ -title => $name}, esc_html($basename));
+ } elsif (defined $type && $type eq 'tree') {
+ print $cgi->a({-href => href(action=>"tree", file_name=>$file_name,
+ hash_base=>$hb),
+ -title => $name}, esc_html($basename));
+ } else {
+ print esc_html($basename);
+ }
+ print "<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;
+ }
+}
+
+sub git_print_simplified_log {
+ my $log = shift;
+ my $remove_title = shift;
+
+ git_print_log($log,
+ -final_empty_line=> 1,
+ -remove_title => $remove_title);
+}
+
+# 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_html($t->{'name'})) . "</td>\n";
+ print "<td class=\"link\">";
+ 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) {
+ if ($have_blame) {
+ print " | ";
+ }
+ 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_html($t->{'name'}));
+ print "</td>\n";
+ print "<td class=\"link\">";
+ if (defined $hash_base) {
+ print $cgi->a({-href => href(action=>"history", hash_base=>$hash_base,
+ file_name=>"$basedir$t->{'name'}")},
+ "history");
+ }
+ print "</td>\n";
}
}
@@ -1140,7 +1697,7 @@ sub git_print_page_path {
## functions printing large fragments of HTML
sub git_difftree_body {
- my ($difftree, $parent) = @_;
+ my ($difftree, $hash, $parent) = @_;
print "<div class=\"list_head\">\n";
if ($#{$difftree} > 10) {
@@ -1149,20 +1706,10 @@ sub git_difftree_body {
print "</div>\n";
print "<table class=\"diff_tree\">\n";
- my $alternate = 0;
+ my $alternate = 1;
+ my $patchno = 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;
- }
- my $from_mode = $1;
- my $to_mode = $2;
- my $from_id = $3;
- my $to_id = $4;
- my $status = $5;
- my $similarity = $6; # score
- my $file = validate_input(unquote($7));
+ my %diff = parse_difftree_raw_line($line);
if ($alternate) {
print "<tr class=\"dark\">\n";
@@ -1171,121 +1718,289 @@ sub git_difftree_body {
}
$alternate ^= 1;
- if ($status eq "A") { # created
- my $mode_chng = "";
- if (S_ISREG(oct $to_mode)) {
- $mode_chng = sprintf(" with mode: %04o", (oct $to_mode) & 0777);
+ 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
}
- 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";
+ $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'});
+ }
- } elsif ($status eq "D") { # deleted
- 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"
+ 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_html($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_html($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=>"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 ($status eq "M" || $status eq "T") { # modified, or type changed
+ } elsif ($diff{'status'} eq "M" || $diff{'status'} eq "T") { # modified, or type changed
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 ($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 (((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);
+ 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>";
- if ($to_id ne $from_id) { # modified
- 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 { # mode changed
- 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\">" .
- $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) { # modified
- 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 $cgi->a({-href => href(action=>"blob", hash=>$diff{'to_id'},
+ hash_base=>$hash, file_name=>$diff{'file'}),
+ -class => "list"}, esc_html($diff{'file'}));
print "</td>\n";
-
- } elsif ($status eq "R") { # renamed
- 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;fp=$from_file")}, "diff");
+ print "<td>$mode_chnge</td>\n";
+ print "<td class=\"link\">";
+ if ($diff{'to_id'} ne $diff{'from_id'}) { # modified
+ if ($action eq 'commitdiff') {
+ # link to patch
+ $patchno++;
+ print $cgi->a({-href => "#patch$patchno"}, "patch");
+ } else {
+ 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 " | ";
}
+ 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 ($status eq "C") { # copied
- my ($from_file, $to_file) = split "\t", $file;
+ } 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 ($from_mode != $to_mode) {
- $mode_chng = sprintf(", mode: %04o", (oct $to_mode) & 0777);
+ 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 => "$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 copied\">[copied 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;fp=$from_file")}, "diff");
+ $cgi->a({-href => href(action=>"blob", hash_base=>$hash,
+ hash=>$diff{'to_id'}, file_name=>$diff{'to_file'}),
+ -class => "list"}, esc_html($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_html($diff{'from_file'})) .
+ " with " . (int $diff{'similarity'}) . "% similarity$mode_chng]</span></td>\n" .
+ "<td class=\"link\">";
+ if ($diff{'to_id'} ne $diff{'from_id'}) {
+ if ($action eq 'commitdiff') {
+ # link to patch
+ $patchno++;
+ print $cgi->a({-href => "#patch$patchno"}, "patch");
+ } else {
+ 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 " | ";
}
+ print $cgi->a({-href => href(action=>"blame", hash_base=>$parent,
+ file_name=>$diff{'from_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;
+
+ 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 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";
+
+ if (ref($difftree->[$patch_idx]) eq "HASH") {
+ $diffinfo = $difftree->[$patch_idx];
+ } else {
+ $diffinfo = parse_difftree_raw_line($difftree->[$patch_idx]);
+ }
+ $patch_idx++;
+
+ # for now, no extended header, hence we skip empty patches
+ # companion to next LINE if $in_header;
+ if ($diffinfo->{'from_id'} eq $diffinfo->{'to_id'}) { # no change
+ $in_header = 1;
+ next LINE;
+ }
+
+ if ($diffinfo->{'status'} eq "A") { # added
+ print "<div class=\"diff_info\">" . file_type($diffinfo->{'to_mode'}) . ":" .
+ $cgi->a({-href => href(action=>"blob", hash_base=>$hash,
+ hash=>$diffinfo->{'to_id'}, file_name=>$diffinfo->{'file'})},
+ $diffinfo->{'to_id'}) . "(new)" .
+ "</div>\n"; # class="diff_info"
+
+ } elsif ($diffinfo->{'status'} eq "D") { # deleted
+ print "<div class=\"diff_info\">" . file_type($diffinfo->{'from_mode'}) . ":" .
+ $cgi->a({-href => href(action=>"blob", hash_base=>$hash_parent,
+ hash=>$diffinfo->{'from_id'}, file_name=>$diffinfo->{'file'})},
+ $diffinfo->{'from_id'}) . "(deleted)" .
+ "</div>\n"; # class="diff_info"
+
+ } elsif ($diffinfo->{'status'} eq "R" || # renamed
+ $diffinfo->{'status'} eq "C" || # copied
+ $diffinfo->{'status'} eq "2") { # with two filenames (from git_blobdiff)
+ print "<div class=\"diff_info\">" .
+ file_type($diffinfo->{'from_mode'}) . ":" .
+ $cgi->a({-href => href(action=>"blob", hash_base=>$hash_parent,
+ hash=>$diffinfo->{'from_id'}, file_name=>$diffinfo->{'from_file'})},
+ $diffinfo->{'from_id'}) .
+ " -> " .
+ file_type($diffinfo->{'to_mode'}) . ":" .
+ $cgi->a({-href => href(action=>"blob", hash_base=>$hash,
+ hash=>$diffinfo->{'to_id'}, file_name=>$diffinfo->{'to_file'})},
+ $diffinfo->{'to_id'});
+ print "</div>\n"; # class="diff_info"
+
+ } else { # modified, mode changed, ...
+ print "<div class=\"diff_info\">" .
+ file_type($diffinfo->{'from_mode'}) . ":" .
+ $cgi->a({-href => href(action=>"blob", hash_base=>$hash_parent,
+ hash=>$diffinfo->{'from_id'}, file_name=>$diffinfo->{'file'})},
+ $diffinfo->{'from_id'}) .
+ " -> " .
+ file_type($diffinfo->{'to_mode'}) . ":" .
+ $cgi->a({-href => href(action=>"blob", hash_base=>$hash,
+ hash=>$diffinfo->{'to_id'}, file_name=>$diffinfo->{'file'})},
+ $diffinfo->{'to_id'});
+ print "</div>\n"; # class="diff_info"
+ }
+
+ #print "<div class=\"diff extended_header\">\n";
+ $in_header = 1;
+ next LINE;
+ } # start of patch in patchset
+
+
+ if ($in_header && $patch_line =~ m/^---/) {
+ #print "</div>\n"; # class="diff extended_header"
+ $in_header = 0;
+
+ my $file = $diffinfo->{'from_file'};
+ $file ||= $diffinfo->{'file'};
+ $file = $cgi->a({-href => href(action=>"blob", hash_base=>$hash_parent,
+ hash=>$diffinfo->{'from_id'}, file_name=>$file),
+ -class => "list"}, esc_html($file));
+ $patch_line =~ s|a/.*$|a/$file|g;
+ print "<div class=\"diff from_file\">$patch_line</div>\n";
+
+ $patch_line = <$fd>;
+ chomp $patch_line;
+
+ #$patch_line =~ m/^+++/;
+ $file = $diffinfo->{'to_file'};
+ $file ||= $diffinfo->{'file'};
+ $file = $cgi->a({-href => href(action=>"blob", hash_base=>$hash,
+ hash=>$diffinfo->{'to_id'}, file_name=>$file),
+ -class => "list"}, esc_html($file));
+ $patch_line =~ s|b/.*|b/$file|g;
+ print "<div class=\"diff to_file\">$patch_line</div>\n";
+
+ next LINE;
+ }
+ next LINE if $in_header;
+
+ print format_diff_line($patch_line);
+ }
+ print "</div>\n" if $patch_found; # class="patch"
+
+ print "</div>\n"; # class="patchset"
+}
+
+# . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
+
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 ? format_ref_marker($refs, $commit) : '';
@@ -1301,12 +2016,14 @@ 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>";
- print format_subject_html($co{'title'}, $co{'title_short'}, "p=$project;a=commit;h=$commit", $ref);
+ 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=>"commitdiff", hash=>$commit)}, "commitdiff") . " | " .
+ $cgi->a({-href => href(action=>"tree", hash=>$commit, hash_base=>$commit)}, "tree") . " | " .
+ $cgi->a({-href => href(action=>"snapshot", hash=>$commit)}, "snapshot");
+ print "</td>\n" .
"</tr>\n";
}
if (defined $extra) {
@@ -1319,12 +2036,15 @@ sub git_shortlog_body {
sub git_history_body {
# Warning: assumes constant type (blob or tree) during history
- my ($fd, $refs, $hash_base, $ftype, $extra) = @_;
+ 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 = 0;
- while (my $line = <$fd>) {
- if ($line !~ m/^([0-9a-fA-F]{40})/) {
+ my $alternate = 1;
+ for (my $i = $from; $i <= $to; $i++) {
+ if ($revlist->[$i] !~ m/^([0-9a-fA-F]{40})/) {
next;
}
@@ -1347,12 +2067,12 @@ sub git_history_body {
"<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'}, "p=$project;a=commit;h=$commit", $ref);
+ 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") . " | " .
- $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$ftype;hb=$commit;f=$file_name")}, $ftype);
+ $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);
@@ -1360,7 +2080,10 @@ sub git_history_body {
if (defined $blob_current && defined $blob_parent &&
$blob_current ne $blob_parent) {
print " | " .
- $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blobdiff;h=$blob_current;hp=$blob_parent;hb=$commit;f=$file_name")},
+ $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");
}
}
@@ -1382,7 +2105,7 @@ 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;
@@ -1400,28 +2123,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) {
- print format_subject_html($comment, $comment_short, "p=$project;a=tag;h=$tag{'id'}");
+ 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{'refid'})}, "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>";
@@ -1436,14 +2160,14 @@ 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 $entry = $headlist->[$i];
my %tag = %$entry;
my $curr = $tag{'id'} eq $head;
if ($alternate) {
@@ -1454,12 +2178,13 @@ sub git_heads_body {
$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>") .
+ $cgi->a({-href => href(action=>"shortlog", hash=>$tag{'name'}),
+ -class => "list name"},esc_html($tag{'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=>$tag{'name'})}, "shortlog") . " | " .
+ $cgi->a({-href => href(action=>"log", hash=>$tag{'name'})}, "log") . " | " .
+ $cgi->a({-href => href(action=>"tree", hash=>$tag{'name'}, hash_base=>$tag{'name'})}, "tree") .
"</td>\n" .
"</tr>";
}
@@ -1471,77 +2196,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
@@ -1562,7 +2216,7 @@ sub git_project_list {
if (!defined $head) {
next;
}
- $ENV{'GIT_DIR'} = "$projectroot/$pr->{'path'}";
+ $git_dir = "$projectroot/$pr->{'path'}";
my %co = parse_commit($head);
if (!%co) {
next;
@@ -1594,7 +2248,7 @@ sub git_project_list {
print "<th>Project</th>\n";
} else {
print "<th>" .
- $cgi->a({-href => "$my_uri?" . esc_param("o=project"),
+ $cgi->a({-href => href(project=>undef, order=>'project'),
-class => "header"}, "Project") .
"</th>\n";
}
@@ -1603,7 +2257,7 @@ sub git_project_list {
print "<th>Description</th>\n";
} else {
print "<th>" .
- $cgi->a({-href => "$my_uri?" . esc_param("o=descr"),
+ $cgi->a({-href => href(project=>undef, order=>'descr'),
-class => "header"}, "Description") .
"</th>\n";
}
@@ -1612,7 +2266,7 @@ sub git_project_list {
print "<th>Owner</th>\n";
} else {
print "<th>" .
- $cgi->a({-href => "$my_uri?" . esc_param("o=owner"),
+ $cgi->a({-href => href(project=>undef, order=>'owner'),
-class => "header"}, "Owner") .
"</th>\n";
}
@@ -1621,13 +2275,13 @@ sub git_project_list {
print "<th>Last Change</th>\n";
} else {
print "<th>" .
- $cgi->a({-href => "$my_uri?" . esc_param("o=age"),
+ $cgi->a({-href => href(project=>undef, order=>'age'),
-class => "header"}, "Last Change") .
"</th>\n";
}
print "<th></th>\n" .
"</tr>\n";
- my $alternate = 0;
+ my $alternate = 1;
foreach my $pr (@projects) {
if ($alternate) {
print "<tr class=\"dark\">\n";
@@ -1635,16 +2289,17 @@ sub git_project_list {
print "<tr class=\"light\">\n";
}
$alternate ^= 1;
- print "<td>" . $cgi->a({-href => "$my_uri?" . esc_param("p=$pr->{'path'};a=summary"),
+ 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->{'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") .
+ $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") .
"</td>\n" .
"</tr>\n";
}
@@ -1652,6 +2307,30 @@ sub git_project_list {
git_footer_html();
}
+sub git_project_index {
+ my @projects = git_get_projects_list();
+
+ 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");
+ }
+
+ 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";
+ }
+}
+
sub git_summary {
my $descr = git_get_project_description($project) || "none";
my $head = git_get_head_hash($project);
@@ -1660,7 +2339,19 @@ sub git_summary {
my $owner = git_get_project_owner($project);
- my $refs = git_get_references();
+ my ($reflist, $refs) = git_get_refs_list();
+
+ my @taglist;
+ my @headlist;
+ foreach my $ref (@$reflist) {
+ if ($ref->{'name'} =~ s!^heads/!!) {
+ push @headlist, $ref;
+ } else {
+ $ref->{'name'} =~ s!^tags/!!;
+ push @taglist, $ref;
+ }
+ }
+
git_header_html();
git_print_page_nav('summary','', $head);
@@ -1668,29 +2359,38 @@ sub git_summary {
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_get_head_hash($project)
+ 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_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_get_refs_list("refs/tags");
- if (defined @$taglist) {
+ if (@taglist) {
git_print_header_div('tags');
- git_tags_body($taglist, 0, 15,
- $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tags")}, "..."));
+ git_tags_body(\@taglist, 0, 15,
+ $cgi->a({-href => href(action=>"tags")}, "..."));
}
- my $headlist = git_get_refs_list("refs/heads");
- if (defined @$headlist) {
+ if (@headlist) {
git_print_header_div('heads');
- git_heads_body($headlist, $head, 0, 15,
- $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=heads")}, "..."));
+ git_heads_body(\@headlist, $head, 0, 15,
+ $cgi->a({-href => href(action=>"heads")}, "..."));
}
git_footer_html();
@@ -1706,13 +2406,17 @@ sub git_tag {
"<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 = 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";
@@ -1728,7 +2432,11 @@ 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_get_head_hash($project);
die_error(undef, "Couldn't find base commit") unless ($hash_base);
@@ -1742,22 +2450,30 @@ 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", '-l', '--', $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");
+ $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);
+ 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";
+ print <<HTML;
+<div class="page_body">
+<table class="blame">
+<tr><th>Commit</th><th>Line</th><th>Data</th></tr>
+HTML
while (<$fd>) {
/^([0-9a-fA-F]{40}).*?(\d+)\)\s{1}(\s*.*)/;
my $full_rev = $1;
@@ -1773,20 +2489,27 @@ sub git_blame2 {
}
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";
+ $cgi->a({-href => href(action=>"commit", hash=>$full_rev, file_name=>$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";
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_get_head_hash($project);
die_error(undef, "Couldn't find base commit") unless ($hash_base);
@@ -1796,15 +2519,21 @@ sub git_blame {
$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");
+ $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');
+ git_print_page_path($file_name, 'blob', $hash_base);
print "<div class=\"page_body\">\n";
print <<HTML;
<table class="blame">
@@ -1833,7 +2562,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;
@@ -1856,7 +2585,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>
@@ -1865,7 +2594,8 @@ 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();
}
@@ -1876,8 +2606,8 @@ sub git_tags {
git_print_page_nav('','', $head,undef,$head);
git_print_header_div('summary', $project);
- my $taglist = git_get_refs_list("refs/tags");
- if (defined @$taglist) {
+ my ($taglist) = git_get_refs_list("tags");
+ if (@$taglist) {
git_tags_body($taglist);
}
git_footer_html();
@@ -1889,14 +2619,16 @@ sub git_heads {
git_print_page_nav('','', $head,undef,$head);
git_print_header_div('summary', $project);
- my $taglist = git_get_refs_list("refs/heads");
- if (defined @$taglist) {
- git_heads_body($taglist, $head);
+ my ($headlist) = git_get_refs_list("heads");
+ if (@$headlist) {
+ git_heads_body($headlist, $head);
}
git_footer_html();
}
sub git_blob_plain {
+ my $expires;
+
if (!defined $hash) {
if (defined $file_name) {
my $base = $hash_base || git_get_head_hash($project);
@@ -1905,9 +2637,13 @@ sub git_blob_plain {
} 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 ||= blob_mimetype($fd, $file_name);
@@ -1920,7 +2656,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>;
@@ -1930,6 +2669,8 @@ sub git_blob_plain {
}
sub git_blob {
+ my $expires;
+
if (!defined $hash) {
if (defined $file_name) {
my $base = $hash_base || git_get_head_hash($project);
@@ -1938,27 +2679,45 @@ sub git_blob {
} 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 = 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 = 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_print_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav);
git_print_header_div('commit', esc_html($co{'title'}), $hash_base);
@@ -1967,33 +2726,37 @@ sub git_blob {
"<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);
}
- 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_get_head_hash($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");
@@ -2002,14 +2765,29 @@ sub git_tree {
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');
+ my ($have_blame) = gitweb_check_feature('blame');
if (defined $hash_base && (my %co = parse_commit($hash_base))) {
- $base_key = ";hb=$hash_base";
- git_print_page_nav('tree','', $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";
@@ -2017,45 +2795,22 @@ sub git_tree {
if (defined $file_name) {
$base = esc_html("$file_name/");
}
- 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;
+ my $alternate = 1;
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 %t = parse_ls_tree_line($line, -z => 1);
+
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";
- }
+
+ git_print_tree_entry(\%t, $base, $hash_base, $have_blame);
+
print "</tr>\n";
}
print "</table>\n" .
@@ -2063,6 +2818,35 @@ 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_command = git_cmd_str();
+ open my $fd, "-|", "$git_command tar-tree $hash \'$project\' | $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_get_head_hash($project);
if (!defined $hash) {
@@ -2074,7 +2858,7 @@ sub git_log {
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;
@@ -2102,32 +2886,18 @@ sub git_log {
$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_simplified_log($co{'comment'});
print "</div>\n";
}
git_footer_html();
@@ -2145,7 +2915,7 @@ sub git_commit {
if (!defined $parent) {
$parent = "--root";
}
- open my $fd, "-|", $GIT, "diff-tree", '-r', '-M', $parent, $hash
+ open my $fd, "-|", git_cmd(), "diff-tree", '-r', @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");
@@ -2157,15 +2927,24 @@ sub git_commit {
}
my $refs = git_get_references();
my $ref = format_ref_marker($refs, $co{'id'});
- my $formats_nav = '';
+
+ 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");
+ }
+ if (defined $co{'parent'}) {
+ push @views_nav,
+ $cgi->a({-href => href(action=>"shortlog", hash=>$hash)}, "shortlog"),
+ $cgi->a({-href => href(action=>"log", hash=>$hash)}, "log");
}
git_header_html(undef, $expires);
git_print_page_nav('commit', defined $co{'parent'} ? '' : 'commitdiff',
- $hash, $co{'tree'}, $hash,
- $formats_nav);
+ $hash, $co{'tree'}, $hash,
+ join (' | ', @views_nav));
if (defined $co{'parent'}) {
git_print_header_div('commitdiff', esc_html($co{'title'}) . $ref, $hash);
@@ -2178,189 +2957,231 @@ 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;
- } else {
- $empty = 0;
- }
- 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";
- }
- }
+ git_print_log($co{'comment'});
print "</div>\n";
- git_difftree_body(\@difftree, $parent);
+ git_difftree_body(\@difftree, $hash, $parent);
git_footer_html();
}
sub git_blobdiff {
- mkdir($git_temp, 0700);
- git_header_html();
- if (defined $hash_base && (my %co = parse_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_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 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();
-}
+ my $format = shift || 'html';
-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");
-}
+ my $fd;
+ my @difftree;
+ my %diffinfo;
+ my $expires;
-sub git_commitdiff {
- mkdir($git_temp, 0700);
- my %co = parse_commit($hash);
- if (!%co) {
- die_error(undef, "Unknown commit object");
- }
- if (!defined $hash_parent) {
- $hash_parent = $co{'parent'} || '--root';
+ # 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 {
+ die_error('404 Not Found', "Missing one of the blob diff parameters");
+ }
+
+ 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");
}
- 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";
+ # 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;
+ }
+
+ # 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;
}
- my $refs = git_get_references();
- my $ref = format_ref_marker($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_print_page_nav('commitdiff','', $hash,$co{'tree'},$hash, $formats_nav);
- git_print_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;
+
+ # 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 "<div class=\"page_nav\"><br/>$formats_nav<br/></div>\n";
+ print "<div class=\"title\">$hash vs $hash_parent</div>\n";
}
- if ($line eq "") {
- if ($empty) {
- next;
- }
- $empty = 1;
+ if (defined $file_name) {
+ git_print_page_path($file_name, "blob", $hash_base);
} else {
- $empty = 0;
+ print "<div class=\"page_path\"></div>\n";
}
- print format_log_line_html($line) . "<br/>\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 "<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");
- }
+
+ # 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 {
+ while (my $line = <$fd>) {
+ $line =~ s!a/($hash|$hash_parent)!'a/'.esc_html($diffinfo{'from_file'})!eg;
+ $line =~ s!b/($hash|$hash_parent)!'b/'.esc_html($diffinfo{'to_file'})!eg;
+
+ print $line;
+
+ last if $line =~ m!^\+\+\+!;
}
+ local $/ = undef;
+ print <$fd>;
+ close $fd;
}
- print "<br/>\n" .
- "</div>";
- git_footer_html();
}
-sub git_commitdiff_plain {
- mkdir($git_temp, 0700);
+sub git_blobdiff_plain {
+ git_blobdiff('plain');
+}
+
+sub git_commitdiff {
+ my $format = shift || 'html';
my %co = parse_commit($hash);
if (!%co) {
die_error(undef, "Unknown commit object");
@@ -2368,87 +3189,169 @@ sub git_commitdiff_plain {
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 = git_get_references("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,
+ "--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 = parse_date($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;
- }
- 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");
- }
+ # write commit message
+ if ($format eq 'html') {
+ my $refs = git_get_references();
+ my $ref = format_ref_marker($refs, $co{'id'});
+ my $formats_nav =
+ $cgi->a({-href => href(action=>"commitdiff_plain",
+ hash=>$hash, hash_parent=>$hash_parent)},
+ "raw");
+
+ 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";
+ print "<div class=\"log\">\n";
+ git_print_simplified_log($co{'comment'}, 1); # skip title
+ print "</div>\n"; # class="log"
+
+ } 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_get_head_hash($project);
}
+ if (!defined $page) {
+ $page = 0;
+ }
my $ftype;
my %co = parse_commit($hash_base);
if (!%co) {
die_error(undef, "Unknown commit object");
}
+
my $refs = git_get_references();
- git_header_html();
- git_print_page_nav('','', $hash_base,$co{'tree'},$hash_base);
- git_print_header_div('commit', esc_html($co{'title'}), $hash_base);
+ 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;
- git_history_body($fd, $refs, $hash_base, $ftype);
+ 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";
+ }
+ 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);
- close $fd;
git_footer_html();
}
@@ -2463,8 +3366,7 @@ sub git_search {
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;
@@ -2476,16 +3378,23 @@ sub git_search {
} elsif ($searchtext =~ s/^pickaxe\\://i) {
$commit_search = 0;
$pickaxe_search = 1;
+
+ # 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_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;
+ my $alternate = 1;
if ($commit_search) {
$/ = "\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;
@@ -2510,7 +3419,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) {
@@ -2525,8 +3435,9 @@ 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";
}
@@ -2535,7 +3446,9 @@ sub git_search {
if ($pickaxe_search) {
$/ = "\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>) {
@@ -2563,18 +3476,22 @@ 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'}"),