diff options
239 files changed, 18669 insertions, 8123 deletions
diff --git a/.gitignore b/.gitignore index fb0fa3f16a..25eb4637a6 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,14 +121,15 @@ 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 git-whatchanged git-write-tree git-core-*/?* +gitweb/gitweb.cgi test-date test-delta test-dump-cache-tree @@ -138,9 +143,10 @@ git-core.spec *.py[co] config.mak autom4te.cache +config.cache config.log config.status -config.mak.in config.mak.autogen +config.mak.append configure git-blame diff --git a/Documentation/Makefile b/Documentation/Makefile index 0d9ffb4ad9..c00f5f62b7 100644 --- a/Documentation/Makefile +++ b/Documentation/Makefile @@ -33,6 +33,8 @@ man7dir=$(mandir)/man7 INSTALL?=install +-include ../config.mak.autogen + # # Please note that there is a minor bug in asciidoc. # The version after 6.0.3 _will_ include the patch found here: @@ -105,7 +107,7 @@ WEBDOC_DEST = /pub/software/scm/git/docs $(patsubst %.txt,%.html,$(wildcard howto/*.txt)): %.html : %.txt rm -f $@+ $@ - sed -e '1,/^$$/d' $? | asciidoc -b xhtml11 - >$@+ + sed -e '1,/^$$/d' $< | asciidoc -b xhtml11 - >$@+ mv $@+ $@ install-webdoc : html diff --git a/Documentation/asciidoc.conf b/Documentation/asciidoc.conf index 8196d787ab..44b1ce4c6b 100644 --- a/Documentation/asciidoc.conf +++ b/Documentation/asciidoc.conf @@ -11,6 +11,7 @@ caret=^ startsb=[ endsb=] +tilde=~ ifdef::backend-docbook[] [gitlink-inlinemacro] diff --git a/Documentation/config.txt b/Documentation/config.txt index ce722a2db0..84e38911ee 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 1185897f70..47505aa20a 100644 --- a/Documentation/core-tutorial.txt +++ b/Documentation/core-tutorial.txt @@ -1620,7 +1620,7 @@ suggested in the previous section may be new to you. You do not have to worry. git supports "shared public repository" style of cooperation you are probably more familiar with as well. -See link:cvs-migration.txt[git for CVS users] for the details. +See link:cvs-migration.html[git for CVS users] for the details. Bundling your work together --------------------------- diff --git a/Documentation/cvs-migration.txt b/Documentation/cvs-migration.txt index d2b0bd38de..6812683a16 100644 --- a/Documentation/cvs-migration.txt +++ b/Documentation/cvs-migration.txt @@ -172,7 +172,7 @@ Advanced Shared Repository Management Git allows you to specify scripts called "hooks" to be run at certain points. You can use these, for example, to send all commits to the shared -repository to a mailing list. See link:hooks.txt[Hooks used by git]. +repository to a mailing list. See link:hooks.html[Hooks used by git]. You can enforce finer grained permissions using update hooks. See link:howto/update-hook-example.txt[Controlling access to branches using diff --git a/Documentation/diff-options.txt b/Documentation/diff-options.txt index 47ba9a403a..7b7b9e8ce9 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 @@ -36,6 +39,9 @@ Turn off colored diff, even when the configuration file gives the default to color output. +--color-words:: + Show colored word diff, i.e. color words which have changed. + --no-renames:: Turn off rename detection, even when the configuration file gives the default to do so. diff --git a/Documentation/git-apply.txt b/Documentation/git-apply.txt index 2ff74949a7..d9137c7489 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 0000000000..031fcd5190 --- /dev/null +++ b/Documentation/git-archive.txt @@ -0,0 +1,113 @@ +git-archive(1) +============== + +NAME +---- +git-archive - Creates a archive of the files in the named tree + + +SYNOPSIS +-------- +'git-archive' --format=<fmt> [--list] [--prefix=<prefix>/] [<extra>] + [--remote=<repo>] <tree-ish> [path...] + +DESCRIPTION +----------- +Creates an archive of the specified format containing the tree +structure for the named tree. If <prefix> is specified it is +prepended to the filenames in the archive. + +'git-archive' behaves differently when given a tree ID versus when +given a commit ID or tag ID. In the first case the current time is +used as modification time of each file in the archive. In the latter +case the commit time as recorded in the referenced commit object is +used instead. Additionally the commit ID is stored in a global +extended pax header if the tar format is used; it can be extracted +using 'git-get-tar-commit-id'. In ZIP files it is stored as a file +comment. + +OPTIONS +------- + +--format=<fmt>:: + Format of the resulting archive: 'tar', 'zip'... + +--list:: + Show all available formats. + +--prefix=<prefix>/:: + Prepend <prefix>/ to each filename in the archive. + +<extra>:: + This can be any options that the archiver backend understand. + See next section. + +--remote=<repo>:: + Instead of making a tar archive from local repository, + retrieve a tar archive from a remote repository. + +<tree-ish>:: + The tree or commit to produce an archive for. + +path:: + If one or more paths are specified, include only these in the + archive, otherwise include all files and subdirectories. + +BACKEND EXTRA OPTIONS +--------------------- + +zip +~~~ +-0:: + Store the files instead of deflating them. +-9:: + Highest and slowest compression level. You can specify any + number from 1 to 9 to adjust compression speed and ratio. + + +CONFIGURATION +------------- +By default, file and directories modes are set to 0666 or 0777 in tar +archives. It is possible to change this by setting the "umask" variable +in the repository configuration as follows : + +[tar] + umask = 002 ;# group friendly + +The special umask value "user" indicates that the user's current umask +will be used instead. The default value remains 0, which means world +readable/writable files and directories. + +EXAMPLES +-------- +git archive --format=tar --prefix=junk/ HEAD | (cd /var/tmp/ && tar xf -):: + + Create a tar archive that contains the contents of the + latest commit on the current branch, and extracts it in + `/var/tmp/junk` directory. + +git archive --format=tar --prefix=git-1.4.0/ v1.4.0 | gzip >git-1.4.0.tar.gz:: + + Create a compressed tarball for v1.4.0 release. + +git archive --format=tar --prefix=git-1.4.0/ v1.4.0{caret}\{tree\} | gzip >git-1.4.0.tar.gz:: + + Create a compressed tarball for v1.4.0 release, but without a + global extended pax header. + +git archive --format=zip --prefix=git-docs/ HEAD:Documentation/ > git-1.4.0-docs.zip:: + + Put everything in the current head's Documentation/ directory + into 'git-1.4.0-docs.zip', with the prefix 'git-docs/'. + +Author +------ +Written by Franck Bui-Huu and Rene Scharfe. + +Documentation +-------------- +Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>. + +GIT +--- +Part of the gitlink:git[7] suite diff --git a/Documentation/git-blame.txt b/Documentation/git-blame.txt index bfed945914..e1f89444a9 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-cherry-pick.txt b/Documentation/git-cherry-pick.txt index bfa950ca19..875edb6b9f 100644 --- a/Documentation/git-cherry-pick.txt +++ b/Documentation/git-cherry-pick.txt @@ -7,7 +7,7 @@ git-cherry-pick - Apply the change introduced by an existing commit SYNOPSIS -------- -'git-cherry-pick' [--edit] [-n] [-r] <commit> +'git-cherry-pick' [--edit] [-n] [-x] <commit> DESCRIPTION ----------- @@ -24,13 +24,22 @@ OPTIONS With this option, `git-cherry-pick` will let you edit the commit message prior committing. --r|--replay:: - Usually the command appends which commit was +-x:: + Cause the command to append which commit was cherry-picked after the original commit message when - making a commit. This option, '--replay', causes it to - use the original commit message intact. This is useful - when you are reordering the patches in your private tree - before publishing. + making a commit. Do not use this option if you are + cherry-picking from your private branch because the + information is useless to the recipient. If on the + other hand you are cherry-picking between two publicly + visible branches (e.g. backporting a fix to a + maintenance branch for an older release from a + development branch), adding this information can be + useful. + +-r|--replay:: + It used to be that the command defaulted to do `-x` + described above, and `-r` was to disable it. Now the + default is not to do `-x` so this option is a no-op. -n|--no-commit:: Usually the command automatically creates a commit with diff --git a/Documentation/git-daemon.txt b/Documentation/git-daemon.txt index 0f7d274eab..d562232e52 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 dc7683383c..d8af4d961b 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] + [-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,19 @@ 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 + option forces paths to be output relative to the project + top directory. + -E | --extended-regexp | -G | --basic-regexp:: Use POSIX extended/basic regexp for patterns. Default is to use basic regexp. diff --git a/Documentation/git-http-push.txt b/Documentation/git-http-push.txt index 7e1f894a92..c2485c6e9c 100644 --- a/Documentation/git-http-push.txt +++ b/Documentation/git-http-push.txt @@ -34,7 +34,7 @@ OPTIONS Report the list of objects being walked locally and the list of objects successfully sent to the remote repository. -<ref>...: +<ref>...:: The remote refs to update. diff --git a/Documentation/git-init-db.txt b/Documentation/git-init-db.txt index 63cd5dab3f..ca7d09dc0a 100644 --- a/Documentation/git-init-db.txt +++ b/Documentation/git-init-db.txt @@ -48,6 +48,10 @@ is given: - 'all' (or 'world' or 'everybody'): Same as 'group', but make the repository readable by all users. +By default, the configuration flag receive.denyNonFastforward is enabled +in shared repositories, so that you cannot force a non fast-forwarding push +into it. + -- diff --git a/Documentation/git-ls-remote.txt b/Documentation/git-ls-remote.txt index ae4c1a250e..c8a4c5a4d9 100644 --- a/Documentation/git-ls-remote.txt +++ b/Documentation/git-ls-remote.txt @@ -3,16 +3,19 @@ git-ls-remote(1) NAME ---- -git-ls-remote - Look at references other repository has +git-ls-remote - List references in a remote repository SYNOPSIS -------- -'git-ls-remote' [--heads] [--tags] <repository> <refs>... +[verse] +'git-ls-remote' [--heads] [--tags] [-u <exec> | --upload-pack <exec>] + <repository> <refs>... DESCRIPTION ----------- -Displays the references other repository has. +Displays references available in a remote repository along with the associated +commit IDs. OPTIONS @@ -23,9 +26,16 @@ OPTIONS both, references stored in refs/heads and refs/tags are displayed. +-u <exec>, --upload-pack=<exec>:: + Specify the full path of gitlink:git-upload-pack[1] on the remote + host. This allows listing references from repositories accessed via + SSH and where the SSH deamon does not use the PATH configured by the + user. Also see the '--exec' option for gitlink:git-peek-remote[1]. + <repository>:: Location of the repository. The shorthand defined in - $GIT_DIR/branches/ can be used. + $GIT_DIR/branches/ can be used. Use "." (dot) to list references in + the local repository. <refs>...:: When unspecified, all references, after filtering done diff --git a/Documentation/git-pack-objects.txt b/Documentation/git-pack-objects.txt index 4991f88c92..f52e8fa8bf 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,8 +56,26 @@ base-name:: Write the pack contents (what would have been written to .pack file) out to the standard output. ---window and --depth:: - These two options affects how the objects contained in +--revs:: + Read the revision arguments from the standard input, instead of + individual object names. The revision arguments are processed + the same way as gitlink:git-rev-list[1] with `--objects` flag + uses its `commit` arguments to build the list of objects it + outputs. The objects on the resulting list are packed. + +--unpacked:: + This implies `--revs`. When processing the list of + revision arguments read from the standard input, limit + the objects packed to those that are not already packed. + +--all:: + This implies `--revs`. In addition to the list of + revision arguments read from the standard input, pretend + as if all refs under `$GIT_DIR/refs` are specified to be + included. + +--window=[N], --depth=[N]:: + These two options affect how the objects contained in the pack are stored using delta compression. The objects are first internally sorted by type, size and optionally names and compared against the other objects @@ -66,6 +84,7 @@ base-name:: it too deep affects the performance on the unpacker side, because delta data needs to be applied that many times to get to the necessary object. + The default value for both --window and --depth is 10. --incremental:: This flag causes an object already in a pack ignored @@ -103,6 +122,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 f9457d45ed..0dfadc2a32 100644 --- a/Documentation/git-receive-pack.txt +++ b/Documentation/git-receive-pack.txt @@ -73,6 +73,8 @@ packed and is served via a dumb transport. There are other real-world examples of using update and post-update hooks found in the Documentation/howto directory. +git-receive-pack honours the receive.denyNonFastforwards flag, which +tells it if updates to a ref should be denied if they are not fast-forwards. OPTIONS ------- diff --git a/Documentation/git-repack.txt b/Documentation/git-repack.txt index 951622774a..d2eaa0995d 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,17 @@ OPTIONS Do not update the server information with `git update-server-info`. +--window=[N], --depth=[N]:: + These two options affect how the objects contained in the pack are + stored using delta compression. The objects are first internally + sorted by type, size and optionally names and compared against the + other objects within `--window` to see if using delta compression saves + space. `--depth` limits the maximum delta depth; making it too deep + affects the performance on the unpacker side, because delta data needs + to be applied that many times to get to the necessary object. + The default value for both --window and --depth is 10. + + Author ------ Written by Linus Torvalds <torvalds@osdl.org> diff --git a/Documentation/git-repo-config.txt b/Documentation/git-repo-config.txt index b03d66f61c..8a1ab61e94 100644 --- a/Documentation/git-repo-config.txt +++ b/Documentation/git-repo-config.txt @@ -54,7 +54,8 @@ OPTIONS --get:: Get the value for a given key (optionally filtered by a regex - matching the value). + matching the value). Returns error code 1 if the key was not + found and error code 2 if multiple key values were found. --get-all:: Like get, but does not fail if the number of values for the key diff --git a/Documentation/git-rev-list.txt b/Documentation/git-rev-list.txt index dd9fff16d3..00a95e249f 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-rev-parse.txt b/Documentation/git-rev-parse.txt index b761b4b965..2f1306c1d9 100644 --- a/Documentation/git-rev-parse.txt +++ b/Documentation/git-rev-parse.txt @@ -138,7 +138,7 @@ syntax. 'rev{caret}0' means the commit itself and is used when 'rev' is the object name of a tag object that refers to a commit object. -* A suffix '~<n>' to a revision parameter means the commit +* A suffix '{tilde}<n>' to a revision parameter means the commit object that is the <n>th generation grand-parent of the named commit object, following only the first parent. I.e. rev~3 is equivalent to rev{caret}{caret}{caret} which is equivalent to\ diff --git a/Documentation/git-send-pack.txt b/Documentation/git-send-pack.txt index 9e67f17302..5376f68548 100644 --- a/Documentation/git-send-pack.txt +++ b/Documentation/git-send-pack.txt @@ -43,7 +43,7 @@ OPTIONS <directory>:: The repository to update. -<ref>...: +<ref>...:: The remote refs to update. diff --git a/Documentation/git-shortlog.txt b/Documentation/git-shortlog.txt index 7486ebe785..d54fc3e5c6 100644 --- a/Documentation/git-shortlog.txt +++ b/Documentation/git-shortlog.txt @@ -7,16 +7,29 @@ git-shortlog - Summarize 'git log' output SYNOPSIS -------- -git-log --pretty=short | 'git-shortlog' +git-log --pretty=short | 'git-shortlog' [-h] [-n] [-s] DESCRIPTION ----------- Summarizes 'git log' output in a format suitable for inclusion -in release announcements. Each commit will be grouped by author +in release announcements. Each commit will be grouped by author and the first line of the commit message will be shown. Additionally, "[PATCH]" will be stripped from the commit description. +OPTIONS +------- + +-h:: + Print a short usage message and exit. + +-n:: + Sort output according to the number of commits per author instead + of author alphabetic order. + +-s:: + Supress commit description and provide a commit count summary only. + FILES ----- '.mailmap':: diff --git a/Documentation/git-svn.txt b/Documentation/git-svn.txt index 7d86809844..450ff1f85b 100644 --- a/Documentation/git-svn.txt +++ b/Documentation/git-svn.txt @@ -12,10 +12,8 @@ SYNOPSIS DESCRIPTION ----------- git-svn is a simple conduit for changesets between a single Subversion -branch and git. - -git-svn is not to be confused with git-svnimport. The were designed -with very different goals in mind. +branch and git. It is not to be confused with gitlink:git-svnimport[1]. +They were designed with very different goals in mind. git-svn is designed for an individual developer who wants a bidirectional flow of changesets between a single branch in Subversion @@ -34,26 +32,63 @@ git-svnimport is designed for. COMMANDS -------- -init:: +-- + +'init':: Creates an empty git repository with additional metadata directories for git-svn. The Subversion URL must be specified - as a command-line argument. - -fetch:: - Fetch unfetched revisions from the Subversion URL we are - tracking. refs/remotes/git-svn will be updated to the - latest revision. - - Note: You should never attempt to modify the remotes/git-svn - branch outside of git-svn. Instead, create a branch from - remotes/git-svn and work on that branch. Use the 'commit' - command (see below) to write git commits back to - remotes/git-svn. - - See 'Additional Fetch Arguments' if you are interested in - manually joining branches on commit. - -commit:: + as a command-line argument. Optionally, the target directory + to operate on can be specified as a second argument. Normally + this command initializes the current directory. + +'fetch':: + +Fetch unfetched revisions from the Subversion URL we are +tracking. refs/remotes/git-svn will be updated to the +latest revision. + +Note: You should never attempt to modify the remotes/git-svn +branch outside of git-svn. Instead, create a branch from +remotes/git-svn and work on that branch. Use the 'commit' +command (see below) to write git commits back to +remotes/git-svn. + +See '<<fetch-args,Additional Fetch Arguments>>' if you are interested in +manually joining branches on commit. + +'dcommit':: + Commit all diffs from the current HEAD directly to the SVN + repository, and then rebase or reset (depending on whether or + not there is a diff between SVN and HEAD). It is recommended + that you run git-svn fetch and rebase (not pull) your commits + against the latest changes in the SVN repository. + This is advantageous over 'commit' (below) because it produces + cleaner, more linear history. + +'log':: + This should make it easy to look up svn log messages when svn + users refer to -r/--revision numbers. + + The following features from `svn log' are supported: + + --revision=<n>[:<n>] - is supported, non-numeric args are not: + HEAD, NEXT, BASE, PREV, etc ... + -v/--verbose - it's not completely compatible with + the --verbose output in svn log, but + reasonably close. + --limit=<n> - is NOT the same as --max-count, + doesn't count merged/excluded commits + --incremental - supported + + New features: + + --show-commit - shows the git commit sha1, as well + --oneline - our version of --pretty=oneline + + Any other arguments are passed directly to `git log' + +'commit':: + You should consider using 'dcommit' instead of this command. Commit specified commit or tree objects to SVN. This relies on your imported fetch data being up-to-date. This makes absolutely no attempts to do patching when committing to SVN, it @@ -61,9 +96,9 @@ commit:: commit. All merging is assumed to have taken place independently of git-svn functions. -rebuild:: +'rebuild':: Not a part of daily usage, but this is a useful command if - you've just cloned a repository (using git-clone) that was + you've just cloned a repository (using gitlink:git-clone[1]) that was tracked with git-svn. Unfortunately, git-clone does not clone git-svn metadata and the svn working tree that git-svn uses for its operations. This rebuilds the metadata so git-svn can @@ -71,130 +106,263 @@ rebuild:: specified at the command-line if the directory/repository you're tracking has moved or changed protocols. -show-ignore:: +'show-ignore':: Recursively finds and lists the svn:ignore property on directories. The output is suitable for appending to the $GIT_DIR/info/exclude file. +'commit-diff':: + Commits the diff of two tree-ish arguments from the + command-line. This command is intended for interopability with + git-svnimport and does not rely on being inside an git-svn + init-ed repository. This command takes three arguments, (a) the + original tree to diff against, (b) the new tree result, (c) the + URL of the target Subversion repository. The final argument + (URL) may be omitted if you are working from a git-svn-aware + repository (that has been init-ed with git-svn). + +'graft-branches':: + This command attempts to detect merges/branches from already + imported history. Techniques used currently include regexes, + file copies, and tree-matches). This command generates (or + modifies) the $GIT_DIR/info/grafts file. This command is + considered experimental, and inherently flawed because + merge-tracking in SVN is inherently flawed and inconsistent + across different repositories. + +'multi-init':: + This command supports git-svnimport-like command-line syntax for + importing repositories that are layed out as recommended by the + SVN folks. This is a bit more tolerant than the git-svnimport + command-line syntax and doesn't require the user to figure out + where the repository URL ends and where the repository path + begins. + +'multi-fetch':: + This runs fetch on all known SVN branches we're tracking. This + will NOT discover new branches (unlike git-svnimport), so + multi-init will need to be re-run (it's idempotent). + +-- + OPTIONS ------- +-- + +--shared:: +--template=<template_directory>:: + Only used with the 'init' command. + These are passed directly to gitlink:git-init-db[1]. + -r <ARG>:: --revision <ARG>:: - Only used with the 'fetch' command. - Takes any valid -r<argument> svn would accept and passes it - directly to svn. -r<ARG1>:<ARG2> ranges and "{" DATE "}" syntax - is also supported. This is passed directly to svn, see svn - documentation for more details. +Only used with the 'fetch' command. + +Takes any valid -r<argument> svn would accept and passes it +directly to svn. -r<ARG1>:<ARG2> ranges and "{" DATE "}" syntax +is also supported. This is passed directly to svn, see svn +documentation for more details. - This can allow you to make partial mirrors when running fetch. +This can allow you to make partial mirrors when running fetch. -:: --stdin:: - Only used with the 'commit' command. - Read a list of commits from stdin and commit them in reverse - order. Only the leading sha1 is read from each line, so - git-rev-list --pretty=oneline output can be used. +Only used with the 'commit' command. + +Read a list of commits from stdin and commit them in reverse +order. Only the leading sha1 is read from each line, so +git-rev-list --pretty=oneline output can be used. --rmdir:: - Only used with the 'commit' command. - Remove directories from the SVN tree if there are no files left - behind. SVN can version empty directories, and they are not - removed by default if there are no files left in them. git - cannot version empty directories. Enabling this flag will make - the commit to SVN act like git. +Only used with the 'dcommit', 'commit' and 'commit-diff' commands. + +Remove directories from the SVN tree if there are no files left +behind. SVN can version empty directories, and they are not +removed by default if there are no files left in them. git +cannot version empty directories. Enabling this flag will make +the commit to SVN act like git. - repo-config key: svn.rmdir +repo-config key: svn.rmdir -e:: --edit:: - Only used with the 'commit' command. - Edit the commit message before committing to SVN. This is off by - default for objects that are commits, and forced on when committing - tree objects. +Only used with the 'dcommit', 'commit' and 'commit-diff' commands. + +Edit the commit message before committing to SVN. This is off by +default for objects that are commits, and forced on when committing +tree objects. - repo-config key: svn.edit +repo-config key: svn.edit -l<num>:: --find-copies-harder:: - Both of these are only used with the 'commit' command. - They are both passed directly to git-diff-tree see - git-diff-tree(1) for more information. +Only used with the 'dcommit', 'commit' and 'commit-diff' commands. - repo-config key: svn.l - repo-config key: svn.findcopiesharder +They are both passed directly to git-diff-tree see +gitlink:git-diff-tree[1] for more information. + +[verse] +repo-config key: svn.l +repo-config key: svn.findcopiesharder -A<filename>:: --authors-file=<filename>:: - Syntax is compatible with the files used by git-svnimport and - git-cvsimport: +Syntax is compatible with the files used by git-svnimport and +git-cvsimport: ------------------------------------------------------------------------ -loginname = Joe User <user@example.com> + loginname = Joe User <user@example.com> ------------------------------------------------------------------------ - If this option is specified and git-svn encounters an SVN - committer name that does not exist in the authors-file, git-svn - will abort operation. The user will then have to add the - appropriate entry. Re-running the previous git-svn command - after the authors-file is modified should continue operation. +If this option is specified and git-svn encounters an SVN +committer name that does not exist in the authors-file, git-svn +will abort operation. The user will then have to add the +appropriate entry. Re-running the previous git-svn command +after the authors-file is modified should continue operation. + +repo-config key: svn.authorsfile + +-q:: +--quiet:: + Make git-svn less verbose. This only affects git-svn if you + have the SVN::* libraries installed and are using them. + +--repack[=<n>]:: +--repack-flags=<flags> + These should help keep disk usage sane for large fetches + with many revisions. + + --repack takes an optional argument for the number of revisions + to fetch before repacking. This defaults to repacking every + 1000 commits fetched if no argument is specified. + + --repack-flags are passed directly to gitlink:git-repack[1]. + +repo-config key: svn.repack +repo-config key: svn.repackflags + +-m:: +--merge:: +-s<strategy>:: +--strategy=<strategy>:: + +These are only used with the 'dcommit' command. + +Passed directly to git-rebase when using 'dcommit' if a +'git-reset' cannot be used (see dcommit). + +-n:: +--dry-run:: + +This is only used with the 'dcommit' command. - repo-config key: svn.authors-file +Print out the series of git arguments that would show +which diffs would be committed to SVN. + +-- ADVANCED OPTIONS ---------------- +-- + -b<refname>:: --branch <refname>:: - Used with 'fetch' or 'commit'. +Used with 'fetch' or 'commit'. - This can be used to join arbitrary git branches to remotes/git-svn - on new commits where the tree object is equivalent. +This can be used to join arbitrary git branches to remotes/git-svn +on new commits where the tree object is equivalent. - When used with different GIT_SVN_ID values, tags and branches in - SVN can be tracked this way, as can some merges where the heads - end up having completely equivalent content. This can even be - used to track branches across multiple SVN _repositories_. +When used with different GIT_SVN_ID values, tags and branches in +SVN can be tracked this way, as can some merges where the heads +end up having completely equivalent content. This can even be +used to track branches across multiple SVN _repositories_. - This option may be specified multiple times, once for each - branch. +This option may be specified multiple times, once for each +branch. - repo-config key: svn.branch +repo-config key: svn.branch -i<GIT_SVN_ID>:: --id <GIT_SVN_ID>:: - This sets GIT_SVN_ID (instead of using the environment). See - the section on "Tracking Multiple Repositories or Branches" for - more information on using GIT_SVN_ID. + +This sets GIT_SVN_ID (instead of using the environment). See the +section on +'<<tracking-multiple-repos,Tracking Multiple Repositories or Branches>>' +for more information on using GIT_SVN_ID. + +--follow-parent:: + This is especially helpful when we're tracking a directory + that has been moved around within the repository, or if we + started tracking a branch and never tracked the trunk it was + descended from. + + This relies on the SVN::* libraries to work. + +repo-config key: svn.followparent + +--no-metadata:: + This gets rid of the git-svn-id: lines at the end of every commit. + + With this, you lose the ability to use the rebuild command. If + you ever lose your .git/svn/git-svn/.rev_db file, you won't be + able to fetch again, either. This is fine for one-shot imports. + + The 'git-svn log' command will not work on repositories using this, + either. + +repo-config key: svn.nometadata + +-- COMPATIBILITY OPTIONS --------------------- +-- + --upgrade:: - Only used with the 'rebuild' command. +Only used with the 'rebuild' command. - Run this if you used an old version of git-svn that used - "git-svn-HEAD" instead of "remotes/git-svn" as the branch - for tracking the remote. +Run this if you used an old version of git-svn that used +"git-svn-HEAD" instead of "remotes/git-svn" as the branch +for tracking the remote. --no-ignore-externals:: - Only used with the 'fetch' and 'rebuild' command. +Only used with the 'fetch' and 'rebuild' command. + +This command has no effect when you are using the SVN::* +libraries with git, svn:externals are always avoided. - By default, git-svn passes --ignore-externals to svn to avoid - fetching svn:external trees into git. Pass this flag to enable - externals tracking directly via git. +By default, git-svn passes --ignore-externals to svn to avoid +fetching svn:external trees into git. Pass this flag to enable +externals tracking directly via git. - Versions of svn that do not support --ignore-externals are - automatically detected and this flag will be automatically - enabled for them. +Versions of svn that do not support --ignore-externals are +automatically detected and this flag will be automatically +enabled for them. - Otherwise, do not enable this flag unless you know what you're - doing. +Otherwise, do not enable this flag unless you know what you're +doing. - repo-config key: svn.noignoreexternals +repo-config key: svn.noignoreexternals + +--ignore-nodate:: +Only used with the 'fetch' command. + +By default git-svn will crash if it tries to import a revision +from SVN which has '(no date)' listed as the date of the revision. +This is repository corruption on SVN's part, plain and simple. +But sometimes you really need those revisions anyway. + +If supplied git-svn will convert '(no date)' entries to the UNIX +epoch (midnight on Jan. 1, 1970). Yes, that's probably very wrong. +SVN was very wrong. + +-- Basic Examples ~~~~~~~~~~~~~~ @@ -202,7 +370,7 @@ Basic Examples Tracking and contributing to an Subversion managed-project: ------------------------------------------------------------------------ -# Initialize a tree (like git init-db): +# Initialize a repo (like git init-db): git-svn init http://svn.foo.org/project/trunk # Fetch remote revisions: git-svn fetch @@ -212,12 +380,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 +408,7 @@ any automated merge/branch tracking on the Subversion side and leaves it entirely up to the user on the git side. It's simply not worth it to do a useful translation when the original signal is weak. +[[tracking-multiple-repos]] TRACKING MULTIPLE REPOSITORIES OR BRANCHES ------------------------------------------ This is for advanced users, most users should ignore this section. @@ -235,12 +418,13 @@ branches or directories in a Subversion repository, git-svn has a simple hack to allow it to track an arbitrary number of related _or_ unrelated SVN repositories via one git repository. Simply set the GIT_SVN_ID environment variable to a name other other than "git-svn" (the default) -and git-svn will ignore the contents of the $GIT_DIR/git-svn directory -and instead do all of its work in $GIT_DIR/$GIT_SVN_ID for that +and git-svn will ignore the contents of the $GIT_DIR/svn/git-svn directory +and instead do all of its work in $GIT_DIR/svn/$GIT_SVN_ID for that invocation. The interface branch will be remotes/$GIT_SVN_ID, instead of remotes/git-svn. Any remotes/$GIT_SVN_ID branch should never be modified by the user outside of git-svn commands. +[[fetch-args]] ADDITIONAL FETCH ARGUMENTS -------------------------- This is for advanced users, most users should ignore this section. @@ -251,14 +435,21 @@ optionally be specified in the form of sha1 hex sums at the command-line. Unfetched SVN revisions may also be tied to particular git commits with the following syntax: +------------------------------------------------ svn_revision_number=git_commit_sha1 +------------------------------------------------ -This allows you to tie unfetched SVN revision 375 to your current HEAD:: +This allows you to tie unfetched SVN revision 375 to your current HEAD: - `git-svn fetch 375=$(git-rev-parse HEAD)` +------------------------------------------------ + git-svn fetch 375=$(git-rev-parse HEAD) +------------------------------------------------ Advanced Example: Tracking a Reorganized Repository ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Note: this example is now obsolete if you have SVN::* libraries +installed. Simply use --follow-parent when fetching. + If you're tracking a directory that has moved, or otherwise been branched or tagged off of another directory in the repository and you care about the full history of the project, then you can read this @@ -289,20 +480,18 @@ he needed to continue tracking /ufoai/trunk where /trunk left off. BUGS ---- -If somebody commits a conflicting changeset to SVN at a bad moment -(right before you commit) causing a conflict and your commit to fail, -your svn working tree ($GIT_DIR/git-svn/tree) may be dirtied. The -easiest thing to do is probably just to rm -rf $GIT_DIR/git-svn/tree and -run 'rebuild'. + +If you are not using the SVN::* Perl libraries and somebody commits a +conflicting changeset to SVN at a bad moment (right before you commit) +causing a conflict and your commit to fail, your svn working tree +($GIT_DIR/git-svn/tree) may be dirtied. The easiest thing to do is +probably just to rm -rf $GIT_DIR/git-svn/tree and run 'rebuild'. We ignore all SVN properties except svn:executable. Too difficult to map them since we rely heavily on git write-tree being _exactly_ the same on both the SVN and git working trees and I prefer not to clutter working trees with metadata files. -svn:keywords can't be ignored in Subversion (at least I don't know of -a way to ignore them). - Renamed and copied directories are not detected by git and hence not tracked when committing to SVN. I do not plan on adding support for this as it's quite difficult and time-consuming to get working for all @@ -310,6 +499,10 @@ the possible corner cases (git doesn't do it, either). Renamed and copied files are fully supported if they're similar enough for git to detect them. +SEE ALSO +-------- +gitlink:git-rebase[1] + Author ------ Written by Eric Wong <normalperson@yhbt.net>. diff --git a/Documentation/git-tar-tree.txt b/Documentation/git-tar-tree.txt index 1e1c7fa856..74a6fddd9a 100644 --- a/Documentation/git-tar-tree.txt +++ b/Documentation/git-tar-tree.txt @@ -12,6 +12,9 @@ SYNOPSIS DESCRIPTION ----------- +THIS COMMAND IS DEPRECATED. Use `git-archive` with `--format=tar` +option instead. + Creates a tar archive containing the tree structure for the named tree. When <base> is specified it is added as a leading path to the files in the generated tar archive. diff --git a/Documentation/git-unpack-objects.txt b/Documentation/git-unpack-objects.txt index c20b38b08a..ff6184b0f7 100644 --- a/Documentation/git-unpack-objects.txt +++ b/Documentation/git-unpack-objects.txt @@ -8,7 +8,7 @@ git-unpack-objects - Unpack objects from a packed archive SYNOPSIS -------- -'git-unpack-objects' [-n] [-q] <pack-file +'git-unpack-objects' [-n] [-q] [-r] <pack-file DESCRIPTION @@ -34,6 +34,12 @@ OPTIONS The command usually shows percentage progress. This flag suppresses it. +-r:: + When unpacking a corrupt packfile, the command dies at + the first corruption. This flag tells it to keep going + and make the best effort to recover as many objects as + possible. + Author ------ diff --git a/Documentation/git-update-index.txt b/Documentation/git-update-index.txt index 3ae6e74573..41bb7e12e3 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 0000000000..388bb53d29 --- /dev/null +++ b/Documentation/git-upload-archive.txt @@ -0,0 +1,37 @@ +git-upload-archive(1) +==================== + +NAME +---- +git-upload-archive - Send archive + + +SYNOPSIS +-------- +'git-upload-archive' <directory> + +DESCRIPTION +----------- +Invoked by 'git-archive --remote' and sends a generated archive to the +other end over the git protocol. + +This command is usually not invoked directly by the end user. The UI +for the protocol is on the 'git-archive' side, and the program pair +is meant to be used to get an archive from a remote repository. + +OPTIONS +------- +<directory>:: + The repository to get a tar archive from. + +Author +------ +Written by Franck Bui-Huu. + +Documentation +-------------- +Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>. + +GIT +--- +Part of the gitlink:git[7] suite diff --git a/Documentation/git-upload-tar.txt b/Documentation/git-upload-tar.txt deleted file mode 100644 index 394af62015..0000000000 --- a/Documentation/git-upload-tar.txt +++ /dev/null @@ -1,39 +0,0 @@ -git-upload-tar(1) -================= - -NAME ----- -git-upload-tar - Send tar archive - - -SYNOPSIS --------- -'git-upload-tar' <directory> - -DESCRIPTION ------------ -Invoked by 'git-tar-tree --remote' and sends a generated tar archive -to the other end over the git protocol. - -This command is usually not invoked directly by the end user. -The UI for the protocol is on the 'git-tar-tree' side, and the -program pair is meant to be used to get a tar archive from a -remote repository. - - -OPTIONS -------- -<directory>:: - The repository to get a tar archive from. - -Author ------- -Written by Junio C Hamano <junio@kernel.org> - -Documentation --------------- -Documentation by Junio C Hamano. - -GIT ---- -Part of the gitlink:git[7] suite diff --git a/Documentation/git.txt b/Documentation/git.txt index bcf187a11c..3af6fc63e2 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 ----------- @@ -242,14 +243,13 @@ gitlink:git-update-server-info[1]:: Updates auxiliary information on a dumb server to help clients discover references and packs on it. +gitlink:git-upload-archive[1]:: + Invoked by 'git-archive' to send a generated archive. + gitlink:git-upload-pack[1]:: Invoked by 'git-fetch-pack' to push what are asked for. -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 +269,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 +305,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 +488,6 @@ gitlink:git-stripspace[1]:: Filter out empty lines. -Commands not yet documented ---------------------------- - -gitlink:gitk[1]:: - The gitk repository browser. - - Configuration Mechanism ----------------------- @@ -633,10 +632,22 @@ git Diffs other ~~~~~ +'GIT_PAGER':: + This environment variable overrides `$PAGER`. + 'GIT_TRACE':: - If this variable is set git will print `trace:` messages on + If this variable is set to "1", "2" or "true" (comparison + is case insensitive), git will print `trace:` messages on stderr telling about alias expansion, built-in command execution and external command execution. + If this variable is set to an integer value greater than 1 + and lower than 10 (strictly) then git will interpret this + value as an open file descriptor and will try to write the + trace messages into this file descriptor. + Alternatively, if this variable is set to an absolute path + (starting with a '/' character), git will interpret this + as a file path and will try to write the trace messages + into it. Discussion[[Discussion]] ------------------------ diff --git a/Documentation/gitk.txt b/Documentation/gitk.txt index cb482bf98e..f1aeb07f64 100644 --- a/Documentation/gitk.txt +++ b/Documentation/gitk.txt @@ -3,26 +3,56 @@ gitk(1) NAME ---- -gitk - Some git command not yet documented. - +gitk - git repository browser SYNOPSIS -------- -'gitk' [ --option ] <args>... +'gitk' [<option>...] [<revs>] [--] [<path>...] DESCRIPTION ----------- -Does something not yet documented. +Displays changes in a repository or a selected set of commits. This includes +visualizing the commit graph, showing information related to each commit, and +the files in the trees of each revision. +Historically, gitk was the first repository browser. It's written in tcl/tk +and started off in a separate repository but was later merged into the main +git repository. OPTIONS ------- ---option:: - Some option not yet documented. +To control which revisions to shown, the command takes options applicable to +the gitlink:git-rev-list[1] command. This manual page describes only the most +frequently used options. + +-n <number>, --max-count=<number>:: + + Limits the number of commits to show. + +--since=<date>:: + + Show commits more recent than a specific date. + +--until=<date>:: + + Show commits older than a specific date. + +--all:: + + Show all branches. -<args>...:: - Some argument not yet documented. +<revs>:: + Limit the revisions to show. This can be either a single revision + meaning show from the given revision and back, or it can be a range in + the form "'<from>'..'<to>'" to show all revisions between '<from>' and + back to '<to>'. Note, more advanced revision selection can be applied. + +<path>:: + + Limit commits to the ones touching files in the given paths. Note, to + avoid ambiguity wrt. revision names use "--" to separate the paths + from any preceeding options. Examples -------- @@ -37,13 +67,32 @@ gitk --since="2 weeks ago" \-- gitk:: The "--" is necessary to avoid confusion with the *branch* named 'gitk' +gitk --max-count=100 --all -- Makefile:: + + Show at most 100 changes made to the file 'Makefile'. Instead of only + looking for changes in the current branch look in all branches. + +See Also +-------- +'qgit(1)':: + A repository browser written in C++ using Qt. + +'gitview(1)':: + A repository browser written in Python using Gtk. It's based on + 'bzrk(1)' and distributed in the contrib area of the git repository. + +'tig(1)':: + A minimal repository browser and git tool output highlighter written + in C using Ncurses. + Author ------ -Written by Paul Mackerras <paulus@samba.org> +Written by Paul Mackerras <paulus@samba.org>. Documentation -------------- -Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>. +Documentation by Junio C Hamano, Jonas Fonseca, and the git-list +<git@vger.kernel.org>. GIT --- diff --git a/Documentation/glossary.txt b/Documentation/glossary.txt index 14449ca8ba..7e560b0eea 100644 --- a/Documentation/glossary.txt +++ b/Documentation/glossary.txt @@ -179,7 +179,7 @@ object name:: character hexadecimal encoding of the hash of the object (possibly followed by a white space). -object type: +object type:: One of the identifiers "commit","tree","tag" and "blob" describing the type of an object. @@ -324,7 +324,7 @@ tag:: A tag is most typically used to mark a particular point in the commit ancestry chain. -unmerged index: +unmerged index:: An index which contains unmerged index entries. working tree:: diff --git a/Documentation/hooks.txt b/Documentation/hooks.txt index 898b4aaf80..517f49b5cc 100644 --- a/Documentation/hooks.txt +++ b/Documentation/hooks.txt @@ -5,8 +5,7 @@ Hooks are little scripts you can place in `$GIT_DIR/hooks` directory to trigger action at certain points. When `git-init-db` is run, a handful example hooks are copied in the `hooks` directory of the new repository, but by default they are -all disabled. To enable a hook, make it executable with `chmod -+x`. +all disabled. To enable a hook, make it executable with `chmod +x`. This document describes the currently defined hooks. @@ -16,16 +15,16 @@ applypatch-msg This hook is invoked by `git-applypatch` script, which is typically invoked by `git-applymbox`. It takes a single parameter, the name of the file that holds the proposed commit -log message. Exiting with non-zero status causes the -'git-applypatch' to abort before applying the patch. +log message. Exiting with non-zero status causes +`git-applypatch` to abort before applying the patch. The hook is allowed to edit the message file in place, and can be used to normalize the message into some project standard format (if the project has one). It can also be used to refuse the commit after inspecting the message file. -The default applypatch-msg hook, when enabled, runs the -commit-msg hook, if the latter is enabled. +The default 'applypatch-msg' hook, when enabled, runs the +'commit-msg' hook, if the latter is enabled. pre-applypatch -------------- @@ -39,8 +38,8 @@ after application of the patch not committed. It can be used to inspect the current working tree and refuse to make a commit if it does not pass certain test. -The default pre-applypatch hook, when enabled, runs the -pre-commit hook, if the latter is enabled. +The default 'pre-applypatch' hook, when enabled, runs the +'pre-commit' hook, if the latter is enabled. post-applypatch --------------- @@ -61,9 +60,9 @@ invoked before obtaining the proposed commit log message and making a commit. Exiting with non-zero status from this script causes the `git-commit` to abort. -The default pre-commit hook, when enabled, catches introduction +The default 'pre-commit' hook, when enabled, catches introduction of lines with trailing whitespaces and aborts the commit when -a such line is found. +such a line is found. commit-msg ---------- @@ -79,8 +78,8 @@ be used to normalize the message into some project standard format (if the project has one). It can also be used to refuse the commit after inspecting the message file. -The default commit-msg hook, when enabled, detects duplicate -Signed-off-by: lines, and aborts the commit when one is found. +The default 'commit-msg' hook, when enabled, detects duplicate +"Signed-off-by" lines, and aborts the commit if one is found. post-commit ----------- @@ -91,23 +90,24 @@ parameter, and is invoked after a commit is made. This hook is meant primarily for notification, and cannot affect the outcome of `git-commit`. -The default post-commit hook, when enabled, demonstrates how to +The default 'post-commit' hook, when enabled, demonstrates how to send out a commit notification e-mail. update ------ This hook is invoked by `git-receive-pack` on the remote repository, -which is happens when a `git push` is done on a local repository. +which happens when a `git push` is done on a local repository. Just before updating the ref on the remote repository, the update hook is invoked. Its exit status determines the success or failure of the ref update. The hook executes once for each ref to be updated, and takes three parameters: - - the name of the ref being updated, - - the old object name stored in the ref, - - and the new objectname to be stored in the ref. + + - the name of the ref being updated, + - the old object name stored in the ref, + - and the new objectname to be stored in the ref. A zero exit from the update hook allows the ref to be updated. Exiting with a non-zero status prevents `git-receive-pack` @@ -126,16 +126,16 @@ Another use suggested on the mailing list is to use this hook to implement access control which is finer grained than the one based on filesystem group. -The standard output of this hook is sent to /dev/null; if you -want to report something to the git-send-pack on the other end, -you can redirect your output to your stderr. +The standard output of this hook is sent to `/dev/null`; if you +want to report something to the `git-send-pack` on the other end, +you can redirect your output to your `stderr`. post-update ----------- This hook is invoked by `git-receive-pack` on the remote repository, -which is happens when a `git push` is done on a local repository. +which happens when a `git push` is done on a local repository. It executes on the remote repository once after all the refs have been updated. @@ -145,16 +145,16 @@ name of ref that was actually updated. This hook is meant primarily for notification, and cannot affect the outcome of `git-receive-pack`. -The post-update hook can tell what are the heads that were pushed, +The 'post-update' hook can tell what are the heads that were pushed, but it does not know what their original and updated values are, so it is a poor place to do log old..new. -The default post-update hook, when enabled, runs +When enabled, the default 'post-update' hook runs `git-update-server-info` to keep the information used by dumb -transports (e.g., http) up-to-date. If you are publishing -a git repository that is accessible via http, you should +transports (e.g., HTTP) up-to-date. If you are publishing +a git repository that is accessible via HTTP, you should probably enable this hook. -The standard output of this hook is sent to /dev/null; if you -want to report something to the git-send-pack on the other end, -you can redirect your output to your stderr. +The standard output of this hook is sent to `/dev/null`; if you +want to report something to the `git-send-pack` on the other end, +you can redirect your output to your `stderr`. diff --git a/Documentation/technical/racy-git.txt b/Documentation/technical/racy-git.txt new file mode 100644 index 0000000000..7597d04142 --- /dev/null +++ b/Documentation/technical/racy-git.txt @@ -0,0 +1,193 @@ +Use of index and Racy git problem +================================= + +Background +---------- + +The index is one of the most important data structure in git. +It represents a virtual working tree state by recording list of +paths and their object names and serves as a staging area to +write out the next tree object to be committed. The state is +"virtual" in the sense that it does not necessarily have to, and +often does not, match the files in the working tree. + +There are cases git needs to examine the differences between the +virtual working tree state in the index and the files in the +working tree. The most obvious case is when the user asks `git +diff` (or its low level implementation, `git diff-files`) or +`git-ls-files --modified`. In addition, git internally checks +if the files in the working tree is different from what are +recorded in the index to avoid stomping on local changes in them +during patch application, switching branches, and merging. + +In order to speed up this comparison between the files in the +working tree and the index entries, the index entries record the +information obtained from the filesystem via `lstat(2)` system +call when they were last updated. When checking if they differ, +git first runs `lstat(2)` on the files and compare the result +with this information (this is what was originally done by the +`ce_match_stat()` function, which the current code does in +`ce_match_stat_basic()` function). If some of these "cached +stat information" fields do not match, git can tell that the +files are modified without even looking at their contents. + +Note: not all members in `struct stat` obtained via `lstat(2)` +are used for this comparison. For example, `st_atime` obviously +is not useful. Currently, git compares the file type (regular +files vs symbolic links) and executable bits (only for regular +files) from `st_mode` member, `st_mtime` and `st_ctime` +timestamps, `st_uid`, `st_gid`, `st_ino`, and `st_size` members. +With a `USE_STDEV` compile-time option, `st_dev` is also +compared, but this is not enabled by default because this member +is not stable on network filesystems. With `USE_NSEC` +compile-time option, `st_mtim.tv_nsec` and `st_ctim.tv_nsec` +members are also compared, but this is not enabled by default +because the value of this member becomes meaningless once the +inode is evicted from the inode cache on filesystems that do not +store it on disk. + + +Racy git +-------- + +There is one slight problem with the optimization based on the +cached stat information. Consider this sequence: + + $ git update-index 'foo' + : modify 'foo' in-place without changing its size + +The first `update-index` computes the object name of the +contents of file `foo` and updates the index entry for `foo` +along with the `struct stat` information. If the modification +that follows it happens very fast so that the file's `st_mtime` +timestamp does not change, after this sequence, the cached stat +information the index entry records still exactly match what you +can obtain from the filesystem, but the file `foo` is modified. +This way, git can incorrectly think files in the working tree +are unmodified even though they actually are. This is called +the "racy git" problem (discovered by Pasky), and the entries +that appear clean when they may not be because of this problem +are called "racily clean". + +To avoid this problem, git does two things: + +. When the cached stat information says the file has not been + modified, and the `st_mtime` is the same as (or newer than) + the timestamp of the index file itself (which is the time `git + update-index foo` finished running in the above example), it + also compares the contents with the object registered in the + index entry to make sure they match. + +. When the index file is updated that contains racily clean + entries, cached `st_size` information is truncated to zero + before writing a new version of the index file. + +Because the index file itself is written after collecting all +the stat information from updated paths, `st_mtime` timestamp of +it is usually the same as or newer than any of the paths the +index contains. And no matter how quick the modification that +follows `git update-index foo` finishes, the resulting +`st_mtime` timestamp on `foo` cannot get the timestamp earlier +than the index file. Therefore, index entries that can be +racily clean are limited to the ones that have the same +timestamp as the index file itself. + +The callers that want to check if an index entry matches the +corresponding file in the working tree continue to call +`ce_match_stat()`, but with this change, `ce_match_stat()` uses +`ce_modified_check_fs()` to see if racily clean ones are +actually clean after comparing the cached stat information using +`ce_match_stat_basic()`. + +The problem the latter solves is this sequence: + + $ git update-index 'foo' + : modify 'foo' in-place without changing its size + : wait for enough time + $ git update-index 'bar' + +Without the latter, the timestamp of the index file gets a newer +value, and falsely clean entry `foo` would not be caught by the +timestamp comparison check done with the former logic anymore. +The latter makes sure that the cached stat information for `foo` +would never match with the file in the working tree, so later +checks by `ce_match_stat_basic()` would report the index entry +does not match the file and git does not have to fall back on more +expensive `ce_modified_check_fs()`. + + +Runtime penalty +--------------- + +The runtime penalty of falling back to `ce_modified_check_fs()` +from `ce_match_stat()` can be very expensive when there are many +racily clean entries. An obvious way to artificially create +this situation is to give the same timestamp to all the files in +the working tree in a large project, run `git update-index` on +them, and give the same timestamp to the index file: + + $ date >.datestamp + $ git ls-files | xargs touch -r .datestamp + $ git ls-files | git update-index --stdin + $ touch -r .datestamp .git/index + +This will make all index entries racily clean. The linux-2.6 +project, for example, there are over 20,000 files in the working +tree. On my Athron 64X2 3800+, after the above: + + $ /usr/bin/time git diff-files + 1.68user 0.54system 0:02.22elapsed 100%CPU (0avgtext+0avgdata 0maxresident)k + 0inputs+0outputs (0major+67111minor)pagefaults 0swaps + $ git update-index MAINTAINERS + $ /usr/bin/time git diff-files + 0.02user 0.12system 0:00.14elapsed 100%CPU (0avgtext+0avgdata 0maxresident)k + 0inputs+0outputs (0major+935minor)pagefaults 0swaps + +Running `git update-index` in the middle checked the racily +clean entries, and left the cached `st_mtime` for all the paths +intact because they were actually clean (so this step took about +the same amount of time as the first `git diff-files`). After +that, they are not racily clean anymore but are truly clean, so +the second invocation of `git diff-files` fully took advantage +of the cached stat information. + + +Avoiding runtime penalty +------------------------ + +In order to avoid the above runtime penalty, the recent "master" +branch (post 1.4.2) has a code that makes sure the index file +gets timestamp newer than the youngest files in the index when +there are many young files with the same timestamp as the +resulting index file would otherwise would have by waiting +before finishing writing the index file out. + +I suspect that in practice the situation where many paths in the +index are all racily clean is quite rare. The only code paths +that can record recent timestamp for large number of paths I +know of are: + +. Initial `git add .` of a large project. + +. `git checkout` of a large project from an empty index into an + unpopulated working tree. + +Note: switching branches with `git checkout` keeps the cached +stat information of existing working tree files that are the +same between the current branch and the new branch, which are +all older than the resulting index file, and they will not +become racily clean. Only the files that are actually checked +out can become racily clean. + +In a large project where raciness avoidance cost really matters, +however, the initial computation of all object names in the +index takes more than one second, and the index file is written +out after all that happens. Therefore the timestamp of the +index file will be more than one seconds later than the the +youngest file in the working tree. This means that in these +cases there actually will not be any racily clean entry in +the resulting index. + +So in summary I think we should not worry about avoiding the +runtime penalty and get rid of the "wait before finishing +writing" code out. diff --git a/Documentation/tutorial-2.txt b/Documentation/tutorial-2.txt index 2f4fe1217a..42b6e7d7d2 100644 --- a/Documentation/tutorial-2.txt +++ b/Documentation/tutorial-2.txt @@ -368,7 +368,7 @@ in the index file is identical to the one in the working directory. In addition to being the staging area for new commits, the index file is also populated from the object database when checking out a branch, and is used to hold the trees involved in a merge operation. -See the link:core-tutorial.txt[core tutorial] and the relevant man +See the link:core-tutorial.html[core tutorial] and the relevant man pages for details. What next? diff --git a/GIT-VERSION-GEN b/GIT-VERSION-GEN index 14923c973b..0cacac38fc 100755 --- a/GIT-VERSION-GEN +++ b/GIT-VERSION-GEN @@ -1,7 +1,7 @@ #!/bin/sh GVF=GIT-VERSION-FILE -DEF_VER=v1.4.2.GIT +DEF_VER=v1.4.3.GIT LF=' ' @@ -16,7 +16,7 @@ install" would not work. Alternatively you can use autoconf generated ./configure script to set up install paths (via config.mak.autogen), so you can write instead - $ autoconf ;# as yourself if ./configure doesn't exist yet + $ make configure ;# as yourself $ ./configure --prefix=/usr ;# as yourself $ make all doc ;# as yourself # make install install-doc ;# as root @@ -38,6 +38,19 @@ Issues of note: has been actively developed since 1997, and people have moved over to graphical file managers. + - You can use git after building but without installing if you + wanted to. Various git commands need to find other git + commands and scripts to do their work, so you would need to + arrange a few environment variables to tell them that their + friends will be found in your built source area instead of at + their standard installation area. Something like this works + for me: + + GIT_EXEC_PATH=`pwd` + PATH=`pwd`:$PATH + GITPERLLIB=`pwd`/perl/blib/lib + export GIT_EXEC_PATH PATH GITPERLLIB + - Git is reasonably self-sufficient, but does depend on a few external programs and libraries: @@ -1,11 +1,6 @@ # The default target of this Makefile is... all: -# Define MOZILLA_SHA1 environment variable when running make to make use of -# a bundled SHA1 routine coming from Mozilla. It is GPL'd and should be fast -# on non-x86 architectures (e.g. PowerPC), while the OpenSSL version (default -# choice) has very fast version optimized for i586. -# # Define NO_OPENSSL environment variable if you do not have OpenSSL. # This also implies MOZILLA_SHA1. # @@ -27,7 +22,7 @@ all: # Define NO_C99_FORMAT if your formatted IO functions (printf/scanf et.al.) # 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. +# some C compilers supported these specifiers prior to C99 as an extension. # # Define NO_STRCASESTR if you don't have strcasestr. # @@ -60,6 +55,11 @@ all: # Define ARM_SHA1 environment variable when running make to make use of # a bundled SHA1 routine optimized for ARM. # +# Define MOZILLA_SHA1 environment variable when running make to make use of +# a bundled SHA1 routine coming from Mozilla. It is GPL'd and should be fast +# on non-x86 architectures (e.g. PowerPC), while the OpenSSL version (default +# choice) has very fast version optimized for i586. +# # Define NEEDS_SSL_WITH_CRYPTO if you need -lcrypto with -lssl (Darwin). # # Define NEEDS_LIBICONV if linking with libc is not enough (Darwin). @@ -81,18 +81,16 @@ all: # Define NO_ACCURATE_DIFF if your diff program at least sometimes misses # a missing newline at the end of the file. # -# Define NO_PYTHON if you want to lose all benefits of the recursive merge. -# # Define COLLISION_CHECK below if you believe that SHA1's # 1461501637330902918203684832716283019655932542976 hashes do not give you # sufficient guarantee that no collisions between objects will ever happen. - +# # Define USE_NSEC below if you want git to care about sub-second file mtimes # and ctimes. Note that you need recent glibc (at least 2.2.4) for this, and # it will BREAK YOUR LOCAL DIFFS! show-diff and anything using it will likely # randomly break unless your underlying filesystem supports those sub-second # times (my ext3 doesn't). - +# # Define USE_STDEV below if you want git to care about the underlying device # change being considered an inode change from the update-cache perspective. @@ -121,6 +119,20 @@ template_dir = $(prefix)/share/git-core/templates/ GIT_PYTHON_DIR = $(prefix)/share/git-core/python # DESTDIR= +# default configuration for gitweb +GITWEB_CONFIG = gitweb_config.perl +GITWEB_HOME_LINK_STR = projects +GITWEB_SITENAME = +GITWEB_PROJECTROOT = /pub/git +GITWEB_EXPORT_OK = +GITWEB_STRICT_EXPORT = +GITWEB_BASE_URL = +GITWEB_LIST = +GITWEB_HOMETEXT = indextext.html +GITWEB_CSS = gitweb.css +GITWEB_LOGO = git-logo.png +GITWEB_FAVICON = git-favicon.png + export prefix bindir gitexecdir template_dir GIT_PYTHON_DIR CC = gcc @@ -137,6 +149,12 @@ SPARSE_FLAGS = -D__BIG_ENDIAN__ -D__powerpc__ ### --- END CONFIGURATION SECTION --- +# Those must not be GNU-specific; they are shared with perl/ which may +# be built by a different compiler. (Note that this is an artifact now +# but it still might be nice to keep that distinction.) +BASIC_CFLAGS = +BASIC_LDFLAGS = + SCRIPT_SH = \ git-bisect.sh git-branch.sh git-checkout.sh \ git-cherry.sh git-clean.sh git-clone.sh git-commit.sh \ @@ -160,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)) \ @@ -173,35 +191,32 @@ SIMPLE_PROGRAMS = \ # ... and all the rest that could be moved out of bindir to gitexecdir PROGRAMS = \ - git-checkout-index$X \ git-convert-objects$X git-fetch-pack$X git-fsck-objects$X \ git-hash-object$X git-index-pack$X git-local-fetch$X \ git-merge-base$X \ - git-merge-index$X git-mktag$X git-mktree$X git-pack-objects$X git-patch-id$X \ + git-merge-index$X git-mktag$X git-mktree$X git-patch-id$X \ git-peek-remote$X git-receive-pack$X \ git-send-pack$X git-shell$X \ git-show-index$X git-ssh-fetch$X \ git-ssh-upload$X git-unpack-file$X \ - git-unpack-objects$X git-update-server-info$X \ + git-update-server-info$X \ git-upload-pack$X git-verify-pack$X \ - git-symbolic-ref$X \ - git-name-rev$X git-pack-redundant$X git-var$X \ - git-describe$X git-merge-tree$X git-blame$X git-imap-send$X - -BUILT_INS = git-log$X git-whatchanged$X git-show$X git-update-ref$X \ - git-count-objects$X git-diff$X git-push$X git-mailsplit$X \ - git-grep$X git-add$X git-rm$X git-rev-list$X git-stripspace$X \ - git-check-ref-format$X git-rev-parse$X git-mailinfo$X \ - git-init-db$X git-tar-tree$X git-upload-tar$X git-format-patch$X \ - git-ls-files$X git-ls-tree$X git-get-tar-commit-id$X \ - git-read-tree$X git-commit-tree$X git-write-tree$X \ - git-apply$X git-show-branch$X git-diff-files$X git-update-index$X \ - git-diff-index$X git-diff-stages$X git-diff-tree$X git-cat-file$X \ - git-fmt-merge-msg$X git-prune$X git-mv$X git-prune-packed$X \ - git-repo-config$X + git-pack-redundant$X git-var$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 \ + git-get-tar-commit-id$X \ + $(patsubst builtin-%.o,git-%$X,$(BUILTIN_OBJS)) # what 'all' will build and 'install' will install, in gitexecdir -ALL_PROGRAMS = $(PROGRAMS) $(SIMPLE_PROGRAMS) $(SCRIPTS) +ALL_PROGRAMS = $(PROGRAMS) $(SIMPLE_PROGRAMS) $(SCRIPTS) \ + git-merge-recur$X # Backward compatibility -- to be removed after 1.0 PROGRAMS += git-ssh-pull$X git-ssh-push$X @@ -224,10 +239,10 @@ 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 + tree-walk.h log-tree.h dir.h path-list.h unpack-trees.h builtin.h DIFF_OBJS = \ diff.o diff-lib.o diffcore-break.o diffcore-order.o \ @@ -236,29 +251,65 @@ 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 $(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-log.o builtin-help.o builtin-count.o builtin-diff.o builtin-push.o \ - builtin-grep.o builtin-add.o builtin-rev-list.o builtin-check-ref-format.o \ - builtin-rm.o builtin-init-db.o builtin-rev-parse.o \ - builtin-tar-tree.o builtin-upload-tar.o builtin-update-index.o \ - builtin-ls-files.o builtin-ls-tree.o builtin-write-tree.o \ - builtin-read-tree.o builtin-commit-tree.o builtin-mailinfo.o \ - builtin-apply.o builtin-show-branch.o builtin-diff-files.o \ - builtin-diff-index.o builtin-diff-stages.o builtin-diff-tree.o \ - builtin-cat-file.o builtin-mailsplit.o builtin-stripspace.o \ - builtin-update-ref.o builtin-fmt-merge-msg.o builtin-prune.o \ - builtin-mv.o builtin-prune-packed.o builtin-repo-config.o + builtin-add.o \ + builtin-apply.o \ + builtin-archive.o \ + builtin-cat-file.o \ + builtin-checkout-index.o \ + builtin-check-ref-format.o \ + builtin-commit-tree.o \ + builtin-count-objects.o \ + builtin-diff.o \ + builtin-diff-files.o \ + builtin-diff-index.o \ + builtin-diff-stages.o \ + builtin-diff-tree.o \ + builtin-fmt-merge-msg.o \ + builtin-grep.o \ + builtin-init-db.o \ + builtin-log.o \ + builtin-ls-files.o \ + builtin-ls-tree.o \ + builtin-mailinfo.o \ + builtin-mailsplit.o \ + builtin-mv.o \ + builtin-name-rev.o \ + builtin-pack-objects.o \ + builtin-prune.o \ + builtin-prune-packed.o \ + builtin-push.o \ + builtin-read-tree.o \ + builtin-repo-config.o \ + 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 \ + builtin-tar-tree.o \ + builtin-unpack-objects.o \ + builtin-update-index.o \ + builtin-update-ref.o \ + builtin-upload-archive.o \ + builtin-verify-pack.o \ + builtin-write-tree.o GITLIBS = $(LIB_FILE) $(XDIFF_LIB) -LIBS = $(GITLIBS) -lz +EXTLIBS = -lz # # Platform specific tweaks @@ -280,14 +331,14 @@ ifeq ($(uname_S),Darwin) NO_STRLCPY = YesPlease ifndef NO_FINK ifeq ($(shell test -d /sw/lib && echo y),y) - ALL_CFLAGS += -I/sw/include - ALL_LDFLAGS += -L/sw/lib + BASIC_CFLAGS += -I/sw/include + BASIC_LDFLAGS += -L/sw/lib endif endif ifndef NO_DARWIN_PORTS ifeq ($(shell test -d /opt/local/lib && echo y),y) - ALL_CFLAGS += -I/opt/local/include - ALL_LDFLAGS += -L/opt/local/lib + BASIC_CFLAGS += -I/opt/local/include + BASIC_LDFLAGS += -L/opt/local/lib endif endif endif @@ -296,7 +347,6 @@ ifeq ($(uname_S),SunOS) NEEDS_NSL = YesPlease SHELL_PATH = /bin/bash NO_STRCASESTR = YesPlease - NO_STRLCPY = YesPlease ifeq ($(uname_R),5.8) NEEDS_LIBICONV = YesPlease NO_UNSETENV = YesPlease @@ -310,7 +360,7 @@ ifeq ($(uname_S),SunOS) endif INSTALL = ginstall TAR = gtar - ALL_CFLAGS += -D__EXTENSIONS__ + BASIC_CFLAGS += -D__EXTENSIONS__ endif ifeq ($(uname_O),Cygwin) NO_D_TYPE_IN_DIRENT = YesPlease @@ -328,21 +378,22 @@ ifeq ($(uname_O),Cygwin) endif ifeq ($(uname_S),FreeBSD) NEEDS_LIBICONV = YesPlease - ALL_CFLAGS += -I/usr/local/include - ALL_LDFLAGS += -L/usr/local/lib + BASIC_CFLAGS += -I/usr/local/include + BASIC_LDFLAGS += -L/usr/local/lib endif ifeq ($(uname_S),OpenBSD) NO_STRCASESTR = YesPlease NEEDS_LIBICONV = YesPlease - ALL_CFLAGS += -I/usr/local/include - ALL_LDFLAGS += -L/usr/local/lib + BASIC_CFLAGS += -I/usr/local/include + BASIC_LDFLAGS += -L/usr/local/lib endif ifeq ($(uname_S),NetBSD) ifeq ($(shell expr "$(uname_R)" : '[01]\.'),2) NEEDS_LIBICONV = YesPlease endif - ALL_CFLAGS += -I/usr/pkg/include - ALL_LDFLAGS += -L/usr/pkg/lib -Wl,-rpath,/usr/pkg/lib + BASIC_CFLAGS += -I/usr/pkg/include + BASIC_LDFLAGS += -L/usr/pkg/lib + ALL_LDFLAGS += -Wl,-rpath,/usr/pkg/lib endif ifeq ($(uname_S),AIX) NO_STRCASESTR=YesPlease @@ -356,9 +407,9 @@ ifeq ($(uname_S),IRIX64) NO_STRLCPY = YesPlease NO_SOCKADDR_STORAGE=YesPlease SHELL_PATH=/usr/gnu/bin/bash - ALL_CFLAGS += -DPATH_MAX=1024 + BASIC_CFLAGS += -DPATH_MAX=1024 # for now, build 32-bit version - ALL_LDFLAGS += -L/usr/lib32 + BASIC_LDFLAGS += -L/usr/lib32 endif ifneq (,$(findstring arm,$(uname_M))) ARM_SHA1 = YesPlease @@ -380,7 +431,7 @@ endif ifndef NO_CURL ifdef CURLDIR # This is still problematic -- gcc does not always want -R. - ALL_CFLAGS += -I$(CURLDIR)/include + BASIC_CFLAGS += -I$(CURLDIR)/include CURL_LIBCURL = -L$(CURLDIR)/lib -R$(CURLDIR)/lib -lcurl else CURL_LIBCURL = -lcurl @@ -401,13 +452,13 @@ ifndef NO_OPENSSL OPENSSL_LIBSSL = -lssl ifdef OPENSSLDIR # Again this may be problematic -- gcc does not always want -R. - ALL_CFLAGS += -I$(OPENSSLDIR)/include + BASIC_CFLAGS += -I$(OPENSSLDIR)/include OPENSSL_LINK = -L$(OPENSSLDIR)/lib -R$(OPENSSLDIR)/lib else OPENSSL_LINK = endif else - ALL_CFLAGS += -DNO_OPENSSL + BASIC_CFLAGS += -DNO_OPENSSL MOZILLA_SHA1 = 1 OPENSSL_LIBSSL = endif @@ -419,32 +470,32 @@ endif ifdef NEEDS_LIBICONV ifdef ICONVDIR # Again this may be problematic -- gcc does not always want -R. - ALL_CFLAGS += -I$(ICONVDIR)/include + BASIC_CFLAGS += -I$(ICONVDIR)/include ICONV_LINK = -L$(ICONVDIR)/lib -R$(ICONVDIR)/lib else ICONV_LINK = endif - LIBS += $(ICONV_LINK) -liconv + EXTLIBS += $(ICONV_LINK) -liconv endif ifdef NEEDS_SOCKET - LIBS += -lsocket + EXTLIBS += -lsocket SIMPLE_LIB += -lsocket endif ifdef NEEDS_NSL - LIBS += -lnsl + EXTLIBS += -lnsl SIMPLE_LIB += -lnsl endif ifdef NO_D_TYPE_IN_DIRENT - ALL_CFLAGS += -DNO_D_TYPE_IN_DIRENT + BASIC_CFLAGS += -DNO_D_TYPE_IN_DIRENT endif ifdef NO_D_INO_IN_DIRENT - ALL_CFLAGS += -DNO_D_INO_IN_DIRENT + BASIC_CFLAGS += -DNO_D_INO_IN_DIRENT endif ifdef NO_C99_FORMAT ALL_CFLAGS += -DNO_C99_FORMAT endif ifdef NO_SYMLINK_HEAD - ALL_CFLAGS += -DNO_SYMLINK_HEAD + BASIC_CFLAGS += -DNO_SYMLINK_HEAD endif ifdef NO_STRCASESTR COMPAT_CFLAGS += -DNO_STRCASESTR @@ -458,7 +509,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 @@ -467,21 +518,24 @@ ifdef NO_MMAP COMPAT_OBJS += compat/mmap.o endif ifdef NO_IPV6 - ALL_CFLAGS += -DNO_IPV6 + BASIC_CFLAGS += -DNO_IPV6 endif ifdef NO_SOCKADDR_STORAGE ifdef NO_IPV6 - ALL_CFLAGS += -Dsockaddr_storage=sockaddr_in + BASIC_CFLAGS += -Dsockaddr_storage=sockaddr_in else - ALL_CFLAGS += -Dsockaddr_storage=sockaddr_in6 + BASIC_CFLAGS += -Dsockaddr_storage=sockaddr_in6 endif endif ifdef NO_INET_NTOP LIB_OBJS += compat/inet_ntop.o endif +ifdef NO_INET_PTON + LIB_OBJS += compat/inet_pton.o +endif ifdef NO_ICONV - ALL_CFLAGS += -DNO_ICONV + BASIC_CFLAGS += -DNO_ICONV endif ifdef PPC_SHA1 @@ -497,12 +551,12 @@ ifdef MOZILLA_SHA1 LIB_OBJS += mozilla-sha1/sha1.o else SHA1_HEADER = <openssl/sha.h> - LIBS += $(LIB_4_CRYPTO) + EXTLIBS += $(LIB_4_CRYPTO) endif endif endif ifdef NO_ACCURATE_DIFF - ALL_CFLAGS += -DNO_ACCURATE_DIFF + BASIC_CFLAGS += -DNO_ACCURATE_DIFF endif # Shell quote (do not use $(call) to accommodate ancient setups); @@ -520,14 +574,23 @@ PERL_PATH_SQ = $(subst ','\'',$(PERL_PATH)) PYTHON_PATH_SQ = $(subst ','\'',$(PYTHON_PATH)) GIT_PYTHON_DIR_SQ = $(subst ','\'',$(GIT_PYTHON_DIR)) -ALL_CFLAGS += -DSHA1_HEADER='$(SHA1_HEADER_SQ)' $(COMPAT_CFLAGS) +LIBS = $(GITLIBS) $(EXTLIBS) + +BASIC_CFLAGS += -DSHA1_HEADER='$(SHA1_HEADER_SQ)' $(COMPAT_CFLAGS) LIB_OBJS += $(COMPAT_OBJS) + +ALL_CFLAGS += $(BASIC_CFLAGS) +ALL_LDFLAGS += $(BASIC_LDFLAGS) + export prefix TAR INSTALL DESTDIR SHELL_PATH template_dir + + ### Build rules -all: $(ALL_PROGRAMS) $(BUILT_INS) git$X gitk +all: $(ALL_PROGRAMS) $(BUILT_INS) git$X gitk gitweb/gitweb.cgi -all: +all: perl/Makefile + $(MAKE) -C perl $(MAKE) -C templates strip: $(PROGRAMS) git$X @@ -538,7 +601,10 @@ git$X: git.c common-cmds.h $(BUILTIN_OBJS) $(GITLIBS) GIT-CFLAGS $(ALL_CFLAGS) -o $@ $(filter %.c,$^) \ $(BUILTIN_OBJS) $(ALL_LDFLAGS) $(LIBS) -builtin-help.o: common-cmds.h +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 $@ @@ -558,9 +624,18 @@ $(patsubst %.sh,%,$(SCRIPT_SH)) : % : %.sh chmod +x $@+ mv $@+ $@ -$(patsubst %.perl,%,$(SCRIPT_PERL)) : % : %.perl +$(patsubst %.perl,%,$(SCRIPT_PERL)): perl/Makefile +$(patsubst %.perl,%,$(SCRIPT_PERL)): % : %.perl rm -f $@ $@+ - sed -e '1s|#!.*perl|#!$(PERL_PATH_SQ)|' \ + INSTLIBDIR=`$(MAKE) -C perl -s --no-print-directory instlibdir` && \ + sed -e '1{' \ + -e ' s|#!.*perl|#!$(PERL_PATH_SQ)|' \ + -e ' h' \ + -e ' s=.*=use lib (split(/:/, $$ENV{GITPERLLIB} || "@@INSTLIBDIR@@"));=' \ + -e ' H' \ + -e ' x' \ + -e '}' \ + -e 's|@@INSTLIBDIR@@|'"$$INSTLIBDIR"'|g' \ -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \ $@.perl >$@+ chmod +x $@+ @@ -583,6 +658,27 @@ git-status: git-commit cp $< $@+ mv $@+ $@ +gitweb/gitweb.cgi: gitweb/gitweb.perl + rm -f $@ $@+ + sed -e '1s|#!.*perl|#!$(PERL_PATH_SQ)|' \ + -e 's|++GIT_VERSION++|$(GIT_VERSION)|g' \ + -e 's|++GIT_BINDIR++|$(bindir)|g' \ + -e 's|++GITWEB_CONFIG++|$(GITWEB_CONFIG)|g' \ + -e 's|++GITWEB_HOME_LINK_STR++|$(GITWEB_HOME_LINK_STR)|g' \ + -e 's|++GITWEB_SITENAME++|$(GITWEB_SITENAME)|g' \ + -e 's|++GITWEB_PROJECTROOT++|$(GITWEB_PROJECTROOT)|g' \ + -e 's|++GITWEB_EXPORT_OK++|$(GITWEB_EXPORT_OK)|g' \ + -e 's|++GITWEB_STRICT_EXPORT++|$(GITWEB_STRICT_EXPORT)|g' \ + -e 's|++GITWEB_BASE_URL++|$(GITWEB_BASE_URL)|g' \ + -e 's|++GITWEB_LIST++|$(GITWEB_LIST)|g' \ + -e 's|++GITWEB_HOMETEXT++|$(GITWEB_HOMETEXT)|g' \ + -e 's|++GITWEB_CSS++|$(GITWEB_CSS)|g' \ + -e 's|++GITWEB_LOGO++|$(GITWEB_LOGO)|g' \ + -e 's|++GITWEB_FAVICON++|$(GITWEB_FAVICON)|g' \ + $< >$@+ + chmod +x $@+ + mv $@+ $@ + git-instaweb: git-instaweb.sh gitweb/gitweb.cgi gitweb/gitweb.css rm -f $@ $@+ sed -e '1s|#!.*/sh|#!$(SHELL_PATH_SQ)|' \ @@ -593,10 +689,17 @@ git-instaweb: git-instaweb.sh gitweb/gitweb.cgi gitweb/gitweb.css -e '/@@GITWEB_CGI@@/d' \ -e '/@@GITWEB_CSS@@/r gitweb/gitweb.css' \ -e '/@@GITWEB_CSS@@/d' \ - $@.sh | sed "s|/usr/bin/git|$(bindir)/git|" > $@+ + $@.sh > $@+ chmod +x $@+ mv $@+ $@ +configure: configure.ac + rm -f $@ $<+ + sed -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \ + $< > $<+ + autoconf -o $@ $<+ + rm -f $<+ + # These can record GIT_VERSION git$X git.spec \ $(patsubst %.sh,%,$(SCRIPT_SH)) \ @@ -662,6 +765,10 @@ $(XDIFF_LIB): $(XDIFF_OBJS) rm -f $@ && $(AR) rcs $@ $(XDIFF_OBJS) +perl/Makefile: perl/Git.pm perl/Makefile.PL GIT-CFLAGS + (cd perl && $(PERL_PATH) Makefile.PL \ + PREFIX='$(prefix_SQ)') + doc: $(MAKE) -C Documentation all @@ -724,6 +831,7 @@ install: all $(INSTALL) $(ALL_PROGRAMS) '$(DESTDIR_SQ)$(gitexecdir_SQ)' $(INSTALL) git$X gitk '$(DESTDIR_SQ)$(bindir_SQ)' $(MAKE) -C templates DESTDIR='$(DESTDIR_SQ)' install + $(MAKE) -C perl install $(INSTALL) -d -m755 '$(DESTDIR_SQ)$(GIT_PYTHON_DIR_SQ)' $(INSTALL) $(PYMODULES) '$(DESTDIR_SQ)$(GIT_PYTHON_DIR_SQ)' if test 'z$(bindir_SQ)' != 'z$(gitexecdir_SQ)'; \ @@ -748,8 +856,9 @@ git.spec: git.spec.in mv $@+ $@ GIT_TARNAME=git-$(GIT_VERSION) -dist: git.spec git-tar-tree - ./git-tar-tree HEAD^{tree} $(GIT_TARNAME) > $(GIT_TARNAME).tar +dist: git.spec git-archive + ./git-archive --format=tar \ + --prefix=$(GIT_TARNAME)/ HEAD^{tree} > $(GIT_TARNAME).tar @mkdir -p $(GIT_TARNAME) @cp git.spec $(GIT_TARNAME) @echo $(GIT_VERSION) > $(GIT_TARNAME)/version @@ -788,12 +897,15 @@ clean: rm -f $(ALL_PROGRAMS) $(BUILT_INS) git$X rm -f *.spec *.pyc *.pyo */*.pyc */*.pyo common-cmds.h TAGS tags rm -rf autom4te.cache - rm -f config.log config.mak.autogen configure config.status config.cache + rm -f configure config.log config.mak.autogen config.mak.append config.status config.cache rm -rf $(GIT_TARNAME) .doc-tmp-dir rm -f $(GIT_TARNAME).tar.gz git-core_$(GIT_VERSION)-*.tar.gz rm -f $(htmldocs).tar.gz $(manpages).tar.gz + rm -f gitweb/gitweb.cgi $(MAKE) -C Documentation/ clean - $(MAKE) -C templates clean + [ ! -f perl/Makefile ] || $(MAKE) -C perl/ clean || $(MAKE) -C perl/ clean + rm -f perl/ppport.h perl/Makefile.old + $(MAKE) -C templates/ clean $(MAKE) -C t/ clean rm -f GIT-VERSION-FILE GIT-CFLAGS @@ -807,7 +919,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 0000000000..ff0f6e2929 --- /dev/null +++ b/archive-tar.c @@ -0,0 +1,325 @@ +/* + * Copyright (c) 2005, 2006 Rene Scharfe + */ +#include <time.h> +#include "cache.h" +#include "commit.h" +#include "strbuf.h" +#include "tar.h" +#include "builtin.h" +#include "archive.h" + +#define RECORDSIZE (512) +#define BLOCKSIZE (RECORDSIZE * 20) + +static char block[BLOCKSIZE]; +static unsigned long offset; + +static time_t archive_time; +static int tar_umask; +static int verbose; + +/* writes out the whole block, but only if it is full */ +static void write_if_needed(void) +{ + if (offset == BLOCKSIZE) { + write_or_die(1, block, BLOCKSIZE); + offset = 0; + } +} + +/* + * queues up writes, so that all our write(2) calls write exactly one + * full block; pads writes to RECORDSIZE + */ +static void write_blocked(const void *data, unsigned long size) +{ + const char *buf = data; + unsigned long tail; + + if (offset) { + unsigned long chunk = BLOCKSIZE - offset; + if (size < chunk) + chunk = size; + memcpy(block + offset, buf, chunk); + size -= chunk; + offset += chunk; + buf += chunk; + write_if_needed(); + } + while (size >= BLOCKSIZE) { + write_or_die(1, buf, BLOCKSIZE); + size -= BLOCKSIZE; + buf += BLOCKSIZE; + } + if (size) { + memcpy(block + offset, buf, size); + offset += size; + } + tail = offset % RECORDSIZE; + if (tail) { + memset(block + offset, 0, RECORDSIZE - tail); + offset += RECORDSIZE - tail; + } + write_if_needed(); +} + +/* + * The end of tar archives is marked by 2*512 nul bytes and after that + * follows the rest of the block (if any). + */ +static void write_trailer(void) +{ + int tail = BLOCKSIZE - offset; + memset(block + offset, 0, tail); + write_or_die(1, block, BLOCKSIZE); + if (tail < 2 * RECORDSIZE) { + memset(block, 0, offset); + write_or_die(1, block, BLOCKSIZE); + } +} + +static void strbuf_append_string(struct strbuf *sb, const char *s) +{ + int slen = strlen(s); + int total = sb->len + slen; + if (total > sb->alloc) { + sb->buf = xrealloc(sb->buf, total); + sb->alloc = total; + } + memcpy(sb->buf + sb->len, s, slen); + sb->len = total; +} + +/* + * pax extended header records have the format "%u %s=%s\n". %u contains + * the size of the whole string (including the %u), the first %s is the + * keyword, the second one is the value. This function constructs such a + * string and appends it to a struct strbuf. + */ +static void strbuf_append_ext_header(struct strbuf *sb, const char *keyword, + const char *value, unsigned int valuelen) +{ + char *p; + int len, total, tmp; + + /* "%u %s=%s\n" */ + len = 1 + 1 + strlen(keyword) + 1 + valuelen + 1; + for (tmp = len; tmp > 9; tmp /= 10) + len++; + + total = sb->len + len; + if (total > sb->alloc) { + sb->buf = xrealloc(sb->buf, total); + sb->alloc = total; + } + + p = sb->buf; + p += sprintf(p, "%u %s=", len, keyword); + memcpy(p, value, valuelen); + p += valuelen; + *p = '\n'; + sb->len = total; +} + +static unsigned int ustar_header_chksum(const struct ustar_header *header) +{ + char *p = (char *)header; + unsigned int chksum = 0; + while (p < header->chksum) + chksum += *p++; + chksum += sizeof(header->chksum) * ' '; + p += sizeof(header->chksum); + while (p < (char *)header + sizeof(struct ustar_header)) + chksum += *p++; + return chksum; +} + +static int get_path_prefix(const struct strbuf *path, int maxlen) +{ + int i = path->len; + if (i > maxlen) + i = maxlen; + do { + i--; + } while (i > 0 && path->buf[i] != '/'); + return i; +} + +static void write_entry(const unsigned char *sha1, struct strbuf *path, + unsigned int mode, void *buffer, unsigned long size) +{ + struct ustar_header header; + struct strbuf ext_header; + + memset(&header, 0, sizeof(header)); + ext_header.buf = NULL; + ext_header.len = ext_header.alloc = 0; + + if (!sha1) { + *header.typeflag = TYPEFLAG_GLOBAL_HEADER; + mode = 0100666; + strcpy(header.name, "pax_global_header"); + } else if (!path) { + *header.typeflag = TYPEFLAG_EXT_HEADER; + mode = 0100666; + sprintf(header.name, "%s.paxheader", sha1_to_hex(sha1)); + } else { + if (verbose) + fprintf(stderr, "%.*s\n", path->len, path->buf); + if (S_ISDIR(mode)) { + *header.typeflag = TYPEFLAG_DIR; + mode = (mode | 0777) & ~tar_umask; + } else if (S_ISLNK(mode)) { + *header.typeflag = TYPEFLAG_LNK; + mode |= 0777; + } else if (S_ISREG(mode)) { + *header.typeflag = TYPEFLAG_REG; + mode = (mode | ((mode & 0100) ? 0777 : 0666)) & ~tar_umask; + } else { + error("unsupported file mode: 0%o (SHA1: %s)", + mode, sha1_to_hex(sha1)); + return; + } + if (path->len > sizeof(header.name)) { + int plen = get_path_prefix(path, sizeof(header.prefix)); + int rest = path->len - plen - 1; + if (plen > 0 && rest <= sizeof(header.name)) { + memcpy(header.prefix, path->buf, plen); + memcpy(header.name, path->buf + plen + 1, rest); + } else { + sprintf(header.name, "%s.data", + sha1_to_hex(sha1)); + strbuf_append_ext_header(&ext_header, "path", + path->buf, path->len); + } + } else + memcpy(header.name, path->buf, path->len); + } + + if (S_ISLNK(mode) && buffer) { + if (size > sizeof(header.linkname)) { + sprintf(header.linkname, "see %s.paxheader", + sha1_to_hex(sha1)); + strbuf_append_ext_header(&ext_header, "linkpath", + buffer, size); + } else + memcpy(header.linkname, buffer, size); + } + + sprintf(header.mode, "%07o", mode & 07777); + sprintf(header.size, "%011lo", S_ISREG(mode) ? size : 0); + sprintf(header.mtime, "%011lo", archive_time); + + /* XXX: should we provide more meaningful info here? */ + sprintf(header.uid, "%07o", 0); + sprintf(header.gid, "%07o", 0); + strlcpy(header.uname, "git", sizeof(header.uname)); + strlcpy(header.gname, "git", sizeof(header.gname)); + sprintf(header.devmajor, "%07o", 0); + sprintf(header.devminor, "%07o", 0); + + memcpy(header.magic, "ustar", 6); + memcpy(header.version, "00", 2); + + sprintf(header.chksum, "%07o", ustar_header_chksum(&header)); + + if (ext_header.len > 0) { + write_entry(sha1, NULL, 0, ext_header.buf, ext_header.len); + free(ext_header.buf); + } + write_blocked(&header, sizeof(header)); + if (S_ISREG(mode) && buffer && size > 0) + write_blocked(buffer, size); +} + +static void write_global_extended_header(const unsigned char *sha1) +{ + struct strbuf ext_header; + ext_header.buf = NULL; + ext_header.len = ext_header.alloc = 0; + strbuf_append_ext_header(&ext_header, "comment", sha1_to_hex(sha1), 40); + write_entry(NULL, NULL, 0, ext_header.buf, ext_header.len); + free(ext_header.buf); +} + +static int git_tar_config(const char *var, const char *value) +{ + if (!strcmp(var, "tar.umask")) { + if (!strcmp(value, "user")) { + tar_umask = umask(0); + umask(tar_umask); + } else { + tar_umask = git_config_int(var, value); + } + return 0; + } + return git_default_config(var, value); +} + +static int write_tar_entry(const unsigned char *sha1, + const char *base, int baselen, + const char *filename, unsigned mode, int stage) +{ + static struct strbuf path; + int filenamelen = strlen(filename); + void *buffer; + char type[20]; + unsigned long size; + + if (!path.alloc) { + path.buf = xmalloc(PATH_MAX); + path.alloc = PATH_MAX; + path.len = path.eof = 0; + } + if (path.alloc < baselen + filenamelen) { + free(path.buf); + path.buf = xmalloc(baselen + filenamelen); + path.alloc = baselen + filenamelen; + } + memcpy(path.buf, base, baselen); + memcpy(path.buf + baselen, filename, filenamelen); + path.len = baselen + filenamelen; + if (S_ISDIR(mode)) { + strbuf_append_string(&path, "/"); + buffer = NULL; + size = 0; + } else { + buffer = read_sha1_file(sha1, type, &size); + if (!buffer) + die("cannot read %s", sha1_to_hex(sha1)); + } + + write_entry(sha1, &path, mode, buffer, size); + free(buffer); + + return READ_TREE_RECURSIVE; +} + +int write_tar_archive(struct archiver_args *args) +{ + int plen = args->base ? strlen(args->base) : 0; + + git_config(git_tar_config); + + archive_time = args->time; + verbose = args->verbose; + + if (args->commit_sha1) + write_global_extended_header(args->commit_sha1); + + if (args->base && plen > 0 && args->base[plen - 1] == '/') { + char *base = xstrdup(args->base); + int baselen = strlen(base); + + while (baselen > 0 && base[baselen - 1] == '/') + base[--baselen] = '\0'; + write_tar_entry(args->tree->object.sha1, "", 0, base, 040777, 0); + free(base); + } + read_tree_recursive(args->tree, args->base, plen, 0, + args->pathspec, write_tar_entry); + write_trailer(); + + return 0; +} diff --git a/archive-zip.c b/archive-zip.c new file mode 100644 index 0000000000..3ffdad68d1 --- /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 0000000000..16dcdb875c --- /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 */ @@ -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; } @@ -351,10 +351,7 @@ static int fill_util_info(struct commit *commit) assert(util); assert(util->pathname); - if (get_blob_sha1(commit->tree, util->pathname, util->sha1)) - return 1; - else - return 0; + return !!get_blob_sha1(commit->tree, util->pathname, util->sha1); } static void alloc_line_map(struct commit *commit) @@ -620,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-apply.c b/builtin-apply.c index 9cf477c701..cbe597771b 100644 --- a/builtin-apply.c +++ b/builtin-apply.c @@ -27,22 +27,25 @@ static const char *prefix; static int prefix_length = -1; static int newfd = -1; +static int unidiff_zero; static int p_value = 1; -static int allow_binary_replacement = 0; -static int check_index = 0; -static int write_index = 0; -static int cached = 0; -static int diffstat = 0; -static int numstat = 0; -static int summary = 0; -static int check = 0; +static int check_index; +static int write_index; +static int cached; +static int diffstat; +static int numstat; +static int summary; +static int check; static int apply = 1; -static int no_add = 0; -static int show_index_info = 0; +static int apply_in_reverse; +static int apply_with_reject; +static int apply_verbosely; +static int no_add; +static int show_index_info; static int line_termination = '\n'; static unsigned long p_context = -1; static const char apply_usage[] = -"git-apply [--stat] [--numstat] [--summary] [--check] [--index] [--cached] [--apply] [--no-add] [--index-info] [--allow-binary-replacement] [-z] [-pNUM] [-CNUM] [--whitespace=<nowarn|warn|error|error-all|strip>] <patch>..."; +"git-apply [--stat] [--numstat] [--summary] [--check] [--index] [--cached] [--apply] [--no-add] [--index-info] [--allow-binary-replacement] [--reverse] [--reject] [--verbose] [-z] [-pNUM] [-CNUM] [--whitespace=<nowarn|warn|error|error-all|strip>] <patch>..."; static enum whitespace_eol { nowarn_whitespace, @@ -50,10 +53,10 @@ static enum whitespace_eol { error_on_whitespace, strip_whitespace, } new_whitespace = warn_on_whitespace; -static int whitespace_error = 0; +static int whitespace_error; static int squelch_whitespace_errors = 5; -static int applied_after_stripping = 0; -static const char *patch_input_file = NULL; +static int applied_after_stripping; +static const char *patch_input_file; static void parse_whitespace_option(const char *option) { @@ -108,21 +111,37 @@ static int max_change, max_len; */ static int linenr = 1; +/* + * This represents one "hunk" from a patch, starting with + * "@@ -oldpos,oldlines +newpos,newlines @@" marker. The + * patch text is pointed at by patch, and its byte length + * is stored in size. leading and trailing are the number + * of context lines. + */ struct fragment { unsigned long leading, trailing; unsigned long oldpos, oldlines; unsigned long newpos, newlines; const char *patch; int size; + int rejected; struct fragment *next; }; +/* + * When dealing with a binary patch, we reuse "leading" field + * to store the type of the binary hunk, either deflated "delta" + * or deflated "literal". + */ +#define binary_patch_method leading +#define BINARY_DELTA_DEFLATED 1 +#define BINARY_LITERAL_DEFLATED 2 + struct patch { char *new_name, *old_name, *def_name; unsigned int old_mode, new_mode; - int is_rename, is_copy, is_new, is_delete, is_binary, is_reverse; -#define BINARY_DELTA_DEFLATED 1 -#define BINARY_LITERAL_DEFLATED 2 + int is_rename, is_copy, is_new, is_delete, is_binary; + int rejected; unsigned long deflate_origlen; int lines_added, lines_deleted; int score; @@ -135,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) @@ -591,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': @@ -819,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) { @@ -841,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); @@ -887,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; @@ -938,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)) { @@ -954,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; @@ -965,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; } @@ -978,51 +1069,82 @@ static inline int metadata_changes(struct patch *patch) patch->old_mode != patch->new_mode); } -static int parse_binary(char *buffer, unsigned long size, struct patch *patch) +static char *inflate_it(const void *data, unsigned long size, + unsigned long inflated_size) { - /* We have read "GIT binary patch\n"; what follows is a line - * that says the patch method (currently, either "deflated - * literal" or "deflated delta") and the length of data before - * deflating; a sequence of 'length-byte' followed by base-85 - * encoded data follows. + z_stream stream; + void *out; + int st; + + memset(&stream, 0, sizeof(stream)); + + stream.next_in = (unsigned char *)data; + stream.avail_in = size; + stream.next_out = out = xmalloc(inflated_size); + stream.avail_out = inflated_size; + inflateInit(&stream); + st = inflate(&stream, Z_FINISH); + if ((st != Z_STREAM_END) || stream.total_out != inflated_size) { + free(out); + return NULL; + } + return out; +} + +static struct fragment *parse_binary_hunk(char **buf_p, + unsigned long *sz_p, + int *status_p, + int *used_p) +{ + /* Expect a line that begins with binary patch method ("literal" + * or "delta"), followed by the length of data before deflating. + * a sequence of 'length-byte' followed by base-85 encoded data + * should follow, terminated by a newline. * * Each 5-byte sequence of base-85 encodes up to 4 bytes, * and we would limit the patch line to 66 characters, * so one line can fit up to 13 groups that would decode * to 52 bytes max. The length byte 'A'-'Z' corresponds * to 1-26 bytes, and 'a'-'z' corresponds to 27-52 bytes. - * The end of binary is signaled with an empty line. */ int llen, used; - struct fragment *fragment; + unsigned long size = *sz_p; + char *buffer = *buf_p; + int patch_method; + unsigned long origlen; char *data = NULL; + int hunk_size = 0; + struct fragment *frag; - patch->fragments = fragment = xcalloc(1, sizeof(*fragment)); - - /* Grab the type of patch */ llen = linelen(buffer, size); used = llen; - linenr++; + + *status_p = 0; if (!strncmp(buffer, "delta ", 6)) { - patch->is_binary = BINARY_DELTA_DEFLATED; - patch->deflate_origlen = strtoul(buffer + 6, NULL, 10); + patch_method = BINARY_DELTA_DEFLATED; + origlen = strtoul(buffer + 6, NULL, 10); } else if (!strncmp(buffer, "literal ", 8)) { - patch->is_binary = BINARY_LITERAL_DEFLATED; - patch->deflate_origlen = strtoul(buffer + 8, NULL, 10); + patch_method = BINARY_LITERAL_DEFLATED; + origlen = strtoul(buffer + 8, NULL, 10); } else - return error("unrecognized binary patch at line %d: %.*s", - linenr-1, llen-1, buffer); + return NULL; + + linenr++; buffer += llen; while (1) { int byte_length, max_byte_length, newsize; llen = linelen(buffer, size); used += llen; linenr++; - if (llen == 1) + if (llen == 1) { + /* consume the blank line */ + buffer++; + size--; break; + } /* Minimum line is "A00000\n" which is 7-byte long, * and the line length must be multiple of 5 plus 2. */ @@ -1043,21 +1165,78 @@ static int parse_binary(char *buffer, unsigned long size, struct patch *patch) if (max_byte_length < byte_length || byte_length <= max_byte_length - 4) goto corrupt; - newsize = fragment->size + byte_length; + newsize = hunk_size + byte_length; data = xrealloc(data, newsize); - if (decode_85(data + fragment->size, - buffer + 1, - byte_length)) + if (decode_85(data + hunk_size, buffer + 1, byte_length)) goto corrupt; - fragment->size = newsize; + hunk_size = newsize; buffer += llen; size -= llen; } - fragment->patch = data; - return used; + + frag = xcalloc(1, sizeof(*frag)); + frag->patch = inflate_it(data, hunk_size, origlen); + if (!frag->patch) + goto corrupt; + free(data); + frag->size = origlen; + *buf_p = buffer; + *sz_p = size; + *used_p = used; + frag->binary_patch_method = patch_method; + return frag; + corrupt: - return error("corrupt binary patch at line %d: %.*s", - linenr-1, llen-1, buffer); + free(data); + *status_p = -1; + error("corrupt binary patch at line %d: %.*s", + linenr-1, llen-1, buffer); + return NULL; +} + +static int parse_binary(char *buffer, unsigned long size, struct patch *patch) +{ + /* We have read "GIT binary patch\n"; what follows is a line + * that says the patch method (currently, either "literal" or + * "delta") and the length of data before deflating; a + * sequence of 'length-byte' followed by base-85 encoded data + * follows. + * + * When a binary patch is reversible, there is another binary + * hunk in the same format, starting with patch method (either + * "literal" or "delta") with the length of data, and a sequence + * of length-byte + base-85 encoded data, terminated with another + * empty line. This data, when applied to the postimage, produces + * the preimage. + */ + struct fragment *forward; + struct fragment *reverse; + int status; + int used, used_1; + + forward = parse_binary_hunk(&buffer, &size, &status, &used); + if (!forward && !status) + /* there has to be one hunk (forward hunk) */ + return error("unrecognized binary patch at line %d", linenr-1); + if (status) + /* otherwise we already gave an error message */ + return status; + + reverse = parse_binary_hunk(&buffer, &size, &status, &used_1); + if (reverse) + used += used_1; + else if (status) { + /* not having reverse hunk is not an error, but having + * a corrupt reverse hunk is. + */ + free((void*) forward->patch); + free(forward); + return status; + } + forward->next = reverse; + patch->fragments = forward; + patch->is_binary = 1; + return used; } static int parse_chunk(char *buffer, unsigned long size, struct patch *patch) @@ -1105,14 +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); } @@ -1143,7 +1320,6 @@ static void reverse_patches(struct patch *p) swap(frag->newpos, frag->oldpos); swap(frag->newlines, frag->oldlines); } - p->is_reverse = !p->is_reverse; } } @@ -1206,8 +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) @@ -1345,26 +1520,71 @@ static int apply_line(char *output, const char *patch, int plen) { /* plen is number of bytes to be copied from patch, * starting at patch+1 (patch[0] is '+'). Typically - * patch[plen] is '\n'. + * patch[plen] is '\n', unless this is the incomplete + * last line. */ + int i; int add_nl_to_tail = 0; - if ((new_whitespace == strip_whitespace) && - 1 < plen && isspace(patch[plen-1])) { + int fixed = 0; + int last_tab_in_indent = -1; + int last_space_in_indent = -1; + int need_fix_leading_space = 0; + char *buf; + + if ((new_whitespace != strip_whitespace) || !whitespace_error) { + memcpy(output, patch + 1, plen); + return plen; + } + + if (1 < plen && isspace(patch[plen-1])) { if (patch[plen] == '\n') add_nl_to_tail = 1; plen--; while (0 < plen && isspace(patch[plen])) plen--; - applied_after_stripping++; + fixed = 1; + } + + for (i = 1; i < plen; i++) { + char ch = patch[i]; + if (ch == '\t') { + last_tab_in_indent = i; + if (0 <= last_space_in_indent) + need_fix_leading_space = 1; + } + else if (ch == ' ') + last_space_in_indent = i; + else + break; + } + + 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; } - memcpy(output, patch + 1, plen); + else + i = 1; + + memcpy(output, patch + i, plen); if (add_nl_to_tail) output[plen++] = '\n'; - return plen; + if (fixed) + applied_after_stripping++; + return output + plen - buf; } -static int apply_one_fragment(struct buffer_desc *desc, struct fragment *frag, - int reverse, int inaccurate_eof) +static int apply_one_fragment(struct buffer_desc *desc, struct fragment *frag, int inaccurate_eof) { int match_beginning, match_end; char *buf = desc->buffer; @@ -1396,7 +1616,7 @@ static int apply_one_fragment(struct buffer_desc *desc, struct fragment *frag, if (len < size && patch[len] == '\\') plen--; first = *patch; - if (reverse) { + if (apply_in_reverse) { if (first == '-') first = '+'; else if (first == '+') @@ -1439,14 +1659,25 @@ static int apply_one_fragment(struct buffer_desc *desc, struct fragment *frag, /* * If we don't have any leading/trailing data in the patch, * we want it to match at the beginning/end of the file. + * + * But that would break if the patch is generated with + * --unified=0; sane people wouldn't do that to cause us + * trouble, but we try to please not so sane ones as well. */ - match_beginning = !leading && (frag->oldpos == 1); - match_end = !trailing; + if (unidiff_zero) { + match_beginning = (!leading && !frag->oldpos); + match_end = 0; + } + else { + match_beginning = !leading && (frag->oldpos == 1); + match_end = !trailing; + } lines = 0; pos = frag->newpos; for (;;) { - offset = find_offset(buf, desc->size, oldlines, oldsize, pos, &lines); + offset = find_offset(buf, desc->size, + oldlines, oldsize, pos, &lines); if (match_end && offset + oldsize != desc->size) offset = -1; if (match_beginning && offset) @@ -1459,8 +1690,10 @@ static int apply_one_fragment(struct buffer_desc *desc, struct fragment *frag, /* Warn if it was necessary to reduce the number * of context lines. */ - if ((leading != frag->leading) || (trailing != frag->trailing)) - fprintf(stderr, "Context reduced to (%ld/%ld) to apply fragment at %d\n", + if ((leading != frag->leading) || + (trailing != frag->trailing)) + fprintf(stderr, "Context reduced to (%ld/%ld)" + " to apply fragment at %d\n", leading, trailing, pos + lines); if (size > alloc) { @@ -1470,7 +1703,9 @@ static int apply_one_fragment(struct buffer_desc *desc, struct fragment *frag, desc->buffer = buf; } desc->size = size; - memmove(buf + offset + newsize, buf + offset + oldsize, size - offset - newsize); + memmove(buf + offset + newsize, + buf + offset + oldsize, + size - offset - newsize); memcpy(buf + offset, newlines, newsize); offset = 0; @@ -1506,28 +1741,6 @@ static int apply_one_fragment(struct buffer_desc *desc, struct fragment *frag, return offset; } -static char *inflate_it(const void *data, unsigned long size, - unsigned long inflated_size) -{ - z_stream stream; - void *out; - int st; - - memset(&stream, 0, sizeof(stream)); - - stream.next_in = (unsigned char *)data; - stream.avail_in = size; - stream.next_out = out = xmalloc(inflated_size); - stream.avail_out = inflated_size; - inflateInit(&stream); - st = inflate(&stream, Z_FINISH); - if ((st != Z_STREAM_END) || stream.total_out != inflated_size) { - free(out); - return NULL; - } - return out; -} - static int apply_binary_fragment(struct buffer_desc *desc, struct patch *patch) { unsigned long dst_size; @@ -1535,30 +1748,29 @@ static int apply_binary_fragment(struct buffer_desc *desc, struct patch *patch) void *data; void *result; - /* Binary patch is irreversible */ - if (patch->is_reverse) - return error("cannot reverse-apply a binary patch to '%s'", - patch->new_name - ? patch->new_name : patch->old_name); - - data = inflate_it(fragment->patch, fragment->size, - patch->deflate_origlen); - if (!data) - return error("corrupt patch data"); - switch (patch->is_binary) { + /* Binary patch is irreversible without the optional second hunk */ + if (apply_in_reverse) { + if (!fragment->next) + return error("cannot reverse-apply a binary patch " + "without the reverse hunk to '%s'", + patch->new_name + ? patch->new_name : patch->old_name); + fragment = fragment->next; + } + data = (void*) fragment->patch; + switch (fragment->binary_patch_method) { case BINARY_DELTA_DEFLATED: result = patch_delta(desc->buffer, desc->size, data, - patch->deflate_origlen, + fragment->size, &dst_size); free(desc->buffer); desc->buffer = result; - free(data); break; case BINARY_LITERAL_DEFLATED: free(desc->buffer); desc->buffer = data; - dst_size = patch->deflate_origlen; + dst_size = fragment->size; break; } if (!desc->buffer) @@ -1571,13 +1783,6 @@ static int apply_binary(struct buffer_desc *desc, struct patch *patch) { const char *name = patch->old_name ? patch->old_name : patch->new_name; unsigned char sha1[20]; - unsigned char hdr[50]; - int hdrlen; - - if (!allow_binary_replacement) - return error("cannot apply binary patch to '%s' " - "without --allow-binary-replacement", - name); /* For safety, we require patch index line to contain * full 40-byte textual SHA1 for old and new, at least for now. @@ -1593,8 +1798,7 @@ static int apply_binary(struct buffer_desc *desc, struct patch *patch) /* See if the old one matches what the patch * applies to. */ - write_sha1_file_prepare(desc->buffer, desc->size, - blob_type, sha1, hdr, &hdrlen); + hash_sha1_file(desc->buffer, desc->size, blob_type, sha1); if (strcmp(sha1_to_hex(sha1), patch->old_sha1_prefix)) return error("the patch applies to '%s' (%s), " "which does not match the " @@ -1609,7 +1813,7 @@ static int apply_binary(struct buffer_desc *desc, struct patch *patch) } get_sha1_hex(patch->new_sha1_prefix, sha1); - if (!memcmp(sha1, null_sha1, 20)) { + if (is_null_sha1(sha1)) { free(desc->buffer); desc->alloc = desc->size = 0; desc->buffer = NULL; @@ -1639,10 +1843,9 @@ static int apply_binary(struct buffer_desc *desc, struct patch *patch) name); /* verify that the result matches */ - write_sha1_file_prepare(desc->buffer, desc->size, blob_type, - sha1, hdr, &hdrlen); + hash_sha1_file(desc->buffer, desc->size, blob_type, sha1); if (strcmp(sha1_to_hex(sha1), patch->new_sha1_prefix)) - return error("binary patch to '%s' creates incorrect result", name); + return error("binary patch to '%s' creates incorrect result (expecting %s, got %s)", name, patch->new_sha1_prefix, sha1_to_hex(sha1)); } return 0; @@ -1657,10 +1860,12 @@ static int apply_fragments(struct buffer_desc *desc, struct patch *patch) return apply_binary(desc, patch); while (frag) { - if (apply_one_fragment(desc, frag, patch->is_reverse, - patch->inaccurate_eof) < 0) - return error("patch failed: %s:%ld", - name, frag->oldpos); + if (apply_one_fragment(desc, frag, patch->inaccurate_eof)) { + error("patch failed: %s:%ld", name, frag->oldpos); + if (!apply_with_reject) + return -1; + frag->rejected = 1; + } frag = frag->next; } return 0; @@ -1696,8 +1901,9 @@ static int apply_data(struct patch *patch, struct stat *st, struct cache_entry * desc.size = size; desc.alloc = alloc; desc.buffer = buf; + if (apply_fragments(&desc, patch) < 0) - return -1; + return -1; /* note with --reject this succeeds. */ /* NUL terminate the result */ if (desc.alloc <= desc.size) @@ -1707,7 +1913,7 @@ static int apply_data(struct patch *patch, struct stat *st, struct cache_entry * patch->result = desc.buffer; patch->resultsize = desc.size; - if (patch->is_delete && patch->resultsize) + if (0 < patch->is_delete && patch->resultsize) return error("removal patch leaves file contents"); return 0; @@ -1722,6 +1928,7 @@ static int check_patch(struct patch *patch, struct patch *prev_patch) struct cache_entry *ce = NULL; int ok_if_exists; + patch->rejected = 1; /* we will drop this after we succeed */ if (old_name) { int changed = 0; int stat_ret = 0; @@ -1778,7 +1985,7 @@ static int check_patch(struct patch *patch, struct patch *prev_patch) old_name, st_mode, patch->old_mode); } - if (new_name && prev_patch && prev_patch->is_delete && + if (new_name && prev_patch && 0 < prev_patch->is_delete && !strcmp(prev_patch->old_name, new_name)) /* A type-change diff is always split into a patch to * delete old, immediately followed by a patch to @@ -1791,7 +1998,8 @@ static int check_patch(struct patch *patch, struct patch *prev_patch) else ok_if_exists = 0; - if (new_name && (patch->is_new | patch->is_rename | patch->is_copy)) { + if (new_name && + ((0 < patch->is_new) | (0 < patch->is_rename) | patch->is_copy)) { if (check_index && cache_name_pos(new_name, strlen(new_name)) >= 0 && !ok_if_exists) @@ -1808,7 +2016,7 @@ static int check_patch(struct patch *patch, struct patch *prev_patch) return error("%s: %s", new_name, strerror(errno)); } if (!patch->new_mode) { - if (patch->is_new) + if (0 < patch->is_new) patch->new_mode = S_IFREG | 0644; else patch->new_mode = patch->old_mode; @@ -1827,24 +2035,23 @@ static int check_patch(struct patch *patch, struct patch *prev_patch) if (apply_data(patch, &st, ce) < 0) return error("%s: patch does not apply", name); + patch->rejected = 0; return 0; } static int check_patch_list(struct patch *patch) { struct patch *prev_patch = NULL; - int error = 0; + int err = 0; for (prev_patch = NULL; patch ; patch = patch->next) { - error |= check_patch(patch, prev_patch); + if (apply_verbosely) + say_patch_name(stderr, + "Checking patch ", patch, "...\n"); + err |= check_patch(patch, prev_patch); prev_patch = patch; } - return error; -} - -static inline int is_null_sha1(const unsigned char *sha1) -{ - return !memcmp(sha1, null_sha1, 20); + return err; } static void show_index_list(struct patch *list) @@ -1860,7 +2067,7 @@ static void show_index_list(struct patch *list) const char *name; name = patch->old_name ? patch->old_name : patch->new_name; - if (patch->is_new) + if (0 < patch->is_new) sha1_ptr = null_sha1; else if (get_sha1(patch->old_sha1_prefix, sha1)) die("sha1 information is lacking or useless (%s).", @@ -1901,7 +2108,7 @@ static void numstat_patch_list(struct patch *patch) quote_c_style(name, NULL, stdout, 0); else fputs(name, stdout); - putchar('\n'); + putchar(line_termination); } } @@ -2150,23 +2357,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 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 void write_out_results(struct patch *list, int skipped_patch) +static int write_out_results(struct patch *list, int skipped_patch) { int phase; + int errs = 0; + struct patch *l; if (!list && !skipped_patch) - die("No changes"); + return error("No changes"); for (phase = 0; phase < 2; phase++) { - struct patch *l = list; + l = list; while (l) { - write_out_one_result(l, phase); + if (l->rejected) + errs = 1; + else { + write_out_one_result(l, phase); + if (phase == 1 && write_out_one_reject(l)) + errs = 1; + } l = l->next; } } + return errs; } static struct lock_file lock_file; @@ -2194,8 +2477,7 @@ static int use_patch(struct patch *p) return 1; } -static int apply_patch(int fd, const char *filename, - int reverse, int inaccurate_eof) +static int apply_patch(int fd, const char *filename, int inaccurate_eof) { unsigned long offset, size; char *buffer = read_patch_file(fd, &size); @@ -2215,7 +2497,7 @@ static int apply_patch(int fd, const char *filename, nr = parse_chunk(buffer + offset, size, patch); if (nr < 0) break; - if (reverse) + if (apply_in_reverse) reverse_patches(patch); if (use_patch(patch)) { patch_stats(patch); @@ -2242,11 +2524,13 @@ static int apply_patch(int fd, const char *filename, die("unable to read index file"); } - if ((check || apply) && check_patch_list(list) < 0) + if ((check || apply) && + check_patch_list(list) < 0 && + !apply_with_reject) exit(1); - if (apply) - write_out_results(list, skipped_patch); + if (apply && write_out_results(list, skipped_patch)) + exit(1); if (show_index_info) show_index_list(list); @@ -2267,7 +2551,7 @@ static int apply_patch(int fd, const char *filename, static int git_apply_config(const char *var, const char *value) { if (!strcmp(var, "apply.whitespace")) { - apply_default_whitespace = strdup(value); + apply_default_whitespace = xstrdup(value); return 0; } return git_default_config(var, value); @@ -2278,8 +2562,8 @@ int cmd_apply(int argc, const char **argv, const char *prefix) { int i; int read_stdin = 1; - int reverse = 0; int inaccurate_eof = 0; + int errs = 0; const char *whitespace_option = NULL; @@ -2289,7 +2573,7 @@ int cmd_apply(int argc, const char **argv, const char *prefix) int fd; if (!strcmp(arg, "-")) { - apply_patch(0, "<stdin>", reverse, inaccurate_eof); + errs |= apply_patch(0, "<stdin>", inaccurate_eof); read_stdin = 0; continue; } @@ -2315,8 +2599,7 @@ int cmd_apply(int argc, const char **argv, const char *prefix) } if (!strcmp(arg, "--allow-binary-replacement") || !strcmp(arg, "--binary")) { - allow_binary_replacement = 1; - continue; + continue; /* now no-op */ } if (!strcmp(arg, "--numstat")) { apply = 0; @@ -2367,7 +2650,19 @@ int cmd_apply(int argc, const char **argv, const char *prefix) continue; } if (!strcmp(arg, "-R") || !strcmp(arg, "--reverse")) { - reverse = 1; + apply_in_reverse = 1; + continue; + } + if (!strcmp(arg, "--unidiff-zero")) { + unidiff_zero = 1; + continue; + } + if (!strcmp(arg, "--reject")) { + apply = apply_with_reject = apply_verbosely = 1; + continue; + } + if (!strcmp(arg, "--verbose")) { + apply_verbosely = 1; continue; } if (!strcmp(arg, "--inaccurate-eof")) { @@ -2390,18 +2685,19 @@ int cmd_apply(int argc, const char **argv, const char *prefix) usage(apply_usage); read_stdin = 0; set_default_whitespace_mode(whitespace_option); - apply_patch(fd, arg, reverse, inaccurate_eof); + errs |= apply_patch(fd, arg, inaccurate_eof); close(fd); } set_default_whitespace_mode(whitespace_option); if (read_stdin) - apply_patch(0, "<stdin>", reverse, inaccurate_eof); + errs |= apply_patch(0, "<stdin>", inaccurate_eof); if (whitespace_error) { if (squelch_whitespace_errors && squelch_whitespace_errors < whitespace_error) { int squelched = whitespace_error - squelch_whitespace_errors; - fprintf(stderr, "warning: squelched %d whitespace error%s\n", + fprintf(stderr, "warning: squelched %d " + "whitespace error%s\n", squelched, squelched == 1 ? "" : "s"); } @@ -2429,5 +2725,5 @@ int cmd_apply(int argc, const char **argv, const char *prefix) die("Unable to write new index file"); } - return 0; + return !!errs; } diff --git a/builtin-archive.c b/builtin-archive.c new file mode 100644 index 0000000000..9177379122 --- /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); + 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 814fb0743f..6c16bfa1ae 100644 --- a/builtin-cat-file.c +++ b/builtin-cat-file.c @@ -9,24 +9,7 @@ #include "tree.h" #include "builtin.h" -static void flush_buffer(const char *buf, unsigned long size) -{ - while (size > 0) { - long ret = xwrite(1, buf, size); - if (ret < 0) { - /* Ignore epipe */ - if (errno == EPIPE) - break; - die("git-cat-file: %s", strerror(errno)); - } else if (!ret) { - die("git-cat-file: disk full?"); - } - size -= ret; - buf += ret; - } -} - -static int pprint_tag(const unsigned char *sha1, const char *buf, unsigned long size) +static void pprint_tag(const unsigned char *sha1, const char *buf, unsigned long size) { /* the parser in tag.c is useless here. */ const char *endp = buf + size; @@ -42,7 +25,7 @@ static int pprint_tag(const unsigned char *sha1, const char *buf, unsigned long /* Found the tagger line. Copy out the contents * of the buffer so far. */ - flush_buffer(buf, cp - buf); + write_or_die(1, buf, cp - buf); /* * Do something intelligent, like pretty-printing @@ -61,18 +44,18 @@ static int pprint_tag(const unsigned char *sha1, const char *buf, unsigned long sp++; if (sp == cp) { /* give up */ - flush_buffer(tagger, + write_or_die(1, tagger, cp - tagger); break; } while (sp < cp && !('0' <= *sp && *sp <= '9')) sp++; - flush_buffer(tagger, sp - tagger); + write_or_die(1, tagger, sp - tagger); date = strtoul(sp, &ep, 10); tz = strtol(ep, NULL, 10); - sp = show_date(date, tz); - flush_buffer(sp, strlen(sp)); + sp = show_date(date, tz, 0); + write_or_die(1, sp, strlen(sp)); xwrite(1, "\n", 1); break; } @@ -90,8 +73,7 @@ static int pprint_tag(const unsigned char *sha1, const char *buf, unsigned long * remainder as is. */ if (cp < endp) - flush_buffer(cp, endp - cp); - return 0; + write_or_die(1, cp, endp - cp); } int cmd_cat_file(int argc, const char **argv, const char *prefix) @@ -145,8 +127,10 @@ int cmd_cat_file(int argc, const char **argv, const char *prefix) buf = read_sha1_file(sha1, type, &size); if (!buf) die("Cannot read object %s", argv[2]); - if (!strcmp(type, tag_type)) - return pprint_tag(sha1, buf, size); + if (!strcmp(type, tag_type)) { + pprint_tag(sha1, buf, size); + return 0; + } /* otherwise just spit out the data */ break; @@ -161,6 +145,6 @@ int cmd_cat_file(int argc, const char **argv, const char *prefix) if (!buf) die("git-cat-file %s: bad file", argv[2]); - flush_buffer(buf, size); + write_or_die(1, buf, size); return 0; } diff --git a/checkout-index.c b/builtin-checkout-index.c index dfb1c44415..b097c888a0 100644 --- a/checkout-index.c +++ b/builtin-checkout-index.c @@ -42,16 +42,14 @@ #include "cache-tree.h" #define CHECKOUT_ALL 4 -static const char *prefix; -static int prefix_length; static int line_termination = '\n'; static int checkout_stage; /* default to checkout stage0 */ static int to_tempfile; -static char topath[4][MAXPATHLEN+1]; +static char topath[4][PATH_MAX + 1]; static struct checkout state; -static void write_tempfile_record (const char *name) +static void write_tempfile_record(const char *name, int prefix_length) { int i; @@ -77,7 +75,7 @@ static void write_tempfile_record (const char *name) } } -static int checkout_file(const char *name) +static int checkout_file(const char *name, int prefix_length) { int namelen = strlen(name); int pos = cache_name_pos(name, namelen); @@ -106,7 +104,7 @@ static int checkout_file(const char *name) if (did_checkout) { if (to_tempfile) - write_tempfile_record(name); + write_tempfile_record(name, prefix_length); return errs > 0 ? -1 : 0; } @@ -124,7 +122,7 @@ static int checkout_file(const char *name) return -1; } -static int checkout_all(void) +static void checkout_all(const char *prefix, int prefix_length) { int i, errs = 0; struct cache_entry* last_ce = NULL; @@ -141,7 +139,7 @@ static int checkout_all(void) if (last_ce && to_tempfile) { if (ce_namelen(last_ce) != ce_namelen(ce) || memcmp(last_ce->name, ce->name, ce_namelen(ce))) - write_tempfile_record(last_ce->name); + write_tempfile_record(last_ce->name, prefix_length); } if (checkout_entry(ce, &state, to_tempfile ? topath[ce_stage(ce)] : NULL) < 0) @@ -149,13 +147,12 @@ static int checkout_all(void) last_ce = ce; } if (last_ce && to_tempfile) - write_tempfile_record(last_ce->name); + write_tempfile_record(last_ce->name, prefix_length); if (errs) /* we have already done our error reporting. * exit with the same code as die(). */ exit(128); - return 0; } static const char checkout_cache_usage[] = @@ -163,16 +160,16 @@ static const char checkout_cache_usage[] = static struct lock_file lock_file; -int main(int argc, char **argv) +int cmd_checkout_index(int argc, const char **argv, const char *prefix) { int i; int newfd = -1; int all = 0; int read_from_stdin = 0; + int prefix_length; - state.base_dir = ""; - prefix = setup_git_directory(); git_config(git_default_config); + state.base_dir = ""; prefix_length = prefix ? strlen(prefix) : 0; if (read_cache() < 0) { @@ -270,7 +267,7 @@ int main(int argc, char **argv) if (read_from_stdin) die("git-checkout-index: don't mix '--stdin' and explicit filenames"); p = prefix_path(prefix, prefix_length, arg); - checkout_file(p); + checkout_file(p, prefix_length); if (p < arg || p > arg + strlen(arg)) free((char*)p); } @@ -292,7 +289,7 @@ int main(int argc, char **argv) else path_name = buf.buf; p = prefix_path(prefix, prefix_length, path_name); - checkout_file(p); + checkout_file(p, prefix_length); if (p < path_name || p > path_name + strlen(path_name)) free((char *)p); if (path_name != buf.buf) @@ -301,7 +298,7 @@ int main(int argc, char **argv) } if (all) - checkout_all(); + checkout_all(prefix, prefix_length); if (0 <= newfd && (write_cache(newfd, active_cache, active_nr) || diff --git a/builtin-commit-tree.c b/builtin-commit-tree.c index 9c98796671..e2e690a1ae 100644 --- a/builtin-commit-tree.c +++ b/builtin-commit-tree.c @@ -69,7 +69,7 @@ static int new_parent(int idx) int i; unsigned char *sha1 = parent_sha1[idx]; for (i = 0; i < idx; i++) { - if (!memcmp(parent_sha1[i], sha1, 20)) { + if (!hashcmp(parent_sha1[i], sha1)) { error("duplicate parent %s ignored", sha1_to_hex(sha1)); return 0; } diff --git a/builtin-count.c b/builtin-count-objects.c index 1d3729aa99..73c5982423 100644 --- a/builtin-count.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-files.c b/builtin-diff-files.c index ac13db70ff..5d4a5c5828 100644 --- a/builtin-diff-files.c +++ b/builtin-diff-files.c @@ -47,12 +47,5 @@ int cmd_diff_files(int argc, const char **argv, const char *prefix) if (rev.pending.nr || rev.min_age != -1 || rev.max_age != -1) usage(diff_files_usage); - /* - * Backward compatibility wart - "diff-files -s" used to - * defeat the common diff option "-s" which asked for - * DIFF_FORMAT_NO_OUTPUT. - */ - if (rev.diffopt.output_format == DIFF_FORMAT_NO_OUTPUT) - rev.diffopt.output_format = DIFF_FORMAT_RAW; return run_diff_files(&rev, silent); } diff --git a/builtin-diff-stages.c b/builtin-diff-stages.c index 5960e08997..70bb89808d 100644 --- a/builtin-diff-stages.c +++ b/builtin-diff-stages.c @@ -46,7 +46,7 @@ static void diff_stages(int stage1, int stage2, const char **pathspec) else if (!two) diff_addremove(&diff_options, '-', ntohl(one->ce_mode), one->sha1, name, NULL); - else if (memcmp(one->sha1, two->sha1, 20) || + else if (hashcmp(one->sha1, two->sha1) || (one->ce_mode != two->ce_mode) || diff_options.find_copies_harder) diff_change(&diff_options, diff --git a/builtin-diff.c b/builtin-diff.c index a090e298a5..a6590205e8 100644 --- a/builtin-diff.c +++ b/builtin-diff.c @@ -56,13 +56,6 @@ static int builtin_diff_files(struct rev_info *revs, if (revs->max_count < 0 && (revs->diffopt.output_format & DIFF_FORMAT_PATCH)) revs->combine_merges = revs->dense_combined_merges = 1; - /* - * Backward compatibility wart - "diff-files -s" used to - * defeat the common diff option "-s" which asked for - * DIFF_FORMAT_NO_OUTPUT. - */ - if (revs->diffopt.output_format == DIFF_FORMAT_NO_OUTPUT) - revs->diffopt.output_format = DIFF_FORMAT_RAW; return run_diff_files(revs, silent); } @@ -75,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) { @@ -200,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; @@ -298,7 +290,7 @@ int cmd_diff(int argc, const char **argv, const char *prefix) if (obj->type == OBJ_BLOB) { if (2 <= blobs) die("more than two blobs given: '%s'", name); - memcpy(blob[blobs].sha1, obj->sha1, 20); + hashcpy(blob[blobs].sha1, obj->sha1); blob[blobs].name = name; blobs++; continue; diff --git a/builtin-fmt-merge-msg.c b/builtin-fmt-merge-msg.c index 485ede7cad..c407c033e7 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 93b7e07b30..4205e5d38d 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,569 +83,40 @@ 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; - 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; - int regflags; - unsigned pre_context; - unsigned post_context; -}; - -static void add_pattern(struct grep_opt *opt, const char *pat, - const char *origin, int no, enum grep_pat_token t) -{ - struct grep_pat *p = xcalloc(1, sizeof(*p)); - p->pattern = pat; - p->origin = origin; - p->no = no; - p->token = t; - *opt->pattern_tail = p; - opt->pattern_tail = &p->next; - p->next = NULL; -} - -static void compile_regexp(struct grep_pat *p, struct grep_opt *opt) -{ - int err = regcomp(&p->regexp, p->pattern, opt->regflags); - if (err) { - char errbuf[1024]; - char where[1024]; - if (p->no) - sprintf(where, "In '%s' at %d, ", - p->origin, p->no); - else if (p->origin) - sprintf(where, "%s, ", p->origin); - else - where[0] = 0; - regerror(err, &p->regexp, errbuf, 1024); - regfree(&p->regexp); - die("%s'%s': %s", where, p->pattern, errbuf); - } -} - -#if DEBUG -static inline void indent(int in) -{ - int i; - for (i = 0; i < in; i++) putchar(' '); -} - -static void dump_pattern_exp(struct grep_expr *x, int in) -{ - switch (x->node) { - case GREP_NODE_ATOM: - indent(in); - puts(x->u.atom->pattern); - break; - case GREP_NODE_NOT: - indent(in); - puts("--not"); - dump_pattern_exp(x->u.unary, in+1); - break; - case GREP_NODE_AND: - dump_pattern_exp(x->u.binary.left, in+1); - indent(in); - puts("--and"); - dump_pattern_exp(x->u.binary.right, in+1); - break; - case GREP_NODE_OR: - dump_pattern_exp(x->u.binary.left, in+1); - indent(in); - puts("--or"); - dump_pattern_exp(x->u.binary.right, in+1); - break; - } -} - -static void looking_at(const char *msg, struct grep_pat **list) -{ - struct grep_pat *p = *list; - fprintf(stderr, "%s: looking at ", msg); - if (!p) - fprintf(stderr, "empty\n"); - else - fprintf(stderr, "<%s>\n", p->pattern); -} -#else -#define looking_at(a,b) do {} while(0) -#endif - -static struct grep_expr *compile_pattern_expr(struct grep_pat **); -static struct grep_expr *compile_pattern_atom(struct grep_pat **list) -{ - struct grep_pat *p; - struct grep_expr *x; - - looking_at("atom", list); - - p = *list; - switch (p->token) { - case GREP_PATTERN: /* atom */ - x = xcalloc(1, sizeof (struct grep_expr)); - x->node = GREP_NODE_ATOM; - x->u.atom = p; - *list = p->next; - return x; - case GREP_OPEN_PAREN: - *list = p->next; - x = compile_pattern_expr(list); - if (!x) - return NULL; - if (!*list || (*list)->token != GREP_CLOSE_PAREN) - die("unmatched parenthesis"); - *list = (*list)->next; - return x; - default: - return NULL; - } -} - -static struct grep_expr *compile_pattern_not(struct grep_pat **list) -{ - struct grep_pat *p; - struct grep_expr *x; - - looking_at("not", list); - - p = *list; - switch (p->token) { - case GREP_NOT: - if (!p->next) - die("--not not followed by pattern expression"); - *list = p->next; - x = xcalloc(1, sizeof (struct grep_expr)); - x->node = GREP_NODE_NOT; - x->u.unary = compile_pattern_not(list); - if (!x->u.unary) - die("--not followed by non pattern expression"); - return x; - default: - return compile_pattern_atom(list); - } -} - -static struct grep_expr *compile_pattern_and(struct grep_pat **list) -{ - struct grep_pat *p; - struct grep_expr *x, *y, *z; - - looking_at("and", list); - - x = compile_pattern_not(list); - p = *list; - if (p && p->token == GREP_AND) { - if (!p->next) - die("--and not followed by pattern expression"); - *list = p->next; - y = compile_pattern_and(list); - if (!y) - die("--and not followed by pattern expression"); - z = xcalloc(1, sizeof (struct grep_expr)); - z->node = GREP_NODE_AND; - z->u.binary.left = x; - z->u.binary.right = y; - return z; - } - return x; -} - -static struct grep_expr *compile_pattern_or(struct grep_pat **list) -{ - struct grep_pat *p; - struct grep_expr *x, *y, *z; - - looking_at("or", list); - - x = compile_pattern_and(list); - p = *list; - if (x && p && p->token != GREP_CLOSE_PAREN) { - y = compile_pattern_or(list); - if (!y) - die("not a pattern expression %s", p->pattern); - z = xcalloc(1, sizeof (struct grep_expr)); - z->node = GREP_NODE_OR; - z->u.binary.left = x; - z->u.binary.right = y; - return z; - } - return x; -} - -static struct grep_expr *compile_pattern_expr(struct grep_pat **list) -{ - looking_at("expr", list); - - return compile_pattern_or(list); -} - -static void compile_patterns(struct grep_opt *opt) -{ - struct grep_pat *p; - - /* First compile regexps */ - for (p = opt->pattern_list; p; p = p->next) { - if (p->token == GREP_PATTERN) - compile_regexp(p, opt); - else - opt->extended = 1; - } - - if (!opt->extended) - return; - - /* Then bundle them up in an expression. - * A classic recursive descent parser would do. - */ - p = opt->pattern_list; - opt->pattern_expression = compile_pattern_expr(&p); -#if DEBUG - dump_pattern_exp(opt->pattern_expression, 0); -#endif - if (p) - die("incomplete pattern expression: %s", p->pattern); -} - -static char *end_of_line(char *cp, unsigned long *left) -{ - unsigned long l = *left; - while (l && *cp != '\n') { - l--; - cp++; - } - *left = l; - return cp; -} - -static int word_char(char ch) -{ - return isalnum(ch) || ch == '_'; -} - -static void show_line(struct grep_opt *opt, const char *bol, const char *eol, - const char *name, unsigned lno, char sign) -{ - printf("%s%c", name, sign); - if (opt->linenum) - printf("%d%c", lno, sign); - printf("%.*s\n", (int)(eol-bol), bol); -} - -/* - * NEEDSWORK: share code with diff.c - */ -#define FIRST_FEW_BYTES 8000 -static int buffer_is_binary(const char *ptr, unsigned long size) -{ - if (FIRST_FEW_BYTES < size) - size = FIRST_FEW_BYTES; - if (memchr(ptr, 0, size)) - return 1; - return 0; -} - -static int fixmatch(const char *pattern, char *line, regmatch_t *match) -{ - char *hit = strstr(line, pattern); - if (!hit) { - match->rm_so = match->rm_eo = -1; - return REG_NOMATCH; - } - else { - match->rm_so = hit - line; - match->rm_eo = match->rm_so + strlen(pattern); - return 0; - } -} - -static int match_one_pattern(struct grep_opt *opt, struct grep_pat *p, char *bol, char *eol) -{ - int hit = 0; - int at_true_bol = 1; - regmatch_t pmatch[10]; - - again: - if (!opt->fixed) { - regex_t *exp = &p->regexp; - hit = !regexec(exp, bol, ARRAY_SIZE(pmatch), - pmatch, 0); - } - else { - hit = !fixmatch(p->pattern, bol, pmatch); - } - - if (hit && opt->word_regexp) { - if ((pmatch[0].rm_so < 0) || - (eol - bol) <= pmatch[0].rm_so || - (pmatch[0].rm_eo < 0) || - (eol - bol) < pmatch[0].rm_eo) - die("regexp returned nonsense"); - - /* Match beginning must be either beginning of the - * line, or at word boundary (i.e. the last char must - * not be a word char). Similarly, match end must be - * either end of the line, or at word boundary - * (i.e. the next char must not be a word char). - */ - if ( ((pmatch[0].rm_so == 0 && at_true_bol) || - !word_char(bol[pmatch[0].rm_so-1])) && - ((pmatch[0].rm_eo == (eol-bol)) || - !word_char(bol[pmatch[0].rm_eo])) ) - ; - else - hit = 0; - - if (!hit && pmatch[0].rm_so + bol + 1 < eol) { - /* There could be more than one match on the - * line, and the first match might not be - * strict word match. But later ones could be! - */ - bol = pmatch[0].rm_so + bol + 1; - at_true_bol = 0; - goto again; - } - } - return hit; -} - -static int match_expr_eval(struct grep_opt *opt, - struct grep_expr *x, - char *bol, char *eol) -{ - switch (x->node) { - case GREP_NODE_ATOM: - return match_one_pattern(opt, x->u.atom, bol, eol); - break; - case GREP_NODE_NOT: - return !match_expr_eval(opt, x->u.unary, bol, eol); - case GREP_NODE_AND: - return (match_expr_eval(opt, x->u.binary.left, bol, eol) && - match_expr_eval(opt, x->u.binary.right, bol, eol)); - case GREP_NODE_OR: - return (match_expr_eval(opt, x->u.binary.left, bol, eol) || - match_expr_eval(opt, x->u.binary.right, bol, eol)); - } - die("Unexpected node type (internal error) %d\n", x->node); -} - -static int match_expr(struct grep_opt *opt, char *bol, char *eol) -{ - struct grep_expr *x = opt->pattern_expression; - return match_expr_eval(opt, x, bol, eol); -} - -static int match_line(struct grep_opt *opt, char *bol, char *eol) -{ - struct grep_pat *p; - if (opt->extended) - return match_expr(opt, bol, eol); - for (p = opt->pattern_list; p; p = p->next) { - if (match_one_pattern(opt, p, bol, eol)) - return 1; - } - return 0; -} - -static int grep_buffer(struct grep_opt *opt, const char *name, - char *buf, unsigned long size) -{ - char *bol = buf; - unsigned long left = size; - unsigned lno = 1; - struct pre_context_line { - char *bol; - char *eol; - } *prev = NULL, *pcl; - unsigned last_hit = 0; - unsigned last_shown = 0; - int binary_match_only = 0; - const char *hunk_mark = ""; - unsigned count = 0; - - if (buffer_is_binary(buf, size)) { - switch (opt->binary) { - case GREP_BINARY_DEFAULT: - binary_match_only = 1; - break; - case GREP_BINARY_NOMATCH: - return 0; /* Assume unmatch */ - break; - default: - break; - } - } - - if (opt->pre_context) - prev = xcalloc(opt->pre_context, sizeof(*prev)); - if (opt->pre_context || opt->post_context) - hunk_mark = "--\n"; - - while (left) { - char *eol, ch; - int hit = 0; - - eol = end_of_line(bol, &left); - ch = *eol; - *eol = 0; - - hit = match_line(opt, bol, eol); - - /* "grep -v -e foo -e bla" should list lines - * that do not have either, so inversion should - * be done outside. - */ - if (opt->invert) - hit = !hit; - if (opt->unmatch_name_only) { - if (hit) - return 0; - goto next_line; - } - if (hit) { - count++; - if (binary_match_only) { - printf("Binary file %s matches\n", name); - return 1; - } - if (opt->name_only) { - printf("%s\n", name); - return 1; - } - /* Hit at this line. If we haven't shown the - * pre-context lines, we would need to show them. - * When asked to do "count", this still show - * the context which is nonsense, but the user - * deserves to get that ;-). - */ - if (opt->pre_context) { - unsigned from; - if (opt->pre_context < lno) - from = lno - opt->pre_context; - else - from = 1; - if (from <= last_shown) - from = last_shown + 1; - if (last_shown && from != last_shown + 1) - printf(hunk_mark); - while (from < lno) { - pcl = &prev[lno-from-1]; - show_line(opt, pcl->bol, pcl->eol, - name, from, '-'); - from++; - } - last_shown = lno-1; - } - if (last_shown && lno != last_shown + 1) - printf(hunk_mark); - if (!opt->count) - show_line(opt, bol, eol, name, lno, ':'); - last_shown = last_hit = lno; - } - else if (last_hit && - lno <= last_hit + opt->post_context) { - /* If the last hit is within the post context, - * we need to show this line. - */ - if (last_shown && lno != last_shown + 1) - printf(hunk_mark); - show_line(opt, bol, eol, name, lno, '-'); - last_shown = lno; - } - if (opt->pre_context) { - memmove(prev+1, prev, - (opt->pre_context-1) * sizeof(*prev)); - prev->bol = bol; - prev->eol = eol; - } - - next_line: - *eol = ch; - bol = eol + 1; - if (!left) - break; - left--; - lno++; - } - - if (opt->unmatch_name_only) { - /* We did not see any hit, so we want to show this */ - printf("%s\n", name); - return 1; - } - - /* NEEDSWORK: - * The real "grep -c foo *.c" gives many "bar.c:0" lines, - * which feels mostly useless but sometimes useful. Maybe - * make it another option? For now suppress them. - */ - if (opt->count && count) - printf("%s:%u\n", name, count); - return !!last_hit; -} - -static int grep_sha1(struct grep_opt *opt, const unsigned char *sha1, const char *name) +static int grep_sha1(struct grep_opt *opt, const unsigned char *sha1, const char *name, int tree_name_len) { unsigned long size; char *data; char type[20]; + char *to_free = NULL; int hit; + data = read_sha1_file(sha1, type, &size); if (!data) { error("'%s': unable to read %s", name, sha1_to_hex(sha1)); return 0; } + if (opt->relative && opt->prefix_length) { + static char name_buf[PATH_MAX]; + char *cp; + int name_len = strlen(name) - opt->prefix_length + 1; + + if (!tree_name_len) + name += opt->prefix_length; + else { + if (ARRAY_SIZE(name_buf) <= name_len) + cp = to_free = xmalloc(name_len); + else + cp = name_buf; + memcpy(cp, name, tree_name_len); + strcpy(cp + tree_name_len, + name + tree_name_len + opt->prefix_length); + name = cp; + } + } hit = grep_buffer(opt, name, data, size); free(data); + free(to_free); return hit; } @@ -674,6 +146,8 @@ static int grep_file(struct grep_opt *opt, const char *filename) return 0; } close(i); + if (opt->relative && opt->prefix_length) + filename += opt->prefix_length; i = grep_buffer(opt, filename, data, st.st_size); free(data); return i; @@ -720,7 +194,7 @@ static int external_grep(struct grep_opt *opt, const char **paths, int cached) char *argptr = randarg; struct grep_pat *p; - if (opt->extended) + if (opt->extended || (opt->relative && opt->prefix_length)) return -1; len = nr = 0; push_arg("grep"); @@ -728,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) @@ -845,10 +321,11 @@ static int grep_cache(struct grep_opt *opt, const char **paths, int cached) if (!pathspec_matches(paths, ce->name)) continue; if (cached) - hit |= grep_sha1(opt, ce->sha1, ce->name); + hit |= grep_sha1(opt, ce->sha1, ce->name, 0); else hit |= grep_file(opt, ce->name); } + free_grep_patterns(opt); return hit; } @@ -860,11 +337,12 @@ static int grep_tree(struct grep_opt *opt, const char **paths, int hit = 0; struct name_entry entry; char *down; - char *path_buf = xmalloc(PATH_MAX + strlen(tree_name) + 100); + int tn_len = strlen(tree_name); + char *path_buf = xmalloc(PATH_MAX + tn_len + 100); - if (tree_name[0]) { - int offset = sprintf(path_buf, "%s:", tree_name); - down = path_buf + offset; + if (tn_len) { + tn_len = sprintf(path_buf, "%s:", tree_name); + down = path_buf + tn_len; strcat(down, base); } else { @@ -886,7 +364,7 @@ static int grep_tree(struct grep_opt *opt, const char **paths, if (!pathspec_matches(paths, down)) ; else if (S_ISREG(entry.mode)) - hit |= grep_sha1(opt, entry.sha1, path_buf); + hit |= grep_sha1(opt, entry.sha1, path_buf, tn_len); else if (S_ISDIR(entry.mode)) { char type[20]; struct tree_desc sub; @@ -907,7 +385,7 @@ static int grep_object(struct grep_opt *opt, const char **paths, struct object *obj, const char *name) { if (obj->type == OBJ_BLOB) - return grep_sha1(opt, obj->sha1, name); + return grep_sha1(opt, obj->sha1, name, 0); if (obj->type == OBJ_COMMIT || obj->type == OBJ_TREE) { struct tree_desc tree; void *data; @@ -945,6 +423,9 @@ int cmd_grep(int argc, const char **argv, const char *prefix) int i; 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; @@ -1004,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) || @@ -1082,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++; @@ -1091,33 +575,42 @@ 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; } die(emsg_missing_argument, arg); } + if (!strcmp("--full-name", arg)) { + opt.relative = 0; + continue; + } if (!strcmp("--", arg)) { /* later processing wants to have this at argv[1] */ argv--; @@ -1129,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 { @@ -1147,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++) { @@ -1176,8 +668,15 @@ int cmd_grep(int argc, const char **argv, const char *prefix) verify_filename(prefix, argv[j]); } - if (i < argc) + if (i < argc) { paths = get_pathspec(prefix, argv + i); + if (opt.prefix_length && opt.relative) { + /* Make sure we do not get outside of paths */ + for (i = 0; paths[i]; i++) + if (strncmp(prefix, paths[i], opt.prefix_length)) + die("git-grep: cannot generate relative filenames containing '..'"); + } + } else if (prefix) { paths = xcalloc(2, sizeof(const char *)); paths[0] = prefix; @@ -1196,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 5085018e46..c3ed1ce492 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 691cf3aef7..9d1ceae44c 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 11386c432b..ad8c41e731 100644 --- a/builtin-ls-files.c +++ b/builtin-ls-files.c @@ -12,21 +12,22 @@ #include "dir.h" #include "builtin.h" -static int abbrev = 0; -static int show_deleted = 0; -static int show_cached = 0; -static int show_others = 0; -static int show_stage = 0; -static int show_unmerged = 0; -static int show_modified = 0; -static int show_killed = 0; -static int show_valid_bit = 0; +static int abbrev; +static int show_deleted; +static int show_cached; +static int show_others; +static int show_stage; +static int show_unmerged; +static int show_modified; +static int show_killed; +static int show_valid_bit; static int line_terminator = '\n'; -static int prefix_len = 0, prefix_offset = 0; -static const char **pathspec = NULL; -static int error_unmatch = 0; -static char *ps_matched = NULL; +static int prefix_len; +static int prefix_offset; +static const char **pathspec; +static int error_unmatch; +static char *ps_matched; static const char *tag_cached = ""; static const char *tag_unmerged = ""; diff --git a/builtin-ls-tree.c b/builtin-ls-tree.c index 261147fdbe..201defd934 100644 --- a/builtin-ls-tree.c +++ b/builtin-ls-tree.c @@ -14,10 +14,10 @@ static int line_termination = '\n'; #define LS_TREE_ONLY 2 #define LS_SHOW_TREES 4 #define LS_NAME_ONLY 8 -static int abbrev = 0; -static int ls_options = 0; +static int abbrev; +static int ls_options; static const char **pathspec; -static int chomp_prefix = 0; +static int chomp_prefix; static const char *ls_tree_prefix; static const char ls_tree_usage[] = diff --git a/builtin-mailinfo.c b/builtin-mailinfo.c index 24a4fc63b3..b8d7dbc0b7 100644 --- a/builtin-mailinfo.c +++ b/builtin-mailinfo.c @@ -16,8 +16,8 @@ static FILE *cmitmsg, *patchfile, *fin, *fout; -static int keep_subject = 0; -static const char *metainfo_charset = NULL; +static int keep_subject; +static const char *metainfo_charset; static char line[1000]; static char date[1000]; static char name[1000]; @@ -31,7 +31,7 @@ static char charset[256]; static char multipart_boundary[1000]; static int multipart_boundary_len; -static int patch_lines = 0; +static int patch_lines; static char *sanity_check(char *name, char *email) { @@ -451,17 +451,6 @@ static int read_one_header_line(char *line, int sz, FILE *in) return ofs; } -static unsigned hexval(int c) -{ - if (c >= '0' && c <= '9') - return c - '0'; - if (c >= 'a' && c <= 'f') - return c - 'a' + 10; - if (c >= 'A' && c <= 'F') - return c - 'A' + 10; - return ~0; -} - static int decode_q_segment(char *in, char *ot, char *ep, int rfc2047) { int c; diff --git a/builtin-mv.c b/builtin-mv.c index 6b0ab8aa9f..54dd3bfe8a 100644 --- a/builtin-mv.c +++ b/builtin-mv.c @@ -168,13 +168,13 @@ int cmd_mv(int argc, const char **argv, const char *prefix) 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)); } @@ -262,10 +262,10 @@ int cmd_mv(int argc, const char **argv, const char *prefix) } 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/name-rev.c b/builtin-name-rev.c index f92f14e32f..52886b69b0 100644 --- a/name-rev.c +++ b/builtin-name-rev.c @@ -1,4 +1,5 @@ #include <stdlib.h> +#include "builtin.h" #include "cache.h" #include "commit.h" #include "tag.h" @@ -74,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) { @@ -99,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; } @@ -126,12 +127,11 @@ static const char* get_rev_name(struct object *o) return buffer; } -int main(int argc, char **argv) +int cmd_name_rev(int argc, const char **argv, const char *prefix) { struct object_array revs = { 0, 0, NULL }; int as_is = 0, all = 0, transform_stdin = 0; - setup_git_directory(); git_config(git_default_config); if (argc < 2) diff --git a/pack-objects.c b/builtin-pack-objects.c index 861c7f08ff..96c069a81d 100644 --- a/pack-objects.c +++ b/builtin-pack-objects.c @@ -1,3 +1,4 @@ +#include "builtin.h" #include "cache.h" #include "object.h" #include "blob.h" @@ -8,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]; @@ -52,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, @@ -71,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 @@ -89,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) { @@ -241,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) { @@ -269,7 +351,26 @@ static unsigned long write_object(struct sha1file *f, * and we do not need to deltify it. */ - if (! to_reuse) { + if (!entry->in_pack && !entry->delta) { + unsigned char *map; + unsigned long mapsize; + 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++; + reused++; + return mapsize; + } + if (map) + munmap(map, mapsize); + } + + if (!to_reuse) { buf = read_sha1_file(entry->sha1, type, &size); if (!buf) die("unable to read %s", sha1_to_hex(entry->sha1)); @@ -302,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 */ @@ -424,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; @@ -493,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; } } } @@ -517,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) @@ -590,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; } @@ -632,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; @@ -738,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); @@ -767,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; } @@ -782,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; } @@ -914,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) @@ -1146,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; @@ -1226,90 +1333,13 @@ static int git_pack_config(const char *k, const char *v) return git_default_config(k, v); } -int main(int argc, char **argv) +static void read_object_list_from_stdin(void) { - SHA_CTX ctx; char line[40 + 1 + PATH_MAX + 2]; - int depth = 10, pack_to_stdout = 0; - struct object_entry **list; - int num_preferred_base = 0; - int i; - - setup_git_directory(); - git_config(git_pack_config); - - progress = isatty(2); - 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 (base_name) - usage(pack_usage); - base_name = arg; - } - - if (pack_to_stdout != !base_name) - usage(pack_usage); - - prepare_packed_git(); - - if (progress) { - fprintf(stderr, "Generating pack...\n"); - setup_progress_signal(); - } + unsigned char sha1[20]; + unsigned hash; for (;;) { - unsigned char sha1[20]; - unsigned hash; - if (!fgets(line, sizeof(line), stdin)) { if (feof(stdin)) break; @@ -1320,21 +1350,202 @@ int main(int argc, char **argv) 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); + 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; + int depth = 10; + struct object_entry **list; + 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); + + progress = isatty(2); + for (i = 1; i < argc; i++) { + const char *arg = argv[i]; + + 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 (!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) { + fprintf(stderr, "Generating pack...\n"); + setup_progress_signal(); + } + + 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(); @@ -1351,7 +1562,7 @@ int main(int argc, char **argv) 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 d3dd94d9ef..960db49859 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 89ec7f1426..6228c7907b 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 273b27dcea..f5150ed82d 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) { @@ -32,10 +32,8 @@ static int expand_one_ref(const char *ref, const unsigned char *sha1) /* Ignore the "refs/" at the beginning of the refname */ ref += 5; - if (strncmp(ref, "tags/", 5)) - return 0; - - add_refspec(strdup(ref)); + if (!strncmp(ref, "tags/", 5)) + add_refspec(xstrdup(ref)); return 0; } @@ -102,12 +100,12 @@ static int get_remotes_uri(const char *repo, const char *uri[MAX_URI]) if (!is_refspec) { if (n < MAX_URI) - uri[n++] = strdup(s); + uri[n++] = xstrdup(s); else error("more than %d URL's specified, ignoring the rest", MAX_URI); } else if (is_refspec && !has_explicit_refspec) - add_refspec(strdup(s)); + add_refspec(xstrdup(s)); } fclose(f); if (!n) @@ -127,13 +125,13 @@ static int get_remote_config(const char* key, const char* value) !strncmp(key + 7, config_repo, config_repo_len)) { if (!strcmp(key + 7 + config_repo_len, ".url")) { if (config_current_uri < MAX_URI) - config_uri[config_current_uri++] = strdup(value); + config_uri[config_current_uri++] = xstrdup(value); else error("more than %d URL's specified, ignoring the rest", MAX_URI); } else if (config_get_refspecs && !strcmp(key + 7 + config_repo_len, ".push")) - add_refspec(strdup(value)); + add_refspec(xstrdup(value)); } return 0; } @@ -234,7 +232,7 @@ static int do_push(const char *repo) common_argc = argc; for (i = 0; i < n; i++) { - int error; + int err; int dest_argc = common_argc; int dest_refspec_nr = refspec_nr; const char **dest_refspec = refspec; @@ -250,10 +248,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: @@ -264,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 71a7026df4..c1867d2a00 100644 --- a/builtin-read-tree.c +++ b/builtin-read-tree.c @@ -3,418 +3,16 @@ * * Copyright (C) Linus Torvalds, 2005 */ -#define DBRT_DEBUG 1 #include "cache.h" - #include "object.h" #include "tree.h" #include "tree-walk.h" #include "cache-tree.h" -#include <sys/time.h> -#include <signal.h> +#include "unpack-trees.h" #include "builtin.h" -static int reset = 0; -static int merge = 0; -static int update = 0; -static int index_only = 0; -static int nontrivial_merge = 0; -static int trivial_merges_only = 0; -static int aggressive = 0; -static int verbose_update = 0; -static volatile int progress_update = 0; -static const char *prefix = NULL; - -static int head_idx = -1; -static int merge_size = 0; - -static struct object_list *trees = NULL; - -static struct cache_entry df_conflict_entry; - -struct tree_entry_list { - struct tree_entry_list *next; - unsigned directory : 1; - unsigned executable : 1; - unsigned symlink : 1; - unsigned int mode; - const char *name; - const unsigned char *sha1; -}; - -static struct tree_entry_list df_conflict_list; - -typedef int (*merge_fn_t)(struct cache_entry **src); - -static struct tree_entry_list *create_tree_entry_list(struct tree *tree) -{ - struct tree_desc desc; - struct name_entry one; - struct tree_entry_list *ret = NULL; - struct tree_entry_list **list_p = &ret; - - desc.buf = tree->buffer; - desc.size = tree->size; - - while (tree_entry(&desc, &one)) { - struct tree_entry_list *entry; - - entry = xmalloc(sizeof(struct tree_entry_list)); - entry->name = one.path; - entry->sha1 = one.sha1; - entry->mode = one.mode; - entry->directory = S_ISDIR(one.mode) != 0; - entry->executable = (one.mode & S_IXUSR) != 0; - entry->symlink = S_ISLNK(one.mode) != 0; - entry->next = NULL; - - *list_p = entry; - list_p = &entry->next; - } - return ret; -} - -static int entcmp(const char *name1, int dir1, const char *name2, int dir2) -{ - int len1 = strlen(name1); - int len2 = strlen(name2); - int len = len1 < len2 ? len1 : len2; - int ret = memcmp(name1, name2, len); - unsigned char c1, c2; - if (ret) - return ret; - c1 = name1[len]; - c2 = name2[len]; - if (!c1 && dir1) - c1 = '/'; - if (!c2 && dir2) - c2 = '/'; - ret = (c1 < c2) ? -1 : (c1 > c2) ? 1 : 0; - if (c1 && c2 && !ret) - ret = len1 - len2; - return ret; -} - -static int unpack_trees_rec(struct tree_entry_list **posns, int len, - const char *base, merge_fn_t fn, int *indpos) -{ - int baselen = strlen(base); - int src_size = len + 1; - do { - int i; - const char *first; - int firstdir = 0; - int pathlen; - unsigned ce_size; - struct tree_entry_list **subposns; - struct cache_entry **src; - int any_files = 0; - int any_dirs = 0; - char *cache_name; - int ce_stage; - - /* Find the first name in the input. */ - - first = NULL; - cache_name = NULL; - - /* Check the cache */ - if (merge && *indpos < active_nr) { - /* This is a bit tricky: */ - /* If the index has a subdirectory (with - * contents) as the first name, it'll get a - * filename like "foo/bar". But that's after - * "foo", so the entry in trees will get - * handled first, at which point we'll go into - * "foo", and deal with "bar" from the index, - * because the base will be "foo/". The only - * way we can actually have "foo/bar" first of - * all the things is if the trees don't - * contain "foo" at all, in which case we'll - * handle "foo/bar" without going into the - * directory, but that's fine (and will return - * an error anyway, with the added unknown - * file case. - */ - - cache_name = active_cache[*indpos]->name; - if (strlen(cache_name) > baselen && - !memcmp(cache_name, base, baselen)) { - cache_name += baselen; - first = cache_name; - } else { - cache_name = NULL; - } - } - -#if DBRT_DEBUG > 1 - if (first) - printf("index %s\n", first); -#endif - for (i = 0; i < len; i++) { - if (!posns[i] || posns[i] == &df_conflict_list) - continue; -#if DBRT_DEBUG > 1 - printf("%d %s\n", i + 1, posns[i]->name); -#endif - if (!first || entcmp(first, firstdir, - posns[i]->name, - posns[i]->directory) > 0) { - first = posns[i]->name; - firstdir = posns[i]->directory; - } - } - /* No name means we're done */ - if (!first) - return 0; - - pathlen = strlen(first); - ce_size = cache_entry_size(baselen + pathlen); - - src = xcalloc(src_size, sizeof(struct cache_entry *)); - - subposns = xcalloc(len, sizeof(struct tree_list_entry *)); - - if (cache_name && !strcmp(cache_name, first)) { - any_files = 1; - src[0] = active_cache[*indpos]; - remove_cache_entry_at(*indpos); - } - - for (i = 0; i < len; i++) { - struct cache_entry *ce; - - if (!posns[i] || - (posns[i] != &df_conflict_list && - strcmp(first, posns[i]->name))) { - continue; - } - - if (posns[i] == &df_conflict_list) { - src[i + merge] = &df_conflict_entry; - continue; - } - - if (posns[i]->directory) { - struct tree *tree = lookup_tree(posns[i]->sha1); - any_dirs = 1; - parse_tree(tree); - subposns[i] = create_tree_entry_list(tree); - posns[i] = posns[i]->next; - src[i + merge] = &df_conflict_entry; - continue; - } - - if (!merge) - ce_stage = 0; - else if (i + 1 < head_idx) - ce_stage = 1; - else if (i + 1 > head_idx) - ce_stage = 3; - else - ce_stage = 2; - - ce = xcalloc(1, ce_size); - ce->ce_mode = create_ce_mode(posns[i]->mode); - ce->ce_flags = create_ce_flags(baselen + pathlen, - ce_stage); - memcpy(ce->name, base, baselen); - memcpy(ce->name + baselen, first, pathlen + 1); - - any_files = 1; - - memcpy(ce->sha1, posns[i]->sha1, 20); - src[i + merge] = ce; - subposns[i] = &df_conflict_list; - posns[i] = posns[i]->next; - } - if (any_files) { - if (merge) { - int ret; - -#if DBRT_DEBUG > 1 - printf("%s:\n", first); - for (i = 0; i < src_size; i++) { - printf(" %d ", i); - if (src[i]) - printf("%s\n", sha1_to_hex(src[i]->sha1)); - else - printf("\n"); - } -#endif - ret = fn(src); - -#if DBRT_DEBUG > 1 - printf("Added %d entries\n", ret); -#endif - *indpos += ret; - } else { - for (i = 0; i < src_size; i++) { - if (src[i]) { - add_cache_entry(src[i], ADD_CACHE_OK_TO_ADD|ADD_CACHE_SKIP_DFCHECK); - } - } - } - } - if (any_dirs) { - char *newbase = xmalloc(baselen + 2 + pathlen); - memcpy(newbase, base, baselen); - memcpy(newbase + baselen, first, pathlen); - newbase[baselen + pathlen] = '/'; - newbase[baselen + pathlen + 1] = '\0'; - if (unpack_trees_rec(subposns, len, newbase, fn, - indpos)) - return -1; - free(newbase); - } - free(subposns); - free(src); - } while (1); -} - -static void reject_merge(struct cache_entry *ce) -{ - die("Entry '%s' would be overwritten by merge. Cannot merge.", - ce->name); -} - -/* Unlink the last component and attempt to remove leading - * directories, in case this unlink is the removal of the - * last entry in the directory -- empty directories are removed. - */ -static void unlink_entry(char *name) -{ - char *cp, *prev; - - if (unlink(name)) - return; - prev = NULL; - while (1) { - int status; - cp = strrchr(name, '/'); - if (prev) - *prev = '/'; - if (!cp) - break; - - *cp = 0; - status = rmdir(name); - if (status) { - *cp = '/'; - break; - } - prev = cp; - } -} - -static void progress_interval(int signum) -{ - progress_update = 1; -} - -static void setup_progress_signal(void) -{ - struct sigaction sa; - struct itimerval v; - - memset(&sa, 0, sizeof(sa)); - sa.sa_handler = progress_interval; - sigemptyset(&sa.sa_mask); - sa.sa_flags = SA_RESTART; - sigaction(SIGALRM, &sa, NULL); - - v.it_interval.tv_sec = 1; - v.it_interval.tv_usec = 0; - v.it_value = v.it_interval; - setitimer(ITIMER_REAL, &v, NULL); -} - -static struct checkout state; -static void check_updates(struct cache_entry **src, int nr) -{ - unsigned short mask = htons(CE_UPDATE); - unsigned last_percent = 200, cnt = 0, total = 0; - - if (update && verbose_update) { - for (total = cnt = 0; cnt < nr; cnt++) { - struct cache_entry *ce = src[cnt]; - if (!ce->ce_mode || ce->ce_flags & mask) - total++; - } - - /* Don't bother doing this for very small updates */ - if (total < 250) - total = 0; - - if (total) { - fprintf(stderr, "Checking files out...\n"); - setup_progress_signal(); - progress_update = 1; - } - cnt = 0; - } - - while (nr--) { - struct cache_entry *ce = *src++; - - if (total) { - if (!ce->ce_mode || ce->ce_flags & mask) { - unsigned percent; - cnt++; - percent = (cnt * 100) / total; - if (percent != last_percent || - progress_update) { - fprintf(stderr, "%4u%% (%u/%u) done\r", - percent, cnt, total); - last_percent = percent; - progress_update = 0; - } - } - } - if (!ce->ce_mode) { - if (update) - unlink_entry(ce->name); - continue; - } - if (ce->ce_flags & mask) { - ce->ce_flags &= ~mask; - if (update) - checkout_entry(ce, &state, NULL); - } - } - if (total) { - signal(SIGALRM, SIG_IGN); - fputc('\n', stderr); - } -} - -static int unpack_trees(merge_fn_t fn) -{ - int indpos = 0; - unsigned len = object_list_length(trees); - struct tree_entry_list **posns; - int i; - struct object_list *posn = trees; - merge_size = len; - - if (len) { - posns = xmalloc(len * sizeof(struct tree_entry_list *)); - for (i = 0; i < len; i++) { - posns[i] = create_tree_entry_list((struct tree *) posn->item); - posn = posn->next; - } - if (unpack_trees_rec(posns, len, prefix ? prefix : "", - fn, &indpos)) - return -1; - } - - if (trivial_merges_only && nontrivial_merge) - die("Merge requires file-level merging"); - - check_updates(active_cache, active_nr); - return 0; -} +static struct object_list *trees; static int list_tree(unsigned char *sha1) { @@ -425,386 +23,6 @@ static int list_tree(unsigned char *sha1) return 0; } -static int same(struct cache_entry *a, struct cache_entry *b) -{ - if (!!a != !!b) - return 0; - if (!a && !b) - return 1; - return a->ce_mode == b->ce_mode && - !memcmp(a->sha1, b->sha1, 20); -} - - -/* - * When a CE gets turned into an unmerged entry, we - * want it to be up-to-date - */ -static void verify_uptodate(struct cache_entry *ce) -{ - struct stat st; - - if (index_only || reset) - return; - - if (!lstat(ce->name, &st)) { - unsigned changed = ce_match_stat(ce, &st, 1); - if (!changed) - return; - errno = 0; - } - if (reset) { - ce->ce_flags |= htons(CE_UPDATE); - return; - } - if (errno == ENOENT) - return; - die("Entry '%s' not uptodate. Cannot merge.", ce->name); -} - -static void invalidate_ce_path(struct cache_entry *ce) -{ - if (ce) - cache_tree_invalidate_path(active_cache_tree, ce->name); -} - -/* - * We do not want to remove or overwrite a working tree file that - * is not tracked. - */ -static void verify_absent(const char *path, const char *action) -{ - struct stat st; - - if (index_only || reset || !update) - return; - if (!lstat(path, &st)) - die("Untracked working tree file '%s' " - "would be %s by merge.", path, action); -} - -static int merged_entry(struct cache_entry *merge, struct cache_entry *old) -{ - merge->ce_flags |= htons(CE_UPDATE); - if (old) { - /* - * See if we can re-use the old CE directly? - * That way we get the uptodate stat info. - * - * This also removes the UPDATE flag on - * a match. - */ - if (same(old, merge)) { - *merge = *old; - } else { - verify_uptodate(old); - invalidate_ce_path(old); - } - } - else { - verify_absent(merge->name, "overwritten"); - invalidate_ce_path(merge); - } - - merge->ce_flags &= ~htons(CE_STAGEMASK); - add_cache_entry(merge, ADD_CACHE_OK_TO_ADD|ADD_CACHE_OK_TO_REPLACE); - return 1; -} - -static int deleted_entry(struct cache_entry *ce, struct cache_entry *old) -{ - if (old) - verify_uptodate(old); - else - verify_absent(ce->name, "removed"); - ce->ce_mode = 0; - add_cache_entry(ce, ADD_CACHE_OK_TO_ADD|ADD_CACHE_OK_TO_REPLACE); - invalidate_ce_path(ce); - return 1; -} - -static int keep_entry(struct cache_entry *ce) -{ - add_cache_entry(ce, ADD_CACHE_OK_TO_ADD); - return 1; -} - -#if DBRT_DEBUG -static void show_stage_entry(FILE *o, - const char *label, const struct cache_entry *ce) -{ - if (!ce) - fprintf(o, "%s (missing)\n", label); - else - fprintf(o, "%s%06o %s %d\t%s\n", - label, - ntohl(ce->ce_mode), - sha1_to_hex(ce->sha1), - ce_stage(ce), - ce->name); -} -#endif - -static int threeway_merge(struct cache_entry **stages) -{ - struct cache_entry *index; - struct cache_entry *head; - struct cache_entry *remote = stages[head_idx + 1]; - int count; - int head_match = 0; - int remote_match = 0; - const char *path = NULL; - - int df_conflict_head = 0; - int df_conflict_remote = 0; - - int any_anc_missing = 0; - int no_anc_exists = 1; - int i; - - for (i = 1; i < head_idx; i++) { - if (!stages[i]) - any_anc_missing = 1; - else { - if (!path) - path = stages[i]->name; - no_anc_exists = 0; - } - } - - index = stages[0]; - head = stages[head_idx]; - - if (head == &df_conflict_entry) { - df_conflict_head = 1; - head = NULL; - } - - if (remote == &df_conflict_entry) { - df_conflict_remote = 1; - remote = NULL; - } - - if (!path && index) - path = index->name; - if (!path && head) - path = head->name; - if (!path && remote) - path = remote->name; - - /* First, if there's a #16 situation, note that to prevent #13 - * and #14. - */ - if (!same(remote, head)) { - for (i = 1; i < head_idx; i++) { - if (same(stages[i], head)) { - head_match = i; - } - if (same(stages[i], remote)) { - remote_match = i; - } - } - } - - /* We start with cases where the index is allowed to match - * something other than the head: #14(ALT) and #2ALT, where it - * is permitted to match the result instead. - */ - /* #14, #14ALT, #2ALT */ - if (remote && !df_conflict_head && head_match && !remote_match) { - if (index && !same(index, remote) && !same(index, head)) - reject_merge(index); - return merged_entry(remote, index); - } - /* - * If we have an entry in the index cache, then we want to - * make sure that it matches head. - */ - if (index && !same(index, head)) { - reject_merge(index); - } - - if (head) { - /* #5ALT, #15 */ - if (same(head, remote)) - return merged_entry(head, index); - /* #13, #3ALT */ - if (!df_conflict_remote && remote_match && !head_match) - return merged_entry(head, index); - } - - /* #1 */ - if (!head && !remote && any_anc_missing) - return 0; - - /* Under the new "aggressive" rule, we resolve mostly trivial - * cases that we historically had git-merge-one-file resolve. - */ - if (aggressive) { - int head_deleted = !head && !df_conflict_head; - int remote_deleted = !remote && !df_conflict_remote; - /* - * Deleted in both. - * Deleted in one and unchanged in the other. - */ - if ((head_deleted && remote_deleted) || - (head_deleted && remote && remote_match) || - (remote_deleted && head && head_match)) { - if (index) - return deleted_entry(index, index); - else if (path) - verify_absent(path, "removed"); - return 0; - } - /* - * Added in both, identically. - */ - if (no_anc_exists && head && remote && same(head, remote)) - return merged_entry(head, index); - - } - - /* Below are "no merge" cases, which require that the index be - * up-to-date to avoid the files getting overwritten with - * conflict resolution files. - */ - if (index) { - verify_uptodate(index); - } - else if (path) - verify_absent(path, "overwritten"); - - nontrivial_merge = 1; - - /* #2, #3, #4, #6, #7, #9, #11. */ - count = 0; - if (!head_match || !remote_match) { - for (i = 1; i < head_idx; i++) { - if (stages[i]) { - keep_entry(stages[i]); - count++; - break; - } - } - } -#if DBRT_DEBUG - else { - fprintf(stderr, "read-tree: warning #16 detected\n"); - show_stage_entry(stderr, "head ", stages[head_match]); - show_stage_entry(stderr, "remote ", stages[remote_match]); - } -#endif - if (head) { count += keep_entry(head); } - if (remote) { count += keep_entry(remote); } - return count; -} - -/* - * Two-way merge. - * - * The rule is to "carry forward" what is in the index without losing - * information across a "fast forward", favoring a successful merge - * over a merge failure when it makes sense. For details of the - * "carry forward" rule, please see <Documentation/git-read-tree.txt>. - * - */ -static int twoway_merge(struct cache_entry **src) -{ - struct cache_entry *current = src[0]; - struct cache_entry *oldtree = src[1], *newtree = src[2]; - - if (merge_size != 2) - return error("Cannot do a twoway merge of %d trees", - merge_size); - - if (current) { - if ((!oldtree && !newtree) || /* 4 and 5 */ - (!oldtree && newtree && - same(current, newtree)) || /* 6 and 7 */ - (oldtree && newtree && - same(oldtree, newtree)) || /* 14 and 15 */ - (oldtree && newtree && - !same(oldtree, newtree) && /* 18 and 19*/ - same(current, newtree))) { - return keep_entry(current); - } - else if (oldtree && !newtree && same(current, oldtree)) { - /* 10 or 11 */ - return deleted_entry(oldtree, current); - } - else if (oldtree && newtree && - same(current, oldtree) && !same(current, newtree)) { - /* 20 or 21 */ - return merged_entry(newtree, current); - } - else { - /* all other failures */ - if (oldtree) - reject_merge(oldtree); - if (current) - reject_merge(current); - if (newtree) - reject_merge(newtree); - return -1; - } - } - else if (newtree) - return merged_entry(newtree, current); - else - return deleted_entry(oldtree, current); -} - -/* - * Bind merge. - * - * Keep the index entries at stage0, collapse stage1 but make sure - * stage0 does not have anything there. - */ -static int bind_merge(struct cache_entry **src) -{ - struct cache_entry *old = src[0]; - struct cache_entry *a = src[1]; - - if (merge_size != 1) - return error("Cannot do a bind merge of %d trees\n", - merge_size); - if (a && old) - die("Entry '%s' overlaps. Cannot bind.", a->name); - if (!a) - return keep_entry(old); - else - return merged_entry(a, NULL); -} - -/* - * One-way merge. - * - * The rule is: - * - take the stat information from stage0, take the data from stage1 - */ -static int oneway_merge(struct cache_entry **src) -{ - struct cache_entry *old = src[0]; - struct cache_entry *a = src[1]; - - if (merge_size != 1) - return error("Cannot do a oneway merge of %d trees", - merge_size); - - if (!a) - return deleted_entry(old, old); - if (old && same(old, a)) { - if (reset) { - struct stat st; - if (lstat(old->name, &st) || - ce_match_stat(old, &st, 1)) - old->ce_flags |= htons(CE_UPDATE); - } - return keep_entry(old); - } - return merged_entry(a, old); -} - static int read_cache_unmerged(void) { int i; @@ -818,7 +36,7 @@ static int read_cache_unmerged(void) if (ce_stage(ce)) { if (last && !strcmp(ce->name, last->name)) continue; - invalidate_ce_path(ce); + cache_tree_invalidate_path(active_cache_tree, ce->name); last = ce; ce->ce_mode = 0; ce->ce_flags &= ~htons(CE_STAGEMASK); @@ -835,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; @@ -874,22 +92,18 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix) { int i, newfd, stage = 0; unsigned char sha1[20]; - merge_fn_t fn = NULL; + struct unpack_trees_options opts; - df_conflict_list.next = &df_conflict_list; - state.base_dir = ""; - state.force = 1; - state.quiet = 1; - state.refresh_cache = 1; + memset(&opts, 0, sizeof(opts)); + opts.head_idx = -1; + setup_git_directory(); git_config(git_default_config); newfd = hold_lock_file_for_update(&lock_file, get_index_file(), 1); git_config(git_default_config); - merge = 0; - reset = 0; for (i = 1; i < argc; i++) { const char *arg = argv[i]; @@ -897,12 +111,12 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix) * the working tree. */ if (!strcmp(arg, "-u")) { - update = 1; + opts.update = 1; continue; } if (!strcmp(arg, "-v")) { - verbose_update = 1; + opts.verbose_update = 1; continue; } @@ -910,7 +124,7 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix) * not even look at the working tree. */ if (!strcmp(arg, "-i")) { - index_only = 1; + opts.index_only = 1; continue; } @@ -919,10 +133,10 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix) * given subdirectory. */ if (!strncmp(arg, "--prefix=", 9)) { - if (stage || merge || prefix) + if (stage || opts.merge || opts.prefix) usage(read_tree_usage); - prefix = arg + 9; - merge = 1; + opts.prefix = arg + 9; + opts.merge = 1; stage = 1; if (read_cache_unmerged()) die("you need to resolve your current index first"); @@ -934,38 +148,38 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix) * correspond to them. */ if (!strcmp(arg, "--reset")) { - if (stage || merge || prefix) + if (stage || opts.merge || opts.prefix) usage(read_tree_usage); - reset = 1; - merge = 1; + opts.reset = 1; + opts.merge = 1; stage = 1; read_cache_unmerged(); continue; } if (!strcmp(arg, "--trivial")) { - trivial_merges_only = 1; + opts.trivial_merges_only = 1; continue; } if (!strcmp(arg, "--aggressive")) { - aggressive = 1; + opts.aggressive = 1; continue; } /* "-m" stands for "merge", meaning we start in stage 1 */ if (!strcmp(arg, "-m")) { - if (stage || merge || prefix) + if (stage || opts.merge || opts.prefix) usage(read_tree_usage); if (read_cache_unmerged()) die("you need to resolve your current index first"); stage = 1; - merge = 1; + opts.merge = 1; continue; } /* using -u and -i at the same time makes no sense */ - if (1 < index_only + update) + if (1 < opts.index_only + opts.update) usage(read_tree_usage); if (get_sha1(arg, sha1)) @@ -974,52 +188,53 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix) die("failed to unpack tree object %s", arg); stage++; } - if ((update||index_only) && !merge) + if ((opts.update||opts.index_only) && !opts.merge) usage(read_tree_usage); - if (prefix) { - int pfxlen = strlen(prefix); + if (opts.prefix) { + int pfxlen = strlen(opts.prefix); int pos; - if (prefix[pfxlen-1] != '/') + if (opts.prefix[pfxlen-1] != '/') die("prefix must end with /"); if (stage != 2) die("binding merge takes only one tree"); - pos = cache_name_pos(prefix, pfxlen); + pos = cache_name_pos(opts.prefix, pfxlen); if (0 <= pos) die("corrupt index file"); pos = -pos-1; if (pos < active_nr && - !strncmp(active_cache[pos]->name, prefix, pfxlen)) - die("subdirectory '%s' already exists.", prefix); - pos = cache_name_pos(prefix, pfxlen-1); + !strncmp(active_cache[pos]->name, opts.prefix, pfxlen)) + die("subdirectory '%s' already exists.", opts.prefix); + pos = cache_name_pos(opts.prefix, pfxlen-1); if (0 <= pos) - die("file '%.*s' already exists.", pfxlen-1, prefix); + die("file '%.*s' already exists.", + pfxlen-1, opts.prefix); } - if (merge) { + if (opts.merge) { if (stage < 2) die("just how do you expect me to merge %d trees?", stage-1); switch (stage - 1) { case 1: - fn = prefix ? bind_merge : oneway_merge; + opts.fn = opts.prefix ? bind_merge : oneway_merge; break; case 2: - fn = twoway_merge; + opts.fn = twoway_merge; break; case 3: default: - fn = threeway_merge; + opts.fn = threeway_merge; cache_tree_free(&active_cache_tree); break; } if (stage - 1 >= 3) - head_idx = stage - 2; + opts.head_idx = stage - 2; else - head_idx = 1; + opts.head_idx = 1; } - unpack_trees(fn); + unpack_trees(trees, &opts); /* * When reading only one tree (either the most basic form, @@ -1027,7 +242,7 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix) * valid cache-tree because the index must match exactly * what came from the tree. */ - if (trees && trees->item && !prefix && (!merge || (stage == 2))) { + if (trees && trees->item && !opts.prefix && (!opts.merge || (stage == 2))) { cache_tree_free(&active_cache_tree); prime_cache_tree(); } diff --git a/builtin-repo-config.c b/builtin-repo-config.c index c821e22717..f60cee1dc5 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; @@ -119,13 +119,11 @@ static int get_value(const char* key_, const char* regex_) if (do_all) ret = !seen; else - ret = (seen == 1) ? 0 : 1; + ret = (seen == 1) ? 0 : seen > 1 ? 2 : 1; free_strings: - if (repo_config) - free(repo_config); - if (global) - free(global); + free(repo_config); + free(global); return ret; } diff --git a/builtin-rev-list.c b/builtin-rev-list.c index 0dee1734a3..fb7fc92145 100644 --- a/builtin-rev-list.c +++ b/builtin-rev-list.c @@ -7,6 +7,7 @@ #include "tree-walk.h" #include "diff.h" #include "revision.h" +#include "list-objects.h" #include "builtin.h" /* bits #0-15 in revision.h */ @@ -23,6 +24,7 @@ static const char rev_list_usage[] = " --no-merges\n" " --remove-empty\n" " --all\n" +" --stdin\n" " ordering output:\n" " --topo-order\n" " --date-order\n" @@ -39,9 +41,9 @@ static const char rev_list_usage[] = static struct rev_info revs; -static int bisect_list = 0; -static int show_timestamp = 0; -static int hdr_termination = 0; +static int bisect_list; +static int show_timestamp; +static int hdr_termination; static const char *header_prefix; static void show_commit(struct commit *commit) @@ -85,7 +87,7 @@ static void show_commit(struct commit *commit) static char pretty_header[16384]; pretty_print_commit(revs.commit_format, commit, ~0, pretty_header, sizeof(pretty_header), - revs.abbrev, NULL, NULL); + revs.abbrev, NULL, NULL, revs.relative_date); printf("%s%c", pretty_header, hdr_termination); } fflush(stdout); @@ -93,110 +95,28 @@ static void show_commit(struct commit *commit) free_commit_list(commit->parents); commit->parents = NULL; } - if (commit->buffer) { - free(commit->buffer); - commit->buffer = NULL; - } + free(commit->buffer); + commit->buffer = NULL; } -static void process_blob(struct blob *blob, - struct object_array *p, - struct name_path *path, - const char *name) +static void show_object(struct object_array_entry *p) { - struct object *obj = &blob->object; - - if (!revs.blob_objects) - return; - if (obj->flags & (UNINTERESTING | SEEN)) - return; - obj->flags |= SEEN; - name = strdup(name); - add_object(obj, p, path, name); -} - -static void process_tree(struct tree *tree, - struct object_array *p, - struct name_path *path, - const char *name) -{ - struct object *obj = &tree->object; - struct tree_desc desc; - struct name_entry entry; - struct name_path me; - - if (!revs.tree_objects) - return; - if (obj->flags & (UNINTERESTING | SEEN)) - return; - if (parse_tree(tree) < 0) - die("bad tree object %s", sha1_to_hex(obj->sha1)); - obj->flags |= SEEN; - name = strdup(name); - add_object(obj, p, path, name); - me.up = path; - me.elem = name; - me.elem_len = strlen(name); - - desc.buf = tree->buffer; - desc.size = tree->size; - - while (tree_entry(&desc, &entry)) { - if (S_ISDIR(entry.mode)) - process_tree(lookup_tree(entry.sha1), p, &me, entry.path); - else - process_blob(lookup_blob(entry.sha1), p, &me, entry.path); + /* An object with name "foo\n0000000..." can be used to + * confuse downstream git-pack-objects very badly. + */ + const char *ep = strchr(p->name, '\n'); + if (ep) { + printf("%s %.*s\n", sha1_to_hex(p->item->sha1), + (int) (ep - p->name), + p->name); } - free(tree->buffer); - tree->buffer = NULL; + else + printf("%s %s\n", sha1_to_hex(p->item->sha1), p->name); } -static void show_commit_list(struct rev_info *revs) +static void show_edge(struct commit *commit) { - int i; - struct commit *commit; - struct object_array objects = { 0, 0, NULL }; - - while ((commit = get_revision(revs)) != NULL) { - process_tree(commit->tree, &objects, NULL, ""); - show_commit(commit); - } - for (i = 0; i < revs->pending.nr; i++) { - struct object_array_entry *pending = revs->pending.objects + i; - struct object *obj = pending->item; - const char *name = pending->name; - if (obj->flags & (UNINTERESTING | SEEN)) - continue; - if (obj->type == OBJ_TAG) { - obj->flags |= SEEN; - add_object_array(obj, name, &objects); - continue; - } - if (obj->type == OBJ_TREE) { - process_tree((struct tree *)obj, &objects, NULL, name); - continue; - } - if (obj->type == OBJ_BLOB) { - process_blob((struct blob *)obj, &objects, NULL, name); - continue; - } - die("unknown pending object %s (%s)", sha1_to_hex(obj->sha1), name); - } - for (i = 0; i < objects.nr; i++) { - struct object_array_entry *p = objects.objects + i; - - /* An object with name "foo\n0000000..." can be used to - * confuse downstream git-pack-objects very badly. - */ - const char *ep = strchr(p->name, '\n'); - if (ep) { - printf("%s %.*s\n", sha1_to_hex(p->item->sha1), - (int) (ep - p->name), - p->name); - } - else - printf("%s %s\n", sha1_to_hex(p->item->sha1), p->name); - } + printf("-%s\n", sha1_to_hex(commit->object.sha1)); } /* @@ -277,32 +197,20 @@ static struct commit_list *find_bisection(struct commit_list *list) return best; } -static void mark_edge_parents_uninteresting(struct commit *commit) +static void read_revisions_from_stdin(struct rev_info *revs) { - struct commit_list *parents; - - for (parents = commit->parents; parents; parents = parents->next) { - struct commit *parent = parents->item; - if (!(parent->object.flags & UNINTERESTING)) - continue; - mark_tree_uninteresting(parent->tree); - if (revs.edge_hint && !(parent->object.flags & SHOWN)) { - parent->object.flags |= SHOWN; - printf("-%s\n", sha1_to_hex(parent->object.sha1)); - } - } -} + char line[1000]; -static void mark_edges_uninteresting(struct commit_list *list) -{ - for ( ; list; list = list->next) { - struct commit *commit = list->item; - - if (commit->object.flags & UNINTERESTING) { - mark_tree_uninteresting(commit->tree); - continue; - } - mark_edge_parents_uninteresting(commit); + while (fgets(line, sizeof(line), stdin) != NULL) { + int len = strlen(line); + if (line[len - 1] == '\n') + line[--len] = 0; + if (!len) + break; + if (line[0] == '-') + die("options not supported in --stdin mode"); + if (handle_revision_arg(line, revs, 0, 1)) + die("bad revision '%s'", line); } } @@ -310,6 +218,7 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) { struct commit_list *list; int i; + int read_from_stdin = 0; init_revisions(&revs, prefix); revs.abbrev = 0; @@ -331,6 +240,12 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) bisect_list = 1; continue; } + if (!strcmp(arg, "--stdin")) { + if (read_from_stdin++) + die("--stdin given twice?"); + read_revisions_from_stdin(&revs); + continue; + } usage(rev_list_usage); } @@ -354,19 +269,19 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) revs.diff) usage(rev_list_usage); - save_commit_buffer = revs.verbose_header; + save_commit_buffer = revs.verbose_header || revs.grep_filter; track_object_refs = 0; if (bisect_list) revs.limited = 1; prepare_revision_walk(&revs); if (revs.tree_objects) - mark_edges_uninteresting(revs.commits); + mark_edges_uninteresting(revs.commits, &revs, show_edge); if (bisect_list) revs.commits = find_bisection(revs.commits); - show_commit_list(&revs); + traverse_commit_list(&revs, show_commit, show_object); return 0; } diff --git a/builtin-rev-parse.c b/builtin-rev-parse.c index aca4a36032..fd3ccc8546 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 593d86744c..33d04bd015 100644 --- a/builtin-rm.c +++ b/builtin-rm.c @@ -32,7 +32,7 @@ static int remove_file(const char *name) ret = unlink(name); if (!ret && (slash = strrchr(name, '/'))) { - char *n = strdup(name); + char *n = xstrdup(name); do { n[slash - name] = 0; name = n; diff --git a/builtin-runstatus.c b/builtin-runstatus.c new file mode 100644 index 0000000000..303c556da0 --- /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 2a1b848f6c..578c9fafd0 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/symbolic-ref.c b/builtin-symbolic-ref.c index 193c87c174..1d3a5e229a 100644 --- a/symbolic-ref.c +++ b/builtin-symbolic-ref.c @@ -1,3 +1,4 @@ +#include "builtin.h" #include "cache.h" static const char git_symbolic_ref_usage[] = @@ -6,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 */ @@ -17,16 +18,15 @@ static void check_symref(const char *HEAD) die("No such ref: %s", HEAD); } -int main(int argc, const char **argv) +int cmd_symbolic_ref(int argc, const char **argv, const char *prefix) { - setup_git_directory(); git_config(git_default_config); switch (argc) { case 2: 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 215892b696..4d4cfec878 100644 --- a/builtin-tar-tree.c +++ b/builtin-tar-tree.c @@ -3,416 +3,69 @@ */ #include <time.h> #include "cache.h" -#include "tree-walk.h" #include "commit.h" -#include "strbuf.h" #include "tar.h" #include "builtin.h" -#include "pkt-line.h" - -#define RECORDSIZE (512) -#define BLOCKSIZE (RECORDSIZE * 20) +#include "quote.h" static const char tar_tree_usage[] = -"git-tar-tree [--remote=<repo>] <ent> [basedir]"; - -static char block[BLOCKSIZE]; -static unsigned long offset; - -static time_t archive_time; -static int tar_umask; - -/* tries hard to write, either succeeds or dies in the attempt */ -static void reliable_write(const void *data, unsigned long size) -{ - const char *buf = data; - - while (size > 0) { - long ret = xwrite(1, buf, size); - if (ret < 0) { - if (errno == EPIPE) - exit(0); - die("git-tar-tree: %s", strerror(errno)); - } else if (!ret) { - die("git-tar-tree: disk full?"); - } - size -= ret; - buf += ret; - } -} - -/* writes out the whole block, but only if it is full */ -static void write_if_needed(void) -{ - if (offset == BLOCKSIZE) { - reliable_write(block, BLOCKSIZE); - offset = 0; - } -} - -/* - * queues up writes, so that all our write(2) calls write exactly one - * full block; pads writes to RECORDSIZE - */ -static void write_blocked(const void *data, unsigned long size) -{ - const char *buf = data; - unsigned long tail; - - if (offset) { - unsigned long chunk = BLOCKSIZE - offset; - if (size < chunk) - chunk = size; - memcpy(block + offset, buf, chunk); - size -= chunk; - offset += chunk; - buf += chunk; - write_if_needed(); - } - while (size >= BLOCKSIZE) { - reliable_write(buf, BLOCKSIZE); - size -= BLOCKSIZE; - buf += BLOCKSIZE; - } - if (size) { - memcpy(block + offset, buf, size); - offset += size; - } - tail = offset % RECORDSIZE; - if (tail) { - memset(block + offset, 0, RECORDSIZE - tail); - offset += RECORDSIZE - tail; - } - write_if_needed(); -} - -/* - * The end of tar archives is marked by 2*512 nul bytes and after that - * follows the rest of the block (if any). - */ -static void write_trailer(void) -{ - int tail = BLOCKSIZE - offset; - memset(block + offset, 0, tail); - reliable_write(block, BLOCKSIZE); - if (tail < 2 * RECORDSIZE) { - memset(block, 0, offset); - reliable_write(block, BLOCKSIZE); - } -} - -static void strbuf_append_string(struct strbuf *sb, const char *s) -{ - int slen = strlen(s); - int total = sb->len + slen; - if (total > sb->alloc) { - sb->buf = xrealloc(sb->buf, total); - sb->alloc = total; - } - memcpy(sb->buf + sb->len, s, slen); - sb->len = total; -} - -/* - * pax extended header records have the format "%u %s=%s\n". %u contains - * the size of the whole string (including the %u), the first %s is the - * keyword, the second one is the value. This function constructs such a - * string and appends it to a struct strbuf. - */ -static void strbuf_append_ext_header(struct strbuf *sb, const char *keyword, - const char *value, unsigned int valuelen) -{ - char *p; - int len, total, tmp; - - /* "%u %s=%s\n" */ - len = 1 + 1 + strlen(keyword) + 1 + valuelen + 1; - for (tmp = len; tmp > 9; tmp /= 10) - len++; - - total = sb->len + len; - if (total > sb->alloc) { - sb->buf = xrealloc(sb->buf, total); - sb->alloc = total; - } - - p = sb->buf; - p += sprintf(p, "%u %s=", len, keyword); - memcpy(p, value, valuelen); - p += valuelen; - *p = '\n'; - sb->len = total; -} - -static unsigned int ustar_header_chksum(const struct ustar_header *header) -{ - char *p = (char *)header; - unsigned int chksum = 0; - while (p < header->chksum) - chksum += *p++; - chksum += sizeof(header->chksum) * ' '; - p += sizeof(header->chksum); - while (p < (char *)header + sizeof(struct ustar_header)) - chksum += *p++; - return chksum; -} - -static int get_path_prefix(const struct strbuf *path, int maxlen) -{ - int i = path->len; - if (i > maxlen) - i = maxlen; - do { - i--; - } while (i > 0 && path->buf[i] != '/'); - return i; -} - -static void write_entry(const unsigned char *sha1, struct strbuf *path, - unsigned int mode, void *buffer, unsigned long size) -{ - struct ustar_header header; - struct strbuf ext_header; - - memset(&header, 0, sizeof(header)); - ext_header.buf = NULL; - ext_header.len = ext_header.alloc = 0; - - if (!sha1) { - *header.typeflag = TYPEFLAG_GLOBAL_HEADER; - mode = 0100666; - strcpy(header.name, "pax_global_header"); - } else if (!path) { - *header.typeflag = TYPEFLAG_EXT_HEADER; - mode = 0100666; - sprintf(header.name, "%s.paxheader", sha1_to_hex(sha1)); - } else { - if (S_ISDIR(mode)) { - *header.typeflag = TYPEFLAG_DIR; - mode = (mode | 0777) & ~tar_umask; - } else if (S_ISLNK(mode)) { - *header.typeflag = TYPEFLAG_LNK; - mode |= 0777; - } else if (S_ISREG(mode)) { - *header.typeflag = TYPEFLAG_REG; - mode = (mode | ((mode & 0100) ? 0777 : 0666)) & ~tar_umask; - } else { - error("unsupported file mode: 0%o (SHA1: %s)", - mode, sha1_to_hex(sha1)); - return; - } - if (path->len > sizeof(header.name)) { - int plen = get_path_prefix(path, sizeof(header.prefix)); - int rest = path->len - plen - 1; - if (plen > 0 && rest <= sizeof(header.name)) { - memcpy(header.prefix, path->buf, plen); - memcpy(header.name, path->buf + plen + 1, rest); - } else { - sprintf(header.name, "%s.data", - sha1_to_hex(sha1)); - strbuf_append_ext_header(&ext_header, "path", - path->buf, path->len); - } - } else - memcpy(header.name, path->buf, path->len); - } - - if (S_ISLNK(mode) && buffer) { - if (size > sizeof(header.linkname)) { - sprintf(header.linkname, "see %s.paxheader", - sha1_to_hex(sha1)); - strbuf_append_ext_header(&ext_header, "linkpath", - buffer, size); - } else - memcpy(header.linkname, buffer, size); - } - - sprintf(header.mode, "%07o", mode & 07777); - sprintf(header.size, "%011lo", S_ISREG(mode) ? size : 0); - sprintf(header.mtime, "%011lo", archive_time); - - /* XXX: should we provide more meaningful info here? */ - sprintf(header.uid, "%07o", 0); - sprintf(header.gid, "%07o", 0); - strlcpy(header.uname, "git", sizeof(header.uname)); - strlcpy(header.gname, "git", sizeof(header.gname)); - sprintf(header.devmajor, "%07o", 0); - sprintf(header.devminor, "%07o", 0); - - memcpy(header.magic, "ustar", 6); - memcpy(header.version, "00", 2); - - sprintf(header.chksum, "%07o", ustar_header_chksum(&header)); - - if (ext_header.len > 0) { - write_entry(sha1, NULL, 0, ext_header.buf, ext_header.len); - free(ext_header.buf); - } - write_blocked(&header, sizeof(header)); - if (S_ISREG(mode) && buffer && size > 0) - write_blocked(buffer, size); -} - -static void write_global_extended_header(const unsigned char *sha1) -{ - struct strbuf ext_header; - ext_header.buf = NULL; - ext_header.len = ext_header.alloc = 0; - strbuf_append_ext_header(&ext_header, "comment", sha1_to_hex(sha1), 40); - write_entry(NULL, NULL, 0, ext_header.buf, ext_header.len); - free(ext_header.buf); -} - -static void traverse_tree(struct tree_desc *tree, struct strbuf *path) -{ - int pathlen = path->len; - struct name_entry entry; - - while (tree_entry(tree, &entry)) { - void *eltbuf; - char elttype[20]; - unsigned long eltsize; +"git-tar-tree [--remote=<repo>] <tree-ish> [basedir]\n" +"*** Note that this command is now deprecated; use git-archive instead."; - eltbuf = read_sha1_file(entry.sha1, elttype, &eltsize); - if (!eltbuf) - die("cannot read %s", sha1_to_hex(entry.sha1)); - - path->len = pathlen; - strbuf_append_string(path, entry.path); - if (S_ISDIR(entry.mode)) - strbuf_append_string(path, "/"); - - write_entry(entry.sha1, path, entry.mode, eltbuf, eltsize); - - if (S_ISDIR(entry.mode)) { - struct tree_desc subtree; - subtree.buf = eltbuf; - subtree.size = eltsize; - traverse_tree(&subtree, path); - } - free(eltbuf); - } -} - -int git_tar_config(const char *var, const char *value) +int cmd_tar_tree(int argc, const char **argv, const char *prefix) { - if (!strcmp(var, "tar.umask")) { - if (!strcmp(value, "user")) { - tar_umask = umask(0); - umask(tar_umask); - } else { - tar_umask = git_config_int(var, value); - } - return 0; + /* + * git-tar-tree is now a wrapper around git-archive --format=tar + * + * $0 --remote=<repo> arg... ==> + * git-archive --format=tar --remote=<repo> arg... + * $0 tree-ish ==> + * git-archive --format=tar tree-ish + * $0 tree-ish basedir ==> + * git-archive --format-tar --prefix=basedir tree-ish + */ + int i; + const char **nargv = xcalloc(sizeof(*nargv), argc + 2); + char *basedir_arg; + int nargc = 0; + + nargv[nargc++] = "git-archive"; + nargv[nargc++] = "--format=tar"; + + if (2 <= argc && !strncmp("--remote=", argv[1], 9)) { + nargv[nargc++] = argv[1]; + argv++; + argc--; } - return git_default_config(var, value); -} - -static int generate_tar(int argc, const char **argv, const char *prefix) -{ - unsigned char sha1[20], tree_sha1[20]; - struct commit *commit; - struct tree_desc tree; - struct strbuf current_path; - void *buffer; - - current_path.buf = xmalloc(PATH_MAX); - current_path.alloc = PATH_MAX; - current_path.len = current_path.eof = 0; - - git_config(git_tar_config); - switch (argc) { - case 3: - strbuf_append_string(¤t_path, argv[2]); - strbuf_append_string(¤t_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, ¤t_path, 040777, NULL, 0); - traverse_tree(&tree, ¤t_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/unpack-objects.c b/builtin-unpack-objects.c index 48c1ee7968..4f96bcae32 100644 --- a/unpack-objects.c +++ b/builtin-unpack-objects.c @@ -1,3 +1,4 @@ +#include "builtin.h" #include "cache.h" #include "object.h" #include "delta.h" @@ -9,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; /* @@ -25,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) { @@ -72,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; } @@ -94,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; @@ -111,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; @@ -126,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) @@ -135,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); @@ -146,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; @@ -158,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) @@ -237,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); } } @@ -260,12 +272,12 @@ static void unpack_all(void) die("unresolved deltas left after unpacking"); } -int main(int argc, char **argv) +int cmd_unpack_objects(int argc, const char **argv, const char *prefix) { int i; unsigned char sha1[20]; - setup_git_directory(); + git_config(git_default_config); quiet = !isatty(2); @@ -281,6 +293,10 @@ int main(int argc, char **argv) quiet = 1; continue; } + if (!strcmp(arg, "-r")) { + recover = 1; + continue; + } usage(unpack_usage); } @@ -291,7 +307,7 @@ int main(int argc, char **argv) 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); @@ -307,5 +323,5 @@ int main(int argc, char **argv) /* All done */ if (!quiet) fprintf(stderr, "\n"); - return 0; + return has_errors; } diff --git a/builtin-update-index.c b/builtin-update-index.c index d2556f376b..a3c0a455ae 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 5bd71825fd..90a3da53ad 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 0000000000..45c92e163c --- /dev/null +++ b/builtin-upload-archive.c @@ -0,0 +1,175 @@ +/* + * Copyright (c) 2006 Franck Bui-Huu + */ +#include <time.h> +#include <sys/wait.h> +#include <sys/poll.h> +#include "cache.h" +#include "builtin.h" +#include "archive.h" +#include "pkt-line.h" +#include "sideband.h" + +static const char upload_archive_usage[] = + "git-upload-archive <repo>"; + +static const char deadchild[] = +"git-upload-archive: archiver died with error"; + +static const char lostchild[] = +"git-upload-archive: archiver process was lost"; + + +static int run_upload_archive(int argc, const char **argv, const char *prefix) +{ + struct archiver ar; + const char *sent_argv[MAX_ARGS]; + const char *arg_cmd = "argument "; + char *p, buf[4096]; + int treeish_idx; + int sent_argc; + int len; + + if (argc != 2) + usage(upload_archive_usage); + + if (strlen(argv[1]) > sizeof(buf)) + die("insanely long repository name"); + + strcpy(buf, argv[1]); /* enter-repo smudges its argument */ + + if (!enter_repo(buf, 0)) + die("not a git archive"); + + /* put received options in sent_argv[] */ + sent_argc = 1; + sent_argv[0] = "git-upload-archive"; + for (p = buf;;) { + /* This will die if not enough free space in buf */ + len = packet_read_line(0, p, (buf + sizeof buf) - p); + if (len == 0) + break; /* got a flush */ + if (sent_argc > MAX_ARGS - 2) + die("Too many options (>29)"); + + if (p[len-1] == '\n') { + p[--len] = 0; + } + if (len < strlen(arg_cmd) || + strncmp(arg_cmd, p, strlen(arg_cmd))) + die("'argument' token or flush expected"); + + len -= strlen(arg_cmd); + memmove(p, p + strlen(arg_cmd), len); + sent_argv[sent_argc++] = p; + p += len; + *p++ = 0; + } + sent_argv[sent_argc] = NULL; + + /* parse all options sent by the client */ + treeish_idx = parse_archive_args(sent_argc, sent_argv, &ar); + + parse_treeish_arg(sent_argv + treeish_idx, &ar.args, prefix); + parse_pathspec_arg(sent_argv + treeish_idx + 1, &ar.args); + + return ar.write_archive(&ar.args); +} + +static void error_clnt(const char *fmt, ...) +{ + char buf[1024]; + va_list params; + int len; + + va_start(params, fmt); + len = vsprintf(buf, fmt, params); + va_end(params); + send_sideband(1, 3, buf, len, LARGE_PACKET_MAX); + die("sent error to the client: %s", buf); +} + +static void process_input(int child_fd, int band) +{ + char buf[16384]; + ssize_t sz = read(child_fd, buf, sizeof(buf)); + if (sz < 0) { + if (errno != EINTR) + error_clnt("read error: %s\n", strerror(errno)); + return; + } + send_sideband(1, band, buf, sz, LARGE_PACKET_MAX); +} + +int cmd_upload_archive(int argc, const char **argv, const char *prefix) +{ + pid_t writer; + int fd1[2], fd2[2]; + /* + * Set up sideband subprocess. + * + * We (parent) monitor and read from child, sending its fd#1 and fd#2 + * multiplexed out to our fd#1. If the child dies, we tell the other + * end over channel #3. + */ + if (pipe(fd1) < 0 || pipe(fd2) < 0) { + int err = errno; + packet_write(1, "NACK pipe failed on the remote side\n"); + die("upload-archive: %s", strerror(err)); + } + writer = fork(); + if (writer < 0) { + int err = errno; + packet_write(1, "NACK fork failed on the remote side\n"); + die("upload-archive: %s", strerror(err)); + } + if (!writer) { + /* child - connect fd#1 and fd#2 to the pipe */ + dup2(fd1[1], 1); + dup2(fd2[1], 2); + close(fd1[1]); close(fd2[1]); + close(fd1[0]); close(fd2[0]); /* we do not read from pipe */ + + exit(run_upload_archive(argc, argv, prefix)); + } + + /* parent - read from child, multiplex and send out to fd#1 */ + close(fd1[1]); close(fd2[1]); /* we do not write to pipe */ + packet_write(1, "ACK\n"); + packet_flush(1); + + while (1) { + struct pollfd pfd[2]; + int status; + + pfd[0].fd = fd1[0]; + pfd[0].events = POLLIN; + pfd[1].fd = fd2[0]; + pfd[1].events = POLLIN; + if (poll(pfd, 2, -1) < 0) { + if (errno != EINTR) { + error("poll failed resuming: %s", + strerror(errno)); + sleep(1); + } + continue; + } + if (pfd[0].revents & POLLIN) + /* Data stream ready */ + process_input(pfd[0].fd, 1); + if (pfd[1].revents & POLLIN) + /* Status stream ready */ + process_input(pfd[1].fd, 2); + /* Always finish to read data when available */ + if ((pfd[0].revents | pfd[1].revents) & POLLIN) + continue; + + if (waitpid(writer, &status, 0) < 0) + error_clnt("%s", lostchild); + else if (!WIFEXITED(status) || WEXITSTATUS(status) > 0) + error_clnt("%s", deadchild); + packet_flush(1); + break; + } + return 0; +} diff --git a/builtin-upload-tar.c b/builtin-upload-tar.c deleted file mode 100644 index 7b401bbb77..0000000000 --- a/builtin-upload-tar.c +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright (c) 2006 Junio C Hamano - */ -#include "cache.h" -#include "pkt-line.h" -#include "exec_cmd.h" -#include "builtin.h" - -static const char upload_tar_usage[] = "git-upload-tar <repo>"; - -static int nak(const char *reason) -{ - packet_write(1, "NACK %s\n", reason); - packet_flush(1); - return 1; -} - -int cmd_upload_tar(int argc, const char **argv, const char *prefix) -{ - int len; - const char *dir = argv[1]; - char buf[8192]; - unsigned char sha1[20]; - char *base = NULL; - char hex[41]; - int ac; - const char *av[4]; - - if (argc != 2) - usage(upload_tar_usage); - if (strlen(dir) < sizeof(buf)-1) - strcpy(buf, dir); /* enter-repo smudges its argument */ - else - packet_write(1, "NACK insanely long repository name %s\n", dir); - if (!enter_repo(buf, 0)) { - packet_write(1, "NACK not a git archive %s\n", dir); - packet_flush(1); - return 1; - } - - len = packet_read_line(0, buf, sizeof(buf)); - if (len < 5 || strncmp("want ", buf, 5)) - return nak("expected want"); - if (buf[len-1] == '\n') - buf[--len] = 0; - if (get_sha1(buf + 5, sha1)) - return nak("expected sha1"); - strcpy(hex, sha1_to_hex(sha1)); - - len = packet_read_line(0, buf, sizeof(buf)); - if (len) { - if (len < 5 || strncmp("base ", buf, 5)) - return nak("expected (optional) base"); - if (buf[len-1] == '\n') - buf[--len] = 0; - base = strdup(buf + 5); - len = packet_read_line(0, buf, sizeof(buf)); - } - if (len) - return nak("expected flush"); - - packet_write(1, "ACK\n"); - packet_flush(1); - - ac = 0; - av[ac++] = "tar-tree"; - av[ac++] = hex; - if (base) - av[ac++] = base; - av[ac++] = NULL; - execv_git_cmd(av); - /* should it return that is an error */ - return 1; -} diff --git a/verify-pack.c b/builtin-verify-pack.c index 357970da39..7d39d9bcd1 100644 --- a/verify-pack.c +++ b/builtin-verify-pack.c @@ -1,3 +1,4 @@ +#include "builtin.h" #include "cache.h" #include "pack.h" @@ -47,28 +48,28 @@ static int verify_one_pack(const char *path, int verbose) static const char verify_pack_usage[] = "git-verify-pack [-v] <pack>..."; -int main(int ac, char **av) +int cmd_verify_pack(int argc, const char **argv, const char *prefix) { int err = 0; int verbose = 0; int no_more_options = 0; int nothing_done = 1; - while (1 < ac) { - if (!no_more_options && av[1][0] == '-') { - if (!strcmp("-v", av[1])) + while (1 < argc) { + if (!no_more_options && argv[1][0] == '-') { + if (!strcmp("-v", argv[1])) verbose = 1; - else if (!strcmp("--", av[1])) + else if (!strcmp("--", argv[1])) no_more_options = 1; else usage(verify_pack_usage); } else { - if (verify_one_pack(av[1], verbose)) + if (verify_one_pack(argv[1], verbose)) err = 1; nothing_done = 0; } - ac--; av++; + argc--; argv++; } if (nothing_done) diff --git a/builtin-write-tree.c b/builtin-write-tree.c index ca06149f18..50670dc7bf 100644 --- a/builtin-write-tree.c +++ b/builtin-write-tree.c @@ -50,10 +50,10 @@ int write_tree(unsigned char *sha1, int missing_ok, const char *prefix) if (prefix) { struct cache_tree *subtree = cache_tree_find(active_cache_tree, prefix); - memcpy(sha1, subtree->sha1, 20); + hashcpy(sha1, subtree->sha1); } else - memcpy(sha1, active_cache_tree->sha1, 20); + hashcpy(sha1, active_cache_tree->sha1); rollback_lock_file(lock_file); @@ -1,64 +1,66 @@ #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[]; extern void help_unknown_cmd(const char *cmd); +extern int mailinfo(FILE *in, FILE *out, int ks, const char *encoding, const char *msg, const char *patch); +extern int split_mbox(const char **mbox, const char *dir, int allow_bare, int nr_prec, int skip); +extern void stripspace(FILE *in, FILE *out); +extern int write_tree(unsigned char *sha1, int missing_ok, const char *prefix); -extern int cmd_help(int argc, const char **argv, const char *prefix); -extern int cmd_version(int argc, const char **argv, const char *prefix); - -extern int cmd_whatchanged(int argc, const char **argv, const char *prefix); -extern int cmd_show(int argc, const char **argv, const char *prefix); -extern int cmd_log(int argc, const char **argv, const char *prefix); -extern int cmd_diff(int argc, const char **argv, const char *prefix); -extern int cmd_format_patch(int argc, const char **argv, const char *prefix); -extern int cmd_count_objects(int argc, const char **argv, const char *prefix); - -extern int cmd_prune(int argc, const char **argv, const char *prefix); -extern int cmd_prune_packed(int argc, const char **argv, const char *prefix); - -extern int cmd_push(int argc, const char **argv, const char *prefix); -extern int cmd_grep(int argc, const char **argv, const char *prefix); -extern int cmd_rm(int argc, const char **argv, const char *prefix); extern int cmd_add(int argc, const char **argv, const char *prefix); -extern int cmd_rev_list(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); -extern int cmd_init_db(int argc, const char **argv, const char *prefix); -extern int cmd_tar_tree(int argc, const char **argv, const char *prefix); -extern int cmd_upload_tar(int argc, const char **argv, const char *prefix); -extern int cmd_get_tar_commit_id(int argc, const char **argv, const char *prefix); -extern int cmd_ls_files(int argc, const char **argv, const char *prefix); -extern int cmd_ls_tree(int argc, const char **argv, const char *prefix); -extern int cmd_read_tree(int argc, const char **argv, const char *prefix); extern int cmd_commit_tree(int argc, const char **argv, const char *prefix); -extern int cmd_apply(int argc, const char **argv, const char *prefix); -extern int cmd_show_branch(int argc, const char **argv, const char *prefix); +extern int cmd_count_objects(int argc, const char **argv, const char *prefix); extern int cmd_diff_files(int argc, const char **argv, const char *prefix); extern int cmd_diff_index(int argc, const char **argv, const char *prefix); +extern int cmd_diff(int argc, const char **argv, const char *prefix); extern int cmd_diff_stages(int argc, const char **argv, const char *prefix); extern int cmd_diff_tree(int argc, const char **argv, const char *prefix); -extern int cmd_cat_file(int argc, const char **argv, const char *prefix); -extern int cmd_rev_parse(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_fmt_merge_msg(int argc, const char **argv, const char *prefix); +extern int cmd_format_patch(int argc, const char **argv, const char *prefix); +extern int cmd_get_tar_commit_id(int argc, const char **argv, const char *prefix); +extern int cmd_grep(int argc, const char **argv, const char *prefix); +extern int cmd_help(int argc, const char **argv, const char *prefix); +extern int cmd_init_db(int argc, const char **argv, const char *prefix); +extern int cmd_log(int argc, const char **argv, const char *prefix); +extern int cmd_ls_files(int argc, const char **argv, const char *prefix); +extern int cmd_ls_tree(int argc, const char **argv, const char *prefix); +extern int cmd_mailinfo(int argc, const char **argv, const char *prefix); +extern int cmd_mailsplit(int argc, const char **argv, const char *prefix); extern int cmd_mv(int argc, const char **argv, const char *prefix); +extern int cmd_name_rev(int argc, const char **argv, const char *prefix); +extern int cmd_pack_objects(int argc, const char **argv, const char *prefix); +extern int cmd_prune(int argc, const char **argv, const char *prefix); +extern int cmd_prune_packed(int argc, const char **argv, const char *prefix); +extern int cmd_push(int argc, const char **argv, const char *prefix); +extern int cmd_read_tree(int argc, const char **argv, const char *prefix); 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); +extern int cmd_symbolic_ref(int argc, const char **argv, const char *prefix); +extern int cmd_tar_tree(int argc, const char **argv, const char *prefix); +extern int cmd_unpack_objects(int argc, const char **argv, const char *prefix); +extern int cmd_update_index(int argc, const char **argv, const char *prefix); +extern int cmd_update_ref(int argc, const char **argv, const char *prefix); +extern int cmd_upload_archive(int argc, const char **argv, const char *prefix); +extern int cmd_upload_tar(int argc, const char **argv, const char *prefix); +extern int cmd_version(int argc, const char **argv, const char *prefix); +extern int cmd_whatchanged(int argc, const char **argv, const char *prefix); extern int cmd_write_tree(int argc, const char **argv, const char *prefix); -extern int write_tree(unsigned char *sha1, int missing_ok, const char *prefix); +extern int cmd_verify_pack(int argc, const char **argv, const char *prefix); -extern int cmd_mailsplit(int argc, const char **argv, const char *prefix); -extern int split_mbox(const char **mbox, const char *dir, int allow_bare, int nr_prec, int skip); - -extern int cmd_mailinfo(int argc, const char **argv, const char *prefix); -extern int mailinfo(FILE *in, FILE *out, int ks, const char *encoding, const char *msg, const char *patch); - -extern int cmd_stripspace(int argc, const char **argv, const char *prefix); -extern void stripspace(FILE *in, FILE *out); #endif diff --git a/cache-tree.c b/cache-tree.c index d9f7e1e3dd..d388848dd2 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 @@ -344,12 +344,8 @@ static int update_one(struct cache_tree *it, #endif } - if (dryrun) { - unsigned char hdr[200]; - int hdrlen; - write_sha1_file_prepare(buffer, offset, tree_type, it->sha1, - hdr, &hdrlen); - } + if (dryrun) + hash_sha1_file(buffer, offset, tree_type, it->sha1); else write_sha1_file(buffer, offset, tree_type, it->sha1); free(buffer); @@ -412,7 +408,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 +474,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; } @@ -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); @@ -227,27 +245,41 @@ char *enter_repo(char *path, int strict); extern int sha1_object_info(const unsigned char *, char *, unsigned long *); extern void * unpack_sha1_file(void *map, unsigned long mapsize, char *type, unsigned long *size); extern void * read_sha1_file(const unsigned char *sha1, char *type, unsigned long *size); +extern int hash_sha1_file(void *buf, unsigned long len, const char *type, unsigned char *sha1); extern int write_sha1_file(void *buf, unsigned long len, const char *type, unsigned char *return_sha1); -extern char *write_sha1_file_prepare(void *buf, - unsigned long len, - const char *type, - unsigned char *sha1, - unsigned char *hdr, - int *hdrlen); extern int check_sha1_signature(const unsigned char *sha1, void *buf, unsigned long size, const char *type); extern int write_sha1_from_fd(const unsigned char *sha1, int fd, char *buffer, size_t bufsize, size_t *bufposn); extern int write_sha1_to_fd(int fd, const unsigned char *sha1); -extern int move_temp_to_file(const char *tmpfile, char *filename); +extern int move_temp_to_file(const char *tmpfile, const char *filename); -extern int has_sha1_pack(const unsigned char *sha1); +extern int has_sha1_pack(const unsigned char *sha1, const char **ignore); extern int has_sha1_file(const unsigned char *sha1); +extern void *map_sha1_file(const unsigned char *sha1, unsigned long *); +extern int legacy_loose_object(unsigned char *); 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 @@ -268,7 +300,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); @@ -329,7 +361,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, @@ -353,9 +385,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); @@ -378,6 +411,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); @@ -403,4 +438,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 0000000000..d6a08b4a55 --- /dev/null +++ b/check-racy.c @@ -0,0 +1,28 @@ +#include "cache.h" + +int main(int ac, char **av) +{ + int i; + int dirty, clean, racy; + + dirty = clean = racy = 0; + read_cache(); + for (i = 0; i < active_nr; i++) { + struct cache_entry *ce = active_cache[i]; + struct stat st; + + if (lstat(ce->name, &st)) { + error("lstat(%s): %s", ce->name, strerror(errno)); + continue; + } + + if (ce_match_stat(ce, &st, 0)) + dirty++; + else if (ce_match_stat(ce, &st, 2)) + racy++; + else + clean++; + } + printf("dirty %d, clean %d, racy %d\n", dirty, clean, racy); + return 0; +} diff --git a/color.c b/color.c new file mode 100644 index 0000000000..d8c8399d59 --- /dev/null +++ b/color.c @@ -0,0 +1,176 @@ +#include "color.h" +#include "cache.h" +#include "git-compat-util.h" + +#include <stdarg.h> + +#define COLOR_RESET "\033[m" + +static int parse_color(const char *name, int len) +{ + static const char * const color_names[] = { + "normal", "black", "red", "green", "yellow", + "blue", "magenta", "cyan", "white" + }; + char *end; + int i; + for (i = 0; i < ARRAY_SIZE(color_names); i++) { + const char *str = color_names[i]; + if (!strncasecmp(name, str, len) && !str[len]) + return i - 1; + } + i = strtol(name, &end, 10); + if (*name && !*end && i >= -1 && i <= 255) + return i; + return -2; +} + +static int parse_attr(const char *name, int len) +{ + static const int attr_values[] = { 1, 2, 4, 5, 7 }; + static const char * const attr_names[] = { + "bold", "dim", "ul", "blink", "reverse" + }; + int i; + for (i = 0; i < ARRAY_SIZE(attr_names); i++) { + const char *str = attr_names[i]; + if (!strncasecmp(name, str, len) && !str[len]) + return attr_values[i]; + } + return -1; +} + +void color_parse(const char *value, const char *var, char *dst) +{ + const char *ptr = value; + int attr = -1; + int fg = -2; + int bg = -2; + + if (!strcasecmp(value, "reset")) { + strcpy(dst, "\033[m"); + return; + } + + /* [fg [bg]] [attr] */ + while (*ptr) { + const char *word = ptr; + int val, len = 0; + + while (word[len] && !isspace(word[len])) + len++; + + ptr = word + len; + while (*ptr && isspace(*ptr)) + ptr++; + + val = parse_color(word, len); + if (val >= -1) { + if (fg == -2) { + fg = val; + continue; + } + if (bg == -2) { + bg = val; + continue; + } + goto bad; + } + val = parse_attr(word, len); + if (val < 0 || attr != -1) + goto bad; + attr = val; + } + + if (attr >= 0 || fg >= 0 || bg >= 0) { + int sep = 0; + + *dst++ = '\033'; + *dst++ = '['; + if (attr >= 0) { + *dst++ = '0' + attr; + sep++; + } + if (fg >= 0) { + if (sep++) + *dst++ = ';'; + if (fg < 8) { + *dst++ = '3'; + *dst++ = '0' + fg; + } else { + dst += sprintf(dst, "38;5;%d", fg); + } + } + if (bg >= 0) { + if (sep++) + *dst++ = ';'; + if (bg < 8) { + *dst++ = '4'; + *dst++ = '0' + bg; + } else { + dst += sprintf(dst, "48;5;%d", bg); + } + } + *dst++ = 'm'; + } + *dst = 0; + return; +bad: + die("bad config value '%s' for variable '%s'", value, var); +} + +int git_config_colorbool(const char *var, const char *value) +{ + if (!value) + return 1; + if (!strcasecmp(value, "auto")) { + if (isatty(1) || (pager_in_use && pager_use_color)) { + char *term = getenv("TERM"); + if (term && strcmp(term, "dumb")) + return 1; + } + return 0; + } + if (!strcasecmp(value, "never")) + return 0; + if (!strcasecmp(value, "always")) + return 1; + return git_config_bool(var, value); +} + +static int color_vprintf(const char *color, const char *fmt, + va_list args, const char *trail) +{ + int r = 0; + + if (*color) + r += printf("%s", color); + r += vprintf(fmt, args); + if (*color) + r += printf("%s", COLOR_RESET); + if (trail) + r += printf("%s", trail); + return r; +} + + + +int color_printf(const char *color, const char *fmt, ...) +{ + va_list args; + int r; + va_start(args, fmt); + r = color_vprintf(color, fmt, args, NULL); + va_end(args); + return r; +} + +int color_printf_ln(const char *color, const char *fmt, ...) +{ + va_list args; + int r; + va_start(args, fmt); + r = color_vprintf(color, fmt, args, "\n"); + va_end(args); + return r; +} diff --git a/color.h b/color.h new file mode 100644 index 0000000000..88bb8ff1bd --- /dev/null +++ b/color.h @@ -0,0 +1,12 @@ +#ifndef COLOR_H +#define COLOR_H + +/* "\033[1;38;5;2xx;48;5;2xxm\0" is 23 bytes */ +#define COLOR_MAXLEN 24 + +int git_config_colorbool(const char *var, const char *value); +void color_parse(const char *var, const char *value, char *dst); +int color_printf(const char *color, const char *fmt, ...); +int color_printf_ln(const char *color, const char *fmt, ...); + +#endif /* COLOR_H */ diff --git a/combine-diff.c b/combine-diff.c index ba8baca0ab..46d9121baf 100644 --- a/combine-diff.c +++ b/combine-diff.c @@ -7,13 +7,6 @@ #include "xdiff-interface.h" #include "log-tree.h" -static int uninteresting(struct diff_filepair *p) -{ - if (diff_unmodified_pair(p)) - return 1; - return 0; -} - static struct combine_diff_path *intersect_paths(struct combine_diff_path *curr, int n, int num_parent) { struct diff_queue_struct *q = &diff_queued_diff; @@ -25,7 +18,7 @@ static struct combine_diff_path *intersect_paths(struct combine_diff_path *curr, for (i = 0; i < q->nr; i++) { int len; const char *path; - if (uninteresting(q->queue[i])) + if (diff_unmodified_pair(q->queue[i])) continue; path = q->queue[i]->two->path; len = strlen(path); @@ -38,9 +31,9 @@ static struct combine_diff_path *intersect_paths(struct combine_diff_path *curr, memset(p->parent, 0, sizeof(p->parent[0]) * num_parent); - memcpy(p->sha1, q->queue[i]->two->sha1, 20); + hashcpy(p->sha1, q->queue[i]->two->sha1); p->mode = q->queue[i]->two->mode; - memcpy(p->parent[n].sha1, q->queue[i]->one->sha1, 20); + hashcpy(p->parent[n].sha1, q->queue[i]->one->sha1); p->parent[n].mode = q->queue[i]->one->mode; p->parent[n].status = q->queue[i]->status; *tail = p; @@ -57,14 +50,13 @@ static struct combine_diff_path *intersect_paths(struct combine_diff_path *curr, const char *path; int len; - if (uninteresting(q->queue[i])) + if (diff_unmodified_pair(q->queue[i])) continue; path = q->queue[i]->two->path; len = strlen(path); if (len == p->len && !memcmp(path, p->path, len)) { found = 1; - memcpy(p->parent[n].sha1, - q->queue[i]->one->sha1, 20); + hashcpy(p->parent[n].sha1, q->queue[i]->one->sha1); p->parent[n].mode = q->queue[i]->one->mode; p->parent[n].status = q->queue[i]->status; break; @@ -101,7 +93,7 @@ static char *grab_blob(const unsigned char *sha1, unsigned long *size) { char *blob; char type[20]; - if (!memcmp(sha1, null_sha1, 20)) { + if (is_null_sha1(sha1)) { /* deleted blob */ *size = 0; return xcalloc(1, 1); @@ -609,16 +601,16 @@ static void dump_quoted_path(const char *prefix, const char *path, printf("%s\n", c_reset); } -static int show_patch_diff(struct combine_diff_path *elem, int num_parent, - int dense, struct rev_info *rev) +static void show_patch_diff(struct combine_diff_path *elem, int num_parent, + int dense, struct rev_info *rev) { struct diff_options *opt = &rev->diffopt; unsigned long result_size, cnt, lno; char *result, *cp; struct sline *sline; /* survived lines */ int mode_differs = 0; - int i, show_hunks, shown_header = 0; - int working_tree_file = !memcmp(elem->sha1, null_sha1, 20); + int i, show_hunks; + int working_tree_file = is_null_sha1(elem->sha1); int abbrev = opt->full_index ? 40 : DEFAULT_ABBREV; mmfile_t result_file; @@ -695,8 +687,8 @@ static int show_patch_diff(struct combine_diff_path *elem, int num_parent, for (i = 0; i < num_parent; i++) { int j; for (j = 0; j < i; j++) { - if (!memcmp(elem->parent[i].sha1, - elem->parent[j].sha1, 20)) { + if (!hashcmp(elem->parent[i].sha1, + elem->parent[j].sha1)) { reuse_combine_diff(sline, cnt, i, j); break; } @@ -769,7 +761,6 @@ static int show_patch_diff(struct combine_diff_path *elem, int num_parent, } free(sline[0].p_lno); free(sline); - return shown_header; } #define COLONS "::::::::::::::::::::::::::::::::" @@ -837,11 +828,10 @@ void show_combined_diff(struct combine_diff_path *p, return; if (opt->output_format & (DIFF_FORMAT_RAW | DIFF_FORMAT_NAME | - DIFF_FORMAT_NAME_STATUS)) { + DIFF_FORMAT_NAME_STATUS)) show_raw_diff(p, num_parent, rev); - } else if (opt->output_format & DIFF_FORMAT_PATCH) { + else if (opt->output_format & DIFF_FORMAT_PATCH) show_patch_diff(p, num_parent, dense, rev); - } } void diff_tree_combined(const unsigned char *sha1, @@ -936,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); } @@ -7,15 +7,15 @@ int save_commit_buffer = 1; struct sort_node { /* - * the number of children of the associated commit - * that also occur in the list being sorted. - */ + * the number of children of the associated commit + * that also occur in the list being sorted. + */ unsigned int indegree; /* - * reference to original list item that we will re-use - * on output. - */ + * reference to original list item that we will re-use + * on output. + */ struct commit_list * list_item; }; @@ -123,7 +123,7 @@ static int commit_graft_pos(const unsigned char *sha1) while (lo < hi) { int mi = (lo + hi) / 2; struct commit_graft *graft = commit_graft[mi]; - int cmp = memcmp(sha1, graft->sha1, 20); + int cmp = hashcmp(sha1, graft->sha1); if (!cmp) return mi; if (cmp < 0) @@ -467,7 +467,8 @@ static int add_rfc2047(char *buf, const char *line, int len) return bp - buf; } -static int add_user_info(const char *what, enum cmit_fmt fmt, char *buf, const char *line) +static int add_user_info(const char *what, enum cmit_fmt fmt, char *buf, + const char *line, int relative_date) { char *date; int namelen; @@ -507,14 +508,16 @@ static int add_user_info(const char *what, enum cmit_fmt fmt, char *buf, const c } switch (fmt) { case CMIT_FMT_MEDIUM: - ret += sprintf(buf + ret, "Date: %s\n", show_date(time, tz)); + ret += sprintf(buf + ret, "Date: %s\n", + show_date(time, tz, relative_date)); break; case CMIT_FMT_EMAIL: ret += sprintf(buf + ret, "Date: %s\n", show_rfc2822_date(time, tz)); break; case CMIT_FMT_FULLER: - ret += sprintf(buf + ret, "%sDate: %s\n", what, show_date(time, tz)); + ret += sprintf(buf + ret, "%sDate: %s\n", what, + show_date(time, tz, relative_date)); break; default: /* notin' */ @@ -545,10 +548,13 @@ static int add_merge_info(enum cmit_fmt fmt, char *buf, const struct commit *com while (parent) { struct commit *p = parent->item; - const char *hex = abbrev - ? find_unique_abbrev(p->object.sha1, abbrev) - : sha1_to_hex(p->object.sha1); - const char *dots = (abbrev && strlen(hex) != 40) ? "..." : ""; + const char *hex = NULL; + const char *dots; + if (abbrev) + hex = find_unique_abbrev(p->object.sha1, abbrev); + if (!hex) + hex = sha1_to_hex(p->object.sha1); + dots = (abbrev && strlen(hex) != 40) ? "..." : ""; parent = parent->next; offset += sprintf(buf + offset, " %s%s", hex, dots); @@ -557,7 +563,10 @@ static int add_merge_info(enum cmit_fmt fmt, char *buf, const struct commit *com return offset; } -unsigned long pretty_print_commit(enum cmit_fmt fmt, const struct commit *commit, unsigned long len, char *buf, unsigned long space, int abbrev, const char *subject, const char *after_subject) +unsigned long pretty_print_commit(enum cmit_fmt fmt, const struct commit *commit, + unsigned long len, char *buf, unsigned long space, + int abbrev, const char *subject, + const char *after_subject, int relative_date) { int hdr = 1, body = 0; unsigned long offset = 0; @@ -646,12 +655,14 @@ unsigned long pretty_print_commit(enum cmit_fmt fmt, const struct commit *commit if (!memcmp(line, "author ", 7)) offset += add_user_info("Author", fmt, buf + offset, - line + 7); + line + 7, + relative_date); if (!memcmp(line, "committer ", 10) && (fmt == CMIT_FMT_FULL || fmt == CMIT_FMT_FULLER)) offset += add_user_info("Commit", fmt, buf + offset, - line + 10); + line + 10, + relative_date); continue; } @@ -727,10 +738,10 @@ struct commit *pop_commit(struct commit_list **stack) int count_parents(struct commit * commit) { - int count = 0; + int count; struct commit_list * parents = commit->parents; - for (count=0;parents; parents=parents->next,count++) - ; + for (count = 0; parents; parents = parents->next,count++) + ; return count; } @@ -52,7 +52,7 @@ enum cmit_fmt { }; extern enum cmit_fmt get_commit_format(const char *arg); -extern unsigned long pretty_print_commit(enum cmit_fmt fmt, const struct commit *, unsigned long len, char *buf, unsigned long space, int abbrev, const char *subject, const char *after_subject); +extern unsigned long pretty_print_commit(enum cmit_fmt fmt, const struct commit *, unsigned long len, char *buf, unsigned long space, int abbrev, const char *subject, const char *after_subject, int relative_date); /** Removes the first commit from a list sorted by date, and adds all * of its parents. diff --git a/compat/inet_pton.c b/compat/inet_pton.c new file mode 100644 index 0000000000..5704e0d2b6 --- /dev/null +++ b/compat/inet_pton.c @@ -0,0 +1,220 @@ +/* + * Copyright (C) 1996-2001 Internet Software Consortium. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM + * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL + * INTERNET SOFTWARE CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING + * FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, + * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION + * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <errno.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <stdio.h> +#include <string.h> + +#ifndef NS_INT16SZ +#define NS_INT16SZ 2 +#endif + +#ifndef NS_INADDRSZ +#define NS_INADDRSZ 4 +#endif + +#ifndef NS_IN6ADDRSZ +#define NS_IN6ADDRSZ 16 +#endif + +/* + * WARNING: Don't even consider trying to compile this on a system where + * sizeof(int) < 4. sizeof(int) > 4 is fine; all the world's not a VAX. + */ + +static int inet_pton4(const char *src, unsigned char *dst); +static int inet_pton6(const char *src, unsigned char *dst); + +/* int + * inet_pton4(src, dst) + * like inet_aton() but without all the hexadecimal and shorthand. + * return: + * 1 if `src' is a valid dotted quad, else 0. + * notice: + * does not touch `dst' unless it's returning 1. + * author: + * Paul Vixie, 1996. + */ +static int +inet_pton4(const char *src, unsigned char *dst) +{ + static const char digits[] = "0123456789"; + int saw_digit, octets, ch; + unsigned char tmp[NS_INADDRSZ], *tp; + + saw_digit = 0; + octets = 0; + *(tp = tmp) = 0; + while ((ch = *src++) != '\0') { + const char *pch; + + if ((pch = strchr(digits, ch)) != NULL) { + unsigned int new = *tp * 10 + (pch - digits); + + if (new > 255) + return (0); + *tp = new; + if (! saw_digit) { + if (++octets > 4) + return (0); + saw_digit = 1; + } + } else if (ch == '.' && saw_digit) { + if (octets == 4) + return (0); + *++tp = 0; + saw_digit = 0; + } else + return (0); + } + if (octets < 4) + return (0); + memcpy(dst, tmp, NS_INADDRSZ); + return (1); +} + +/* int + * inet_pton6(src, dst) + * convert presentation level address to network order binary form. + * return: + * 1 if `src' is a valid [RFC1884 2.2] address, else 0. + * notice: + * (1) does not touch `dst' unless it's returning 1. + * (2) :: in a full address is silently ignored. + * credit: + * inspired by Mark Andrews. + * author: + * Paul Vixie, 1996. + */ + +#ifndef NO_IPV6 +static int +inet_pton6(const char *src, unsigned char *dst) +{ + static const char xdigits_l[] = "0123456789abcdef", + xdigits_u[] = "0123456789ABCDEF"; + unsigned char tmp[NS_IN6ADDRSZ], *tp, *endp, *colonp; + const char *xdigits, *curtok; + int ch, saw_xdigit; + unsigned int val; + + memset((tp = tmp), '\0', NS_IN6ADDRSZ); + endp = tp + NS_IN6ADDRSZ; + colonp = NULL; + /* Leading :: requires some special handling. */ + if (*src == ':') + if (*++src != ':') + return (0); + curtok = src; + saw_xdigit = 0; + val = 0; + while ((ch = *src++) != '\0') { + const char *pch; + + if ((pch = strchr((xdigits = xdigits_l), ch)) == NULL) + pch = strchr((xdigits = xdigits_u), ch); + if (pch != NULL) { + val <<= 4; + val |= (pch - xdigits); + if (val > 0xffff) + return (0); + saw_xdigit = 1; + continue; + } + if (ch == ':') { + curtok = src; + if (!saw_xdigit) { + if (colonp) + return (0); + colonp = tp; + continue; + } + if (tp + NS_INT16SZ > endp) + return (0); + *tp++ = (unsigned char) (val >> 8) & 0xff; + *tp++ = (unsigned char) val & 0xff; + saw_xdigit = 0; + val = 0; + continue; + } + if (ch == '.' && ((tp + NS_INADDRSZ) <= endp) && + inet_pton4(curtok, tp) > 0) { + tp += NS_INADDRSZ; + saw_xdigit = 0; + break; /* '\0' was seen by inet_pton4(). */ + } + return (0); + } + if (saw_xdigit) { + if (tp + NS_INT16SZ > endp) + return (0); + *tp++ = (unsigned char) (val >> 8) & 0xff; + *tp++ = (unsigned char) val & 0xff; + } + if (colonp != NULL) { + /* + * Since some memmove()'s erroneously fail to handle + * overlapping regions, we'll do the shift by hand. + */ + const int n = tp - colonp; + int i; + + for (i = 1; i <= n; i++) { + endp[- i] = colonp[n - i]; + colonp[n - i] = 0; + } + tp = endp; + } + if (tp != endp) + return (0); + memcpy(dst, tmp, NS_IN6ADDRSZ); + return (1); +} +#endif + +/* int + * isc_net_pton(af, src, dst) + * convert from presentation format (which usually means ASCII printable) + * to network format (which is usually some kind of binary format). + * return: + * 1 if the address was valid for the specified address family + * 0 if the address wasn't valid (`dst' is untouched in this case) + * -1 if some other error occurred (`dst' is untouched in this case, too) + * author: + * Paul Vixie, 1996. + */ +int +inet_pton(int af, const char *src, void *dst) +{ + switch (af) { + case AF_INET: + return (inet_pton4(src, dst)); +#ifndef NO_IPV6 + case AF_INET6: + return (inet_pton6(src, dst)); +#endif + default: + errno = EAFNOSUPPORT; + return (-1); + } + /* NOTREACHED */ +} @@ -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 04f508ab90..1cafa19ed4 100644 --- a/config.mak.in +++ b/config.mak.in @@ -2,6 +2,7 @@ # @configure_input@ CC = @CC@ +CFLAGS = @CFLAGS@ AR = @AR@ TAR = @TAR@ #INSTALL = @INSTALL@ # needs install-sh or install.sh in sources @@ -22,3 +23,20 @@ VPATH = @srcdir@ export exec_prefix mandir export srcdir VPATH +NO_PYTHON=@NO_PYTHON@ +NEEDS_SSL_WITH_CRYPTO=@NEEDS_SSL_WITH_CRYPTO@ +NO_OPENSSL=@NO_OPENSSL@ +NO_CURL=@NO_CURL@ +NO_EXPAT=@NO_EXPAT@ +NEEDS_LIBICONV=@NEEDS_LIBICONV@ +NEEDS_SOCKET=@NEEDS_SOCKET@ +NO_D_INO_IN_DIRENT=@NO_D_INO_IN_DIRENT@ +NO_D_TYPE_IN_DIRENT=@NO_D_TYPE_IN_DIRENT@ +NO_SOCKADDR_STORAGE=@NO_SOCKADDR_STORAGE@ +NO_IPV6=@NO_IPV6@ +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 a9c88c6a4d..cff5722eb9 100644 --- a/configure.ac +++ b/configure.ac @@ -2,7 +2,7 @@ # Process this file with autoconf to produce a configure script. AC_PREREQ(2.59) -AC_INIT([git], [1.4.2], [git@vger.kernel.org]) +AC_INIT([git], [@@GIT_VERSION@@], [git@vger.kernel.org]) AC_CONFIG_SRCDIR([git.c]) @@ -19,17 +19,96 @@ echo "# ${config_append}. Generated by configure." > "${config_append}" # Append LINE to file ${config_append} AC_DEFUN([GIT_CONF_APPEND_LINE], [echo "$1" >> "${config_append}"])# GIT_CONF_APPEND_LINE +# +# GIT_ARG_SET_PATH(PROGRAM) +# ------------------------- +# Provide --with-PROGRAM=PATH option to set PATH to PROGRAM +AC_DEFUN([GIT_ARG_SET_PATH], +[AC_ARG_WITH([$1], + [AS_HELP_STRING([--with-$1=PATH], + [provide PATH to $1])], + [GIT_CONF_APPEND_PATH($1)],[]) +])# GIT_ARG_SET_PATH +# +# GIT_CONF_APPEND_PATH(PROGRAM) +# ------------------------------ +# Parse --with-PROGRAM=PATH option to set PROGRAM_PATH=PATH +# Used by GIT_ARG_SET_PATH(PROGRAM) +AC_DEFUN([GIT_CONF_APPEND_PATH], +[PROGRAM=m4_toupper($1); \ +if test "$withval" = "no"; then \ + AC_MSG_ERROR([You cannot use git without $1]); \ +else \ + if test "$withval" = "yes"; then \ + AC_MSG_WARN([You should provide path for --with-$1=PATH]); \ + else \ + GIT_CONF_APPEND_LINE(${PROGRAM}_PATH=$withval); \ + fi; \ +fi; \ +]) # GIT_CONF_APPEND_PATH +# +# GIT_PARSE_WITH(PACKAGE) +# ----------------------- +# For use in AC_ARG_WITH action-if-found, for packages default ON. +# * Set NO_PACKAGE=YesPlease for --without-PACKAGE +# * Set PACKAGEDIR=PATH for --with-PACKAGE=PATH +# * Unset NO_PACKAGE for --with-PACKAGE without ARG +AC_DEFUN([GIT_PARSE_WITH], +[PACKAGE=m4_toupper($1); \ +if test "$withval" = "no"; then \ + m4_toupper(NO_$1)=YesPlease; \ +elif test "$withval" = "yes"; then \ + m4_toupper(NO_$1)=; \ +else \ + m4_toupper(NO_$1)=; \ + GIT_CONF_APPEND_LINE(${PACKAGE}DIR=$withval); \ +fi \ +])# GIT_PARSE_WITH + + +## Site configuration related to programs (before tests) +## --with-PACKAGE[=ARG] and --without-PACKAGE +# +# Define SHELL_PATH to provide path to shell. +GIT_ARG_SET_PATH(shell) +# +# Define PERL_PATH to provide path to Perl. +GIT_ARG_SET_PATH(perl) +# +# 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])], + [if test "$withval" = "no"; then \ + NO_PYTHON=YesPlease; \ + elif test "$withval" = "yes"; then \ + NO_PYTHON=; \ + else \ + NO_PYTHON=; \ + PYTHON_PATH=$withval; \ + fi; \ + ]) +AC_SUBST(NO_PYTHON) +AC_SUBST(PYTHON_PATH) ## Checks for programs. AC_MSG_NOTICE([CHECKS for programs]) # -AC_PROG_CC +AC_PROG_CC([cc gcc]) #AC_PROG_INSTALL # needs install-sh or install.sh in sources AC_CHECK_TOOL(AR, ar, :) AC_CHECK_PROGS(TAR, [gtar tar]) # -# Define NO_PYTHON if you want to lose all benefits of the recursive merge. +# Define PYTHON_PATH to provide path to Python. +if test -z "$NO_PYTHON"; then + if test -z "$PYTHON_PATH"; then + AC_PATH_PROGS(PYTHON_PATH, [python python2.4 python2.3 python2]) + fi + if test -n "$PYTHON_PATH"; then + GIT_CONF_APPEND_LINE([PYTHON_PATH=@PYTHON_PATH@]) + NO_PYTHON="" + fi +fi ## Checks for libraries. @@ -37,32 +116,48 @@ AC_MSG_NOTICE([CHECKS for libraries]) # # Define NO_OPENSSL environment variable if you do not have OpenSSL. # Define NEEDS_SSL_WITH_CRYPTO if you need -lcrypto with -lssl (Darwin). -AC_CHECK_LIB([ssl], [SHA1_Init],[], -[AC_CHECK_LIB([crypto], [SHA1_INIT], - [GIT_CONF_APPEND_LINE(NEEDS_SSL_WITH_CRYPTO=YesPlease)], - [GIT_CONF_APPEND_LINE(NO_OPENSSL=YesPlease)])]) +AC_CHECK_LIB([crypto], [SHA1_Init], +[NEEDS_SSL_WITH_CRYPTO=], +[AC_CHECK_LIB([ssl], [SHA1_Init], + [NEEDS_SSL_WITH_CRYPTO=YesPlease + NEEDS_SSL_WITH_CRYPTO=], + [NO_OPENSSL=YesPlease])]) +AC_SUBST(NEEDS_SSL_WITH_CRYPTO) +AC_SUBST(NO_OPENSSL) # # Define NO_CURL if you do not have curl installed. git-http-pull and # git-http-push are not built, and you cannot use http:// and https:// # transports. -AC_CHECK_LIB([curl], [curl_global_init],[], -[GIT_CONF_APPEND_LINE(NO_CURL=YesPlease)]) +AC_CHECK_LIB([curl], [curl_global_init], +[NO_CURL=], +[NO_CURL=YesPlease]) +AC_SUBST(NO_CURL) # # Define NO_EXPAT if you do not have expat installed. git-http-push is # not built, and you cannot push using http:// and https:// transports. -AC_CHECK_LIB([expat], [XML_ParserCreate],[], -[GIT_CONF_APPEND_LINE(NO_EXPAT=YesPlease)]) +AC_CHECK_LIB([expat], [XML_ParserCreate], +[NO_EXPAT=], +[NO_EXPAT=YesPlease]) +AC_SUBST(NO_EXPAT) # # Define NEEDS_LIBICONV if linking with libc is not enough (Darwin). -AC_CHECK_LIB([c], [iconv],[], -[AC_CHECK_LIB([iconv],[iconv], - [GIT_CONF_APPEND_LINE(NEEDS_LIBICONV=YesPlease)],[])]) +# Define NO_ICONV if neither libc nor libiconv support iconv. +AC_CHECK_LIB([c], [iconv], + [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). -AC_CHECK_LIB([c], [socket],[], -[AC_CHECK_LIB([socket],[socket], - [GIT_CONF_APPEND_LINE(NEEDS_SOCKET=YesPlease)],[])]) +AC_CHECK_LIB([c], [socket], +[NEEDS_SOCKET=], +[NEEDS_SOCKET=YesPlease]) +AC_SUBST(NEEDS_SOCKET) +test -n "$NEEDS_SOCKET" && LIBS="$LIBS -lsocket" ## Checks for header files. @@ -72,21 +167,65 @@ AC_CHECK_LIB([c], [socket],[], AC_MSG_NOTICE([CHECKS for typedefs, structures, and compiler characteristics]) # # Define NO_D_INO_IN_DIRENT if you don't have d_ino in your struct dirent. -AC_CHECK_MEMBER(struct dirent.d_ino,[], -[GIT_CONF_APPEND_LINE(NO_D_INO_IN_DIRENT=YesPlease)], +AC_CHECK_MEMBER(struct dirent.d_ino, +[NO_D_INO_IN_DIRENT=], +[NO_D_INO_IN_DIRENT=YesPlease], [#include <dirent.h>]) +AC_SUBST(NO_D_INO_IN_DIRENT) # # Define NO_D_TYPE_IN_DIRENT if your platform defines DT_UNKNOWN but lacks # d_type in struct dirent (latest Cygwin -- will be fixed soonish). -AC_CHECK_MEMBER(struct dirent.d_type,[], -[GIT_CONF_APPEND_LINE(NO_D_TYPE_IN_DIRENT=YesPlease)], +AC_CHECK_MEMBER(struct dirent.d_type, +[NO_D_TYPE_IN_DIRENT=], +[NO_D_TYPE_IN_DIRENT=YesPlease], [#include <dirent.h>]) +AC_SUBST(NO_D_TYPE_IN_DIRENT) # # Define NO_SOCKADDR_STORAGE if your platform does not have struct # sockaddr_storage. -AC_CHECK_TYPE(struct sockaddr_storage,[], -[GIT_CONF_APPEND_LINE(NO_SOCKADDR_STORAGE=YesPlease)], -[#include <netinet/in.h>]) +AC_CHECK_TYPE(struct sockaddr_storage, +[NO_SOCKADDR_STORAGE=], +[NO_SOCKADDR_STORAGE=YesPlease],[ +#include <sys/types.h> +#include <sys/socket.h> +]) +AC_SUBST(NO_SOCKADDR_STORAGE) +# +# Define NO_IPV6 if you lack IPv6 support and getaddrinfo(). +AC_CHECK_TYPE([struct addrinfo],[ + AC_CHECK_FUNC([getaddrinfo], + [NO_IPV6=], + [NO_IPV6=YesPlease]) +],[NO_IPV6=YesPlease],[ +#include <sys/types.h> +#include <sys/socket.h> +#include <netdb.h> +]) +AC_SUBST(NO_IPV6) +# +# Define NO_C99_FORMAT if your formatted IO functions (printf/scanf et.al.) +# 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], +[# Actually git uses only %z (%zu) in alloc.c, and %t (%td) in mktag.c +AC_RUN_IFELSE( + [AC_LANG_PROGRAM([AC_INCLUDES_DEFAULT], + [[char buf[64]; + if (sprintf(buf, "%lld%hhd%jd%zd%td", (long long int)1, (char)2, (intmax_t)3, (size_t)4, (ptrdiff_t)5) != 5) + exit(1); + else if (strcmp(buf, "12345")) + exit(2);]])], + [ac_cv_c_c99_format=yes], + [ac_cv_c_c99_format=no]) +]) +if test $ac_cv_c_c99_format = no; then + NO_C99_FORMAT=YesPlease +else + NO_C99_FORMAT= +fi +AC_SUBST(NO_C99_FORMAT) ## Checks for library functions. @@ -94,21 +233,25 @@ AC_CHECK_TYPE(struct sockaddr_storage,[], AC_MSG_NOTICE([CHECKS for library functions]) # # Define NO_STRCASESTR if you don't have strcasestr. -AC_CHECK_FUNC(strcasestr,[], -[GIT_CONF_APPEND_LINE(NO_STRCASESTR=YesPlease)]) +AC_CHECK_FUNC(strcasestr, +[NO_STRCASESTR=], +[NO_STRCASESTR=YesPlease]) +AC_SUBST(NO_STRCASESTR) # # Define NO_STRLCPY if you don't have strlcpy. -AC_CHECK_FUNC(strlcpy,[], -[GIT_CONF_APPEND_LINE(NO_STRLCPY=YesPlease)]) +AC_CHECK_FUNC(strlcpy, +[NO_STRLCPY=], +[NO_STRLCPY=YesPlease]) +AC_SUBST(NO_STRLCPY) # # Define NO_SETENV if you don't have setenv in the C library. -AC_CHECK_FUNC(setenv,[], -[GIT_CONF_APPEND_LINE(NO_SETENV=YesPlease)]) +AC_CHECK_FUNC(setenv, +[NO_SETENV=], +[NO_SETENV=YesPlease]) +AC_SUBST(NO_SETENV) # # Define NO_MMAP if you want to avoid mmap. # -# Define NO_IPV6 if you lack IPv6 support and getaddrinfo(). -# # Define NO_ICONV if your libc does not properly support iconv. @@ -120,14 +263,26 @@ AC_CHECK_FUNC(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. -## Site configuration +## Site configuration (override autodetection) ## --with-PACKAGE[=ARG] and --without-PACKAGE -# Define NO_SVN_TESTS if you want to skip time-consuming SVN interopability +AC_MSG_NOTICE([CHECKS for site configuration]) +# +# Define NO_SVN_TESTS if you want to skip time-consuming SVN interoperability # tests. These tests take up a significant amount of the total test time # but are not needed unless you plan to talk to SVN repos. # @@ -145,21 +300,61 @@ AC_CHECK_FUNC(setenv,[], # Define NO_OPENSSL environment variable if you do not have OpenSSL. # This also implies MOZILLA_SHA1. # +# Define OPENSSLDIR=/foo/bar if your openssl header and library files are in +# /foo/bar/include and /foo/bar/lib directories. +AC_ARG_WITH(openssl, +AS_HELP_STRING([--with-openssl],[use OpenSSL library (default is YES)]) +AS_HELP_STRING([], [ARG can be prefix for openssl library and headers]),\ +GIT_PARSE_WITH(openssl)) +# # Define NO_CURL if you do not have curl installed. git-http-pull and # git-http-push are not built, and you cannot use http:// and https:// # transports. # # Define CURLDIR=/foo/bar if your curl header and library files are in # /foo/bar/include and /foo/bar/lib directories. +AC_ARG_WITH(curl, +AS_HELP_STRING([--with-curl],[support http(s):// transports (default is YES)]) +AS_HELP_STRING([], [ARG can be also prefix for curl library and headers]), +GIT_PARSE_WITH(curl)) # # Define NO_EXPAT if you do not have expat installed. git-http-push is # not built, and you cannot push using http:// and https:// transports. # -# Define NO_MMAP if you want to avoid mmap. +# Define EXPATDIR=/foo/bar if your expat header and library files are in +# /foo/bar/include and /foo/bar/lib directories. +AC_ARG_WITH(expat, +AS_HELP_STRING([--with-expat], +[support git-push using http:// and https:// transports via WebDAV (default is YES)]) +AS_HELP_STRING([], [ARG can be also prefix for expat library and headers]), +GIT_PARSE_WITH(expat)) # -# Define NO_PYTHON if you want to loose all benefits of the recursive merge. +# Define NO_FINK if you are building on Darwin/Mac OS X, have Fink +# installed in /sw, but don't want GIT to link against any libraries +# installed there. If defined you may specify your own (or Fink's) +# include directories and library directories by defining CFLAGS +# and LDFLAGS appropriately. # +# Define NO_DARWIN_PORTS if you are building on Darwin/Mac OS X, +# have DarwinPorts installed in /opt/local, but don't want GIT to +# link against any libraries installed there. If defined you may +# specify your own (or DarwinPort's) include directories and +# 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 +# # 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. @@ -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,6 +754,9 @@ int git_connect(int fd[2], char *url, const char *prog) int finish_connect(pid_t pid) { + if (pid == 0) + return 0; + while (waitpid(pid, NULL, 0) < 0) { if (errno != EINTR) return -1; diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash new file mode 100755 index 0000000000..d9cb17d0b2 --- /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/git.el b/contrib/emacs/git.el index 68de9be0c7..5354cd67b3 100644 --- a/contrib/emacs/git.el +++ b/contrib/emacs/git.el @@ -422,8 +422,8 @@ and returns the process output as a string." (propertize (concat " (" (if (eq state 'copy) "copied from " - (if (eq (git-fileinfo->state info) 'added) "renamed to " - "renamed from ")) + (if (eq (git-fileinfo->state info) 'added) "renamed from " + "renamed to ")) (git-escape-file-name (git-fileinfo->orig-name info)) ")") 'face 'git-status-face) ""))) diff --git a/contrib/emacs/vc-git.el b/contrib/emacs/vc-git.el index 3f6ed699f0..4189c4ced0 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")))))))) @@ -119,10 +119,10 @@ (defun vc-git-annotate-command (file buf &optional rev) ; FIXME: rev is ignored (let ((name (file-relative-name file))) - (call-process "git" nil buf nil "annotate" name))) + (call-process "git" nil buf nil "blame" name))) (defun vc-git-annotate-time () - (and (re-search-forward "[0-9a-f]+\t(.*\t\\([0-9]+\\)-\\([0-9]+\\)-\\([0-9]+\\) \\([0-9]+\\):\\([0-9]+\\):\\([0-9]+\\) \\([-+0-9]+\\)\t[0-9]+)" nil t) + (and (re-search-forward "[0-9a-f]+ (.* \\([0-9]+\\)-\\([0-9]+\\)-\\([0-9]+\\) \\([0-9]+\\):\\([0-9]+\\):\\([0-9]+\\) \\([-+0-9]+\\) +[0-9]+)" nil t) (vc-annotate-convert-time (apply #'encode-time (mapcar (lambda (match) (string-to-number (match-string match))) '(6 5 4 3 2 1 7)))))) diff --git a/contrib/gitview/gitview.txt b/contrib/gitview/gitview.txt index 6924df286e..77c29de305 100644 --- a/contrib/gitview/gitview.txt +++ b/contrib/gitview/gitview.txt @@ -7,40 +7,50 @@ gitview - A GTK based repository browser for git SYNOPSIS -------- -'gitview' [options] [args] +'gitview' [options] [args] DESCRIPTION --------- -Dependencies +Dependencies: * Python 2.4 * PyGTK 2.8 or later * PyCairo 1.0 or later OPTIONS ------- - --without-diff - If the user doesn't want to list the commit diffs in the main window. This may speed up the repository browsing. - - <args> - All the valid option for git-rev-list(1) - Key Bindings: - F4: - To maximize the window - F5: - To reread references. - F11: - Full screen - F12: - Leave full screen +------- +--without-diff:: + + If the user doesn't want to list the commit diffs in the main window. + This may speed up the repository browsing. + +<args>:: + + All the valid option for gitlink:git-rev-list[1]. + +Key Bindings +------------ +F4:: + To maximize the window + +F5:: + To reread references. + +F11:: + Full screen + +F12:: + Leave full screen EXAMPLES ------- - gitview v2.6.12.. include/scsi drivers/scsi - Show as the changes since version v2.6.12 that changed any file in the include/scsi - or drivers/scsi subdirectories +-------- + +gitview v2.6.12.. include/scsi drivers/scsi:: + + Show as the changes since version v2.6.12 that changed any file in the + include/scsi or drivers/scsi subdirectories - gitview --since=2.weeks.ago - Show the changes during the last two weeks +gitview --since=2.weeks.ago:: + Show the changes during the last two weeks diff --git a/contrib/vim/README b/contrib/vim/README new file mode 100644 index 0000000000..9e7881fea9 --- /dev/null +++ b/contrib/vim/README @@ -0,0 +1,8 @@ +To syntax highlight git's commit messages, you need to: + 1. Copy syntax/gitcommit.vim to vim's syntax directory: + $ mkdir -p $HOME/.vim/syntax + $ cp syntax/gitcommit.vim $HOME/.vim/syntax + 2. Auto-detect the editing of git commit files: + $ cat >>$HOME/.vimrc <<'EOF' + autocmd BufNewFile,BufRead COMMIT_EDITMSG set filetype=gitcommit + EOF diff --git a/contrib/vim/syntax/gitcommit.vim b/contrib/vim/syntax/gitcommit.vim new file mode 100644 index 0000000000..a9de09fa2f --- /dev/null +++ b/contrib/vim/syntax/gitcommit.vim @@ -0,0 +1,18 @@ +syn region gitLine start=/^#/ end=/$/ +syn region gitCommit start=/^# Updated but not checked in:$/ end=/^#$/ contains=gitHead,gitCommitFile +syn region gitHead contained start=/^# (.*)/ end=/^#$/ +syn region gitChanged start=/^# Changed but not updated:/ end=/^#$/ contains=gitHead,gitChangedFile +syn region gitUntracked start=/^# Untracked files:/ end=/^#$/ contains=gitHead,gitUntrackedFile + +syn match gitCommitFile contained /^#\t.*/hs=s+2 +syn match gitChangedFile contained /^#\t.*/hs=s+2 +syn match gitUntrackedFile contained /^#\t.*/hs=s+2 + +hi def link gitLine Comment +hi def link gitCommit Comment +hi def link gitChanged Comment +hi def link gitHead Comment +hi def link gitUntracked Comment +hi def link gitCommitFile Type +hi def link gitChangedFile Constant +hi def link gitUntrackedFile Constant diff --git a/convert-objects.c b/convert-objects.c index 168771ed85..631678b08a 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 6a7b40fd09..b7174c6c05 100644 --- a/csum-file.c +++ b/csum-file.c @@ -10,7 +10,7 @@ #include "cache.h" #include "csum-file.h" -static int sha1flush(struct sha1file *f, unsigned int count) +static void sha1flush(struct sha1file *f, unsigned int count) { void *buf = f->buffer; @@ -21,7 +21,7 @@ static int sha1flush(struct sha1file *f, unsigned int count) count -= ret; if (count) continue; - return 0; + return; } if (!ret) die("sha1 file '%s' write error. Out of diskspace", f->name); @@ -38,7 +38,7 @@ int sha1close(struct sha1file *f, unsigned char *result, int update) } SHA1_Final(f->buffer, &f->ctx); if (result) - memcpy(result, f->buffer, 20); + hashcpy(result, f->buffer); if (update) sha1flush(f, 20); if (close(f->fd)) @@ -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); } @@ -37,6 +37,16 @@ static const char *weekday_names[] = { "Sundays", "Mondays", "Tuesdays", "Wednesdays", "Thursdays", "Fridays", "Saturdays" }; +static time_t gm_time_t(unsigned long time, int tz) +{ + int minutes; + + minutes = tz < 0 ? -tz : tz; + minutes = (minutes / 100)*60 + (minutes % 100); + minutes = tz < 0 ? -minutes : minutes; + return time + minutes * 60; +} + /* * The "tz" thing is passed in as this strange "decimal parse of tz" * thing, which means that tz -0100 is passed in as the integer -100, @@ -44,21 +54,58 @@ static const char *weekday_names[] = { */ static struct tm *time_to_tm(unsigned long time, int tz) { - time_t t; - int minutes; - - minutes = tz < 0 ? -tz : tz; - minutes = (minutes / 100)*60 + (minutes % 100); - minutes = tz < 0 ? -minutes : minutes; - t = time + minutes * 60; + time_t t = gm_time_t(time, tz); return gmtime(&t); } -const char *show_date(unsigned long time, int tz) +const char *show_date(unsigned long time, int tz, int relative) { struct tm *tm; static char timebuf[200]; + if (relative) { + unsigned long diff; + time_t t = gm_time_t(time, tz); + struct timeval now; + gettimeofday(&now, NULL); + if (now.tv_sec < t) + return "in the future"; + diff = now.tv_sec - t; + if (diff < 90) { + snprintf(timebuf, sizeof(timebuf), "%lu seconds ago", diff); + return timebuf; + } + /* Turn it into minutes */ + diff = (diff + 30) / 60; + if (diff < 90) { + snprintf(timebuf, sizeof(timebuf), "%lu minutes ago", diff); + return timebuf; + } + /* Turn it into hours */ + diff = (diff + 30) / 60; + if (diff < 36) { + snprintf(timebuf, sizeof(timebuf), "%lu hours ago", diff); + return timebuf; + } + /* We deal with number of days from here on */ + diff = (diff + 12) / 24; + if (diff < 14) { + snprintf(timebuf, sizeof(timebuf), "%lu days ago", diff); + return timebuf; + } + /* Say weeks for the past 10 weeks or so */ + if (diff < 70) { + snprintf(timebuf, sizeof(timebuf), "%lu weeks ago", (diff + 3) / 7); + return timebuf; + } + /* Say months for the past 12 months or so */ + if (diff < 360) { + snprintf(timebuf, sizeof(timebuf), "%lu months ago", (diff + 15) / 30); + return timebuf; + } + /* Else fall back on absolute format.. */ + } + tm = time_to_tm(time, tz); if (!tm) return NULL; @@ -209,8 +256,12 @@ static int match_alpha(const char *date, struct tm *tm, int *offset) } if (match_string(date, "PM") == 2) { - if (tm->tm_hour > 0 && tm->tm_hour < 12) - tm->tm_hour += 12; + tm->tm_hour = (tm->tm_hour % 12) + 12; + return 2; + } + + if (match_string(date, "AM") == 2) { + tm->tm_hour = (tm->tm_hour % 12) + 0; return 2; } @@ -551,6 +602,34 @@ static void date_tea(struct tm *tm, int *num) date_time(tm, 17); } +static void date_pm(struct tm *tm, int *num) +{ + int hour, n = *num; + *num = 0; + + hour = tm->tm_hour; + if (n) { + hour = n; + tm->tm_min = 0; + tm->tm_sec = 0; + } + tm->tm_hour = (hour % 12) + 12; +} + +static void date_am(struct tm *tm, int *num) +{ + int hour, n = *num; + *num = 0; + + hour = tm->tm_hour; + if (n) { + hour = n; + tm->tm_min = 0; + tm->tm_sec = 0; + } + tm->tm_hour = (hour % 12); +} + static const struct special { const char *name; void (*fn)(struct tm *, int *); @@ -559,6 +638,8 @@ static const struct special { { "noon", date_noon }, { "midnight", date_midnight }, { "tea", date_tea }, + { "PM", date_pm }, + { "AM", date_am }, { NULL } }; @@ -584,10 +665,10 @@ static const char *approxidate_alpha(const char *date, struct tm *tm, int *num) const struct typelen *tl; const struct special *s; const char *end = date; - int n = 1, i; + int i; - while (isalpha(*++end)) - n++; + while (isalpha(*++end)); + ; for (i = 0; i < 12; i++) { int match = match_string(date, month_names[i]); @@ -665,6 +746,27 @@ static const char *approxidate_alpha(const char *date, struct tm *tm, int *num) return end; } +static const char *approxidate_digit(const char *date, struct tm *tm, int *num) +{ + char *end; + unsigned long number = strtoul(date, &end, 10); + + switch (*end) { + case ':': + case '.': + case '/': + case '-': + if (isdigit(end[1])) { + int match = match_multi_number(number, *end, date, end, tm); + if (match) + return date + match; + } + } + + *num = number; + return end; +} + unsigned long approxidate(const char *date) { int number = 0; @@ -684,9 +786,7 @@ unsigned long approxidate(const char *date) break; date++; if (isdigit(c)) { - char *end; - number = strtoul(date-1, &end, 10); - date = end; + date = approxidate_digit(date-1, &tm, &number); continue; } if (isalpha(c)) diff --git a/describe.c b/describe.c index 324ca8965b..ab192f83ae 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 7da9205a5d..fa16d06c8d 100644 --- a/diff-delta.c +++ b/diff-delta.c @@ -152,7 +152,7 @@ struct delta_index * create_delta_index(const void *buf, unsigned long bufsize) initialization in create_delta(). */ entries = (bufsize - 1) / RABIN_WINDOW; hsize = entries / 4; - for (i = 4; (1 << i) < hsize && i < 31; i++); + for (i = 4; (1u << i) < hsize && i < 31; i++); hsize = 1 << i; hmask = hsize - 1; @@ -392,7 +392,7 @@ create_delta(const struct delta_index *index, outsize = max_size + MAX_OP_SIZE + 1; if (max_size && outpos > max_size) break; - out = realloc(out, outsize); + out = xrealloc(out, outsize); if (!out) { free(tmp); return NULL; diff --git a/diff-lib.c b/diff-lib.c index 116b5a9d68..fc69fb92a5 100644 --- a/diff-lib.c +++ b/diff-lib.c @@ -48,7 +48,7 @@ int run_diff_files(struct rev_info *revs, int silent_on_removed) memcpy(dpath->path, ce->name, path_len); dpath->path[path_len] = '\0'; dpath->mode = 0; - memset(dpath->sha1, 0, 20); + hashclr(dpath->sha1); memset(&(dpath->parent[0]), 0, sizeof(struct combine_diff_parent)*5); @@ -66,8 +66,7 @@ int run_diff_files(struct rev_info *revs, int silent_on_removed) if (2 <= stage) { int mode = ntohl(nce->ce_mode); num_compare_stages++; - memcpy(dpath->parent[stage-2].sha1, - nce->sha1, 20); + hashcpy(dpath->parent[stage-2].sha1, nce->sha1); dpath->parent[stage-2].mode = canon_mode(mode); dpath->parent[stage-2].status = @@ -214,8 +213,33 @@ static int show_modified(struct rev_info *revs, return -1; } + if (revs->combine_merges && !cached && + (hashcmp(sha1, old->sha1) || hashcmp(old->sha1, new->sha1))) { + struct combine_diff_path *p; + int pathlen = ce_namelen(new); + + p = xmalloc(combine_diff_path_size(2, pathlen)); + p->path = (char *) &p->parent[2]; + p->next = NULL; + p->len = pathlen; + memcpy(p->path, new->name, pathlen); + p->path[pathlen] = 0; + p->mode = ntohl(mode); + hashclr(p->sha1); + memset(p->parent, 0, 2 * sizeof(struct combine_diff_parent)); + p->parent[0].status = DIFF_STATUS_MODIFIED; + p->parent[0].mode = ntohl(new->ce_mode); + hashcpy(p->parent[0].sha1, new->sha1); + p->parent[1].status = DIFF_STATUS_MODIFIED; + p->parent[1].mode = ntohl(old->ce_mode); + hashcpy(p->parent[1].sha1, old->sha1); + show_combined_diff(p, 2, revs->dense_combined_merges, revs); + free(p); + return 0; + } + oldmode = old->ce_mode; - if (mode == oldmode && !memcmp(sha1, old->sha1, 20) && + if (mode == oldmode && !hashcmp(sha1, old->sha1) && !revs->diffopt.find_copies_harder) return 0; @@ -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; @@ -358,12 +233,152 @@ static int fill_mmfile(mmfile_t *mf, struct diff_filespec *one) return 0; } +struct diff_words_buffer { + mmfile_t text; + long alloc; + long current; /* output pointer */ + int suppressed_newline; +}; + +static void diff_words_append(char *line, unsigned long len, + struct diff_words_buffer *buffer) +{ + if (buffer->text.size + len > buffer->alloc) { + buffer->alloc = (buffer->text.size + len) * 3 / 2; + buffer->text.ptr = xrealloc(buffer->text.ptr, buffer->alloc); + } + line++; + len--; + memcpy(buffer->text.ptr + buffer->text.size, line, len); + buffer->text.size += len; +} + +struct diff_words_data { + struct xdiff_emit_state xm; + struct diff_words_buffer minus, plus; +}; + +static void print_word(struct diff_words_buffer *buffer, int len, int color, + int suppress_newline) +{ + const char *ptr; + int eol = 0; + + if (len == 0) + return; + + ptr = buffer->text.ptr + buffer->current; + buffer->current += len; + + if (ptr[len - 1] == '\n') { + eol = 1; + len--; + } + + fputs(diff_get_color(1, color), stdout); + fwrite(ptr, len, 1, stdout); + fputs(diff_get_color(1, DIFF_RESET), stdout); + + if (eol) { + if (suppress_newline) + buffer->suppressed_newline = 1; + else + putchar('\n'); + } +} + +static void fn_out_diff_words_aux(void *priv, char *line, unsigned long len) +{ + struct diff_words_data *diff_words = priv; + + if (diff_words->minus.suppressed_newline) { + if (line[0] != '+') + putchar('\n'); + diff_words->minus.suppressed_newline = 0; + } + + len--; + switch (line[0]) { + case '-': + print_word(&diff_words->minus, len, DIFF_FILE_OLD, 1); + break; + case '+': + print_word(&diff_words->plus, len, DIFF_FILE_NEW, 0); + break; + case ' ': + print_word(&diff_words->plus, len, DIFF_PLAIN, 0); + diff_words->minus.current += len; + break; + } +} + +/* this executes the word diff on the accumulated buffers */ +static void diff_words_show(struct diff_words_data *diff_words) +{ + xpparam_t xpp; + xdemitconf_t xecfg; + xdemitcb_t ecb; + mmfile_t minus, plus; + int i; + + minus.size = diff_words->minus.text.size; + minus.ptr = xmalloc(minus.size); + memcpy(minus.ptr, diff_words->minus.text.ptr, minus.size); + for (i = 0; i < minus.size; i++) + if (isspace(minus.ptr[i])) + minus.ptr[i] = '\n'; + diff_words->minus.current = 0; + + plus.size = diff_words->plus.text.size; + plus.ptr = xmalloc(plus.size); + memcpy(plus.ptr, diff_words->plus.text.ptr, plus.size); + for (i = 0; i < plus.size; i++) + if (isspace(plus.ptr[i])) + plus.ptr[i] = '\n'; + diff_words->plus.current = 0; + + xpp.flags = XDF_NEED_MINIMAL; + xecfg.ctxlen = diff_words->minus.alloc + diff_words->plus.alloc; + xecfg.flags = 0; + ecb.outf = xdiff_outf; + ecb.priv = diff_words; + diff_words->xm.consume = fn_out_diff_words_aux; + xdl_diff(&minus, &plus, &xpp, &xecfg, &ecb); + + free(minus.ptr); + free(plus.ptr); + diff_words->minus.text.size = diff_words->plus.text.size = 0; + + if (diff_words->minus.suppressed_newline) { + putchar('\n'); + diff_words->minus.suppressed_newline = 0; + } +} + struct emit_callback { struct xdiff_emit_state xm; int nparents, color_diff; const char **label_path; + struct diff_words_data *diff_words; }; +static void free_diff_words_data(struct emit_callback *ecbdata) +{ + if (ecbdata->diff_words) { + /* flush buffers */ + if (ecbdata->diff_words->minus.text.size || + ecbdata->diff_words->plus.text.size) + diff_words_show(ecbdata->diff_words); + + if (ecbdata->diff_words->minus.text.ptr) + free (ecbdata->diff_words->minus.text.ptr); + if (ecbdata->diff_words->plus.text.ptr) + free (ecbdata->diff_words->plus.text.ptr); + free(ecbdata->diff_words); + ecbdata->diff_words = NULL; + } +} + const char *diff_get_color(int diff_use_color, enum color_diff ix) { if (diff_use_color) @@ -371,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); @@ -391,26 +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; - 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) @@ -499,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; } @@ -514,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) @@ -536,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 && @@ -592,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 { @@ -679,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; @@ -690,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. */ @@ -739,15 +918,20 @@ static void emit_binary_diff(mmfile_t *one, mmfile_t *two) free(data); } +static void emit_binary_diff(mmfile_t *one, mmfile_t *two) +{ + printf("GIT binary patch\n"); + emit_binary_diff_body(one, two); + emit_binary_diff_body(two, one); +} + #define FIRST_FEW_BYTES 8000 static int mmfile_is_binary(mmfile_t *mf) { long sz = mf->size; if (FIRST_FEW_BYTES < sz) sz = FIRST_FEW_BYTES; - if (memchr(mf->ptr, 0, sz)) - return 1; - return 0; + return !!memchr(mf->ptr, 0, sz); } static void builtin_diff(const char *name_a, @@ -836,7 +1020,12 @@ static void builtin_diff(const char *name_a, ecb.outf = xdiff_outf; ecb.priv = &ecbdata; ecbdata.xm.consume = fn_out_consume; + if (o->color_diff_words) + ecbdata.diff_words = + xcalloc(1, sizeof(struct diff_words_data)); xdl_diff(&mf1, &mf2, &xpp, &xecfg, &ecb); + if (o->color_diff_words) + free_diff_words_data(&ecbdata); } free_ab_and_return: @@ -939,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); } } @@ -978,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 @@ -1006,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) { @@ -1032,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; } @@ -1354,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) @@ -1417,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), @@ -1515,6 +1710,19 @@ void diff_setup(struct diff_options *options) int diff_setup_done(struct diff_options *options) { + int count = 0; + + if (options->output_format & DIFF_FORMAT_NAME) + count++; + if (options->output_format & DIFF_FORMAT_NAME_STATUS) + count++; + if (options->output_format & DIFF_FORMAT_CHECKDIFF) + count++; + if (options->output_format & DIFF_FORMAT_NO_OUTPUT) + count++; + if (count > 1) + die("--name-only, --name-status, --check and -s are mutually exclusive"); + if (options->find_copies_harder) options->detect_rename = DIFF_DETECT_COPY; @@ -1620,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")) @@ -1637,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; @@ -1697,6 +1930,8 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac) options->xdl_opts |= XDF_IGNORE_WHITESPACE; else if (!strcmp(arg, "-b") || !strcmp(arg, "--ignore-space-change")) options->xdl_opts |= XDF_IGNORE_WHITESPACE_CHANGE; + else if (!strcmp(arg, "--color-words")) + options->color_diff = options->color_diff_words = 1; else if (!strcmp(arg, "--no-renames")) options->detect_rename = 0; else @@ -1921,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. */ @@ -2060,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 { @@ -2377,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++; } @@ -2404,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: @@ -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; @@ -46,7 +52,8 @@ struct diff_options { full_index:1, silent_on_remove:1, find_copies_harder:1, - color_diff:1; + color_diff:1, + color_diff_words:1; int context; int break_opt; int detect_rename; @@ -62,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 { @@ -77,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 ed0e14c6d8..acb18db1db 100644 --- a/diffcore-break.c +++ b/diffcore-break.c @@ -56,7 +56,7 @@ static int should_break(struct diff_filespec *src, return 0; /* leave symlink rename alone */ if (src->sha1_valid && dst->sha1_valid && - !memcmp(src->sha1, dst->sha1, 20)) + !hashcmp(src->sha1, dst->sha1)) return 0; /* they are the same */ if (diff_populate_filespec(src, 0) || diff_populate_filespec(dst, 0)) diff --git a/diffcore-rename.c b/diffcore-rename.c index 0ec488a903..ef239012b6 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; @@ -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; +} @@ -47,5 +47,6 @@ extern int excluded(struct dir_struct *, const char *); extern void add_excludes_from_file(struct dir_struct *, const char *fname); extern void add_exclude(const char *string, const char *base, int baselen, struct exclude_list *which); +extern int file_exists(const char *); #endif diff --git a/dump-cache-tree.c b/dump-cache-tree.c index 1ccaf51773..1f73f1ea7d 100644 --- a/dump-cache-tree.c +++ b/dump-cache-tree.c @@ -33,7 +33,7 @@ static int dump_cache_tree(struct cache_tree *it, } else { dump_one(it, pfx, ""); - if (memcmp(it->sha1, ref->sha1, 20) || + if (hashcmp(it->sha1, ref->sha1) || ref->entry_count != it->entry_count || ref->subtree_nr != it->subtree_nr) { dump_one(ref, pfx, "#(ref) "); @@ -135,7 +135,7 @@ static int write_entry(struct cache_entry *ce, char *path, struct checkout *stat int checkout_entry(struct cache_entry *ce, struct checkout *state, char *topath) { - static char path[MAXPATHLEN+1]; + static char path[PATH_MAX + 1]; struct stat st; int len = state->base_dir_len; @@ -172,5 +172,3 @@ int checkout_entry(struct cache_entry *ce, struct checkout *state, char *topath) create_directories(path, state); return write_entry(ce, path, state, 0); } - - diff --git a/environment.c b/environment.c index 87162b2572..63b1d155be 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 62f51fcd6e..5d6a1247b4 100644 --- a/exec_cmd.c +++ b/exec_cmd.c @@ -5,7 +5,7 @@ extern char **environ; static const char *builtin_exec_path = GIT_EXEC_PATH; -static const char *current_exec_path = NULL; +static const char *current_exec_path; void git_set_exec_path(const char *exec_path) { @@ -97,26 +97,12 @@ int execv_git_cmd(const char **argv) tmp = argv[0]; argv[0] = git_command; - if (getenv("GIT_TRACE")) { - const char **p = argv; - fputs("trace: exec:", stderr); - while (*p) { - fputc(' ', stderr); - sq_quote_print(stderr, *p); - ++p; - } - putc('\n', stderr); - fflush(stderr); - } + trace_argv_printf(argv, -1, "trace: exec:"); /* execve() can only ever return if it fails */ execve(git_command, (char **)argv, environ); - if (getenv("GIT_TRACE")) { - fprintf(stderr, "trace: exec failed: %s\n", - strerror(errno)); - fflush(stderr); - } + trace_printf("trace: exec failed: %s\n", strerror(errno)); argv[0] = tmp; } diff --git a/fetch-clone.c b/fetch-clone.c index 5e84c4620f..76b99afcdb 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)); @@ -118,33 +118,8 @@ static pid_t setup_sideband(int sideband, const char *me, int fd[2], int xd[2]) 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)) + exit(1); exit(0); } close(xd[0]); diff --git a/fetch-pack.c b/fetch-pack.c index b7824dbed4..e8708aa802 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; } @@ -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 e167f4105f..4d994f3fc8 100644 --- a/fsck-objects.c +++ b/fsck-objects.c @@ -14,12 +14,12 @@ #define REACHABLE 0x0001 #define SEEN 0x0002 -static int show_root = 0; -static int show_tags = 0; -static int show_unreachable = 0; -static int check_full = 0; -static int check_strict = 0; -static int keep_cache_objects = 0; +static int show_root; +static int show_tags; +static int show_unreachable; +static int check_full; +static int check_strict; +static int keep_cache_objects; static unsigned char head_sha1[20]; #ifdef NO_D_INO_IN_DIRENT @@ -356,7 +356,7 @@ static void add_sha1_list(unsigned char *sha1, unsigned long ino) int nr; entry->ino = ino; - memcpy(entry->sha1, sha1, 20); + hashcpy(entry->sha1, sha1); nr = sha1_list.nr; if (nr == MAX_SHA1_ENTRIES) { fsck_sha1_list(); @@ -366,13 +366,13 @@ static void add_sha1_list(unsigned char *sha1, unsigned long ino) sha1_list.nr = ++nr; } -static int fsck_dir(int i, char *path) +static void fsck_dir(int i, char *path) { DIR *dir = opendir(path); struct dirent *de; if (!dir) - return 0; + return; while ((de = readdir(dir)) != NULL) { char name[100]; @@ -398,10 +398,9 @@ static int fsck_dir(int i, char *path) fprintf(stderr, "bad sha1 file: %s/%s\n", path, de->d_name); } closedir(dir); - return 0; } -static int default_refs = 0; +static int default_refs; static int fsck_handle_ref(const char *refname, const unsigned char *sha1) { @@ -426,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) @@ -444,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 */ @@ -453,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 ec1eda20de..5450918be3 100755 --- a/generate-cmdlist.sh +++ b/generate-cmdlist.sh @@ -12,6 +12,7 @@ struct cmdname_help common_cmds[] = {" sort <<\EOF | add apply +archive bisect branch checkout diff --git a/git-branch.sh b/git-branch.sh index e0501ec23f..4f31903d63 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 580a9e8a23..dd477245fb 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 f0e8831fa4..8832573fee 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 7060bdab01..bf54a11508 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" @@ -308,7 +312,7 @@ yes,yes) fi ;; *) - cd "$D" && case "$upload_pack" in + case "$upload_pack" in '') git-fetch-pack --all -k $quiet "$repo" ;; *) git-fetch-pack --all -k $quiet "$upload_pack" "$repo" ;; esac >"$GIT_DIR/CLONE_HEAD" || { diff --git a/git-commit.sh b/git-commit.sh index 4cf3fab05c..5b1cf85825 100755 --- a/git-commit.sh +++ b/git-commit.sh @@ -32,54 +32,7 @@ save_index () { cp -p "$THIS_INDEX" "$NEXT_INDEX" } -report () { - header="# -# $1: -# ($2) -# -" - trailer="" - while read status name newname - do - printf '%s' "$header" - header="" - trailer="# -" - case "$status" in - M ) echo "# modified: $name";; - D*) echo "# deleted: $name";; - T ) echo "# typechange: $name";; - C*) echo "# copied: $name -> $newname";; - R*) echo "# renamed: $name -> $newname";; - A*) echo "# new file: $name";; - U ) echo "# unmerged: $name";; - esac - done - printf '%s' "$trailer" - [ "$header" ] -} - run_status () { - ( - # We always show status for the whole tree. - cd "$TOP" - - IS_INITIAL="$initial_commit" - REFERENCE=HEAD - case "$amend" in - t) - # If we are amending the initial commit, there - # is no HEAD^1. - if git-rev-parse --verify "HEAD^1" >/dev/null 2>&1 - then - REFERENCE="HEAD^1" - IS_INITIAL= - else - IS_INITIAL=t - fi - ;; - esac - # If TMP_INDEX is defined, that means we are doing # "--only" partial commit, and that index file is used # to build the tree for the commit. Otherwise, if @@ -88,93 +41,22 @@ run_status () { # so the regular index file is what we use to compare. if test '' != "$TMP_INDEX" then - GIT_INDEX_FILE="$TMP_INDEX" - export GIT_INDEX_FILE + GIT_INDEX_FILE="$TMP_INDEX" + export GIT_INDEX_FILE elif test -f "$NEXT_INDEX" then - GIT_INDEX_FILE="$NEXT_INDEX" - export GIT_INDEX_FILE + GIT_INDEX_FILE="$NEXT_INDEX" + export GIT_INDEX_FILE fi - case "$branch" in - refs/heads/master) ;; - *) echo "# On branch $branch" ;; + case "$status_only" in + t) color= ;; + *) color=--nocolor ;; esac - - if test -z "$IS_INITIAL" - then - git-diff-index -M --cached --name-status \ - --diff-filter=MDTCRA $REFERENCE | - sed -e ' - s/\\/\\\\/g - s/ /\\ /g - ' | - report "Updated but not checked in" "will commit" - committable="$?" - else - echo '# -# Initial commit -#' - git-ls-files | - sed -e ' - s/\\/\\\\/g - s/ /\\ /g - s/^/A / - ' | - report "Updated but not checked in" "will commit" - - committable="$?" - fi - - git-diff-files --name-status | - sed -e ' - s/\\/\\\\/g - s/ /\\ /g - ' | - report "Changed but not updated" \ - "use git-update-index to mark for commit" - - option="" - if test -z "$untracked_files"; then - option="--directory --no-empty-directory" - fi - hdr_shown= - if test -f "$GIT_DIR/info/exclude" - then - git-ls-files --others $option \ - --exclude-from="$GIT_DIR/info/exclude" \ - --exclude-per-directory=.gitignore - else - git-ls-files --others $option \ - --exclude-per-directory=.gitignore - fi | - while read line; do - if [ -z "$hdr_shown" ]; then - echo '#' - echo '# Untracked files:' - echo '# (use "git add" to add to commit)' - echo '#' - hdr_shown=1 - fi - echo "# $line" - done - - if test -n "$verbose" -a -z "$IS_INITIAL" - then - git-diff-index --cached -M -p --diff-filter=MDTCRA $REFERENCE - fi - case "$committable" in - 0) - case "$amend" in - t) - echo "# No changes" ;; - *) - echo "nothing to commit" ;; - esac - exit 1 ;; - esac - exit 0 - ) + git-runstatus ${color} \ + ${verbose:+--verbose} \ + ${amend:+--amend} \ + ${untracked_files:+--untracked} } trap ' @@ -205,179 +87,181 @@ only_include_assumed= untracked_files= while case "$#" in 0) break;; esac do - case "$1" in - -F|--F|-f|--f|--fi|--fil|--file) - case "$#" in 1) usage ;; esac - shift - no_edit=t - log_given=t$log_given - logfile="$1" - shift - ;; - -F*|-f*) - no_edit=t - log_given=t$log_given - logfile=`expr "z$1" : 'z-[Ff]\(.*\)'` - shift - ;; - --F=*|--f=*|--fi=*|--fil=*|--file=*) - no_edit=t - log_given=t$log_given - logfile=`expr "z$1" : 'z-[^=]*=\(.*\)'` - shift - ;; - -a|--a|--al|--all) - all=t - shift - ;; - --au=*|--aut=*|--auth=*|--autho=*|--author=*) - force_author=`expr "z$1" : 'z-[^=]*=\(.*\)'` - shift - ;; - --au|--aut|--auth|--autho|--author) - case "$#" in 1) usage ;; esac - shift - force_author="$1" - shift - ;; - -e|--e|--ed|--edi|--edit) - edit_flag=t - shift - ;; - -i|--i|--in|--inc|--incl|--inclu|--includ|--include) - also=t - shift - ;; - -o|--o|--on|--onl|--only) - only=t - shift - ;; - -m|--m|--me|--mes|--mess|--messa|--messag|--message) - case "$#" in 1) usage ;; esac - shift - log_given=m$log_given - if test "$log_message" = '' - then - log_message="$1" - else - log_message="$log_message + case "$1" in + -F|--F|-f|--f|--fi|--fil|--file) + case "$#" in 1) usage ;; esac + shift + no_edit=t + log_given=t$log_given + logfile="$1" + shift + ;; + -F*|-f*) + no_edit=t + log_given=t$log_given + logfile=`expr "z$1" : 'z-[Ff]\(.*\)'` + shift + ;; + --F=*|--f=*|--fi=*|--fil=*|--file=*) + no_edit=t + log_given=t$log_given + logfile=`expr "z$1" : 'z-[^=]*=\(.*\)'` + shift + ;; + -a|--a|--al|--all) + all=t + shift + ;; + --au=*|--aut=*|--auth=*|--autho=*|--author=*) + force_author=`expr "z$1" : 'z-[^=]*=\(.*\)'` + shift + ;; + --au|--aut|--auth|--autho|--author) + case "$#" in 1) usage ;; esac + shift + force_author="$1" + shift + ;; + -e|--e|--ed|--edi|--edit) + edit_flag=t + shift + ;; + -i|--i|--in|--inc|--incl|--inclu|--includ|--include) + also=t + shift + ;; + -o|--o|--on|--onl|--only) + only=t + shift + ;; + -m|--m|--me|--mes|--mess|--messa|--messag|--message) + case "$#" in 1) usage ;; esac + shift + log_given=m$log_given + if test "$log_message" = '' + then + log_message="$1" + else + log_message="$log_message $1" - fi - no_edit=t - shift - ;; - -m*) - log_given=m$log_given - if test "$log_message" = '' - then - log_message=`expr "z$1" : 'z-m\(.*\)'` - else - log_message="$log_message + fi + no_edit=t + shift + ;; + -m*) + log_given=m$log_given + if test "$log_message" = '' + then + log_message=`expr "z$1" : 'z-m\(.*\)'` + else + log_message="$log_message `expr "z$1" : 'z-m\(.*\)'`" - fi - no_edit=t - shift - ;; - --m=*|--me=*|--mes=*|--mess=*|--messa=*|--messag=*|--message=*) - log_given=m$log_given - if test "$log_message" = '' - then - log_message=`expr "z$1" : 'z-[^=]*=\(.*\)'` - else - log_message="$log_message + fi + no_edit=t + shift + ;; + --m=*|--me=*|--mes=*|--mess=*|--messa=*|--messag=*|--message=*) + log_given=m$log_given + if test "$log_message" = '' + then + log_message=`expr "z$1" : 'z-[^=]*=\(.*\)'` + else + log_message="$log_message `expr "z$1" : 'zq-[^=]*=\(.*\)'`" - fi - no_edit=t - shift - ;; - -n|--n|--no|--no-|--no-v|--no-ve|--no-ver|--no-veri|--no-verif|--no-verify) - verify= - shift - ;; - --a|--am|--ame|--amen|--amend) - amend=t - log_given=t$log_given - use_commit=HEAD - shift - ;; - -c) - case "$#" in 1) usage ;; esac - shift - log_given=t$log_given - use_commit="$1" - no_edit= - shift - ;; - --ree=*|--reed=*|--reedi=*|--reedit=*|--reedit-=*|--reedit-m=*|\ - --reedit-me=*|--reedit-mes=*|--reedit-mess=*|--reedit-messa=*|\ - --reedit-messag=*|--reedit-message=*) - log_given=t$log_given - use_commit=`expr "z$1" : 'z-[^=]*=\(.*\)'` - no_edit= - shift - ;; - --ree|--reed|--reedi|--reedit|--reedit-|--reedit-m|--reedit-me|\ - --reedit-mes|--reedit-mess|--reedit-messa|--reedit-messag|--reedit-message) - case "$#" in 1) usage ;; esac - shift - log_given=t$log_given - use_commit="$1" - no_edit= - shift - ;; - -C) - case "$#" in 1) usage ;; esac - shift - log_given=t$log_given - use_commit="$1" - no_edit=t - shift - ;; - --reu=*|--reus=*|--reuse=*|--reuse-=*|--reuse-m=*|--reuse-me=*|\ - --reuse-mes=*|--reuse-mess=*|--reuse-messa=*|--reuse-messag=*|\ - --reuse-message=*) - log_given=t$log_given - use_commit=`expr "z$1" : 'z-[^=]*=\(.*\)'` - no_edit=t - shift - ;; - --reu|--reus|--reuse|--reuse-|--reuse-m|--reuse-me|--reuse-mes|\ - --reuse-mess|--reuse-messa|--reuse-messag|--reuse-message) - case "$#" in 1) usage ;; esac - shift - log_given=t$log_given - use_commit="$1" - no_edit=t - shift - ;; - -s|--s|--si|--sig|--sign|--signo|--signof|--signoff) - signoff=t - shift - ;; - -v|--v|--ve|--ver|--verb|--verbo|--verbos|--verbose) - verbose=t - shift - ;; - -u|--u|--un|--unt|--untr|--untra|--untrac|--untrack|--untracke|--untracked|\ - --untracked-|--untracked-f|--untracked-fi|--untracked-fil|--untracked-file|\ - --untracked-files) - untracked_files=t - shift - ;; - --) - shift - break - ;; - -*) - usage - ;; - *) - break - ;; - esac + fi + no_edit=t + shift + ;; + -n|--n|--no|--no-|--no-v|--no-ve|--no-ver|--no-veri|--no-verif|\ + --no-verify) + verify= + shift + ;; + --a|--am|--ame|--amen|--amend) + amend=t + log_given=t$log_given + use_commit=HEAD + shift + ;; + -c) + case "$#" in 1) usage ;; esac + shift + log_given=t$log_given + use_commit="$1" + no_edit= + shift + ;; + --ree=*|--reed=*|--reedi=*|--reedit=*|--reedit-=*|--reedit-m=*|\ + --reedit-me=*|--reedit-mes=*|--reedit-mess=*|--reedit-messa=*|\ + --reedit-messag=*|--reedit-message=*) + log_given=t$log_given + use_commit=`expr "z$1" : 'z-[^=]*=\(.*\)'` + no_edit= + shift + ;; + --ree|--reed|--reedi|--reedit|--reedit-|--reedit-m|--reedit-me|\ + --reedit-mes|--reedit-mess|--reedit-messa|--reedit-messag|\ + --reedit-message) + case "$#" in 1) usage ;; esac + shift + log_given=t$log_given + use_commit="$1" + no_edit= + shift + ;; + -C) + case "$#" in 1) usage ;; esac + shift + log_given=t$log_given + use_commit="$1" + no_edit=t + shift + ;; + --reu=*|--reus=*|--reuse=*|--reuse-=*|--reuse-m=*|--reuse-me=*|\ + --reuse-mes=*|--reuse-mess=*|--reuse-messa=*|--reuse-messag=*|\ + --reuse-message=*) + log_given=t$log_given + use_commit=`expr "z$1" : 'z-[^=]*=\(.*\)'` + no_edit=t + shift + ;; + --reu|--reus|--reuse|--reuse-|--reuse-m|--reuse-me|--reuse-mes|\ + --reuse-mess|--reuse-messa|--reuse-messag|--reuse-message) + case "$#" in 1) usage ;; esac + shift + log_given=t$log_given + use_commit="$1" + no_edit=t + shift + ;; + -s|--s|--si|--sig|--sign|--signo|--signof|--signoff) + signoff=t + shift + ;; + -v|--v|--ve|--ver|--verb|--verbo|--verbos|--verbose) + verbose=t + shift + ;; + -u|--u|--un|--unt|--untr|--untra|--untrac|--untrack|--untracke|\ + --untracked|--untracked-|--untracked-f|--untracked-fi|--untracked-fil|\ + --untracked-file|--untracked-files) + untracked_files=t + shift + ;; + --) + shift + break + ;; + -*) + usage + ;; + *) + break + ;; + esac done case "$edit_flag" in t) no_edit= ;; esac @@ -386,33 +270,33 @@ case "$edit_flag" in t) no_edit= ;; esac case "$amend,$initial_commit" in t,t) - die "You do not have anything to amend." ;; + die "You do not have anything to amend." ;; t,) - if [ -f "$GIT_DIR/MERGE_HEAD" ]; then - die "You are in the middle of a merge -- cannot amend." - fi ;; + if [ -f "$GIT_DIR/MERGE_HEAD" ]; then + die "You are in the middle of a merge -- cannot amend." + fi ;; esac case "$log_given" in tt*) - die "Only one of -c/-C/-F can be used." ;; + die "Only one of -c/-C/-F can be used." ;; *tm*|*mt*) - die "Option -m cannot be combined with -c/-C/-F." ;; + die "Option -m cannot be combined with -c/-C/-F." ;; esac case "$#,$also,$only,$amend" in *,t,t,*) - die "Only one of --include/--only can be used." ;; + die "Only one of --include/--only can be used." ;; 0,t,,* | 0,,t,) - die "No paths with --include/--only does not make sense." ;; + die "No paths with --include/--only does not make sense." ;; 0,,t,t) - only_include_assumed="# Clever... amending the last one with dirty index." ;; + only_include_assumed="# Clever... amending the last one with dirty index." ;; 0,,,*) - ;; + ;; *,,,*) - only_include_assumed="# Explicit paths specified without -i nor -o; assuming --only paths..." - also= - ;; + only_include_assumed="# Explicit paths specified without -i nor -o; assuming --only paths..." + also= + ;; esac unset only case "$all,$also,$#" in @@ -459,47 +343,47 @@ t,) ,) case "$#" in 0) - ;; # commit as-is + ;; # commit as-is *) - if test -f "$GIT_DIR/MERGE_HEAD" - then - refuse_partial "Cannot do a partial commit during a merge." - fi - TMP_INDEX="$GIT_DIR/tmp-index$$" - if test -z "$initial_commit" - then - # make sure index is clean at the specified paths, or - # they are additions. - dirty_in_index=`git-diff-index --cached --name-status \ - --diff-filter=DMTU HEAD -- "$@"` - test -z "$dirty_in_index" || - refuse_partial "Different in index and the last commit: + if test -f "$GIT_DIR/MERGE_HEAD" + then + refuse_partial "Cannot do a partial commit during a merge." + fi + TMP_INDEX="$GIT_DIR/tmp-index$$" + if test -z "$initial_commit" + then + # make sure index is clean at the specified paths, or + # they are additions. + dirty_in_index=`git-diff-index --cached --name-status \ + --diff-filter=DMTU HEAD -- "$@"` + test -z "$dirty_in_index" || + refuse_partial "Different in index and the last commit: $dirty_in_index" - fi - commit_only=`git-ls-files --error-unmatch -- "$@"` || exit - - # Build the temporary index and update the real index - # the same way. - if test -z "$initial_commit" - then - cp "$THIS_INDEX" "$TMP_INDEX" - GIT_INDEX_FILE="$TMP_INDEX" git-read-tree -m HEAD - else - rm -f "$TMP_INDEX" - fi || exit - - echo "$commit_only" | - GIT_INDEX_FILE="$TMP_INDEX" \ - git-update-index --add --remove --stdin && - - save_index && - echo "$commit_only" | - ( - GIT_INDEX_FILE="$NEXT_INDEX" - export GIT_INDEX_FILE - git-update-index --remove --stdin - ) || exit - ;; + fi + commit_only=`git-ls-files --error-unmatch -- "$@"` || exit + + # Build the temporary index and update the real index + # the same way. + if test -z "$initial_commit" + then + cp "$THIS_INDEX" "$TMP_INDEX" + GIT_INDEX_FILE="$TMP_INDEX" git-read-tree -m HEAD + else + rm -f "$TMP_INDEX" + fi || exit + + echo "$commit_only" | + GIT_INDEX_FILE="$TMP_INDEX" \ + git-update-index --add --remove --stdin && + + save_index && + echo "$commit_only" | + ( + GIT_INDEX_FILE="$NEXT_INDEX" + export GIT_INDEX_FILE + git-update-index --remove --stdin + ) || exit + ;; esac ;; esac @@ -517,7 +401,7 @@ else fi GIT_INDEX_FILE="$USE_INDEX" \ - git-update-index -q $unmerged_ok_if_status --refresh || exit + git-update-index -q $unmerged_ok_if_status --refresh || exit ################################################################ # If the request is status, just show it and exit. diff --git a/git-compat-util.h b/git-compat-util.h index b2e18954c0..0272d043d0 100644 --- a/git-compat-util.h +++ b/git-compat-util.h @@ -26,6 +26,13 @@ #include <sys/types.h> #include <dirent.h> +/* On most systems <limits.h> would have given us this, but + * not on some systems (e.g. GNU/Hurd). + */ +#ifndef PATH_MAX +#define PATH_MAX 4096 +#endif + #ifdef __GNUC__ #define NORETURN __attribute__((__noreturn__)) #else @@ -84,6 +91,14 @@ extern char *gitstrcasestr(const char *haystack, const char *needle); extern size_t gitstrlcpy(char *, const char *, size_t); #endif +static inline char* xstrdup(const char *str) +{ + char *ret = strdup(str); + if (!ret) + die("Out of memory, strdup failed"); + return ret; +} + static inline void *xmalloc(size_t size) { void *ret = malloc(size); @@ -172,7 +187,4 @@ static inline int sane_case(int x, int high) return x; } -#ifndef MAXPATHLEN -#define MAXPATHLEN 256 -#endif #endif diff --git a/git-cvsexportcommit.perl b/git-cvsexportcommit.perl index 99b3dc392a..5e23851f8c 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-cvsserver.perl b/git-cvsserver.perl index 2130d57020..08ad831a39 100755 --- a/git-cvsserver.perl +++ b/git-cvsserver.perl @@ -275,7 +275,7 @@ sub req_Directory $state->{directory} = "" if ( $state->{directory} eq "." ); $state->{directory} .= "/" if ( $state->{directory} =~ /\S/ ); - if ( not defined($state->{prependdir}) and $state->{localdir} eq "." and $state->{path} =~ /\S/ ) + if ( (not defined($state->{prependdir}) or $state->{prependdir} eq '') and $state->{localdir} eq "." and $state->{path} =~ /\S/ ) { $log->info("Setting prepend to '$state->{path}'"); $state->{prependdir} = $state->{path}; @@ -805,7 +805,14 @@ sub req_update $meta = $updater->getmeta($filename); } - next unless ( $meta->{revision} ); + if ( ! defined $meta ) + { + $meta = { + name => $filename, + revision => 0, + filehash => 'added' + }; + } my $oldmeta = $meta; @@ -835,7 +842,7 @@ sub req_update and not exists ( $state->{opt}{C} ) ) { $log->info("Tell the client the file is modified"); - print "MT text U\n"; + print "MT text M \n"; print "MT fname $filename\n"; print "MT newline\n"; next; @@ -855,15 +862,36 @@ sub req_update } } elsif ( not defined ( $state->{entries}{$filename}{modified_hash} ) - or $state->{entries}{$filename}{modified_hash} eq $oldmeta->{filehash} ) + or $state->{entries}{$filename}{modified_hash} eq $oldmeta->{filehash} + or $meta->{filehash} eq 'added' ) { - $log->info("Updating '$filename'"); - # normal update, just send the new revision (either U=Update, or A=Add, or R=Remove) - print "MT +updated\n"; - print "MT text U\n"; - print "MT fname $filename\n"; - print "MT newline\n"; - print "MT -updated\n"; + # normal update, just send the new revision (either U=Update, + # or A=Add, or R=Remove) + if ( defined($wrev) && $wrev < 0 ) + { + $log->info("Tell the client the file is scheduled for removal"); + print "MT text R \n"; + print "MT fname $filename\n"; + print "MT newline\n"; + next; + } + elsif ( !defined($wrev) || $wrev == 0 ) + { + $log->info("Tell the client the file will be added"); + print "MT text A \n"; + print "MT fname $filename\n"; + print "MT newline\n"; + next; + + } + else { + $log->info("Updating '$filename' $wrev"); + print "MT +updated\n"; + print "MT text U \n"; + print "MT fname $filename\n"; + print "MT newline\n"; + print "MT -updated\n"; + } my ( $filepart, $dirpart ) = filenamesplit($filename,1); @@ -1709,6 +1737,17 @@ sub argsfromdir return if ( scalar ( @{$state->{args}} ) > 1 ); + my @gethead = @{$updater->gethead}; + + # push added files + foreach my $file (keys %{$state->{entries}}) { + if ( exists $state->{entries}{$file}{revision} && + $state->{entries}{$file}{revision} == 0 ) + { + push @gethead, { name => $file, filehash => 'added' }; + } + } + if ( scalar(@{$state->{args}}) == 1 ) { my $arg = $state->{args}[0]; @@ -1716,7 +1755,7 @@ sub argsfromdir $log->info("Only one arg specified, checking for directory expansion on '$arg'"); - foreach my $file ( @{$updater->gethead} ) + foreach my $file ( @gethead ) { next if ( $file->{filehash} eq "deleted" and not defined ( $state->{entries}{$file->{name}} ) ); next unless ( $file->{name} =~ /^$arg\// or $file->{name} eq $arg ); @@ -1729,7 +1768,7 @@ sub argsfromdir $state->{args} = []; - foreach my $file ( @{$updater->gethead} ) + foreach my $file ( @gethead ) { next if ( $file->{filehash} eq "deleted" and not defined ( $state->{entries}{$file->{name}} ) ); next unless ( $file->{name} =~ s/^$state->{prependdir}// ); diff --git a/git-fetch.sh b/git-fetch.sh index c2eebee798..79222fbb1a 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 @@ -432,10 +436,10 @@ esac # If the original head was empty (i.e. no "master" yet), or # if we were told not to worry, we do not have to check. -case ",$update_head_ok,$orig_head," in -*,, | t,* ) +case "$orig_head" in +'') ;; -*) +?*) curr_head=$(git-rev-parse --verify HEAD 2>/dev/null) if test "$curr_head" != "$orig_head" then diff --git a/git-ls-remote.sh b/git-ls-remote.sh index 2fdcaf7886..0f88953f29 100755 --- a/git-ls-remote.sh +++ b/git-ls-remote.sh @@ -49,10 +49,14 @@ trap "rm -fr $tmp-*" 0 1 2 3 15 tmpdir=$tmp-d case "$peek_repo" in -http://* | https://* ) +http://* | https://* | ftp://* ) if [ -n "$GIT_SSL_NO_VERIFY" ]; then curl_extra_args="-k" fi + if [ -n "$GIT_CURL_FTP_NO_EPSV" -o \ + "`git-repo-config --bool http.noEPSV`" = true ]; then + curl_extra_args="${curl_extra_args} --disable-epsv" + fi curl -nsf $curl_extra_args --header "Pragma: no-cache" "$peek_repo/info/refs" || echo "failed slurping" ;; diff --git a/git-merge-recursive.py b/git-merge-recursive-old.py index 4039435ce4..4039435ce4 100755 --- a/git-merge-recursive.py +++ b/git-merge-recursive-old.py diff --git a/git-merge.sh b/git-merge.sh index a9cfafb1df..5b34b4de99 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 187f0883c9..c325ef761e 100755 --- a/git-parse-remote.sh +++ b/git-parse-remote.sh @@ -68,6 +68,12 @@ get_remote_url () { esac } +get_default_remote () { + curr_branch=$(git-symbolic-ref HEAD | sed -e 's|^refs/heads/||') + origin=$(git-repo-config --get "branch.$curr_branch.remote") + echo ${origin:-origin} +} + get_remote_default_refs_for_push () { data_source=$(get_data_source "$1") case "$data_source" in @@ -86,9 +92,22 @@ get_remote_default_refs_for_push () { # Subroutine to canonicalize remote:local notation. canon_refs_list_for_fetch () { - # Leave only the first one alone; add prefix . to the rest + # If called from get_remote_default_refs_for_fetch + # leave the branches in branch.${curr_branch}.merge alone, + # or the first one otherwise; add prefix . to the rest # to prevent the secondary branches to be merged by default. - dot_prefix= + merge_branches= + if test "$1" = "-d" + then + shift ; remote="$1" ; shift + if test "$remote" = "$(get_default_remote)" + then + curr_branch=$(git-symbolic-ref HEAD | \ + sed -e 's|^refs/heads/||') + merge_branches=$(git-repo-config \ + --get-all "branch.${curr_branch}.merge") + fi + fi for ref do force= @@ -101,6 +120,18 @@ canon_refs_list_for_fetch () { expr "z$ref" : 'z.*:' >/dev/null || ref="${ref}:" remote=$(expr "z$ref" : 'z\([^:]*\):') local=$(expr "z$ref" : 'z[^:]*:\(.*\)') + dot_prefix=. + if test -z "$merge_branches" + then + merge_branches=$remote + dot_prefix= + else + for merge_branch in $merge_branches + do + [ "$remote" = "$merge_branch" ] && + dot_prefix= && break + done + fi case "$remote" in '') remote=HEAD ;; refs/heads/* | refs/tags/* | refs/remotes/*) ;; @@ -120,7 +151,6 @@ canon_refs_list_for_fetch () { die "* refusing to create funny ref '$local_ref_name' locally" fi echo "${dot_prefix}${force}${remote}:${local}" - dot_prefix=. done } @@ -131,7 +161,7 @@ get_remote_default_refs_for_fetch () { '' | config-partial | branches-partial) echo "HEAD:" ;; config) - canon_refs_list_for_fetch \ + canon_refs_list_for_fetch -d "$1" \ $(git-repo-config --get-all "remote.$1.fetch") ;; branches) remote_branch=$(sed -ne '/#/s/.*#//p' "$GIT_DIR/branches/$1") @@ -139,10 +169,7 @@ get_remote_default_refs_for_fetch () { echo "refs/heads/${remote_branch}:refs/heads/$1" ;; remotes) - # This prefixes the second and later default refspecs - # with a '.', to signal git-fetch to mark them - # not-for-merge. - canon_refs_list_for_fetch $(sed -ne '/^Pull: */{ + canon_refs_list_for_fetch -d "$1" $(sed -ne '/^Pull: */{ s///p }' "$GIT_DIR/remotes/$1") ;; diff --git a/git-pull.sh b/git-pull.sh index f380437997..ed04e7d8d8 100755 --- a/git-pull.sh +++ b/git-pull.sh @@ -58,7 +58,7 @@ then echo >&2 "Warning: fetch updated the current branch head." echo >&2 "Warning: fast forwarding your working tree from" - echo >&2 "Warning: $orig_head commit." + echo >&2 "Warning: commit $orig_head." git-update-index --refresh 2>/dev/null git-read-tree -u -m "$orig_head" "$curr_head" || die 'Cannot fast-forward your working tree. diff --git a/git-rebase.sh b/git-rebase.sh index 7d3a5d0e71..a7373c0532 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 9da92fb061..f2c9071d11 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 36fc8ce25b..3133b5bd25 100755 --- a/git-reset.sh +++ b/git-reset.sh @@ -3,9 +3,6 @@ USAGE='[--mixed | --soft | --hard] [<commit-ish>]' . git-sh-setup -tmp=${GIT_DIR}/reset.$$ -trap 'rm -f $tmp-*' 0 1 2 3 15 - update= reset_type=--mixed case "$1" in diff --git a/git-resolve.sh b/git-resolve.sh index a7bc680d90..729ec65dc9 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-revert.sh b/git-revert.sh index 2bf35d116c..4fd81b6ed6 100755 --- a/git-revert.sh +++ b/git-revert.sh @@ -7,18 +7,20 @@ case "$0" in *-revert* ) test -t 0 && edit=-e + replay= me=revert USAGE='[--edit | --no-edit] [-n] <commit-ish>' ;; *-cherry-pick* ) + replay=t edit= me=cherry-pick - USAGE='[--edit] [-n] [-r] <commit-ish>' ;; + USAGE='[--edit] [-n] [-r] [-x] <commit-ish>' ;; * ) die "What are you talking about?" ;; esac . git-sh-setup -no_commit= replay= +no_commit= while case "$#" in 0) break ;; esac do case "$1" in @@ -32,8 +34,10 @@ do --n|--no|--no-|--no-e|--no-ed|--no-edi|--no-edit) edit= ;; - -r|--r|--re|--rep|--repl|--repla|--replay) - replay=t + -r) + : no-op ;; + -x|--i-really-want-to-expose-my-private-commit-object-name) + replay= ;; -*) usage @@ -121,7 +125,7 @@ cherry-pick) git-cat-file commit $commit | sed -e '1,/^$/d' case "$replay" in '') - echo "(cherry picked from $commit commit)" + echo "(cherry picked from commit $commit)" test "$rev" = "$commit" || echo "(original 'git cherry-pick' arguments: $@)" ;; diff --git a/git-send-email.perl b/git-send-email.perl index a83c7e9094..3f50abaeb6 100755 --- a/git-send-email.perl +++ b/git-send-email.perl @@ -21,6 +21,7 @@ use warnings; use Term::ReadLine; use Getopt::Long; use Data::Dumper; +use Git; package FakeTerm; sub new { @@ -92,6 +93,7 @@ my $smtp_server; # Example reply to: #$initial_reply_to = ''; #<20050203173208.GA23964@foobar.com>'; +my $repo = Git->repository(); my $term = eval { new Term::ReadLine 'git-send-email'; }; @@ -132,33 +134,12 @@ foreach my $entry (@bcclist) { # Now, let's fill any that aren't set in with defaults: -sub gitvar { - my ($var) = @_; - my $fh; - my $pid = open($fh, '-|'); - die "$!" unless defined $pid; - if (!$pid) { - exec('git-var', $var) or die "$!"; - } - my ($val) = <$fh>; - close $fh or die "$!"; - chomp($val); - return $val; -} - -sub gitvar_ident { - my ($name) = @_; - my $val = gitvar($name); - my @field = split(/\s+/, $val); - return join(' ', @field[0...(@field-3)]); -} - -my ($author) = gitvar_ident('GIT_AUTHOR_IDENT'); -my ($committer) = gitvar_ident('GIT_COMMITTER_IDENT'); +my ($author) = $repo->ident_person('author'); +my ($committer) = $repo->ident_person('committer'); my %aliases; -chomp(my @alias_files = `git-repo-config --get-all sendemail.aliasesfile`); -chomp(my $aliasfiletype = `git-repo-config sendemail.aliasfiletype`); +my @alias_files = $repo->config('sendemail.aliasesfile'); +my $aliasfiletype = $repo->config('sendemail.aliasfiletype'); my %parse_alias = ( # multiline formats can be supported in the future mutt => sub { my $fh = shift; while (<$fh>) { @@ -183,7 +164,7 @@ my %parse_alias = ( }}} ); -if (@alias_files && defined $parse_alias{$aliasfiletype}) { +if (@alias_files and $aliasfiletype and defined $parse_alias{$aliasfiletype}) { foreach my $file (@alias_files) { open my $fh, '<', $file or die "opening $file: $!\n"; $parse_alias{$aliasfiletype}->($fh); @@ -425,10 +406,7 @@ sub send_message my $date = format_2822_time($time++); my $gitversion = '@@GIT_VERSION@@'; if ($gitversion =~ m/..GIT_VERSION../) { - $gitversion = `git --version`; - chomp $gitversion; - # keep only what's after the last space - $gitversion =~ s/^.* //; + $gitversion = Git::version(); } my $header = "From: $from @@ -510,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", @@ -560,7 +538,7 @@ foreach my $t (@files) { send_message(); # set up for the next message - if ($chain_reply_to || length($reply_to) == 0) { + if ($chain_reply_to || !defined $reply_to || length($reply_to) == 0) { $reply_to = $message_id; if (length $references > 0) { $references .= " $message_id"; diff --git a/git-shortlog.perl b/git-shortlog.perl index 0b14f833ee..334fec7477 100755 --- a/git-shortlog.perl +++ b/git-shortlog.perl @@ -1,6 +1,18 @@ #!/usr/bin/perl -w use strict; +use Getopt::Std; +use File::Basename qw(basename dirname); + +our ($opt_h, $opt_n, $opt_s); +getopts('hns'); + +$opt_h && usage(); + +sub usage { + print STDERR "Usage: ${\basename $0} [-h] [-n] [-s] < <log_data>\n"; + exit(1); +} my (%mailmap); my (%email); @@ -38,16 +50,38 @@ sub by_name($$) { uc($a) cmp uc($b); } +sub by_nbentries($$) { + my ($a, $b) = @_; + my $a_entries = $map{$a}; + my $b_entries = $map{$b}; + + @$b_entries - @$a_entries || by_name $a, $b; +} + +my $sort_method = $opt_n ? \&by_nbentries : \&by_name; + +sub summary_output { + my ($obj, $num, $key); + + foreach $key (sort $sort_method keys %map) { + $obj = $map{$key}; + $num = @$obj; + printf "%s: %u\n", $key, $num; + $n_output += $num; + } +} sub shortlog_output { - my ($obj, $key, $desc); + my ($obj, $num, $key, $desc); + + foreach $key (sort $sort_method keys %map) { + $obj = $map{$key}; + $num = @$obj; - foreach $key (sort by_name keys %map) { # output author - printf "%s:\n", $key; + printf "%s (%u):\n", $key, $num; # output author's 1-line summaries - $obj = $map{$key}; foreach $desc (reverse @$obj) { print " $desc\n"; $n_output++; @@ -152,7 +186,7 @@ sub finalize { &setup_mailmap; &changelog_input; -&shortlog_output; +$opt_s ? &summary_output : &shortlog_output; &finalize; exit(0); diff --git a/git-svn.perl b/git-svn.perl index 0d58bb9b37..54d2356933 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -40,8 +40,22 @@ memoize('cmt_metadata'); memoize('get_commit_time'); my ($SVN_PATH, $SVN, $SVN_LOG, $_use_lib); + +sub nag_lib { + print STDERR <<EOF; +! Please consider installing the SVN Perl libraries (version 1.1.0 or +! newer). You will generally get better performance and fewer bugs, +! especially if you: +! 1) have a case-insensitive filesystem +! 2) replace symlinks with files (and vice-versa) in commits + +EOF +} + $_use_lib = 1 unless $ENV{GIT_SVN_NO_LIB}; libsvn_load(); +nag_lib() unless $_use_lib; + my $_optimize_commits = 1 unless $ENV{GIT_SVN_NO_OPTIMIZE_COMMITS}; my $sha1 = qr/[a-f\d]{40}/; my $sha1_short = qr/[a-f\d]{4,40}/; @@ -51,7 +65,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, $_non_recursive); my (@_branch_from, %tree_map, %users, %rusers, %equiv); my ($_svn_co_url_revs, $_svn_pg_peg_revs); my @repo_path_split_cache; @@ -64,6 +79,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); @@ -112,12 +128,18 @@ my %cmd = ( 'incremental' => \$_incremental, 'oneline' => \$_oneline, 'show-commit' => \$_show_commit, + 'non-recursive' => \$_non_recursive, 'authors-file|A=s' => \$_authors, } ], 'commit-diff' => [ \&commit_diff, 'Commit a diff between two trees', { 'message|m=s' => \$_message, 'file|F=s' => \$_file, %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; @@ -161,11 +183,11 @@ Usage: $0 <command> [options] [arguments]\n foreach (sort keys %cmd) { next if $cmd && $cmd ne $_; - print $fd ' ',pack('A13',$_),$cmd{$_}->[1],"\n"; + print $fd ' ',pack('A17',$_),$cmd{$_}->[1],"\n"; foreach (keys %{$cmd{$_}->[2]}) { # prints out arguments as they should be passed: my $x = s#[:=]s$## ? '<arg>' : s#[:=]i$## ? '<num>' : ''; - print $fd ' ' x 17, join(', ', map { length $_ > 1 ? + print $fd ' ' x 21, join(', ', map { length $_ > 1 ? "--$_" : "-$_" } split /\|/,$_)," $x\n"; } @@ -500,6 +522,8 @@ sub commit_lib { my @lock = $SVN::Core::VERSION ge '1.2.0' ? (undef, 0) : (); my $commit_msg = "$GIT_SVN_DIR/.svn-commit.tmp.$$"; + my $repo; + ($repo, $SVN_PATH) = repo_path_split($SVN_URL); set_svn_commit_env(); foreach my $c (@revs) { my $log_msg = get_commit_message($c, $commit_msg); @@ -508,9 +532,11 @@ sub commit_lib { # can't track down... (it's probably in the SVN code) defined(my $pid = open my $fh, '-|') or croak $!; if (!$pid) { + $SVN_LOG = libsvn_connect($repo); + $SVN = libsvn_connect($repo); my $ed = SVN::Git::Editor->new( { r => $r_last, - ra => $SVN, + ra => $SVN_LOG, c => $c, svn_path => $SVN_PATH }, @@ -557,6 +583,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(); @@ -644,12 +697,17 @@ sub multi_init { } $_trunk = $url . $_trunk; } + my $ch_id; if ($GIT_SVN eq 'git-svn') { - print "GIT_SVN_ID set to 'trunk' for $_trunk\n"; + $ch_id = 1; $GIT_SVN = $ENV{GIT_SVN_ID} = 'trunk'; } init_vars(); - init($_trunk); + unless (-d $GIT_SVN_DIR) { + print "GIT_SVN_ID set to 'trunk' for $_trunk\n" if $ch_id; + init($_trunk); + sys('git-repo-config', 'svn.trunk', $_trunk); + } complete_url_ls_init($url, $_branches, '--branches/-b', ''); complete_url_ls_init($url, $_tags, '--tags/-t', 'tags/'); } @@ -709,13 +767,18 @@ sub show_log { # ignore } elsif (/^:\d{6} \d{6} $sha1_short/o) { push @{$c->{raw}}, $_; + } elsif (/^[ACRMDT]\t/) { + # we could add $SVN_PATH here, but that requires + # remote access at the moment (repo_path_split)... + s#^([ACRMDT])\t# $1 #; + push @{$c->{changed}}, $_; } elsif (/^diff /) { $d = 1; push @{$c->{diff}}, $_; } elsif ($d) { push @{$c->{diff}}, $_; } elsif (/^ (git-svn-id:.+)$/) { - (undef, $c->{r}, undef) = extract_metadata($1); + ($c->{url}, $c->{r}, undef) = extract_metadata($1); } elsif (s/^ //) { push @{$c->{l}}, $_; } @@ -769,7 +832,7 @@ sub commit_diff { $SVN ||= libsvn_connect($repo); my @lock = $SVN::Core::VERSION ge '1.2.0' ? (undef, 0) : (); my $ed = SVN::Git::Editor->new({ r => $SVN->get_latest_revnum, - ra => $SVN, c => $tb, + ra => $SVN_LOG, c => $tb, svn_path => $SVN_PATH }, $SVN->get_commit_editor($_message, @@ -782,6 +845,7 @@ sub commit_diff { } else { $ed->close_edit; } + $_message = $_file = undef; } ########################### utility functions ######################### @@ -806,7 +870,8 @@ sub git_svn_log_cmd { my ($r_min, $r_max) = @_; my @cmd = (qw/git-log --abbrev-commit --pretty=raw --default/, "refs/remotes/$GIT_SVN"); - push @cmd, '--summary' if $_verbose; + push @cmd, '-r' unless $_non_recursive; + push @cmd, qw/--raw --name-status/ if $_verbose; return @cmd unless defined $r_max; if ($r_max == $r_min) { push @cmd, '--max-count=1'; @@ -817,7 +882,7 @@ sub git_svn_log_cmd { my ($c_min, $c_max); $c_max = revdb_get($REVDB, $r_max); $c_min = revdb_get($REVDB, $r_min); - if ($c_min && $c_max) { + if (defined $c_min && defined $c_max) { if ($r_max > $r_max) { push @cmd, "$c_min..$c_max"; } else { @@ -898,16 +963,21 @@ sub complete_url_ls_init { print STDERR "W: Unrecognized URL: $u\n"; die "This should never happen\n"; } + # don't try to init already existing refs my $id = $pfx.$1; - print "init $u => $id\n"; $GIT_SVN = $ENV{GIT_SVN_ID} = $id; init_vars(); - init($u); + unless (-d $GIT_SVN_DIR) { + print "init $u => $id\n"; + init($u); + } } exit 0; } waitpid $pid, 0; croak $? if $?; + my ($n) = ($switch =~ /^--(\w+)/); + sys('git-repo-config', "svn.$n", $var); } sub common_prefix { @@ -1208,6 +1278,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 +1767,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 +2203,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]; } @@ -2509,6 +2582,12 @@ sub show_commit { } } +sub show_commit_changed_paths { + my ($c) = @_; + return unless $c->{changed}; + print "Changed paths:\n", @{$c->{changed}}; +} + sub show_commit_normal { my ($c) = @_; print '-' x72, "\nr$c->{r} | "; @@ -2518,7 +2597,8 @@ sub show_commit_normal { my $nr_line = 0; if (my $l = $c->{l}) { - while ($l->[$#$l] eq "\n" && $l->[($#$l - 1)] eq "\n") { + while ($l->[$#$l] eq "\n" && $#$l > 0 + && $l->[($#$l - 1)] eq "\n") { pop @$l; } $nr_line = scalar @$l; @@ -2530,11 +2610,15 @@ sub show_commit_normal { } else { $nr_line .= ' lines'; } - print $nr_line, "\n\n"; + print $nr_line, "\n"; + show_commit_changed_paths($c); + print "\n"; print $_ foreach @$l; } } else { - print "1 line\n\n"; + print "1 line\n"; + show_commit_changed_paths($c); + print "\n"; } foreach my $x (qw/raw diff/) { @@ -3270,9 +3354,11 @@ sub chg_file { seek $fh, 0, 0 or croak $!; my $exp = $md5->hexdigest; - my $atd = $self->apply_textdelta($fbat, undef, $self->{pool}); - my $got = SVN::TxDelta::send_stream($fh, @$atd, $self->{pool}); + my $pool = SVN::Pool->new; + my $atd = $self->apply_textdelta($fbat, undef, $pool); + my $got = SVN::TxDelta::send_stream($fh, @$atd, $pool); die "Checksum mismatch\nexpected: $exp\ngot: $got\n" if ($got ne $exp); + $pool->clear; close $fh or croak $!; } diff --git a/git-svnimport.perl b/git-svnimport.perl index 26dc454795..aca0e4f64e 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> 0 +0000\n") and close($out) or die "Cannot create tag object $dest: $!\n"; @@ -16,7 +16,7 @@ #include "builtin.h" const char git_usage_string[] = - "git [--version] [--exec-path[=GIT_EXEC_PATH]] [--help] COMMAND [ ARGS ]"; + "git [--version] [--exec-path[=GIT_EXEC_PATH]] [-p|--paginate] [--bare] [--git-dir=GIT_DIR] [--help] COMMAND [ARGS]"; static void prepend_to_path(const char *dir, int len) { @@ -29,13 +29,15 @@ static void prepend_to_path(const char *dir, int len) path_len = len + strlen(old_path) + 1; - path = malloc(path_len + 1); + path = xmalloc(path_len + 1); memcpy(path, dir, len); path[len] = ':'; memcpy(path + len + 1, old_path, path_len - len); setenv("PATH", path, 1); + + free(path); } static int handle_options(const char*** argv, int* argc) @@ -92,12 +94,12 @@ static int handle_options(const char*** argv, int* argc) } static const char *alias_command; -static char *alias_string = NULL; +static char *alias_string; static int git_alias_config(const char *var, const char *value) { if (!strncmp(var, "alias.", 6) && !strcmp(var + 6, alias_command)) { - alias_string = strdup(value); + alias_string = xstrdup(value); } return 0; } @@ -120,7 +122,7 @@ static int split_cmdline(char *cmdline, const char ***argv) ; /* skip */ if (count >= size) { size += 16; - *argv = realloc(*argv, sizeof(char*) * size); + *argv = xrealloc(*argv, sizeof(char*) * size); } (*argv)[count++] = cmdline + dst; } else if(!quoted && (c == '\'' || c == '"')) { @@ -179,20 +181,12 @@ static int handle_alias(int *argcp, const char ***argv) if (!strcmp(alias_command, new_argv[0])) die("recursive alias: %s", alias_command); - if (getenv("GIT_TRACE")) { - int i; - fprintf(stderr, "trace: alias expansion: %s =>", - alias_command); - for (i = 0; i < count; ++i) { - fputc(' ', stderr); - sq_quote_print(stderr, new_argv[i]); - } - fputc('\n', stderr); - fflush(stderr); - } + trace_argv_printf(new_argv, count, + "trace: alias expansion: %s =>", + alias_command); - new_argv = realloc(new_argv, sizeof(char*) * - (count + *argcp + 1)); + new_argv = xrealloc(new_argv, sizeof(char*) * + (count + *argcp + 1)); /* insert after command name */ memcpy(new_argv + count, *argv + 1, sizeof(char*) * *argcp); new_argv[count+*argcp] = NULL; @@ -213,8 +207,8 @@ static int handle_alias(int *argcp, const char ***argv) const char git_version_string[] = GIT_VERSION; -#define NEEDS_PREFIX 1 -#define USE_PAGER 2 +#define RUN_SETUP (1<<0) +#define USE_PAGER (1<<1) static void handle_internal_command(int argc, const char **argv, char **envp) { @@ -224,47 +218,55 @@ static void handle_internal_command(int argc, const char **argv, char **envp) int (*fn)(int, const char **, const char *); int option; } commands[] = { - { "version", cmd_version }, + { "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, RUN_SETUP }, + { "diff", cmd_diff, RUN_SETUP | USE_PAGER }, + { "diff-files", cmd_diff_files, RUN_SETUP }, + { "diff-index", cmd_diff_index, RUN_SETUP }, + { "diff-stages", cmd_diff_stages, RUN_SETUP }, + { "diff-tree", cmd_diff_tree, RUN_SETUP }, + { "fmt-merge-msg", cmd_fmt_merge_msg, RUN_SETUP }, + { "format-patch", cmd_format_patch, RUN_SETUP }, + { "get-tar-commit-id", cmd_get_tar_commit_id }, + { "grep", cmd_grep, RUN_SETUP }, { "help", cmd_help }, - { "log", cmd_log, NEEDS_PREFIX | USE_PAGER }, - { "whatchanged", cmd_whatchanged, NEEDS_PREFIX | USE_PAGER }, - { "show", cmd_show, NEEDS_PREFIX | USE_PAGER }, - { "push", cmd_push, NEEDS_PREFIX }, - { "format-patch", cmd_format_patch, NEEDS_PREFIX }, - { "count-objects", cmd_count_objects }, - { "diff", cmd_diff, NEEDS_PREFIX }, - { "grep", cmd_grep, NEEDS_PREFIX }, - { "rm", cmd_rm, NEEDS_PREFIX }, - { "add", cmd_add, NEEDS_PREFIX }, - { "rev-list", cmd_rev_list, NEEDS_PREFIX }, { "init-db", cmd_init_db }, - { "get-tar-commit-id", cmd_get_tar_commit_id }, - { "upload-tar", cmd_upload_tar }, - { "check-ref-format", cmd_check_ref_format }, - { "ls-files", cmd_ls_files, NEEDS_PREFIX }, - { "ls-tree", cmd_ls_tree, NEEDS_PREFIX }, - { "tar-tree", cmd_tar_tree, NEEDS_PREFIX }, - { "read-tree", cmd_read_tree, NEEDS_PREFIX }, - { "commit-tree", cmd_commit_tree, NEEDS_PREFIX }, - { "apply", cmd_apply }, - { "show-branch", cmd_show_branch, NEEDS_PREFIX }, - { "diff-files", cmd_diff_files, NEEDS_PREFIX }, - { "diff-index", cmd_diff_index, NEEDS_PREFIX }, - { "diff-stages", cmd_diff_stages, NEEDS_PREFIX }, - { "diff-tree", cmd_diff_tree, NEEDS_PREFIX }, - { "cat-file", cmd_cat_file, NEEDS_PREFIX }, - { "rev-parse", cmd_rev_parse, NEEDS_PREFIX }, - { "write-tree", cmd_write_tree, NEEDS_PREFIX }, - { "mailsplit", cmd_mailsplit }, + { "log", cmd_log, RUN_SETUP | USE_PAGER }, + { "ls-files", cmd_ls_files, RUN_SETUP }, + { "ls-tree", cmd_ls_tree, RUN_SETUP }, { "mailinfo", cmd_mailinfo }, - { "stripspace", cmd_stripspace }, - { "update-index", cmd_update_index, NEEDS_PREFIX }, - { "update-ref", cmd_update_ref, NEEDS_PREFIX }, - { "fmt-merge-msg", cmd_fmt_merge_msg, NEEDS_PREFIX }, - { "prune", cmd_prune, NEEDS_PREFIX }, - { "mv", cmd_mv, NEEDS_PREFIX }, - { "prune-packed", cmd_prune_packed, NEEDS_PREFIX }, + { "mailsplit", cmd_mailsplit }, + { "mv", cmd_mv, RUN_SETUP }, + { "name-rev", cmd_name_rev, RUN_SETUP }, + { "pack-objects", cmd_pack_objects, RUN_SETUP }, + { "prune", cmd_prune, RUN_SETUP }, + { "prune-packed", cmd_prune_packed, RUN_SETUP }, + { "push", cmd_push, RUN_SETUP }, + { "read-tree", cmd_read_tree, RUN_SETUP }, { "repo-config", cmd_repo_config }, + { "rev-list", cmd_rev_list, RUN_SETUP }, + { "rev-parse", cmd_rev_parse, RUN_SETUP }, + { "rm", cmd_rm, RUN_SETUP }, + { "runstatus", cmd_runstatus, RUN_SETUP }, + { "show-branch", cmd_show_branch, RUN_SETUP }, + { "show", cmd_show, RUN_SETUP | USE_PAGER }, + { "stripspace", cmd_stripspace }, + { "symbolic-ref", cmd_symbolic_ref, RUN_SETUP }, + { "tar-tree", cmd_tar_tree }, + { "unpack-objects", cmd_unpack_objects, RUN_SETUP }, + { "update-index", cmd_update_index, RUN_SETUP }, + { "update-ref", cmd_update_ref, RUN_SETUP }, + { "upload-archive", cmd_upload_archive }, + { "version", cmd_version }, + { "whatchanged", cmd_whatchanged, RUN_SETUP | USE_PAGER }, + { "write-tree", cmd_write_tree, RUN_SETUP }, + { "verify-pack", cmd_verify_pack }, }; int i; @@ -281,20 +283,11 @@ static void handle_internal_command(int argc, const char **argv, char **envp) continue; prefix = NULL; - if (p->option & NEEDS_PREFIX) + if (p->option & RUN_SETUP) 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)); } @@ -302,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/git.spec.in b/git.spec.in index 8ccd2564e7..9b1217ac3f 100644 --- a/git.spec.in +++ b/git.spec.in @@ -9,7 +9,7 @@ URL: http://kernel.org/pub/software/scm/git/ Source: http://kernel.org/pub/software/scm/git/%{name}-%{version}.tar.gz BuildRequires: zlib-devel >= 1.2, openssl-devel, curl-devel, expat-devel %{!?_without_docs:, xmlto, asciidoc > 6.0.3} BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) -Requires: git-core, git-svn, git-cvs, git-arch, git-email, gitk +Requires: git-core, git-svn, git-cvs, git-arch, git-email, gitk, perl-Git %description This is a stupid (but extremely fast) directory content manager. It @@ -70,6 +70,16 @@ Requires: git-core = %{version}-%{release}, tk >= 8.4 %description -n gitk Git revision tree visualiser ('gitk') +%package -n perl-Git +Summary: Perl interface to Git +Group: Development/Libraries +Requires: git-core = %{version}-%{release} +Requires: perl(:MODULE_COMPAT_%(eval "`%{__perl} -V:version`"; echo $version)) +BuildRequires: perl(Error) + +%description -n perl-Git +Perl interface to Git + %prep %setup -q @@ -80,12 +90,18 @@ make %{_smp_mflags} CFLAGS="$RPM_OPT_FLAGS" WITH_OWN_SUBPROCESS_PY=YesPlease \ %install rm -rf $RPM_BUILD_ROOT make %{_smp_mflags} DESTDIR=$RPM_BUILD_ROOT WITH_OWN_SUBPROCESS_PY=YesPlease \ - prefix=%{_prefix} mandir=%{_mandir} \ + prefix=%{_prefix} mandir=%{_mandir} INSTALLDIRS=vendor \ install %{!?_without_docs: install-doc} +find $RPM_BUILD_ROOT -type f -name .packlist -exec rm -f {} ';' +find $RPM_BUILD_ROOT -type f -name '*.bs' -empty -exec rm -f {} ';' +find $RPM_BUILD_ROOT -type f -name perllocal.pod -exec rm -f {} ';' (find $RPM_BUILD_ROOT%{_bindir} -type f | grep -vE "arch|svn|cvs|email|gitk" | sed -e s@^$RPM_BUILD_ROOT@@) > bin-man-doc-files +(find $RPM_BUILD_ROOT%{perl_vendorlib} -type f | sed -e s@^$RPM_BUILD_ROOT@@) >> perl-files %if %{!?_without_docs:1}0 (find $RPM_BUILD_ROOT%{_mandir} $RPM_BUILD_ROOT/Documentation -type f | grep -vE "arch|svn|git-cvs|email|gitk" | sed -e s@^$RPM_BUILD_ROOT@@ -e 's/$/*/' ) >> bin-man-doc-files +%else +rm -rf $RPM_BUILD_ROOT%{_mandir} %endif %clean @@ -129,6 +145,9 @@ rm -rf $RPM_BUILD_ROOT %{!?_without_docs: %{_mandir}/man1/*gitk*.1*} %{!?_without_docs: %doc Documentation/*gitk*.html } +%files -n perl-Git -f perl-files +%defattr(-,root,root) + %files core -f bin-man-doc-files %defattr(-,root,root) %{_datadir}/git-core/ @@ -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/README b/gitweb/README index 8d672762ea..78e6fc05f3 100644 --- a/gitweb/README +++ b/gitweb/README @@ -1,9 +1,68 @@ GIT web Interface +================= The one working on: http://www.kernel.org/git/ From the git version 1.4.0 gitweb is bundled with git. -Any comment/question/concern to: + +How to configure gitweb for your local system +--------------------------------------------- + +You can specify the following configuration variables when building GIT: + * GITWEB_SITENAME + Shown in the title of all generated pages, defaults to the servers name. + * GITWEB_PROJECTROOT + The root directory for all projects shown by gitweb. + * GITWEB_LIST + points to a directory to scan for projects (defaults to project root) + or to a file for explicit listing of projects. + * GITWEB_HOMETEXT + points to an .html file which is included on the gitweb project + overview page. + * GITWEB_CSS + Points to the location where you put gitweb.css on your web server. + * GITWEB_LOGO + Points to the location where you put git-logo.png on your web server. + * GITWEB_CONFIG + This file will be loaded using 'require'. If the environment + $GITWEB_CONFIG is set when gitweb.cgi is executed the file in the + environment variable will be loaded instead of the file + specified when gitweb.cgi was created. + + +Webserver configuration +----------------------- + +If you want to have one URL for both gitweb and your http:// +repositories, you can configure apache like this: + +<VirtualHost www:80> + ServerName git.domain.org + DocumentRoot /pub/git + RewriteEngine on + RewriteRule ^/(.*\.git/(?!/?(info|objects|refs)).*)?$ /cgi-bin/gitweb.cgi%{REQUEST_URI} [L,PT] + SetEnv GITWEB_CONFIG /etc/gitweb.conf +</VirtualHost> + +The above configuration expects your public repositories to live under +/pub/git and will serve them as http://git.domain.org/dir-under-pub-git, +both as cloneable GIT URL and as browseable gitweb interface. +If you then start your git-daemon with --base-path=/pub/git --export-all +then you can even use the git:// URL with exactly the same path. + +Setting the environment variable GITWEB_CONFIG will tell gitweb to use +the named file (i.e. in this example /etc/gitweb.conf) as a +configuration for gitweb. Perl variables defined in here will +override the defaults given at the head of the gitweb.perl (or +gitweb.cgi). Look at the comments in that file for information on +which variables and what they mean. + + +Originally written by: Kay Sievers <kay.sievers@vrfy.org> + +Any comment/question/concern to: + Git mailing list <git@vger.kernel.org> + diff --git a/gitweb/git-favicon.png b/gitweb/git-favicon.png Binary files differnew file mode 100644 index 0000000000..de637c0608 --- /dev/null +++ b/gitweb/git-favicon.png diff --git a/gitweb/git-logo.png b/gitweb/git-logo.png Binary files differnew file mode 100644 index 0000000000..16ae8d5382 --- /dev/null +++ b/gitweb/git-logo.png diff --git a/gitweb/gitweb.cgi b/gitweb/gitweb.cgi deleted file mode 100755 index e5fca63b9c..0000000000 --- a/gitweb/gitweb.cgi +++ /dev/null @@ -1,2696 +0,0 @@ -#!/usr/bin/perl - -# gitweb - simple web interface to track changes in git repositories -# -# (C) 2005-2006, Kay Sievers <kay.sievers@vrfy.org> -# (C) 2005, Christian Gierke -# -# This program is licensed under the GPLv2 - -use strict; -use warnings; -use CGI qw(:standard :escapeHTML -nosticky); -use CGI::Util qw(unescape); -use CGI::Carp qw(fatalsToBrowser); -use Encode; -use Fcntl ':mode'; -binmode STDOUT, ':utf8'; - -our $cgi = new CGI; -our $version = "267"; -our $my_url = $cgi->url(); -our $my_uri = $cgi->url(-absolute => 1); -our $rss_link = ""; - -# core git executable to use -# this can just be "git" if your webserver has a sensible PATH -our $GIT = "/usr/bin/git"; - -# absolute fs-path which will be prepended to the project path -#our $projectroot = "/pub/scm"; -our $projectroot = "/home/kay/public_html/pub/scm"; - -# version of the core git binary -our $git_version = qx($GIT --version) =~ m/git version (.*)$/ ? $1 : "unknown"; - -# location for temporary files needed for diffs -our $git_temp = "/tmp/gitweb"; -if (! -d $git_temp) { - mkdir($git_temp, 0700) || die_error("Couldn't mkdir $git_temp"); -} - -# target of the home link on top of all pages -our $home_link = $my_uri; - -# name of your site or organization to appear in page titles -# replace this with something more descriptive for clearer bookmarks -our $site_name = $ENV{'SERVER_NAME'} || "Untitled"; - -# html text to include at home page -our $home_text = "indextext.html"; - -# URI of default stylesheet -our $stylesheet = "gitweb.css"; - -# source of projects list -#our $projects_list = $projectroot; -our $projects_list = "index/index.aux"; - -# default blob_plain mimetype and default charset for text/plain blob -our $default_blob_plain_mimetype = 'text/plain'; -our $default_text_plain_charset = undef; - -# file to use for guessing MIME types before trying /etc/mime.types -# (relative to the current git repository) -our $mimetypes_file = undef; - -# input validation and dispatch -our $action = $cgi->param('a'); -if (defined $action) { - if ($action =~ m/[^0-9a-zA-Z\.\-_]/) { - undef $action; - die_error(undef, "Invalid action parameter."); - } - if ($action eq "git-logo.png") { - git_logo(); - exit; - } elsif ($action eq "opml") { - git_opml(); - exit; - } -} - -our $order = $cgi->param('o'); -if (defined $order) { - if ($order =~ m/[^0-9a-zA-Z_]/) { - undef $order; - die_error(undef, "Invalid order parameter."); - } -} - -our $project = ($cgi->param('p') || $ENV{'PATH_INFO'}); -if (defined $project) { - $project =~ s|^/||; $project =~ s|/$||; - $project = validate_input($project); - if (!defined($project)) { - die_error(undef, "Invalid project parameter."); - } - if (!(-d "$projectroot/$project")) { - undef $project; - die_error(undef, "No such directory."); - } - if (!(-e "$projectroot/$project/HEAD")) { - undef $project; - die_error(undef, "No such project."); - } - $rss_link = "<link rel=\"alternate\" title=\"" . esc_param($project) . " log\" href=\"" . - "$my_uri?" . esc_param("p=$project;a=rss") . "\" type=\"application/rss+xml\"/>"; - $ENV{'GIT_DIR'} = "$projectroot/$project"; -} else { - git_project_list(); - exit; -} - -our $file_name = $cgi->param('f'); -if (defined $file_name) { - $file_name = validate_input($file_name); - if (!defined($file_name)) { - die_error(undef, "Invalid file parameter."); - } -} - -our $hash = $cgi->param('h'); -if (defined $hash) { - $hash = validate_input($hash); - if (!defined($hash)) { - die_error(undef, "Invalid hash parameter."); - } -} - -our $hash_parent = $cgi->param('hp'); -if (defined $hash_parent) { - $hash_parent = validate_input($hash_parent); - if (!defined($hash_parent)) { - die_error(undef, "Invalid hash parent parameter."); - } -} - -our $hash_base = $cgi->param('hb'); -if (defined $hash_base) { - $hash_base = validate_input($hash_base); - if (!defined($hash_base)) { - die_error(undef, "Invalid hash base parameter."); - } -} - -our $page = $cgi->param('pg'); -if (defined $page) { - if ($page =~ m/[^0-9]$/) { - undef $page; - die_error(undef, "Invalid page parameter."); - } -} - -our $searchtext = $cgi->param('s'); -if (defined $searchtext) { - if ($searchtext =~ m/[^a-zA-Z0-9_\.\/\-\+\:\@ ]/) { - undef $searchtext; - die_error(undef, "Invalid search parameter."); - } - $searchtext = quotemeta $searchtext; -} - -sub validate_input { - my $input = shift; - - if ($input =~ m/^[0-9a-fA-F]{40}$/) { - return $input; - } - if ($input =~ m/(^|\/)(|\.|\.\.)($|\/)/) { - return undef; - } - if ($input =~ m/[^a-zA-Z0-9_\x80-\xff\ \t\.\/\-\+\#\~\%]/) { - return undef; - } - return $input; -} - -if (!defined $action || $action eq "summary") { - git_summary(); - exit; -} elsif ($action eq "heads") { - git_heads(); - exit; -} elsif ($action eq "tags") { - git_tags(); - exit; -} elsif ($action eq "blob") { - git_blob(); - exit; -} elsif ($action eq "blob_plain") { - git_blob_plain(); - exit; -} elsif ($action eq "tree") { - git_tree(); - exit; -} elsif ($action eq "rss") { - git_rss(); - exit; -} elsif ($action eq "commit") { - git_commit(); - exit; -} elsif ($action eq "log") { - git_log(); - exit; -} elsif ($action eq "blobdiff") { - git_blobdiff(); - exit; -} elsif ($action eq "blobdiff_plain") { - git_blobdiff_plain(); - exit; -} elsif ($action eq "commitdiff") { - git_commitdiff(); - exit; -} elsif ($action eq "commitdiff_plain") { - git_commitdiff_plain(); - exit; -} elsif ($action eq "history") { - git_history(); - exit; -} elsif ($action eq "search") { - git_search(); - exit; -} elsif ($action eq "shortlog") { - git_shortlog(); - exit; -} elsif ($action eq "tag") { - git_tag(); - exit; -} elsif ($action eq "blame") { - git_blame2(); - exit; -} else { - undef $action; - die_error(undef, "Unknown action."); - exit; -} - -# quote unsafe chars, but keep the slash, even when it's not -# correct, but quoted slashes look too horrible in bookmarks -sub esc_param { - my $str = shift; - $str =~ s/([^A-Za-z0-9\-_.~();\/;?:@&=])/sprintf("%%%02X", ord($1))/eg; - $str =~ s/\+/%2B/g; - $str =~ s/ /\+/g; - return $str; -} - -# replace invalid utf8 character with SUBSTITUTION sequence -sub esc_html { - my $str = shift; - $str = decode("utf8", $str, Encode::FB_DEFAULT); - $str = escapeHTML($str); - return $str; -} - -# git may return quoted and escaped filenames -sub unquote { - my $str = shift; - if ($str =~ m/^"(.*)"$/) { - $str = $1; - $str =~ s/\\([0-7]{1,3})/chr(oct($1))/eg; - } - return $str; -} - -# CSS class for given age value (in seconds) -sub age_class { - my $age = shift; - - if ($age < 60*60*2) { - return "age0"; - } elsif ($age < 60*60*24*2) { - return "age1"; - } else { - return "age2"; - } -} - -sub git_header_html { - my $status = shift || "200 OK"; - my $expires = shift; - - my $title = "$site_name git"; - if (defined $project) { - $title .= " - $project"; - if (defined $action) { - $title .= "/$action"; - if (defined $file_name) { - $title .= " - $file_name"; - if ($action eq "tree" && $file_name !~ m|/$|) { - $title .= "/"; - } - } - } - } - my $content_type; - # require explicit support from the UA if we are to send the page as - # '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 ($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 <<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 core binaries version $git_version --> -<head> -<meta http-equiv="content-type" content="$content_type; charset=utf-8"/> -<meta name="robots" content="index, nofollow"/> -<title>$title</title> -<link rel="stylesheet" type="text/css" href="$stylesheet"/> -$rss_link -</head> -<body> -EOF - print "<div class=\"page_header\">\n" . - "<a href=\"http://www.kernel.org/pub/software/scm/git/docs/\" title=\"git documentation\">" . - "<img src=\"$my_uri?" . esc_param("a=git-logo.png") . "\" width=\"72\" height=\"27\" alt=\"git\" style=\"float:right; border-width:0px;\"/>" . - "</a>\n"; - print $cgi->a({-href => esc_param($home_link)}, "projects") . " / "; - if (defined $project) { - print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary")}, esc_html($project)); - if (defined $action) { - print " / $action"; - } - print "\n"; - if (!defined $searchtext) { - $searchtext = ""; - } - my $search_hash; - if (defined $hash_base) { - $search_hash = $hash_base; - } elsif (defined $hash) { - $search_hash = $hash; - } else { - $search_hash = "HEAD"; - } - $cgi->param("a", "search"); - $cgi->param("h", $search_hash); - print $cgi->startform(-method => "get", -action => $my_uri) . - "<div class=\"search\">\n" . - $cgi->hidden(-name => "p") . "\n" . - $cgi->hidden(-name => "a") . "\n" . - $cgi->hidden(-name => "h") . "\n" . - $cgi->textfield(-name => "s", -value => $searchtext) . "\n" . - "</div>" . - $cgi->end_form() . "\n"; - } - print "</div>\n"; -} - -sub git_footer_html { - print "<div class=\"page_footer\">\n"; - if (defined $project) { - my $descr = git_read_description($project); - if (defined $descr) { - print "<div class=\"page_footer_text\">" . esc_html($descr) . "</div>\n"; - } - print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=rss"), -class => "rss_logo"}, "RSS") . "\n"; - } else { - print $cgi->a({-href => "$my_uri?" . esc_param("a=opml"), -class => "rss_logo"}, "OPML") . "\n"; - } - print "</div>\n" . - "</body>\n" . - "</html>"; -} - -sub die_error { - my $status = shift || "403 Forbidden"; - 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"; - git_footer_html(); - exit; -} - -sub git_get_type { - my $hash = shift; - - open my $fd, "-|", "$GIT cat-file -t $hash" or return; - my $type = <$fd>; - close $fd or return; - chomp $type; - return $type; -} - -sub git_read_head { - my $project = shift; - my $oENV = $ENV{'GIT_DIR'}; - my $retval = undef; - $ENV{'GIT_DIR'} = "$projectroot/$project"; - if (open my $fd, "-|", $GIT, "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; - } - return $retval; -} - -sub git_read_hash { - 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_read_description { - my $path = shift; - - open my $fd, "$projectroot/$path/description" or return undef; - my $descr = <$fd>; - close $fd; - chomp $descr; - return $descr; -} - -sub git_read_tag { - my $tag_id = shift; - my %tag; - my @comment; - - open my $fd, "-|", "$GIT cat-file tag $tag_id" or return; - $tag{'id'} = $tag_id; - while (my $line = <$fd>) { - chomp $line; - if ($line =~ m/^object ([0-9a-fA-F]{40})$/) { - $tag{'object'} = $1; - } elsif ($line =~ m/^type (.+)$/) { - $tag{'type'} = $1; - } elsif ($line =~ m/^tag (.+)$/) { - $tag{'name'} = $1; - } elsif ($line =~ m/^tagger (.*) ([0-9]+) (.*)$/) { - $tag{'author'} = $1; - $tag{'epoch'} = $2; - $tag{'tz'} = $3; - } elsif ($line =~ m/--BEGIN/) { - push @comment, $line; - last; - } elsif ($line eq "") { - last; - } - } - push @comment, <$fd>; - $tag{'comment'} = \@comment; - close $fd or return; - if (!defined $tag{'name'}) { - return - }; - return %tag -} - -sub age_string { - my $age = shift; - my $age_str; - - if ($age > 60*60*24*365*2) { - $age_str = (int $age/60/60/24/365); - $age_str .= " years ago"; - } elsif ($age > 60*60*24*(365/12)*2) { - $age_str = int $age/60/60/24/(365/12); - $age_str .= " months ago"; - } elsif ($age > 60*60*24*7*2) { - $age_str = int $age/60/60/24/7; - $age_str .= " weeks ago"; - } elsif ($age > 60*60*24*2) { - $age_str = int $age/60/60/24; - $age_str .= " days ago"; - } elsif ($age > 60*60*2) { - $age_str = int $age/60/60; - $age_str .= " hours ago"; - } elsif ($age > 60*2) { - $age_str = int $age/60; - $age_str .= " min ago"; - } elsif ($age > 2) { - $age_str = int $age; - $age_str .= " sec ago"; - } else { - $age_str .= " right now"; - } - return $age_str; -} - -sub git_read_commit { - my $commit_id = shift; - my $commit_text = shift; - - my @commit_lines; - my %co; - - if (defined $commit_text) { - @commit_lines = @$commit_text; - } else { - $/ = "\0"; - open my $fd, "-|", "$GIT rev-list --header --parents --max-count=1 $commit_id" or return; - @commit_lines = split '\n', <$fd>; - close $fd or return; - $/ = "\n"; - pop @commit_lines; - } - my $header = shift @commit_lines; - if (!($header =~ m/^[0-9a-fA-F]{40}/)) { - return; - } - ($co{'id'}, my @parents) = split ' ', $header; - $co{'parents'} = \@parents; - $co{'parent'} = $parents[0]; - while (my $line = shift @commit_lines) { - last if $line eq "\n"; - if ($line =~ m/^tree ([0-9a-fA-F]{40})$/) { - $co{'tree'} = $1; - } elsif ($line =~ m/^author (.*) ([0-9]+) (.*)$/) { - $co{'author'} = $1; - $co{'author_epoch'} = $2; - $co{'author_tz'} = $3; - if ($co{'author'} =~ m/^([^<]+) </) { - $co{'author_name'} = $1; - } else { - $co{'author_name'} = $co{'author'}; - } - } elsif ($line =~ m/^committer (.*) ([0-9]+) (.*)$/) { - $co{'committer'} = $1; - $co{'committer_epoch'} = $2; - $co{'committer_tz'} = $3; - $co{'committer_name'} = $co{'committer'}; - $co{'committer_name'} =~ s/ <.*//; - } - } - if (!defined $co{'tree'}) { - return; - }; - - foreach my $title (@commit_lines) { - $title =~ s/^ //; - if ($title ne "") { - $co{'title'} = chop_str($title, 80, 5); - # remove leading stuff of merges to make the interesting part visible - if (length($title) > 50) { - $title =~ s/^Automatic //; - $title =~ s/^merge (of|with) /Merge ... /i; - if (length($title) > 50) { - $title =~ s/(http|rsync):\/\///; - } - if (length($title) > 50) { - $title =~ s/(master|www|rsync)\.//; - } - if (length($title) > 50) { - $title =~ s/kernel.org:?//; - } - if (length($title) > 50) { - $title =~ s/\/pub\/scm//; - } - } - $co{'title_short'} = chop_str($title, 50, 5); - last; - } - } - # remove added spaces - foreach my $line (@commit_lines) { - $line =~ s/^ //; - } - $co{'comment'} = \@commit_lines; - - my $age = time - $co{'committer_epoch'}; - $co{'age'} = $age; - $co{'age_string'} = age_string($age); - my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday) = gmtime($co{'committer_epoch'}); - if ($age > 60*60*24*7*2) { - $co{'age_string_date'} = sprintf "%4i-%02u-%02i", 1900 + $year, $mon+1, $mday; - $co{'age_string_age'} = $co{'age_string'}; - } else { - $co{'age_string_date'} = $co{'age_string'}; - $co{'age_string_age'} = sprintf "%4i-%02u-%02i", 1900 + $year, $mon+1, $mday; - } - return %co; -} - -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; - } - while ((my $pos = index($line, "\t")) != -1) { - if (my $count = (8 - (($pos-1) % 8))) { - my $spaces = ' ' x $count; - $line =~ s/\t/$spaces/; - } - } - 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); - } -} - -sub mode_str { - my $mode = oct shift; - - if (S_ISDIR($mode & S_IFMT)) { - return 'drwxr-xr-x'; - } elsif (S_ISLNK($mode)) { - return 'lrwxrwxrwx'; - } elsif (S_ISREG($mode)) { - # git cares only about the executable bit - if ($mode & S_IXUSR) { - return '-rwxr-xr-x'; - } else { - return '-rw-r--r--'; - }; - } else { - return '----------'; - } -} - -sub chop_str { - my $str = shift; - my $len = shift; - my $add_len = shift || 10; - - # allow only $len chars, but don't cut a word if it would fit in $add_len - # if it doesn't fit, cut it if it's still longer than the dots we would add - $str =~ m/^(.{0,$len}[^ \/\-_:\.@]{0,$add_len})(.*)/; - my $body = $1; - my $tail = $2; - if (length($tail) > 4) { - $tail = " ..."; - } - return "$body$tail"; -} - -sub file_type { - my $mode = oct shift; - - if (S_ISDIR($mode & S_IFMT)) { - return "directory"; - } elsif (S_ISLNK($mode)) { - return "symlink"; - } elsif (S_ISREG($mode)) { - return "file"; - } else { - return "unknown"; - } -} - -sub format_log_line_html { - my $line = shift; - - $line = esc_html($line); - $line =~ s/ / /g; - 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); - $line =~ s/$hash_text/$link/; - } - } - return $line; -} - -sub date_str { - my $epoch = shift; - my $tz = shift || "-0000"; - - my %date; - my @months = ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"); - my @days = ("Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"); - my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday) = gmtime($epoch); - $date{'hour'} = $hour; - $date{'minute'} = $min; - $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; - - $tz =~ m/^([+\-][0-9][0-9])([0-9][0-9])$/; - my $local = $epoch + ((int $1 + ($2/60)) * 3600); - ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday) = gmtime($local); - $date{'hour_local'} = $hour; - $date{'minute_local'} = $min; - $date{'tz_local'} = $tz; - return %date; -} - -# git-logo (cached in browser for one day) -sub git_logo { - binmode STDOUT, ':raw'; - print $cgi->header(-type => 'image/png', -expires => '+1d'); - # cat git-logo.png | hexdump -e '16/1 " %02x" "\n"' | sed 's/ /\\x/g' - print "\x89\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52" . - "\x00\x00\x00\x48\x00\x00\x00\x1b\x04\x03\x00\x00\x00\x2d\xd9\xd4" . - "\x2d\x00\x00\x00\x18\x50\x4c\x54\x45\xff\xff\xff\x60\x60\x5d\xb0" . - "\xaf\xaa\x00\x80\x00\xce\xcd\xc7\xc0\x00\x00\xe8\xe8\xe6\xf7\xf7" . - "\xf6\x95\x0c\xa7\x47\x00\x00\x00\x73\x49\x44\x41\x54\x28\xcf\x63" . - "\x48\x67\x20\x04\x4a\x5c\x18\x0a\x08\x2a\x62\x53\x61\x20\x02\x08" . - "\x0d\x69\x45\xac\xa1\xa1\x01\x30\x0c\x93\x60\x36\x26\x52\x91\xb1" . - "\x01\x11\xd6\xe1\x55\x64\x6c\x6c\xcc\x6c\x6c\x0c\xa2\x0c\x70\x2a" . - "\x62\x06\x2a\xc1\x62\x1d\xb3\x01\x02\x53\xa4\x08\xe8\x00\x03\x18" . - "\x26\x56\x11\xd4\xe1\x20\x97\x1b\xe0\xb4\x0e\x35\x24\x71\x29\x82" . - "\x99\x30\xb8\x93\x0a\x11\xb9\x45\x88\xc1\x8d\xa0\xa2\x44\x21\x06" . - "\x27\x41\x82\x40\x85\xc1\x45\x89\x20\x70\x01\x00\xa4\x3d\x21\xc5" . - "\x12\x1c\x9a\xfe\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82"; -} - -sub get_file_owner { - my $path = shift; - - my ($dev, $ino, $mode, $nlink, $st_uid, $st_gid, $rdev, $size) = stat($path); - my ($name, $passwd, $uid, $gid, $quota, $comment, $gcos, $dir, $shell) = getpwuid($st_uid); - if (!defined $gcos) { - return undef; - } - my $owner = $gcos; - $owner =~ s/[,;].*$//; - return decode("utf8", $owner, Encode::FB_DEFAULT); -} - -sub git_read_projects { - 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); - } 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; - while (my $line = <$fd>) { - chomp $line; - my ($path, $owner) = split ' ', $line; - $path = unescape($path); - $owner = unescape($owner); - if (!defined $path) { - next; - } - if (-e "$projectroot/$path/HEAD") { - my $pr = { - path => $path, - owner => decode("utf8", $owner, Encode::FB_DEFAULT), - }; - push @list, $pr - } - } - close $fd; - } - @list = sort {$a->{'path'} cmp $b->{'path'}} @list; - return @list; -} - -sub git_get_project_config { - my $key = shift; - - return unless ($key); - $key =~ s/^gitweb\.//; - return if ($key =~ m/\W/); - - my $val = qx($GIT repo-config --get gitweb.$key); - 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 -} - -sub git_project_list { - my @list = git_read_projects(); - my @projects; - if (!@list) { - die_error(undef, "No project found."); - } - foreach my $pr (@list) { - my $head = git_read_head($pr->{'path'}); - if (!defined $head) { - next; - } - $ENV{'GIT_DIR'} = "$projectroot/$pr->{'path'}"; - my %co = git_read_commit($head); - if (!%co) { - next; - } - $pr->{'commit'} = \%co; - if (!defined $pr->{'descr'}) { - my $descr = git_read_description($pr->{'path'}) || ""; - $pr->{'descr'} = chop_str($descr, 25, 5); - } - if (!defined $pr->{'owner'}) { - $pr->{'owner'} = get_file_owner("$projectroot/$pr->{'path'}") || ""; - } - push @projects, $pr; - } - git_header_html(); - if (-f $home_text) { - print "<div class=\"index_include\">\n"; - open (my $fd, $home_text); - print <$fd>; - close $fd; - print "</div>\n"; - } - print "<table class=\"project_list\">\n" . - "<tr>\n"; - if (!defined($order) || (defined($order) && ($order eq "project"))) { - @projects = sort {$a->{'path'} cmp $b->{'path'}} @projects; - print "<th>Project</th>\n"; - } else { - print "<th>" . $cgi->a({-class => "header", -href => "$my_uri?" . esc_param("o=project")}, "Project") . "</th>\n"; - } - if (defined($order) && ($order eq "descr")) { - @projects = sort {$a->{'descr'} cmp $b->{'descr'}} @projects; - print "<th>Description</th>\n"; - } else { - print "<th>" . $cgi->a({-class => "header", -href => "$my_uri?" . esc_param("o=descr")}, "Description") . "</th>\n"; - } - if (defined($order) && ($order eq "owner")) { - @projects = sort {$a->{'owner'} cmp $b->{'owner'}} @projects; - print "<th>Owner</th>\n"; - } else { - print "<th>" . $cgi->a({-class => "header", -href => "$my_uri?" . esc_param("o=owner")}, "Owner") . "</th>\n"; - } - if (defined($order) && ($order eq "age")) { - @projects = sort {$a->{'commit'}{'age'} <=> $b->{'commit'}{'age'}} @projects; - print "<th>Last Change</th>\n"; - } else { - print "<th>" . $cgi->a({-class => "header", -href => "$my_uri?" . esc_param("o=age")}, "Last Change") . "</th>\n"; - } - print "<th></th>\n" . - "</tr>\n"; - my $alternate = 0; - foreach my $pr (@projects) { - if ($alternate) { - print "<tr class=\"dark\">\n"; - } else { - print "<tr class=\"light\">\n"; - } - $alternate ^= 1; - print "<td>" . $cgi->a({-href => "$my_uri?" . esc_param("p=$pr->{'path'};a=summary"), -class => "list"}, esc_html($pr->{'path'})) . "</td>\n" . - "<td>$pr->{'descr'}</td>\n" . - "<td><i>" . chop_str($pr->{'owner'}, 15) . "</i></td>\n"; - print "<td class=\"". age_class($pr->{'commit'}{'age'}) . "\">" . $pr->{'commit'}{'age_string'} . "</td>\n" . - "<td class=\"link\">" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$pr->{'path'};a=summary")}, "summary") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$pr->{'path'};a=shortlog")}, "shortlog") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$pr->{'path'};a=log")}, "log") . - "</td>\n" . - "</tr>\n"; - } - print "</table>\n"; - git_footer_html(); -} - -sub read_info_ref { - my $type = shift || ""; - my %refs; - # 5dc01c595e6c6ec9ccda4f6f69c131c0dd945f8c refs/tags/v2.6.11 - # c39ae07f393806ccf406ef966e9a15afc43cc36a refs/tags/v2.6.11^{} - open my $fd, "$projectroot/$project/info/refs" or return; - while (my $line = <$fd>) { - chomp($line); - if ($line =~ m/^([0-9a-fA-F]{40})\t.*$type\/([^\^]+)/) { - if (defined $refs{$1}) { - $refs{$1} .= " / $2"; - } else { - $refs{$1} = $2; - } - } - } - close $fd or return; - return \%refs; -} - -sub git_read_refs { - my $ref_dir = shift; - my @reflist; - - my @refs; - opendir my $dh, "$projectroot/$project/$ref_dir"; - while (my $dir = readdir($dh)) { - if ($dir =~ m/^\./) { - next; - } - if (-d "$projectroot/$project/$ref_dir/$dir") { - opendir my $dh2, "$projectroot/$project/$ref_dir/$dir"; - my @subdirs = grep !m/^\./, readdir $dh2; - closedir($dh2); - foreach my $subdir (@subdirs) { - push @refs, "$dir/$subdir" - } - next; - } - push @refs, $dir; - } - closedir($dh); - foreach my $ref_file (@refs) { - my $ref_id = git_read_hash("$project/$ref_dir/$ref_file"); - my $type = git_get_type($ref_id) || next; - my %ref_item; - my %co; - $ref_item{'type'} = $type; - $ref_item{'id'} = $ref_id; - $ref_item{'epoch'} = 0; - $ref_item{'age'} = "unknown"; - if ($type eq "tag") { - my %tag = git_read_tag($ref_id); - $ref_item{'comment'} = $tag{'comment'}; - if ($tag{'type'} eq "commit") { - %co = git_read_commit($tag{'object'}); - $ref_item{'epoch'} = $co{'committer_epoch'}; - $ref_item{'age'} = $co{'age_string'}; - } elsif (defined($tag{'epoch'})) { - my $age = time - $tag{'epoch'}; - $ref_item{'epoch'} = $tag{'epoch'}; - $ref_item{'age'} = age_string($age); - } - $ref_item{'reftype'} = $tag{'type'}; - $ref_item{'name'} = $tag{'name'}; - $ref_item{'refid'} = $tag{'object'}; - } elsif ($type eq "commit"){ - %co = git_read_commit($ref_id); - $ref_item{'reftype'} = "commit"; - $ref_item{'name'} = $ref_file; - $ref_item{'title'} = $co{'title'}; - $ref_item{'refid'} = $ref_id; - $ref_item{'epoch'} = $co{'committer_epoch'}; - $ref_item{'age'} = $co{'age_string'}; - } - - push @reflist, \%ref_item; - } - # sort tags by age - @reflist = sort {$b->{'epoch'} <=> $a->{'epoch'}} @reflist; - return \@reflist; -} - -sub git_summary { - my $descr = git_read_description($project) || "none"; - my $head = git_read_head($project); - my %co = git_read_commit($head); - my %cd = date_str($co{'committer_epoch'}, $co{'committer_tz'}); - - my $owner; - if (-f $projects_list) { - open (my $fd , $projects_list); - while (my $line = <$fd>) { - chomp $line; - my ($pr, $ow) = split ' ', $line; - $pr = unescape($pr); - $ow = unescape($ow); - if ($pr eq $project) { - $owner = decode("utf8", $ow, Encode::FB_DEFAULT); - last; - } - } - close $fd; - } - if (!defined $owner) { - $owner = get_file_owner("$projectroot/$project"); - } - - my $refs = read_info_ref(); - git_header_html(); - print "<div class=\"page_nav\">\n" . - "summary". - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog")}, "shortlog") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log")}, "log") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$head")}, "commit") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$head")}, "commitdiff") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree")}, "tree") . - "<br/><br/>\n" . - "</div>\n"; - print "<div class=\"title\"> </div>\n"; - print "<table cellspacing=\"0\">\n" . - "<tr><td>description</td><td>" . esc_html($descr) . "</td></tr>\n" . - "<tr><td>owner</td><td>$owner</td></tr>\n" . - "<tr><td>last change</td><td>$cd{'rfc2822'}</td></tr>\n" . - "</table>\n"; - open my $fd, "-|", "$GIT rev-list --max-count=17 " . git_read_head($project) or die_error(undef, "Open failed."); - my (@revlist) = map { chomp; $_ } <$fd>; - close $fd; - print "<div>\n" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog"), -class => "title"}, "shortlog") . - "</div>\n"; - my $i = 16; - print "<table cellspacing=\"0\">\n"; - my $alternate = 0; - foreach my $commit (@revlist) { - my %co = git_read_commit($commit); - my %ad = date_str($co{'author_epoch'}); - if ($alternate) { - print "<tr class=\"dark\">\n"; - } else { - print "<tr class=\"light\">\n"; - } - $alternate ^= 1; - if ($i-- > 0) { - my $ref = ""; - if (defined $refs->{$commit}) { - $ref = " <span class=\"tag\">" . esc_html($refs->{$commit}) . "</span>"; - } - print "<td><i>$co{'age_string'}</i></td>\n" . - "<td><i>" . esc_html(chop_str($co{'author_name'}, 10)) . "</i></td>\n" . - "<td>"; - if (length($co{'title_short'}) < length($co{'title'})) { - print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$commit"), -class => "list", -title => "$co{'title'}"}, - "<b>" . esc_html($co{'title_short'}) . "$ref</b>"); - } else { - print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$commit"), -class => "list"}, - "<b>" . esc_html($co{'title'}) . "$ref</b>"); - } - print "</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" . - "</tr>"; - } else { - print "<td>" . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog")}, "...") . "</td>\n" . - "</tr>"; - last; - } - } - print "</table\n>"; - - my $taglist = git_read_refs("refs/tags"); - if (defined @$taglist) { - print "<div>\n" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tags"), -class => "title"}, "tags") . - "</div>\n"; - my $i = 16; - print "<table cellspacing=\"0\">\n"; - my $alternate = 0; - foreach my $entry (@$taglist) { - my %tag = %$entry; - my $comment_lines = $tag{'comment'}; - my $comment = shift @$comment_lines; - if (defined($comment)) { - $comment = chop_str($comment, 30, 5); - } - if ($alternate) { - print "<tr class=\"dark\">\n"; - } else { - print "<tr class=\"light\">\n"; - } - $alternate ^= 1; - if ($i-- > 0) { - 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>") . - "</td>\n" . - "<td>"; - if (defined($comment)) { - print $cgi->a({-class => "list", -href => "$my_uri?" . esc_param("p=$project;a=tag;h=$tag{'id'}")}, esc_html($comment)); - } - print "</td>\n" . - "<td class=\"link\">"; - 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 => "$my_uri?" . esc_param("p=$project;a=$tag{'reftype'};h=$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 "</td>\n" . - "</tr>"; - } else { - print "<td>" . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tags")}, "...") . "</td>\n" . - "</tr>"; - last; - } - } - print "</table\n>"; - } - - my $headlist = git_read_refs("refs/heads"); - if (defined @$headlist) { - print "<div>\n" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=heads"), -class => "title"}, "heads") . - "</div>\n"; - my $i = 16; - print "<table cellspacing=\"0\">\n"; - my $alternate = 0; - foreach my $entry (@$headlist) { - my %tag = %$entry; - if ($alternate) { - print "<tr class=\"dark\">\n"; - } else { - print "<tr class=\"light\">\n"; - } - $alternate ^= 1; - if ($i-- > 0) { - print "<td><i>$tag{'age'}</i></td>\n" . - "<td>" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog;h=$tag{'name'}"), -class => "list"}, - "<b>" . esc_html($tag{'name'}) . "</b>") . - "</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") . - "</td>\n" . - "</tr>"; - } else { - print "<td>" . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=heads")}, "...") . "</td>\n" . - "</tr>"; - last; - } - } - print "</table\n>"; - } - git_footer_html(); -} - -sub git_print_page_path { - my $name = shift; - my $type = shift; - - if (!defined $name) { - print "<div class=\"page_path\"><b>/</b></div>\n"; - } elsif ($type =~ "blob") { - print "<div class=\"page_path\"><b>" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob_plain;f=$file_name")}, esc_html($name)) . "</b><br/></div>\n"; - } else { - print "<div class=\"page_path\"><b>" . esc_html($name) . "</b><br/></div>\n"; - } -} - -sub git_tag { - my $head = git_read_head($project); - git_header_html(); - print "<div class=\"page_nav\">\n" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary")}, "summary") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog")}, "shortlog") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log")}, "log") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$head")}, "commit") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$head")}, "commitdiff") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;hb=$head")}, "tree") . "<br/>\n" . - "<br/>\n" . - "</div>\n"; - my %tag = git_read_tag($hash); - print "<div>\n" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash"), -class => "title"}, esc_html($tag{'name'})) . "\n" . - "</div>\n"; - print "<div class=\"title_text\">\n" . - "<table cellspacing=\"0\">\n" . - "<tr>\n" . - "<td>object</td>\n" . - "<td>" . $cgi->a({-class => "list", -href => "$my_uri?" . esc_param("p=$project;a=$tag{'type'};h=$tag{'object'}")}, $tag{'object'}) . "</td>\n" . - "<td class=\"link\">" . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$tag{'type'};h=$tag{'object'}")}, $tag{'type'}) . "</td>\n" . - "</tr>\n"; - if (defined($tag{'author'})) { - my %ad = date_str($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 "</table>\n\n" . - "</div>\n"; - print "<div class=\"page_body\">"; - my $comment = $tag{'comment'}; - foreach my $line (@$comment) { - print esc_html($line) . "<br/>\n"; - } - print "</div>\n"; - git_footer_html(); -} - -sub git_blame2 { - my $fd; - my $ftype; - die_error(undef, "Permission denied.") if (!git_get_project_config_bool ('blame')); - die_error('404 Not Found', "File name not defined") if (!$file_name); - $hash_base ||= git_read_head($project); - die_error(undef, "Reading commit failed") unless ($hash_base); - my %co = git_read_commit($hash_base) - or die_error(undef, "Reading commit failed"); - if (!defined $hash) { - $hash = git_get_hash_by_path($hash_base, $file_name, "blob") - or die_error(undef, "Error looking up file"); - } - $ftype = git_get_type($hash); - if ($ftype !~ "blob") { - die_error("400 Bad Request", "object is not a blob"); - } - open ($fd, "-|", $GIT, "blame", '-l', $file_name, $hash_base) - or die_error(undef, "Open failed"); - git_header_html(); - print "<div class=\"page_nav\">\n" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary")}, "summary") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog")}, "shortlog") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log")}, "log") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash_base")}, "commit") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$hash_base")}, "commitdiff") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;h=$co{'tree'};hb=$hash_base")}, "tree") . "<br/>\n"; - print $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") . "<br/>\n"; - print "</div>\n". - "<div>" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash_base"), -class => "title"}, esc_html($co{'title'})) . - "</div>\n"; - git_print_page_path($file_name, $ftype); - my @rev_color = (qw(light dark)); - my $num_colors = scalar(@rev_color); - my $current_color = 0; - my $last_rev; - print "<div class=\"page_body\">\n"; - print "<table class=\"blame\">\n"; - print "<tr><th>Commit</th><th>Line</th><th>Data</th></tr>\n"; - while (<$fd>) { - /^([0-9a-fA-F]{40}).*?(\d+)\)\s{1}(\s*.*)/; - my $full_rev = $1; - my $rev = substr($full_rev, 0, 8); - my $lineno = $2; - my $data = $3; - - if (!defined $last_rev) { - $last_rev = $full_rev; - } elsif ($last_rev ne $full_rev) { - $last_rev = $full_rev; - $current_color = ++$current_color % $num_colors; - } - print "<tr class=\"$rev_color[$current_color]\">\n"; - print "<td class=\"sha1\">" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$full_rev;f=$file_name")}, esc_html($rev)) . "</td>\n"; - print "<td class=\"linenr\"><a id=\"l$lineno\" href=\"#l$lineno\" class=\"linenr\">" . esc_html($lineno) . "</a></td>\n"; - 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"; - git_footer_html(); -} - -sub git_blame { - my $fd; - die_error('403 Permission denied', "Permission denied.") if (!git_get_project_config_bool ('blame')); - die_error('404 Not Found', "What file will it be, master?") if (!$file_name); - $hash_base ||= git_read_head($project); - die_error(undef, "Reading commit failed.") unless ($hash_base); - my %co = git_read_commit($hash_base) - or die_error(undef, "Reading commit failed."); - if (!defined $hash) { - $hash = git_get_hash_by_path($hash_base, $file_name, "blob") - or die_error(undef, "Error lookup file."); - } - open ($fd, "-|", $GIT, "annotate", '-l', '-t', '-r', $file_name, $hash_base) - or die_error(undef, "Open failed."); - git_header_html(); - print "<div class=\"page_nav\">\n" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary")}, "summary") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog")}, "shortlog") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log")}, "log") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash_base")}, "commit") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$hash_base")}, "commitdiff") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;h=$co{'tree'};hb=$hash_base")}, "tree") . "<br/>\n"; - print $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") . "<br/>\n"; - print "</div>\n". - "<div>" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash_base"), -class => "title"}, esc_html($co{'title'})) . - "</div>\n"; - git_print_page_path($file_name); - print "<div class=\"page_body\">\n"; - print <<HTML; -<table class="blame"> - <tr> - <th>Commit</th> - <th>Age</th> - <th>Author</th> - <th>Line</th> - <th>Data</th> - </tr> -HTML - my @line_class = (qw(light dark)); - my $line_class_len = scalar (@line_class); - my $line_class_num = $#line_class; - while (my $line = <$fd>) { - my $long_rev; - my $short_rev; - my $author; - my $time; - my $lineno; - my $data; - my $age; - my $age_str; - my $age_class; - - 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+)\)(.*)$/) { - $long_rev = $1; - $author = $2; - $time = $3; - $lineno = $4; - $data = $5; - } else { - print qq( <tr><td colspan="5" class="error">Unable to parse: $line</td></tr>\n); - next; - } - $short_rev = substr ($long_rev, 0, 8); - $age = time () - $time; - $age_str = age_string ($age); - $age_str =~ s/ / /g; - $age_class = age_class($age); - $author = esc_html ($author); - $author =~ s/ / /g; - # escape tabs - while ((my $pos = index($data, "\t")) != -1) { - if (my $count = (8 - ($pos % 8))) { - my $spaces = ' ' x $count; - $data =~ s/\t/$spaces/; - } - } - $data = esc_html ($data); - - 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="$age_class">$age_str</td> - <td>$author</td> - <td class="linenr"><a id="$lineno" href="#$lineno" class="linenr">$lineno</a></td> - <td class="pre">$data</td> - </tr> -HTML - } # while (my $line = <$fd>) - print "</table>\n\n"; - close $fd or print "Reading blob failed.\n"; - print "</div>"; - git_footer_html(); -} - -sub git_tags { - my $head = git_read_head($project); - git_header_html(); - print "<div class=\"page_nav\">\n" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary")}, "summary") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog")}, "shortlog") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log")}, "log") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$head")}, "commit") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$head")}, "commitdiff") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;hb=$head")}, "tree") . "<br/>\n" . - "<br/>\n" . - "</div>\n"; - my $taglist = git_read_refs("refs/tags"); - print "<div>\n" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary"), -class => "title"}, " ") . - "</div>\n"; - print "<table cellspacing=\"0\">\n"; - my $alternate = 0; - if (defined @$taglist) { - foreach my $entry (@$taglist) { - my %tag = %$entry; - my $comment_lines = $tag{'comment'}; - my $comment = shift @$comment_lines; - if (defined($comment)) { - $comment = chop_str($comment, 30, 5); - } - if ($alternate) { - print "<tr class=\"dark\">\n"; - } else { - print "<tr class=\"light\">\n"; - } - $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>") . - "</td>\n" . - "<td>"; - if (defined($comment)) { - print $cgi->a({-class => "list", -href => "$my_uri?" . esc_param("p=$project;a=tag;h=$tag{'id'}")}, $comment); - } - print "</td>\n" . - "<td class=\"link\">"; - 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 => "$my_uri?" . esc_param("p=$project;a=$tag{'reftype'};h=$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 "</td>\n" . - "</tr>"; - } - } - print "</table\n>"; - git_footer_html(); -} - -sub git_heads { - my $head = git_read_head($project); - git_header_html(); - print "<div class=\"page_nav\">\n" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary")}, "summary") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog")}, "shortlog") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log")}, "log") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$head")}, "commit") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$head")}, "commitdiff") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;hb=$head")}, "tree") . "<br/>\n" . - "<br/>\n" . - "</div>\n"; - my $taglist = git_read_refs("refs/heads"); - print "<div>\n" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary"), -class => "title"}, " ") . - "</div>\n"; - print "<table cellspacing=\"0\">\n"; - my $alternate = 0; - if (defined @$taglist) { - foreach my $entry (@$taglist) { - my %tag = %$entry; - if ($alternate) { - print "<tr class=\"dark\">\n"; - } else { - print "<tr class=\"light\">\n"; - } - $alternate ^= 1; - print "<td><i>$tag{'age'}</i></td>\n" . - "<td>" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog;h=$tag{'name'}"), -class => "list"}, "<b>" . esc_html($tag{'name'}) . "</b>") . - "</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") . - "</td>\n" . - "</tr>"; - } - } - print "</table\n>"; - git_footer_html(); -} - -sub git_get_hash_by_path { - my $base = shift; - my $path = shift || return undef; - - my $tree = $base; - my @parts = split '/', $path; - while (my $part = shift @parts) { - open my $fd, "-|", "$GIT ls-tree $tree" or die_error(undef, "Open git-ls-tree failed."); - my (@entries) = map { chomp; $_ } <$fd>; - close $fd or return undef; - 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(unquote($4)); - if ($t_name eq $part) { - if (!(@parts)) { - return $t_hash; - } - if ($t_type eq "tree") { - $tree = $t_hash; - } - last; - } - } - } -} - -sub mimetype_guess_file { - my $filename = shift; - my $mimemap = shift; - -r $mimemap or return undef; - - my %mimemap; - open(MIME, $mimemap) or return undef; - while (<MIME>) { - my ($mime, $exts) = split(/\t+/); - my @exts = split(/\s+/, $exts); - foreach my $ext (@exts) { - $mimemap{$ext} = $mime; - } - } - close(MIME); - - $filename =~ /\.(.*?)$/; - return $mimemap{$1}; -} - -sub mimetype_guess { - my $filename = shift; - my $mime; - $filename =~ /\./ or return undef; - - if ($mimetypes_file) { - my $file = $mimetypes_file; - #$file =~ m#^/# or $file = "$projectroot/$path/$file"; - $mime = mimetype_guess_file($filename, $file); - } - $mime ||= mimetype_guess_file($filename, '/etc/mime.types'); - return $mime; -} - -sub git_blob_plain_mimetype { - my $fd = shift; - my $filename = shift; - - if ($filename) { - my $mime = mimetype_guess($filename); - $mime and return $mime; - } - - # just in case - return $default_blob_plain_mimetype unless $fd; - - if (-T $fd) { - return 'text/plain' . - ($default_text_plain_charset ? '; charset='.$default_text_plain_charset : ''); - } elsif (! $filename) { - return 'application/octet-stream'; - } elsif ($filename =~ m/\.png$/i) { - return 'image/png'; - } elsif ($filename =~ m/\.gif$/i) { - return 'image/gif'; - } elsif ($filename =~ m/\.jpe?g$/i) { - return 'image/jpeg'; - } else { - return 'application/octet-stream'; - } -} - -sub git_blob_plain { - if (!defined $hash) { - if (defined $file_name) { - my $base = $hash_base || git_read_head($project); - $hash = git_get_hash_by_path($base, $file_name, "blob") || die_error(undef, "Error lookup file."); - } else { - die_error(undef, "No file name defined."); - } - } - my $type = shift; - open my $fd, "-|", "$GIT cat-file blob $hash" or die_error("Couldn't cat $file_name, $hash"); - - $type ||= git_blob_plain_mimetype($fd, $file_name); - - # save as filename, even when no $file_name is given - my $save_as = "$hash"; - if (defined $file_name) { - $save_as = $file_name; - } elsif ($type =~ m/^text\//) { - $save_as .= '.txt'; - } - - print $cgi->header(-type => "$type", '-content-disposition' => "inline; filename=\"$save_as\""); - undef $/; - binmode STDOUT, ':raw'; - print <$fd>; - binmode STDOUT, ':utf8'; # as set at the beginning of gitweb.cgi - $/ = "\n"; - close $fd; -} - -sub git_blob { - if (!defined $hash) { - if (defined $file_name) { - my $base = $hash_base || git_read_head($project); - $hash = git_get_hash_by_path($base, $file_name, "blob") || die_error(undef, "Error lookup file."); - } else { - die_error(undef, "No file name defined."); - } - } - my $have_blame = git_get_project_config_bool ('blame'); - open my $fd, "-|", "$GIT cat-file blob $hash" or die_error(undef, "Open failed."); - my $mimetype = git_blob_plain_mimetype($fd, $file_name); - if ($mimetype !~ m/^text\//) { - close $fd; - return git_blob_plain($mimetype); - } - git_header_html(); - if (defined $hash_base && (my %co = git_read_commit($hash_base))) { - print "<div class=\"page_nav\">\n" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary")}, "summary") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog")}, "shortlog") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log")}, "log") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash_base")}, "commit") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$hash_base")}, "commitdiff") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;h=$co{'tree'};hb=$hash_base")}, "tree") . "<br/>\n"; - if (defined $file_name) { - if ($have_blame) { - print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blame;h=$hash;hb=$hash_base;f=$file_name")}, "blame") . " | "; - } - print $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") . "<br/>\n"; - } else { - print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob_plain;h=$hash")}, "plain") . "<br/>\n"; - } - print "</div>\n". - "<div>" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash_base"), -class => "title"}, esc_html($co{'title'})) . - "</div>\n"; - } else { - print "<div class=\"page_nav\">\n" . - "<br/><br/></div>\n" . - "<div class=\"title\">$hash</div>\n"; - } - git_print_page_path($file_name, "blob"); - print "<div class=\"page_body\">\n"; - my $nr; - while (my $line = <$fd>) { - chomp $line; - $nr++; - while ((my $pos = index($line, "\t")) != -1) { - if (my $count = (8 - ($pos % 8))) { - my $spaces = ' ' x $count; - $line =~ s/\t/$spaces/; - } - } - 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"; - print "</div>"; - git_footer_html(); -} - -sub git_tree { - if (!defined $hash) { - $hash = git_read_head($project); - if (defined $file_name) { - my $base = $hash_base || $hash; - $hash = git_get_hash_by_path($base, $file_name, "tree"); - } - if (!defined $hash_base) { - $hash_base = $hash; - } - } - $/ = "\0"; - open my $fd, "-|", "$GIT ls-tree -z $hash" or die_error(undef, "Open git-ls-tree failed."); - chomp (my (@entries) = <$fd>); - close $fd or die_error(undef, "Reading tree failed."); - $/ = "\n"; - - my $refs = read_info_ref(); - my $ref = ""; - if (defined $refs->{$hash_base}) { - $ref = " <span class=\"tag\">" . esc_html($refs->{$hash_base}) . "</span>"; - } - git_header_html(); - my $base_key = ""; - my $base = ""; - if (defined $hash_base && (my %co = git_read_commit($hash_base))) { - $base_key = ";hb=$hash_base"; - print "<div class=\"page_nav\">\n" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary")}, "summary") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog;h=$hash_base")}, "shortlog") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log;h=$hash_base")}, "log") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash_base")}, "commit") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$hash_base")}, "commitdiff") . - " | tree" . - "<br/><br/>\n" . - "</div>\n"; - print "<div>\n" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash_base"), -class => "title"}, esc_html($co{'title'}) . $ref) . "\n" . - "</div>\n"; - } else { - print "<div class=\"page_nav\">\n"; - print "<br/><br/></div>\n"; - print "<div class=\"title\">$hash</div>\n"; - } - if (defined $file_name) { - $base = esc_html("$file_name/"); - } - git_print_page_path($file_name); - print "<div class=\"page_body\">\n"; - print "<table cellspacing=\"0\">\n"; - my $alternate = 0; - foreach my $line (@entries) { - #'100644 blob 0fa3f3a66fb6a137f6ec2c19351ed4d807070ffa panic.c' - $line =~ m/^([0-9]+) (.+) ([0-9a-fA-F]{40})\t(.+)$/; - my $t_mode = $1; - my $t_type = $2; - my $t_hash = $3; - my $t_name = validate_input($4); - 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") . -# " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blame;h=$t_hash$base_key;f=$base$t_name")}, "blame") . - " | " . $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"; - } - print "</tr>\n"; - } - print "</table>\n" . - "</div>"; - git_footer_html(); -} - -sub git_rss { - # http://www.notestips.com/80256B3A007F2692/1/NAMO5P9UPQ - open my $fd, "-|", "$GIT rev-list --max-count=150 " . git_read_head($project) or die_error(undef, "Open failed."); - my (@revlist) = map { chomp; $_ } <$fd>; - close $fd or die_error(undef, "Reading rev-list failed."); - print $cgi->header(-type => 'text/xml', -charset => 'utf-8'); - print "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n". - "<rss version=\"2.0\" xmlns:content=\"http://purl.org/rss/1.0/modules/content/\">\n"; - print "<channel>\n"; - print "<title>$project</title>\n". - "<link>" . esc_html("$my_url?p=$project;a=summary") . "</link>\n". - "<description>$project log</description>\n". - "<language>en</language>\n"; - - for (my $i = 0; $i <= $#revlist; $i++) { - my $commit = $revlist[$i]; - my %co = git_read_commit($commit); - # we read 150, we always show 30 and the ones more recent than 48 hours - if (($i >= 20) && ((time - $co{'committer_epoch'}) > 48*60*60)) { - last; - } - my %cd = date_str($co{'committer_epoch'}); - open $fd, "-|", "$GIT diff-tree -r $co{'parent'} $co{'id'}" or next; - my @difftree = map { chomp; $_ } <$fd>; - close $fd or next; - print "<item>\n" . - "<title>" . - sprintf("%d %s %02d:%02d", $cd{'mday'}, $cd{'month'}, $cd{'hour'}, $cd{'minute'}) . " - " . esc_html($co{'title'}) . - "</title>\n" . - "<author>" . esc_html($co{'author'}) . "</author>\n" . - "<pubDate>$cd{'rfc2822'}</pubDate>\n" . - "<guid isPermaLink=\"true\">" . esc_html("$my_url?p=$project;a=commit;h=$commit") . "</guid>\n" . - "<link>" . esc_html("$my_url?p=$project;a=commit;h=$commit") . "</link>\n" . - "<description>" . esc_html($co{'title'}) . "</description>\n" . - "<content:encoded>" . - "<![CDATA[\n"; - my $comment = $co{'comment'}; - foreach my $line (@$comment) { - $line = decode("utf8", $line, Encode::FB_DEFAULT); - print "$line<br/>\n"; - } - print "<br/>\n"; - foreach my $line (@difftree) { - if (!($line =~ m/^:([0-7]{6}) ([0-7]{6}) ([0-9a-fA-F]{40}) ([0-9a-fA-F]{40}) (.)([0-9]{0,3})\t(.*)$/)) { - next; - } - my $file = validate_input(unquote($7)); - $file = decode("utf8", $file, Encode::FB_DEFAULT); - print "$file<br/>\n"; - } - print "]]>\n" . - "</content:encoded>\n" . - "</item>\n"; - } - print "</channel></rss>"; -} - -sub git_opml { - my @list = git_read_projects(); - - print $cgi->header(-type => 'text/xml', -charset => 'utf-8'); - print "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n". - "<opml version=\"1.0\">\n". - "<head>". - " <title>$site_name Git OPML Export</title>\n". - "</head>\n". - "<body>\n". - "<outline text=\"git RSS feeds\">\n"; - - foreach my $pr (@list) { - my %proj = %$pr; - my $head = git_read_head($proj{'path'}); - if (!defined $head) { - next; - } - $ENV{'GIT_DIR'} = "$projectroot/$proj{'path'}"; - my %co = git_read_commit($head); - if (!%co) { - next; - } - - my $path = esc_html(chop_str($proj{'path'}, 25, 5)); - my $rss = "$my_url?p=$proj{'path'};a=rss"; - my $html = "$my_url?p=$proj{'path'};a=summary"; - print "<outline type=\"rss\" text=\"$path\" title=\"$path\" xmlUrl=\"$rss\" htmlUrl=\"$html\"/>\n"; - } - print "</outline>\n". - "</body>\n". - "</opml>\n"; -} - -sub git_log { - my $head = git_read_head($project); - if (!defined $hash) { - $hash = $head; - } - if (!defined $page) { - $page = 0; - } - my $refs = read_info_ref(); - git_header_html(); - print "<div class=\"page_nav\">\n"; - print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary")}, "summary") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog;h=$hash")}, "shortlog") . - " | log" . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash")}, "commit") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$hash")}, "commitdiff") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;h=$hash;hb=$hash")}, "tree") . "<br/>\n"; - - my $limit = sprintf("--max-count=%i", (100 * ($page+1))); - open my $fd, "-|", "$GIT rev-list $limit $hash" or die_error(undef, "Open failed."); - my (@revlist) = map { chomp; $_ } <$fd>; - close $fd; - - if ($hash ne $head || $page) { - print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log")}, "HEAD"); - } else { - print "HEAD"; - } - if ($page > 0) { - print " ⋅ " . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log;h=$hash;pg=" . ($page-1)), -accesskey => "p", -title => "Alt-p"}, "prev"); - } else { - print " ⋅ prev"; - } - if ($#revlist >= (100 * ($page+1)-1)) { - print " ⋅ " . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log;h=$hash;pg=" . ($page+1)), -accesskey => "n", -title => "Alt-n"}, "next"); - } else { - print " ⋅ next"; - } - print "<br/>\n" . - "</div>\n"; - if (!@revlist) { - print "<div>\n" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary"), -class => "title"}, " ") . - "</div>\n"; - my %co = git_read_commit($hash); - print "<div class=\"page_body\"> Last change $co{'age_string'}.<br/><br/></div>\n"; - } - for (my $i = ($page * 100); $i <= $#revlist; $i++) { - my $commit = $revlist[$i]; - my $ref = ""; - if (defined $refs->{$commit}) { - $ref = " <span class=\"tag\">" . esc_html($refs->{$commit}) . "</span>"; - } - my %co = git_read_commit($commit); - next if !%co; - my %ad = date_str($co{'author_epoch'}); - print "<div>\n" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$commit"), -class => "title"}, - "<span class=\"age\">$co{'age_string'}</span>" . esc_html($co{'title'}) . $ref) . "\n"; - print "</div>\n"; - 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") . - "<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"; - } - print "</div>\n"; - } - git_footer_html(); -} - -sub git_commit { - my %co = git_read_commit($hash); - if (!%co) { - die_error(undef, "Unknown commit object."); - } - my %ad = date_str($co{'author_epoch'}, $co{'author_tz'}); - my %cd = date_str($co{'committer_epoch'}, $co{'committer_tz'}); - - my @difftree; - my $root = ""; - my $parent = $co{'parent'}; - if (!defined $parent) { - $root = " --root"; - $parent = ""; - } - open my $fd, "-|", "$GIT diff-tree -r -M $root $parent $hash" or die_error(undef, "Open failed."); - @difftree = map { chomp; $_ } <$fd>; - close $fd or die_error(undef, "Reading diff-tree failed."); - - # non-textual hash id's can be cached - my $expires; - if ($hash =~ m/^[0-9a-fA-F]{40}$/) { - $expires = "+1d"; - } - my $refs = read_info_ref(); - my $ref = ""; - if (defined $refs->{$co{'id'}}) { - $ref = " <span class=\"tag\">" . esc_html($refs->{$co{'id'}}) . "</span>"; - } - git_header_html(undef, $expires); - print "<div class=\"page_nav\">\n" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary")}, "summary") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog;h=$hash")}, "shortlog") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log;h=$hash")}, "log") . - " | commit"; - if (defined $co{'parent'}) { - print " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$hash")}, "commitdiff"); - } - print " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;h=$co{'tree'};hb=$hash")}, "tree") . "\n" . - "<br/>\n"; - if (defined $file_name && defined $co{'parent'}) { - my $parent = $co{'parent'}; - print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blame;hb=$parent;f=$file_name")}, "blame") . "\n"; - } - print "<br/></div>\n"; - - if (defined $co{'parent'}) { - print "<div>\n" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$hash"), -class => "title"}, esc_html($co{'title'}) . $ref) . "\n" . - "</div>\n"; - } else { - print "<div>\n" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;h=$co{'tree'};hb=$hash"), -class => "title"}, esc_html($co{'title'})) . "\n" . - "</div>\n"; - } - print "<div class=\"title_text\">\n" . - "<table cellspacing=\"0\">\n"; - print "<tr><td>author</td><td>" . esc_html($co{'author'}) . "</td></tr>\n". - "<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'}); - } else { - 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>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") . - "</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=\"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") . - "</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"; - } - } - print "</div>\n"; - print "<div class=\"list_head\">\n"; - if ($#difftree > 10) { - print(($#difftree + 1) . " files changed:\n"); - } - print "</div>\n"; - print "<table class=\"diff_tree\">\n"; - my $alternate = 0; - foreach my $line (@difftree) { - # ':100644 100644 03b218260e99b78c6df0ed378e59ed9205ccc96d 3b93d5e7cc7f7dd4ebed13a5cc1a4ad976fc94d8 M ls-files.c' - # ':100644 100644 7f9281985086971d3877aca27704f2aaf9c448ce bc190ebc71bbd923f2b728e505408f5e54bd073a M rev-tree.c' - if (!($line =~ m/^:([0-7]{6}) ([0-7]{6}) ([0-9a-fA-F]{40}) ([0-9a-fA-F]{40}) (.)([0-9]{0,3})\t(.*)$/)) { - next; - } - my $from_mode = $1; - my $to_mode = $2; - my $from_id = $3; - my $to_id = $4; - my $status = $5; - my $similarity = $6; - my $file = validate_input(unquote($7)); - if ($alternate) { - print "<tr class=\"dark\">\n"; - } else { - print "<tr class=\"light\">\n"; - } - $alternate ^= 1; - if ($status eq "A") { - my $mode_chng = ""; - if (S_ISREG(oct $to_mode)) { - $mode_chng = sprintf(" with mode: %04o", (oct $to_mode) & 0777); - } - print "<td>" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$to_id;hb=$hash;f=$file"), -class => "list"}, esc_html($file)) . "</td>\n" . - "<td><span class=\"file_status new\">[new " . file_type($to_mode) . "$mode_chng]</span></td>\n" . - "<td class=\"link\">" . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$to_id;hb=$hash;f=$file")}, "blob") . "</td>\n"; - } elsif ($status eq "D") { - print "<td>" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$from_id;hb=$hash;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=$hash;f=$file")}, "blob") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=history;hb=$hash;f=$file")}, "history") . - "</td>\n" - } elsif ($status eq "M" || $status eq "T") { - my $mode_chnge = ""; - if ($from_mode != $to_mode) { - $mode_chnge = " <span class=\"file_status mode_chnge\">[changed"; - if (((oct $from_mode) & S_IFMT) != ((oct $to_mode) & S_IFMT)) { - $mode_chnge .= " from " . file_type($from_mode) . " to " . file_type($to_mode); - } - if (((oct $from_mode) & 0777) != ((oct $to_mode) & 0777)) { - if (S_ISREG($from_mode) && S_ISREG($to_mode)) { - $mode_chnge .= sprintf(" mode: %04o->%04o", (oct $from_mode) & 0777, (oct $to_mode) & 0777); - } elsif (S_ISREG($to_mode)) { - $mode_chnge .= sprintf(" mode: %04o", (oct $to_mode) & 0777); - } - } - $mode_chnge .= "]</span>\n"; - } - print "<td>"; - if ($to_id ne $from_id) { - print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blobdiff;h=$to_id;hp=$from_id;hb=$hash;f=$file"), -class => "list"}, esc_html($file)); - } else { - print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$to_id;hb=$hash;f=$file"), -class => "list"}, esc_html($file)); - } - print "</td>\n" . - "<td>$mode_chnge</td>\n" . - "<td class=\"link\">"; - print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$to_id;hb=$hash;f=$file")}, "blob"); - if ($to_id ne $from_id) { - print " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blobdiff;h=$to_id;hp=$from_id;hb=$hash;f=$file")}, "diff"); - } - print " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=history;hb=$hash;f=$file")}, "history") . "\n"; - print "</td>\n"; - } elsif ($status eq "R") { - my ($from_file, $to_file) = split "\t", $file; - my $mode_chng = ""; - if ($from_mode != $to_mode) { - $mode_chng = sprintf(", mode: %04o", (oct $to_mode) & 0777); - } - print "<td>" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$to_id;hb=$hash;f=$to_file"), -class => "list"}, esc_html($to_file)) . "</td>\n" . - "<td><span class=\"file_status moved\">[moved from " . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$from_id;hb=$hash;f=$from_file"), -class => "list"}, esc_html($from_file)) . - " with " . (int $similarity) . "% similarity$mode_chng]</span></td>\n" . - "<td class=\"link\">" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$to_id;hb=$hash;f=$to_file")}, "blob"); - if ($to_id ne $from_id) { - print " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blobdiff;h=$to_id;hp=$from_id;hb=$hash;f=$to_file")}, "diff"); - } - print "</td>\n"; - } - print "</tr>\n"; - } - print "</table>\n"; - git_footer_html(); -} - -sub git_blobdiff { - mkdir($git_temp, 0700); - git_header_html(); - if (defined $hash_base && (my %co = git_read_commit($hash_base))) { - print "<div class=\"page_nav\">\n" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary")}, "summary") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog")}, "shortlog") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log")}, "log") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash_base")}, "commit") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$hash_base")}, "commitdiff") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;h=$co{'tree'};hb=$hash_base")}, "tree") . - "<br/>\n"; - print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blobdiff_plain;h=$hash;hp=$hash_parent")}, "plain") . - "</div>\n"; - print "<div>\n" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash_base"), -class => "title"}, esc_html($co{'title'})) . "\n" . - "</div>\n"; - } 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(); -} - -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"); -} - -sub git_commitdiff { - mkdir($git_temp, 0700); - my %co = git_read_commit($hash); - if (!%co) { - die_error(undef, "Unknown commit object."); - } - if (!defined $hash_parent) { - $hash_parent = $co{'parent'}; - } - open my $fd, "-|", "$GIT diff-tree -r $hash_parent $hash" or die_error(undef, "Open failed."); - my (@difftree) = map { chomp; $_ } <$fd>; - close $fd or die_error(undef, "Reading diff-tree failed."); - - # non-textual hash id's can be cached - my $expires; - if ($hash =~ m/^[0-9a-fA-F]{40}$/) { - $expires = "+1d"; - } - my $refs = read_info_ref(); - my $ref = ""; - if (defined $refs->{$co{'id'}}) { - $ref = " <span class=\"tag\">" . esc_html($refs->{$co{'id'}}) . "</span>"; - } - git_header_html(undef, $expires); - print "<div class=\"page_nav\">\n" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary")}, "summary") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog;h=$hash")}, "shortlog") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log;h=$hash")}, "log") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash")}, "commit") . - " | commitdiff" . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;h=$co{'tree'};hb=$hash")}, "tree") . "<br/>\n"; - print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff_plain;h=$hash;hp=$hash_parent")}, "plain") . "\n" . - "</div>\n"; - print "<div>\n" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash"), -class => "title"}, esc_html($co{'title'}) . $ref) . "\n" . - "</div>\n"; - print "<div class=\"page_body\">\n"; - my $comment = $co{'comment'}; - my $empty = 0; - my $signed = 0; - my @log = @$comment; - # remove first and empty lines after that - shift @log; - while (defined $log[0] && $log[0] eq "") { - shift @log; - } - foreach my $line (@log) { - if ($line =~ m/^ *(signed[ \-]off[ \-]by[ :]|acked[ \-]by[ :]|cc[ :])/i) { - next; - } - if ($line eq "") { - if ($empty) { - next; - } - $empty = 1; - } else { - $empty = 0; - } - print format_log_line_html($line) . "<br/>\n"; - } - print "<br/>\n"; - foreach my $line (@difftree) { - # ':100644 100644 03b218260e99b78c6df0ed378e59ed9205ccc96d 3b93d5e7cc7f7dd4ebed13a5cc1a4ad976fc94d8 M ls-files.c' - # ':100644 100644 7f9281985086971d3877aca27704f2aaf9c448ce bc190ebc71bbd923f2b728e505408f5e54bd073a M rev-tree.c' - $line =~ m/^:([0-7]{6}) ([0-7]{6}) ([0-9a-fA-F]{40}) ([0-9a-fA-F]{40}) (.)\t(.*)$/; - 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;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;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"); - } - } - } - print "<br/>\n" . - "</div>"; - git_footer_html(); -} - -sub git_commitdiff_plain { - mkdir($git_temp, 0700); - open my $fd, "-|", "$GIT diff-tree -r $hash_parent $hash" or die_error(undef, "Open failed."); - my (@difftree) = map { chomp; $_ } <$fd>; - close $fd or die_error(undef, "Reading diff-tree failed."); - - # try to figure out the next tag after this commit - my $tagname; - my $refs = read_info_ref("tags"); - open $fd, "-|", "$GIT rev-list HEAD"; - chomp (my (@commits) = <$fd>); - close $fd; - foreach my $commit (@commits) { - if (defined $refs->{$commit}) { - $tagname = $refs->{$commit} - } - if ($commit eq $hash) { - last; - } - } - - print $cgi->header(-type => "text/plain", -charset => 'utf-8', '-content-disposition' => "inline; filename=\"git-$hash.patch\""); - my %co = git_read_commit($hash); - my %ad = date_str($co{'author_epoch'}, $co{'author_tz'}); - my $comment = $co{'comment'}; - print "From: $co{'author'}\n" . - "Date: $ad{'rfc2822'} ($ad{'tz_local'})\n". - "Subject: $co{'title'}\n"; - if (defined $tagname) { - print "X-Git-Tag: $tagname\n"; - } - print "X-Git-Url: $my_url?p=$project;a=commitdiff;h=$hash\n" . - "\n"; - - foreach my $line (@$comment) {; - print "$line\n"; - } - print "---\n\n"; - - foreach my $line (@difftree) { - $line =~ m/^:([0-7]{6}) ([0-7]{6}) ([0-9a-fA-F]{40}) ([0-9a-fA-F]{40}) (.)\t(.*)$/; - 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"); - } - } -} - -sub git_history { - if (!defined $hash_base) { - $hash_base = git_read_head($project); - } - my $ftype; - my %co = git_read_commit($hash_base); - if (!%co) { - die_error(undef, "Unknown commit object."); - } - my $refs = read_info_ref(); - git_header_html(); - print "<div class=\"page_nav\">\n" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary")}, "summary") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog")}, "shortlog") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log")}, "log") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash_base")}, "commit") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$hash_base")}, "commitdiff") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;h=$co{'tree'};hb=$hash_base")}, "tree") . - "<br/><br/>\n" . - "</div>\n"; - print "<div>\n" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash_base"), -class => "title"}, esc_html($co{'title'})) . "\n" . - "</div>\n"; - if (!defined $hash && defined $file_name) { - $hash = git_get_hash_by_path($hash_base, $file_name); - } - if (defined $hash) { - $ftype = git_get_type($hash); - } - git_print_page_path($file_name, $ftype); - - open my $fd, "-|", - "$GIT rev-list --full-history $hash_base -- \'$file_name\'"; - print "<table cellspacing=\"0\">\n"; - my $alternate = 0; - while (my $line = <$fd>) { - if ($line =~ m/^([0-9a-fA-F]{40})/){ - my $commit = $1; - my %co = git_read_commit($commit); - if (!%co) { - next; - } - my $ref = ""; - if (defined $refs->{$commit}) { - $ref = " <span class=\"tag\">" . esc_html($refs->{$commit}) . "</span>"; - } - if ($alternate) { - print "<tr class=\"dark\">\n"; - } else { - print "<tr class=\"light\">\n"; - } - $alternate ^= 1; - print "<td title=\"$co{'age_string_age'}\"><i>$co{'age_string_date'}</i></td>\n" . - "<td><i>" . esc_html(chop_str($co{'author_name'}, 15, 3)) . "</i></td>\n" . - "<td>" . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$commit"), -class => "list"}, "<b>" . - esc_html(chop_str($co{'title'}, 50)) . "$ref</b>") . "</td>\n" . - "<td class=\"link\">" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$commit")}, "commit") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$commit")}, "commitdiff") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;hb=$commit;f=$file_name")}, "blob"); - my $blob = git_get_hash_by_path($hash_base, $file_name); - my $blob_parent = git_get_hash_by_path($commit, $file_name); - if (defined $blob && defined $blob_parent && $blob ne $blob_parent) { - print " | " . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blobdiff;h=$blob;hp=$blob_parent;hb=$commit;f=$file_name")}, - "diff to current"); - } - print "</td>\n" . - "</tr>\n"; - } - } - print "</table>\n"; - close $fd; - git_footer_html(); -} - -sub git_search { - if (!defined $searchtext) { - die_error("", "Text field empty."); - } - if (!defined $hash) { - $hash = git_read_head($project); - } - my %co = git_read_commit($hash); - if (!%co) { - die_error(undef, "Unknown commit object."); - } - # pickaxe may take all resources of your box and run for several minutes - # with every query - so decide by yourself how public you make this feature :) - my $commit_search = 1; - my $author_search = 0; - my $committer_search = 0; - my $pickaxe_search = 0; - if ($searchtext =~ s/^author\\://i) { - $author_search = 1; - } elsif ($searchtext =~ s/^committer\\://i) { - $committer_search = 1; - } elsif ($searchtext =~ s/^pickaxe\\://i) { - $commit_search = 0; - $pickaxe_search = 1; - } - git_header_html(); - print "<div class=\"page_nav\">\n" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary;h=$hash")}, "summary") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog")}, "shortlog") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log;h=$hash")}, "log") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash")}, "commit") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$hash")}, "commitdiff") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;h=$co{'tree'};hb=$hash")}, "tree") . - "<br/><br/>\n" . - "</div>\n"; - - print "<div>\n" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash"), -class => "title"}, esc_html($co{'title'})) . "\n" . - "</div>\n"; - print "<table cellspacing=\"0\">\n"; - my $alternate = 0; - if ($commit_search) { - $/ = "\0"; - open my $fd, "-|", "$GIT rev-list --header --parents $hash" or next; - while (my $commit_text = <$fd>) { - if (!grep m/$searchtext/i, $commit_text) { - next; - } - if ($author_search && !grep m/\nauthor .*$searchtext/i, $commit_text) { - next; - } - if ($committer_search && !grep m/\ncommitter .*$searchtext/i, $commit_text) { - next; - } - my @commit_lines = split "\n", $commit_text; - my %co = git_read_commit(undef, \@commit_lines); - if (!%co) { - next; - } - if ($alternate) { - print "<tr class=\"dark\">\n"; - } else { - print "<tr class=\"light\">\n"; - } - $alternate ^= 1; - print "<td title=\"$co{'age_string_age'}\"><i>$co{'age_string_date'}</i></td>\n" . - "<td><i>" . esc_html(chop_str($co{'author_name'}, 15, 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/>"); - my $comment = $co{'comment'}; - foreach my $line (@$comment) { - if ($line =~ m/^(.*)($searchtext)(.*)$/i) { - my $lead = esc_html($1) || ""; - $lead = chop_str($lead, 30, 10); - my $match = esc_html($2) || ""; - my $trail = esc_html($3) || ""; - $trail = chop_str($trail, 30, 10); - my $text = "$lead<span class=\"match\">$match</span>$trail"; - print chop_str($text, 80, 5) . "<br/>\n"; - } - } - print "</td>\n" . - "<td class=\"link\">" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$co{'id'}")}, "commit") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;h=$co{'tree'};hb=$co{'id'}")}, "tree"); - print "</td>\n" . - "</tr>\n"; - } - close $fd; - } - - if ($pickaxe_search) { - $/ = "\n"; - open my $fd, "-|", "$GIT rev-list $hash | $GIT diff-tree -r --stdin -S\'$searchtext\'"; - undef %co; - my @files; - while (my $line = <$fd>) { - if (%co && $line =~ m/^:([0-7]{6}) ([0-7]{6}) ([0-9a-fA-F]{40}) ([0-9a-fA-F]{40}) (.)\t(.*)$/) { - my %set; - $set{'file'} = $6; - $set{'from_id'} = $3; - $set{'to_id'} = $4; - $set{'id'} = $set{'to_id'}; - if ($set{'id'} =~ m/0{40}/) { - $set{'id'} = $set{'from_id'}; - } - if ($set{'id'} =~ m/0{40}/) { - next; - } - push @files, \%set; - } elsif ($line =~ m/^([0-9a-fA-F]{40})$/){ - if (%co) { - if ($alternate) { - print "<tr class=\"dark\">\n"; - } else { - print "<tr class=\"light\">\n"; - } - $alternate ^= 1; - print "<td title=\"$co{'age_string_age'}\"><i>$co{'age_string_date'}</i></td>\n" . - "<td><i>" . esc_html(chop_str($co{'author_name'}, 15, 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/>"); - while (my $setref = shift @files) { - my %set = %$setref; - print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$set{'id'};hb=$co{'id'};f=$set{'file'}"), class => "list"}, - "<span class=\"match\">" . esc_html($set{'file'}) . "</span>") . - "<br/>\n"; - } - print "</td>\n" . - "<td class=\"link\">" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$co{'id'}")}, "commit") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;h=$co{'tree'};hb=$co{'id'}")}, "tree"); - print "</td>\n" . - "</tr>\n"; - } - %co = git_read_commit($1); - } - } - close $fd; - } - print "</table>\n"; - git_footer_html(); -} - -sub git_shortlog { - my $head = git_read_head($project); - if (!defined $hash) { - $hash = $head; - } - if (!defined $page) { - $page = 0; - } - my $refs = read_info_ref(); - git_header_html(); - print "<div class=\"page_nav\">\n" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary")}, "summary") . - " | shortlog" . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log;h=$hash")}, "log") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash")}, "commit") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$hash")}, "commitdiff") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;h=$hash;hb=$hash")}, "tree") . "<br/>\n"; - - my $limit = sprintf("--max-count=%i", (100 * ($page+1))); - open my $fd, "-|", "$GIT rev-list $limit $hash" or die_error(undef, "Open failed."); - my (@revlist) = map { chomp; $_ } <$fd>; - close $fd; - - if ($hash ne $head || $page) { - print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog")}, "HEAD"); - } else { - print "HEAD"; - } - if ($page > 0) { - print " ⋅ " . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog;h=$hash;pg=" . ($page-1)), -accesskey => "p", -title => "Alt-p"}, "prev"); - } else { - print " ⋅ prev"; - } - if ($#revlist >= (100 * ($page+1)-1)) { - print " ⋅ " . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog;h=$hash;pg=" . ($page+1)), -accesskey => "n", -title => "Alt-n"}, "next"); - } else { - print " ⋅ next"; - } - print "<br/>\n" . - "</div>\n"; - print "<div>\n" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary"), -class => "title"}, " ") . - "</div>\n"; - print "<table cellspacing=\"0\">\n"; - my $alternate = 0; - for (my $i = ($page * 100); $i <= $#revlist; $i++) { - my $commit = $revlist[$i]; - my $ref = ""; - if (defined $refs->{$commit}) { - $ref = " <span class=\"tag\">" . esc_html($refs->{$commit}) . "</span>"; - } - my %co = git_read_commit($commit); - my %ad = date_str($co{'author_epoch'}); - if ($alternate) { - print "<tr class=\"dark\">\n"; - } else { - print "<tr class=\"light\">\n"; - } - $alternate ^= 1; - print "<td title=\"$co{'age_string_age'}\"><i>$co{'age_string_date'}</i></td>\n" . - "<td><i>" . esc_html(chop_str($co{'author_name'}, 10)) . "</i></td>\n" . - "<td>"; - if (length($co{'title_short'}) < length($co{'title'})) { - print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$commit"), -class => "list", -title => "$co{'title'}"}, - "<b>" . esc_html($co{'title_short'}) . "$ref</b>"); - } else { - print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$commit"), -class => "list"}, - "<b>" . esc_html($co{'title_short'}) . "$ref</b>"); - } - 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" . - "</tr>"; - } - if ($#revlist >= (100 * ($page+1)-1)) { - print "<tr>\n" . - "<td>" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog;h=$hash;pg=" . ($page+1)), -title => "Alt-n"}, "next") . - "</td>\n" . - "</tr>\n"; - } - print "</table\n>"; - git_footer_html(); -} diff --git a/gitweb/gitweb.css b/gitweb/gitweb.css index fffdb13d09..3f62b6d752 100644 --- a/gitweb/gitweb.css +++ b/gitweb/gitweb.css @@ -16,6 +16,11 @@ a:hover, a:visited, a:active { color: #880000; } +img.logo { + float: right; + border-width: 0px; +} + div.page_header { height: 25px; padding: 8px; @@ -42,6 +47,7 @@ div.page_nav a:visited { div.page_path { padding: 8px; + font-weight: bold; border: solid #d9d8d1; border-width: 0px 0px 1px; } @@ -115,11 +121,26 @@ div.list_head { font-style: italic; } +div.author_date { + padding: 8px; + border: solid #d9d8d1; + border-width: 0px 0px 1px 0px; + font-style: italic; +} + a.list { text-decoration: none; color: #000000; } +a.subject, a.name { + font-weight: bold; +} + +table.tags a.subject { + font-weight: normal; +} + a.list:hover { text-decoration: underline; color: #880000; @@ -157,6 +178,12 @@ table.blame { border-collapse: collapse; } +table.blame td { + padding: 0px 5px; + font-size: 12px; + vertical-align: top; +} + th { padding: 2px 5px; font-size: 12px; @@ -171,6 +198,10 @@ tr.dark { background-color: #f6f6f0; } +tr.dark2 { + background-color: #f6f6f0; +} + tr.dark:hover { background-color: #edece6; } @@ -181,12 +212,16 @@ td { vertical-align: top; } -td.link { +td.link, td.selflink { padding: 2px 5px; font-family: sans-serif; font-size: 10px; } +td.selflink { + padding-right: 0px; +} + td.sha1 { font-family: monospace; } @@ -196,6 +231,10 @@ td.error { background-color: yellow; } +td.current_head { + text-decoration: underline; +} + table.diff_tree span.file_status.new { color: #008000; } @@ -209,6 +248,10 @@ table.diff_tree span.file_status.mode_chnge { color: #777777; } +table.diff_tree span.file_status.copied { + color: #70a070; +} + /* age2: 60*60*24*2 <= age */ table.project_list td.age2, table.blame td.age2 { font-style: italic; @@ -248,10 +291,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; } @@ -260,6 +315,10 @@ div.diff.chunk_header { color: #990099; } +div.diff.incomplete { + color: #cccccc; +} + div.diff_info { font-family: monospace; color: #000099; @@ -309,15 +368,30 @@ a.rss_logo:hover { background-color: #ee5500; } -span.tag { +span.refs span { padding: 0px 4px; font-size: 10px; font-weight: normal; - background-color: #ffffaa; border: 1px solid; + background-color: #ffaaff; + border-color: #ffccff #ff00ee #ff00ee #ffccff; +} + +span.refs span.ref { + background-color: #aaaaff; + border-color: #ccccff #0033cc #0033cc #ccccff; +} + +span.refs span.tag { + background-color: #ffffaa; border-color: #ffffcc #ffee00 #ffee00 #ffffcc; } +span.refs span.head { + background-color: #aaffaa; + border-color: #ccffcc #00cc33 #00cc33 #ccffcc; +} + span.atnight { color: #cc0000; } diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl new file mode 100755 index 0000000000..0ec1eeffa1 --- /dev/null +++ b/gitweb/gitweb.perl @@ -0,0 +1,3659 @@ +#!/usr/bin/perl + +# gitweb - simple web interface to track changes in git repositories +# +# (C) 2005-2006, Kay Sievers <kay.sievers@vrfy.org> +# (C) 2005, Christian Gierke +# +# This program is licensed under the GPLv2 + +use strict; +use warnings; +use CGI qw(:standard :escapeHTML -nosticky); +use CGI::Util qw(unescape); +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; +our $version = "++GIT_VERSION++"; +our $my_url = $cgi->url(); +our $my_uri = $cgi->url(-absolute => 1); + +# core git executable to use +# this can just be "git" if your webserver has a sensible PATH +our $GIT = "++GIT_BINDIR++/git"; + +# absolute fs-path which will be prepended to the project path +#our $projectroot = "/pub/scm"; +our $projectroot = "++GITWEB_PROJECTROOT++"; + +# target of the home link on top of all pages +our $home_link = $my_uri || "/"; + +# string of the home link on top of all pages +our $home_link_str = "++GITWEB_HOME_LINK_STR++"; + +# name of your site or organization to appear in page titles +# replace this with something more descriptive for clearer bookmarks +our $site_name = "++GITWEB_SITENAME++" || $ENV{'SERVER_NAME'} || "Untitled"; + +# html text to include at home page +our $home_text = "++GITWEB_HOMETEXT++"; + +# URI of default stylesheet +our $stylesheet = "++GITWEB_CSS++"; +# URI of GIT logo (72x27 size) +our $logo = "++GITWEB_LOGO++"; +# URI of GIT favicon, assumed to be image/png type +our $favicon = "++GITWEB_FAVICON++"; + +# URI and label (title) of GIT logo link +#our $logo_url = "http://www.kernel.org/pub/software/scm/git/docs/"; +#our $logo_label = "git documentation"; +our $logo_url = "http://git.or.cz/"; +our $logo_label = "git homepage"; + +# source of projects list +our $projects_list = "++GITWEB_LIST++"; + +# show repository only if this file exists +# (only effective if this variable evaluates to true) +our $export_ok = "++GITWEB_EXPORT_OK++"; + +# only allow viewing of repositories also shown on the overview page +our $strict_export = "++GITWEB_STRICT_EXPORT++"; + +# list of git base URLs used for URL to where fetch project from, +# i.e. full URL is "$git_base_url/$project" +our @git_base_url_list = ("++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; + +# file to use for guessing MIME types before trying /etc/mime.types +# (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++"; +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; + +# ====================================================================== +# input validation and dispatch +our $action = $cgi->param('a'); +if (defined $action) { + if ($action =~ m/[^0-9a-zA-Z\.\-_]/) { + die_error(undef, "Invalid action parameter"); + } +} + +# parameters which are pathnames +our $project = $cgi->param('p'); +if (defined $project) { + 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"); + } +} + +our $file_name = $cgi->param('f'); +if (defined $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_refname($hash)) { + die_error(undef, "Invalid hash parameter"); + } +} + +our $hash_parent = $cgi->param('hp'); +if (defined $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_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]/) { + die_error(undef, "Invalid page parameter"); + } +} + +our $searchtext = $cgi->param('s'); +if (defined $searchtext) { + if ($searchtext =~ m/[^a-zA-Z0-9_\.\/\-\+\:\@ ]/) { + die_error(undef, "Invalid search parameter"); + } + $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, + "blobdiff" => \&git_blobdiff, + "blobdiff_plain" => \&git_blobdiff_plain, + "blob" => \&git_blob, + "blob_plain" => \&git_blob_plain, + "commitdiff" => \&git_commitdiff, + "commitdiff_plain" => \&git_commitdiff_plain, + "commit" => \&git_commit, + "heads" => \&git_heads, + "history" => \&git_history, + "log" => \&git_log, + "rss" => \&git_rss, + "search" => \&git_search, + "shortlog" => \&git_shortlog, + "summary" => \&git_summary, + "tag" => \&git_tag, + "tags" => \&git_tags, + "tree" => \&git_tree, + "snapshot" => \&git_snapshot, + # those below don't need $project + "opml" => \&git_opml, + "project_list" => \&git_project_list, + "project_index" => \&git_project_index, +); + +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_pathname { + my $input = shift || return undef; + + # 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; + } + # no null characters + if ($input =~ m!\0!) { + return undef; + } + return $input; +} + +sub validate_refname { + my $input = shift || return undef; + + # textual hashes are O.K. + if ($input =~ m/^[0-9a-fA-F]{40}$/) { + return $input; + } + # it must be correct pathname + $input = validate_pathname($input) + or return undef; + # restrictions on ref name according to git-check-ref-format + if ($input =~ m!(/\.|\.\.|[\000-\040\177 ~^:?*\[]|/$)!) { + return undef; + } + return $input; +} + +# very thin wrapper for decode("utf8", $str, Encode::FB_DEFAULT); +sub to_utf8 { + my $str = shift; + return decode("utf8", $str, Encode::FB_DEFAULT); +} + +# quote unsafe chars, but keep the slash, even when it's not +# correct, but quoted slashes look too horrible in bookmarks +sub esc_param { + my $str = shift; + $str =~ s/([^A-Za-z0-9\-_.~()\/:@])/sprintf("%%%02X", ord($1))/eg; + $str =~ s/\+/%2B/g; + $str =~ s/ /\+/g; + return $str; +} + +# quote unsafe chars in whole URL, so some charactrs cannot be quoted +sub esc_url { + my $str = shift; + $str =~ s/([^A-Za-z0-9\-_.~();\/;?:@&=])/sprintf("%%%02X", ord($1))/eg; + $str =~ s/\+/%2B/g; + $str =~ s/ /\+/g; + return $str; +} + +# replace invalid utf8 character with SUBSTITUTION sequence +sub esc_html { + my $str = shift; + $str = to_utf8($str); + $str = escapeHTML($str); + $str =~ s/\014/^L/g; # escape FORM FEED (FF) character (e.g. in COPYING file) + $str =~ s/\033/^[/g; # "escape" ESCAPE (\e) character (e.g. commit 20a3847d8a5032ce41f90dcc68abfb36e6fee9b1) + return $str; +} + +# git may return quoted and escaped filenames +sub unquote { + my $str = shift; + if ($str =~ m/^"(.*)"$/) { + $str = $1; + $str =~ s/\\([0-7]{1,3})/chr(oct($1))/eg; + } + return $str; +} + +# escape tabs (convert tabs to spaces) +sub untabify { + my $line = shift; + + while ((my $pos = index($line, "\t")) != -1) { + if (my $count = (8 - ($pos % 8))) { + my $spaces = ' ' x $count; + $line =~ s/\t/$spaces/; + } + } + + 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 + +sub chop_str { + my $str = shift; + my $len = shift; + my $add_len = shift || 10; + + # allow only $len chars, but don't cut a word if it would fit in $add_len + # if it doesn't fit, cut it if it's still longer than the dots we would add + $str =~ m/^(.{0,$len}[^ \/\-_:\.@]{0,$add_len})(.*)/; + my $body = $1; + my $tail = $2; + if (length($tail) > 4) { + $tail = " ..."; + $body =~ s/&[^;]*$//; # remove chopped character entities + } + return "$body$tail"; +} + +## ---------------------------------------------------------------------- +## functions returning short strings + +# CSS class for given age value (in seconds) +sub age_class { + my $age = shift; + + if ($age < 60*60*2) { + return "age0"; + } elsif ($age < 60*60*24*2) { + return "age1"; + } else { + return "age2"; + } +} + +# convert age in seconds to "nn units ago" string +sub age_string { + my $age = shift; + my $age_str; + + if ($age > 60*60*24*365*2) { + $age_str = (int $age/60/60/24/365); + $age_str .= " years ago"; + } elsif ($age > 60*60*24*(365/12)*2) { + $age_str = int $age/60/60/24/(365/12); + $age_str .= " months ago"; + } elsif ($age > 60*60*24*7*2) { + $age_str = int $age/60/60/24/7; + $age_str .= " weeks ago"; + } elsif ($age > 60*60*24*2) { + $age_str = int $age/60/60/24; + $age_str .= " days ago"; + } elsif ($age > 60*60*2) { + $age_str = int $age/60/60; + $age_str .= " hours ago"; + } elsif ($age > 60*2) { + $age_str = int $age/60; + $age_str .= " min ago"; + } elsif ($age > 2) { + $age_str = int $age; + $age_str .= " sec ago"; + } else { + $age_str .= " right now"; + } + return $age_str; +} + +# convert file mode in octal to symbolic file mode string +sub mode_str { + my $mode = oct shift; + + if (S_ISDIR($mode & S_IFMT)) { + return 'drwxr-xr-x'; + } elsif (S_ISLNK($mode)) { + return 'lrwxrwxrwx'; + } elsif (S_ISREG($mode)) { + # git cares only about the executable bit + if ($mode & S_IXUSR) { + return '-rwxr-xr-x'; + } else { + return '-rw-r--r--'; + }; + } else { + return '----------'; + } +} + +# convert file mode in octal to file type string +sub file_type { + my $mode = shift; + + if ($mode !~ m/^[0-7]+$/) { + return $mode; + } else { + $mode = oct $mode; + } + + if (S_ISDIR($mode & S_IFMT)) { + return "directory"; + } elsif (S_ISLNK($mode)) { + return "symlink"; + } elsif (S_ISREG($mode)) { + return "file"; + } else { + return "unknown"; + } +} + +## ---------------------------------------------------------------------- +## functions returning short HTML fragments, or transforming HTML fragments +## which don't beling to other sections + +# format line of commit message or tag comment +sub format_log_line_html { + my $line = shift; + + $line = esc_html($line); + $line =~ s/ / /g; + if ($line =~ m/([0-9a-fA-F]{40})/) { + my $hash_text = $1; + if (git_get_type($hash_text) eq "commit") { + my $link = + $cgi->a({-href => href(action=>"commit", hash=>$hash_text), + -class => "text"}, $hash_text); + $line =~ s/$hash_text/$link/; + } + } + return $line; +} + +# format marker of refs pointing to given object +sub format_ref_marker { + my ($refs, $id) = @_; + my $markers = ''; + + if (defined $refs->{$id}) { + foreach my $ref (@{$refs->{$id}}) { + my ($type, $name) = qw(); + # e.g. tags/v2.6.11 or heads/next + if ($ref =~ m!^(.*?)s?/(.*)$!) { + $type = $1; + $name = $2; + } else { + $type = "ref"; + $name = $ref; + } + + $markers .= " <span class=\"$type\">" . esc_html($name) . "</span>"; + } + } + + if ($markers) { + return ' <span class="refs">'. $markers . '</span>'; + } else { + return ""; + } +} + +# format, perhaps shortened and with markers, title line +sub format_subject_html { + my ($long, $short, $href, $extra) = @_; + $extra = '' unless defined($extra); + + if (length($short) < length($long)) { + return $cgi->a({-href => $href, -class => "list subject", + -title => to_utf8($long)}, + esc_html($short) . $extra); + } else { + return $cgi->a({-href => $href, -class => "list subject"}, + esc_html($long) . $extra); + } +} + +sub format_diff_line { + my $line = shift; + my $char = substr($line, 0, 1); + my $diff_class = ""; + + chomp $line; + + if ($char eq '+') { + $diff_class = " add"; + } elsif ($char eq "-") { + $diff_class = " rem"; + } elsif ($char eq "@") { + $diff_class = " chunk_header"; + } elsif ($char eq "\\") { + $diff_class = " incomplete"; + } + $line = untabify($line); + return "<div class=\"diff$diff_class\">" . esc_html($line) . "</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 $o_git_dir = $git_dir; + my $retval = undef; + $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 $o_git_dir) { + $git_dir = $o_git_dir; + } + return $retval; +} + +# get type of given object +sub git_get_type { + my $hash = shift; + + open my $fd, "-|", git_cmd(), "cat-file", '-t', $hash or return; + my $type = <$fd>; + close $fd or return; + chomp $type; + return $type; +} + +sub git_get_project_config { + my ($key, $type) = @_; + + return unless ($key); + $key =~ s/^gitweb\.//; + return if ($key =~ m/\W/); + + 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); +} + +# 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; + + $path =~ s,/+$,,; + + 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 + +sub git_get_project_description { + my $path = shift; + + open my $fd, "$projectroot/$path/description" or return undef; + my $descr = <$fd>; + close $fd; + chomp $descr; + 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; + 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; + while (my $line = <$fd>) { + chomp $line; + my ($path, $owner) = split ' ', $line; + $path = unescape($path); + $owner = unescape($owner); + if (!defined $path) { + next; + } + if (-e "$projectroot/$path/HEAD" && (!$export_ok || + -e "$projectroot/$path/$export_ok")) { + my $pr = { + path => $path, + owner => to_utf8($owner), + }; + push @list, $pr + } + } + close $fd; + } + @list = sort {$a->{'path'} cmp $b->{'path'}} @list; + return @list; +} + +sub git_get_project_owner { + my $project = shift; + my $owner; + + return undef unless $project; + + # read from file (url-encoded): + # 'git%2Fgit.git Linus+Torvalds' + # 'libs%2Fklibc%2Fklibc.git H.+Peter+Anvin' + # 'linux%2Fhotplug%2Fudev.git Greg+Kroah-Hartman' + if (-f $projects_list) { + open (my $fd , $projects_list); + while (my $line = <$fd>) { + chomp $line; + my ($pr, $ow) = split ' ', $line; + $pr = unescape($pr); + $ow = unescape($ow); + if ($pr eq $project) { + $owner = to_utf8($ow); + last; + } + } + close $fd; + } + if (!defined $owner) { + $owner = get_file_owner("$projectroot/$project"); + } + + return $owner; +} + +sub git_get_references { + my $type = shift || ""; + my %refs; + # 5dc01c595e6c6ec9ccda4f6f69c131c0dd945f8c refs/tags/v2.6.11 + # c39ae07f393806ccf406ef966e9a15afc43cc36a refs/tags/v2.6.11^{} + 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 ]; + } + } + } + close $fd or return; + return \%refs; +} + +sub git_get_rev_name_tags { + my $hash = shift || return undef; + + open my $fd, "-|", git_cmd(), "name-rev", "--tags", $hash + or return; + my $name_rev = <$fd>; + close $fd; + + if ($name_rev =~ m|^$hash tags/(.*)$|) { + return $1; + } else { + # catches also '$hash undefined' output + return undef; + } +} + +## ---------------------------------------------------------------------- +## parse to hash functions + +sub parse_date { + my $epoch = shift; + my $tz = shift || "-0000"; + + my %date; + my @months = ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"); + my @days = ("Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"); + my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday) = gmtime($epoch); + $date{'hour'} = $hour; + $date{'minute'} = $min; + $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; + + $tz =~ m/^([+\-][0-9][0-9])([0-9][0-9])$/; + my $local = $epoch + ((int $1 + ($2/60)) * 3600); + ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday) = gmtime($local); + $date{'hour_local'} = $hour; + $date{'minute_local'} = $min; + $date{'tz_local'} = $tz; + return %date; +} + +sub parse_tag { + my $tag_id = shift; + my %tag; + my @comment; + + open my $fd, "-|", git_cmd(), "cat-file", "tag", $tag_id or return; + $tag{'id'} = $tag_id; + while (my $line = <$fd>) { + chomp $line; + if ($line =~ m/^object ([0-9a-fA-F]{40})$/) { + $tag{'object'} = $1; + } elsif ($line =~ m/^type (.+)$/) { + $tag{'type'} = $1; + } elsif ($line =~ m/^tag (.+)$/) { + $tag{'name'} = $1; + } elsif ($line =~ m/^tagger (.*) ([0-9]+) (.*)$/) { + $tag{'author'} = $1; + $tag{'epoch'} = $2; + $tag{'tz'} = $3; + } elsif ($line =~ m/--BEGIN/) { + push @comment, $line; + last; + } elsif ($line eq "") { + last; + } + } + push @comment, <$fd>; + $tag{'comment'} = \@comment; + close $fd or return; + if (!defined $tag{'name'}) { + return + }; + return %tag +} + +sub parse_commit { + my $commit_id = shift; + my $commit_text = shift; + + my @commit_lines; + my %co; + + if (defined $commit_text) { + @commit_lines = @$commit_text; + } else { + $/ = "\0"; + open my $fd, "-|", git_cmd(), "rev-list", "--header", "--parents", "--max-count=1", $commit_id + or return; + @commit_lines = split '\n', <$fd>; + close $fd or return; + $/ = "\n"; + pop @commit_lines; + } + my $header = shift @commit_lines; + if (!($header =~ m/^[0-9a-fA-F]{40}/)) { + return; + } + ($co{'id'}, my @parents) = split ' ', $header; + $co{'parents'} = \@parents; + $co{'parent'} = $parents[0]; + while (my $line = shift @commit_lines) { + last if $line eq "\n"; + if ($line =~ m/^tree ([0-9a-fA-F]{40})$/) { + $co{'tree'} = $1; + } elsif ($line =~ m/^author (.*) ([0-9]+) (.*)$/) { + $co{'author'} = $1; + $co{'author_epoch'} = $2; + $co{'author_tz'} = $3; + if ($co{'author'} =~ m/^([^<]+) </) { + $co{'author_name'} = $1; + } else { + $co{'author_name'} = $co{'author'}; + } + } elsif ($line =~ m/^committer (.*) ([0-9]+) (.*)$/) { + $co{'committer'} = $1; + $co{'committer_epoch'} = $2; + $co{'committer_tz'} = $3; + $co{'committer_name'} = $co{'committer'}; + $co{'committer_name'} =~ s/ <.*//; + } + } + if (!defined $co{'tree'}) { + return; + }; + + foreach my $title (@commit_lines) { + $title =~ s/^ //; + if ($title ne "") { + $co{'title'} = chop_str($title, 80, 5); + # remove leading stuff of merges to make the interesting part visible + if (length($title) > 50) { + $title =~ s/^Automatic //; + $title =~ s/^merge (of|with) /Merge ... /i; + if (length($title) > 50) { + $title =~ s/(http|rsync):\/\///; + } + if (length($title) > 50) { + $title =~ s/(master|www|rsync)\.//; + } + if (length($title) > 50) { + $title =~ s/kernel.org:?//; + } + if (length($title) > 50) { + $title =~ s/\/pub\/scm//; + } + } + $co{'title_short'} = chop_str($title, 50, 5); + last; + } + } + if ($co{'title'} eq "") { + $co{'title'} = $co{'title_short'} = '(no commit message)'; + } + # remove added spaces + foreach my $line (@commit_lines) { + $line =~ s/^ //; + } + $co{'comment'} = \@commit_lines; + + my $age = time - $co{'committer_epoch'}; + $co{'age'} = $age; + $co{'age_string'} = age_string($age); + my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday) = gmtime($co{'committer_epoch'}); + if ($age > 60*60*24*7*2) { + $co{'age_string_date'} = sprintf "%4i-%02u-%02i", 1900 + $year, $mon+1, $mday; + $co{'age_string_age'} = $co{'age_string'}; + } else { + $co{'age_string_date'} = $co{'age_string'}; + $co{'age_string_age'} = sprintf "%4i-%02u-%02i", 1900 + $year, $mon+1, $mday; + } + return %co; +} + +# parse ref from ref_file, given by ref_id, with given type +sub parse_ref { + my $ref_file = shift; + my $ref_id = shift; + my $type = shift || git_get_type($ref_id); + my %ref_item; + + $ref_item{'type'} = $type; + $ref_item{'id'} = $ref_id; + $ref_item{'epoch'} = 0; + $ref_item{'age'} = "unknown"; + if ($type eq "tag") { + my %tag = parse_tag($ref_id); + $ref_item{'comment'} = $tag{'comment'}; + if ($tag{'type'} eq "commit") { + my %co = parse_commit($tag{'object'}); + $ref_item{'epoch'} = $co{'committer_epoch'}; + $ref_item{'age'} = $co{'age_string'}; + } elsif (defined($tag{'epoch'})) { + my $age = time - $tag{'epoch'}; + $ref_item{'epoch'} = $tag{'epoch'}; + $ref_item{'age'} = age_string($age); + } + $ref_item{'reftype'} = $tag{'type'}; + $ref_item{'name'} = $tag{'name'}; + $ref_item{'refid'} = $tag{'object'}; + } elsif ($type eq "commit"){ + my %co = parse_commit($ref_id); + $ref_item{'reftype'} = "commit"; + $ref_item{'name'} = $ref_file; + $ref_item{'title'} = $co{'title'}; + $ref_item{'refid'} = $ref_id; + $ref_item{'epoch'} = $co{'committer_epoch'}; + $ref_item{'age'} = $co{'age_string'}; + } else { + $ref_item{'reftype'} = $type; + $ref_item{'name'} = $ref_file; + $ref_item{'refid'} = $ref_id; + } + + return %ref_item; +} + +# parse line of git-diff-tree "raw" output +sub parse_difftree_raw_line { + my $line = shift; + my %res; + + # ':100644 100644 03b218260e99b78c6df0ed378e59ed9205ccc96d 3b93d5e7cc7f7dd4ebed13a5cc1a4ad976fc94d8 M ls-files.c' + # ':100644 100644 7f9281985086971d3877aca27704f2aaf9c448ce bc190ebc71bbd923f2b728e505408f5e54bd073a M rev-tree.c' + if ($line =~ m/^:([0-7]{6}) ([0-7]{6}) ([0-9a-fA-F]{40}) ([0-9a-fA-F]{40}) (.)([0-9]{0,3})\t(.*)$/) { + $res{'from_mode'} = $1; + $res{'to_mode'} = $2; + $res{'from_id'} = $3; + $res{'to_id'} = $4; + $res{'status'} = $5; + $res{'similarity'} = $6; + if ($res{'status'} eq 'R' || $res{'status'} eq 'C') { # renamed or copied + ($res{'from_file'}, $res{'to_file'}) = map { unquote($_) } split("\t", $7); + } else { + $res{'file'} = unquote($7); + } + } + # 'c512b523472485aef4fff9e57b229d9d243c967f' + elsif ($line =~ m/^([0-9a-fA-F]{40})$/) { + $res{'commit'} = $1; + } + + return wantarray ? %res : \%res; +} + +# parse line of git-ls-tree output +sub parse_ls_tree_line ($;%) { + my $line = shift; + my %opts = @_; + my %res; + + #'100644 blob 0fa3f3a66fb6a137f6ec2c19351ed4d807070ffa panic.c' + $line =~ m/^([0-9]+) (.+) ([0-9a-fA-F]{40})\t(.+)$/; + + $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 $type = shift || ""; + my %refs; + my @reflist; + + my @refs; + 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"; + } + } + } + close $fd; + + foreach my $ref (@refs) { + my $ref_file = $ref->{'name'}; + my $ref_id = $ref->{'hash'}; + + 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, \%refs); +} + +## ---------------------------------------------------------------------- +## filesystem-related functions + +sub get_file_owner { + my $path = shift; + + my ($dev, $ino, $mode, $nlink, $st_uid, $st_gid, $rdev, $size) = stat($path); + my ($name, $passwd, $uid, $gid, $quota, $comment, $gcos, $dir, $shell) = getpwuid($st_uid); + if (!defined $gcos) { + return undef; + } + my $owner = $gcos; + $owner =~ s/[,;].*$//; + return to_utf8($owner); +} + +## ...................................................................... +## mimetype related functions + +sub mimetype_guess_file { + my $filename = shift; + my $mimemap = shift; + -r $mimemap or return undef; + + my %mimemap; + open(MIME, $mimemap) or return undef; + while (<MIME>) { + next if m/^#/; # skip comments + my ($mime, $exts) = split(/\t+/); + if (defined $exts) { + my @exts = split(/\s+/, $exts); + foreach my $ext (@exts) { + $mimemap{$ext} = $mime; + } + } + } + close(MIME); + + $filename =~ /\.([^.]*)$/; + return $mimemap{$1}; +} + +sub mimetype_guess { + my $filename = shift; + my $mime; + $filename =~ /\./ or return undef; + + if ($mimetypes_file) { + my $file = $mimetypes_file; + if ($file !~ m!^/!) { # if it is relative path + # it is relative to project + $file = "$projectroot/$project/$file"; + } + $mime = mimetype_guess_file($filename, $file); + } + $mime ||= mimetype_guess_file($filename, '/etc/mime.types'); + return $mime; +} + +sub blob_mimetype { + my $fd = shift; + my $filename = shift; + + if ($filename) { + my $mime = mimetype_guess($filename); + $mime and return $mime; + } + + # just in case + return $default_blob_plain_mimetype unless $fd; + + if (-T $fd) { + return 'text/plain' . + ($default_text_plain_charset ? '; charset='.$default_text_plain_charset : ''); + } elsif (! $filename) { + return 'application/octet-stream'; + } elsif ($filename =~ m/\.png$/i) { + return 'image/png'; + } elsif ($filename =~ m/\.gif$/i) { + return 'image/gif'; + } elsif ($filename =~ m/\.jpe?g$/i) { + return 'image/jpeg'; + } else { + return 'application/octet-stream'; + } +} + +## ====================================================================== +## functions printing HTML: header, footer, error page + +sub git_header_html { + my $status = shift || "200 OK"; + my $expires = shift; + + my $title = "$site_name git"; + if (defined $project) { + $title .= " - $project"; + if (defined $action) { + $title .= "/$action"; + if (defined $file_name) { + $title .= " - " . esc_html($file_name); + if ($action eq "tree" && $file_name !~ m|/$|) { + $title .= "/"; + } + } + } + } + my $content_type; + # require explicit support from the UA if we are to send the page as + # '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) { + $content_type = 'application/xhtml+xml'; + } else { + $content_type = 'text/html'; + } + 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 version $version, (C) 2005-2006, Kay Sievers <kay.sievers\@vrfy.org>, Christian Gierke --> +<!-- git core binaries version $git_version --> +<head> +<meta http-equiv="content-type" content="$content_type; charset=utf-8"/> +<meta name="generator" content="gitweb/$version git/$git_version"/> +<meta name="robots" content="index, nofollow"/> +<title>$title</title> +<link rel="stylesheet" type="text/css" href="$stylesheet"/> +EOF + if (defined $project) { + printf('<link rel="alternate" title="%s log" '. + 'href="%s" type="application/rss+xml"/>'."\n", + esc_param($project), href(action=>"rss")); + } else { + printf('<link rel="alternate" title="%s projects list" '. + 'href="%s" type="text/plain; charset=utf-8"/>'."\n", + $site_name, href(project=>undef, action=>"project_index")); + printf('<link rel="alternate" title="%s projects logs" '. + 'href="%s" type="text/x-opml"/>'."\n", + $site_name, href(project=>undef, action=>"opml")); + } + if (defined $favicon) { + print qq(<link rel="shortcut icon" href="$favicon" type="image/png"/>\n); + } + + print "</head>\n" . + "<body>\n" . + "<div class=\"page_header\">\n" . + $cgi->a({-href => esc_url($logo_url), + -title => $logo_label}, + qq(<img src="$logo" width="72" height="27" alt="git" class="logo"/>)); + print $cgi->a({-href => esc_url($home_link)}, $home_link_str) . " / "; + if (defined $project) { + print $cgi->a({-href => href(action=>"summary")}, esc_html($project)); + if (defined $action) { + print " / $action"; + } + print "\n"; + if (!defined $searchtext) { + $searchtext = ""; + } + my $search_hash; + if (defined $hash_base) { + $search_hash = $hash_base; + } elsif (defined $hash) { + $search_hash = $hash; + } else { + $search_hash = "HEAD"; + } + $cgi->param("a", "search"); + $cgi->param("h", $search_hash); + print $cgi->startform(-method => "get", -action => $my_uri) . + "<div class=\"search\">\n" . + $cgi->hidden(-name => "p") . "\n" . + $cgi->hidden(-name => "a") . "\n" . + $cgi->hidden(-name => "h") . "\n" . + $cgi->textfield(-name => "s", -value => $searchtext) . "\n" . + "</div>" . + $cgi->end_form() . "\n"; + } + print "</div>\n"; +} + +sub git_footer_html { + print "<div class=\"page_footer\">\n"; + if (defined $project) { + my $descr = git_get_project_description($project); + if (defined $descr) { + print "<div class=\"page_footer_text\">" . esc_html($descr) . "</div>\n"; + } + print $cgi->a({-href => href(action=>"rss"), + -class => "rss_logo"}, "RSS") . "\n"; + } else { + 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" . + "</html>"; +} + +sub die_error { + my $status = shift || "403 Forbidden"; + my $error = shift || "Malformed query, file missing or permission denied"; + + git_header_html($status); + print <<EOF; +<div class="page_body"> +<br /><br /> +$status - $error +<br /> +</div> +EOF + git_footer_html(); + exit; +} + +## ---------------------------------------------------------------------- +## functions printing or outputting HTML: navigation + +sub git_print_page_nav { + my ($current, $suppress, $head, $treehead, $treebase, $extra) = @_; + $extra = '' if !defined $extra; # pager or formats + + my @navs = qw(summary shortlog log commit commitdiff tree); + if ($suppress) { + @navs = grep { $_ ne $suppress } @navs; + } + + my %arg = map { $_ => {action=>$_} } @navs; + if (defined $head) { + for (qw(commit commitdiff)) { + $arg{$_}{hash} = $head; + } + if ($current =~ m/^(tree | log | shortlog | commit | commitdiff | search)$/x) { + for (qw(shortlog log)) { + $arg{$_}{hash} = $head; + } + } + } + $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 => href(%{$arg{$_}})}, "$_") + } @navs); + print "<br/>\n$extra<br/>\n" . + "</div>\n"; +} + +sub format_paging_nav { + my ($action, $hash, $head, $page, $nrevs) = @_; + my $paging_nav; + + + if ($hash ne $head || $page) { + $paging_nav .= $cgi->a({-href => href(action=>$action)}, "HEAD"); + } else { + $paging_nav .= "HEAD"; + } + + if ($page > 0) { + $paging_nav .= " ⋅ " . + $cgi->a({-href => href(action=>$action, hash=>$hash, page=>$page-1), + -accesskey => "p", -title => "Alt-p"}, "prev"); + } else { + $paging_nav .= " ⋅ prev"; + } + + if ($nrevs >= (100 * ($page+1)-1)) { + $paging_nav .= " ⋅ " . + $cgi->a({-href => href(action=>$action, hash=>$hash, page=>$page+1), + -accesskey => "n", -title => "Alt-n"}, "next"); + } else { + $paging_nav .= " ⋅ next"; + } + + return $paging_nav; +} + +## ...................................................................... +## functions printing or outputting HTML: div + +sub git_print_header_div { + my ($action, $title, $hash, $hash_base) = @_; + my %args = (); + + $args{action} = $action; + $args{hash} = $hash if $hash; + $args{hash_base} = $hash_base if $hash_base; + + print "<div class=\"header\">\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\">/</div>\n"; + } else { + 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"; + } +} + +## ...................................................................... +## functions printing large fragments of HTML + +sub git_difftree_body { + my ($difftree, $hash, $parent) = @_; + + print "<div class=\"list_head\">\n"; + if ($#{$difftree} > 10) { + print(($#{$difftree} + 1) . " files changed:\n"); + } + print "</div>\n"; + + print "<table class=\"diff_tree\">\n"; + my $alternate = 1; + my $patchno = 0; + foreach my $line (@{$difftree}) { + my %diff = parse_difftree_raw_line($line); + + if ($alternate) { + print "<tr class=\"dark\">\n"; + } else { + print "<tr class=\"light\">\n"; + } + $alternate ^= 1; + + my ($to_mode_oct, $to_mode_str, $to_file_type); + my ($from_mode_oct, $from_mode_str, $from_file_type); + if ($diff{'to_mode'} ne ('0' x 6)) { + $to_mode_oct = oct $diff{'to_mode'}; + if (S_ISREG($to_mode_oct)) { # only for regular file + $to_mode_str = sprintf("%04o", $to_mode_oct & 0777); # permission bits + } + $to_file_type = file_type($diff{'to_mode'}); + } + if ($diff{'from_mode'} ne ('0' x 6)) { + $from_mode_oct = oct $diff{'from_mode'}; + if (S_ISREG($to_mode_oct)) { # only for regular file + $from_mode_str = sprintf("%04o", $from_mode_oct & 0777); # permission bits + } + $from_file_type = file_type($diff{'from_mode'}); + } + + if ($diff{'status'} eq "A") { # created + my $mode_chng = "<span class=\"file_status new\">[new $to_file_type"; + $mode_chng .= " with mode: $to_mode_str" if $to_mode_str; + $mode_chng .= "]</span>"; + print "<td>"; + print $cgi->a({-href => href(action=>"blob", hash=>$diff{'to_id'}, + hash_base=>$hash, file_name=>$diff{'file'}), + -class => "list"}, esc_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 ($diff{'status'} eq "M" || $diff{'status'} eq "T") { # modified, or type changed + my $mode_chnge = ""; + if ($diff{'from_mode'} != $diff{'to_mode'}) { + $mode_chnge = "<span class=\"file_status mode_chnge\">[changed"; + if ($from_file_type != $to_file_type) { + $mode_chnge .= " from $from_file_type to $to_file_type"; + } + if (($from_mode_oct & 0777) != ($to_mode_oct & 0777)) { + if ($from_mode_str && $to_mode_str) { + $mode_chnge .= " mode: $from_mode_str->$to_mode_str"; + } elsif ($to_mode_str) { + $mode_chnge .= " mode: $to_mode_str"; + } + } + $mode_chnge .= "]</span>\n"; + } + print "<td>"; + print $cgi->a({-href => href(action=>"blob", hash=>$diff{'to_id'}, + hash_base=>$hash, file_name=>$diff{'file'}), + -class => "list"}, esc_html($diff{'file'})); + print "</td>\n"; + 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 ($diff{'status'} eq "R" || $diff{'status'} eq "C") { # renamed or copied + my %status_name = ('R' => 'moved', 'C' => 'copied'); + my $nstatus = $status_name{$diff{'status'}}; + my $mode_chng = ""; + if ($diff{'from_mode'} != $diff{'to_mode'}) { + # mode also for directories, so we cannot use $to_mode_str + $mode_chng = sprintf(", mode: %04o", $to_mode_oct & 0777); + } + print "<td>" . + $cgi->a({-href => href(action=>"blob", hash_base=>$hash, + hash=>$diff{'to_id'}, file_name=>$diff{'to_file'}), + -class => "list"}, esc_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 = 1; + for (my $i = $from; $i <= $to; $i++) { + my $commit = $revlist->[$i]; + #my $ref = defined $refs ? format_ref_marker($refs, $commit) : ''; + my $ref = format_ref_marker($refs, $commit); + my %co = parse_commit($commit); + if ($alternate) { + print "<tr class=\"dark\">\n"; + } else { + print "<tr class=\"light\">\n"; + } + $alternate ^= 1; + # git_summary() used print "<td><i>$co{'age_string'}</i></td>\n" . + 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'}, + href(action=>"commit", hash=>$commit), $ref); + print "</td>\n" . + "<td class=\"link\">" . + $cgi->a({-href => href(action=>"commitdiff", hash=>$commit)}, "commitdiff") . " | " . + $cgi->a({-href => href(action=>"tree", hash=>$commit, hash_base=>$commit)}, "tree"); + if (gitweb_have_snapshot()) { + print " | " . $cgi->a({-href => href(action=>"snapshot", hash=>$commit)}, "snapshot"); + } + print "</td>\n" . + "</tr>\n"; + } + if (defined $extra) { + print "<tr>\n" . + "<td colspan=\"4\">$extra</td>\n" . + "</tr>\n"; + } + print "</table>\n"; +} + +sub git_history_body { + # Warning: assumes constant type (blob or tree) during history + my ($revlist, $from, $to, $refs, $hash_base, $ftype, $extra) = @_; + + $from = 0 unless defined $from; + $to = $#{$revlist} unless (defined $to && $to <= $#{$revlist}); + + print "<table class=\"history\" cellspacing=\"0\">\n"; + my $alternate = 1; + for (my $i = $from; $i <= $to; $i++) { + if ($revlist->[$i] !~ m/^([0-9a-fA-F]{40})/) { + next; + } + + my $commit = $1; + my %co = parse_commit($commit); + if (!%co) { + next; + } + + my $ref = format_ref_marker($refs, $commit); + + if ($alternate) { + print "<tr class=\"dark\">\n"; + } else { + print "<tr class=\"light\">\n"; + } + $alternate ^= 1; + print "<td title=\"$co{'age_string_age'}\"><i>$co{'age_string_date'}</i></td>\n" . + # shortlog uses chop_str($co{'author_name'}, 10) + "<td><i>" . esc_html(chop_str($co{'author_name'}, 15, 3)) . "</i></td>\n" . + "<td>"; + # originally git_history used chop_str($co{'title'}, 50) + print format_subject_html($co{'title'}, $co{'title_short'}, + href(action=>"commit", hash=>$commit), $ref); + print "</td>\n" . + "<td class=\"link\">" . + $cgi->a({-href => href(action=>$ftype, hash_base=>$commit, file_name=>$file_name)}, $ftype) . " | " . + $cgi->a({-href => href(action=>"commitdiff", hash=>$commit)}, "commitdiff"); + + if ($ftype eq 'blob') { + my $blob_current = git_get_hash_by_path($hash_base, $file_name); + my $blob_parent = git_get_hash_by_path($commit, $file_name); + if (defined $blob_current && defined $blob_parent && + $blob_current ne $blob_parent) { + print " | " . + $cgi->a({-href => href(action=>"blobdiff", + hash=>$blob_current, hash_parent=>$blob_parent, + hash_base=>$hash_base, hash_parent_base=>$commit, + file_name=>$file_name)}, + "diff to current"); + } + } + print "</td>\n" . + "</tr>\n"; + } + if (defined $extra) { + print "<tr>\n" . + "<td colspan=\"4\">$extra</td>\n" . + "</tr>\n"; + } + print "</table>\n"; +} + +sub git_tags_body { + # uses global variable $project + my ($taglist, $from, $to, $extra) = @_; + $from = 0 unless defined $from; + $to = $#{$taglist} if (!defined $to || $#{$taglist} < $to); + + print "<table class=\"tags\" cellspacing=\"0\">\n"; + my $alternate = 1; + for (my $i = $from; $i <= $to; $i++) { + my $entry = $taglist->[$i]; + my %tag = %$entry; + my $comment_lines = $tag{'comment'}; + my $comment = shift @$comment_lines; + my $comment_short; + if (defined $comment) { + $comment_short = chop_str($comment, 30, 5); + } + if ($alternate) { + print "<tr class=\"dark\">\n"; + } else { + print "<tr class=\"light\">\n"; + } + $alternate ^= 1; + print "<td><i>$tag{'age'}</i></td>\n" . + "<td>" . + $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, + href(action=>"tag", hash=>$tag{'id'})); + } + print "</td>\n" . + "<td class=\"selflink\">"; + if ($tag{'type'} eq "tag") { + print $cgi->a({-href => href(action=>"tag", hash=>$tag{'id'})}, "tag"); + } else { + print " "; + } + print "</td>\n" . + "<td class=\"link\">" . " | " . + $cgi->a({-href => href(action=>$tag{'reftype'}, hash=>$tag{'refid'})}, $tag{'reftype'}); + if ($tag{'reftype'} eq "commit") { + 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 => href(action=>"blob_plain", hash=>$tag{'refid'})}, "raw"); + } + print "</td>\n" . + "</tr>"; + } + if (defined $extra) { + print "<tr>\n" . + "<td colspan=\"5\">$extra</td>\n" . + "</tr>\n"; + } + print "</table>\n"; +} + +sub git_heads_body { + # uses global variable $project + my ($headlist, $head, $from, $to, $extra) = @_; + $from = 0 unless defined $from; + $to = $#{$headlist} if (!defined $to || $#{$headlist} < $to); + + print "<table class=\"heads\" cellspacing=\"0\">\n"; + my $alternate = 1; + for (my $i = $from; $i <= $to; $i++) { + my $entry = $headlist->[$i]; + my %tag = %$entry; + my $curr = $tag{'id'} eq $head; + if ($alternate) { + print "<tr class=\"dark\">\n"; + } else { + print "<tr class=\"light\">\n"; + } + $alternate ^= 1; + print "<td><i>$tag{'age'}</i></td>\n" . + ($tag{'id'} eq $head ? "<td class=\"current_head\">" : "<td>") . + $cgi->a({-href => href(action=>"shortlog", hash=>$tag{'name'}), + -class => "list name"},esc_html($tag{'name'})) . + "</td>\n" . + "<td class=\"link\">" . + $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>"; + } + if (defined $extra) { + print "<tr>\n" . + "<td colspan=\"3\">$extra</td>\n" . + "</tr>\n"; + } + print "</table>\n"; +} + +## ====================================================================== +## ====================================================================== +## actions + +sub git_project_list { + my $order = $cgi->param('o'); + if (defined $order && $order !~ m/project|descr|owner|age/) { + die_error(undef, "Unknown order parameter"); + } + + my @list = git_get_projects_list(); + my @projects; + if (!@list) { + die_error(undef, "No projects found"); + } + foreach my $pr (@list) { + my $head = git_get_head_hash($pr->{'path'}); + if (!defined $head) { + next; + } + $git_dir = "$projectroot/$pr->{'path'}"; + my %co = parse_commit($head); + if (!%co) { + next; + } + $pr->{'commit'} = \%co; + if (!defined $pr->{'descr'}) { + my $descr = git_get_project_description($pr->{'path'}) || ""; + $pr->{'descr'} = chop_str($descr, 25, 5); + } + if (!defined $pr->{'owner'}) { + $pr->{'owner'} = get_file_owner("$projectroot/$pr->{'path'}") || ""; + } + push @projects, $pr; + } + + git_header_html(); + if (-f $home_text) { + print "<div class=\"index_include\">\n"; + open (my $fd, $home_text); + print <$fd>; + close $fd; + print "</div>\n"; + } + print "<table class=\"project_list\">\n" . + "<tr>\n"; + $order ||= "project"; + if ($order eq "project") { + @projects = sort {$a->{'path'} cmp $b->{'path'}} @projects; + print "<th>Project</th>\n"; + } else { + print "<th>" . + $cgi->a({-href => href(project=>undef, order=>'project'), + -class => "header"}, "Project") . + "</th>\n"; + } + if ($order eq "descr") { + @projects = sort {$a->{'descr'} cmp $b->{'descr'}} @projects; + print "<th>Description</th>\n"; + } else { + print "<th>" . + $cgi->a({-href => href(project=>undef, order=>'descr'), + -class => "header"}, "Description") . + "</th>\n"; + } + if ($order eq "owner") { + @projects = sort {$a->{'owner'} cmp $b->{'owner'}} @projects; + print "<th>Owner</th>\n"; + } else { + print "<th>" . + $cgi->a({-href => href(project=>undef, order=>'owner'), + -class => "header"}, "Owner") . + "</th>\n"; + } + if ($order eq "age") { + @projects = sort {$a->{'commit'}{'age'} <=> $b->{'commit'}{'age'}} @projects; + print "<th>Last Change</th>\n"; + } else { + print "<th>" . + $cgi->a({-href => href(project=>undef, order=>'age'), + -class => "header"}, "Last Change") . + "</th>\n"; + } + print "<th></th>\n" . + "</tr>\n"; + my $alternate = 1; + foreach my $pr (@projects) { + if ($alternate) { + print "<tr class=\"dark\">\n"; + } else { + print "<tr class=\"light\">\n"; + } + $alternate ^= 1; + 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 => 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"; + } + print "</table>\n"; + 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); + my %co = parse_commit($head); + my %cd = parse_date($co{'committer_epoch'}, $co{'committer_tz'}); + + my $owner = git_get_project_owner($project); + + 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); + + print "<div class=\"title\"> </div>\n"; + print "<table cellspacing=\"0\">\n" . + "<tr><td>description</td><td>" . esc_html($descr) . "</td></tr>\n" . + "<tr><td>owner</td><td>$owner</td></tr>\n" . + "<tr><td>last change</td><td>$cd{'rfc2822'}</td></tr>\n"; + # 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_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 => href(action=>"shortlog")}, "...")); + + if (@taglist) { + git_print_header_div('tags'); + git_tags_body(\@taglist, 0, 15, + $cgi->a({-href => href(action=>"tags")}, "...")); + } + + if (@headlist) { + git_print_header_div('heads'); + git_heads_body(\@headlist, $head, 0, 15, + $cgi->a({-href => href(action=>"heads")}, "...")); + } + + git_footer_html(); +} + +sub git_tag { + my $head = git_get_head_hash($project); + git_header_html(); + git_print_page_nav('','', $head,undef,$head); + my %tag = parse_tag($hash); + git_print_header_div('commit', esc_html($tag{'name'}), $hash); + print "<div class=\"title_text\">\n" . + "<table cellspacing=\"0\">\n" . + "<tr>\n" . + "<td>object</td>\n" . + "<td>" . $cgi->a({-class => "list", -href => 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 "</table>\n\n" . + "</div>\n"; + print "<div class=\"page_body\">"; + my $comment = $tag{'comment'}; + foreach my $line (@$comment) { + print esc_html($line) . "<br/>\n"; + } + print "</div>\n"; + git_footer_html(); +} + +sub git_blame2 { + my $fd; + my $ftype; + + 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); + my %co = parse_commit($hash_base) + or die_error(undef, "Reading commit failed"); + if (!defined $hash) { + $hash = git_get_hash_by_path($hash_base, $file_name, "blob") + or die_error(undef, "Error looking up file"); + } + $ftype = git_get_type($hash); + if ($ftype !~ "blob") { + die_error("400 Bad Request", "Object is not a blob"); + } + 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 => href(action=>"blob", hash=>$hash, hash_base=>$hash_base, file_name=>$file_name)}, + "blob") . + " | " . + $cgi->a({-href => href(action=>"history", hash=>$hash, hash_base=>$hash_base, file_name=>$file_name)}, + "history") . + " | " . + $cgi->a({-href => href(action=>"blame", file_name=>$file_name)}, + "HEAD"); + git_print_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav); + git_print_header_div('commit', esc_html($co{'title'}), $hash_base); + git_print_page_path($file_name, $ftype, $hash_base); + my @rev_color = (qw(light2 dark2)); + my $num_colors = scalar(@rev_color); + my $current_color = 0; + my $last_rev; + print <<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; + my $rev = substr($full_rev, 0, 8); + my $lineno = $2; + my $data = $3; + + if (!defined $last_rev) { + $last_rev = $full_rev; + } elsif ($last_rev ne $full_rev) { + $last_rev = $full_rev; + $current_color = ++$current_color % $num_colors; + } + print "<tr class=\"$rev_color[$current_color]\">\n"; + print "<td class=\"sha1\">" . + $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"; + git_footer_html(); +} + +sub git_blame { + my $fd; + + 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); + my %co = parse_commit($hash_base) + or die_error(undef, "Reading commit failed"); + if (!defined $hash) { + $hash = git_get_hash_by_path($hash_base, $file_name, "blob") + or die_error(undef, "Error lookup file"); + } + open ($fd, "-|", git_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 => href(action=>"blob", hash=>$hash, hash_base=>$hash_base, file_name=>$file_name)}, + "blob") . + " | " . + $cgi->a({-href => href(action=>"history", hash=>$hash, hash_base=>$hash_base, file_name=>$file_name)}, + "history") . + " | " . + $cgi->a({-href => href(action=>"blame", file_name=>$file_name)}, + "HEAD"); + git_print_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav); + git_print_header_div('commit', esc_html($co{'title'}), $hash_base); + git_print_page_path($file_name, 'blob', $hash_base); + print "<div class=\"page_body\">\n"; + print <<HTML; +<table class="blame"> + <tr> + <th>Commit</th> + <th>Age</th> + <th>Author</th> + <th>Line</th> + <th>Data</th> + </tr> +HTML + my @line_class = (qw(light dark)); + my $line_class_len = scalar (@line_class); + my $line_class_num = $#line_class; + while (my $line = <$fd>) { + my $long_rev; + my $short_rev; + my $author; + my $time; + my $lineno; + my $data; + my $age; + my $age_str; + my $age_class; + + 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+)\)(.*)$/) { + $long_rev = $1; + $author = $2; + $time = $3; + $lineno = $4; + $data = $5; + } else { + print qq( <tr><td colspan="5" class="error">Unable to parse: $line</td></tr>\n); + next; + } + $short_rev = substr ($long_rev, 0, 8); + $age = time () - $time; + $age_str = age_string ($age); + $age_str =~ s/ / /g; + $age_class = age_class($age); + $author = esc_html ($author); + $author =~ s/ / /g; + + $data = untabify($data); + $data = esc_html ($data); + + print <<HTML; + <tr class="$line_class[$line_class_num]"> + <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> + <td class="pre">$data</td> + </tr> +HTML + } # while (my $line = <$fd>) + print "</table>\n\n"; + close $fd + or print "Reading blob failed.\n"; + print "</div>"; + git_footer_html(); +} + +sub git_tags { + my $head = git_get_head_hash($project); + git_header_html(); + git_print_page_nav('','', $head,undef,$head); + git_print_header_div('summary', $project); + + my ($taglist) = git_get_refs_list("tags"); + if (@$taglist) { + git_tags_body($taglist); + } + git_footer_html(); +} + +sub git_heads { + my $head = git_get_head_hash($project); + git_header_html(); + git_print_page_nav('','', $head,undef,$head); + git_print_header_div('summary', $project); + + 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); + $hash = git_get_hash_by_path($base, $file_name, "blob") + or die_error(undef, "Error lookup file"); + } else { + die_error(undef, "No file name defined"); + } + } elsif ($hash =~ m/^[0-9a-fA-F]{40}$/) { + # blobs defined by non-textual hash id's can be cached + $expires = "+1d"; + } + + my $type = shift; + open my $fd, "-|", git_cmd(), "cat-file", "blob", $hash + or die_error(undef, "Couldn't cat $file_name, $hash"); + + $type ||= blob_mimetype($fd, $file_name); + + # save as filename, even when no $file_name is given + my $save_as = "$hash"; + if (defined $file_name) { + $save_as = $file_name; + } elsif ($type =~ m/^text\//) { + $save_as .= '.txt'; + } + + print $cgi->header( + -type => "$type", + -expires=>$expires, + -content_disposition => 'inline; filename="' . "$save_as" . '"'); + undef $/; + binmode STDOUT, ':raw'; + print <$fd>; + binmode STDOUT, ':utf8'; # as set at the beginning of gitweb.cgi + $/ = "\n"; + close $fd; +} + +sub git_blob { + my $expires; + + if (!defined $hash) { + if (defined $file_name) { + my $base = $hash_base || git_get_head_hash($project); + $hash = git_get_hash_by_path($base, $file_name, "blob") + or die_error(undef, "Error lookup file"); + } else { + die_error(undef, "No file name defined"); + } + } elsif ($hash =~ m/^[0-9a-fA-F]{40}$/) { + # blobs defined by non-textual hash id's can be cached + $expires = "+1d"; + } + + my ($have_blame) = 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(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 => href(action=>"blame", hash_base=>$hash_base, + hash=>$hash, file_name=>$file_name)}, + "blame") . + " | "; + } + $formats_nav .= + $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 => 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); + } else { + print "<div class=\"page_nav\">\n" . + "<br/><br/></div>\n" . + "<div class=\"title\">$hash</div>\n"; + } + 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); + } + 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) { + if (defined $file_name) { + $hash = git_get_hash_by_path($hash_base, $file_name, "tree"); + } else { + $hash = $hash_base; + } + } + $/ = "\0"; + open my $fd, "-|", git_cmd(), "ls-tree", '-z', $hash + or die_error(undef, "Open git-ls-tree failed"); + my @entries = map { chomp; $_ } <$fd>; + close $fd or die_error(undef, "Reading tree failed"); + $/ = "\n"; + + my $refs = git_get_references(); + my $ref = format_ref_marker($refs, $hash_base); + git_header_html(); + my $base = ""; + my ($have_blame) = gitweb_check_feature('blame'); + if (defined $hash_base && (my %co = parse_commit($hash_base))) { + my @views_nav = (); + if (defined $file_name) { + push @views_nav, + $cgi->a({-href => href(action=>"history", hash_base=>$hash_base, + hash=>$hash, file_name=>$file_name)}, + "history"), + $cgi->a({-href => href(action=>"tree", + hash_base=>"HEAD", file_name=>$file_name)}, + "HEAD"), + } + if ($have_snapshot) { + # FIXME: Should be available when we have no hash base as well. + push @views_nav, + $cgi->a({-href => href(action=>"snapshot", hash=>$hash)}, + "snapshot"); + } + git_print_page_nav('tree','', $hash_base, undef, undef, join(' | ', @views_nav)); + git_print_header_div('commit', esc_html($co{'title'}) . $ref, $hash_base); + } else { + undef $hash_base; + print "<div class=\"page_nav\">\n"; + print "<br/><br/></div>\n"; + print "<div class=\"title\">$hash</div>\n"; + } + if (defined $file_name) { + $base = esc_html("$file_name/"); + } + git_print_page_path($file_name, 'tree', $hash_base); + print "<div class=\"page_body\">\n"; + print "<table cellspacing=\"0\">\n"; + my $alternate = 1; + foreach my $line (@entries) { + my %t = parse_ls_tree_line($line, -z => 1); + + if ($alternate) { + print "<tr class=\"dark\">\n"; + } else { + print "<tr class=\"light\">\n"; + } + $alternate ^= 1; + + git_print_tree_entry(\%t, $base, $hash_base, $have_blame); + + print "</tr>\n"; + } + print "</table>\n" . + "</div>"; + git_footer_html(); +} + +sub git_snapshot { + my ($ctype, $suffix, $command) = gitweb_check_feature('snapshot'); + my $have_snapshot = (defined $ctype && defined $suffix); + if (!$have_snapshot) { + die_error('403 Permission denied', "Permission denied"); + } + + if (!defined $hash) { + $hash = git_get_head_hash($project); + } + + my $filename = basename($project) . "-$hash.tar.$suffix"; + + print $cgi->header( + -type => 'application/x-tar', + -content_encoding => $ctype, + -content_disposition => 'inline; filename="' . "$filename" . '"', + -status => '200 OK'); + + my $git = git_cmd_str(); + my $name = $project; + $name =~ s/\047/\047\\\047\047/g; + open my $fd, "-|", + "$git archive --format=tar --prefix=\'$name\'/ $hash | $command" + or die_error(undef, "Execute git-tar-tree failed."); + binmode STDOUT, ':raw'; + print <$fd>; + binmode STDOUT, ':utf8'; # as set at the beginning of gitweb.cgi + close $fd; + +} + +sub git_log { + my $head = git_get_head_hash($project); + if (!defined $hash) { + $hash = $head; + } + if (!defined $page) { + $page = 0; + } + my $refs = git_get_references(); + + my $limit = sprintf("--max-count=%i", (100 * ($page+1))); + open my $fd, "-|", git_cmd(), "rev-list", $limit, $hash + or die_error(undef, "Open git-rev-list failed"); + my @revlist = map { chomp; $_ } <$fd>; + close $fd; + + my $paging_nav = format_paging_nav('log', $hash, $head, $page, $#revlist); + + git_header_html(); + git_print_page_nav('log','', $hash,undef,undef, $paging_nav); + + if (!@revlist) { + my %co = parse_commit($hash); + + git_print_header_div('summary', $project); + print "<div class=\"page_body\"> Last change $co{'age_string'}.<br/><br/></div>\n"; + } + for (my $i = ($page * 100); $i <= $#revlist; $i++) { + my $commit = $revlist[$i]; + my $ref = format_ref_marker($refs, $commit); + my %co = parse_commit($commit); + next if !%co; + my %ad = parse_date($co{'author_epoch'}); + git_print_header_div('commit', + "<span class=\"age\">$co{'age_string'}</span>" . + esc_html($co{'title'}) . $ref, + $commit); + print "<div class=\"title_text\">\n" . + "<div class=\"log_link\">\n" . + $cgi->a({-href => 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"; + + print "<div class=\"log_body\">\n"; + git_print_simplified_log($co{'comment'}); + print "</div>\n"; + } + git_footer_html(); +} + +sub git_commit { + my %co = parse_commit($hash); + if (!%co) { + die_error(undef, "Unknown commit object"); + } + my %ad = parse_date($co{'author_epoch'}, $co{'author_tz'}); + my %cd = parse_date($co{'committer_epoch'}, $co{'committer_tz'}); + + my $parent = $co{'parent'}; + if (!defined $parent) { + $parent = "--root"; + } + open my $fd, "-|", git_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"); + + # non-textual hash id's can be cached + my $expires; + if ($hash =~ m/^[0-9a-fA-F]{40}$/) { + $expires = "+1d"; + } + my $refs = git_get_references(); + my $ref = format_ref_marker($refs, $co{'id'}); + + my $have_snapshot = gitweb_have_snapshot(); + + my @views_nav = (); + if (defined $file_name && defined $co{'parent'}) { + push @views_nav, + $cgi->a({-href => href(action=>"blame", hash_parent=>$parent, file_name=>$file_name)}, + "blame"); + } + git_header_html(undef, $expires); + git_print_page_nav('commit', '', + $hash, $co{'tree'}, $hash, + join (' | ', @views_nav)); + + if (defined $co{'parent'}) { + git_print_header_div('commitdiff', esc_html($co{'title'}) . $ref, $hash); + } else { + git_print_header_div('tree', esc_html($co{'title'}) . $ref, $co{'tree'}, $hash); + } + print "<div class=\"title_text\">\n" . + "<table cellspacing=\"0\">\n"; + print "<tr><td>author</td><td>" . esc_html($co{'author'}) . "</td></tr>\n". + "<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'}); + } else { + 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>commit</td><td class=\"sha1\">$co{'id'}</td></tr>\n"; + print "<tr>" . + "<td>tree</td>" . + "<td class=\"sha1\">" . + $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 => href(action=>"commit", hash=>$par), + class => "list"}, $par) . + "</td>" . + "<td class=\"link\">" . + $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"; + git_print_log($co{'comment'}); + print "</div>\n"; + + git_difftree_body(\@difftree, $hash, $parent); + + git_footer_html(); +} + +sub git_blobdiff { + my $format = shift || 'html'; + + my $fd; + my @difftree; + my %diffinfo; + my $expires; + + # preparing $fd and %diffinfo for git_patchset_body + # new style URI + if (defined $hash_base && defined $hash_parent_base) { + if (defined $file_name) { + # read raw output + open $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts, $hash_parent_base, $hash_base, + "--", $file_name + or die_error(undef, "Open git-diff-tree failed"); + @difftree = map { chomp; $_ } <$fd>; + close $fd + or die_error(undef, "Reading git-diff-tree failed"); + @difftree + or die_error('404 Not Found', "Blob diff not found"); + + } elsif (defined $hash && + $hash =~ /[0-9a-fA-F]{40}/) { + # try to find filename from $hash + + # read filtered raw output + open $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts, $hash_parent_base, $hash_base + or die_error(undef, "Open git-diff-tree failed"); + @difftree = + # ':100644 100644 03b21826... 3b93d5e7... M ls-files.c' + # $hash == to_id + grep { /^:[0-7]{6} [0-7]{6} [0-9a-fA-F]{40} $hash/ } + map { chomp; $_ } <$fd>; + close $fd + or die_error(undef, "Reading git-diff-tree failed"); + @difftree + or die_error('404 Not Found', "Blob diff not found"); + + } else { + 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"); + } + + # 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; + } + + # 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 (defined $file_name) { + git_print_page_path($file_name, "blob", $hash_base); + } else { + print "<div class=\"page_path\"></div>\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"); + } + + # 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; + } +} + +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"); + } + if (!defined $hash_parent) { + $hash_parent = $co{'parent'} || '--root'; + } + + # 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; + } + + } 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"); + } + + # non-textual hash id's can be cached + my $expires; + if ($hash =~ m/^[0-9a-fA-F]{40}$/) { + $expires = "+1d"; + } + + # 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(); + 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); + } + + open my $fd, "-|", + 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 .= " ⋅ " . + $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 .= " ⋅ prev"; + } + if ($#revlist >= (100 * ($page+1)-1)) { + $paging_nav .= " ⋅ " . + $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 .= " ⋅ next"; + } + my $next_link = ''; + if ($#revlist >= (100 * ($page+1)-1)) { + $next_link = + $cgi->a({-href => href(action=>"history", hash=>$hash, hash_base=>$hash_base, + file_name=>$file_name, page=>$page+1), + -title => "Alt-n"}, "next"); + } + + git_header_html(); + git_print_page_nav('history','', $hash_base,$co{'tree'},$hash_base, $paging_nav); + git_print_header_div('commit', esc_html($co{'title'}), $hash_base); + git_print_page_path($file_name, $ftype, $hash_base); + + git_history_body(\@revlist, ($page * 100), $#revlist, + $refs, $hash_base, $ftype, $next_link); + + git_footer_html(); +} + +sub git_search { + if (!defined $searchtext) { + die_error(undef, "Text field empty"); + } + if (!defined $hash) { + $hash = git_get_head_hash($project); + } + my %co = parse_commit($hash); + if (!%co) { + die_error(undef, "Unknown commit object"); + } + + my $commit_search = 1; + my $author_search = 0; + my $committer_search = 0; + my $pickaxe_search = 0; + if ($searchtext =~ s/^author\\://i) { + $author_search = 1; + } elsif ($searchtext =~ s/^committer\\://i) { + $committer_search = 1; + } elsif ($searchtext =~ s/^pickaxe\\://i) { + $commit_search = 0; + $pickaxe_search = 1; + + # 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 = 1; + if ($commit_search) { + $/ = "\0"; + open my $fd, "-|", git_cmd(), "rev-list", "--header", "--parents", $hash or next; + while (my $commit_text = <$fd>) { + if (!grep m/$searchtext/i, $commit_text) { + next; + } + if ($author_search && !grep m/\nauthor .*$searchtext/i, $commit_text) { + next; + } + if ($committer_search && !grep m/\ncommitter .*$searchtext/i, $commit_text) { + next; + } + my @commit_lines = split "\n", $commit_text; + my %co = parse_commit(undef, \@commit_lines); + if (!%co) { + next; + } + if ($alternate) { + print "<tr class=\"dark\">\n"; + } else { + print "<tr class=\"light\">\n"; + } + $alternate ^= 1; + print "<td title=\"$co{'age_string_age'}\"><i>$co{'age_string_date'}</i></td>\n" . + "<td><i>" . esc_html(chop_str($co{'author_name'}, 15, 5)) . "</i></td>\n" . + "<td>" . + $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) { + my $lead = esc_html($1) || ""; + $lead = chop_str($lead, 30, 10); + my $match = esc_html($2) || ""; + my $trail = esc_html($3) || ""; + $trail = chop_str($trail, 30, 10); + my $text = "$lead<span class=\"match\">$match</span>$trail"; + print chop_str($text, 80, 5) . "<br/>\n"; + } + } + print "</td>\n" . + "<td class=\"link\">" . + $cgi->a({-href => href(action=>"commit", hash=>$co{'id'})}, "commit") . + " | " . + $cgi->a({-href => href(action=>"tree", hash=>$co{'tree'}, hash_base=>$co{'id'})}, "tree"); + print "</td>\n" . + "</tr>\n"; + } + close $fd; + } + + if ($pickaxe_search) { + $/ = "\n"; + 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>) { + if (%co && $line =~ m/^:([0-7]{6}) ([0-7]{6}) ([0-9a-fA-F]{40}) ([0-9a-fA-F]{40}) (.)\t(.*)$/) { + my %set; + $set{'file'} = $6; + $set{'from_id'} = $3; + $set{'to_id'} = $4; + $set{'id'} = $set{'to_id'}; + if ($set{'id'} =~ m/0{40}/) { + $set{'id'} = $set{'from_id'}; + } + if ($set{'id'} =~ m/0{40}/) { + next; + } + push @files, \%set; + } elsif ($line =~ m/^([0-9a-fA-F]{40})$/){ + if (%co) { + if ($alternate) { + print "<tr class=\"dark\">\n"; + } else { + print "<tr class=\"light\">\n"; + } + $alternate ^= 1; + print "<td title=\"$co{'age_string_age'}\"><i>$co{'age_string_date'}</i></td>\n" . + "<td><i>" . esc_html(chop_str($co{'author_name'}, 15, 5)) . "</i></td>\n" . + "<td>" . + $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 => href(action=>"blob", hash_base=>$co{'id'}, + hash=>$set{'id'}, file_name=>$set{'file'}), + -class => "list"}, + "<span class=\"match\">" . esc_html($set{'file'}) . "</span>") . + "<br/>\n"; + } + print "</td>\n" . + "<td class=\"link\">" . + $cgi->a({-href => href(action=>"commit", hash=>$co{'id'})}, "commit") . + " | " . + $cgi->a({-href => href(action=>"tree", hash=>$co{'tree'}, hash_base=>$co{'id'})}, "tree"); + print "</td>\n" . + "</tr>\n"; + } + %co = parse_commit($1); + } + } + close $fd; + } + print "</table>\n"; + git_footer_html(); +} + +sub git_shortlog { + my $head = git_get_head_hash($project); + if (!defined $hash) { + $hash = $head; + } + if (!defined $page) { + $page = 0; + } + my $refs = git_get_references(); + + my $limit = sprintf("--max-count=%i", (100 * ($page+1))); + open my $fd, "-|", git_cmd(), "rev-list", $limit, $hash + or die_error(undef, "Open git-rev-list failed"); + my @revlist = map { chomp; $_ } <$fd>; + close $fd; + + my $paging_nav = format_paging_nav('shortlog', $hash, $head, $page, $#revlist); + my $next_link = ''; + if ($#revlist >= (100 * ($page+1)-1)) { + $next_link = + $cgi->a({-href => href(action=>"shortlog", hash=>$hash, page=>$page+1), + -title => "Alt-n"}, "next"); + } + + + git_header_html(); + git_print_page_nav('shortlog','', $hash,$hash,$hash, $paging_nav); + git_print_header_div('summary', $project); + + git_shortlog_body(\@revlist, ($page * 100), $#revlist, $refs, $next_link); + + git_footer_html(); +} + +## ...................................................................... +## feeds (RSS, OPML) + +sub git_rss { + # http://www.notestips.com/80256B3A007F2692/1/NAMO5P9UPQ + open my $fd, "-|", git_cmd(), "rev-list", "--max-count=150", git_get_head_hash($project) + or die_error(undef, "Open git-rev-list failed"); + my @revlist = map { chomp; $_ } <$fd>; + close $fd or die_error(undef, "Reading git-rev-list failed"); + print $cgi->header(-type => 'text/xml', -charset => 'utf-8'); + print <<XML; +<?xml version="1.0" encoding="utf-8"?> +<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"> +<channel> +<title>$project $my_uri $my_url</title> +<link>${\esc_html("$my_url?p=$project;a=summary")}</link> +<description>$project log</description> +<language>en</language> +XML + + for (my $i = 0; $i <= $#revlist; $i++) { + my $commit = $revlist[$i]; + my %co = parse_commit($commit); + # we read 150, we always show 30 and the ones more recent than 48 hours + if (($i >= 20) && ((time - $co{'committer_epoch'}) > 48*60*60)) { + last; + } + my %cd = parse_date($co{'committer_epoch'}); + open $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts, + $co{'parent'}, $co{'id'} + or next; + my @difftree = map { chomp; $_ } <$fd>; + close $fd + or next; + print "<item>\n" . + "<title>" . + sprintf("%d %s %02d:%02d", $cd{'mday'}, $cd{'month'}, $cd{'hour'}, $cd{'minute'}) . " - " . esc_html($co{'title'}) . + "</title>\n" . + "<author>" . esc_html($co{'author'}) . "</author>\n" . + "<pubDate>$cd{'rfc2822'}</pubDate>\n" . + "<guid isPermaLink=\"true\">" . esc_html("$my_url?p=$project;a=commit;h=$commit") . "</guid>\n" . + "<link>" . esc_html("$my_url?p=$project;a=commit;h=$commit") . "</link>\n" . + "<description>" . esc_html($co{'title'}) . "</description>\n" . + "<content:encoded>" . + "<![CDATA[\n"; + my $comment = $co{'comment'}; + foreach my $line (@$comment) { + $line = to_utf8($line); + print "$line<br/>\n"; + } + print "<br/>\n"; + foreach my $line (@difftree) { + if (!($line =~ m/^:([0-7]{6}) ([0-7]{6}) ([0-9a-fA-F]{40}) ([0-9a-fA-F]{40}) (.)([0-9]{0,3})\t(.*)$/)) { + next; + } + my $file = esc_html(unquote($7)); + $file = to_utf8($file); + print "$file<br/>\n"; + } + print "]]>\n" . + "</content:encoded>\n" . + "</item>\n"; + } + print "</channel></rss>"; +} + +sub git_opml { + my @list = git_get_projects_list(); + + print $cgi->header(-type => 'text/xml', -charset => 'utf-8'); + print <<XML; +<?xml version="1.0" encoding="utf-8"?> +<opml version="1.0"> +<head> + <title>$site_name Git OPML Export</title> +</head> +<body> +<outline text="git RSS feeds"> +XML + + foreach my $pr (@list) { + my %proj = %$pr; + my $head = git_get_head_hash($proj{'path'}); + if (!defined $head) { + next; + } + $git_dir = "$projectroot/$proj{'path'}"; + my %co = parse_commit($head); + if (!%co) { + next; + } + + my $path = esc_html(chop_str($proj{'path'}, 25, 5)); + my $rss = "$my_url?p=$proj{'path'};a=rss"; + my $html = "$my_url?p=$proj{'path'};a=summary"; + print "<outline type=\"rss\" text=\"$path\" title=\"$path\" xmlUrl=\"$rss\" htmlUrl=\"$html\"/>\n"; + } + print <<XML; +</outline> +</body> +</opml> +XML +} diff --git a/grep.c b/grep.c new file mode 100644 index 0000000000..c411ddd4d5 --- /dev/null +++ b/grep.c @@ -0,0 +1,498 @@ +#include "cache.h" +#include <regex.h> +#include "grep.h" + +void append_grep_pattern(struct grep_opt *opt, const char *pat, + const char *origin, int no, enum grep_pat_token t) +{ + struct grep_pat *p = xcalloc(1, sizeof(*p)); + p->pattern = pat; + p->origin = origin; + p->no = no; + p->token = t; + *opt->pattern_tail = p; + opt->pattern_tail = &p->next; + p->next = NULL; +} + +static void compile_regexp(struct grep_pat *p, struct grep_opt *opt) +{ + int err = regcomp(&p->regexp, p->pattern, opt->regflags); + if (err) { + char errbuf[1024]; + char where[1024]; + if (p->no) + sprintf(where, "In '%s' at %d, ", + p->origin, p->no); + else if (p->origin) + sprintf(where, "%s, ", p->origin); + else + where[0] = 0; + regerror(err, &p->regexp, errbuf, 1024); + regfree(&p->regexp); + die("%s'%s': %s", where, p->pattern, errbuf); + } +} + +static struct grep_expr *compile_pattern_expr(struct grep_pat **); +static struct grep_expr *compile_pattern_atom(struct grep_pat **list) +{ + struct grep_pat *p; + struct grep_expr *x; + + p = *list; + switch (p->token) { + case GREP_PATTERN: /* atom */ + case GREP_PATTERN_HEAD: + case GREP_PATTERN_BODY: + x = xcalloc(1, sizeof (struct grep_expr)); + x->node = GREP_NODE_ATOM; + x->u.atom = p; + *list = p->next; + return x; + case GREP_OPEN_PAREN: + *list = p->next; + x = compile_pattern_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; + + p = *list; + switch (p->token) { + case GREP_NOT: + if (!p->next) + die("--not not followed by pattern expression"); + *list = p->next; + x = xcalloc(1, sizeof (struct grep_expr)); + x->node = GREP_NODE_NOT; + x->u.unary = compile_pattern_not(list); + if (!x->u.unary) + die("--not followed by non pattern expression"); + return x; + default: + return compile_pattern_atom(list); + } +} + +static struct grep_expr *compile_pattern_and(struct grep_pat **list) +{ + struct grep_pat *p; + struct grep_expr *x, *y, *z; + + x = compile_pattern_not(list); + p = *list; + if (p && p->token == GREP_AND) { + if (!p->next) + die("--and not followed by pattern expression"); + *list = p->next; + y = compile_pattern_and(list); + if (!y) + die("--and not followed by pattern expression"); + z = xcalloc(1, sizeof (struct grep_expr)); + z->node = GREP_NODE_AND; + z->u.binary.left = x; + z->u.binary.right = y; + return z; + } + return x; +} + +static struct grep_expr *compile_pattern_or(struct grep_pat **list) +{ + struct grep_pat *p; + struct grep_expr *x, *y, *z; + + x = compile_pattern_and(list); + p = *list; + if (x && p && p->token != GREP_CLOSE_PAREN) { + y = compile_pattern_or(list); + if (!y) + die("not a pattern expression %s", p->pattern); + z = xcalloc(1, sizeof (struct grep_expr)); + z->node = GREP_NODE_OR; + z->u.binary.left = x; + z->u.binary.right = y; + return z; + } + return x; +} + +static struct grep_expr *compile_pattern_expr(struct grep_pat **list) +{ + return compile_pattern_or(list); +} + +void compile_grep_patterns(struct grep_opt *opt) +{ + struct grep_pat *p; + + for (p = opt->pattern_list; p; p = p->next) { + switch (p->token) { + case GREP_PATTERN: /* atom */ + case GREP_PATTERN_HEAD: + case GREP_PATTERN_BODY: + if (!opt->fixed) + compile_regexp(p, opt); + break; + default: + opt->extended = 1; + break; + } + } + + if (!opt->extended) + return; + + /* Then bundle them up in an expression. + * A classic recursive descent parser would do. + */ + p = opt->pattern_list; + opt->pattern_expression = compile_pattern_expr(&p); + if (p) + die("incomplete pattern expression: %s", p->pattern); +} + +static void free_pattern_expr(struct grep_expr *x) +{ + switch (x->node) { + case GREP_NODE_ATOM: + break; + case GREP_NODE_NOT: + free_pattern_expr(x->u.unary); + break; + case GREP_NODE_AND: + case GREP_NODE_OR: + free_pattern_expr(x->u.binary.left); + free_pattern_expr(x->u.binary.right); + break; + } + free(x); +} + +void free_grep_patterns(struct grep_opt *opt) +{ + struct grep_pat *p, *n; + + for (p = opt->pattern_list; p; p = n) { + n = p->next; + switch (p->token) { + case GREP_PATTERN: /* atom */ + case GREP_PATTERN_HEAD: + case GREP_PATTERN_BODY: + regfree(&p->regexp); + break; + default: + break; + } + free(p); + } + + if (!opt->extended) + return; + free_pattern_expr(opt->pattern_expression); +} + +static char *end_of_line(char *cp, unsigned long *left) +{ + unsigned long l = *left; + while (l && *cp != '\n') { + l--; + cp++; + } + *left = l; + return cp; +} + +static int word_char(char ch) +{ + return isalnum(ch) || ch == '_'; +} + +static void show_line(struct grep_opt *opt, const char *bol, const char *eol, + const char *name, unsigned lno, char sign) +{ + if (opt->pathname) + printf("%s%c", name, sign); + if (opt->linenum) + printf("%d%c", lno, sign); + printf("%.*s\n", (int)(eol-bol), bol); +} + +/* + * NEEDSWORK: share code with diff.c + */ +#define FIRST_FEW_BYTES 8000 +static int buffer_is_binary(const char *ptr, unsigned long size) +{ + if (FIRST_FEW_BYTES < size) + size = FIRST_FEW_BYTES; + return !!memchr(ptr, 0, size); +} + +static int fixmatch(const char *pattern, char *line, regmatch_t *match) +{ + char *hit = strstr(line, pattern); + if (!hit) { + match->rm_so = match->rm_eo = -1; + return REG_NOMATCH; + } + else { + match->rm_so = hit - line; + match->rm_eo = match->rm_so + strlen(pattern); + return 0; + } +} + +static int match_one_pattern(struct grep_opt *opt, struct grep_pat *p, char *bol, char *eol, enum grep_context ctx) +{ + int hit = 0; + int at_true_bol = 1; + regmatch_t pmatch[10]; + + if ((p->token != GREP_PATTERN) && + ((p->token == GREP_PATTERN_HEAD) != (ctx == GREP_CONTEXT_HEAD))) + return 0; + + again: + if (!opt->fixed) { + regex_t *exp = &p->regexp; + hit = !regexec(exp, bol, ARRAY_SIZE(pmatch), + pmatch, 0); + } + else { + hit = !fixmatch(p->pattern, bol, pmatch); + } + + if (hit && opt->word_regexp) { + if ((pmatch[0].rm_so < 0) || + (eol - bol) <= pmatch[0].rm_so || + (pmatch[0].rm_eo < 0) || + (eol - bol) < pmatch[0].rm_eo) + die("regexp returned nonsense"); + + /* Match beginning must be either beginning of the + * line, or at word boundary (i.e. the last char must + * not be a word char). Similarly, match end must be + * either end of the line, or at word boundary + * (i.e. the next char must not be a word char). + */ + if ( ((pmatch[0].rm_so == 0 && at_true_bol) || + !word_char(bol[pmatch[0].rm_so-1])) && + ((pmatch[0].rm_eo == (eol-bol)) || + !word_char(bol[pmatch[0].rm_eo])) ) + ; + else + hit = 0; + + if (!hit && pmatch[0].rm_so + bol + 1 < eol) { + /* There could be more than one match on the + * line, and the first match might not be + * strict word match. But later ones could be! + */ + bol = pmatch[0].rm_so + bol + 1; + at_true_bol = 0; + goto again; + } + } + return hit; +} + +static int match_expr_eval(struct grep_opt *opt, + struct grep_expr *x, + char *bol, char *eol, + enum grep_context ctx) +{ + switch (x->node) { + case GREP_NODE_ATOM: + return match_one_pattern(opt, x->u.atom, bol, eol, ctx); + break; + case GREP_NODE_NOT: + return !match_expr_eval(opt, x->u.unary, bol, eol, ctx); + case GREP_NODE_AND: + return (match_expr_eval(opt, x->u.binary.left, bol, eol, ctx) && + match_expr_eval(opt, x->u.binary.right, bol, eol, ctx)); + case GREP_NODE_OR: + return (match_expr_eval(opt, x->u.binary.left, bol, eol, ctx) || + match_expr_eval(opt, x->u.binary.right, bol, eol, ctx)); + } + die("Unexpected node type (internal error) %d\n", x->node); +} + +static int match_expr(struct grep_opt *opt, char *bol, char *eol, + enum grep_context ctx) +{ + struct grep_expr *x = opt->pattern_expression; + return match_expr_eval(opt, x, bol, eol, ctx); +} + +static int match_line(struct grep_opt *opt, char *bol, char *eol, + enum grep_context ctx) +{ + struct grep_pat *p; + if (opt->extended) + return match_expr(opt, bol, eol, ctx); + for (p = opt->pattern_list; p; p = p->next) { + if (match_one_pattern(opt, p, bol, eol, ctx)) + return 1; + } + return 0; +} + +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; + enum grep_context ctx = GREP_CONTEXT_HEAD; + + if (buffer_is_binary(buf, size)) { + switch (opt->binary) { + case GREP_BINARY_DEFAULT: + binary_match_only = 1; + break; + case GREP_BINARY_NOMATCH: + return 0; /* Assume unmatch */ + break; + default: + break; + } + } + + if (opt->pre_context) + prev = xcalloc(opt->pre_context, sizeof(*prev)); + if (opt->pre_context || opt->post_context) + hunk_mark = "--\n"; + + while (left) { + char *eol, ch; + int hit = 0; + + eol = end_of_line(bol, &left); + ch = *eol; + *eol = 0; + + if ((ctx == GREP_CONTEXT_HEAD) && (eol == bol)) + ctx = GREP_CONTEXT_BODY; + + hit = match_line(opt, bol, eol, ctx); + *eol = ch; + + /* "grep -v -e foo -e bla" should list lines + * that do not have either, so inversion should + * be done outside. + */ + if (opt->invert) + hit = !hit; + if (opt->unmatch_name_only) { + if (hit) + return 0; + goto next_line; + } + if (hit) { + count++; + if (opt->status_only) + return 1; + if (binary_match_only) { + printf("Binary file %s matches\n", name); + return 1; + } + if (opt->name_only) { + printf("%s\n", name); + return 1; + } + /* Hit at this line. If we haven't shown the + * pre-context lines, we would need to show them. + * When asked to do "count", this still show + * the context which is nonsense, but the user + * deserves to get that ;-). + */ + if (opt->pre_context) { + unsigned from; + if (opt->pre_context < lno) + from = lno - opt->pre_context; + else + from = 1; + if (from <= last_shown) + from = last_shown + 1; + if (last_shown && from != last_shown + 1) + printf(hunk_mark); + while (from < lno) { + pcl = &prev[lno-from-1]; + show_line(opt, pcl->bol, pcl->eol, + name, from, '-'); + from++; + } + last_shown = lno-1; + } + if (last_shown && lno != last_shown + 1) + printf(hunk_mark); + if (!opt->count) + show_line(opt, bol, eol, name, lno, ':'); + last_shown = last_hit = lno; + } + else if (last_hit && + lno <= last_hit + opt->post_context) { + /* If the last hit is within the post context, + * we need to show this line. + */ + if (last_shown && lno != last_shown + 1) + printf(hunk_mark); + show_line(opt, bol, eol, name, lno, '-'); + last_shown = lno; + } + if (opt->pre_context) { + memmove(prev+1, prev, + (opt->pre_context-1) * sizeof(*prev)); + prev->bol = bol; + prev->eol = eol; + } + + next_line: + bol = eol + 1; + if (!left) + break; + left--; + lno++; + } + + free(prev); + + if (opt->status_only) + return 0; + if (opt->unmatch_name_only) { + /* We did not see any hit, so we want to show this */ + printf("%s\n", name); + return 1; + } + + /* NEEDSWORK: + * The real "grep -c foo *.c" gives many "bar.c:0" lines, + * which feels mostly useless but sometimes useful. Maybe + * make it another option? For now suppress them. + */ + if (opt->count && count) + printf("%s:%u\n", name, count); + return !!last_hit; +} + diff --git a/grep.h b/grep.h new file mode 100644 index 0000000000..af9098cfe8 --- /dev/null +++ b/grep.h @@ -0,0 +1,79 @@ +#ifndef GREP_H +#define GREP_H + +enum grep_pat_token { + GREP_PATTERN, + GREP_PATTERN_HEAD, + GREP_PATTERN_BODY, + GREP_AND, + GREP_OPEN_PAREN, + GREP_CLOSE_PAREN, + GREP_NOT, + GREP_OR, +}; + +enum grep_context { + GREP_CONTEXT_HEAD, + GREP_CONTEXT_BODY, +}; + +struct grep_pat { + struct grep_pat *next; + const char *origin; + int no; + enum grep_pat_token token; + const char *pattern; + regex_t regexp; +}; + +enum grep_expr_node { + GREP_NODE_ATOM, + GREP_NODE_NOT, + GREP_NODE_AND, + GREP_NODE_OR, +}; + +struct grep_expr { + enum grep_expr_node node; + union { + struct grep_pat *atom; + struct grep_expr *unary; + struct { + struct grep_expr *left; + struct grep_expr *right; + } binary; + } u; +}; + +struct grep_opt { + struct grep_pat *pattern_list; + struct grep_pat **pattern_tail; + struct grep_expr *pattern_expression; + int prefix_length; + regex_t regexp; + unsigned linenum:1; + unsigned invert:1; + unsigned status_only:1; + unsigned name_only:1; + unsigned unmatch_name_only:1; + unsigned count:1; + unsigned word_regexp:1; + unsigned fixed:1; +#define GREP_BINARY_DEFAULT 0 +#define GREP_BINARY_NOMATCH 1 +#define GREP_BINARY_TEXT 2 + unsigned binary:2; + unsigned extended:1; + unsigned relative:1; + unsigned pathname:1; + int regflags; + unsigned pre_context; + unsigned post_context; +}; + +extern void append_grep_pattern(struct grep_opt *opt, const char *pat, const char *origin, int no, enum grep_pat_token t); +extern void compile_grep_patterns(struct grep_opt *opt); +extern void free_grep_patterns(struct grep_opt *opt); +extern int grep_buffer(struct grep_opt *opt, const char *name, char *buf, unsigned long size); + +#endif diff --git a/builtin-help.c b/help.c index 6484cb9df2..0824c25226 100644 --- a/builtin-help.c +++ b/help.c @@ -14,7 +14,7 @@ static int term_columns(void) { char *col_string = getenv("COLUMNS"); - int n_cols = 0; + int n_cols; if (col_string && (n_cols = atoi(col_string)) > 0) return n_cols; @@ -184,7 +184,7 @@ static void show_man_page(const char *git_cmd) page = git_cmd; else { int page_len = strlen(git_cmd) + 4; - char *p = malloc(page_len + 1); + char *p = xmalloc(page_len + 1); strcpy(p, "git-"); strcpy(p + 4, git_cmd); p[page_len] = 0; diff --git a/http-fetch.c b/http-fetch.c index 259292d4de..396552da02 100644 --- a/http-fetch.c +++ b/http-fetch.c @@ -4,42 +4,13 @@ #include "fetch.h" #include "http.h" -#ifndef NO_EXPAT -#include <expat.h> - -/* Definitions for DAV requests */ -#define DAV_PROPFIND "PROPFIND" -#define DAV_PROPFIND_RESP ".multistatus.response" -#define DAV_PROPFIND_NAME ".multistatus.response.href" -#define DAV_PROPFIND_COLLECTION ".multistatus.response.propstat.prop.resourcetype.collection" -#define PROPFIND_ALL_REQUEST "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n<D:propfind xmlns:D=\"DAV:\">\n<D:allprop/>\n</D:propfind>" - -/* Definitions for processing XML DAV responses */ -#ifndef XML_STATUS_OK -enum XML_Status { - XML_STATUS_OK = 1, - XML_STATUS_ERROR = 0 -}; -#define XML_STATUS_OK 1 -#define XML_STATUS_ERROR 0 -#endif - -/* Flags that control remote_ls processing */ -#define PROCESS_FILES (1u << 0) -#define PROCESS_DIRS (1u << 1) -#define RECURSIVE (1u << 2) - -/* Flags that remote_ls passes to callback functions */ -#define IS_DIR (1u << 0) -#endif - #define PREV_BUF_SIZE 4096 #define RANGE_HEADER_SIZE 30 -static int commits_on_stdin = 0; +static int commits_on_stdin; static int got_alternates = -1; -static int corrupt_object_found = 0; +static int corrupt_object_found; static struct curl_slist *no_pragma_header; @@ -52,7 +23,7 @@ struct alt_base struct alt_base *next; }; -static struct alt_base *alt = NULL; +static struct alt_base *alt; enum object_request_state { WAITING, @@ -90,31 +61,7 @@ struct alternates_request { int http_specific; }; -#ifndef NO_EXPAT -struct xml_ctx -{ - char *name; - int len; - char *cdata; - void (*userFunc)(struct xml_ctx *ctx, int tag_closed); - void *userData; -}; - -struct remote_ls_ctx -{ - struct alt_base *repo; - char *path; - void (*userFunc)(struct remote_ls_ctx *ls); - void *userData; - int flags; - char *dentry_name; - int dentry_flags; - int rc; - struct remote_ls_ctx *parent; -}; -#endif - -static struct object_request *object_queue_head = NULL; +static struct object_request *object_queue_head; static size_t fwrite_sha1_file(void *ptr, size_t eltsize, size_t nmemb, void *data) @@ -144,6 +91,19 @@ static size_t fwrite_sha1_file(void *ptr, size_t eltsize, size_t nmemb, return size; } +static int missing__target(int code, int result) +{ + return /* file:// URL -- do we ever use one??? */ + (result == CURLE_FILE_COULDNT_READ_FILE) || + /* http:// and https:// URL */ + (code == 404 && result == CURLE_HTTP_RETURNED_ERROR) || + /* ftp:// URL */ + (code == 550 && result == CURLE_FTP_COULDNT_RETR_FILE) + ; +} + +#define missing_target(a) missing__target((a)->http_code, (a)->curl_result) + static void fetch_alternates(const char *base); static void process_object_response(void *callback_data); @@ -301,7 +261,7 @@ static void finish_object_request(struct object_request *obj_req) unlink(obj_req->tmpfile); return; } - if (memcmp(obj_req->sha1, obj_req->real_sha1, 20)) { + if (hashcmp(obj_req->sha1, obj_req->real_sha1)) { unlink(obj_req->tmpfile); return; } @@ -323,8 +283,7 @@ static void process_object_response(void *callback_data) obj_req->state = COMPLETE; /* Use alternates if necessary */ - if (obj_req->http_code == 404 || - obj_req->curl_result == CURLE_FILE_COULDNT_READ_FILE) { + if (missing_target(obj_req)) { fetch_alternates(alt->base); if (obj_req->repo->next != NULL) { obj_req->repo = @@ -393,7 +352,7 @@ void prefetch(unsigned char *sha1) char *filename = sha1_file_name(sha1); newreq = xmalloc(sizeof(*newreq)); - memcpy(newreq->sha1, sha1, 20); + hashcpy(newreq->sha1, sha1); newreq->repo = alt; newreq->url = NULL; newreq->local = -1; @@ -537,8 +496,7 @@ static void process_alternates_response(void *callback_data) return; } } else if (slot->curl_result != CURLE_OK) { - if (slot->http_code != 404 && - slot->curl_result != CURLE_FILE_COULDNT_READ_FILE) { + if (!missing_target(slot)) { got_alternates = -1; return; } @@ -703,209 +661,6 @@ static void fetch_alternates(const char *base) free(url); } -#ifndef NO_EXPAT -static void -xml_start_tag(void *userData, const char *name, const char **atts) -{ - struct xml_ctx *ctx = (struct xml_ctx *)userData; - const char *c = strchr(name, ':'); - int new_len; - - if (c == NULL) - c = name; - else - c++; - - new_len = strlen(ctx->name) + strlen(c) + 2; - - if (new_len > ctx->len) { - ctx->name = xrealloc(ctx->name, new_len); - ctx->len = new_len; - } - strcat(ctx->name, "."); - strcat(ctx->name, c); - - if (ctx->cdata) { - free(ctx->cdata); - ctx->cdata = NULL; - } - - ctx->userFunc(ctx, 0); -} - -static void -xml_end_tag(void *userData, const char *name) -{ - struct xml_ctx *ctx = (struct xml_ctx *)userData; - const char *c = strchr(name, ':'); - char *ep; - - ctx->userFunc(ctx, 1); - - if (c == NULL) - c = name; - else - c++; - - ep = ctx->name + strlen(ctx->name) - strlen(c) - 1; - *ep = 0; -} - -static void -xml_cdata(void *userData, const XML_Char *s, int len) -{ - struct xml_ctx *ctx = (struct xml_ctx *)userData; - if (ctx->cdata) - free(ctx->cdata); - ctx->cdata = xmalloc(len + 1); - strlcpy(ctx->cdata, s, len + 1); -} - -static int remote_ls(struct alt_base *repo, const char *path, int flags, - void (*userFunc)(struct remote_ls_ctx *ls), - void *userData); - -static void handle_remote_ls_ctx(struct xml_ctx *ctx, int tag_closed) -{ - struct remote_ls_ctx *ls = (struct remote_ls_ctx *)ctx->userData; - - if (tag_closed) { - if (!strcmp(ctx->name, DAV_PROPFIND_RESP) && ls->dentry_name) { - if (ls->dentry_flags & IS_DIR) { - if (ls->flags & PROCESS_DIRS) { - ls->userFunc(ls); - } - if (strcmp(ls->dentry_name, ls->path) && - ls->flags & RECURSIVE) { - ls->rc = remote_ls(ls->repo, - ls->dentry_name, - ls->flags, - ls->userFunc, - ls->userData); - } - } else if (ls->flags & PROCESS_FILES) { - ls->userFunc(ls); - } - } else if (!strcmp(ctx->name, DAV_PROPFIND_NAME) && ctx->cdata) { - ls->dentry_name = xmalloc(strlen(ctx->cdata) - - ls->repo->path_len + 1); - strcpy(ls->dentry_name, ctx->cdata + ls->repo->path_len); - } else if (!strcmp(ctx->name, DAV_PROPFIND_COLLECTION)) { - ls->dentry_flags |= IS_DIR; - } - } else if (!strcmp(ctx->name, DAV_PROPFIND_RESP)) { - if (ls->dentry_name) { - free(ls->dentry_name); - } - ls->dentry_name = NULL; - ls->dentry_flags = 0; - } -} - -static int remote_ls(struct alt_base *repo, const char *path, int flags, - void (*userFunc)(struct remote_ls_ctx *ls), - void *userData) -{ - char *url = xmalloc(strlen(repo->base) + strlen(path) + 1); - struct active_request_slot *slot; - struct slot_results results; - struct buffer in_buffer; - struct buffer out_buffer; - char *in_data; - char *out_data; - XML_Parser parser = XML_ParserCreate(NULL); - enum XML_Status result; - struct curl_slist *dav_headers = NULL; - struct xml_ctx ctx; - struct remote_ls_ctx ls; - - ls.flags = flags; - ls.repo = repo; - ls.path = strdup(path); - ls.dentry_name = NULL; - ls.dentry_flags = 0; - ls.userData = userData; - ls.userFunc = userFunc; - ls.rc = 0; - - sprintf(url, "%s%s", repo->base, path); - - out_buffer.size = strlen(PROPFIND_ALL_REQUEST); - out_data = xmalloc(out_buffer.size + 1); - snprintf(out_data, out_buffer.size + 1, PROPFIND_ALL_REQUEST); - out_buffer.posn = 0; - out_buffer.buffer = out_data; - - in_buffer.size = 4096; - in_data = xmalloc(in_buffer.size); - in_buffer.posn = 0; - in_buffer.buffer = in_data; - - dav_headers = curl_slist_append(dav_headers, "Depth: 1"); - dav_headers = curl_slist_append(dav_headers, "Content-Type: text/xml"); - - slot = get_active_slot(); - slot->results = &results; - curl_easy_setopt(slot->curl, CURLOPT_INFILE, &out_buffer); - curl_easy_setopt(slot->curl, CURLOPT_INFILESIZE, out_buffer.size); - curl_easy_setopt(slot->curl, CURLOPT_READFUNCTION, fread_buffer); - curl_easy_setopt(slot->curl, CURLOPT_FILE, &in_buffer); - curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_buffer); - curl_easy_setopt(slot->curl, CURLOPT_URL, url); - curl_easy_setopt(slot->curl, CURLOPT_UPLOAD, 1); - curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, DAV_PROPFIND); - curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, dav_headers); - - if (start_active_slot(slot)) { - run_active_slot(slot); - if (results.curl_result == CURLE_OK) { - ctx.name = xcalloc(10, 1); - ctx.len = 0; - ctx.cdata = NULL; - ctx.userFunc = handle_remote_ls_ctx; - ctx.userData = &ls; - XML_SetUserData(parser, &ctx); - XML_SetElementHandler(parser, xml_start_tag, - xml_end_tag); - XML_SetCharacterDataHandler(parser, xml_cdata); - result = XML_Parse(parser, in_buffer.buffer, - in_buffer.posn, 1); - free(ctx.name); - - if (result != XML_STATUS_OK) { - ls.rc = error("XML error: %s", - XML_ErrorString( - XML_GetErrorCode(parser))); - } - } else { - ls.rc = -1; - } - } else { - ls.rc = error("Unable to start PROPFIND request"); - } - - free(ls.path); - free(url); - free(out_data); - free(in_buffer.buffer); - curl_slist_free_all(dav_headers); - - return ls.rc; -} - -static void process_ls_pack(struct remote_ls_ctx *ls) -{ - unsigned char sha1[20]; - - if (strlen(ls->dentry_name) == 63 && - !strncmp(ls->dentry_name, "objects/pack/pack-", 18) && - has_extension(ls->dentry_name, ".pack")) { - get_sha1_hex(ls->dentry_name + 18, sha1); - setup_index(ls->repo, sha1); - } -} -#endif - static int fetch_indices(struct alt_base *repo) { unsigned char sha1[20]; @@ -928,12 +683,6 @@ static int fetch_indices(struct alt_base *repo) if (get_verbosely) fprintf(stderr, "Getting pack list for %s\n", repo->base); -#ifndef NO_EXPAT - if (remote_ls(repo, "objects/pack/", PROCESS_FILES, - process_ls_pack, NULL) == 0) - return 0; -#endif - url = xmalloc(strlen(repo->base) + 21); sprintf(url, "%s/objects/info/packs", repo->base); @@ -946,8 +695,7 @@ static int fetch_indices(struct alt_base *repo) if (start_active_slot(slot)) { run_active_slot(slot); if (results.curl_result != CURLE_OK) { - if (results.http_code == 404 || - results.curl_result == CURLE_FILE_COULDNT_READ_FILE) { + if (missing_target(&results)) { repo->got_indices = 1; free(buffer.buffer); return 0; @@ -1099,7 +847,7 @@ static int fetch_object(struct alt_base *repo, unsigned char *sha1) int ret = 0; struct object_request *obj_req = object_queue_head; - while (obj_req != NULL && memcmp(obj_req->sha1, sha1, 20)) + while (obj_req != NULL && hashcmp(obj_req->sha1, sha1)) obj_req = obj_req->next; if (obj_req == NULL) return error("Couldn't find request for %s in the queue", hex); @@ -1128,8 +876,7 @@ static int fetch_object(struct alt_base *repo, unsigned char *sha1) ret = error("Request for %s aborted", hex); } else if (obj_req->curl_result != CURLE_OK && obj_req->http_code != 416) { - if (obj_req->http_code == 404 || - obj_req->curl_result == CURLE_FILE_COULDNT_READ_FILE) + if (missing_target(obj_req)) ret = -1; /* Be silent, it is probably in a pack. */ else ret = error("%s (curl_result = %d, http_code = %ld, sha1 = %s)", @@ -1138,7 +885,7 @@ static int fetch_object(struct alt_base *repo, unsigned char *sha1) } else if (obj_req->zret != Z_STREAM_END) { corrupt_object_found++; ret = error("File %s (%s) corrupt", hex, obj_req->url); - } else if (memcmp(obj_req->sha1, obj_req->real_sha1, 20)) { + } else if (hashcmp(obj_req->sha1, obj_req->real_sha1)) { ret = error("File %s has bad hash", hex); } else if (obj_req->rename < 0) { ret = error("unable to write sha1 filename %s", diff --git a/http-push.c b/http-push.c index d45733ef64..670ff007be 100644 --- a/http-push.c +++ b/http-push.c @@ -70,18 +70,18 @@ enum XML_Status { /* We allow "recursive" symbolic refs. Only within reason, though */ #define MAXDEPTH 5 -static int pushing = 0; -static int aborted = 0; +static int pushing; +static int aborted; static signed char remote_dir_exists[256]; static struct curl_slist *no_pragma_header; static struct curl_slist *default_headers; -static int push_verbosely = 0; -static int push_all = 0; -static int force_all = 0; +static int push_verbosely; +static int push_all; +static int force_all; -static struct object_list *objects = NULL; +static struct object_list *objects; struct repo { @@ -94,7 +94,7 @@ struct repo struct remote_lock *locks; }; -static struct repo *remote = NULL; +static struct repo *remote; enum transfer_state { NEED_FETCH, @@ -134,7 +134,7 @@ struct transfer_request struct transfer_request *next; }; -static struct transfer_request *request_queue_head = NULL; +static struct transfer_request *request_queue_head; struct xml_ctx { @@ -745,7 +745,7 @@ static void finish_request(struct transfer_request *request) SHA1_Final(request->real_sha1, &request->c); if (request->zret != Z_STREAM_END) { unlink(request->tmpfile); - } else if (memcmp(request->obj->sha1, request->real_sha1, 20)) { + } else if (hashcmp(request->obj->sha1, request->real_sha1)) { unlink(request->tmpfile); } else { request->rename = @@ -1238,10 +1238,8 @@ xml_start_tag(void *userData, const char *name, const char **atts) strcat(ctx->name, "."); strcat(ctx->name, c); - if (ctx->cdata) { - free(ctx->cdata); - ctx->cdata = NULL; - } + free(ctx->cdata); + ctx->cdata = NULL; ctx->userFunc(ctx, 0); } @@ -1268,8 +1266,7 @@ static void xml_cdata(void *userData, const XML_Char *s, int len) { struct xml_ctx *ctx = (struct xml_ctx *)userData; - if (ctx->cdata) - free(ctx->cdata); + free(ctx->cdata); ctx->cdata = xmalloc(len + 1); strlcpy(ctx->cdata, s, len + 1); } @@ -1518,9 +1515,7 @@ static void handle_remote_ls_ctx(struct xml_ctx *ctx, int tag_closed) ls->dentry_flags |= IS_DIR; } } else if (!strcmp(ctx->name, DAV_PROPFIND_RESP)) { - if (ls->dentry_name) { - free(ls->dentry_name); - } + free(ls->dentry_name); ls->dentry_name = NULL; ls->dentry_flags = 0; } @@ -1544,7 +1539,7 @@ static void remote_ls(const char *path, int flags, struct remote_ls_ctx ls; ls.flags = flags; - ls.path = strdup(path); + ls.path = xstrdup(path); ls.dentry_name = NULL; ls.dentry_flags = 0; ls.userData = userData; @@ -1700,7 +1695,7 @@ static int locking_available(void) return lock_flags; } -struct object_list **add_one_object(struct object *obj, struct object_list **p) +static struct object_list **add_one_object(struct object *obj, struct object_list **p) { struct object_list *entry = xmalloc(sizeof(struct object_list)); entry->item = obj; @@ -1743,7 +1738,7 @@ static struct object_list **process_tree(struct tree *tree, die("bad tree object %s", sha1_to_hex(obj->sha1)); obj->flags |= SEEN; - name = strdup(name); + name = xstrdup(name); p = add_one_object(obj, p); me.up = path; me.elem = name; @@ -1874,7 +1869,7 @@ static int one_local_ref(const char *refname, const unsigned char *sha1) struct ref *ref; int len = strlen(refname) + 1; ref = xcalloc(1, sizeof(*ref) + len); - memcpy(ref->new_sha1, sha1, 20); + hashcpy(ref->new_sha1, sha1); memcpy(ref->name, refname, len); *local_tail = ref; local_tail = &ref->next; @@ -1909,7 +1904,7 @@ static void one_remote_ref(char *refname) } ref = xcalloc(1, sizeof(*ref) + len); - memcpy(ref->old_sha1, remote_sha1, 20); + hashcpy(ref->old_sha1, remote_sha1); memcpy(ref->name, refname, len); *remote_tail = ref; remote_tail = &ref->next; @@ -2164,7 +2159,7 @@ static void fetch_symref(const char *path, char **symref, unsigned char *sha1) if (*symref != NULL) free(*symref); *symref = NULL; - memset(sha1, 0, 20); + hashclr(sha1); if (buffer.posn == 0) return; @@ -2182,49 +2177,11 @@ static void fetch_symref(const char *path, char **symref, unsigned char *sha1) static int verify_merge_base(unsigned char *head_sha1, unsigned char *branch_sha1) { - int pipe_fd[2]; - pid_t merge_base_pid; - char line[PATH_MAX + 20]; - unsigned char merge_sha1[20]; - int verified = 0; - - if (pipe(pipe_fd) < 0) - die("Verify merge base: pipe failed"); - - merge_base_pid = fork(); - if (!merge_base_pid) { - static const char *args[] = { - "merge-base", - "-a", - NULL, - NULL, - NULL - }; - args[2] = strdup(sha1_to_hex(head_sha1)); - args[3] = sha1_to_hex(branch_sha1); - - dup2(pipe_fd[1], 1); - close(pipe_fd[0]); - close(pipe_fd[1]); - execv_git_cmd(args); - die("merge-base setup failed"); - } - if (merge_base_pid < 0) - die("merge-base fork failed"); - - dup2(pipe_fd[0], 0); - close(pipe_fd[0]); - close(pipe_fd[1]); - while (fgets(line, sizeof(line), stdin) != NULL) { - if (get_sha1_hex(line, merge_sha1)) - die("expected sha1, got garbage:\n %s", line); - if (!memcmp(branch_sha1, merge_sha1, 20)) { - verified = 1; - break; - } - } + struct commit *head = lookup_commit(head_sha1); + struct commit *branch = lookup_commit(branch_sha1); + struct commit_list *merge_bases = get_merge_bases(head, branch, 1); - return verified; + return (merge_bases && !merge_bases->next && merge_bases->item == branch); } static int delete_remote_branch(char *pattern, int force) @@ -2454,7 +2411,7 @@ int main(int argc, char **argv) if (!ref->peer_ref) continue; - if (!memcmp(ref->old_sha1, ref->peer_ref->new_sha1, 20)) { + if (!hashcmp(ref->old_sha1, ref->peer_ref->new_sha1)) { if (push_verbosely || 1) fprintf(stderr, "'%s': up-to-date\n", ref->name); continue; @@ -2483,7 +2440,7 @@ int main(int argc, char **argv) continue; } } - memcpy(ref->new_sha1, ref->peer_ref->new_sha1, 20); + hashcpy(ref->new_sha1, ref->peer_ref->new_sha1); if (is_zero_sha1(ref->new_sha1)) { error("cannot happen anymore"); rc = -3; @@ -2510,7 +2467,7 @@ int main(int argc, char **argv) /* Set up revision info for this refspec */ commit_argc = 3; - new_sha1_hex = strdup(sha1_to_hex(ref->new_sha1)); + new_sha1_hex = xstrdup(sha1_to_hex(ref->new_sha1)); old_sha1_hex = NULL; commit_argv[1] = "--objects"; commit_argv[2] = new_sha1_hex; @@ -23,6 +23,7 @@ char *ssl_capath = NULL; char *ssl_cainfo = NULL; long curl_low_speed_limit = -1; long curl_low_speed_time = -1; +int curl_ftp_no_epsv = 0; struct curl_slist *pragma_header; @@ -155,6 +156,11 @@ static int http_options(const char *var, const char *value) return 0; } + if (!strcmp("http.noepsv", var)) { + curl_ftp_no_epsv = git_config_bool(var, value); + return 0; + } + /* Fall back on the default ones */ return git_default_config(var, value); } @@ -196,6 +202,9 @@ static CURL* get_curl_handle(void) curl_easy_setopt(result, CURLOPT_USERAGENT, GIT_USER_AGENT); + if (curl_ftp_no_epsv) + curl_easy_setopt(result, CURLOPT_FTP_USE_EPSV, 0); + return result; } @@ -251,6 +260,9 @@ void http_init(void) max_requests = DEFAULT_MAX_REQUESTS; #endif + if (getenv("GIT_CURL_FTP_NO_EPSV")) + curl_ftp_no_epsv = 1; + #ifndef NO_CURL_EASY_DUPHANDLE curl_default = get_curl_handle(); #endif @@ -22,6 +22,10 @@ #define NO_CURL_EASY_DUPHANDLE #endif +#if LIBCURL_VERSION_NUM < 0x070a03 +#define CURLE_HTTP_RETURNED_ERROR CURLE_HTTP_NOT_FOUND +#endif + struct slot_results { CURLcode curl_result; diff --git a/imap-send.c b/imap-send.c index 65c71c602d..362e474374 100644 --- a/imap-send.c +++ b/imap-send.c @@ -110,7 +110,6 @@ static char *next_arg( char ** ); static void free_generic_messages( message_t * ); -static int nfvasprintf( char **str, const char *fmt, va_list va ); static int nfsnprintf( char *buf, int blen, const char *fmt, ... ); @@ -372,21 +371,6 @@ free_generic_messages( message_t *msgs ) } static int -git_vasprintf( char **strp, const char *fmt, va_list ap ) -{ - int len; - char tmp[1024]; - - if ((len = vsnprintf( tmp, sizeof(tmp), fmt, ap )) < 0 || !(*strp = xmalloc( len + 1 ))) - return -1; - if (len >= (int)sizeof(tmp)) - vsprintf( *strp, fmt, ap ); - else - memcpy( *strp, tmp, len + 1 ); - return len; -} - -static int nfsnprintf( char *buf, int blen, const char *fmt, ... ) { int ret; @@ -399,15 +383,6 @@ nfsnprintf( char *buf, int blen, const char *fmt, ... ) return ret; } -static int -nfvasprintf( char **str, const char *fmt, va_list va ) -{ - int ret = git_vasprintf( str, fmt, va ); - if (ret < 0) - die( "Fatal: Out of memory\n"); - return ret; -} - static struct { unsigned char i, j, s[256]; } rs; @@ -1032,7 +1007,7 @@ imap_open_store( imap_server_conf_t *srvc ) * getpass() returns a pointer to a static buffer. make a copy * for long term storage. */ - srvc->pass = strdup( arg ); + srvc->pass = xstrdup( arg ); } if (CAP(NOLOGIN)) { fprintf( stderr, "Skipping account %s@%s, server forbids LOGIN\n", srvc->user, srvc->host ); @@ -1288,7 +1263,7 @@ git_imap_config(const char *key, const char *val) key += sizeof imap_key - 1; if (!strcmp( "folder", key )) { - imap_folder = strdup( val ); + imap_folder = xstrdup( val ); } else if (!strcmp( "host", key )) { { if (!strncmp( "imap:", val, 5 )) @@ -1298,16 +1273,16 @@ git_imap_config(const char *key, const char *val) } if (!strncmp( "//", val, 2 )) val += 2; - server.host = strdup( val ); + server.host = xstrdup( val ); } else if (!strcmp( "user", key )) - server.user = strdup( val ); + server.user = xstrdup( val ); else if (!strcmp( "pass", key )) - server.pass = strdup( val ); + server.pass = xstrdup( val ); else if (!strcmp( "port", key )) server.port = git_config_int( key, val ); else if (!strcmp( "tunnel", key )) - server.tunnel = strdup( val ); + server.tunnel = xstrdup( val ); return 0; } diff --git a/index-pack.c b/index-pack.c index b20659c259..80bc6cb45b 100644 --- a/index-pack.c +++ b/index-pack.c @@ -82,7 +82,7 @@ static void parse_pack_header(void) SHA1_Init(&ctx); SHA1_Update(&ctx, pack_base, pack_size - 20); SHA1_Final(sha1, &ctx); - if (memcmp(sha1, pack_base + pack_size - 20, 20)) + if (hashcmp(sha1, pack_base + pack_size - 20)) die("packfile '%s' SHA1 mismatch", pack_name); } @@ -161,7 +161,7 @@ static void *unpack_raw_entry(unsigned long offset, case OBJ_DELTA: if (pos + 20 >= pack_limit) bad_object(offset, "object extends past end of pack"); - memcpy(delta_base, pack_base + pos, 20); + hashcpy(delta_base, pack_base + pos); pos += 20; /* fallthru */ case OBJ_COMMIT: @@ -189,7 +189,7 @@ static int find_delta(const unsigned char *base_sha1) struct delta_entry *delta = &deltas[next]; int cmp; - cmp = memcmp(base_sha1, delta->base_sha1, 20); + cmp = hashcmp(base_sha1, delta->base_sha1); if (!cmp) return next; if (cmp < 0) { @@ -210,9 +210,9 @@ static int find_deltas_based_on_sha1(const unsigned char *base_sha1, if (first < 0) return -1; - while (first > 0 && !memcmp(deltas[first-1].base_sha1, base_sha1, 20)) + while (first > 0 && !hashcmp(deltas[first - 1].base_sha1, base_sha1)) --first; - while (last < end && !memcmp(deltas[last+1].base_sha1, base_sha1, 20)) + while (last < end && !hashcmp(deltas[last + 1].base_sha1, base_sha1)) ++last; *first_index = first; *last_index = last; @@ -278,7 +278,7 @@ static int compare_delta_entry(const void *a, const void *b) { const struct delta_entry *delta_a = a; const struct delta_entry *delta_b = b; - return memcmp(delta_a->base_sha1, delta_b->base_sha1, 20); + return hashcmp(delta_a->base_sha1, delta_b->base_sha1); } static void parse_pack_objects(void) @@ -304,7 +304,7 @@ static void parse_pack_objects(void) if (obj->type == OBJ_DELTA) { struct delta_entry *delta = &deltas[nr_deltas++]; delta->obj = obj; - memcpy(delta->base_sha1, base_sha1, 20); + hashcpy(delta->base_sha1, base_sha1); } else sha1_object(data, data_size, obj->type, obj->sha1); free(data); @@ -350,7 +350,7 @@ static int sha1_compare(const void *_a, const void *_b) { struct object_entry *a = *(struct object_entry **)_a; struct object_entry *b = *(struct object_entry **)_b; - return memcmp(a->sha1, b->sha1, 20); + return hashcmp(a->sha1, b->sha1); } static void write_index_file(const char *index_name, unsigned char *sha1) diff --git a/interpolate.c b/interpolate.c new file mode 100644 index 0000000000..5d9d1889f0 --- /dev/null +++ b/interpolate.c @@ -0,0 +1,108 @@ +/* + * Copyright 2006 Jon Loeliger + */ + +#include <string.h> + +#include "git-compat-util.h" +#include "interpolate.h" + + +void interp_set_entry(struct interp *table, int slot, const char *value) +{ + char *oldval = table[slot].value; + char *newval = NULL; + + if (oldval) + free(oldval); + + if (value) + newval = xstrdup(value); + + table[slot].value = newval; +} + + +void interp_clear_table(struct interp *table, int ninterps) +{ + int i; + + for (i = 0; i < ninterps; i++) { + interp_set_entry(table, i, NULL); + } +} + + +/* + * Convert a NUL-terminated string in buffer orig + * into the supplied buffer, result, whose length is reslen, + * performing substitutions on %-named sub-strings from + * the table, interps, with ninterps entries. + * + * Example interps: + * { + * { "%H", "example.org"}, + * { "%port", "123"}, + * { "%%", "%"}, + * } + * + * Returns 1 on a successful substitution pass that fits in result, + * Returns 0 on a failed or overflowing substitution pass. + */ + +int interpolate(char *result, int reslen, + const char *orig, + const struct interp *interps, int ninterps) +{ + const char *src = orig; + char *dest = result; + int newlen = 0; + char *name, *value; + int namelen, valuelen; + int i; + char c; + + memset(result, 0, reslen); + + while ((c = *src) && newlen < reslen - 1) { + if (c == '%') { + /* Try to match an interpolation string. */ + for (i = 0; i < ninterps; i++) { + name = interps[i].name; + namelen = strlen(name); + if (strncmp(src, name, namelen) == 0) { + break; + } + } + + /* Check for valid interpolation. */ + if (i < ninterps) { + value = interps[i].value; + valuelen = strlen(value); + + if (newlen + valuelen < reslen - 1) { + /* Substitute. */ + strncpy(dest, value, valuelen); + newlen += valuelen; + dest += valuelen; + src += namelen; + } else { + /* Something's not fitting. */ + return 0; + } + + } else { + /* Skip bogus interpolation. */ + *dest++ = *src++; + newlen++; + } + + } else { + /* Straight copy one non-interpolation character. */ + *dest++ = *src++; + newlen++; + } + } + + return newlen < reslen - 1; +} diff --git a/interpolate.h b/interpolate.h new file mode 100644 index 0000000000..190a180b58 --- /dev/null +++ b/interpolate.h @@ -0,0 +1,26 @@ +/* + * Copyright 2006 Jon Loeliger + */ + +#ifndef INTERPOLATE_H +#define INTERPOLATE_H + +/* + * Convert a NUL-terminated string in buffer orig, + * performing substitutions on %-named sub-strings from + * the interpretation table. + */ + +struct interp { + char *name; + char *value; +}; + +extern void interp_set_entry(struct interp *table, int slot, const char *value); +extern void interp_clear_table(struct interp *table, int ninterps); + +extern int interpolate(char *result, int reslen, + const char *orig, + const struct interp *interps, int ninterps); + +#endif /* INTERPOLATE_H */ diff --git a/list-objects.c b/list-objects.c new file mode 100644 index 0000000000..f1fa21c397 --- /dev/null +++ b/list-objects.c @@ -0,0 +1,140 @@ +#include "cache.h" +#include "tag.h" +#include "commit.h" +#include "tree.h" +#include "blob.h" +#include "diff.h" +#include "tree-walk.h" +#include "revision.h" +#include "list-objects.h" + +static void process_blob(struct rev_info *revs, + struct blob *blob, + struct object_array *p, + struct name_path *path, + const char *name) +{ + struct object *obj = &blob->object; + + if (!revs->blob_objects) + return; + if (obj->flags & (UNINTERESTING | SEEN)) + return; + obj->flags |= SEEN; + name = xstrdup(name); + add_object(obj, p, path, name); +} + +static void process_tree(struct rev_info *revs, + struct tree *tree, + struct object_array *p, + struct name_path *path, + const char *name) +{ + struct object *obj = &tree->object; + struct tree_desc desc; + struct name_entry entry; + struct name_path me; + + if (!revs->tree_objects) + return; + if (obj->flags & (UNINTERESTING | SEEN)) + return; + if (parse_tree(tree) < 0) + die("bad tree object %s", sha1_to_hex(obj->sha1)); + obj->flags |= SEEN; + name = xstrdup(name); + add_object(obj, p, path, name); + me.up = path; + me.elem = name; + me.elem_len = strlen(name); + + desc.buf = tree->buffer; + desc.size = tree->size; + + while (tree_entry(&desc, &entry)) { + if (S_ISDIR(entry.mode)) + process_tree(revs, + lookup_tree(entry.sha1), + p, &me, entry.path); + else + process_blob(revs, + lookup_blob(entry.sha1), + p, &me, entry.path); + } + free(tree->buffer); + tree->buffer = NULL; +} + +static void mark_edge_parents_uninteresting(struct commit *commit, + struct rev_info *revs, + show_edge_fn show_edge) +{ + struct commit_list *parents; + + for (parents = commit->parents; parents; parents = parents->next) { + struct commit *parent = parents->item; + if (!(parent->object.flags & UNINTERESTING)) + continue; + mark_tree_uninteresting(parent->tree); + if (revs->edge_hint && !(parent->object.flags & SHOWN)) { + parent->object.flags |= SHOWN; + show_edge(parent); + } + } +} + +void mark_edges_uninteresting(struct commit_list *list, + struct rev_info *revs, + show_edge_fn show_edge) +{ + for ( ; list; list = list->next) { + struct commit *commit = list->item; + + if (commit->object.flags & UNINTERESTING) { + mark_tree_uninteresting(commit->tree); + continue; + } + mark_edge_parents_uninteresting(commit, revs, show_edge); + } +} + +void traverse_commit_list(struct rev_info *revs, + void (*show_commit)(struct commit *), + void (*show_object)(struct object_array_entry *)) +{ + int i; + struct commit *commit; + struct object_array objects = { 0, 0, NULL }; + + while ((commit = get_revision(revs)) != NULL) { + process_tree(revs, commit->tree, &objects, NULL, ""); + show_commit(commit); + } + for (i = 0; i < revs->pending.nr; i++) { + struct object_array_entry *pending = revs->pending.objects + i; + struct object *obj = pending->item; + const char *name = pending->name; + if (obj->flags & (UNINTERESTING | SEEN)) + continue; + if (obj->type == OBJ_TAG) { + obj->flags |= SEEN; + add_object_array(obj, name, &objects); + continue; + } + if (obj->type == OBJ_TREE) { + process_tree(revs, (struct tree *)obj, &objects, + NULL, name); + continue; + } + if (obj->type == OBJ_BLOB) { + process_blob(revs, (struct blob *)obj, &objects, + NULL, name); + continue; + } + die("unknown pending object %s (%s)", + sha1_to_hex(obj->sha1), name); + } + for (i = 0; i < objects.nr; i++) + show_object(&objects.objects[i]); +} diff --git a/list-objects.h b/list-objects.h new file mode 100644 index 0000000000..0f41391ecc --- /dev/null +++ b/list-objects.h @@ -0,0 +1,12 @@ +#ifndef LIST_OBJECTS_H +#define LIST_OBJECTS_H + +typedef void (*show_commit_fn)(struct commit *); +typedef void (*show_object_fn)(struct object_array_entry *); +typedef void (*show_edge_fn)(struct commit *); + +void traverse_commit_list(struct rev_info *revs, show_commit_fn, show_object_fn); + +void mark_edges_uninteresting(struct commit_list *, struct rev_info *, show_edge_fn); + +#endif diff --git a/local-fetch.c b/local-fetch.c index 7d01845d39..7b6875cce6 100644 --- a/local-fetch.c +++ b/local-fetch.c @@ -5,10 +5,10 @@ #include "commit.h" #include "fetch.h" -static int use_link = 0; -static int use_symlink = 0; +static int use_link; +static int use_symlink; static int use_filecopy = 1; -static int commits_on_stdin = 0; +static int commits_on_stdin; static const char *path; /* "Remote" git repository */ @@ -16,7 +16,7 @@ void prefetch(unsigned char *sha1) { } -static struct packed_git *packs = NULL; +static struct packed_git *packs; static void setup_index(unsigned char *sha1) { diff --git a/log-tree.c b/log-tree.c index 05ede0c175..fbe139920a 100644 --- a/log-tree.c +++ b/log-tree.c @@ -12,28 +12,86 @@ static void show_parents(struct commit *commit, int abbrev) } } +/* + * Search for "^[-A-Za-z]+: [^@]+@" pattern. It usually matches + * Signed-off-by: and Acked-by: lines. + */ +static int detect_any_signoff(char *letter, int size) +{ + char ch, *cp; + int seen_colon = 0; + int seen_at = 0; + int seen_name = 0; + int seen_head = 0; + + cp = letter + size; + while (letter <= --cp && (ch = *cp) == '\n') + continue; + + while (letter <= cp) { + ch = *cp--; + if (ch == '\n') + break; + + if (!seen_at) { + if (ch == '@') + seen_at = 1; + continue; + } + if (!seen_colon) { + if (ch == '@') + return 0; + else if (ch == ':') + seen_colon = 1; + else + seen_name = 1; + continue; + } + if (('A' <= ch && ch <= 'Z') || + ('a' <= ch && ch <= 'z') || + ch == '-') { + seen_head = 1; + continue; + } + /* no empty last line doesn't match */ + return 0; + } + return seen_head && seen_name; +} + static int append_signoff(char *buf, int buf_sz, int at, const char *signoff) { - int signoff_len = strlen(signoff); static const char signed_off_by[] = "Signed-off-by: "; + int signoff_len = strlen(signoff); + int has_signoff = 0; char *cp = buf; /* Do we have enough space to add it? */ - if (buf_sz - at <= strlen(signed_off_by) + signoff_len + 2) + if (buf_sz - at <= strlen(signed_off_by) + signoff_len + 3) return at; /* First see if we already have the sign-off by the signer */ - while (1) { - cp = strstr(cp, signed_off_by); - if (!cp) - break; + while ((cp = strstr(cp, signed_off_by))) { + + has_signoff = 1; + cp += strlen(signed_off_by); - if ((cp + signoff_len < buf + at) && - !strncmp(cp, signoff, signoff_len) && - isspace(cp[signoff_len])) - return at; /* we already have him */ + if (cp + signoff_len >= buf + at) + break; + if (strncmp(cp, signoff, signoff_len)) + continue; + if (!isspace(cp[signoff_len])) + continue; + /* we already have him */ + return at; } + if (!has_signoff) + has_signoff = detect_any_signoff(buf, at); + + if (!has_signoff) + buf[at++] = '\n'; + strcpy(buf + at, signed_off_by); at += strlen(signed_off_by); strcpy(buf + at, signoff); @@ -152,7 +210,9 @@ void show_log(struct rev_info *opt, const char *sep) /* * And then the pretty-printed message itself */ - len = pretty_print_commit(opt->commit_format, commit, ~0u, this_header, sizeof(this_header), abbrev, subject, extra_headers); + len = pretty_print_commit(opt->commit_format, commit, ~0u, this_header, + sizeof(this_header), abbrev, subject, + extra_headers, opt->relative_date); if (opt->add_signoff) len = append_signoff(this_header, sizeof(this_header), len, diff --git a/merge-base.c b/merge-base.c index 59f723f404..009caf804b 100644 --- a/merge-base.c +++ b/merge-base.c @@ -2,7 +2,7 @@ #include "cache.h" #include "commit.h" -static int show_all = 0; +static int show_all; static int merge_base(struct commit *rev1, struct commit *rev2) { diff --git a/merge-file.c b/merge-file.c index f32c653825..fc9b148993 100644 --- a/merge-file.c +++ b/merge-file.c @@ -21,7 +21,7 @@ static const char *write_temp_file(mmfile_t *f) fd = mkstemp(filename); if (fd < 0) return NULL; - filename = strdup(filename); + filename = xstrdup(filename); if (f->size != xwrite(fd, f->ptr, f->size)) { rm_temp_file(filename); return NULL; diff --git a/merge-index.c b/merge-index.c index 0498a6f45e..646d090c58 100644 --- a/merge-index.c +++ b/merge-index.c @@ -4,14 +4,15 @@ #include "cache.h" -static const char *pgm = NULL; +static const char *pgm; static const char *arguments[8]; static int one_shot, quiet; static int err; static void run_program(void) { - int pid = fork(), status; + pid_t pid = fork(); + int status; if (pid < 0) die("unable to fork"); diff --git a/merge-recursive.c b/merge-recursive.c new file mode 100644 index 0000000000..2ba43ae84b --- /dev/null +++ b/merge-recursive.c @@ -0,0 +1,1351 @@ +/* + * Recursive Merge algorithm stolen from git-merge-recursive.py by + * Fredrik Kuivinen. + * The thieves were Alex Riesen and Johannes Schindelin, in June/July 2006 + */ +#include <stdarg.h> +#include <string.h> +#include <assert.h> +#include <sys/wait.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <time.h> +#include "cache.h" +#include "cache-tree.h" +#include "commit.h" +#include "blob.h" +#include "tree-walk.h" +#include "diff.h" +#include "diffcore.h" +#include "run-command.h" +#include "tag.h" +#include "unpack-trees.h" +#include "path-list.h" + +/* + * A virtual commit has + * - (const char *)commit->util set to the name, and + * - *(int *)commit->object.sha1 set to the virtual id. + */ + +static unsigned commit_list_count(const struct commit_list *l) +{ + unsigned c = 0; + for (; l; l = l->next ) + c++; + return c; +} + +static struct commit *make_virtual_commit(struct tree *tree, const char *comment) +{ + struct commit *commit = xcalloc(1, sizeof(struct commit)); + static unsigned virtual_id = 1; + commit->tree = tree; + commit->util = (void*)comment; + *(int*)commit->object.sha1 = virtual_id++; + /* avoid warnings */ + commit->object.parsed = 1; + return commit; +} + +/* + * Since we use get_tree_entry(), which does not put the read object into + * the object pool, we cannot rely on a == b. + */ +static int sha_eq(const unsigned char *a, const unsigned char *b) +{ + if (!a && !b) + return 2; + return a && b && hashcmp(a, b) == 0; +} + +/* + * Since we want to write the index eventually, we cannot reuse the index + * for these (temporary) data. + */ +struct stage_data +{ + struct + { + unsigned mode; + unsigned char sha[20]; + } stages[4]; + unsigned processed:1; +}; + +static struct path_list current_file_set = {NULL, 0, 0, 1}; +static struct path_list current_directory_set = {NULL, 0, 0, 1}; + +static int output_indent = 0; + +static void output(const char *fmt, ...) +{ + va_list args; + int i; + for (i = output_indent; i--;) + fputs(" ", stdout); + va_start(args, fmt); + vfprintf(stdout, fmt, args); + va_end(args); + fputc('\n', stdout); +} + +static void output_commit_title(struct commit *commit) +{ + int i; + for (i = output_indent; i--;) + fputs(" ", stdout); + if (commit->util) + printf("virtual %s\n", (char *)commit->util); + else { + printf("%s ", sha1_to_hex(commit->object.sha1)); + if (parse_commit(commit) != 0) + printf("(bad commit)\n"); + else { + const char *s; + int len; + for (s = commit->buffer; *s; s++) + if (*s == '\n' && s[1] == '\n') { + s += 2; + break; + } + for (len = 0; s[len] && '\n' != s[len]; len++) + ; /* do nothing */ + printf("%.*s\n", len, s); + } + } +} + +static const char *current_index_file = NULL; +static const char *original_index_file; +static const char *temporary_index_file; +static int cache_dirty = 0; + +static int flush_cache(void) +{ + /* flush temporary index */ + struct lock_file *lock = xcalloc(1, sizeof(struct lock_file)); + int fd = hold_lock_file_for_update(lock, current_index_file, 1); + if (write_cache(fd, active_cache, active_nr) || + close(fd) || commit_lock_file(lock)) + die ("unable to write %s", current_index_file); + discard_cache(); + cache_dirty = 0; + return 0; +} + +static void setup_index(int temp) +{ + current_index_file = temp ? temporary_index_file: original_index_file; + if (cache_dirty) { + discard_cache(); + cache_dirty = 0; + } + unlink(temporary_index_file); + discard_cache(); +} + +static struct cache_entry *make_cache_entry(unsigned int mode, + const unsigned char *sha1, const char *path, int stage, int refresh) +{ + int size, len; + struct cache_entry *ce; + + if (!verify_path(path)) + return NULL; + + len = strlen(path); + size = cache_entry_size(len); + ce = xcalloc(1, size); + + hashcpy(ce->sha1, sha1); + memcpy(ce->name, path, len); + ce->ce_flags = create_ce_flags(len, stage); + ce->ce_mode = create_ce_mode(mode); + + if (refresh) + return refresh_cache_entry(ce, 0); + + return ce; +} + +static int add_cacheinfo(unsigned int mode, const unsigned char *sha1, + const char *path, int stage, int refresh, int options) +{ + struct cache_entry *ce; + if (!cache_dirty) + read_cache_from(current_index_file); + cache_dirty++; + ce = make_cache_entry(mode, sha1 ? sha1 : null_sha1, path, stage, refresh); + if (!ce) + return error("cache_addinfo failed: %s", strerror(cache_errno)); + return add_cache_entry(ce, options); +} + +/* + * This is a global variable which is used in a number of places but + * only written to in the 'merge' function. + * + * index_only == 1 => Don't leave any non-stage 0 entries in the cache and + * don't update the working directory. + * 0 => Leave unmerged entries in the cache and update + * the working directory. + */ +static int index_only = 0; + +static int git_read_tree(struct tree *tree) +{ + int rc; + struct object_list *trees = NULL; + struct unpack_trees_options opts; + + if (cache_dirty) + die("read-tree with dirty cache"); + + memset(&opts, 0, sizeof(opts)); + object_list_append(&tree->object, &trees); + rc = unpack_trees(trees, &opts); + cache_tree_free(&active_cache_tree); + + if (rc == 0) + cache_dirty = 1; + + return rc; +} + +static int git_merge_trees(int index_only, + struct tree *common, + struct tree *head, + struct tree *merge) +{ + int rc; + struct object_list *trees = NULL; + struct unpack_trees_options opts; + + if (!cache_dirty) { + read_cache_from(current_index_file); + cache_dirty = 1; + } + + memset(&opts, 0, sizeof(opts)); + if (index_only) + opts.index_only = 1; + else + opts.update = 1; + opts.merge = 1; + opts.head_idx = 2; + opts.fn = threeway_merge; + + object_list_append(&common->object, &trees); + object_list_append(&head->object, &trees); + object_list_append(&merge->object, &trees); + + rc = unpack_trees(trees, &opts); + cache_tree_free(&active_cache_tree); + + cache_dirty = 1; + + return rc; +} + +static struct tree *git_write_tree(void) +{ + struct tree *result = NULL; + + if (cache_dirty) { + unsigned i; + for (i = 0; i < active_nr; i++) { + struct cache_entry *ce = active_cache[i]; + if (ce_stage(ce)) + return NULL; + } + } else + read_cache_from(current_index_file); + + if (!active_cache_tree) + active_cache_tree = cache_tree(); + + if (!cache_tree_fully_valid(active_cache_tree) && + cache_tree_update(active_cache_tree, + active_cache, active_nr, 0, 0) < 0) + die("error building trees"); + + result = lookup_tree(active_cache_tree->sha1); + + flush_cache(); + cache_dirty = 0; + + return result; +} + +static int save_files_dirs(const unsigned char *sha1, + const char *base, int baselen, const char *path, + unsigned int mode, int stage) +{ + int len = strlen(path); + char *newpath = xmalloc(baselen + len + 1); + memcpy(newpath, base, baselen); + memcpy(newpath + baselen, path, len); + newpath[baselen + len] = '\0'; + + if (S_ISDIR(mode)) + path_list_insert(newpath, ¤t_directory_set); + else + path_list_insert(newpath, ¤t_file_set); + free(newpath); + + return READ_TREE_RECURSIVE; +} + +static int get_files_dirs(struct tree *tree) +{ + int n; + if (read_tree_recursive(tree, "", 0, 0, NULL, save_files_dirs) != 0) + return 0; + n = current_file_set.nr + current_directory_set.nr; + return n; +} + +/* + * Returns a index_entry instance which doesn't have to correspond to + * a real cache entry in Git's index. + */ +static struct stage_data *insert_stage_data(const char *path, + struct tree *o, struct tree *a, struct tree *b, + struct path_list *entries) +{ + struct path_list_item *item; + struct stage_data *e = xcalloc(1, sizeof(struct stage_data)); + get_tree_entry(o->object.sha1, path, + e->stages[1].sha, &e->stages[1].mode); + get_tree_entry(a->object.sha1, path, + e->stages[2].sha, &e->stages[2].mode); + get_tree_entry(b->object.sha1, path, + e->stages[3].sha, &e->stages[3].mode); + item = path_list_insert(path, entries); + item->util = e; + return e; +} + +/* + * Create a dictionary mapping file names to stage_data objects. The + * dictionary contains one entry for every path with a non-zero stage entry. + */ +static struct path_list *get_unmerged(void) +{ + struct path_list *unmerged = xcalloc(1, sizeof(struct path_list)); + int i; + + unmerged->strdup_paths = 1; + if (!cache_dirty) { + read_cache_from(current_index_file); + cache_dirty++; + } + for (i = 0; i < active_nr; i++) { + struct path_list_item *item; + struct stage_data *e; + struct cache_entry *ce = active_cache[i]; + if (!ce_stage(ce)) + continue; + + item = path_list_lookup(ce->name, unmerged); + if (!item) { + item = path_list_insert(ce->name, unmerged); + item->util = xcalloc(1, sizeof(struct stage_data)); + } + e = item->util; + e->stages[ce_stage(ce)].mode = ntohl(ce->ce_mode); + hashcpy(e->stages[ce_stage(ce)].sha, ce->sha1); + } + + return unmerged; +} + +struct rename +{ + struct diff_filepair *pair; + struct stage_data *src_entry; + struct stage_data *dst_entry; + unsigned processed:1; +}; + +/* + * Get information of all renames which occured between 'o_tree' and + * 'tree'. We need the three trees in the merge ('o_tree', 'a_tree' and + * 'b_tree') to be able to associate the correct cache entries with + * the rename information. 'tree' is always equal to either a_tree or b_tree. + */ +static struct path_list *get_renames(struct tree *tree, + struct tree *o_tree, + struct tree *a_tree, + struct tree *b_tree, + struct path_list *entries) +{ + int i; + struct path_list *renames; + struct diff_options opts; + + renames = xcalloc(1, sizeof(struct path_list)); + diff_setup(&opts); + opts.recursive = 1; + opts.detect_rename = DIFF_DETECT_RENAME; + opts.output_format = DIFF_FORMAT_NO_OUTPUT; + if (diff_setup_done(&opts) < 0) + die("diff setup failed"); + diff_tree_sha1(o_tree->object.sha1, tree->object.sha1, "", &opts); + diffcore_std(&opts); + for (i = 0; i < diff_queued_diff.nr; ++i) { + struct path_list_item *item; + struct rename *re; + struct diff_filepair *pair = diff_queued_diff.queue[i]; + if (pair->status != 'R') { + diff_free_filepair(pair); + continue; + } + re = xmalloc(sizeof(*re)); + re->processed = 0; + re->pair = pair; + item = path_list_lookup(re->pair->one->path, entries); + if (!item) + re->src_entry = insert_stage_data(re->pair->one->path, + o_tree, a_tree, b_tree, entries); + else + re->src_entry = item->util; + + item = path_list_lookup(re->pair->two->path, entries); + if (!item) + re->dst_entry = insert_stage_data(re->pair->two->path, + o_tree, a_tree, b_tree, entries); + else + re->dst_entry = item->util; + item = path_list_insert(pair->one->path, renames); + item->util = re; + } + opts.output_format = DIFF_FORMAT_NO_OUTPUT; + diff_queued_diff.nr = 0; + diff_flush(&opts); + return renames; +} + +int update_stages(const char *path, struct diff_filespec *o, + struct diff_filespec *a, struct diff_filespec *b, int clear) +{ + int options = ADD_CACHE_OK_TO_ADD | ADD_CACHE_OK_TO_REPLACE; + if (clear) + if (remove_file_from_cache(path)) + return -1; + if (o) + if (add_cacheinfo(o->mode, o->sha1, path, 1, 0, options)) + return -1; + if (a) + if (add_cacheinfo(a->mode, a->sha1, path, 2, 0, options)) + return -1; + if (b) + if (add_cacheinfo(b->mode, b->sha1, path, 3, 0, options)) + return -1; + return 0; +} + +static int remove_path(const char *name) +{ + int ret, len; + char *slash, *dirs; + + ret = unlink(name); + if (ret) + return ret; + len = strlen(name); + dirs = xmalloc(len+1); + memcpy(dirs, name, len); + dirs[len] = '\0'; + while ((slash = strrchr(name, '/'))) { + *slash = '\0'; + len = slash - name; + if (rmdir(name) != 0) + break; + } + free(dirs); + return ret; +} + +int remove_file(int clean, const char *path) +{ + int update_cache = index_only || clean; + int update_working_directory = !index_only; + + if (update_cache) { + if (!cache_dirty) + read_cache_from(current_index_file); + cache_dirty++; + if (remove_file_from_cache(path)) + return -1; + } + if (update_working_directory) + { + unlink(path); + if (errno != ENOENT || errno != EISDIR) + return -1; + remove_path(path); + } + return 0; +} + +static char *unique_path(const char *path, const char *branch) +{ + char *newpath = xmalloc(strlen(path) + 1 + strlen(branch) + 8 + 1); + int suffix = 0; + struct stat st; + char *p = newpath + strlen(path); + strcpy(newpath, path); + *(p++) = '~'; + strcpy(p, branch); + for (; *p; ++p) + if ('/' == *p) + *p = '_'; + while (path_list_has_path(¤t_file_set, newpath) || + path_list_has_path(¤t_directory_set, newpath) || + lstat(newpath, &st) == 0) + sprintf(p, "_%d", suffix++); + + path_list_insert(newpath, ¤t_file_set); + return newpath; +} + +static int mkdir_p(const char *path, unsigned long mode) +{ + /* path points to cache entries, so xstrdup before messing with it */ + char *buf = xstrdup(path); + int result = safe_create_leading_directories(buf); + free(buf); + return result; +} + +static void flush_buffer(int fd, const char *buf, unsigned long size) +{ + while (size > 0) { + long ret = xwrite(fd, buf, size); + if (ret < 0) { + /* Ignore epipe */ + if (errno == EPIPE) + break; + die("merge-recursive: %s", strerror(errno)); + } else if (!ret) { + die("merge-recursive: disk full?"); + } + size -= ret; + buf += ret; + } +} + +void update_file_flags(const unsigned char *sha, + unsigned mode, + const char *path, + int update_cache, + int update_wd) +{ + if (index_only) + update_wd = 0; + + if (update_wd) { + char type[20]; + void *buf; + unsigned long size; + + buf = read_sha1_file(sha, type, &size); + if (!buf) + die("cannot read object %s '%s'", sha1_to_hex(sha), path); + if (strcmp(type, blob_type) != 0) + die("blob expected for %s '%s'", sha1_to_hex(sha), path); + + if (S_ISREG(mode)) { + int fd; + if (mkdir_p(path, 0777)) + die("failed to create path %s: %s", path, strerror(errno)); + unlink(path); + if (mode & 0100) + mode = 0777; + else + mode = 0666; + fd = open(path, O_WRONLY | O_TRUNC | O_CREAT, mode); + if (fd < 0) + die("failed to open %s: %s", path, strerror(errno)); + flush_buffer(fd, buf, size); + close(fd); + } else if (S_ISLNK(mode)) { + char *lnk = xmalloc(size + 1); + memcpy(lnk, buf, size); + lnk[size] = '\0'; + mkdir_p(path, 0777); + unlink(lnk); + symlink(lnk, path); + } else + die("do not know what to do with %06o %s '%s'", + mode, sha1_to_hex(sha), path); + } + if (update_cache) + add_cacheinfo(mode, sha, path, 0, update_wd, ADD_CACHE_OK_TO_ADD); +} + +void update_file(int clean, + const unsigned char *sha, + unsigned mode, + const char *path) +{ + update_file_flags(sha, mode, path, index_only || clean, !index_only); +} + +/* Low level file merging, update and removal */ + +struct merge_file_info +{ + unsigned char sha[20]; + unsigned mode; + unsigned clean:1, + merge:1; +}; + +static char *git_unpack_file(const unsigned char *sha1, char *path) +{ + void *buf; + char type[20]; + unsigned long size; + int fd; + + buf = read_sha1_file(sha1, type, &size); + if (!buf || strcmp(type, blob_type)) + die("unable to read blob object %s", sha1_to_hex(sha1)); + + strcpy(path, ".merge_file_XXXXXX"); + fd = mkstemp(path); + if (fd < 0) + die("unable to create temp-file"); + flush_buffer(fd, buf, size); + close(fd); + return path; +} + +static struct merge_file_info merge_file(struct diff_filespec *o, + struct diff_filespec *a, struct diff_filespec *b, + const char *branch1, const char *branch2) +{ + struct merge_file_info result; + result.merge = 0; + result.clean = 1; + + if ((S_IFMT & a->mode) != (S_IFMT & b->mode)) { + result.clean = 0; + if (S_ISREG(a->mode)) { + result.mode = a->mode; + hashcpy(result.sha, a->sha1); + } else { + result.mode = b->mode; + hashcpy(result.sha, b->sha1); + } + } else { + if (!sha_eq(a->sha1, o->sha1) && !sha_eq(b->sha1, o->sha1)) + result.merge = 1; + + result.mode = a->mode == o->mode ? b->mode: a->mode; + + if (sha_eq(a->sha1, o->sha1)) + hashcpy(result.sha, b->sha1); + else if (sha_eq(b->sha1, o->sha1)) + hashcpy(result.sha, a->sha1); + else if (S_ISREG(a->mode)) { + int code = 1, fd; + struct stat st; + char orig[PATH_MAX]; + char src1[PATH_MAX]; + char src2[PATH_MAX]; + const char *argv[] = { + "merge", "-L", NULL, "-L", NULL, "-L", NULL, + NULL, NULL, NULL, + NULL + }; + char *la, *lb, *lo; + + git_unpack_file(o->sha1, orig); + git_unpack_file(a->sha1, src1); + git_unpack_file(b->sha1, src2); + + argv[2] = la = xstrdup(mkpath("%s/%s", branch1, a->path)); + argv[6] = lb = xstrdup(mkpath("%s/%s", branch2, b->path)); + argv[4] = lo = xstrdup(mkpath("orig/%s", o->path)); + argv[7] = src1; + argv[8] = orig; + argv[9] = src2, + + code = run_command_v(10, argv); + + free(la); + free(lb); + free(lo); + if (code && code < -256) { + die("Failed to execute 'merge'. merge(1) is used as the " + "file-level merge tool. Is 'merge' in your path?"); + } + fd = open(src1, O_RDONLY); + if (fd < 0 || fstat(fd, &st) < 0 || + index_fd(result.sha, fd, &st, 1, + "blob")) + die("Unable to add %s to database", src1); + + unlink(orig); + unlink(src1); + unlink(src2); + + result.clean = WEXITSTATUS(code) == 0; + } else { + if (!(S_ISLNK(a->mode) || S_ISLNK(b->mode))) + die("cannot merge modes?"); + + hashcpy(result.sha, a->sha1); + + if (!sha_eq(a->sha1, b->sha1)) + result.clean = 0; + } + } + + return result; +} + +static void conflict_rename_rename(struct rename *ren1, + const char *branch1, + struct rename *ren2, + const char *branch2) +{ + char *del[2]; + int delp = 0; + const char *ren1_dst = ren1->pair->two->path; + const char *ren2_dst = ren2->pair->two->path; + const char *dst_name1 = ren1_dst; + const char *dst_name2 = ren2_dst; + if (path_list_has_path(¤t_directory_set, ren1_dst)) { + dst_name1 = del[delp++] = unique_path(ren1_dst, branch1); + output("%s is a directory in %s adding as %s instead", + ren1_dst, branch2, dst_name1); + remove_file(0, ren1_dst); + } + if (path_list_has_path(¤t_directory_set, ren2_dst)) { + dst_name2 = del[delp++] = unique_path(ren2_dst, branch2); + output("%s is a directory in %s adding as %s instead", + ren2_dst, branch1, dst_name2); + remove_file(0, ren2_dst); + } + update_stages(dst_name1, NULL, ren1->pair->two, NULL, 1); + update_stages(dst_name2, NULL, NULL, ren2->pair->two, 1); + while (delp--) + free(del[delp]); +} + +static void conflict_rename_dir(struct rename *ren1, + const char *branch1) +{ + char *new_path = unique_path(ren1->pair->two->path, branch1); + output("Renaming %s to %s instead", ren1->pair->one->path, new_path); + remove_file(0, ren1->pair->two->path); + update_file(0, ren1->pair->two->sha1, ren1->pair->two->mode, new_path); + free(new_path); +} + +static void conflict_rename_rename_2(struct rename *ren1, + const char *branch1, + struct rename *ren2, + const char *branch2) +{ + char *new_path1 = unique_path(ren1->pair->two->path, branch1); + char *new_path2 = unique_path(ren2->pair->two->path, branch2); + output("Renaming %s to %s and %s to %s instead", + ren1->pair->one->path, new_path1, + ren2->pair->one->path, new_path2); + remove_file(0, ren1->pair->two->path); + update_file(0, ren1->pair->two->sha1, ren1->pair->two->mode, new_path1); + update_file(0, ren2->pair->two->sha1, ren2->pair->two->mode, new_path2); + free(new_path2); + free(new_path1); +} + +static int process_renames(struct path_list *a_renames, + struct path_list *b_renames, + const char *a_branch, + const char *b_branch) +{ + int clean_merge = 1, i, j; + struct path_list a_by_dst = {NULL, 0, 0, 0}, b_by_dst = {NULL, 0, 0, 0}; + const struct rename *sre; + + for (i = 0; i < a_renames->nr; i++) { + sre = a_renames->items[i].util; + path_list_insert(sre->pair->two->path, &a_by_dst)->util + = sre->dst_entry; + } + for (i = 0; i < b_renames->nr; i++) { + sre = b_renames->items[i].util; + path_list_insert(sre->pair->two->path, &b_by_dst)->util + = sre->dst_entry; + } + + for (i = 0, j = 0; i < a_renames->nr || j < b_renames->nr;) { + int compare; + char *src; + struct path_list *renames1, *renames2, *renames2Dst; + struct rename *ren1 = NULL, *ren2 = NULL; + const char *branch1, *branch2; + const char *ren1_src, *ren1_dst; + + if (i >= a_renames->nr) { + compare = 1; + ren2 = b_renames->items[j++].util; + } else if (j >= b_renames->nr) { + compare = -1; + ren1 = a_renames->items[i++].util; + } else { + compare = strcmp(a_renames->items[i].path, + b_renames->items[j].path); + if (compare <= 0) + ren1 = a_renames->items[i++].util; + if (compare >= 0) + ren2 = b_renames->items[j++].util; + } + + /* TODO: refactor, so that 1/2 are not needed */ + if (ren1) { + renames1 = a_renames; + renames2 = b_renames; + renames2Dst = &b_by_dst; + branch1 = a_branch; + branch2 = b_branch; + } else { + struct rename *tmp; + renames1 = b_renames; + renames2 = a_renames; + renames2Dst = &a_by_dst; + branch1 = b_branch; + branch2 = a_branch; + tmp = ren2; + ren2 = ren1; + ren1 = tmp; + } + src = ren1->pair->one->path; + + ren1->dst_entry->processed = 1; + ren1->src_entry->processed = 1; + + if (ren1->processed) + continue; + ren1->processed = 1; + + ren1_src = ren1->pair->one->path; + ren1_dst = ren1->pair->two->path; + + if (ren2) { + const char *ren2_src = ren2->pair->one->path; + const char *ren2_dst = ren2->pair->two->path; + /* Renamed in 1 and renamed in 2 */ + if (strcmp(ren1_src, ren2_src) != 0) + die("ren1.src != ren2.src"); + ren2->dst_entry->processed = 1; + ren2->processed = 1; + if (strcmp(ren1_dst, ren2_dst) != 0) { + clean_merge = 0; + output("CONFLICT (rename/rename): " + "Rename %s->%s in branch %s " + "rename %s->%s in %s", + src, ren1_dst, branch1, + src, ren2_dst, branch2); + conflict_rename_rename(ren1, branch1, ren2, branch2); + } else { + struct merge_file_info mfi; + remove_file(1, ren1_src); + mfi = merge_file(ren1->pair->one, + ren1->pair->two, + ren2->pair->two, + branch1, + branch2); + if (mfi.merge || !mfi.clean) + output("Renaming %s->%s", src, ren1_dst); + + if (mfi.merge) + output("Auto-merging %s", ren1_dst); + + if (!mfi.clean) { + output("CONFLICT (content): merge conflict in %s", + ren1_dst); + clean_merge = 0; + + if (!index_only) + update_stages(ren1_dst, + ren1->pair->one, + ren1->pair->two, + ren2->pair->two, + 1 /* clear */); + } + update_file(mfi.clean, mfi.sha, mfi.mode, ren1_dst); + } + } else { + /* Renamed in 1, maybe changed in 2 */ + struct path_list_item *item; + /* we only use sha1 and mode of these */ + struct diff_filespec src_other, dst_other; + int try_merge, stage = a_renames == renames1 ? 3: 2; + + remove_file(1, ren1_src); + + hashcpy(src_other.sha1, ren1->src_entry->stages[stage].sha); + src_other.mode = ren1->src_entry->stages[stage].mode; + hashcpy(dst_other.sha1, ren1->dst_entry->stages[stage].sha); + dst_other.mode = ren1->dst_entry->stages[stage].mode; + + try_merge = 0; + + if (path_list_has_path(¤t_directory_set, ren1_dst)) { + clean_merge = 0; + output("CONFLICT (rename/directory): Rename %s->%s in %s " + " directory %s added in %s", + ren1_src, ren1_dst, branch1, + ren1_dst, branch2); + conflict_rename_dir(ren1, branch1); + } else if (sha_eq(src_other.sha1, null_sha1)) { + clean_merge = 0; + output("CONFLICT (rename/delete): Rename %s->%s in %s " + "and deleted in %s", + ren1_src, ren1_dst, branch1, + branch2); + update_file(0, ren1->pair->two->sha1, ren1->pair->two->mode, ren1_dst); + } else if (!sha_eq(dst_other.sha1, null_sha1)) { + const char *new_path; + clean_merge = 0; + try_merge = 1; + output("CONFLICT (rename/add): Rename %s->%s in %s. " + "%s added in %s", + ren1_src, ren1_dst, branch1, + ren1_dst, branch2); + new_path = unique_path(ren1_dst, branch2); + output("Adding as %s instead", new_path); + update_file(0, dst_other.sha1, dst_other.mode, new_path); + } else if ((item = path_list_lookup(ren1_dst, renames2Dst))) { + ren2 = item->util; + clean_merge = 0; + ren2->processed = 1; + output("CONFLICT (rename/rename): Rename %s->%s in %s. " + "Rename %s->%s in %s", + ren1_src, ren1_dst, branch1, + ren2->pair->one->path, ren2->pair->two->path, branch2); + conflict_rename_rename_2(ren1, branch1, ren2, branch2); + } else + try_merge = 1; + + if (try_merge) { + struct diff_filespec *o, *a, *b; + struct merge_file_info mfi; + src_other.path = (char *)ren1_src; + + o = ren1->pair->one; + if (a_renames == renames1) { + a = ren1->pair->two; + b = &src_other; + } else { + b = ren1->pair->two; + a = &src_other; + } + mfi = merge_file(o, a, b, + a_branch, b_branch); + + if (mfi.merge || !mfi.clean) + output("Renaming %s => %s", ren1_src, ren1_dst); + if (mfi.merge) + output("Auto-merging %s", ren1_dst); + if (!mfi.clean) { + output("CONFLICT (rename/modify): Merge conflict in %s", + ren1_dst); + clean_merge = 0; + + if (!index_only) + update_stages(ren1_dst, + o, a, b, 1); + } + update_file(mfi.clean, mfi.sha, mfi.mode, ren1_dst); + } + } + } + path_list_clear(&a_by_dst, 0); + path_list_clear(&b_by_dst, 0); + + if (cache_dirty) + flush_cache(); + return clean_merge; +} + +static unsigned char *has_sha(const unsigned char *sha) +{ + return is_null_sha1(sha) ? NULL: (unsigned char *)sha; +} + +/* Per entry merge function */ +static int process_entry(const char *path, struct stage_data *entry, + const char *branch1, + const char *branch2) +{ + /* + printf("processing entry, clean cache: %s\n", index_only ? "yes": "no"); + print_index_entry("\tpath: ", entry); + */ + int clean_merge = 1; + unsigned char *o_sha = has_sha(entry->stages[1].sha); + unsigned char *a_sha = has_sha(entry->stages[2].sha); + unsigned char *b_sha = has_sha(entry->stages[3].sha); + unsigned o_mode = entry->stages[1].mode; + unsigned a_mode = entry->stages[2].mode; + unsigned b_mode = entry->stages[3].mode; + + if (o_sha && (!a_sha || !b_sha)) { + /* Case A: Deleted in one */ + if ((!a_sha && !b_sha) || + (sha_eq(a_sha, o_sha) && !b_sha) || + (!a_sha && sha_eq(b_sha, o_sha))) { + /* Deleted in both or deleted in one and + * unchanged in the other */ + if (a_sha) + output("Removing %s", path); + remove_file(1, path); + } else { + /* Deleted in one and changed in the other */ + clean_merge = 0; + if (!a_sha) { + output("CONFLICT (delete/modify): %s deleted in %s " + "and modified in %s. Version %s of %s left in tree.", + path, branch1, + branch2, branch2, path); + update_file(0, b_sha, b_mode, path); + } else { + output("CONFLICT (delete/modify): %s deleted in %s " + "and modified in %s. Version %s of %s left in tree.", + path, branch2, + branch1, branch1, path); + update_file(0, a_sha, a_mode, path); + } + } + + } else if ((!o_sha && a_sha && !b_sha) || + (!o_sha && !a_sha && b_sha)) { + /* Case B: Added in one. */ + const char *add_branch; + const char *other_branch; + unsigned mode; + const unsigned char *sha; + const char *conf; + + if (a_sha) { + add_branch = branch1; + other_branch = branch2; + mode = a_mode; + sha = a_sha; + conf = "file/directory"; + } else { + add_branch = branch2; + other_branch = branch1; + mode = b_mode; + sha = b_sha; + conf = "directory/file"; + } + if (path_list_has_path(¤t_directory_set, path)) { + const char *new_path = unique_path(path, add_branch); + clean_merge = 0; + output("CONFLICT (%s): There is a directory with name %s in %s. " + "Adding %s as %s", + conf, path, other_branch, path, new_path); + remove_file(0, path); + update_file(0, sha, mode, new_path); + } else { + output("Adding %s", path); + update_file(1, sha, mode, path); + } + } else if (!o_sha && a_sha && b_sha) { + /* Case C: Added in both (check for same permissions). */ + if (sha_eq(a_sha, b_sha)) { + if (a_mode != b_mode) { + clean_merge = 0; + output("CONFLICT: File %s added identically in both branches, " + "but permissions conflict %06o->%06o", + path, a_mode, b_mode); + output("CONFLICT: adding with permission: %06o", a_mode); + update_file(0, a_sha, a_mode, path); + } else { + /* This case is handled by git-read-tree */ + assert(0 && "This case must be handled by git-read-tree"); + } + } else { + const char *new_path1, *new_path2; + clean_merge = 0; + new_path1 = unique_path(path, branch1); + new_path2 = unique_path(path, branch2); + output("CONFLICT (add/add): File %s added non-identically " + "in both branches. Adding as %s and %s instead.", + path, new_path1, new_path2); + remove_file(0, path); + update_file(0, a_sha, a_mode, new_path1); + update_file(0, b_sha, b_mode, new_path2); + } + + } else if (o_sha && a_sha && b_sha) { + /* case D: Modified in both, but differently. */ + struct merge_file_info mfi; + struct diff_filespec o, a, b; + + output("Auto-merging %s", path); + o.path = a.path = b.path = (char *)path; + hashcpy(o.sha1, o_sha); + o.mode = o_mode; + hashcpy(a.sha1, a_sha); + a.mode = a_mode; + hashcpy(b.sha1, b_sha); + b.mode = b_mode; + + mfi = merge_file(&o, &a, &b, + branch1, branch2); + + if (mfi.clean) + update_file(1, mfi.sha, mfi.mode, path); + else { + clean_merge = 0; + output("CONFLICT (content): Merge conflict in %s", path); + + if (index_only) + update_file(0, mfi.sha, mfi.mode, path); + else + update_file_flags(mfi.sha, mfi.mode, path, + 0 /* update_cache */, 1 /* update_working_directory */); + } + } else + die("Fatal merge failure, shouldn't happen."); + + if (cache_dirty) + flush_cache(); + + return clean_merge; +} + +static int merge_trees(struct tree *head, + struct tree *merge, + struct tree *common, + const char *branch1, + const char *branch2, + struct tree **result) +{ + int code, clean; + if (sha_eq(common->object.sha1, merge->object.sha1)) { + output("Already uptodate!"); + *result = head; + return 1; + } + + code = git_merge_trees(index_only, common, head, merge); + + if (code != 0) + die("merging of trees %s and %s failed", + sha1_to_hex(head->object.sha1), + sha1_to_hex(merge->object.sha1)); + + *result = git_write_tree(); + + if (!*result) { + struct path_list *entries, *re_head, *re_merge; + int i; + path_list_clear(¤t_file_set, 1); + path_list_clear(¤t_directory_set, 1); + get_files_dirs(head); + get_files_dirs(merge); + + entries = get_unmerged(); + re_head = get_renames(head, common, head, merge, entries); + re_merge = get_renames(merge, common, head, merge, entries); + clean = process_renames(re_head, re_merge, + branch1, branch2); + for (i = 0; i < entries->nr; i++) { + const char *path = entries->items[i].path; + struct stage_data *e = entries->items[i].util; + if (e->processed) + continue; + if (!process_entry(path, e, branch1, branch2)) + clean = 0; + } + + path_list_clear(re_merge, 0); + path_list_clear(re_head, 0); + path_list_clear(entries, 1); + + if (clean || index_only) + *result = git_write_tree(); + else + *result = NULL; + } else { + clean = 1; + printf("merging of trees %s and %s resulted in %s\n", + sha1_to_hex(head->object.sha1), + sha1_to_hex(merge->object.sha1), + sha1_to_hex((*result)->object.sha1)); + } + + return clean; +} + +static struct commit_list *reverse_commit_list(struct commit_list *list) +{ + struct commit_list *next = NULL, *current, *backup; + for (current = list; current; current = backup) { + backup = current->next; + current->next = next; + next = current; + } + return next; +} + +/* + * Merge the commits h1 and h2, return the resulting virtual + * commit object and a flag indicating the cleaness of the merge. + */ +static +int merge(struct commit *h1, + struct commit *h2, + const char *branch1, + const char *branch2, + int call_depth /* =0 */, + struct commit *ancestor /* =None */, + struct commit **result) +{ + struct commit_list *ca = NULL, *iter; + struct commit *merged_common_ancestors; + struct tree *mrtree; + int clean; + + output("Merging:"); + output_commit_title(h1); + output_commit_title(h2); + + if (ancestor) + commit_list_insert(ancestor, &ca); + else + ca = reverse_commit_list(get_merge_bases(h1, h2, 1)); + + output("found %u common ancestor(s):", commit_list_count(ca)); + for (iter = ca; iter; iter = iter->next) + output_commit_title(iter->item); + + merged_common_ancestors = pop_commit(&ca); + if (merged_common_ancestors == NULL) { + /* if there is no common ancestor, make an empty tree */ + struct tree *tree = xcalloc(1, sizeof(struct tree)); + + tree->object.parsed = 1; + tree->object.type = OBJ_TREE; + hash_sha1_file(NULL, 0, tree_type, tree->object.sha1); + merged_common_ancestors = make_virtual_commit(tree, "ancestor"); + } + + for (iter = ca; iter; iter = iter->next) { + output_indent = call_depth + 1; + /* + * When the merge fails, the result contains files + * with conflict markers. The cleanness flag is + * ignored, it was never acutally used, as result of + * merge_trees has always overwritten it: the commited + * "conflicts" were already resolved. + */ + merge(merged_common_ancestors, iter->item, + "Temporary merge branch 1", + "Temporary merge branch 2", + call_depth + 1, + NULL, + &merged_common_ancestors); + output_indent = call_depth; + + if (!merged_common_ancestors) + die("merge returned no commit"); + } + + if (call_depth == 0) { + setup_index(0 /* $GIT_DIR/index */); + index_only = 0; + } else { + setup_index(1 /* temporary index */); + git_read_tree(h1->tree); + index_only = 1; + } + + clean = merge_trees(h1->tree, h2->tree, merged_common_ancestors->tree, + branch1, branch2, &mrtree); + + if (!ancestor && (clean || index_only)) { + *result = make_virtual_commit(mrtree, "merged tree"); + commit_list_insert(h1, &(*result)->parents); + commit_list_insert(h2, &(*result)->parents->next); + } else + *result = NULL; + + return clean; +} + +static struct commit *get_ref(const char *ref) +{ + unsigned char sha1[20]; + struct object *object; + + if (get_sha1(ref, sha1)) + die("Could not resolve ref '%s'", ref); + object = deref_tag(parse_object(sha1), ref, strlen(ref)); + if (object->type != OBJ_COMMIT) + return NULL; + if (parse_commit((struct commit *)object)) + die("Could not parse commit '%s'", sha1_to_hex(object->sha1)); + return (struct commit *)object; +} + +int main(int argc, char *argv[]) +{ + static const char *bases[2]; + static unsigned bases_count = 0; + int i, clean; + const char *branch1, *branch2; + struct commit *result, *h1, *h2; + + original_index_file = getenv("GIT_INDEX_FILE"); + + if (!original_index_file) + original_index_file = xstrdup(git_path("index")); + + temporary_index_file = xstrdup(git_path("mrg-rcrsv-tmp-idx")); + + if (argc < 4) + die("Usage: %s <base>... -- <head> <remote> ...\n", argv[0]); + + for (i = 1; i < argc; ++i) { + if (!strcmp(argv[i], "--")) + break; + if (bases_count < sizeof(bases)/sizeof(*bases)) + bases[bases_count++] = argv[i]; + } + if (argc - i != 3) /* "--" "<head>" "<remote>" */ + die("Not handling anything other than two heads merge."); + + branch1 = argv[++i]; + branch2 = argv[++i]; + printf("Merging %s with %s\n", branch1, branch2); + + h1 = get_ref(branch1); + h2 = get_ref(branch2); + + if (bases_count == 1) { + struct commit *ancestor = get_ref(bases[0]); + clean = merge(h1, h2, branch1, branch2, 0, ancestor, &result); + } else + clean = merge(h1, h2, branch1, branch2, 0, NULL, &result); + + if (cache_dirty) + flush_cache(); + + return clean ? 0: 1; +} + +/* +vim: sw=8 noet +*/ diff --git a/merge-tree.c b/merge-tree.c index 7cf00be6d5..692ede0e3d 100644 --- a/merge-tree.c +++ b/merge-tree.c @@ -152,7 +152,7 @@ static int same_entry(struct name_entry *a, struct name_entry *b) { return a->sha1 && b->sha1 && - !memcmp(a->sha1, b->sha1, 20) && + !hashcmp(a->sha1, b->sha1) && a->mode == b->mode; } @@ -177,7 +177,7 @@ static void resolve(const char *base, struct name_entry *branch1, struct name_en if (!branch1) return; - path = strdup(mkpath("%s%s", base, result->path)); + path = xstrdup(mkpath("%s%s", base, result->path)); orig = create_entry(2, branch1->mode, branch1->sha1, path); final = create_entry(0, result->mode, result->sha1, path); @@ -233,7 +233,7 @@ static struct merge_list *link_entry(unsigned stage, const char *base, struct na if (entry) path = entry->path; else - path = strdup(mkpath("%s%s", base, n->path)); + path = xstrdup(mkpath("%s%s", base, n->path)); link = create_entry(stage, n->mode, n->sha1, path); link->link = entry; return link; @@ -337,9 +337,11 @@ int main(int argc, char **argv) struct tree_desc t[3]; void *buf1, *buf2, *buf3; - if (argc < 4) + if (argc != 4) usage(merge_tree_usage); + setup_git_directory(); + buf1 = get_tree_descriptor(t+0, argv[1]); buf2 = get_tree_descriptor(t+1, argv[2]); buf3 = get_tree_descriptor(t+2, argv[3]); @@ -119,7 +119,7 @@ static int verify_tag(char *buffer, unsigned long size) int main(int argc, char **argv) { unsigned long size = 4096; - char *buffer = malloc(size); + char *buffer = xmalloc(size); unsigned char result_sha1[20]; if (argc != 1) @@ -30,7 +30,7 @@ static void append_to_tree(unsigned mode, unsigned char *sha1, char *path) ent = entries[used++] = xmalloc(sizeof(**entries) + len + 1); ent->mode = mode; ent->len = len; - memcpy(ent->sha1, sha1, 20); + hashcpy(ent->sha1, sha1); memcpy(ent->name, path, len+1); } @@ -49,7 +49,6 @@ static void write_tree(unsigned char *sha1) int i; qsort(entries, used, sizeof(*entries), ent_compare); - size = 100; for (size = i = 0; i < used; i++) size += 32 + entries[i]->len; buffer = xmalloc(size); @@ -65,7 +64,7 @@ static void write_tree(unsigned char *sha1) offset += sprintf(buffer + offset, "%o ", ent->mode); offset += sprintf(buffer + offset, "%s", ent->name); buffer[offset++] = 0; - memcpy(buffer + offset, ent->sha1, 20); + hashcpy((unsigned char*)buffer + offset, ent->sha1); offset += 20; } write_sha1_file(buffer, offset, tree_type, sha1); diff --git a/object-refs.c b/object-refs.c index b1b8065851..98ea10005a 100644 --- a/object-refs.c +++ b/object-refs.c @@ -30,7 +30,7 @@ static void grow_refs_hash(void) int new_hash_size = (refs_hash_size + 1000) * 3 / 2; struct object_refs **new_hash; - new_hash = calloc(new_hash_size, sizeof(struct object_refs *)); + new_hash = xcalloc(new_hash_size, sizeof(struct object_refs *)); for (i = 0; i < refs_hash_size; i++) { struct object_refs *ref = refs_hash[i]; if (!ref) @@ -55,9 +55,13 @@ static void add_object_refs(struct object *obj, struct object_refs *ref) struct object_refs *lookup_object_refs(struct object *obj) { - int j = hash_obj(obj, refs_hash_size); struct object_refs *ref; + int j; + /* nothing to lookup */ + if (!refs_hash_size) + return NULL; + j = hash_obj(obj, refs_hash_size); while ((ref = refs_hash[j]) != NULL) { if (ref->base == obj) break; @@ -125,9 +129,6 @@ void mark_reachable(struct object *obj, unsigned int mask) if (!track_object_refs) die("cannot do reachability with object refs turned off"); - /* nothing to lookup */ - if (!refs_hash_size) - return; /* If we've been here already, don't bother */ if (obj->flags & mask) return; @@ -58,7 +58,7 @@ struct object *lookup_object(const unsigned char *sha1) i = hashtable_index(sha1); while ((obj = obj_hash[i]) != NULL) { - if (!memcmp(sha1, obj->sha1, 20)) + if (!hashcmp(sha1, obj->sha1)) break; i++; if (i == obj_hash_size) @@ -73,7 +73,7 @@ static void grow_object_hash(void) int new_hash_size = obj_hash_size < 32 ? 32 : 2 * obj_hash_size; struct object **new_hash; - new_hash = calloc(new_hash_size, sizeof(struct object *)); + new_hash = xcalloc(new_hash_size, sizeof(struct object *)); for (i = 0; i < obj_hash_size; i++) { struct object *obj = obj_hash[i]; if (!obj) @@ -91,7 +91,7 @@ void created_object(const unsigned char *sha1, struct object *obj) obj->used = 0; obj->type = OBJ_NONE; obj->flags = 0; - memcpy(obj->sha1, sha1, 20); + hashcpy(obj->sha1, sha1); if (obj_hash_size - 1 <= nr_objs * 2) grow_object_hash(); @@ -27,17 +27,6 @@ struct object_array { /* * The object type is stored in 3 bits. */ -enum object_type { - OBJ_NONE = 0, - OBJ_COMMIT = 1, - OBJ_TREE = 2, - OBJ_BLOB = 3, - OBJ_TAG = 4, - /* 5/6 for future expansion */ - OBJ_DELTA = 7, - OBJ_BAD, -}; - struct object { unsigned parsed : 1; unsigned used : 1; diff --git a/pack-check.c b/pack-check.c index 3a62e1b7e4..c0caaee093 100644 --- a/pack-check.c +++ b/pack-check.c @@ -29,10 +29,10 @@ static int verify_packfile(struct packed_git *p) pack_base = p->pack_base; SHA1_Update(&ctx, pack_base, pack_size - 20); SHA1_Final(sha1, &ctx); - if (memcmp(sha1, (char *) pack_base + pack_size - 20, 20)) + if (hashcmp(sha1, (unsigned char *)pack_base + pack_size - 20)) return error("Packfile %s SHA1 mismatch with itself", p->pack_name); - if (memcmp(sha1, (char *) index_base + index_size - 40, 20)) + if (hashcmp(sha1, (unsigned char *)index_base + index_size - 40)) return error("Packfile %s SHA1 mismatch with idx", p->pack_name); @@ -42,16 +42,16 @@ static int verify_packfile(struct packed_git *p) */ for (i = err = 0; i < nr_objects; i++) { unsigned char sha1[20]; - struct pack_entry e; void *data; char type[20]; - unsigned long size; + unsigned long size, offset; if (nth_packed_object_sha1(p, i, sha1)) die("internal error pack-check nth-packed-object"); - if (!find_pack_entry_one(sha1, &e, p)) + offset = find_pack_entry_one(sha1, p); + if (!offset) die("internal error pack-check find-pack-entry-one"); - data = unpack_entry_gently(&e, type, &size); + data = unpack_entry_gently(p, offset, type, &size); if (!data) { err = error("cannot unpack %s from %s", sha1_to_hex(sha1), p->pack_name); @@ -84,25 +84,26 @@ static void show_pack_info(struct packed_git *p) for (i = 0; i < nr_objects; i++) { unsigned char sha1[20], base_sha1[20]; - struct pack_entry e; char type[20]; unsigned long size; unsigned long store_size; + unsigned long offset; unsigned int delta_chain_length; if (nth_packed_object_sha1(p, i, sha1)) die("internal error pack-check nth-packed-object"); - if (!find_pack_entry_one(sha1, &e, p)) + offset = find_pack_entry_one(sha1, p); + if (!offset) die("internal error pack-check find-pack-entry-one"); - packed_object_info_detail(&e, type, &size, &store_size, + packed_object_info_detail(p, offset, type, &size, &store_size, &delta_chain_length, base_sha1); printf("%s ", sha1_to_hex(sha1)); if (!delta_chain_length) - printf("%-6s %lu %u\n", type, size, e.offset); + printf("%-6s %lu %lu\n", type, size, offset); else { - printf("%-6s %lu %u %u %s\n", type, size, e.offset, + printf("%-6s %lu %lu %u %s\n", type, size, offset, delta_chain_length, sha1_to_hex(base_sha1)); if (delta_chain_length < MAX_CHAIN) chain_histogram[delta_chain_length]++; @@ -135,7 +136,7 @@ int verify_pack(struct packed_git *p, int verbose) SHA1_Init(&ctx); SHA1_Update(&ctx, index_base, index_size - 20); SHA1_Final(sha1, &ctx); - if (memcmp(sha1, (char *) index_base + index_size - 20, 20)) + if (hashcmp(sha1, (unsigned char *)index_base + index_size - 20)) ret = error("Packfile index for %s SHA1 mismatch", p->pack_name); diff --git a/pack-redundant.c b/pack-redundant.c index 41fb960569..edb5524fc4 100644 --- a/pack-redundant.c +++ b/pack-redundant.c @@ -13,7 +13,7 @@ static const char pack_redundant_usage[] = "git-pack-redundant [ --verbose ] [ --alt-odb ] < --all | <.pack filename> ...>"; -static int load_all_packs = 0, verbose = 0, alt_odb = 0; +static int load_all_packs, verbose, alt_odb; struct llist_item { struct llist_item *next; @@ -37,7 +37,7 @@ struct pll { struct pack_list *pl; }; -static struct llist_item *free_nodes = NULL; +static struct llist_item *free_nodes; static inline void llist_item_put(struct llist_item *item) { @@ -139,7 +139,7 @@ static inline struct llist_item *llist_insert_sorted_unique(struct llist *list, l = (hint == NULL) ? list->front : hint; while (l) { - int cmp = memcmp(l->sha1, sha1, 20); + int cmp = hashcmp(l->sha1, sha1); if (cmp > 0) { /* we insert before this entry */ return llist_insert(list, prev, sha1); } @@ -162,7 +162,7 @@ redo_from_start: l = (hint == NULL) ? list->front : hint; prev = NULL; while (l) { - int cmp = memcmp(l->sha1, sha1, 20); + int cmp = hashcmp(l->sha1, sha1); if (cmp > 0) /* not in list, since sorted */ return prev; if(!cmp) { /* found */ @@ -256,7 +256,7 @@ static void cmp_two_packs(struct pack_list *p1, struct pack_list *p2) while (p1_off <= p1->pack->index_size - 3 * 20 && p2_off <= p2->pack->index_size - 3 * 20) { - int cmp = memcmp(p1_base + p1_off, p2_base + p2_off, 20); + int cmp = hashcmp(p1_base + p1_off, p2_base + p2_off); /* cmp ~ p1 - p2 */ if (cmp == 0) { p1_hint = llist_sorted_remove(p1->unique_objects, @@ -351,16 +351,16 @@ static size_t sizeof_union(struct packed_git *p1, struct packed_git *p2) { size_t ret = 0; int p1_off, p2_off; - char *p1_base, *p2_base; + unsigned char *p1_base, *p2_base; p1_off = p2_off = 256 * 4 + 4; - p1_base = (char *)p1->index_base; - p2_base = (char *)p2->index_base; + p1_base = (unsigned char *)p1->index_base; + p2_base = (unsigned char *)p2->index_base; while (p1_off <= p1->index_size - 3 * 20 && p2_off <= p2->index_size - 3 * 20) { - int cmp = memcmp(p1_base + p1_off, p2_base + p2_off, 20); + int cmp = hashcmp(p1_base + p1_off, p2_base + p2_off); /* cmp ~ p1 - p2 */ if (cmp == 0) { ret++; @@ -15,11 +15,13 @@ void setup_pager(void) { pid_t pid; int fd[2]; - const char *pager = getenv("PAGER"); + const char *pager = getenv("GIT_PAGER"); if (!isatty(1)) return; if (!pager) + pager = getenv("PAGER"); + if (!pager) pager = "less"; else if (!*pager || !strcmp(pager, "cat")) return; diff --git a/patch-id.c b/patch-id.c index 3b4c80f764..086d2d9c68 100644 --- a/patch-id.c +++ b/patch-id.c @@ -47,7 +47,7 @@ static void generate_id_list(void) if (!get_sha1_hex(p, n)) { flush_current_id(patchlen, sha1, &ctx); - memcpy(sha1, n, 20); + hashcpy(sha1, n); patchlen = 0; continue; } diff --git a/path-list.c b/path-list.c index f15a10de37..0c332dc7b5 100644 --- a/path-list.c +++ b/path-list.c @@ -45,7 +45,7 @@ static int add_entry(struct path_list *list, const char *path) (list->nr - index) * sizeof(struct path_list_item)); list->items[index].path = list->strdup_paths ? - strdup(path) : (char *)path; + xstrdup(path) : (char *)path; list->items[index].util = NULL; list->nr++; @@ -85,8 +85,7 @@ void path_list_clear(struct path_list *list, int free_items) for (i = 0; i < list->nr; i++) { if (list->strdup_paths) free(list->items[i].path); - if (list->items[i].util) - free(list->items[i].util); + free(list->items[i].util); } free(list->items); } @@ -13,9 +13,15 @@ #include "cache.h" #include <pwd.h> -static char pathname[PATH_MAX]; static char bad_path[] = "/bad-path/"; +static char *get_pathname(void) +{ + static char pathname_array[4][PATH_MAX]; + static int index; + return pathname_array[3 & ++index]; +} + static char *cleanup_path(char *path) { /* Clean it up */ @@ -31,6 +37,7 @@ char *mkpath(const char *fmt, ...) { va_list args; unsigned len; + char *pathname = get_pathname(); va_start(args, fmt); len = vsnprintf(pathname, PATH_MAX, fmt, args); @@ -43,6 +50,7 @@ char *mkpath(const char *fmt, ...) char *git_path(const char *fmt, ...) { const char *git_dir = get_git_dir(); + char *pathname = get_pathname(); va_list args; unsigned len; diff --git a/peek-remote.c b/peek-remote.c index 2b30980b04..353da002b4 100644 --- a/peek-remote.c +++ b/peek-remote.c @@ -1,7 +1,6 @@ #include "cache.h" #include "refs.h" #include "pkt-line.h" -#include <sys/wait.h> static const char peek_remote_usage[] = "git-peek-remote [--exec=upload-pack] [host:]directory"; @@ -67,6 +66,6 @@ int main(int argc, char **argv) ret = peek_remote(fd, flags); close(fd[0]); close(fd[1]); - finish_connect(pid); - return ret; + ret |= finish_connect(pid); + return !!ret; } diff --git a/perl/.gitignore b/perl/.gitignore new file mode 100644 index 0000000000..e990caeea7 --- /dev/null +++ b/perl/.gitignore @@ -0,0 +1,4 @@ +Makefile +blib +blibdirs +pm_to_blib diff --git a/perl/Git.pm b/perl/Git.pm new file mode 100644 index 0000000000..2b26b65bfb --- /dev/null +++ b/perl/Git.pm @@ -0,0 +1,837 @@ +=head1 NAME + +Git - Perl interface to the Git version control system + +=cut + + +package Git; + +use strict; + + +BEGIN { + +our ($VERSION, @ISA, @EXPORT, @EXPORT_OK); + +# Totally unstable API. +$VERSION = '0.01'; + + +=head1 SYNOPSIS + + use Git; + + my $version = Git::command_oneline('version'); + + git_cmd_try { Git::command_noisy('update-server-info') } + '%s failed w/ code %d'; + + my $repo = Git->repository (Directory => '/srv/git/cogito.git'); + + + my @revs = $repo->command('rev-list', '--since=last monday', '--all'); + + my ($fh, $c) = $repo->command_output_pipe('rev-list', '--since=last monday', '--all'); + my $lastrev = <$fh>; chomp $lastrev; + $repo->command_close_pipe($fh, $c); + + my $lastrev = $repo->command_oneline( [ 'rev-list', '--all' ], + STDERR => 0 ); + +=cut + + +require Exporter; + +@ISA = qw(Exporter); + +@EXPORT = qw(git_cmd_try); + +# Methods which can be called as standalone functions as well: +@EXPORT_OK = qw(command command_oneline command_noisy + command_output_pipe command_input_pipe command_close_pipe + version exec_path hash_object git_cmd_try); + + +=head1 DESCRIPTION + +This module provides Perl scripts easy way to interface the Git version control +system. The modules have an easy and well-tested way to call arbitrary Git +commands; in the future, the interface will also provide specialized methods +for doing easily operations which are not totally trivial to do over +the generic command interface. + +While some commands can be executed outside of any context (e.g. 'version' +or 'init-db'), most operations require a repository context, which in practice +means getting an instance of the Git object using the repository() constructor. +(In the future, we will also get a new_repository() constructor.) All commands +called as methods of the object are then executed in the context of the +repository. + +Part of the "repository state" is also information about path to the attached +working copy (unless you work with a bare repository). You can also navigate +inside of the working copy using the C<wc_chdir()> method. (Note that +the repository object is self-contained and will not change working directory +of your process.) + +TODO: In the future, we might also do + + my $remoterepo = $repo->remote_repository (Name => 'cogito', Branch => 'master'); + $remoterepo ||= Git->remote_repository ('http://git.or.cz/cogito.git/'); + my @refs = $remoterepo->refs(); + +Currently, the module merely wraps calls to external Git tools. In the future, +it will provide a much faster way to interact with Git by linking directly +to libgit. This should be completely opaque to the user, though (performance +increate nonwithstanding). + +=cut + + +use Carp qw(carp croak); # but croak is bad - throw instead +use Error qw(:try); +use Cwd qw(abs_path); + +} + + +=head1 CONSTRUCTORS + +=over 4 + +=item repository ( OPTIONS ) + +=item repository ( DIRECTORY ) + +=item repository () + +Construct a new repository object. +C<OPTIONS> are passed in a hash like fashion, using key and value pairs. +Possible options are: + +B<Repository> - Path to the Git repository. + +B<WorkingCopy> - Path to the associated working copy; not strictly required +as many commands will happily crunch on a bare repository. + +B<WorkingSubdir> - Subdirectory in the working copy to work inside. +Just left undefined if you do not want to limit the scope of operations. + +B<Directory> - Path to the Git working directory in its usual setup. +The C<.git> directory is searched in the directory and all the parent +directories; if found, C<WorkingCopy> is set to the directory containing +it and C<Repository> to the C<.git> directory itself. If no C<.git> +directory was found, the C<Directory> is assumed to be a bare repository, +C<Repository> is set to point at it and C<WorkingCopy> is left undefined. +If the C<$GIT_DIR> environment variable is set, things behave as expected +as well. + +You should not use both C<Directory> and either of C<Repository> and +C<WorkingCopy> - the results of that are undefined. + +Alternatively, a directory path may be passed as a single scalar argument +to the constructor; it is equivalent to setting only the C<Directory> option +field. + +Calling the constructor with no options whatsoever is equivalent to +calling it with C<< Directory => '.' >>. In general, if you are building +a standard porcelain command, simply doing C<< Git->repository() >> should +do the right thing and setup the object to reflect exactly where the user +is right now. + +=cut + +sub repository { + my $class = shift; + my @args = @_; + my %opts = (); + my $self; + + if (defined $args[0]) { + if ($#args % 2 != 1) { + # Not a hash. + $#args == 0 or throw Error::Simple("bad usage"); + %opts = ( Directory => $args[0] ); + } else { + %opts = @args; + } + } + + if (not defined $opts{Repository} and not defined $opts{WorkingCopy}) { + $opts{Directory} ||= '.'; + } + + if ($opts{Directory}) { + -d $opts{Directory} or throw Error::Simple("Directory not found: $!"); + + my $search = Git->repository(WorkingCopy => $opts{Directory}); + my $dir; + try { + $dir = $search->command_oneline(['rev-parse', '--git-dir'], + STDERR => 0); + } catch Git::Error::Command with { + $dir = undef; + }; + + if ($dir) { + $dir =~ m#^/# or $dir = $opts{Directory} . '/' . $dir; + $opts{Repository} = $dir; + + # If --git-dir went ok, this shouldn't die either. + my $prefix = $search->command_oneline('rev-parse', '--show-prefix'); + $dir = abs_path($opts{Directory}) . '/'; + if ($prefix) { + if (substr($dir, -length($prefix)) ne $prefix) { + throw Error::Simple("rev-parse confused me - $dir does not have trailing $prefix"); + } + substr($dir, -length($prefix)) = ''; + } + $opts{WorkingCopy} = $dir; + $opts{WorkingSubdir} = $prefix; + + } else { + # A bare repository? Let's see... + $dir = $opts{Directory}; + + unless (-d "$dir/refs" and -d "$dir/objects" and -e "$dir/HEAD") { + # Mimick git-rev-parse --git-dir error message: + throw Error::Simple('fatal: Not a git repository'); + } + my $search = Git->repository(Repository => $dir); + try { + $search->command('symbolic-ref', 'HEAD'); + } catch Git::Error::Command with { + # Mimick git-rev-parse --git-dir error message: + throw Error::Simple('fatal: Not a git repository'); + } + + $opts{Repository} = abs_path($dir); + } + + delete $opts{Directory}; + } + + $self = { opts => \%opts }; + bless $self, $class; +} + + +=back + +=head1 METHODS + +=over 4 + +=item command ( COMMAND [, ARGUMENTS... ] ) + +=item command ( [ COMMAND, ARGUMENTS... ], { Opt => Val ... } ) + +Execute the given Git C<COMMAND> (specify it without the 'git-' +prefix), optionally with the specified extra C<ARGUMENTS>. + +The second more elaborate form can be used if you want to further adjust +the command execution. Currently, only one option is supported: + +B<STDERR> - How to deal with the command's error output. By default (C<undef>) +it is delivered to the caller's C<STDERR>. A false value (0 or '') will cause +it to be thrown away. If you want to process it, you can get it in a filehandle +you specify, but you must be extremely careful; if the error output is not +very short and you want to read it in the same process as where you called +C<command()>, you are set up for a nice deadlock! + +The method can be called without any instance or on a specified Git repository +(in that case the command will be run in the repository context). + +In scalar context, it returns all the command output in a single string +(verbatim). + +In array context, it returns an array containing lines printed to the +command's stdout (without trailing newlines). + +In both cases, the command's stdin and stderr are the same as the caller's. + +=cut + +sub command { + my ($fh, $ctx) = command_output_pipe(@_); + + if (not defined wantarray) { + # Nothing to pepper the possible exception with. + _cmd_close($fh, $ctx); + + } elsif (not wantarray) { + local $/; + my $text = <$fh>; + try { + _cmd_close($fh, $ctx); + } catch Git::Error::Command with { + # Pepper with the output: + my $E = shift; + $E->{'-outputref'} = \$text; + throw $E; + }; + return $text; + + } else { + my @lines = <$fh>; + chomp @lines; + try { + _cmd_close($fh, $ctx); + } catch Git::Error::Command with { + my $E = shift; + $E->{'-outputref'} = \@lines; + throw $E; + }; + return @lines; + } +} + + +=item command_oneline ( COMMAND [, ARGUMENTS... ] ) + +=item command_oneline ( [ COMMAND, ARGUMENTS... ], { Opt => Val ... } ) + +Execute the given C<COMMAND> in the same way as command() +does but always return a scalar string containing the first line +of the command's standard output. + +=cut + +sub command_oneline { + my ($fh, $ctx) = command_output_pipe(@_); + + my $line = <$fh>; + defined $line and chomp $line; + try { + _cmd_close($fh, $ctx); + } catch Git::Error::Command with { + # Pepper with the output: + my $E = shift; + $E->{'-outputref'} = \$line; + throw $E; + }; + return $line; +} + + +=item command_output_pipe ( COMMAND [, ARGUMENTS... ] ) + +=item command_output_pipe ( [ COMMAND, ARGUMENTS... ], { Opt => Val ... } ) + +Execute the given C<COMMAND> in the same way as command() +does but return a pipe filehandle from which the command output can be +read. + +The function can return C<($pipe, $ctx)> in array context. +See C<command_close_pipe()> for details. + +=cut + +sub command_output_pipe { + _command_common_pipe('-|', @_); +} + + +=item command_input_pipe ( COMMAND [, ARGUMENTS... ] ) + +=item command_input_pipe ( [ COMMAND, ARGUMENTS... ], { Opt => Val ... } ) + +Execute the given C<COMMAND> in the same way as command_output_pipe() +does but return an input pipe filehandle instead; the command output +is not captured. + +The function can return C<($pipe, $ctx)> in array context. +See C<command_close_pipe()> for details. + +=cut + +sub command_input_pipe { + _command_common_pipe('|-', @_); +} + + +=item command_close_pipe ( PIPE [, CTX ] ) + +Close the C<PIPE> as returned from C<command_*_pipe()>, checking +whether the command finished successfuly. The optional C<CTX> argument +is required if you want to see the command name in the error message, +and it is the second value returned by C<command_*_pipe()> when +called in array context. The call idiom is: + + my ($fh, $ctx) = $r->command_output_pipe('status'); + while (<$fh>) { ... } + $r->command_close_pipe($fh, $ctx); + +Note that you should not rely on whatever actually is in C<CTX>; +currently it is simply the command name but in future the context might +have more complicated structure. + +=cut + +sub command_close_pipe { + my ($self, $fh, $ctx) = _maybe_self(@_); + $ctx ||= '<unknown>'; + _cmd_close($fh, $ctx); +} + + +=item command_noisy ( COMMAND [, ARGUMENTS... ] ) + +Execute the given C<COMMAND> in the same way as command() does but do not +capture the command output - the standard output is not redirected and goes +to the standard output of the caller application. + +While the method is called command_noisy(), you might want to as well use +it for the most silent Git commands which you know will never pollute your +stdout but you want to avoid the overhead of the pipe setup when calling them. + +The function returns only after the command has finished running. + +=cut + +sub command_noisy { + my ($self, $cmd, @args) = _maybe_self(@_); + _check_valid_cmd($cmd); + + my $pid = fork; + if (not defined $pid) { + throw Error::Simple("fork failed: $!"); + } elsif ($pid == 0) { + _cmd_exec($self, $cmd, @args); + } + if (waitpid($pid, 0) > 0 and $?>>8 != 0) { + throw Git::Error::Command(join(' ', $cmd, @args), $? >> 8); + } +} + + +=item version () + +Return the Git version in use. + +=cut + +sub version { + my $verstr = command_oneline('--version'); + $verstr =~ s/^git version //; + $verstr; +} + + +=item exec_path () + +Return path to the Git sub-command executables (the same as +C<git --exec-path>). Useful mostly only internally. + +=cut + +sub exec_path { command_oneline('--exec-path') } + + +=item repo_path () + +Return path to the git repository. Must be called on a repository instance. + +=cut + +sub repo_path { $_[0]->{opts}->{Repository} } + + +=item wc_path () + +Return path to the working copy. Must be called on a repository instance. + +=cut + +sub wc_path { $_[0]->{opts}->{WorkingCopy} } + + +=item wc_subdir () + +Return path to the subdirectory inside of a working copy. Must be called +on a repository instance. + +=cut + +sub wc_subdir { $_[0]->{opts}->{WorkingSubdir} ||= '' } + + +=item wc_chdir ( SUBDIR ) + +Change the working copy subdirectory to work within. The C<SUBDIR> is +relative to the working copy root directory (not the current subdirectory). +Must be called on a repository instance attached to a working copy +and the directory must exist. + +=cut + +sub wc_chdir { + my ($self, $subdir) = @_; + $self->wc_path() + or throw Error::Simple("bare repository"); + + -d $self->wc_path().'/'.$subdir + or throw Error::Simple("subdir not found: $!"); + # Of course we will not "hold" the subdirectory so anyone + # can delete it now and we will never know. But at least we tried. + + $self->{opts}->{WorkingSubdir} = $subdir; +} + + +=item config ( VARIABLE ) + +Retrieve the configuration C<VARIABLE> in the same manner as C<repo-config> +does. In scalar context requires the variable to be set only one time +(exception is thrown otherwise), in array context returns allows the +variable to be set multiple times and returns all the values. + +Must be called on a repository instance. + +This currently wraps command('repo-config') so it is not so fast. + +=cut + +sub config { + my ($self, $var) = @_; + $self->repo_path() + or throw Error::Simple("not a repository"); + + try { + if (wantarray) { + return $self->command('repo-config', '--get-all', $var); + } else { + return $self->command_oneline('repo-config', '--get', $var); + } + } catch Git::Error::Command with { + my $E = shift; + if ($E->value() == 1) { + # Key not found. + return undef; + } else { + throw $E; + } + }; +} + + +=item ident ( TYPE | IDENTSTR ) + +=item ident_person ( TYPE | IDENTSTR | IDENTARRAY ) + +This suite of functions retrieves and parses ident information, as stored +in the commit and tag objects or produced by C<var GIT_type_IDENT> (thus +C<TYPE> can be either I<author> or I<committer>; case is insignificant). + +The C<ident> method retrieves the ident information from C<git-var> +and either returns it as a scalar string or as an array with the fields parsed. +Alternatively, it can take a prepared ident string (e.g. from the commit +object) and just parse it. + +C<ident_person> returns the person part of the ident - name and email; +it can take the same arguments as C<ident> or the array returned by C<ident>. + +The synopsis is like: + + my ($name, $email, $time_tz) = ident('author'); + "$name <$email>" eq ident_person('author'); + "$name <$email>" eq ident_person($name); + $time_tz =~ /^\d+ [+-]\d{4}$/; + +Both methods must be called on a repository instance. + +=cut + +sub ident { + my ($self, $type) = @_; + my $identstr; + if (lc $type eq lc 'committer' or lc $type eq lc 'author') { + $identstr = $self->command_oneline('var', 'GIT_'.uc($type).'_IDENT'); + } else { + $identstr = $type; + } + if (wantarray) { + return $identstr =~ /^(.*) <(.*)> (\d+ [+-]\d{4})$/; + } else { + return $identstr; + } +} + +sub ident_person { + my ($self, @ident) = @_; + $#ident == 0 and @ident = $self->ident($ident[0]); + return "$ident[0] <$ident[1]>"; +} + + +=item hash_object ( TYPE, FILENAME ) + +Compute the SHA1 object id of the given C<FILENAME> (or data waiting in +C<FILEHANDLE>) considering it is of the C<TYPE> object type (C<blob>, +C<commit>, C<tree>). + +The method can be called without any instance or on a specified Git repository, +it makes zero difference. + +The function returns the SHA1 hash. + +=cut + +# TODO: Support for passing FILEHANDLE instead of FILENAME +sub hash_object { + my ($self, $type, $file) = _maybe_self(@_); + command_oneline('hash-object', '-t', $type, $file); +} + + + +=back + +=head1 ERROR HANDLING + +All functions are supposed to throw Perl exceptions in case of errors. +See the L<Error> module on how to catch those. Most exceptions are mere +L<Error::Simple> instances. + +However, the C<command()>, C<command_oneline()> and C<command_noisy()> +functions suite can throw C<Git::Error::Command> exceptions as well: those are +thrown when the external command returns an error code and contain the error +code as well as access to the captured command's output. The exception class +provides the usual C<stringify> and C<value> (command's exit code) methods and +in addition also a C<cmd_output> method that returns either an array or a +string with the captured command output (depending on the original function +call context; C<command_noisy()> returns C<undef>) and $<cmdline> which +returns the command and its arguments (but without proper quoting). + +Note that the C<command_*_pipe()> functions cannot throw this exception since +it has no idea whether the command failed or not. You will only find out +at the time you C<close> the pipe; if you want to have that automated, +use C<command_close_pipe()>, which can throw the exception. + +=cut + +{ + package Git::Error::Command; + + @Git::Error::Command::ISA = qw(Error); + + sub new { + my $self = shift; + my $cmdline = '' . shift; + my $value = 0 + shift; + my $outputref = shift; + my(@args) = (); + + local $Error::Depth = $Error::Depth + 1; + + push(@args, '-cmdline', $cmdline); + push(@args, '-value', $value); + push(@args, '-outputref', $outputref); + + $self->SUPER::new(-text => 'command returned error', @args); + } + + sub stringify { + my $self = shift; + my $text = $self->SUPER::stringify; + $self->cmdline() . ': ' . $text . ': ' . $self->value() . "\n"; + } + + sub cmdline { + my $self = shift; + $self->{'-cmdline'}; + } + + sub cmd_output { + my $self = shift; + my $ref = $self->{'-outputref'}; + defined $ref or undef; + if (ref $ref eq 'ARRAY') { + return @$ref; + } else { # SCALAR + return $$ref; + } + } +} + +=over 4 + +=item git_cmd_try { CODE } ERRMSG + +This magical statement will automatically catch any C<Git::Error::Command> +exceptions thrown by C<CODE> and make your program die with C<ERRMSG> +on its lips; the message will have %s substituted for the command line +and %d for the exit status. This statement is useful mostly for producing +more user-friendly error messages. + +In case of no exception caught the statement returns C<CODE>'s return value. + +Note that this is the only auto-exported function. + +=cut + +sub git_cmd_try(&$) { + my ($code, $errmsg) = @_; + my @result; + my $err; + my $array = wantarray; + try { + if ($array) { + @result = &$code; + } else { + $result[0] = &$code; + } + } catch Git::Error::Command with { + my $E = shift; + $err = $errmsg; + $err =~ s/\%s/$E->cmdline()/ge; + $err =~ s/\%d/$E->value()/ge; + # We can't croak here since Error.pm would mangle + # that to Error::Simple. + }; + $err and croak $err; + return $array ? @result : $result[0]; +} + + +=back + +=head1 COPYRIGHT + +Copyright 2006 by Petr Baudis E<lt>pasky@suse.czE<gt>. + +This module is free software; it may be used, copied, modified +and distributed under the terms of the GNU General Public Licence, +either version 2, or (at your option) any later version. + +=cut + + +# Take raw method argument list and return ($obj, @args) in case +# the method was called upon an instance and (undef, @args) if +# it was called directly. +sub _maybe_self { + # This breaks inheritance. Oh well. + ref $_[0] eq 'Git' ? @_ : (undef, @_); +} + +# Check if the command id is something reasonable. +sub _check_valid_cmd { + my ($cmd) = @_; + $cmd =~ /^[a-z0-9A-Z_-]+$/ or throw Error::Simple("bad command: $cmd"); +} + +# Common backend for the pipe creators. +sub _command_common_pipe { + my $direction = shift; + my ($self, @p) = _maybe_self(@_); + my (%opts, $cmd, @args); + if (ref $p[0]) { + ($cmd, @args) = @{shift @p}; + %opts = ref $p[0] ? %{$p[0]} : @p; + } else { + ($cmd, @args) = @p; + } + _check_valid_cmd($cmd); + + my $fh; + if ($^O eq '##INSERT_ACTIVESTATE_STRING_HERE##') { + # ActiveState Perl + #defined $opts{STDERR} and + # warn 'ignoring STDERR option - running w/ ActiveState'; + $direction eq '-|' or + die 'input pipe for ActiveState not implemented'; + tie ($fh, 'Git::activestate_pipe', $cmd, @args); + + } else { + my $pid = open($fh, $direction); + if (not defined $pid) { + throw Error::Simple("open failed: $!"); + } elsif ($pid == 0) { + if (defined $opts{STDERR}) { + close STDERR; + } + if ($opts{STDERR}) { + open (STDERR, '>&', $opts{STDERR}) + or die "dup failed: $!"; + } + _cmd_exec($self, $cmd, @args); + } + } + return wantarray ? ($fh, join(' ', $cmd, @args)) : $fh; +} + +# When already in the subprocess, set up the appropriate state +# for the given repository and execute the git command. +sub _cmd_exec { + my ($self, @args) = @_; + if ($self) { + $self->repo_path() and $ENV{'GIT_DIR'} = $self->repo_path(); + $self->wc_path() and chdir($self->wc_path()); + $self->wc_subdir() and chdir($self->wc_subdir()); + } + _execv_git_cmd(@args); + die "exec failed: $!"; +} + +# Execute the given Git command ($_[0]) with arguments ($_[1..]) +# by searching for it at proper places. +sub _execv_git_cmd { exec('git', @_); } + +# Close pipe to a subprocess. +sub _cmd_close { + my ($fh, $ctx) = @_; + if (not close $fh) { + if ($!) { + # It's just close, no point in fatalities + carp "error closing pipe: $!"; + } elsif ($? >> 8) { + # The caller should pepper this. + throw Git::Error::Command($ctx, $? >> 8); + } + # else we might e.g. closed a live stream; the command + # dying of SIGPIPE would drive us here. + } +} + + +sub DESTROY { } + + +# Pipe implementation for ActiveState Perl. + +package Git::activestate_pipe; +use strict; + +sub TIEHANDLE { + my ($class, @params) = @_; + # FIXME: This is probably horrible idea and the thing will explode + # at the moment you give it arguments that require some quoting, + # but I have no ActiveState clue... --pasky + my $cmdline = join " ", @params; + my @data = qx{$cmdline}; + bless { i => 0, data => \@data }, $class; +} + +sub READLINE { + my $self = shift; + if ($self->{i} >= scalar @{$self->{data}}) { + return undef; + } + return $self->{'data'}->[ $self->{i}++ ]; +} + +sub CLOSE { + my $self = shift; + delete $self->{data}; + delete $self->{i}; +} + +sub EOF { + my $self = shift; + return ($self->{i} >= scalar @{$self->{data}}); +} + + +1; # Famous last words diff --git a/perl/Makefile.PL b/perl/Makefile.PL new file mode 100644 index 0000000000..de73235e4c --- /dev/null +++ b/perl/Makefile.PL @@ -0,0 +1,28 @@ +use ExtUtils::MakeMaker; + +sub MY::postamble { + return <<'MAKE_FRAG'; +instlibdir: + @echo '$(INSTALLSITELIB)' + +MAKE_FRAG +} + +my %pm = ('Git.pm' => '$(INST_LIBDIR)/Git.pm'); + +# We come with our own bundled Error.pm. It's not in the set of default +# Perl modules so install it if it's not available on the system yet. +eval { require Error }; +if ($@) { + $pm{'private-Error.pm'} = '$(INST_LIBDIR)/Error.pm'; +} + +my %extra; +$extra{DESTDIR} = $ENV{DESTDIR} if $ENV{DESTDIR}; + +WriteMakefile( + NAME => 'Git', + VERSION_FROM => 'Git.pm', + PM => \%pm, + %extra +); diff --git a/perl/private-Error.pm b/perl/private-Error.pm new file mode 100644 index 0000000000..8fff86699f --- /dev/null +++ b/perl/private-Error.pm @@ -0,0 +1,827 @@ +# Error.pm +# +# Copyright (c) 1997-8 Graham Barr <gbarr@ti.com>. All rights reserved. +# This program is free software; you can redistribute it and/or +# modify it under the same terms as Perl itself. +# +# Based on my original Error.pm, and Exceptions.pm by Peter Seibel +# <peter@weblogic.com> and adapted by Jesse Glick <jglick@sig.bsh.com>. +# +# but modified ***significantly*** + +package Error; + +use strict; +use vars qw($VERSION); +use 5.004; + +$VERSION = "0.15009"; + +use overload ( + '""' => 'stringify', + '0+' => 'value', + 'bool' => sub { return 1; }, + 'fallback' => 1 +); + +$Error::Depth = 0; # Depth to pass to caller() +$Error::Debug = 0; # Generate verbose stack traces +@Error::STACK = (); # Clause stack for try +$Error::THROWN = undef; # last error thrown, a workaround until die $ref works + +my $LAST; # Last error created +my %ERROR; # Last error associated with package + +sub throw_Error_Simple +{ + my $args = shift; + return Error::Simple->new($args->{'text'}); +} + +$Error::ObjectifyCallback = \&throw_Error_Simple; + + +# Exported subs are defined in Error::subs + +sub import { + shift; + local $Exporter::ExportLevel = $Exporter::ExportLevel + 1; + Error::subs->import(@_); +} + +# I really want to use last for the name of this method, but it is a keyword +# which prevent the syntax last Error + +sub prior { + shift; # ignore + + return $LAST unless @_; + + my $pkg = shift; + return exists $ERROR{$pkg} ? $ERROR{$pkg} : undef + unless ref($pkg); + + my $obj = $pkg; + my $err = undef; + if($obj->isa('HASH')) { + $err = $obj->{'__Error__'} + if exists $obj->{'__Error__'}; + } + elsif($obj->isa('GLOB')) { + $err = ${*$obj}{'__Error__'} + if exists ${*$obj}{'__Error__'}; + } + + $err; +} + +sub flush { + shift; #ignore + + unless (@_) { + $LAST = undef; + return; + } + + my $pkg = shift; + return unless ref($pkg); + + undef $ERROR{$pkg} if defined $ERROR{$pkg}; +} + +# Return as much information as possible about where the error +# happened. The -stacktrace element only exists if $Error::DEBUG +# was set when the error was created + +sub stacktrace { + my $self = shift; + + return $self->{'-stacktrace'} + if exists $self->{'-stacktrace'}; + + my $text = exists $self->{'-text'} ? $self->{'-text'} : "Died"; + + $text .= sprintf(" at %s line %d.\n", $self->file, $self->line) + unless($text =~ /\n$/s); + + $text; +} + +# Allow error propagation, ie +# +# $ber->encode(...) or +# return Error->prior($ber)->associate($ldap); + +sub associate { + my $err = shift; + my $obj = shift; + + return unless ref($obj); + + if($obj->isa('HASH')) { + $obj->{'__Error__'} = $err; + } + elsif($obj->isa('GLOB')) { + ${*$obj}{'__Error__'} = $err; + } + $obj = ref($obj); + $ERROR{ ref($obj) } = $err; + + return; +} + +sub new { + my $self = shift; + my($pkg,$file,$line) = caller($Error::Depth); + + my $err = bless { + '-package' => $pkg, + '-file' => $file, + '-line' => $line, + @_ + }, $self; + + $err->associate($err->{'-object'}) + if(exists $err->{'-object'}); + + # To always create a stacktrace would be very inefficient, so + # we only do it if $Error::Debug is set + + if($Error::Debug) { + require Carp; + local $Carp::CarpLevel = $Error::Depth; + my $text = defined($err->{'-text'}) ? $err->{'-text'} : "Error"; + my $trace = Carp::longmess($text); + # Remove try calls from the trace + $trace =~ s/(\n\s+\S+__ANON__[^\n]+)?\n\s+eval[^\n]+\n\s+Error::subs::try[^\n]+(?=\n)//sog; + $trace =~ s/(\n\s+\S+__ANON__[^\n]+)?\n\s+eval[^\n]+\n\s+Error::subs::run_clauses[^\n]+\n\s+Error::subs::try[^\n]+(?=\n)//sog; + $err->{'-stacktrace'} = $trace + } + + $@ = $LAST = $ERROR{$pkg} = $err; +} + +# Throw an error. this contains some very gory code. + +sub throw { + my $self = shift; + local $Error::Depth = $Error::Depth + 1; + + # if we are not rethrow-ing then create the object to throw + $self = $self->new(@_) unless ref($self); + + die $Error::THROWN = $self; +} + +# syntactic sugar for +# +# die with Error( ... ); + +sub with { + my $self = shift; + local $Error::Depth = $Error::Depth + 1; + + $self->new(@_); +} + +# syntactic sugar for +# +# record Error( ... ) and return; + +sub record { + my $self = shift; + local $Error::Depth = $Error::Depth + 1; + + $self->new(@_); +} + +# catch clause for +# +# try { ... } catch CLASS with { ... } + +sub catch { + my $pkg = shift; + my $code = shift; + my $clauses = shift || {}; + my $catch = $clauses->{'catch'} ||= []; + + unshift @$catch, $pkg, $code; + + $clauses; +} + +# Object query methods + +sub object { + my $self = shift; + exists $self->{'-object'} ? $self->{'-object'} : undef; +} + +sub file { + my $self = shift; + exists $self->{'-file'} ? $self->{'-file'} : undef; +} + +sub line { + my $self = shift; + exists $self->{'-line'} ? $self->{'-line'} : undef; +} + +sub text { + my $self = shift; + exists $self->{'-text'} ? $self->{'-text'} : undef; +} + +# overload methods + +sub stringify { + my $self = shift; + defined $self->{'-text'} ? $self->{'-text'} : "Died"; +} + +sub value { + my $self = shift; + exists $self->{'-value'} ? $self->{'-value'} : undef; +} + +package Error::Simple; + +@Error::Simple::ISA = qw(Error); + +sub new { + my $self = shift; + my $text = "" . shift; + my $value = shift; + my(@args) = (); + + local $Error::Depth = $Error::Depth + 1; + + @args = ( -file => $1, -line => $2) + if($text =~ s/\s+at\s+(\S+)\s+line\s+(\d+)(?:,\s*<[^>]*>\s+line\s+\d+)?\.?\n?$//s); + push(@args, '-value', 0 + $value) + if defined($value); + + $self->SUPER::new(-text => $text, @args); +} + +sub stringify { + my $self = shift; + my $text = $self->SUPER::stringify; + $text .= sprintf(" at %s line %d.\n", $self->file, $self->line) + unless($text =~ /\n$/s); + $text; +} + +########################################################################## +########################################################################## + +# Inspired by code from Jesse Glick <jglick@sig.bsh.com> and +# Peter Seibel <peter@weblogic.com> + +package Error::subs; + +use Exporter (); +use vars qw(@EXPORT_OK @ISA %EXPORT_TAGS); + +@EXPORT_OK = qw(try with finally except otherwise); +%EXPORT_TAGS = (try => \@EXPORT_OK); + +@ISA = qw(Exporter); + + +sub blessed { + my $item = shift; + local $@; # don't kill an outer $@ + ref $item and eval { $item->can('can') }; +} + + +sub run_clauses ($$$\@) { + my($clauses,$err,$wantarray,$result) = @_; + my $code = undef; + + $err = $Error::ObjectifyCallback->({'text' =>$err}) unless ref($err); + + CATCH: { + + # catch + my $catch; + if(defined($catch = $clauses->{'catch'})) { + my $i = 0; + + CATCHLOOP: + for( ; $i < @$catch ; $i += 2) { + my $pkg = $catch->[$i]; + unless(defined $pkg) { + #except + splice(@$catch,$i,2,$catch->[$i+1]->()); + $i -= 2; + next CATCHLOOP; + } + elsif(blessed($err) && $err->isa($pkg)) { + $code = $catch->[$i+1]; + while(1) { + my $more = 0; + local($Error::THROWN); + my $ok = eval { + if($wantarray) { + @{$result} = $code->($err,\$more); + } + elsif(defined($wantarray)) { + @{$result} = (); + $result->[0] = $code->($err,\$more); + } + else { + $code->($err,\$more); + } + 1; + }; + if( $ok ) { + next CATCHLOOP if $more; + undef $err; + } + else { + $err = defined($Error::THROWN) + ? $Error::THROWN : $@; + $err = $Error::ObjectifyCallback->({'text' =>$err}) + unless ref($err); + } + last CATCH; + }; + } + } + } + + # otherwise + my $owise; + if(defined($owise = $clauses->{'otherwise'})) { + my $code = $clauses->{'otherwise'}; + my $more = 0; + my $ok = eval { + if($wantarray) { + @{$result} = $code->($err,\$more); + } + elsif(defined($wantarray)) { + @{$result} = (); + $result->[0] = $code->($err,\$more); + } + else { + $code->($err,\$more); + } + 1; + }; + if( $ok ) { + undef $err; + } + else { + $err = defined($Error::THROWN) + ? $Error::THROWN : $@; + + $err = $Error::ObjectifyCallback->({'text' =>$err}) + unless ref($err); + } + } + } + $err; +} + +sub try (&;$) { + my $try = shift; + my $clauses = @_ ? shift : {}; + my $ok = 0; + my $err = undef; + my @result = (); + + unshift @Error::STACK, $clauses; + + my $wantarray = wantarray(); + + do { + local $Error::THROWN = undef; + local $@ = undef; + + $ok = eval { + if($wantarray) { + @result = $try->(); + } + elsif(defined $wantarray) { + $result[0] = $try->(); + } + else { + $try->(); + } + 1; + }; + + $err = defined($Error::THROWN) ? $Error::THROWN : $@ + unless $ok; + }; + + shift @Error::STACK; + + $err = run_clauses($clauses,$err,wantarray,@result) + unless($ok); + + $clauses->{'finally'}->() + if(defined($clauses->{'finally'})); + + if (defined($err)) + { + if (blessed($err) && $err->can('throw')) + { + throw $err; + } + else + { + die $err; + } + } + + wantarray ? @result : $result[0]; +} + +# Each clause adds a sub to the list of clauses. The finally clause is +# always the last, and the otherwise clause is always added just before +# the finally clause. +# +# All clauses, except the finally clause, add a sub which takes one argument +# this argument will be the error being thrown. The sub will return a code ref +# if that clause can handle that error, otherwise undef is returned. +# +# The otherwise clause adds a sub which unconditionally returns the users +# code reference, this is why it is forced to be last. +# +# The catch clause is defined in Error.pm, as the syntax causes it to +# be called as a method + +sub with (&;$) { + @_ +} + +sub finally (&) { + my $code = shift; + my $clauses = { 'finally' => $code }; + $clauses; +} + +# The except clause is a block which returns a hashref or a list of +# key-value pairs, where the keys are the classes and the values are subs. + +sub except (&;$) { + my $code = shift; + my $clauses = shift || {}; + my $catch = $clauses->{'catch'} ||= []; + + my $sub = sub { + my $ref; + my(@array) = $code->($_[0]); + if(@array == 1 && ref($array[0])) { + $ref = $array[0]; + $ref = [ %$ref ] + if(UNIVERSAL::isa($ref,'HASH')); + } + else { + $ref = \@array; + } + @$ref + }; + + unshift @{$catch}, undef, $sub; + + $clauses; +} + +sub otherwise (&;$) { + my $code = shift; + my $clauses = shift || {}; + + if(exists $clauses->{'otherwise'}) { + require Carp; + Carp::croak("Multiple otherwise clauses"); + } + + $clauses->{'otherwise'} = $code; + + $clauses; +} + +1; +__END__ + +=head1 NAME + +Error - Error/exception handling in an OO-ish way + +=head1 SYNOPSIS + + use Error qw(:try); + + throw Error::Simple( "A simple error"); + + sub xyz { + ... + record Error::Simple("A simple error") + and return; + } + + unlink($file) or throw Error::Simple("$file: $!",$!); + + try { + do_some_stuff(); + die "error!" if $condition; + throw Error::Simple -text => "Oops!" if $other_condition; + } + catch Error::IO with { + my $E = shift; + print STDERR "File ", $E->{'-file'}, " had a problem\n"; + } + except { + my $E = shift; + my $general_handler=sub {send_message $E->{-description}}; + return { + UserException1 => $general_handler, + UserException2 => $general_handler + }; + } + otherwise { + print STDERR "Well I don't know what to say\n"; + } + finally { + close_the_garage_door_already(); # Should be reliable + }; # Don't forget the trailing ; or you might be surprised + +=head1 DESCRIPTION + +The C<Error> package provides two interfaces. Firstly C<Error> provides +a procedural interface to exception handling. Secondly C<Error> is a +base class for errors/exceptions that can either be thrown, for +subsequent catch, or can simply be recorded. + +Errors in the class C<Error> should not be thrown directly, but the +user should throw errors from a sub-class of C<Error>. + +=head1 PROCEDURAL INTERFACE + +C<Error> exports subroutines to perform exception handling. These will +be exported if the C<:try> tag is used in the C<use> line. + +=over 4 + +=item try BLOCK CLAUSES + +C<try> is the main subroutine called by the user. All other subroutines +exported are clauses to the try subroutine. + +The BLOCK will be evaluated and, if no error is throw, try will return +the result of the block. + +C<CLAUSES> are the subroutines below, which describe what to do in the +event of an error being thrown within BLOCK. + +=item catch CLASS with BLOCK + +This clauses will cause all errors that satisfy C<$err-E<gt>isa(CLASS)> +to be caught and handled by evaluating C<BLOCK>. + +C<BLOCK> will be passed two arguments. The first will be the error +being thrown. The second is a reference to a scalar variable. If this +variable is set by the catch block then, on return from the catch +block, try will continue processing as if the catch block was never +found. + +To propagate the error the catch block may call C<$err-E<gt>throw> + +If the scalar reference by the second argument is not set, and the +error is not thrown. Then the current try block will return with the +result from the catch block. + +=item except BLOCK + +When C<try> is looking for a handler, if an except clause is found +C<BLOCK> is evaluated. The return value from this block should be a +HASHREF or a list of key-value pairs, where the keys are class names +and the values are CODE references for the handler of errors of that +type. + +=item otherwise BLOCK + +Catch any error by executing the code in C<BLOCK> + +When evaluated C<BLOCK> will be passed one argument, which will be the +error being processed. + +Only one otherwise block may be specified per try block + +=item finally BLOCK + +Execute the code in C<BLOCK> either after the code in the try block has +successfully completed, or if the try block throws an error then +C<BLOCK> will be executed after the handler has completed. + +If the handler throws an error then the error will be caught, the +finally block will be executed and the error will be re-thrown. + +Only one finally block may be specified per try block + +=back + +=head1 CLASS INTERFACE + +=head2 CONSTRUCTORS + +The C<Error> object is implemented as a HASH. This HASH is initialized +with the arguments that are passed to it's constructor. The elements +that are used by, or are retrievable by the C<Error> class are listed +below, other classes may add to these. + + -file + -line + -text + -value + -object + +If C<-file> or C<-line> are not specified in the constructor arguments +then these will be initialized with the file name and line number where +the constructor was called from. + +If the error is associated with an object then the object should be +passed as the C<-object> argument. This will allow the C<Error> package +to associate the error with the object. + +The C<Error> package remembers the last error created, and also the +last error associated with a package. This could either be the last +error created by a sub in that package, or the last error which passed +an object blessed into that package as the C<-object> argument. + +=over 4 + +=item throw ( [ ARGS ] ) + +Create a new C<Error> object and throw an error, which will be caught +by a surrounding C<try> block, if there is one. Otherwise it will cause +the program to exit. + +C<throw> may also be called on an existing error to re-throw it. + +=item with ( [ ARGS ] ) + +Create a new C<Error> object and returns it. This is defined for +syntactic sugar, eg + + die with Some::Error ( ... ); + +=item record ( [ ARGS ] ) + +Create a new C<Error> object and returns it. This is defined for +syntactic sugar, eg + + record Some::Error ( ... ) + and return; + +=back + +=head2 STATIC METHODS + +=over 4 + +=item prior ( [ PACKAGE ] ) + +Return the last error created, or the last error associated with +C<PACKAGE> + +=item flush ( [ PACKAGE ] ) + +Flush the last error created, or the last error associated with +C<PACKAGE>.It is necessary to clear the error stack before exiting the +package or uncaught errors generated using C<record> will be reported. + + $Error->flush; + +=cut + +=back + +=head2 OBJECT METHODS + +=over 4 + +=item stacktrace + +If the variable C<$Error::Debug> was non-zero when the error was +created, then C<stacktrace> returns a string created by calling +C<Carp::longmess>. If the variable was zero the C<stacktrace> returns +the text of the error appended with the filename and line number of +where the error was created, providing the text does not end with a +newline. + +=item object + +The object this error was associated with + +=item file + +The file where the constructor of this error was called from + +=item line + +The line where the constructor of this error was called from + +=item text + +The text of the error + +=back + +=head2 OVERLOAD METHODS + +=over 4 + +=item stringify + +A method that converts the object into a string. This method may simply +return the same as the C<text> method, or it may append more +information. For example the file name and line number. + +By default this method returns the C<-text> argument that was passed to +the constructor, or the string C<"Died"> if none was given. + +=item value + +A method that will return a value that can be associated with the +error. For example if an error was created due to the failure of a +system call, then this may return the numeric value of C<$!> at the +time. + +By default this method returns the C<-value> argument that was passed +to the constructor. + +=back + +=head1 PRE-DEFINED ERROR CLASSES + +=over 4 + +=item Error::Simple + +This class can be used to hold simple error strings and values. It's +constructor takes two arguments. The first is a text value, the second +is a numeric value. These values are what will be returned by the +overload methods. + +If the text value ends with C<at file line 1> as $@ strings do, then +this infomation will be used to set the C<-file> and C<-line> arguments +of the error object. + +This class is used internally if an eval'd block die's with an error +that is a plain string. (Unless C<$Error::ObjectifyCallback> is modified) + +=back + +=head1 $Error::ObjectifyCallback + +This variable holds a reference to a subroutine that converts errors that +are plain strings to objects. It is used by Error.pm to convert textual +errors to objects, and can be overrided by the user. + +It accepts a single argument which is a hash reference to named parameters. +Currently the only named parameter passed is C<'text'> which is the text +of the error, but others may be available in the future. + +For example the following code will cause Error.pm to throw objects of the +class MyError::Bar by default: + + sub throw_MyError_Bar + { + my $args = shift; + my $err = MyError::Bar->new(); + $err->{'MyBarText'} = $args->{'text'}; + return $err; + } + + { + local $Error::ObjectifyCallback = \&throw_MyError_Bar; + + # Error handling here. + } + +=head1 KNOWN BUGS + +None, but that does not mean there are not any. + +=head1 AUTHORS + +Graham Barr <gbarr@pobox.com> + +The code that inspired me to write this was originally written by +Peter Seibel <peter@weblogic.com> and adapted by Jesse Glick +<jglick@sig.bsh.com>. + +=head1 MAINTAINER + +Shlomi Fish <shlomif@iglu.org.il> + +=head1 PAST MAINTAINERS + +Arun Kumar U <u_arunkumar@yahoo.com> + +=cut @@ -74,6 +74,67 @@ char *sq_quote(const char *src) return buf; } +char *sq_quote_argv(const char** argv, int count) +{ + char *buf, *to; + int i; + size_t len = 0; + + /* Count argv if needed. */ + if (count < 0) { + for (count = 0; argv[count]; count++) + ; /* just counting */ + } + + /* Special case: no argv. */ + if (!count) + return xcalloc(1,1); + + /* Get destination buffer length. */ + for (i = 0; i < count; i++) + len += sq_quote_buf(NULL, 0, argv[i]) + 1; + + /* Alloc destination buffer. */ + to = buf = xmalloc(len + 1); + + /* Copy into destination buffer. */ + for (i = 0; i < count; ++i) { + *to++ = ' '; + to += sq_quote_buf(to, len, argv[i]); + } + + return buf; +} + +/* + * Append a string to a string buffer, with or without shell quoting. + * Return true if the buffer overflowed. + */ +int add_to_string(char **ptrp, int *sizep, const char *str, int quote) +{ + char *p = *ptrp; + int size = *sizep; + int oc; + int err = 0; + + if (quote) + oc = sq_quote_buf(p, size, str); + else { + oc = strlen(str); + memcpy(p, str, (size <= oc) ? size - 1 : oc); + } + + if (size <= oc) { + err = 1; + oc = size - 1; + } + + *ptrp += oc; + **ptrp = '\0'; + *sizep -= oc; + return err; +} + char *sq_dequote(char *arg) { char *dst = arg; @@ -31,6 +31,13 @@ extern char *sq_quote(const char *src); extern void sq_quote_print(FILE *stream, const char *src); extern size_t sq_quote_buf(char *dst, size_t n, const char *src); +extern char *sq_quote_argv(const char** argv, int count); + +/* + * Append a string to a string buffer, with or without shell quoting. + * Return true if the buffer overflowed. + */ +extern int add_to_string(char **ptrp, int *sizep, const char *str, int quote); /* This unwraps what sq_quote() produces in place, but returns * NULL if the input does not look like what sq_quote would have diff --git a/read-cache.c b/read-cache.c index f92cdaacee..97c38670b4 100644 --- a/read-cache.c +++ b/read-cache.c @@ -18,16 +18,16 @@ #define CACHE_EXT(s) ( (s[0]<<24)|(s[1]<<16)|(s[2]<<8)|(s[3]) ) #define CACHE_EXT_TREE 0x54524545 /* "TREE" */ -struct cache_entry **active_cache = NULL; +struct cache_entry **active_cache; static time_t index_file_timestamp; -unsigned int active_nr = 0, active_alloc = 0, active_cache_changed = 0; +unsigned int active_nr, active_alloc, active_cache_changed; -struct cache_tree *active_cache_tree = NULL; +struct cache_tree *active_cache_tree; -int cache_errno = 0; +int cache_errno; -static void *cache_mmap = NULL; -static size_t cache_mmap_size = 0; +static void *cache_mmap; +static size_t cache_mmap_size; /* * This only updates the "non-critical" parts of the directory @@ -60,7 +60,7 @@ static int ce_compare_data(struct cache_entry *ce, struct stat *st) if (fd >= 0) { unsigned char sha1[20]; if (!index_fd(sha1, fd, st, 0, NULL)) - match = memcmp(sha1, ce->sha1, 20); + match = hashcmp(sha1, ce->sha1); /* index_fd() closed the file descriptor already */ } return match; @@ -169,9 +169,11 @@ static int ce_match_stat_basic(struct cache_entry *ce, struct stat *st) return changed; } -int ce_match_stat(struct cache_entry *ce, struct stat *st, int ignore_valid) +int ce_match_stat(struct cache_entry *ce, struct stat *st, int options) { unsigned int changed; + int ignore_valid = options & 01; + int assume_racy_is_modified = options & 02; /* * If it's marked as always valid in the index, it's @@ -200,8 +202,12 @@ int ce_match_stat(struct cache_entry *ce, struct stat *st, int ignore_valid) */ if (!changed && index_file_timestamp && - index_file_timestamp <= ntohl(ce->ce_mtime.sec)) - changed |= ce_modified_check_fs(ce, st); + index_file_timestamp <= ntohl(ce->ce_mtime.sec)) { + if (assume_racy_is_modified) + changed |= DATA_CHANGED; + else + changed |= ce_modified_check_fs(ce, st); + } return changed; } @@ -341,11 +347,13 @@ int add_file_to_index(const char *path, int verbose) ce->ce_mode = create_ce_mode(st.st_mode); if (!trust_executable_bit) { /* If there is an existing entry, pick the mode bits - * from it. + * from it, otherwise force to 644. */ int pos = cache_name_pos(path, namelen); if (pos >= 0) 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, 1)) @@ -738,7 +746,7 @@ static int verify_hdr(struct cache_header *hdr, unsigned long size) SHA1_Init(&c); SHA1_Update(&c, hdr, size - 20); SHA1_Final(sha1, &c); - if (memcmp(sha1, (char *) hdr + size - 20, 20)) + if (hashcmp(sha1, (unsigned char *)hdr + size - 20)) return error("bad index file sha1 signature"); return 0; } @@ -836,10 +844,39 @@ unmap: die("index file corrupt"); } +int discard_cache() +{ + int ret; + + active_nr = active_cache_changed = 0; + index_file_timestamp = 0; + cache_tree_free(&active_cache_tree); + if (cache_mmap == NULL) + return 0; + ret = munmap(cache_mmap, cache_mmap_size); + cache_mmap = NULL; + cache_mmap_size = 0; + + /* no need to throw away allocated active_cache */ + return ret; +} + #define WRITE_BUFFER_SIZE 8192 static unsigned char write_buffer[WRITE_BUFFER_SIZE]; static unsigned long write_buffer_len; +static int ce_write_flush(SHA_CTX *context, int fd) +{ + unsigned int buffered = write_buffer_len; + if (buffered) { + SHA1_Update(context, write_buffer, buffered); + if (write(fd, write_buffer, buffered) != buffered) + return -1; + write_buffer_len = 0; + } + return 0; +} + static int ce_write(SHA_CTX *context, int fd, void *data, unsigned int len) { while (len) { @@ -850,8 +887,8 @@ static int ce_write(SHA_CTX *context, int fd, void *data, unsigned int len) memcpy(write_buffer + buffered, data, partial); buffered += partial; if (buffered == WRITE_BUFFER_SIZE) { - SHA1_Update(context, write_buffer, WRITE_BUFFER_SIZE); - if (write(fd, write_buffer, WRITE_BUFFER_SIZE) != WRITE_BUFFER_SIZE) + write_buffer_len = buffered; + if (ce_write_flush(context, fd)) return -1; buffered = 0; } @@ -867,10 +904,8 @@ static int write_index_ext_header(SHA_CTX *context, int fd, { ext = htonl(ext); sz = htonl(sz); - if ((ce_write(context, fd, &ext, 4) < 0) || - (ce_write(context, fd, &sz, 4) < 0)) - return -1; - return 0; + return ((ce_write(context, fd, &ext, 4) < 0) || + (ce_write(context, fd, &sz, 4) < 0)) ? -1 : 0; } static int ce_flush(SHA_CTX *context, int fd) @@ -892,9 +927,7 @@ static int ce_flush(SHA_CTX *context, int fd) /* Append the SHA1 signature at the end */ SHA1_Final(write_buffer + left, context); left += 20; - if (write(fd, write_buffer, left) != left) - return -1; - return 0; + return (write(fd, write_buffer, left) != left) ? -1 : 0; } static void ce_smudge_racily_clean_entry(struct cache_entry *ce) @@ -923,7 +956,7 @@ static void ce_smudge_racily_clean_entry(struct cache_entry *ce) * $ echo filfre >nitfol * $ git-update-index --add nitfol * - * but it does not. Whe the second update-index runs, + * but it does not. When the second update-index runs, * it notices that the entry "frotz" has the same timestamp * as index, and if we were to smudge it by resetting its * size to zero here, then the object name recorded diff --git a/receive-pack.c b/receive-pack.c index 93929b5371..ea2dbd4e33 100644 --- a/receive-pack.c +++ b/receive-pack.c @@ -2,16 +2,17 @@ #include "refs.h" #include "pkt-line.h" #include "run-command.h" -#include <sys/wait.h> +#include "commit.h" +#include "object.h" static const char receive_pack_usage[] = "git-receive-pack <git-dir>"; static const char *unpacker[] = { "unpack-objects", NULL }; -static int report_status = 0; +static int report_status; static char capabilities[] = "report-status"; -static int capabilities_sent = 0; +static int capabilities_sent; static int show_ref(const char *path, const unsigned char *sha1) { @@ -40,7 +41,7 @@ struct command { char ref_name[FLEX_ARRAY]; /* more */ }; -static struct command *commands = NULL; +static struct command *commands; static int is_all_zeroes(const char *hex) { @@ -128,6 +129,21 @@ static int update(struct command *cmd) return error("unpack should have generated %s, " "but I can't find it!", new_hex); } + if (deny_non_fast_forwards && !is_null_sha1(old_sha1)) { + struct commit *old_commit, *new_commit; + struct commit_list *bases, *ent; + + old_commit = (struct commit *)parse_object(old_sha1); + new_commit = (struct commit *)parse_object(new_sha1); + bases = get_merge_bases(old_commit, new_commit, 1); + for (ent = bases; ent; ent = ent->next) + if (!hashcmp(old_sha1, ent->item->object.sha1)) + break; + free_commit_list(bases); + if (!ent) + return error("denying non-fast forward;" + " you should pull first"); + } safe_create_leading_directories(lock_name); newfd = open(lock_name, O_CREAT | O_EXCL | O_WRONLY, 0666); @@ -247,8 +263,8 @@ static void read_head_info(void) report_status = 1; } cmd = xmalloc(sizeof(struct command) + len - 80); - memcpy(cmd->old_sha1, old_sha1, 20); - memcpy(cmd->new_sha1, new_sha1, 20); + hashcpy(cmd->old_sha1, old_sha1); + hashcpy(cmd->new_sha1, new_sha1); memcpy(cmd->ref_name, line + 82, len - 81); cmd->error_string = "n/a (unpacker error)"; cmd->next = NULL; @@ -29,7 +29,7 @@ const char *resolve_ref(const char *path, unsigned char *sha1, int reading) if (lstat(path, &st) < 0) { if (reading || errno != ENOENT) return NULL; - memset(sha1, 0, 20); + hashclr(sha1); return path; } @@ -42,6 +42,12 @@ const char *resolve_ref(const char *path, unsigned char *sha1, int reading) } } + /* Is it a directory? */ + if (S_ISDIR(st.st_mode)) { + errno = EISDIR; + return NULL; + } + /* * Anything else, just open it and try to use it as * a ref @@ -281,7 +287,7 @@ static struct ref_lock *verify_lock(struct ref_lock *lock, unlock_ref(lock); return NULL; } - if (memcmp(lock->old_sha1, old_sha1, 20)) { + if (hashcmp(lock->old_sha1, old_sha1)) { error("Ref %s is at %s but expected %s", lock->ref_file, sha1_to_hex(lock->old_sha1), sha1_to_hex(old_sha1)); unlock_ref(lock); @@ -313,8 +319,8 @@ static struct ref_lock *lock_ref_sha1_basic(const char *path, } lock->lk = xcalloc(1, sizeof(struct lock_file)); - lock->ref_file = strdup(path); - lock->log_file = strdup(git_path("logs/%s", lock->ref_file + plen)); + lock->ref_file = xstrdup(path); + lock->log_file = xstrdup(git_path("logs/%s", lock->ref_file + plen)); lock->force_write = lstat(lock->ref_file, &st) && errno == ENOENT; if (safe_create_leading_directories(lock->ref_file)) @@ -348,10 +354,8 @@ void unlock_ref(struct ref_lock *lock) if (lock->lk) rollback_lock_file(lock->lk); } - if (lock->ref_file) - free(lock->ref_file); - if (lock->log_file) - free(lock->log_file); + free(lock->ref_file); + free(lock->log_file); free(lock); } @@ -411,7 +415,7 @@ int write_ref_sha1(struct ref_lock *lock, if (!lock) return -1; - if (!lock->force_write && !memcmp(lock->old_sha1, sha1, 20)) { + if (!lock->force_write && !hashcmp(lock->old_sha1, sha1)) { unlock_ref(lock); return 0; } @@ -475,7 +479,7 @@ int read_ref_at(const char *ref, unsigned long at_time, unsigned char *sha1) die("Log %s is corrupt.", logfile); if (get_sha1_hex(rec + 41, sha1)) die("Log %s is corrupt.", logfile); - if (memcmp(logged_sha1, sha1, 20)) { + if (hashcmp(logged_sha1, sha1)) { tz = strtoul(tz_c, NULL, 10); fprintf(stderr, "warning: Log %s has gap after %s.\n", @@ -489,7 +493,7 @@ int read_ref_at(const char *ref, unsigned long at_time, unsigned char *sha1) else { if (get_sha1_hex(rec + 41, logged_sha1)) die("Log %s is corrupt.", logfile); - if (memcmp(logged_sha1, sha1, 20)) { + if (hashcmp(logged_sha1, sha1)) { tz = strtoul(tz_c, NULL, 10); fprintf(stderr, "warning: Log %s unexpectedly ended on %s.\n", diff --git a/revision.c b/revision.c index 5a91d06b98..93f25130a0 100644 --- a/revision.c +++ b/revision.c @@ -6,6 +6,8 @@ #include "diff.h" #include "refs.h" #include "revision.h" +#include <regex.h> +#include "grep.h" static char *path_name(struct name_path *path, const char *name) { @@ -416,7 +418,8 @@ static void limit_list(struct rev_info *revs) if (revs->max_age != -1 && (commit->date < revs->max_age)) obj->flags |= UNINTERESTING; - if (revs->unpacked && has_sha1_pack(obj->sha1)) + if (revs->unpacked && + has_sha1_pack(obj->sha1, revs->ignore_packed)) obj->flags |= UNINTERESTING; add_parents_to_list(revs, commit, &list); if (obj->flags & UNINTERESTING) { @@ -496,7 +499,7 @@ static int add_parents_only(struct rev_info *revs, const char *arg, int flags) it = get_reference(revs, arg, sha1, 0); if (it->type != OBJ_TAG) break; - memcpy(sha1, ((struct tag*)it)->tagged->sha1, 20); + hashcpy(sha1, ((struct tag*)it)->tagged->sha1); } if (it->type != OBJ_COMMIT) return 0; @@ -592,6 +595,131 @@ static void prepare_show_merge(struct rev_info *revs) revs->prune_data = prune; } +int handle_revision_arg(const char *arg, struct rev_info *revs, + int flags, + int cant_be_filename) +{ + char *dotdot; + struct object *object; + unsigned char sha1[20]; + int local_flags; + + dotdot = strstr(arg, ".."); + if (dotdot) { + unsigned char from_sha1[20]; + const char *next = dotdot + 2; + const char *this = arg; + int symmetric = *next == '.'; + unsigned int flags_exclude = flags ^ UNINTERESTING; + + *dotdot = 0; + next += symmetric; + + if (!*next) + next = "HEAD"; + if (dotdot == arg) + this = "HEAD"; + if (!get_sha1(this, from_sha1) && + !get_sha1(next, sha1)) { + struct commit *a, *b; + struct commit_list *exclude; + + a = lookup_commit_reference(from_sha1); + b = lookup_commit_reference(sha1); + if (!a || !b) { + die(symmetric ? + "Invalid symmetric difference expression %s...%s" : + "Invalid revision range %s..%s", + arg, next); + } + + if (!cant_be_filename) { + *dotdot = '.'; + verify_non_filename(revs->prefix, arg); + } + + if (symmetric) { + exclude = get_merge_bases(a, b, 1); + add_pending_commit_list(revs, exclude, + flags_exclude); + free_commit_list(exclude); + a->object.flags |= flags; + } else + a->object.flags |= flags_exclude; + b->object.flags |= flags; + add_pending_object(revs, &a->object, this); + add_pending_object(revs, &b->object, next); + return 0; + } + *dotdot = '.'; + } + dotdot = strstr(arg, "^@"); + if (dotdot && !dotdot[2]) { + *dotdot = 0; + if (add_parents_only(revs, arg, flags)) + return 0; + *dotdot = '^'; + } + local_flags = 0; + if (*arg == '^') { + local_flags = UNINTERESTING; + arg++; + } + if (get_sha1(arg, sha1)) + return -1; + if (!cant_be_filename) + verify_non_filename(revs->prefix, arg); + object = get_reference(revs, arg, sha1, flags ^ local_flags); + add_pending_object(revs, object, arg); + return 0; +} + +static void add_grep(struct rev_info *revs, const char *ptn, enum grep_pat_token what) +{ + if (!revs->grep_filter) { + struct grep_opt *opt = xcalloc(1, sizeof(*opt)); + opt->status_only = 1; + opt->pattern_tail = &(opt->pattern_list); + opt->regflags = REG_NEWLINE; + revs->grep_filter = opt; + } + append_grep_pattern(revs->grep_filter, ptn, + "command line", 0, what); +} + +static void add_header_grep(struct rev_info *revs, const char *field, const char *pattern) +{ + char *pat; + const char *prefix; + int patlen, fldlen; + + fldlen = strlen(field); + patlen = strlen(pattern); + pat = xmalloc(patlen + fldlen + 10); + prefix = ".*"; + if (*pattern == '^') { + prefix = ""; + pattern++; + } + sprintf(pat, "^%s %s%s", field, prefix, pattern); + add_grep(revs, pat, GREP_PATTERN_HEAD); +} + +static void add_message_grep(struct rev_info *revs, const char *pattern) +{ + add_grep(revs, pattern, GREP_PATTERN_BODY); +} + +static void add_ignore_packed(struct rev_info *revs, const char *name) +{ + int num = ++revs->num_ignore_packed; + + revs->ignore_packed = xrealloc(revs->ignore_packed, + sizeof(const char **) * (num + 1)); + revs->ignore_packed[num-1] = name; + revs->ignore_packed[num] = NULL; +} + /* * Parse revision information, filling in the "rev_info" structure, * and removing the used arguments from the argument list. @@ -620,12 +748,7 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch flags = show_merge = 0; for (i = 1; i < argc; i++) { - struct object *object; const char *arg = argv[i]; - unsigned char sha1[20]; - char *dotdot; - int local_flags; - if (*arg == '-') { int opts; if (!strncmp(arg, "--max-count=", 12)) { @@ -737,6 +860,14 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch } if (!strcmp(arg, "--unpacked")) { revs->unpacked = 1; + free(revs->ignore_packed); + revs->ignore_packed = NULL; + revs->num_ignore_packed = 0; + continue; + } + if (!strncmp(arg, "--unpacked=", 11)) { + revs->unpacked = 1; + add_ignore_packed(revs, arg+11); continue; } if (!strcmp(arg, "-r")) { @@ -816,6 +947,27 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch revs->simplify_history = 0; continue; } + if (!strcmp(arg, "--relative-date")) { + revs->relative_date = 1; + continue; + } + + /* + * Grepping the commit log + */ + if (!strncmp(arg, "--author=", 9)) { + add_header_grep(revs, "author", arg+9); + continue; + } + if (!strncmp(arg, "--committer=", 12)) { + add_header_grep(revs, "committer", arg+12); + continue; + } + if (!strncmp(arg, "--grep=", 7)) { + add_message_grep(revs, arg+7); + continue; + } + opts = diff_opt_parse(&revs->diffopt, argv+i, argc-i); if (opts > 0) { revs->diff = 1; @@ -826,71 +978,10 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch left++; continue; } - dotdot = strstr(arg, ".."); - if (dotdot) { - unsigned char from_sha1[20]; - const char *next = dotdot + 2; - const char *this = arg; - int symmetric = *next == '.'; - unsigned int flags_exclude = flags ^ UNINTERESTING; - - *dotdot = 0; - next += symmetric; - - if (!*next) - next = "HEAD"; - if (dotdot == arg) - this = "HEAD"; - if (!get_sha1(this, from_sha1) && - !get_sha1(next, sha1)) { - struct commit *a, *b; - struct commit_list *exclude; - - a = lookup_commit_reference(from_sha1); - b = lookup_commit_reference(sha1); - if (!a || !b) { - die(symmetric ? - "Invalid symmetric difference expression %s...%s" : - "Invalid revision range %s..%s", - arg, next); - } - - if (!seen_dashdash) { - *dotdot = '.'; - verify_non_filename(revs->prefix, arg); - } - - if (symmetric) { - exclude = get_merge_bases(a, b, 1); - add_pending_commit_list(revs, exclude, - flags_exclude); - free_commit_list(exclude); - a->object.flags |= flags; - } else - a->object.flags |= flags_exclude; - b->object.flags |= flags; - add_pending_object(revs, &a->object, this); - add_pending_object(revs, &b->object, next); - continue; - } - *dotdot = '.'; - } - dotdot = strstr(arg, "^@"); - if (dotdot && !dotdot[2]) { - *dotdot = 0; - if (add_parents_only(revs, arg, flags)) - continue; - *dotdot = '^'; - } - local_flags = 0; - if (*arg == '^') { - local_flags = UNINTERESTING; - arg++; - } - if (get_sha1(arg, sha1)) { - int j; - if (seen_dashdash || local_flags) + if (handle_revision_arg(arg, revs, flags, seen_dashdash)) { + int j; + if (seen_dashdash || *arg == '^') die("bad revision '%s'", arg); /* If we didn't have a "--": @@ -902,14 +993,12 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch for (j = i; j < argc; j++) verify_filename(revs->prefix, argv[j]); - revs->prune_data = get_pathspec(revs->prefix, argv + i); + revs->prune_data = get_pathspec(revs->prefix, + argv + i); break; } - if (!seen_dashdash) - verify_non_filename(revs->prefix, arg); - object = get_reference(revs, arg, sha1, flags ^ local_flags); - add_pending_object(revs, object, arg); } + if (show_merge) prepare_show_merge(revs); if (def && !revs->pending.nr) { @@ -939,6 +1028,9 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch if (diff_setup_done(&revs->diffopt) < 0) die("diff_setup_done failed"); + if (revs->grep_filter) + compile_grep_patterns(revs->grep_filter); + return left; } @@ -1011,6 +1103,15 @@ static void mark_boundary_to_show(struct commit *commit) } } +static int commit_match(struct commit *commit, struct rev_info *opt) +{ + if (!opt->grep_filter) + return 1; + return grep_buffer(opt->grep_filter, + NULL, /* we say nothing, not even filename */ + commit->buffer, strlen(commit->buffer)); +} + struct commit *get_revision(struct rev_info *revs) { struct commit_list *list = revs->commits; @@ -1042,7 +1143,8 @@ struct commit *get_revision(struct rev_info *revs) */ if (!revs->limited) { if ((revs->unpacked && - has_sha1_pack(commit->object.sha1)) || + has_sha1_pack(commit->object.sha1, + revs->ignore_packed)) || (revs->max_age != -1 && (commit->date < revs->max_age))) continue; @@ -1070,6 +1172,8 @@ struct commit *get_revision(struct rev_info *revs) if (revs->no_merges && commit->parents && commit->parents->next) continue; + if (!commit_match(commit, revs)) + continue; if (revs->prune_fn && revs->dense) { /* Commit without changes? */ if (!(commit->object.flags & TREECHANGE)) { diff --git a/revision.h b/revision.h index 0c3b8d9905..3adab9590a 100644 --- a/revision.h +++ b/revision.h @@ -38,7 +38,7 @@ struct rev_info { blob_objects:1, edge_hint:1, limited:1, - unpacked:1, + unpacked:1, /* see also ignore_packed below */ boundary:1, parents:1; @@ -55,7 +55,12 @@ struct rev_info { /* Format info */ unsigned int shown_one:1, - abbrev_commit:1; + abbrev_commit:1, + relative_date:1; + + const char **ignore_packed; /* pretend objects in these are unpacked */ + int num_ignore_packed; + unsigned int abbrev; enum cmit_fmt commit_format; struct log_info *loginfo; @@ -66,6 +71,9 @@ struct rev_info { const char *add_signoff; const char *extra_headers; + /* Filter by commit log message */ + struct grep_opt *grep_filter; + /* special limits */ int max_count; unsigned long max_age; @@ -89,6 +97,8 @@ extern int rev_compare_tree(struct rev_info *, struct tree *t1, struct tree *t2) extern void init_revisions(struct rev_info *revs, const char *prefix); extern int setup_revisions(int argc, const char **argv, struct rev_info *revs, const char *def); +extern int handle_revision_arg(const char *arg, struct rev_info *revs,int flags,int cant_be_filename); + extern void prepare_revision_walk(struct rev_info *revs); extern struct commit *get_revision(struct rev_info *revs); @@ -8,36 +8,7 @@ #define COMMAND_SIZE 4096 -/* - * Append a string to a string buffer, with or without shell quoting. - * Return true if the buffer overflowed. - */ -static int add_to_string(char **ptrp, int *sizep, const char *str, int quote) -{ - char *p = *ptrp; - int size = *sizep; - int oc; - int err = 0; - - if ( quote ) { - oc = sq_quote_buf(p, size, str); - } else { - oc = strlen(str); - memcpy(p, str, (oc >= size) ? size-1 : oc); - } - - if ( oc >= size ) { - err = 1; - oc = size-1; - } - - *ptrp += oc; - **ptrp = '\0'; - *sizep -= oc; - return err; -} - -int setup_connection(int *fd_in, int *fd_out, const char *remote_prog, +int setup_connection(int *fd_in, int *fd_out, const char *remote_prog, char *url, int rmt_argc, char **rmt_argv) { char *host; diff --git a/run-command.c b/run-command.c index ca67ee9333..61908682b9 100644 --- a/run-command.c +++ b/run-command.c @@ -25,15 +25,15 @@ int run_command_v_opt(int argc, const char **argv, int flags) } for (;;) { int status, code; - int retval = waitpid(pid, &status, 0); + pid_t waiting = waitpid(pid, &status, 0); - if (retval < 0) { + if (waiting < 0) { if (errno == EINTR) continue; - error("waitpid failed (%s)", strerror(retval)); + error("waitpid failed (%s)", strerror(errno)); return -ERR_RUN_COMMAND_WAITPID; } - if (retval != pid) + if (waiting != pid) return -ERR_RUN_COMMAND_WAITPID_WRONG_PID; if (WIFSIGNALED(status)) return -ERR_RUN_COMMAND_WAITPID_SIGNAL; diff --git a/send-pack.c b/send-pack.c index 10bc8bc359..5bb123a376 100644 --- a/send-pack.c +++ b/send-pack.c @@ -9,10 +9,10 @@ static const char send_pack_usage[] = "git-send-pack [--all] [--exec=git-receive-pack] <remote> [<head>...]\n" " --all and explicit <head> specification are mutually exclusive."; static const char *exec = "git-receive-pack"; -static int verbose = 0; -static int send_all = 0; -static int force_update = 0; -static int use_thin_pack = 0; +static int verbose; +static int send_all; +static int force_update; +static int use_thin_pack; static int is_zero_sha1(const unsigned char *sha1) { @@ -38,9 +38,8 @@ static void exec_pack_objects(void) static void exec_rev_list(struct ref *refs) { - struct ref *ref; - static const char *args[1000]; - int i = 0, j; + static const char *args[4]; + int i = 0; args[i++] = "rev-list"; /* 0 */ if (use_thin_pack) /* 1 */ @@ -48,43 +47,16 @@ static void exec_rev_list(struct ref *refs) else args[i++] = "--objects"; - /* First send the ones we care about most */ - for (ref = refs; ref; ref = ref->next) { - if (900 < i) - die("git-rev-list environment overflow"); - if (!is_zero_sha1(ref->new_sha1)) { - char *buf = malloc(100); - args[i++] = buf; - snprintf(buf, 50, "%s", sha1_to_hex(ref->new_sha1)); - buf += 50; - if (!is_zero_sha1(ref->old_sha1) && - has_sha1_file(ref->old_sha1)) { - args[i++] = buf; - snprintf(buf, 50, "^%s", - sha1_to_hex(ref->old_sha1)); - } - } - } + args[i++] = "--stdin"; - /* Then a handful of the remainder - * NEEDSWORK: we would be better off if used the newer ones first. - */ - for (ref = refs, j = i + 16; - i < 900 && i < j && ref; - ref = ref->next) { - if (is_zero_sha1(ref->new_sha1) && - !is_zero_sha1(ref->old_sha1) && - has_sha1_file(ref->old_sha1)) { - char *buf = malloc(42); - args[i++] = buf; - snprintf(buf, 42, "^%s", sha1_to_hex(ref->old_sha1)); - } - } args[i] = NULL; execv_git_cmd(args); die("git-rev-list exec failed (%s)", strerror(errno)); } +/* + * Run "rev-list --stdin | pack-objects" pipe. + */ static void rev_list(int fd, struct ref *refs) { int pipe_fd[2]; @@ -94,6 +66,9 @@ static void rev_list(int fd, struct ref *refs) die("rev-list setup: pipe failed"); pack_objects_pid = fork(); if (!pack_objects_pid) { + /* The child becomes pack-objects; reads from pipe + * and writes to the original fd + */ dup2(pipe_fd[0], 0); dup2(fd, 1); close(pipe_fd[0]); @@ -104,6 +79,8 @@ static void rev_list(int fd, struct ref *refs) } if (pack_objects_pid < 0) die("pack-objects fork failed"); + + /* We become rev-list --stdin; output goes to pipe. */ dup2(pipe_fd[1], 1); close(pipe_fd[0]); close(pipe_fd[1]); @@ -111,13 +88,71 @@ static void rev_list(int fd, struct ref *refs) exec_rev_list(refs); } -static int pack_objects(int fd, struct ref *refs) +/* + * Create "rev-list --stdin | pack-objects" pipe and feed + * the refs into the pipeline. + */ +static void rev_list_generate(int fd, struct ref *refs) +{ + int pipe_fd[2]; + pid_t rev_list_generate_pid; + + if (pipe(pipe_fd) < 0) + die("rev-list-generate setup: pipe failed"); + rev_list_generate_pid = fork(); + if (!rev_list_generate_pid) { + /* The child becomes the "rev-list | pack-objects" + * pipeline. It takes input from us, and its output + * goes to fd. + */ + dup2(pipe_fd[0], 0); + dup2(fd, 1); + close(pipe_fd[0]); + close(pipe_fd[1]); + close(fd); + rev_list(fd, refs); + die("rev-list setup failed"); + } + if (rev_list_generate_pid < 0) + die("rev-list-generate fork failed"); + + /* We feed the rev parameters to them. We do not write into + * fd nor read from the pipe. + */ + close(pipe_fd[0]); + close(fd); + while (refs) { + char buf[42]; + + if (!is_null_sha1(refs->old_sha1) && + has_sha1_file(refs->old_sha1)) { + memcpy(buf + 1, sha1_to_hex(refs->old_sha1), 40); + buf[0] = '^'; + buf[41] = '\n'; + write(pipe_fd[1], buf, 42); + } + if (!is_null_sha1(refs->new_sha1)) { + memcpy(buf, sha1_to_hex(refs->new_sha1), 40); + buf[40] = '\n'; + write(pipe_fd[1], buf, 41); + } + refs = refs->next; + } + close(pipe_fd[1]); + // waitpid(rev_list_generate_pid); + exit(0); +} + +/* + * Make a pack stream and spit it out into file descriptor fd + */ +static void pack_objects(int fd, struct ref *refs) { pid_t rev_list_pid; rev_list_pid = fork(); if (!rev_list_pid) { - rev_list(fd, refs); + rev_list_generate(fd, refs); die("rev-list setup failed"); } if (rev_list_pid < 0) @@ -126,7 +161,6 @@ static int pack_objects(int fd, struct ref *refs) * We don't wait for the rev-list pipeline in the parent: * we end up waiting for the other end instead */ - return 0; } static void unmark_and_free(struct commit_list *list, unsigned int mark) @@ -186,7 +220,7 @@ static int one_local_ref(const char *refname, const unsigned char *sha1) struct ref *ref; int len = strlen(refname) + 1; ref = xcalloc(1, sizeof(*ref) + len); - memcpy(ref->new_sha1, sha1, 20); + hashcpy(ref->new_sha1, sha1); memcpy(ref->name, refname, len); *local_tail = ref; local_tail = &ref->next; @@ -266,7 +300,7 @@ static int send_pack(int in, int out, int nr_refspec, char **refspec) char old_hex[60], *new_hex; if (!ref->peer_ref) continue; - if (!memcmp(ref->old_sha1, ref->peer_ref->new_sha1, 20)) { + if (!hashcmp(ref->old_sha1, ref->peer_ref->new_sha1)) { if (verbose) fprintf(stderr, "'%s': up-to-date\n", ref->name); continue; @@ -311,7 +345,7 @@ static int send_pack(int in, int out, int nr_refspec, char **refspec) continue; } } - memcpy(ref->new_sha1, ref->peer_ref->new_sha1, 20); + hashcpy(ref->new_sha1, ref->peer_ref->new_sha1); if (is_zero_sha1(ref->new_sha1)) { error("cannot happen anymore"); ret = -3; @@ -409,6 +443,6 @@ int main(int argc, char **argv) ret = send_pack(fd[0], fd[1], nr_heads, heads); close(fd[0]); close(fd[1]); - finish_connect(pid); - return ret; + ret |= finish_connect(pid); + return !!ret; } diff --git a/server-info.c b/server-info.c index 7df628f2b2..2fb8f57103 100644 --- a/server-info.c +++ b/server-info.c @@ -23,7 +23,7 @@ static int add_info_ref(const char *path, const unsigned char *sha1) static int update_info_refs(int force) { - char *path0 = strdup(git_path("info/refs")); + char *path0 = xstrdup(git_path("info/refs")); int len = strlen(path0); char *path1 = xmalloc(len + 2); @@ -244,6 +244,8 @@ int check_repository_format_version(const char *var, const char *value) repository_format_version = git_config_int(var, value); else if (strcmp(var, "core.sharedrepository") == 0) shared_repository = git_config_perm(var, value); + else if (strcmp(var, "receive.denynonfastforwards") == 0) + deny_non_fast_forwards = git_config_bool(var, value); return 0; } diff --git a/sha1_file.c b/sha1_file.c index 3db956dd5c..716aef33e3 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -22,20 +22,44 @@ #endif #endif -const unsigned char null_sha1[20] = { 0, }; +const unsigned char null_sha1[20]; static unsigned int sha1_file_open_flag = O_NOATIME; -static unsigned hexval(char c) -{ - if (c >= '0' && c <= '9') - return c - '0'; - if (c >= 'a' && c <= 'f') - return c - 'a' + 10; - if (c >= 'A' && c <= 'F') - return c - 'A' + 10; - return ~0; -} +signed char hexval_table[256] = { + -1, -1, -1, -1, -1, -1, -1, -1, /* 00-07 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 08-0f */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 10-17 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 18-1f */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 20-27 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 28-2f */ + 0, 1, 2, 3, 4, 5, 6, 7, /* 30-37 */ + 8, 9, -1, -1, -1, -1, -1, -1, /* 38-3f */ + -1, 10, 11, 12, 13, 14, 15, -1, /* 40-47 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 48-4f */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 50-57 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 58-5f */ + -1, 10, 11, 12, 13, 14, 15, -1, /* 60-67 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 68-67 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 70-77 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 78-7f */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 80-87 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 88-8f */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 90-97 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 98-9f */ + -1, -1, -1, -1, -1, -1, -1, -1, /* a0-a7 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* a8-af */ + -1, -1, -1, -1, -1, -1, -1, -1, /* b0-b7 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* b8-bf */ + -1, -1, -1, -1, -1, -1, -1, -1, /* c0-c7 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* c8-cf */ + -1, -1, -1, -1, -1, -1, -1, -1, /* d0-d7 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* d8-df */ + -1, -1, -1, -1, -1, -1, -1, -1, /* e0-e7 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* e8-ef */ + -1, -1, -1, -1, -1, -1, -1, -1, /* f0-f7 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* f8-ff */ +}; int get_sha1_hex(const char *hex, unsigned char *sha1) { @@ -115,7 +139,7 @@ static void fill_sha1_path(char *pathbuf, const unsigned char *sha1) /* * NOTE! This returns a statically allocated buffer, so you have to be - * careful about using it. Do a "strdup()" if you need to save the + * careful about using it. Do a "xstrdup()" if you need to save the * filename. * * Also note that this returns the location for creating. Reading @@ -463,6 +487,7 @@ int use_packed_git(struct packed_git *p) int fd; struct stat st; void *map; + struct pack_header *hdr; pack_mapped += p->pack_size; while (PACK_MAX_SZ < pack_mapped && unuse_one_packed_git()) @@ -482,13 +507,24 @@ int use_packed_git(struct packed_git *p) die("packfile %s cannot be mapped.", p->pack_name); p->pack_base = map; + /* Check if we understand this pack file. If we don't we're + * likely too old to handle it. + */ + hdr = map; + if (hdr->hdr_signature != htonl(PACK_SIGNATURE)) + die("packfile %s isn't actually a pack.", p->pack_name); + if (!pack_version_ok(hdr->hdr_version)) + die("packfile %s is version %i and not supported" + " (try upgrading GIT to a newer version)", + p->pack_name, ntohl(hdr->hdr_version)); + /* Check if the pack file matches with the index file. * this is cheap. */ - if (memcmp((char*)(p->index_base) + p->index_size - 40, - (char *) p->pack_base + p->pack_size - 20, - 20)) { - + if (hashcmp((unsigned char *)(p->index_base) + + p->index_size - 40, + (unsigned char *)p->pack_base + + p->pack_size - 20)) { die("packfile %s does not match index.", p->pack_name); } } @@ -528,7 +564,7 @@ struct packed_git *add_packed_git(char *path, int path_len, int local) p->pack_use_cnt = 0; p->pack_local = local; if ((path_len > 44) && !get_sha1_hex(path + path_len - 44, sha1)) - memcpy(p->sha1, sha1, 20); + hashcpy(p->sha1, sha1); return p; } @@ -559,7 +595,7 @@ struct packed_git *parse_pack_index_file(const unsigned char *sha1, char *idx_pa p->pack_base = NULL; p->pack_last_used = 0; p->pack_use_cnt = 0; - memcpy(p->sha1, sha1, 20); + hashcpy(p->sha1, sha1); return p; } @@ -635,19 +671,12 @@ static void reprepare_packed_git(void) int check_sha1_signature(const unsigned char *sha1, void *map, unsigned long size, const char *type) { - char header[100]; unsigned char real_sha1[20]; - SHA_CTX c; - - SHA1_Init(&c); - SHA1_Update(&c, header, 1+sprintf(header, "%s %lu", type, size)); - SHA1_Update(&c, map, size); - SHA1_Final(real_sha1, &c); - return memcmp(sha1, real_sha1, 20) ? -1 : 0; + hash_sha1_file(map, size, type, real_sha1); + return hashcmp(sha1, real_sha1) ? -1 : 0; } -static void *map_sha1_file_internal(const unsigned char *sha1, - unsigned long *size) +void *map_sha1_file(const unsigned char *sha1, unsigned long *size) { struct stat st; void *map; @@ -684,17 +713,55 @@ static void *map_sha1_file_internal(const unsigned char *sha1, return map; } -static int unpack_sha1_header(z_stream *stream, unsigned char *map, unsigned long mapsize, void *buffer, unsigned long bufsiz) +int legacy_loose_object(unsigned char *map) +{ + unsigned int word; + + /* + * Is it a zlib-compressed buffer? If so, the first byte + * must be 0x78 (15-bit window size, deflated), and the + * first 16-bit word is evenly divisible by 31 + */ + word = (map[0] << 8) + map[1]; + if (map[0] == 0x78 && !(word % 31)) + return 1; + else + return 0; +} + +unsigned long unpack_object_header_gently(const unsigned char *buf, unsigned long len, enum object_type *type, unsigned long *sizep) { + unsigned shift; unsigned char c; - unsigned int word, bits; unsigned long size; - static const char *typename[8] = { - NULL, /* OBJ_EXT */ - "commit", "tree", "blob", "tag", - NULL, NULL, NULL + unsigned long used = 0; + + c = buf[used++]; + *type = (c >> 4) & 7; + size = c & 15; + shift = 4; + while (c & 0x80) { + if (len <= used) + return 0; + if (sizeof(long) * 8 <= shift) + return 0; + c = buf[used++]; + size += (c & 0x7f) << shift; + shift += 7; + } + *sizep = size; + return used; +} + +static int unpack_sha1_header(z_stream *stream, unsigned char *map, unsigned long mapsize, void *buffer, unsigned long bufsiz) +{ + unsigned long size, used; + static const char valid_loose_object_type[8] = { + 0, /* OBJ_EXT */ + 1, 1, 1, 1, /* "commit", "tree", "blob", "tag" */ + 0, /* "delta" and others are invalid in a loose object */ }; - const char *type; + enum object_type type; /* Get the data stream */ memset(stream, 0, sizeof(*stream)); @@ -703,33 +770,16 @@ static int unpack_sha1_header(z_stream *stream, unsigned char *map, unsigned lon stream->next_out = buffer; stream->avail_out = bufsiz; - /* - * Is it a zlib-compressed buffer? If so, the first byte - * must be 0x78 (15-bit window size, deflated), and the - * first 16-bit word is evenly divisible by 31 - */ - word = (map[0] << 8) + map[1]; - if (map[0] == 0x78 && !(word % 31)) { + if (legacy_loose_object(map)) { inflateInit(stream); return inflate(stream, 0); } - c = *map++; - mapsize--; - type = typename[(c >> 4) & 7]; - if (!type) + used = unpack_object_header_gently(map, mapsize, &type, &size); + if (!used || !valid_loose_object_type[type]) return -1; - - bits = 4; - size = c & 0xf; - while ((c & 0x80)) { - if (bits >= 8*sizeof(long)) - return -1; - c = *map++; - size += (c & 0x7f) << bits; - bits += 7; - mapsize--; - } + map += used; + mapsize -= used; /* Set up the stream for the rest.. */ stream->next_in = map; @@ -737,7 +787,8 @@ static int unpack_sha1_header(z_stream *stream, unsigned char *map, unsigned lon inflateInit(stream); /* And generate the fake traditional header */ - stream->total_out = 1 + snprintf(buffer, bufsiz, "%s %lu", type, size); + stream->total_out = 1 + snprintf(buffer, bufsiz, "%s %lu", + type_names[type], size); return 0; } @@ -827,33 +878,32 @@ void * unpack_sha1_file(void *map, unsigned long mapsize, char *type, unsigned l } /* forward declaration for a mutually recursive function */ -static int packed_object_info(struct pack_entry *entry, +static int packed_object_info(struct packed_git *p, unsigned long offset, char *type, unsigned long *sizep); -static int packed_delta_info(unsigned char *base_sha1, - unsigned long delta_size, - unsigned long left, +static int packed_delta_info(struct packed_git *p, + unsigned long offset, char *type, - unsigned long *sizep, - struct packed_git *p) + unsigned long *sizep) { - struct pack_entry base_ent; + unsigned long base_offset; + unsigned char *base_sha1 = (unsigned char *) p->pack_base + offset; - if (left < 20) + if (p->pack_size < offset + 20) die("truncated pack file"); - /* The base entry _must_ be in the same pack */ - if (!find_pack_entry_one(base_sha1, &base_ent, p)) + base_offset = find_pack_entry_one(base_sha1, p); + if (!base_offset) die("failed to find delta-pack base object %s", sha1_to_hex(base_sha1)); + offset += 20; /* We choose to only get the type of the base object and * ignore potentially corrupt pack file that expects the delta * based on a base with a wrong size. This saves tons of * inflate() calls. */ - - if (packed_object_info(&base_ent, type, NULL)) + if (packed_object_info(p, base_offset, type, NULL)) die("cannot get info for delta-pack base"); if (sizep) { @@ -865,8 +915,8 @@ static int packed_delta_info(unsigned char *base_sha1, memset(&stream, 0, sizeof(stream)); - data = stream.next_in = base_sha1 + 20; - stream.avail_in = left - 20; + stream.next_in = (unsigned char *) p->pack_base + offset; + stream.avail_in = p->pack_size - offset; stream.next_out = delta_head; stream.avail_out = sizeof(delta_head); @@ -895,29 +945,18 @@ static int packed_delta_info(unsigned char *base_sha1, static unsigned long unpack_object_header(struct packed_git *p, unsigned long offset, enum object_type *type, unsigned long *sizep) { - unsigned shift; - unsigned char *pack, c; - unsigned long size; + unsigned long used; - if (offset >= p->pack_size) + if (p->pack_size <= offset) die("object offset outside of pack file"); - pack = (unsigned char *) p->pack_base + offset; - c = *pack++; - offset++; - *type = (c >> 4) & 7; - size = c & 15; - shift = 4; - while (c & 0x80) { - if (offset >= p->pack_size) - die("object offset outside of pack file"); - c = *pack++; - offset++; - size += (c & 0x7f) << shift; - shift += 7; - } - *sizep = size; - return offset; + used = unpack_object_header_gently((unsigned char *)p->pack_base + + offset, + p->pack_size - offset, type, sizep); + if (!used) + die("object offset outside of pack file"); + + return offset + used; } int check_reuse_pack_delta(struct packed_git *p, unsigned long offset, @@ -932,99 +971,72 @@ int check_reuse_pack_delta(struct packed_git *p, unsigned long offset, ptr = unpack_object_header(p, ptr, kindp, sizep); if (*kindp != OBJ_DELTA) goto done; - memcpy(base, (char *) p->pack_base + ptr, 20); + hashcpy(base, (unsigned char *) p->pack_base + ptr); status = 0; done: unuse_packed_git(p); return status; } -void packed_object_info_detail(struct pack_entry *e, +void packed_object_info_detail(struct packed_git *p, + unsigned long offset, char *type, unsigned long *size, unsigned long *store_size, unsigned int *delta_chain_length, unsigned char *base_sha1) { - struct packed_git *p = e->p; - unsigned long offset; - unsigned char *pack; + unsigned long val; + unsigned char *next_sha1; enum object_type kind; - offset = unpack_object_header(p, e->offset, &kind, size); - pack = (unsigned char *) p->pack_base + offset; - if (kind != OBJ_DELTA) - *delta_chain_length = 0; - else { - unsigned int chain_length = 0; - if (p->pack_size <= offset + 20) - die("pack file %s records an incomplete delta base", - p->pack_name); - memcpy(base_sha1, pack, 20); - do { - struct pack_entry base_ent; - unsigned long junk; - - find_pack_entry_one(pack, &base_ent, p); - offset = unpack_object_header(p, base_ent.offset, - &kind, &junk); - pack = (unsigned char *) p->pack_base + offset; - chain_length++; - } while (kind == OBJ_DELTA); - *delta_chain_length = chain_length; - } - switch (kind) { - case OBJ_COMMIT: - strcpy(type, commit_type); - break; - case OBJ_TREE: - strcpy(type, tree_type); - break; - case OBJ_BLOB: - strcpy(type, blob_type); - break; - case OBJ_TAG: - strcpy(type, tag_type); - break; - default: - die("corrupted pack file %s containing object of kind %d", - p->pack_name, kind); + *delta_chain_length = 0; + offset = unpack_object_header(p, offset, &kind, size); + + for (;;) { + switch (kind) { + default: + die("corrupted pack file %s containing object of kind %d", + p->pack_name, kind); + case OBJ_COMMIT: + case OBJ_TREE: + case OBJ_BLOB: + case OBJ_TAG: + strcpy(type, type_names[kind]); + *store_size = 0; /* notyet */ + return; + case OBJ_DELTA: + if (p->pack_size <= offset + 20) + die("pack file %s records an incomplete delta base", + p->pack_name); + next_sha1 = (unsigned char *) p->pack_base + offset; + if (*delta_chain_length == 0) + hashcpy(base_sha1, next_sha1); + offset = find_pack_entry_one(next_sha1, p); + break; + } + offset = unpack_object_header(p, offset, &kind, &val); + (*delta_chain_length)++; } - *store_size = 0; /* notyet */ } -static int packed_object_info(struct pack_entry *entry, +static int packed_object_info(struct packed_git *p, unsigned long offset, char *type, unsigned long *sizep) { - struct packed_git *p = entry->p; - unsigned long offset, size, left; - unsigned char *pack; + unsigned long size; enum object_type kind; - int retval; - if (use_packed_git(p)) - die("cannot map packed file"); + offset = unpack_object_header(p, offset, &kind, &size); - offset = unpack_object_header(p, entry->offset, &kind, &size); - pack = (unsigned char *) p->pack_base + offset; - left = p->pack_size - offset; + if (kind == OBJ_DELTA) + return packed_delta_info(p, offset, type, sizep); switch (kind) { - case OBJ_DELTA: - retval = packed_delta_info(pack, size, left, type, sizep, p); - unuse_packed_git(p); - return retval; case OBJ_COMMIT: - strcpy(type, commit_type); - break; case OBJ_TREE: - strcpy(type, tree_type); - break; case OBJ_BLOB: - strcpy(type, blob_type); - break; case OBJ_TAG: - strcpy(type, tag_type); + strcpy(type, type_names[kind]); break; default: die("corrupted pack file %s containing object of kind %d", @@ -1032,69 +1044,12 @@ static int packed_object_info(struct pack_entry *entry, } if (sizep) *sizep = size; - unuse_packed_git(p); return 0; } -/* forward declaration for a mutually recursive function */ -static void *unpack_entry(struct pack_entry *, char *, unsigned long *); - -static void *unpack_delta_entry(unsigned char *base_sha1, - unsigned long delta_size, - unsigned long left, - char *type, - unsigned long *sizep, - struct packed_git *p) -{ - struct pack_entry base_ent; - void *data, *delta_data, *result, *base; - unsigned long data_size, result_size, base_size; - z_stream stream; - int st; - - if (left < 20) - die("truncated pack file"); - - /* The base entry _must_ be in the same pack */ - if (!find_pack_entry_one(base_sha1, &base_ent, p)) - die("failed to find delta-pack base object %s", - sha1_to_hex(base_sha1)); - base = unpack_entry_gently(&base_ent, type, &base_size); - if (!base) - die("failed to read delta-pack base object %s", - sha1_to_hex(base_sha1)); - - data = base_sha1 + 20; - data_size = left - 20; - delta_data = xmalloc(delta_size); - - memset(&stream, 0, sizeof(stream)); - - stream.next_in = data; - stream.avail_in = data_size; - stream.next_out = delta_data; - stream.avail_out = delta_size; - - inflateInit(&stream); - st = inflate(&stream, Z_FINISH); - inflateEnd(&stream); - if ((st != Z_STREAM_END) || stream.total_out != delta_size) - die("delta data unpack failed"); - - result = patch_delta(base, base_size, - delta_data, delta_size, - &result_size); - if (!result) - die("failed to apply delta"); - free(delta_data); - free(base); - *sizep = result_size; - return result; -} - -static void *unpack_non_delta_entry(unsigned char *data, - unsigned long size, - unsigned long left) +static void *unpack_compressed_entry(struct packed_git *p, + unsigned long offset, + unsigned long size) { int st; z_stream stream; @@ -1103,8 +1058,8 @@ static void *unpack_non_delta_entry(unsigned char *data, buffer = xmalloc(size + 1); buffer[size] = 0; memset(&stream, 0, sizeof(stream)); - stream.next_in = data; - stream.avail_in = left; + stream.next_in = (unsigned char*)p->pack_base + offset; + stream.avail_in = p->pack_size - offset; stream.next_out = buffer; stream.avail_out = size; @@ -1119,6 +1074,43 @@ static void *unpack_non_delta_entry(unsigned char *data, return buffer; } +static void *unpack_delta_entry(struct packed_git *p, + unsigned long offset, + unsigned long delta_size, + char *type, + unsigned long *sizep) +{ + void *delta_data, *result, *base; + unsigned long result_size, base_size, base_offset; + unsigned char *base_sha1; + + if (p->pack_size < offset + 20) + die("truncated pack file"); + /* The base entry _must_ be in the same pack */ + base_sha1 = (unsigned char*)p->pack_base + offset; + base_offset = find_pack_entry_one(base_sha1, p); + if (!base_offset) + die("failed to find delta-pack base object %s", + sha1_to_hex(base_sha1)); + offset += 20; + + base = unpack_entry_gently(p, base_offset, type, &base_size); + if (!base) + die("failed to read delta base object at %lu from %s", + base_offset, p->pack_name); + + delta_data = unpack_compressed_entry(p, offset, delta_size); + result = patch_delta(base, base_size, + delta_data, delta_size, + &result_size); + if (!result) + die("failed to apply delta"); + free(delta_data); + free(base); + *sizep = result_size; + return result; +} + static void *unpack_entry(struct pack_entry *entry, char *type, unsigned long *sizep) { @@ -1127,7 +1119,7 @@ static void *unpack_entry(struct pack_entry *entry, if (use_packed_git(p)) die("cannot map packed file"); - retval = unpack_entry_gently(entry, type, sizep); + retval = unpack_entry_gently(p, entry->offset, type, sizep); unuse_packed_git(p); if (!retval) die("corrupted pack file %s", p->pack_name); @@ -1135,40 +1127,26 @@ static void *unpack_entry(struct pack_entry *entry, } /* The caller is responsible for use_packed_git()/unuse_packed_git() pair */ -void *unpack_entry_gently(struct pack_entry *entry, +void *unpack_entry_gently(struct packed_git *p, unsigned long offset, char *type, unsigned long *sizep) { - struct packed_git *p = entry->p; - unsigned long offset, size, left; - unsigned char *pack; + unsigned long size; enum object_type kind; - void *retval; - offset = unpack_object_header(p, entry->offset, &kind, &size); - pack = (unsigned char *) p->pack_base + offset; - left = p->pack_size - offset; + offset = unpack_object_header(p, offset, &kind, &size); switch (kind) { case OBJ_DELTA: - retval = unpack_delta_entry(pack, size, left, type, sizep, p); - return retval; + return unpack_delta_entry(p, offset, size, type, sizep); case OBJ_COMMIT: - strcpy(type, commit_type); - break; case OBJ_TREE: - strcpy(type, tree_type); - break; case OBJ_BLOB: - strcpy(type, blob_type); - break; case OBJ_TAG: - strcpy(type, tag_type); - break; + strcpy(type, type_names[kind]); + *sizep = size; + return unpack_compressed_entry(p, offset, size); default: return NULL; } - *sizep = size; - retval = unpack_non_delta_entry(pack, size, left); - return retval; } int num_packed_objects(const struct packed_git *p) @@ -1183,12 +1161,12 @@ int nth_packed_object_sha1(const struct packed_git *p, int n, void *index = p->index_base + 256; if (n < 0 || num_packed_objects(p) <= n) return -1; - memcpy(sha1, (char *) index + (24 * n) + 4, 20); + hashcpy(sha1, (unsigned char *) index + (24 * n) + 4); return 0; } -int find_pack_entry_one(const unsigned char *sha1, - struct pack_entry *e, struct packed_git *p) +unsigned long find_pack_entry_one(const unsigned char *sha1, + struct packed_git *p) { unsigned int *level1_ofs = p->index_base; int hi = ntohl(level1_ofs[*sha1]); @@ -1197,13 +1175,9 @@ int find_pack_entry_one(const unsigned char *sha1, do { int mi = (lo + hi) / 2; - int cmp = memcmp((char *) index + (24 * mi) + 4, sha1, 20); - if (!cmp) { - e->offset = ntohl(*((unsigned int *) ((char *) index + (24 * mi)))); - memcpy(e->sha1, sha1, 20); - e->p = p; - return 1; - } + int cmp = hashcmp((unsigned char *)index + (24 * mi) + 4, sha1); + if (!cmp) + return ntohl(*((unsigned int *) ((char *) index + (24 * mi)))); if (cmp > 0) hi = mi; else @@ -1212,14 +1186,29 @@ int find_pack_entry_one(const unsigned char *sha1, return 0; } -static int find_pack_entry(const unsigned char *sha1, struct pack_entry *e) +static int find_pack_entry(const unsigned char *sha1, struct pack_entry *e, const char **ignore_packed) { struct packed_git *p; + unsigned long offset; + prepare_packed_git(); for (p = packed_git; p; p = p->next) { - if (find_pack_entry_one(sha1, e, p)) + if (ignore_packed) { + const char **ig; + for (ig = ignore_packed; *ig; ig++) + if (!strcmp(p->pack_name, *ig)) + break; + if (*ig) + continue; + } + offset = find_pack_entry_one(sha1, p); + if (offset) { + e->offset = offset; + e->p = p; + hashcpy(e->sha1, sha1); return 1; + } } return 0; } @@ -1228,10 +1217,9 @@ struct packed_git *find_sha1_pack(const unsigned char *sha1, struct packed_git *packs) { struct packed_git *p; - struct pack_entry e; for (p = packs; p; p = p->next) { - if (find_pack_entry_one(sha1, &e, p)) + if (find_pack_entry_one(sha1, p)) return p; } return NULL; @@ -1246,16 +1234,20 @@ int sha1_object_info(const unsigned char *sha1, char *type, unsigned long *sizep z_stream stream; char hdr[128]; - map = map_sha1_file_internal(sha1, &mapsize); + map = map_sha1_file(sha1, &mapsize); if (!map) { struct pack_entry e; - if (find_pack_entry(sha1, &e)) - return packed_object_info(&e, type, sizep); - reprepare_packed_git(); - if (find_pack_entry(sha1, &e)) - return packed_object_info(&e, type, sizep); - return error("unable to find %s", sha1_to_hex(sha1)); + if (!find_pack_entry(sha1, &e, NULL)) { + reprepare_packed_git(); + if (!find_pack_entry(sha1, &e, NULL)) + return error("unable to find %s", sha1_to_hex(sha1)); + } + if (use_packed_git(e.p)) + die("cannot map packed file"); + status = packed_object_info(e.p, e.offset, type, sizep); + unuse_packed_git(e.p); + return status; } if (unpack_sha1_header(&stream, map, mapsize, hdr, sizeof(hdr)) < 0) status = error("unable to unpack %s header", @@ -1276,7 +1268,7 @@ static void *read_packed_sha1(const unsigned char *sha1, char *type, unsigned lo { struct pack_entry e; - if (!find_pack_entry(sha1, &e)) { + if (!find_pack_entry(sha1, &e, NULL)) { error("cannot read sha1_file for %s", sha1_to_hex(sha1)); return NULL; } @@ -1289,16 +1281,16 @@ void * read_sha1_file(const unsigned char *sha1, char *type, unsigned long *size void *map, *buf; struct pack_entry e; - if (find_pack_entry(sha1, &e)) + if (find_pack_entry(sha1, &e, NULL)) return read_packed_sha1(sha1, type, size); - map = map_sha1_file_internal(sha1, &mapsize); + map = map_sha1_file(sha1, &mapsize); if (map) { buf = unpack_sha1_file(map, mapsize, type, size); munmap(map, mapsize); return buf; } reprepare_packed_git(); - if (find_pack_entry(sha1, &e)) + if (find_pack_entry(sha1, &e, NULL)) return read_packed_sha1(sha1, type, size); return NULL; } @@ -1313,7 +1305,7 @@ void *read_object_with_reference(const unsigned char *sha1, unsigned long isize; unsigned char actual_sha1[20]; - memcpy(actual_sha1, sha1, 20); + hashcpy(actual_sha1, sha1); while (1) { int ref_length = -1; const char *ref_type = NULL; @@ -1324,7 +1316,7 @@ void *read_object_with_reference(const unsigned char *sha1, if (!strcmp(type, required_type)) { *size = isize; if (actual_sha1_return) - memcpy(actual_sha1_return, actual_sha1, 20); + hashcpy(actual_sha1_return, actual_sha1); return buffer; } /* Handle references */ @@ -1349,12 +1341,9 @@ void *read_object_with_reference(const unsigned char *sha1, } } -char *write_sha1_file_prepare(void *buf, - unsigned long len, - const char *type, - unsigned char *sha1, - unsigned char *hdr, - int *hdrlen) +static void write_sha1_file_prepare(void *buf, unsigned long len, + const char *type, unsigned char *sha1, + unsigned char *hdr, int *hdrlen) { SHA_CTX c; @@ -1366,8 +1355,6 @@ char *write_sha1_file_prepare(void *buf, SHA1_Update(&c, hdr, *hdrlen); SHA1_Update(&c, buf, len); SHA1_Final(sha1, &c); - - return sha1_file_name(sha1); } /* @@ -1376,7 +1363,7 @@ char *write_sha1_file_prepare(void *buf, * * Returns the errno on failure, 0 on success. */ -static int link_temp_to_file(const char *tmpfile, char *filename) +static int link_temp_to_file(const char *tmpfile, const char *filename) { int ret; char *dir; @@ -1409,7 +1396,7 @@ static int link_temp_to_file(const char *tmpfile, char *filename) /* * Move the just written object into its final resting place */ -int move_temp_to_file(const char *tmpfile, char *filename) +int move_temp_to_file(const char *tmpfile, const char *filename) { int ret = link_temp_to_file(tmpfile, filename); @@ -1503,6 +1490,15 @@ static void setup_object_header(z_stream *stream, const char *type, unsigned lon stream->avail_out -= hdr; } +int hash_sha1_file(void *buf, unsigned long len, const char *type, + unsigned char *sha1) +{ + unsigned char hdr[50]; + int hdrlen; + write_sha1_file_prepare(buf, len, type, sha1, hdr, &hdrlen); + return 0; +} + int write_sha1_file(void *buf, unsigned long len, const char *type, unsigned char *returnsha1) { int size; @@ -1517,9 +1513,10 @@ int write_sha1_file(void *buf, unsigned long len, const char *type, unsigned cha /* Normally if we have it in the pack then we do not bother writing * it out into .git/objects/??/?{38} file. */ - filename = write_sha1_file_prepare(buf, len, type, sha1, hdr, &hdrlen); + write_sha1_file_prepare(buf, len, type, sha1, hdr, &hdrlen); + filename = sha1_file_name(sha1); if (returnsha1) - memcpy(returnsha1, sha1, 20); + hashcpy(returnsha1, sha1); if (has_sha1_file(sha1)) return 0; fd = open(filename, O_RDONLY); @@ -1629,7 +1626,7 @@ int write_sha1_to_fd(int fd, const unsigned char *sha1) { int retval; unsigned long objsize; - void *buf = map_sha1_file_internal(sha1, &objsize); + void *buf = map_sha1_file(sha1, &objsize); if (buf) { retval = write_buffer(fd, buf, objsize); @@ -1706,7 +1703,7 @@ int write_sha1_from_fd(const unsigned char *sha1, int fd, char *buffer, unlink(tmpfile); return error("File %s corrupted", sha1_to_hex(sha1)); } - if (memcmp(sha1, real_sha1, 20)) { + if (hashcmp(sha1, real_sha1)) { unlink(tmpfile); return error("File %s has bad hash", sha1_to_hex(sha1)); } @@ -1730,10 +1727,10 @@ int has_pack_file(const unsigned char *sha1) return 1; } -int has_sha1_pack(const unsigned char *sha1) +int has_sha1_pack(const unsigned char *sha1, const char **ignore_packed) { struct pack_entry e; - return find_pack_entry(sha1, &e); + return find_pack_entry(sha1, &e, ignore_packed); } int has_sha1_file(const unsigned char *sha1) @@ -1741,7 +1738,7 @@ int has_sha1_file(const unsigned char *sha1) struct stat st; struct pack_entry e; - if (find_pack_entry(sha1, &e)) + if (find_pack_entry(sha1, &e, NULL)) return 1; return find_sha1_file(sha1, &st) ? 1 : 0; } @@ -1768,7 +1765,7 @@ int read_pipe(int fd, char** return_buf, unsigned long* return_size) off += iret; if (off == size) { size *= 2; - buf = realloc(buf, size); + buf = xrealloc(buf, size); } } } while (iret > 0); @@ -1784,10 +1781,8 @@ int read_pipe(int fd, char** return_buf, unsigned long* return_size) int index_pipe(unsigned char *sha1, int fd, const char *type, int write_object) { unsigned long size = 4096; - char *buf = malloc(size); + char *buf = xmalloc(size); int ret; - unsigned char hdr[50]; - int hdrlen; if (read_pipe(fd, &buf, &size)) { free(buf); @@ -1798,10 +1793,8 @@ int index_pipe(unsigned char *sha1, int fd, const char *type, int write_object) type = blob_type; if (write_object) ret = write_sha1_file(buf, size, type, sha1); - else { - write_sha1_file_prepare(buf, size, type, sha1, hdr, &hdrlen); - ret = 0; - } + else + ret = hash_sha1_file(buf, size, type, sha1); free(buf); return ret; } @@ -1811,8 +1804,6 @@ int index_fd(unsigned char *sha1, int fd, struct stat *st, int write_object, con unsigned long size = st->st_size; void *buf; int ret; - unsigned char hdr[50]; - int hdrlen; buf = ""; if (size) @@ -1825,10 +1816,8 @@ int index_fd(unsigned char *sha1, int fd, struct stat *st, int write_object, con type = blob_type; if (write_object) ret = write_sha1_file(buf, size, type, sha1); - else { - write_sha1_file_prepare(buf, size, type, sha1, hdr, &hdrlen); - ret = 0; - } + else + ret = hash_sha1_file(buf, size, type, sha1); if (size) munmap(buf, size); return ret; @@ -1857,12 +1846,9 @@ int index_path(unsigned char *sha1, const char *path, struct stat *st, int write return error("readlink(\"%s\"): %s", path, errstr); } - if (!write_object) { - unsigned char hdr[50]; - int hdrlen; - write_sha1_file_prepare(target, st->st_size, blob_type, - sha1, hdr, &hdrlen); - } else if (write_sha1_file(target, st->st_size, blob_type, sha1)) + if (!write_object) + hash_sha1_file(target, st->st_size, blob_type, sha1); + else if (write_sha1_file(target, st->st_size, blob_type, sha1)) return error("%s: failed to insert into database", path); free(target); diff --git a/sha1_name.c b/sha1_name.c index c5a05faeb6..9b226e3579 100644 --- a/sha1_name.c +++ b/sha1_name.c @@ -84,7 +84,7 @@ static int find_short_packed_object(int len, const unsigned char *match, unsigne int cmp; nth_packed_object_sha1(p, mid, now); - cmp = memcmp(match, now, 20); + cmp = hashcmp(match, now); if (!cmp) { first = mid; break; @@ -103,10 +103,10 @@ static int find_short_packed_object(int len, const unsigned char *match, unsigne !match_sha(len, match, next)) { /* unique within this pack */ if (!found) { - memcpy(found_sha1, now, 20); + hashcpy(found_sha1, now); found++; } - else if (memcmp(found_sha1, now, 20)) { + else if (hashcmp(found_sha1, now)) { found = 2; break; } @@ -120,7 +120,7 @@ static int find_short_packed_object(int len, const unsigned char *match, unsigne } } if (found == 1) - memcpy(sha1, found_sha1, 20); + hashcpy(sha1, found_sha1); return found; } @@ -140,13 +140,13 @@ static int find_unique_short_object(int len, char *canonical, if (1 < has_unpacked || 1 < has_packed) return SHORT_NAME_AMBIGUOUS; if (has_unpacked != has_packed) { - memcpy(sha1, (has_packed ? packed_sha1 : unpacked_sha1), 20); + hashcpy(sha1, (has_packed ? packed_sha1 : unpacked_sha1)); return 0; } /* Both have unique ones -- do they match? */ - if (memcmp(packed_sha1, unpacked_sha1, 20)) + if (hashcmp(packed_sha1, unpacked_sha1)) return SHORT_NAME_AMBIGUOUS; - memcpy(sha1, packed_sha1, 20); + hashcpy(sha1, packed_sha1); return 0; } @@ -159,7 +159,7 @@ static int get_short_sha1(const char *name, int len, unsigned char *sha1, if (len < MINIMUM_ABBREV) return -1; - memset(res, 0, 20); + hashclr(res); memset(canonical, 'x', 40); for (i = 0; i < len ;i++) { unsigned char c = name[i]; @@ -191,7 +191,7 @@ const char *find_unique_abbrev(const unsigned char *sha1, int len) int status, is_null; static char hex[41]; - is_null = !memcmp(sha1, null_sha1, 20); + is_null = is_null_sha1(sha1); memcpy(hex, sha1_to_hex(sha1), 40); if (len == 40 || !len) return hex; @@ -279,7 +279,7 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1) pathname = resolve_ref(git_path(*p, len, str), this_result, 1); if (pathname) { if (!refs_found++) - real_path = strdup(pathname); + real_path = xstrdup(pathname); if (!warn_ambiguous_refs) break; } @@ -320,13 +320,13 @@ static int get_parent(const char *name, int len, if (parse_commit(commit)) return -1; if (!idx) { - memcpy(result, commit->object.sha1, 20); + hashcpy(result, commit->object.sha1); return 0; } p = commit->parents; while (p) { if (!--idx) { - memcpy(result, p->item->object.sha1, 20); + hashcpy(result, p->item->object.sha1); return 0; } p = p->next; @@ -347,9 +347,9 @@ static int get_nth_ancestor(const char *name, int len, if (!commit || parse_commit(commit) || !commit->parents) return -1; - memcpy(sha1, commit->parents->item->object.sha1, 20); + hashcpy(sha1, commit->parents->item->object.sha1); } - memcpy(result, sha1, 20); + hashcpy(result, sha1); return 0; } @@ -401,7 +401,7 @@ static int peel_onion(const char *name, int len, unsigned char *sha1) o = deref_tag(o, name, sp - name - 2); if (!o || (!o->parsed && !parse_object(o->sha1))) return -1; - memcpy(sha1, o->sha1, 20); + hashcpy(sha1, o->sha1); } else { /* At this point, the syntax look correct, so @@ -413,7 +413,7 @@ static int peel_onion(const char *name, int len, unsigned char *sha1) if (!o || (!o->parsed && !parse_object(o->sha1))) return -1; if (o->type == expected_type) { - memcpy(sha1, o->sha1, 20); + hashcpy(sha1, o->sha1); return 0; } if (o->type == OBJ_TAG) @@ -431,6 +431,26 @@ static int peel_onion(const char *name, int len, unsigned char *sha1) return 0; } +static int get_describe_name(const char *name, int len, unsigned char *sha1) +{ + const char *cp; + + for (cp = name + len - 1; name + 2 <= cp; cp--) { + char ch = *cp; + if (hexval(ch) & ~0377) { + /* We must be looking at g in "SOMETHING-g" + * for it to be describe output. + */ + if (ch == 'g' && cp[-1] == '-') { + cp++; + len -= cp - name; + return get_short_sha1(cp, len, sha1, 1); + } + } + } + return -1; +} + static int get_sha1_1(const char *name, int len, unsigned char *sha1) { int ret, has_suffix; @@ -472,6 +492,12 @@ static int get_sha1_1(const char *name, int len, unsigned char *sha1) ret = get_sha1_basic(name, len, sha1); if (!ret) return 0; + + /* It could be describe output that is "SOMETHING-gXXXX" */ + ret = get_describe_name(name, len, sha1); + if (!ret) + return 0; + return get_short_sha1(name, len, sha1, 0); } @@ -520,7 +546,7 @@ int get_sha1(const char *name, unsigned char *sha1) memcmp(ce->name, cp, namelen)) break; if (ce_stage(ce) == stage) { - memcpy(sha1, ce->sha1, 20); + hashcpy(sha1, ce->sha1); return 0; } pos++; diff --git a/sideband.c b/sideband.c new file mode 100644 index 0000000000..277fa3c10d --- /dev/null +++ b/sideband.c @@ -0,0 +1,78 @@ +#include "pkt-line.h" +#include "sideband.h" + +/* + * Receive multiplexed output stream over git native protocol. + * in_stream is the input stream from the remote, which carries data + * in pkt_line format with band designator. Demultiplex it into out + * and err and return error appropriately. Band #1 carries the + * primary payload. Things coming over band #2 is not necessarily + * error; they are usually informative message on the standard error + * stream, aka "verbose"). A message over band #3 is a signal that + * the remote died unexpectedly. A flush() concludes the stream. + */ +int recv_sideband(const char *me, int in_stream, int out, int err) +{ + char buf[7 + LARGE_PACKET_MAX + 1]; + strcpy(buf, "remote:"); + while (1) { + int band, len; + len = packet_read_line(in_stream, buf+7, LARGE_PACKET_MAX); + if (len == 0) + break; + if (len < 1) { + len = sprintf(buf, "%s: protocol error: no band designator\n", me); + safe_write(err, buf, len); + return SIDEBAND_PROTOCOL_ERROR; + } + band = buf[7] & 0xff; + len--; + switch (band) { + case 3: + buf[7] = ' '; + buf[8+len] = '\n'; + safe_write(err, buf, 8+len+1); + return SIDEBAND_REMOTE_ERROR; + case 2: + buf[7] = ' '; + safe_write(err, buf, 8+len); + continue; + case 1: + safe_write(out, buf+8, len); + continue; + default: + len = sprintf(buf, + "%s: protocol error: bad band #%d\n", + me, band); + safe_write(err, buf, len); + return SIDEBAND_PROTOCOL_ERROR; + } + } + return 0; +} + +/* + * fd is connected to the remote side; send the sideband data + * over multiplexed packet stream. + */ +ssize_t send_sideband(int fd, int band, const char *data, ssize_t sz, int packet_max) +{ + ssize_t ssz = sz; + const char *p = data; + + while (sz) { + unsigned n; + char hdr[5]; + + n = sz; + if (packet_max - 5 < n) + n = packet_max - 5; + sprintf(hdr, "%04x", n + 5); + hdr[4] = band; + safe_write(fd, hdr, 5); + safe_write(fd, p, n); + p += n; + sz -= n; + } + return ssz; +} diff --git a/sideband.h b/sideband.h new file mode 100644 index 0000000000..a84b6917c7 --- /dev/null +++ b/sideband.h @@ -0,0 +1,13 @@ +#ifndef SIDEBAND_H +#define SIDEBAND_H + +#define SIDEBAND_PROTOCOL_ERROR -2 +#define SIDEBAND_REMOTE_ERROR -1 + +#define DEFAULT_PACKET_MAX 1000 +#define LARGE_PACKET_MAX 65520 + +int recv_sideband(const char *me, int in_stream, int out, int err); +ssize_t send_sideband(int fd, int band, const char *data, ssize_t sz, int packet_max); + +#endif diff --git a/ssh-fetch.c b/ssh-fetch.c index c7d8fa80e4..b006c5c980 100644 --- a/ssh-fetch.c +++ b/ssh-fetch.c @@ -17,7 +17,7 @@ static int fd_in; static int fd_out; -static unsigned char remote_version = 0; +static unsigned char remote_version; static unsigned char local_version = 1; static ssize_t force_write(int fd, void *buffer, size_t length) @@ -36,9 +36,9 @@ static ssize_t force_write(int fd, void *buffer, size_t length) return ret; } -static int prefetches = 0; +static int prefetches; -static struct object_list *in_transit = NULL; +static struct object_list *in_transit; static struct object_list **end_of_transit = &in_transit; void prefetch(unsigned char *sha1) @@ -59,7 +59,7 @@ void prefetch(unsigned char *sha1) } static char conn_buf[4096]; -static size_t conn_buf_posn = 0; +static size_t conn_buf_posn; int fetch(unsigned char *sha1) { @@ -67,7 +67,7 @@ int fetch(unsigned char *sha1) signed char remote; struct object_list *temp; - if (memcmp(sha1, in_transit->item->sha1, 20)) { + if (hashcmp(sha1, in_transit->item->sha1)) { /* we must have already fetched it to clean the queue */ return has_sha1_file(sha1) ? 0 : -1; } diff --git a/ssh-upload.c b/ssh-upload.c index 2da66618fc..20b15eab57 100644 --- a/ssh-upload.c +++ b/ssh-upload.c @@ -15,9 +15,9 @@ #include <string.h> static unsigned char local_version = 1; -static unsigned char remote_version = 0; +static unsigned char remote_version; -static int verbose = 0; +static int verbose; static int serve_object(int fd_in, int fd_out) { ssize_t size; diff --git a/t/t1200-tutorial.sh b/t/t1200-tutorial.sh index c7db20e7f3..0272dd4293 100755 --- a/t/t1200-tutorial.sh +++ b/t/t1200-tutorial.sh @@ -3,7 +3,7 @@ # Copyright (c) 2005 Johannes Schindelin # -test_description='Test git-rev-parse with different parent options' +test_description='A simple turial in the form of a test case' . ./test-lib.sh diff --git a/t/t1400-update-ref.sh b/t/t1400-update-ref.sh index ddc80bbeae..b3b920edb1 100755 --- a/t/t1400-update-ref.sh +++ b/t/t1400-update-ref.sh @@ -19,51 +19,51 @@ n=$n_dir/fixes test_expect_success \ "create $m" \ - 'git-update-ref $m $A && - test $A = $(cat .git/$m)' + "git-update-ref $m $A && + test $A"' = $(cat .git/'"$m"')' test_expect_success \ "create $m" \ - 'git-update-ref $m $B $A && - test $B = $(cat .git/$m)' + "git-update-ref $m $B $A && + test $B"' = $(cat .git/'"$m"')' rm -f .git/$m test_expect_success \ "fail to create $n" \ - 'touch .git/$n_dir + "touch .git/$n_dir git-update-ref $n $A >out 2>err - test $? = 1 && + test "'$? = 1 && test "" = "$(cat out)" && grep "error: unable to resolve reference" err && - grep $n err' + grep '"$n err" rm -f .git/$n_dir out err test_expect_success \ "create $m (by HEAD)" \ - 'git-update-ref HEAD $A && - test $A = $(cat .git/$m)' + "git-update-ref HEAD $A && + test $A"' = $(cat .git/'"$m"')' test_expect_success \ "create $m (by HEAD)" \ - 'git-update-ref HEAD $B $A && - test $B = $(cat .git/$m)' + "git-update-ref HEAD $B $A && + test $B"' = $(cat .git/'"$m"')' rm -f .git/$m test_expect_failure \ '(not) create HEAD with old sha1' \ - 'git-update-ref HEAD $A $B' + "git-update-ref HEAD $A $B" test_expect_failure \ "(not) prior created .git/$m" \ - 'test -f .git/$m' + "test -f .git/$m" rm -f .git/$m test_expect_success \ "create HEAD" \ - 'git-update-ref HEAD $A' + "git-update-ref HEAD $A" test_expect_failure \ '(not) change HEAD with wrong SHA1' \ - 'git-update-ref HEAD $B $Z' + "git-update-ref HEAD $B $Z" test_expect_failure \ "(not) changed .git/$m" \ - 'test $B = $(cat .git/$m)' + "test $B"' = $(cat .git/'"$m"')' rm -f .git/$m mkdir -p .git/logs/refs/heads @@ -71,18 +71,18 @@ touch .git/logs/refs/heads/master test_expect_success \ "create $m (logged by touch)" \ 'GIT_COMMITTER_DATE="2005-05-26 23:30" \ - git-update-ref HEAD $A -m "Initial Creation" && - test $A = $(cat .git/$m)' + git-update-ref HEAD '"$A"' -m "Initial Creation" && + test '"$A"' = $(cat .git/'"$m"')' test_expect_success \ "update $m (logged by touch)" \ 'GIT_COMMITTER_DATE="2005-05-26 23:31" \ - git-update-ref HEAD $B $A -m "Switch" && - test $B = $(cat .git/$m)' + git-update-ref HEAD'" $B $A "'-m "Switch" && + test '"$B"' = $(cat .git/'"$m"')' test_expect_success \ "set $m (logged by touch)" \ 'GIT_COMMITTER_DATE="2005-05-26 23:41" \ - git-update-ref HEAD $A && - test $A = $(cat .git/$m)' + git-update-ref HEAD'" $A && + test $A"' = $(cat .git/'"$m"')' cat >expect <<EOF $Z $A $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150200 +0000 Initial Creation @@ -91,7 +91,7 @@ $B $A $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150860 +0000 EOF test_expect_success \ "verifying $m's log" \ - 'diff expect .git/logs/$m' + "diff expect .git/logs/$m" rm -rf .git/$m .git/logs expect test_expect_success \ @@ -102,18 +102,18 @@ test_expect_success \ test_expect_success \ "create $m (logged by config)" \ 'GIT_COMMITTER_DATE="2005-05-26 23:32" \ - git-update-ref HEAD $A -m "Initial Creation" && - test $A = $(cat .git/$m)' + git-update-ref HEAD'" $A "'-m "Initial Creation" && + test '"$A"' = $(cat .git/'"$m"')' test_expect_success \ "update $m (logged by config)" \ 'GIT_COMMITTER_DATE="2005-05-26 23:33" \ - git-update-ref HEAD $B $A -m "Switch" && - test $B = $(cat .git/$m)' + git-update-ref HEAD'" $B $A "'-m "Switch" && + test '"$B"' = $(cat .git/'"$m"')' test_expect_success \ "set $m (logged by config)" \ 'GIT_COMMITTER_DATE="2005-05-26 23:43" \ - git-update-ref HEAD $A && - test $A = $(cat .git/$m)' + git-update-ref HEAD '"$A && + test $A"' = $(cat .git/'"$m"')' cat >expect <<EOF $Z $A $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150320 +0000 Initial Creation @@ -140,50 +140,50 @@ test_expect_success \ 'Query "master@{May 25 2005}" (before history)' \ 'rm -f o e git-rev-parse --verify "master@{May 25 2005}" >o 2>e && - test $C = $(cat o) && - test "warning: Log .git/logs/$m only goes back to $ed." = "$(cat e)"' + test '"$C"' = $(cat o) && + test "warning: Log .git/logs/'"$m only goes back to $ed"'." = "$(cat e)"' test_expect_success \ "Query master@{2005-05-25} (before history)" \ 'rm -f o e git-rev-parse --verify master@{2005-05-25} >o 2>e && - test $C = $(cat o) && - echo test "warning: Log .git/logs/$m only goes back to $ed." = "$(cat e)"' + test '"$C"' = $(cat o) && + echo test "warning: Log .git/logs/'"$m only goes back to $ed"'." = "$(cat e)"' test_expect_success \ 'Query "master@{May 26 2005 23:31:59}" (1 second before history)' \ 'rm -f o e git-rev-parse --verify "master@{May 26 2005 23:31:59}" >o 2>e && - test $C = $(cat o) && - test "warning: Log .git/logs/$m only goes back to $ed." = "$(cat e)"' + test '"$C"' = $(cat o) && + test "warning: Log .git/logs/'"$m only goes back to $ed"'." = "$(cat e)"' test_expect_success \ 'Query "master@{May 26 2005 23:32:00}" (exactly history start)' \ 'rm -f o e git-rev-parse --verify "master@{May 26 2005 23:32:00}" >o 2>e && - test $A = $(cat o) && + test '"$A"' = $(cat o) && test "" = "$(cat e)"' test_expect_success \ 'Query "master@{2005-05-26 23:33:01}" (middle of history with gap)' \ 'rm -f o e git-rev-parse --verify "master@{2005-05-26 23:33:01}" >o 2>e && - test $B = $(cat o) && - test "warning: Log .git/logs/$m has gap after $gd." = "$(cat e)"' + test '"$B"' = $(cat o) && + test "warning: Log .git/logs/'"$m has gap after $gd"'." = "$(cat e)"' test_expect_success \ 'Query "master@{2005-05-26 23:38:00}" (middle of history)' \ 'rm -f o e git-rev-parse --verify "master@{2005-05-26 23:38:00}" >o 2>e && - test $Z = $(cat o) && + test '"$Z"' = $(cat o) && test "" = "$(cat e)"' test_expect_success \ 'Query "master@{2005-05-26 23:43:00}" (exact end of history)' \ 'rm -f o e git-rev-parse --verify "master@{2005-05-26 23:43:00}" >o 2>e && - test $E = $(cat o) && + test '"$E"' = $(cat o) && test "" = "$(cat e)"' test_expect_success \ 'Query "master@{2005-05-28}" (past end of history)' \ 'rm -f o e git-rev-parse --verify "master@{2005-05-28}" >o 2>e && - test $D = $(cat o) && - test "warning: Log .git/logs/$m unexpectedly ended on $ld." = "$(cat e)"' + test '"$D"' = $(cat o) && + test "warning: Log .git/logs/'"$m unexpectedly ended on $ld"'." = "$(cat e)"' rm -f .git/$m .git/logs/$m expect @@ -221,7 +221,7 @@ $h_FIXED $h_MERGED $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117151100 +0000 c EOF test_expect_success \ 'git-commit logged updates' \ - 'diff expect .git/logs/$m' + "diff expect .git/logs/$m" unset h_TEST h_OTHER h_FIXED h_MERGED test_expect_success \ diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh index 5b04efc89d..6907cbcd29 100755 --- a/t/t3200-branch.sh +++ b/t/t3200-branch.sh @@ -61,4 +61,16 @@ test_expect_success \ test -f .git/logs/refs/heads/g/h/i && diff expect .git/logs/refs/heads/g/h/i' +test_expect_success \ + 'git branch j/k should work after branch j has been deleted' \ + 'git-branch j && + git-branch -d j && + git-branch j/k' + +test_expect_success \ + 'git branch l should work after branch l/m has been deleted' \ + 'git-branch l/m && + git-branch -d l/m && + git-branch l' + test_done diff --git a/t/t3403-rebase-skip.sh b/t/t3403-rebase-skip.sh index 8ab63c5276..bb25315361 100755 --- a/t/t3403-rebase-skip.sh +++ b/t/t3403-rebase-skip.sh @@ -37,7 +37,9 @@ test_expect_success setup ' git branch skip-merge skip-reference ' -test_expect_failure 'rebase with git am -3 (default)' 'git rebase master' +test_expect_failure 'rebase with git am -3 (default)' ' + git rebase master +' test_expect_success 'rebase --skip with am -3' ' git reset --hard HEAD && diff --git a/t/t3700-add.sh b/t/t3700-add.sh index 6cd05c3d90..c20e4c29fc 100755 --- a/t/t3700-add.sh +++ b/t/t3700-add.sh @@ -19,4 +19,26 @@ test_expect_success \ 'Test that "git-add -- -q" works' \ 'touch -- -q && git-add -- -q' +test_expect_success \ + 'git-add: Test that executable bit is not used if core.filemode=0' \ + 'git repo-config core.filemode 0 && + echo foo >xfoo1 && + chmod 755 xfoo1 && + git-add xfoo1 && + case "`git-ls-files --stage xfoo1`" in + 100644" "*xfoo1) echo ok;; + *) echo fail; git-ls-files --stage xfoo1; exit 1;; + esac' + +test_expect_success \ + 'git-update-index --add: Test that executable bit is not used...' \ + 'git repo-config core.filemode 0 && + echo foo >xfoo2 && + chmod 755 xfoo2 && + git-update-index --add xfoo2 && + case "`git-ls-files --stage xfoo2`" in + 100644" "*xfoo2) echo ok;; + *) echo fail; git-ls-files --stage xfoo2; exit 1;; + esac' + test_done diff --git a/t/t4015-diff-whitespace.sh b/t/t4015-diff-whitespace.sh new file mode 100755 index 0000000000..1bc5b7a412 --- /dev/null +++ b/t/t4015-diff-whitespace.sh @@ -0,0 +1,122 @@ +#!/bin/sh +# +# Copyright (c) 2006 Johannes E. Schindelin +# + +test_description='Test special whitespace in diff engine. + +' +. ./test-lib.sh +. ../diff-lib.sh + +# Ray Lehtiniemi's example + +cat << EOF > x +do { + nothing; +} while (0); +EOF + +git-update-index --add x + +cat << EOF > x +do +{ + nothing; +} +while (0); +EOF + +cat << EOF > expect +diff --git a/x b/x +index adf3937..6edc172 100644 +--- a/x ++++ b/x +@@ -1,3 +1,5 @@ +-do { ++do ++{ + nothing; +-} while (0); ++} ++while (0); +EOF + +git-diff > out +test_expect_success "Ray's example without options" 'diff -u expect out' + +git-diff -w > out +test_expect_success "Ray's example with -w" 'diff -u expect out' + +git-diff -b > out +test_expect_success "Ray's example with -b" 'diff -u expect out' + +tr 'Q' '\015' << EOF > x +whitespace at beginning +whitespace change +whitespace in the middle +whitespace at end +unchanged line +CR at endQ +EOF + +git-update-index x + +cat << EOF > x + whitespace at beginning +whitespace change +white space in the middle +whitespace at end +unchanged line +CR at end +EOF + +tr 'Q' '\015' << EOF > expect +diff --git a/x b/x +index d99af23..8b32fb5 100644 +--- a/x ++++ b/x +@@ -1,6 +1,6 @@ +-whitespace at beginning +-whitespace change +-whitespace in the middle +-whitespace at end ++ whitespace at beginning ++whitespace change ++white space in the middle ++whitespace at end + unchanged line +-CR at endQ ++CR at end +EOF +git-diff > out +test_expect_success 'another test, without options' 'diff -u expect out' + +cat << EOF > expect +diff --git a/x b/x +index d99af23..8b32fb5 100644 +EOF +git-diff -w > out +test_expect_success 'another test, with -w' 'diff -u expect out' + +tr 'Q' '\015' << EOF > expect +diff --git a/x b/x +index d99af23..8b32fb5 100644 +--- a/x ++++ b/x +@@ -1,6 +1,6 @@ +-whitespace at beginning ++ whitespace at beginning + whitespace change +-whitespace in the middle +-whitespace at end ++white space in the middle ++whitespace at end + unchanged line +-CR at endQ ++CR at end +EOF +git-diff -b > out +test_expect_success 'another test, with -b' 'diff -u expect out' + +test_done diff --git a/t/t4103-apply-binary.sh b/t/t4103-apply-binary.sh index ff052699a2..e2b1124c78 100755 --- a/t/t4103-apply-binary.sh +++ b/t/t4103-apply-binary.sh @@ -94,11 +94,11 @@ test_expect_failure 'apply binary diff (copy) -- should fail.' \ 'do_reset git-apply --index C.diff' -test_expect_failure 'apply binary diff without replacement -- should fail.' \ +test_expect_success 'apply binary diff without replacement.' \ 'do_reset git-apply BF.diff' -test_expect_failure 'apply binary diff without replacement (copy) -- should fail.' \ +test_expect_success 'apply binary diff without replacement (copy).' \ 'do_reset git-apply CF.diff' diff --git a/t/t4104-apply-boundary.sh b/t/t4104-apply-boundary.sh new file mode 100755 index 0000000000..2ff800c23f --- /dev/null +++ b/t/t4104-apply-boundary.sh @@ -0,0 +1,115 @@ +#!/bin/sh +# +# Copyright (c) 2005 Junio C Hamano +# + +test_description='git-apply boundary tests + +' +. ./test-lib.sh + +L="c d e f g h i j k l m n o p q r s t u v w x" + +test_expect_success setup ' + for i in b '"$L"' y + do + echo $i + done >victim && + cat victim >original && + git update-index --add victim && + + : add to the head + for i in a b '"$L"' y + do + echo $i + done >victim && + cat victim >add-a-expect && + git diff victim >add-a-patch.with && + git diff --unified=0 >add-a-patch.without && + + : modify at the head + for i in a '"$L"' y + do + echo $i + done >victim && + cat victim >mod-a-expect && + git diff victim >mod-a-patch.with && + git diff --unified=0 >mod-a-patch.without && + + : remove from the head + for i in '"$L"' y + do + echo $i + done >victim && + cat victim >del-a-expect && + git diff victim >del-a-patch.with + git diff --unified=0 >del-a-patch.without && + + : add to the tail + for i in b '"$L"' y z + do + echo $i + done >victim && + cat victim >add-z-expect && + git diff victim >add-z-patch.with && + git diff --unified=0 >add-z-patch.without && + + : modify at the tail + for i in a '"$L"' y + do + echo $i + done >victim && + cat victim >mod-z-expect && + git diff victim >mod-z-patch.with && + git diff --unified=0 >mod-z-patch.without && + + : remove from the tail + for i in b '"$L"' + do + echo $i + done >victim && + cat victim >del-z-expect && + git diff victim >del-z-patch.with + git diff --unified=0 >del-z-patch.without && + + : done +' + +for with in with without +do + case "$with" in + with) u= ;; + without) u='--unidiff-zero ' ;; + esac + for kind in add-a add-z mod-a mod-z del-a del-z + do + test_expect_success "apply $kind-patch $with context" ' + cat original >victim && + git update-index victim && + git apply --index '"$u$kind-patch.$with"' || { + cat '"$kind-patch.$with"' + (exit 1) + } && + diff -u '"$kind"'-expect victim + ' + done +done + +for kind in add-a add-z mod-a mod-z del-a del-z +do + rm -f $kind-ng.without + sed -e "s/^diff --git /diff /" \ + -e '/^index /d' \ + <$kind-patch.without >$kind-ng.without + test_expect_success "apply non-git $kind-patch without context" ' + cat original >victim && + git update-index victim && + git apply --unidiff-zero --index '"$kind-ng.without"' || { + cat '"$kind-ng.without"' + (exit 1) + } && + diff -u '"$kind"'-expect victim + ' +done + +test_done diff --git a/t/t4116-apply-reverse.sh b/t/t4116-apply-reverse.sh new file mode 100755 index 0000000000..74f5c2a575 --- /dev/null +++ b/t/t4116-apply-reverse.sh @@ -0,0 +1,85 @@ +#!/bin/sh +# +# Copyright (c) 2005 Junio C Hamano +# + +test_description='git-apply in reverse + +' + +. ./test-lib.sh + +test_expect_success setup ' + + for i in a b c d e f g h i j k l m n; do echo $i; done >file1 && + tr "[ijk]" '\''[\0\1\2]'\'' <file1 >file2 && + + git add file1 file2 && + git commit -m initial && + git tag initial && + + for i in a b c g h i J K L m o n p q; do echo $i; done >file1 && + tr "[mon]" '\''[\0\1\2]'\'' <file1 >file2 && + + git commit -a -m second && + git tag second && + + git diff --binary initial second >patch + +' + +test_expect_success 'apply in forward' ' + + T0=`git rev-parse "second^{tree}"` && + git reset --hard initial && + git apply --index --binary patch && + T1=`git write-tree` && + test "$T0" = "$T1" +' + +test_expect_success 'apply in reverse' ' + + git reset --hard second && + git apply --reverse --binary --index patch && + git diff >diff && + diff -u /dev/null diff + +' + +test_expect_success 'setup separate repository lacking postimage' ' + + git tar-tree initial initial | tar xf - && + ( + cd initial && git init-db && git add . + ) && + + git tar-tree second second | tar xf - && + ( + cd second && git init-db && git add . + ) + +' + +test_expect_success 'apply in forward without postimage' ' + + T0=`git rev-parse "second^{tree}"` && + ( + cd initial && + git apply --index --binary ../patch && + T1=`git write-tree` && + test "$T0" = "$T1" + ) +' + +test_expect_success 'apply in reverse without postimage' ' + + T0=`git rev-parse "initial^{tree}"` && + ( + cd second && + git apply --index --binary --reverse ../patch && + T1=`git write-tree` && + test "$T0" = "$T1" + ) +' + +test_done diff --git a/t/t4117-apply-reject.sh b/t/t4117-apply-reject.sh new file mode 100755 index 0000000000..b4de075a3e --- /dev/null +++ b/t/t4117-apply-reject.sh @@ -0,0 +1,157 @@ +#!/bin/sh +# +# Copyright (c) 2005 Junio C Hamano +# + +test_description='git-apply with rejects + +' + +. ./test-lib.sh + +test_expect_success setup ' + for i in 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 + do + echo $i + done >file1 && + cat file1 >saved.file1 && + git update-index --add file1 && + git commit -m initial && + + for i in 1 2 A B 4 5 6 7 8 9 10 11 12 C 13 14 15 16 17 18 19 20 D 21 + do + echo $i + done >file1 && + git diff >patch.1 && + cat file1 >clean && + + for i in 1 E 2 3 4 5 6 7 8 9 10 11 12 C 13 14 15 16 17 18 19 20 F 21 + do + echo $i + done >expected && + + mv file1 file2 && + git update-index --add --remove file1 file2 && + git diff -M HEAD >patch.2 && + + rm -f file1 file2 && + mv saved.file1 file1 && + git update-index --add --remove file1 file2 && + + for i in 1 E 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 F 21 + do + echo $i + done >file1 && + + cat file1 >saved.file1 +' + +test_expect_success 'apply without --reject should fail' ' + + if git apply patch.1 + then + echo "Eh? Why?" + exit 1 + fi + + diff -u file1 saved.file1 +' + +test_expect_success 'apply without --reject should fail' ' + + if git apply --verbose patch.1 + then + echo "Eh? Why?" + exit 1 + fi + + diff -u file1 saved.file1 +' + +test_expect_success 'apply with --reject should fail but update the file' ' + + cat saved.file1 >file1 && + rm -f file1.rej file2.rej && + + if git apply --reject patch.1 + then + echo "succeeds with --reject?" + exit 1 + fi + + diff -u file1 expected && + + cat file1.rej && + + if test -f file2.rej + then + echo "file2 should not have been touched" + exit 1 + fi +' + +test_expect_success 'apply with --reject should fail but update the file' ' + + cat saved.file1 >file1 && + rm -f file1.rej file2.rej file2 && + + if git apply --reject patch.2 >rejects + then + echo "succeeds with --reject?" + exit 1 + fi + + test -f file1 && { + echo "file1 still exists?" + exit 1 + } + diff -u file2 expected && + + cat file2.rej && + + if test -f file1.rej + then + echo "file2 should not have been touched" + exit 1 + fi + +' + +test_expect_success 'the same test with --verbose' ' + + cat saved.file1 >file1 && + rm -f file1.rej file2.rej file2 && + + if git apply --reject --verbose patch.2 >rejects + then + echo "succeeds with --reject?" + exit 1 + fi + + test -f file1 && { + echo "file1 still exists?" + exit 1 + } + diff -u file2 expected && + + cat file2.rej && + + if test -f file1.rej + then + echo "file2 should not have been touched" + exit 1 + fi + +' + +test_expect_success 'apply cleanly with --verbose' ' + + git cat-file -p HEAD:file1 >file1 && + rm -f file?.rej file2 && + + git apply --verbose patch.1 && + + diff -u file1 clean +' + +test_done diff --git a/t/t5400-send-pack.sh b/t/t5400-send-pack.sh index f3694ac3c7..8afb899717 100755 --- a/t/t5400-send-pack.sh +++ b/t/t5400-send-pack.sh @@ -64,4 +64,18 @@ test_expect_success \ cmp victim/.git/refs/heads/master .git/refs/heads/master ' +unset GIT_CONFIG GIT_CONFIG_LOCAL +HOME=`pwd`/no-such-directory +export HOME ;# this way we force the victim/.git/config to be used. + +test_expect_success \ + 'pushing with --force should be denied with denyNonFastforwards' ' + cd victim && + git-repo-config receive.denyNonFastforwards true && + cd .. && + git-update-ref refs/heads/master master^ && + git-send-pack --force ./victim/.git/ master && + ! diff -u .git/refs/heads/master victim/.git/refs/heads/master +' + test_done diff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh new file mode 100755 index 0000000000..df0ae4811b --- /dev/null +++ b/t/t5510-fetch.sh @@ -0,0 +1,69 @@ +#!/bin/sh +# Copyright (c) 2006, Junio C Hamano. + +test_description='Per branch config variables affects "git fetch". + +' + +. ./test-lib.sh + +D=`pwd` + +test_expect_success setup ' + echo >file original && + git add file && + git commit -a -m original' + +test_expect_success "clone and setup child repos" ' + git clone . one && + cd one && + echo >file updated by one && + git commit -a -m "updated by one" && + cd .. && + git clone . two && + cd two && + git repo-config branch.master.remote one && + { + echo "URL: ../one/.git/" + echo "Pull: refs/heads/master:refs/heads/one" + } >.git/remotes/one + cd .. && + git clone . three && + cd three && + git repo-config branch.master.remote two && + git repo-config branch.master.merge refs/heads/one && + { + echo "URL: ../two/.git/" + echo "Pull: refs/heads/master:refs/heads/two" + echo "Pull: refs/heads/one:refs/heads/one" + } >.git/remotes/two +' + +test_expect_success "fetch test" ' + cd "$D" && + echo >file updated by origin && + git commit -a -m "updated by origin" && + cd two && + git fetch && + test -f .git/refs/heads/one && + mine=`git rev-parse refs/heads/one` && + his=`cd ../one && git rev-parse refs/heads/master` && + test "z$mine" = "z$his" +' + +test_expect_success "fetch test for-merge" ' + cd "$D" && + cd three && + git fetch && + test -f .git/refs/heads/two && + test -f .git/refs/heads/one && + master_in_two=`cd ../two && git rev-parse master` && + one_in_two=`cd ../two && git rev-parse one` && + { + echo "$master_in_two not-for-merge" + echo "$one_in_two " + } >expected && + cut -f -2 .git/FETCH_HEAD >actual && + diff expected actual' + +test_done diff --git a/t/t5600-clone-fail-cleanup.sh b/t/t5600-clone-fail-cleanup.sh index 0c6a363be9..041be04f5c 100755 --- a/t/t5600-clone-fail-cleanup.sh +++ b/t/t5600-clone-fail-cleanup.sh @@ -25,6 +25,12 @@ test_create_repo foo # clone doesn't like it if there is no HEAD. Is that a bug? (cd foo && touch file && git add file && git commit -m 'add file' >/dev/null 2>&1) +# source repository given to git-clone should be relative to the +# current path not to the target dir +test_expect_failure \ + 'clone of non-existent (relative to $PWD) source should fail' \ + 'git-clone ../foo baz' + test_expect_success \ 'clone should work now that source exists' \ 'git-clone foo bar' diff --git a/t/t5710-info-alternate.sh b/t/t5710-info-alternate.sh index 2e1b48a89b..b9f6d96363 100755 --- a/t/t5710-info-alternate.sh +++ b/t/t5710-info-alternate.sh @@ -58,6 +58,8 @@ test_expect_failure 'creating too deep nesting' \ git clone -l -s D E && git clone -l -s E F && git clone -l -s F G && +git clone -l -s G H && +cd H && test_valid_repo' cd "$base_dir" diff --git a/t/t6001-rev-list-graft.sh b/t/t6001-rev-list-graft.sh new file mode 100755 index 0000000000..b2131cdacd --- /dev/null +++ b/t/t6001-rev-list-graft.sh @@ -0,0 +1,113 @@ +#!/bin/sh + +test_description='Revision traversal vs grafts and path limiter' + +. ./test-lib.sh + +test_expect_success setup ' + mkdir subdir && + echo >fileA fileA && + echo >subdir/fileB fileB && + git add fileA subdir/fileB && + git commit -a -m "Initial in one history." && + A0=`git rev-parse --verify HEAD` && + + echo >fileA fileA modified && + git commit -a -m "Second in one history." && + A1=`git rev-parse --verify HEAD` && + + echo >subdir/fileB fileB modified && + git commit -a -m "Third in one history." && + A2=`git rev-parse --verify HEAD` && + + rm -f .git/refs/heads/master .git/index && + + echo >fileA fileA again && + echo >subdir/fileB fileB again && + git add fileA subdir/fileB && + git commit -a -m "Initial in alternate history." && + B0=`git rev-parse --verify HEAD` && + + echo >fileA fileA modified in alternate history && + git commit -a -m "Second in alternate history." && + B1=`git rev-parse --verify HEAD` && + + echo >subdir/fileB fileB modified in alternate history && + git commit -a -m "Third in alternate history." && + B2=`git rev-parse --verify HEAD` && + : done +' + +check () { + type=$1 + shift + + arg= + which=arg + rm -f test.expect + for a + do + if test "z$a" = z-- + then + which=expect + child= + continue + fi + if test "$which" = arg + then + arg="$arg$a " + continue + fi + if test "$type" = basic + then + echo "$a" + else + if test "z$child" != z + then + echo "$child $a" + fi + child="$a" + fi + done >test.expect + if test "$type" != basic && test "z$child" != z + then + echo >>test.expect $child + fi + if test $type = basic + then + git rev-list $arg >test.actual + elif test $type = parents + then + git rev-list --parents $arg >test.actual + elif test $type = parents-raw + then + git rev-list --parents --pretty=raw $arg | + sed -n -e 's/^commit //p' >test.actual + fi + diff test.expect test.actual +} + +for type in basic parents parents-raw +do + test_expect_success 'without grafts' " + rm -f .git/info/grafts + check $type $B2 -- $B2 $B1 $B0 + " + + test_expect_success 'with grafts' " + echo '$B0 $A2' >.git/info/grafts + check $type $B2 -- $B2 $B1 $B0 $A2 $A1 $A0 + " + + test_expect_success 'without grafts, with pathlimit' " + rm -f .git/info/grafts + check $type $B2 subdir -- $B2 $B0 + " + + test_expect_success 'with grafts, with pathlimit' " + echo '$B0 $A2' >.git/info/grafts + check $type $B2 subdir -- $B2 $B0 $A2 $A0 + " + +done +test_done diff --git a/t/t7002-grep.sh b/t/t7002-grep.sh index 00a7d762ce..6bfb899ed1 100755 --- a/t/t7002-grep.sh +++ b/t/t7002-grep.sh @@ -3,7 +3,7 @@ # Copyright (c) 2006 Junio C Hamano # -test_description='git grep -w +test_description='git grep various. ' . ./test-lib.sh @@ -19,7 +19,9 @@ test_expect_success setup ' echo x x xx x >x && echo y yy >y && echo zzz > z && - git add file x y z && + mkdir t && + echo test >t/t && + git add file x y z t/t && git commit -m initial ' @@ -80,6 +82,31 @@ do diff expected actual fi ' + + test_expect_success "grep $L (t-1)" ' + echo "${HC}t/t:1:test" >expected && + git grep -n -e test $H >actual && + diff expected actual + ' + + test_expect_success "grep $L (t-2)" ' + echo "${HC}t:1:test" >expected && + ( + cd t && + git grep -n -e test $H + ) >actual && + diff expected actual + ' + + test_expect_success "grep $L (t-3)" ' + echo "${HC}t/t:1:test" >expected && + ( + cd t && + git grep --full-name -n -e test $H + ) >actual && + diff expected actual + ' + done test_done diff --git a/t/t7201-co.sh b/t/t7201-co.sh index b64e8b7d77..085d4a096b 100755 --- a/t/t7201-co.sh +++ b/t/t7201-co.sh @@ -31,6 +31,15 @@ test_expect_success setup ' git checkout master ' +test_expect_success "checkout from non-existing branch" ' + + git checkout -b delete-me master && + rm .git/refs/heads/delete-me && + test refs/heads/delete-me = "$(git symbolic-ref HEAD)" && + git checkout master && + test refs/heads/master = "$(git symbolic-ref HEAD)" +' + test_expect_success "checkout with dirty tree without -m" ' fill 0 1 2 3 4 5 >one && diff --git a/t/test-lib.sh b/t/test-lib.sh index 470a909891..2488e6eae1 100755 --- a/t/test-lib.sh +++ b/t/test-lib.sh @@ -28,13 +28,21 @@ unset GIT_DIR unset GIT_EXTERNAL_DIFF unset GIT_INDEX_FILE unset GIT_OBJECT_DIRECTORY -unset GIT_TRACE unset SHA1_FILE_DIRECTORIES unset SHA1_FILE_DIRECTORY export GIT_AUTHOR_EMAIL GIT_AUTHOR_NAME export GIT_COMMITTER_EMAIL GIT_COMMITTER_NAME export EDITOR VISUAL +case $(echo $GIT_TRACE |tr "[A-Z]" "[a-z]") in + 1|2|true) + echo "* warning: Some tests will not work if GIT_TRACE" \ + "is set as to trace on STDERR ! *" + echo "* warning: Please set GIT_TRACE to something" \ + "other than 1, 2 or true ! *" + ;; +esac + # Each test should start with something like this, after copyright notices: # # test_description='Description of this test... @@ -127,6 +135,7 @@ test_expect_failure () { else test_failure_ "$@" fi + echo >&3 "" } test_expect_success () { @@ -140,6 +149,7 @@ test_expect_success () { else test_failure_ "$@" fi + echo >&3 "" } test_expect_code () { @@ -153,6 +163,7 @@ test_expect_code () { else test_failure_ "$@" fi + echo >&3 "" } # Most tests can use the created repository, but some amy need to create more. @@ -203,13 +214,15 @@ export PATH GIT_EXEC_PATH PYTHON=`sed -e '1{ s/^#!// q -}' ../git-merge-recursive` || { +}' ../git-merge-recursive-old` || { error "You haven't built things yet, have you?" } "$PYTHON" -c 'import subprocess' 2>/dev/null || { PYTHONPATH=$(pwd)/../compat export PYTHONPATH } +GITPERLLIB=$(pwd)/../perl/blib/lib:$(pwd)/../perl/blib/arch/auto/Git +export GITPERLLIB test -d ../templates/blt || { error "You haven't built things yet, have you?" } diff --git a/trace.c b/trace.c new file mode 100644 index 0000000000..495e5ed92a --- /dev/null +++ b/trace.c @@ -0,0 +1,150 @@ +/* + * GIT - The information manager from hell + * + * Copyright (C) 2000-2002 Michael R. Elkins <me@mutt.org> + * Copyright (C) 2002-2004 Oswald Buddenhagen <ossi@users.sf.net> + * Copyright (C) 2004 Theodore Y. Ts'o <tytso@mit.edu> + * Copyright (C) 2006 Mike McCormack + * Copyright (C) 2006 Christian Couder + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "cache.h" +#include "quote.h" + +/* Stolen from "imap-send.c". */ +static int git_vasprintf(char **strp, const char *fmt, va_list ap) +{ + int len; + char tmp[1024]; + + if ((len = vsnprintf(tmp, sizeof(tmp), fmt, ap)) < 0 || + !(*strp = xmalloc(len + 1))) + return -1; + if (len >= (int)sizeof(tmp)) + vsprintf(*strp, fmt, ap); + else + memcpy(*strp, tmp, len + 1); + return len; +} + +/* Stolen from "imap-send.c". */ +int nfvasprintf(char **str, const char *fmt, va_list va) +{ + int ret = git_vasprintf(str, fmt, va); + if (ret < 0) + die("Fatal: Out of memory\n"); + return ret; +} + +/* Get a trace file descriptor from GIT_TRACE env variable. */ +static int get_trace_fd(int *need_close) +{ + char *trace = getenv("GIT_TRACE"); + + if (!trace || !strcmp(trace, "") || + !strcmp(trace, "0") || !strcasecmp(trace, "false")) + return 0; + if (!strcmp(trace, "1") || !strcasecmp(trace, "true")) + return STDERR_FILENO; + if (strlen(trace) == 1 && isdigit(*trace)) + return atoi(trace); + if (*trace == '/') { + int fd = open(trace, O_WRONLY | O_APPEND | O_CREAT, 0666); + if (fd == -1) { + fprintf(stderr, + "Could not open '%s' for tracing: %s\n" + "Defaulting to tracing on stderr...\n", + trace, strerror(errno)); + return STDERR_FILENO; + } + *need_close = 1; + return fd; + } + + fprintf(stderr, "What does '%s' for GIT_TRACE means ?\n", trace); + fprintf(stderr, "If you want to trace into a file, " + "then please set GIT_TRACE to an absolute pathname " + "(starting with /).\n"); + fprintf(stderr, "Defaulting to tracing on stderr...\n"); + + return STDERR_FILENO; +} + +static const char err_msg[] = "Could not trace into fd given by " + "GIT_TRACE environment variable"; + +void trace_printf(const char *format, ...) +{ + char *trace_str; + va_list rest; + int need_close = 0; + int fd = get_trace_fd(&need_close); + + if (!fd) + return; + + va_start(rest, format); + nfvasprintf(&trace_str, format, rest); + va_end(rest); + + write_or_whine(fd, trace_str, strlen(trace_str), err_msg); + + free(trace_str); + + if (need_close) + close(fd); +} + +void trace_argv_printf(const char **argv, int count, const char *format, ...) +{ + char *argv_str, *format_str, *trace_str; + size_t argv_len, format_len, trace_len; + va_list rest; + int need_close = 0; + int fd = get_trace_fd(&need_close); + + if (!fd) + return; + + /* Get the argv string. */ + argv_str = sq_quote_argv(argv, count); + argv_len = strlen(argv_str); + + /* Get the formated string. */ + va_start(rest, format); + nfvasprintf(&format_str, format, rest); + va_end(rest); + + /* Allocate buffer for trace string. */ + format_len = strlen(format_str); + trace_len = argv_len + format_len + 1; /* + 1 for \n */ + trace_str = xmalloc(trace_len + 1); + + /* Copy everything into the trace string. */ + strncpy(trace_str, format_str, format_len); + strncpy(trace_str + format_len, argv_str, argv_len); + strcpy(trace_str + trace_len - 1, "\n"); + + write_or_whine(fd, trace_str, trace_len, err_msg); + + free(argv_str); + free(format_str); + free(trace_str); + + if (need_close) + close(fd); +} diff --git a/tree-diff.c b/tree-diff.c index 1cdf8aa908..7e2f4f088a 100644 --- a/tree-diff.c +++ b/tree-diff.c @@ -15,7 +15,8 @@ static char *malloc_base(const char *base, const char *path, int pathlen) return newbase; } -static int show_entry(struct diff_options *opt, const char *prefix, struct tree_desc *desc, const char *base); +static void show_entry(struct diff_options *opt, const char *prefix, struct tree_desc *desc, + const char *base); static int compare_tree_entry(struct tree_desc *t1, struct tree_desc *t2, const char *base, struct diff_options *opt) { @@ -38,8 +39,7 @@ static int compare_tree_entry(struct tree_desc *t1, struct tree_desc *t2, const show_entry(opt, "+", t2, base); return 1; } - if (!opt->find_copies_harder && - !memcmp(sha1, sha2, 20) && mode1 == mode2) + if (!opt->find_copies_harder && !hashcmp(sha1, sha2) && mode1 == mode2) return 0; /* @@ -131,7 +131,8 @@ static void show_tree(struct diff_options *opt, const char *prefix, struct tree_ } /* A file entry went away or appeared */ -static int show_entry(struct diff_options *opt, const char *prefix, struct tree_desc *desc, const char *base) +static void show_entry(struct diff_options *opt, const char *prefix, struct tree_desc *desc, + const char *base) { unsigned mode; const char *path; @@ -152,11 +153,9 @@ static int show_entry(struct diff_options *opt, const char *prefix, struct tree_ free(tree); free(newbase); - return 0; + } else { + opt->add_remove(opt, prefix[0], mode, sha1, base, path); } - - opt->add_remove(opt, prefix[0], mode, sha1, base, path); - return 0; } int diff_tree(struct tree_desc *t1, struct tree_desc *t2, const char *base, struct diff_options *opt) diff --git a/tree-walk.c b/tree-walk.c index 3f83e98f3a..14cc5aea6c 100644 --- a/tree-walk.c +++ b/tree-walk.c @@ -179,7 +179,7 @@ static int find_tree_entry(struct tree_desc *t, const char *name, unsigned char if (cmp < 0) break; if (entrylen == namelen) { - memcpy(result, sha1, 20); + hashcpy(result, sha1); return 0; } if (name[entrylen] != '/') @@ -187,7 +187,7 @@ static int find_tree_entry(struct tree_desc *t, const char *name, unsigned char if (!S_ISDIR(*mode)) break; if (++entrylen == namelen) { - memcpy(result, sha1, 20); + hashcpy(result, sha1); return 0; } return get_tree_entry(sha1, name + entrylen, result, mode); @@ -25,7 +25,7 @@ static int read_one_entry(const unsigned char *sha1, const char *base, int basel ce->ce_flags = create_ce_flags(baselen + len, stage); memcpy(ce->name, base, baselen); memcpy(ce->name + baselen, pathname, len+1); - memcpy(ce->sha1, sha1, 20); + hashcpy(ce->sha1, sha1); return add_cache_entry(ce, ADD_CACHE_OK_TO_ADD|ADD_CACHE_SKIP_DFCHECK); } @@ -144,7 +144,7 @@ struct tree *lookup_tree(const unsigned char *sha1) return (struct tree *) obj; } -static int track_tree_refs(struct tree *item) +static void track_tree_refs(struct tree *item) { int n_refs = 0, i; struct object_refs *refs; @@ -174,7 +174,6 @@ static int track_tree_refs(struct tree *item) refs->ref[i++] = obj; } set_object_refs(&item->object, refs); - return 0; } int parse_tree_buffer(struct tree *item, void *buffer, unsigned long size) diff --git a/unpack-trees.c b/unpack-trees.c new file mode 100644 index 0000000000..3ac0289b3a --- /dev/null +++ b/unpack-trees.c @@ -0,0 +1,799 @@ +#include <signal.h> +#include <sys/time.h> +#include "cache.h" +#include "tree.h" +#include "tree-walk.h" +#include "cache-tree.h" +#include "unpack-trees.h" + +#define DBRT_DEBUG 1 + +struct tree_entry_list { + struct tree_entry_list *next; + unsigned directory : 1; + unsigned executable : 1; + unsigned symlink : 1; + unsigned int mode; + const char *name; + const unsigned char *sha1; +}; + +static struct tree_entry_list *create_tree_entry_list(struct tree *tree) +{ + struct tree_desc desc; + struct name_entry one; + struct tree_entry_list *ret = NULL; + struct tree_entry_list **list_p = &ret; + + if (!tree->object.parsed) + parse_tree(tree); + + desc.buf = tree->buffer; + desc.size = tree->size; + + while (tree_entry(&desc, &one)) { + struct tree_entry_list *entry; + + entry = xmalloc(sizeof(struct tree_entry_list)); + entry->name = one.path; + entry->sha1 = one.sha1; + entry->mode = one.mode; + entry->directory = S_ISDIR(one.mode) != 0; + entry->executable = (one.mode & S_IXUSR) != 0; + entry->symlink = S_ISLNK(one.mode) != 0; + entry->next = NULL; + + *list_p = entry; + list_p = &entry->next; + } + return ret; +} + +static int entcmp(const char *name1, int dir1, const char *name2, int dir2) +{ + int len1 = strlen(name1); + int len2 = strlen(name2); + int len = len1 < len2 ? len1 : len2; + int ret = memcmp(name1, name2, len); + unsigned char c1, c2; + if (ret) + return ret; + c1 = name1[len]; + c2 = name2[len]; + if (!c1 && dir1) + c1 = '/'; + if (!c2 && dir2) + c2 = '/'; + ret = (c1 < c2) ? -1 : (c1 > c2) ? 1 : 0; + if (c1 && c2 && !ret) + ret = len1 - len2; + return ret; +} + +static int unpack_trees_rec(struct tree_entry_list **posns, int len, + const char *base, struct unpack_trees_options *o, + int *indpos, + struct tree_entry_list *df_conflict_list) +{ + int baselen = strlen(base); + int src_size = len + 1; + do { + int i; + const char *first; + int firstdir = 0; + int pathlen; + unsigned ce_size; + struct tree_entry_list **subposns; + struct cache_entry **src; + int any_files = 0; + int any_dirs = 0; + char *cache_name; + int ce_stage; + + /* Find the first name in the input. */ + + first = NULL; + cache_name = NULL; + + /* Check the cache */ + if (o->merge && *indpos < active_nr) { + /* This is a bit tricky: */ + /* If the index has a subdirectory (with + * contents) as the first name, it'll get a + * filename like "foo/bar". But that's after + * "foo", so the entry in trees will get + * handled first, at which point we'll go into + * "foo", and deal with "bar" from the index, + * because the base will be "foo/". The only + * way we can actually have "foo/bar" first of + * all the things is if the trees don't + * contain "foo" at all, in which case we'll + * handle "foo/bar" without going into the + * directory, but that's fine (and will return + * an error anyway, with the added unknown + * file case. + */ + + cache_name = active_cache[*indpos]->name; + if (strlen(cache_name) > baselen && + !memcmp(cache_name, base, baselen)) { + cache_name += baselen; + first = cache_name; + } else { + cache_name = NULL; + } + } + +#if DBRT_DEBUG > 1 + if (first) + printf("index %s\n", first); +#endif + for (i = 0; i < len; i++) { + if (!posns[i] || posns[i] == df_conflict_list) + continue; +#if DBRT_DEBUG > 1 + printf("%d %s\n", i + 1, posns[i]->name); +#endif + if (!first || entcmp(first, firstdir, + posns[i]->name, + posns[i]->directory) > 0) { + first = posns[i]->name; + firstdir = posns[i]->directory; + } + } + /* No name means we're done */ + if (!first) + return 0; + + pathlen = strlen(first); + ce_size = cache_entry_size(baselen + pathlen); + + src = xcalloc(src_size, sizeof(struct cache_entry *)); + + subposns = xcalloc(len, sizeof(struct tree_list_entry *)); + + if (cache_name && !strcmp(cache_name, first)) { + any_files = 1; + src[0] = active_cache[*indpos]; + remove_cache_entry_at(*indpos); + } + + for (i = 0; i < len; i++) { + struct cache_entry *ce; + + if (!posns[i] || + (posns[i] != df_conflict_list && + strcmp(first, posns[i]->name))) { + continue; + } + + if (posns[i] == df_conflict_list) { + src[i + o->merge] = o->df_conflict_entry; + continue; + } + + if (posns[i]->directory) { + struct tree *tree = lookup_tree(posns[i]->sha1); + any_dirs = 1; + parse_tree(tree); + subposns[i] = create_tree_entry_list(tree); + posns[i] = posns[i]->next; + src[i + o->merge] = o->df_conflict_entry; + continue; + } + + if (!o->merge) + ce_stage = 0; + else if (i + 1 < o->head_idx) + ce_stage = 1; + else if (i + 1 > o->head_idx) + ce_stage = 3; + else + ce_stage = 2; + + ce = xcalloc(1, ce_size); + ce->ce_mode = create_ce_mode(posns[i]->mode); + ce->ce_flags = create_ce_flags(baselen + pathlen, + ce_stage); + memcpy(ce->name, base, baselen); + memcpy(ce->name + baselen, first, pathlen + 1); + + any_files = 1; + + hashcpy(ce->sha1, posns[i]->sha1); + src[i + o->merge] = ce; + subposns[i] = df_conflict_list; + posns[i] = posns[i]->next; + } + if (any_files) { + if (o->merge) { + int ret; + +#if DBRT_DEBUG > 1 + printf("%s:\n", first); + for (i = 0; i < src_size; i++) { + printf(" %d ", i); + if (src[i]) + printf("%s\n", sha1_to_hex(src[i]->sha1)); + else + printf("\n"); + } +#endif + ret = o->fn(src, o); + +#if DBRT_DEBUG > 1 + printf("Added %d entries\n", ret); +#endif + *indpos += ret; + } else { + for (i = 0; i < src_size; i++) { + if (src[i]) { + add_cache_entry(src[i], ADD_CACHE_OK_TO_ADD|ADD_CACHE_SKIP_DFCHECK); + } + } + } + } + if (any_dirs) { + char *newbase = xmalloc(baselen + 2 + pathlen); + memcpy(newbase, base, baselen); + memcpy(newbase + baselen, first, pathlen); + newbase[baselen + pathlen] = '/'; + newbase[baselen + pathlen + 1] = '\0'; + if (unpack_trees_rec(subposns, len, newbase, o, + indpos, df_conflict_list)) + return -1; + free(newbase); + } + free(subposns); + free(src); + } while (1); +} + +/* Unlink the last component and attempt to remove leading + * directories, in case this unlink is the removal of the + * last entry in the directory -- empty directories are removed. + */ +static void unlink_entry(char *name) +{ + char *cp, *prev; + + if (unlink(name)) + return; + prev = NULL; + while (1) { + int status; + cp = strrchr(name, '/'); + if (prev) + *prev = '/'; + if (!cp) + break; + + *cp = 0; + status = rmdir(name); + if (status) { + *cp = '/'; + break; + } + prev = cp; + } +} + +static volatile sig_atomic_t progress_update; + +static void progress_interval(int signum) +{ + progress_update = 1; +} + +static void setup_progress_signal(void) +{ + struct sigaction sa; + struct itimerval v; + + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = progress_interval; + sigemptyset(&sa.sa_mask); + sa.sa_flags = SA_RESTART; + sigaction(SIGALRM, &sa, NULL); + + v.it_interval.tv_sec = 1; + v.it_interval.tv_usec = 0; + v.it_value = v.it_interval; + setitimer(ITIMER_REAL, &v, NULL); +} + +static struct checkout state; +static void check_updates(struct cache_entry **src, int nr, + struct unpack_trees_options *o) +{ + unsigned short mask = htons(CE_UPDATE); + unsigned last_percent = 200, cnt = 0, total = 0; + + if (o->update && o->verbose_update) { + for (total = cnt = 0; cnt < nr; cnt++) { + struct cache_entry *ce = src[cnt]; + if (!ce->ce_mode || ce->ce_flags & mask) + total++; + } + + /* Don't bother doing this for very small updates */ + if (total < 250) + total = 0; + + if (total) { + fprintf(stderr, "Checking files out...\n"); + setup_progress_signal(); + progress_update = 1; + } + cnt = 0; + } + + while (nr--) { + struct cache_entry *ce = *src++; + + if (total) { + if (!ce->ce_mode || ce->ce_flags & mask) { + unsigned percent; + cnt++; + percent = (cnt * 100) / total; + if (percent != last_percent || + progress_update) { + fprintf(stderr, "%4u%% (%u/%u) done\r", + percent, cnt, total); + last_percent = percent; + progress_update = 0; + } + } + } + if (!ce->ce_mode) { + if (o->update) + unlink_entry(ce->name); + continue; + } + if (ce->ce_flags & mask) { + ce->ce_flags &= ~mask; + if (o->update) + checkout_entry(ce, &state, NULL); + } + } + if (total) { + signal(SIGALRM, SIG_IGN); + fputc('\n', stderr); + } +} + +int unpack_trees(struct object_list *trees, struct unpack_trees_options *o) +{ + int indpos = 0; + unsigned len = object_list_length(trees); + struct tree_entry_list **posns; + int i; + struct object_list *posn = trees; + struct tree_entry_list df_conflict_list; + struct cache_entry df_conflict_entry; + + memset(&df_conflict_list, 0, sizeof(df_conflict_list)); + df_conflict_list.next = &df_conflict_list; + memset(&state, 0, sizeof(state)); + state.base_dir = ""; + state.force = 1; + state.quiet = 1; + state.refresh_cache = 1; + + o->merge_size = len; + memset(&df_conflict_entry, 0, sizeof(df_conflict_entry)); + o->df_conflict_entry = &df_conflict_entry; + + if (len) { + posns = xmalloc(len * sizeof(struct tree_entry_list *)); + for (i = 0; i < len; i++) { + posns[i] = create_tree_entry_list((struct tree *) posn->item); + posn = posn->next; + } + if (unpack_trees_rec(posns, len, o->prefix ? o->prefix : "", + o, &indpos, &df_conflict_list)) + return -1; + } + + if (o->trivial_merges_only && o->nontrivial_merge) + die("Merge requires file-level merging"); + + check_updates(active_cache, active_nr, o); + return 0; +} + +/* Here come the merge functions */ + +static void reject_merge(struct cache_entry *ce) +{ + die("Entry '%s' would be overwritten by merge. Cannot merge.", + ce->name); +} + +static int same(struct cache_entry *a, struct cache_entry *b) +{ + if (!!a != !!b) + return 0; + if (!a && !b) + return 1; + return a->ce_mode == b->ce_mode && + !hashcmp(a->sha1, b->sha1); +} + + +/* + * When a CE gets turned into an unmerged entry, we + * want it to be up-to-date + */ +static void verify_uptodate(struct cache_entry *ce, + struct unpack_trees_options *o) +{ + struct stat st; + + if (o->index_only || o->reset) + return; + + if (!lstat(ce->name, &st)) { + unsigned changed = ce_match_stat(ce, &st, 1); + if (!changed) + return; + errno = 0; + } + if (o->reset) { + ce->ce_flags |= htons(CE_UPDATE); + return; + } + if (errno == ENOENT) + return; + die("Entry '%s' not uptodate. Cannot merge.", ce->name); +} + +static void invalidate_ce_path(struct cache_entry *ce) +{ + if (ce) + cache_tree_invalidate_path(active_cache_tree, ce->name); +} + +/* + * We do not want to remove or overwrite a working tree file that + * is not tracked. + */ +static void verify_absent(const char *path, const char *action, + struct unpack_trees_options *o) +{ + struct stat st; + + if (o->index_only || o->reset || !o->update) + return; + if (!lstat(path, &st)) + die("Untracked working tree file '%s' " + "would be %s by merge.", path, action); +} + +static int merged_entry(struct cache_entry *merge, struct cache_entry *old, + struct unpack_trees_options *o) +{ + merge->ce_flags |= htons(CE_UPDATE); + if (old) { + /* + * See if we can re-use the old CE directly? + * That way we get the uptodate stat info. + * + * This also removes the UPDATE flag on + * a match. + */ + if (same(old, merge)) { + *merge = *old; + } else { + verify_uptodate(old, o); + invalidate_ce_path(old); + } + } + else { + verify_absent(merge->name, "overwritten", o); + invalidate_ce_path(merge); + } + + merge->ce_flags &= ~htons(CE_STAGEMASK); + add_cache_entry(merge, ADD_CACHE_OK_TO_ADD|ADD_CACHE_OK_TO_REPLACE); + return 1; +} + +static int deleted_entry(struct cache_entry *ce, struct cache_entry *old, + struct unpack_trees_options *o) +{ + if (old) + verify_uptodate(old, o); + else + verify_absent(ce->name, "removed", o); + ce->ce_mode = 0; + add_cache_entry(ce, ADD_CACHE_OK_TO_ADD|ADD_CACHE_OK_TO_REPLACE); + invalidate_ce_path(ce); + return 1; +} + +static int keep_entry(struct cache_entry *ce) +{ + add_cache_entry(ce, ADD_CACHE_OK_TO_ADD); + return 1; +} + +#if DBRT_DEBUG +static void show_stage_entry(FILE *o, + const char *label, const struct cache_entry *ce) +{ + if (!ce) + fprintf(o, "%s (missing)\n", label); + else + fprintf(o, "%s%06o %s %d\t%s\n", + label, + ntohl(ce->ce_mode), + sha1_to_hex(ce->sha1), + ce_stage(ce), + ce->name); +} +#endif + +int threeway_merge(struct cache_entry **stages, + struct unpack_trees_options *o) +{ + struct cache_entry *index; + struct cache_entry *head; + struct cache_entry *remote = stages[o->head_idx + 1]; + int count; + int head_match = 0; + int remote_match = 0; + const char *path = NULL; + + int df_conflict_head = 0; + int df_conflict_remote = 0; + + int any_anc_missing = 0; + int no_anc_exists = 1; + int i; + + for (i = 1; i < o->head_idx; i++) { + if (!stages[i]) + any_anc_missing = 1; + else { + if (!path) + path = stages[i]->name; + no_anc_exists = 0; + } + } + + index = stages[0]; + head = stages[o->head_idx]; + + if (head == o->df_conflict_entry) { + df_conflict_head = 1; + head = NULL; + } + + if (remote == o->df_conflict_entry) { + df_conflict_remote = 1; + remote = NULL; + } + + if (!path && index) + path = index->name; + if (!path && head) + path = head->name; + if (!path && remote) + path = remote->name; + + /* First, if there's a #16 situation, note that to prevent #13 + * and #14. + */ + if (!same(remote, head)) { + for (i = 1; i < o->head_idx; i++) { + if (same(stages[i], head)) { + head_match = i; + } + if (same(stages[i], remote)) { + remote_match = i; + } + } + } + + /* We start with cases where the index is allowed to match + * something other than the head: #14(ALT) and #2ALT, where it + * is permitted to match the result instead. + */ + /* #14, #14ALT, #2ALT */ + if (remote && !df_conflict_head && head_match && !remote_match) { + if (index && !same(index, remote) && !same(index, head)) + reject_merge(index); + return merged_entry(remote, index, o); + } + /* + * If we have an entry in the index cache, then we want to + * make sure that it matches head. + */ + if (index && !same(index, head)) { + reject_merge(index); + } + + if (head) { + /* #5ALT, #15 */ + if (same(head, remote)) + return merged_entry(head, index, o); + /* #13, #3ALT */ + if (!df_conflict_remote && remote_match && !head_match) + return merged_entry(head, index, o); + } + + /* #1 */ + if (!head && !remote && any_anc_missing) + return 0; + + /* Under the new "aggressive" rule, we resolve mostly trivial + * cases that we historically had git-merge-one-file resolve. + */ + if (o->aggressive) { + int head_deleted = !head && !df_conflict_head; + int remote_deleted = !remote && !df_conflict_remote; + /* + * Deleted in both. + * Deleted in one and unchanged in the other. + */ + if ((head_deleted && remote_deleted) || + (head_deleted && remote && remote_match) || + (remote_deleted && head && head_match)) { + if (index) + return deleted_entry(index, index, o); + else if (path) + verify_absent(path, "removed", o); + return 0; + } + /* + * Added in both, identically. + */ + if (no_anc_exists && head && remote && same(head, remote)) + return merged_entry(head, index, o); + + } + + /* Below are "no merge" cases, which require that the index be + * up-to-date to avoid the files getting overwritten with + * conflict resolution files. + */ + if (index) { + verify_uptodate(index, o); + } + else if (path) + verify_absent(path, "overwritten", o); + + o->nontrivial_merge = 1; + + /* #2, #3, #4, #6, #7, #9, #11. */ + count = 0; + if (!head_match || !remote_match) { + for (i = 1; i < o->head_idx; i++) { + if (stages[i]) { + keep_entry(stages[i]); + count++; + break; + } + } + } +#if DBRT_DEBUG + else { + fprintf(stderr, "read-tree: warning #16 detected\n"); + show_stage_entry(stderr, "head ", stages[head_match]); + show_stage_entry(stderr, "remote ", stages[remote_match]); + } +#endif + if (head) { count += keep_entry(head); } + if (remote) { count += keep_entry(remote); } + return count; +} + +/* + * Two-way merge. + * + * The rule is to "carry forward" what is in the index without losing + * information across a "fast forward", favoring a successful merge + * over a merge failure when it makes sense. For details of the + * "carry forward" rule, please see <Documentation/git-read-tree.txt>. + * + */ +int twoway_merge(struct cache_entry **src, + struct unpack_trees_options *o) +{ + struct cache_entry *current = src[0]; + struct cache_entry *oldtree = src[1], *newtree = src[2]; + + if (o->merge_size != 2) + return error("Cannot do a twoway merge of %d trees", + o->merge_size); + + if (current) { + if ((!oldtree && !newtree) || /* 4 and 5 */ + (!oldtree && newtree && + same(current, newtree)) || /* 6 and 7 */ + (oldtree && newtree && + same(oldtree, newtree)) || /* 14 and 15 */ + (oldtree && newtree && + !same(oldtree, newtree) && /* 18 and 19*/ + same(current, newtree))) { + return keep_entry(current); + } + else if (oldtree && !newtree && same(current, oldtree)) { + /* 10 or 11 */ + return deleted_entry(oldtree, current, o); + } + else if (oldtree && newtree && + same(current, oldtree) && !same(current, newtree)) { + /* 20 or 21 */ + return merged_entry(newtree, current, o); + } + else { + /* all other failures */ + if (oldtree) + reject_merge(oldtree); + if (current) + reject_merge(current); + if (newtree) + reject_merge(newtree); + return -1; + } + } + else if (newtree) + return merged_entry(newtree, current, o); + else + return deleted_entry(oldtree, current, o); +} + +/* + * Bind merge. + * + * Keep the index entries at stage0, collapse stage1 but make sure + * stage0 does not have anything there. + */ +int bind_merge(struct cache_entry **src, + struct unpack_trees_options *o) +{ + struct cache_entry *old = src[0]; + struct cache_entry *a = src[1]; + + if (o->merge_size != 1) + return error("Cannot do a bind merge of %d trees\n", + o->merge_size); + if (a && old) + die("Entry '%s' overlaps. Cannot bind.", a->name); + if (!a) + return keep_entry(old); + else + return merged_entry(a, NULL, o); +} + +/* + * One-way merge. + * + * The rule is: + * - take the stat information from stage0, take the data from stage1 + */ +int oneway_merge(struct cache_entry **src, + struct unpack_trees_options *o) +{ + struct cache_entry *old = src[0]; + struct cache_entry *a = src[1]; + + if (o->merge_size != 1) + return error("Cannot do a oneway merge of %d trees", + o->merge_size); + + if (!a) + return deleted_entry(old, old, o); + if (old && same(old, a)) { + if (o->reset) { + struct stat st; + if (lstat(old->name, &st) || + ce_match_stat(old, &st, 1)) + old->ce_flags |= htons(CE_UPDATE); + } + return keep_entry(old); + } + return merged_entry(a, old, o); +} diff --git a/unpack-trees.h b/unpack-trees.h new file mode 100644 index 0000000000..c4601621cd --- /dev/null +++ b/unpack-trees.h @@ -0,0 +1,35 @@ +#ifndef UNPACK_TREES_H +#define UNPACK_TREES_H + +struct unpack_trees_options; + +typedef int (*merge_fn_t)(struct cache_entry **src, + struct unpack_trees_options *options); + +struct unpack_trees_options { + int reset; + int merge; + int update; + int index_only; + int nontrivial_merge; + int trivial_merges_only; + int verbose_update; + int aggressive; + const char *prefix; + merge_fn_t fn; + + int head_idx; + int merge_size; + + struct cache_entry *df_conflict_entry; +}; + +extern int unpack_trees(struct object_list *trees, + struct unpack_trees_options *options); + +int threeway_merge(struct cache_entry **stages, struct unpack_trees_options *o); +int twoway_merge(struct cache_entry **src, struct unpack_trees_options *o); +int bind_merge(struct cache_entry **src, struct unpack_trees_options *o); +int oneway_merge(struct cache_entry **src, struct unpack_trees_options *o); + +#endif diff --git a/upload-pack.c b/upload-pack.c index 07ecdb4281..189b239cc0 100644 --- a/upload-pack.c +++ b/upload-pack.c @@ -4,6 +4,7 @@ #include "cache.h" #include "refs.h" #include "pkt-line.h" +#include "sideband.h" #include "tag.h" #include "object.h" #include "commit.h" @@ -14,14 +15,15 @@ static const char upload_pack_usage[] = "git-upload-pack [--strict] [--timeout=n #define THEY_HAVE (1U << 0) #define OUR_REF (1U << 1) #define WANTED (1U << 2) -#define MAX_HAS 256 -#define MAX_NEEDS 256 -static int nr_has = 0, nr_needs = 0, multi_ack = 0, nr_our_refs = 0; -static int use_thin_pack = 0; -static unsigned char has_sha1[MAX_HAS][20]; -static unsigned char needs_sha1[MAX_NEEDS][20]; -static unsigned int timeout = 0; -static int use_sideband = 0; +static int multi_ack, nr_our_refs; +static int use_thin_pack; +static struct object_array have_obj; +static struct object_array want_obj; +static unsigned int timeout; +/* 0 for no sideband, + * otherwise maximum packet size (up to 65520 bytes). + */ +static int use_sideband; static void reset_timeout(void) { @@ -35,45 +37,18 @@ static int strip(char *line, int len) return len; } -#define PACKET_MAX 1000 static ssize_t send_client_data(int fd, const char *data, ssize_t sz) { - ssize_t ssz; - const char *p; - - if (!data) { - if (!use_sideband) - return 0; - packet_flush(1); - } - - if (!use_sideband) { - if (fd == 3) - /* emergency quit */ - fd = 2; - if (fd == 2) { - xwrite(fd, data, sz); - return sz; - } - return safe_write(fd, data, sz); + if (use_sideband) + return send_sideband(1, fd, data, sz, use_sideband); + if (fd == 3) + /* emergency quit */ + fd = 2; + if (fd == 2) { + xwrite(fd, data, sz); + return sz; } - p = data; - ssz = sz; - while (sz) { - unsigned n; - char hdr[5]; - - n = sz; - if (PACKET_MAX - 5 < n) - n = PACKET_MAX - 5; - sprintf(hdr, "%04x", n + 5); - hdr[4] = fd; - safe_write(1, hdr, 5); - safe_write(1, p, n); - p += n; - sz -= n; - } - return ssz; + return safe_write(fd, data, sz); } static void create_pack_file(void) @@ -83,7 +58,7 @@ static void create_pack_file(void) */ int lp_pipe[2], pu_pipe[2], pe_pipe[2]; pid_t pid_rev_list, pid_pack_objects; - int create_full_pack = (nr_our_refs == nr_needs && !nr_has); + int create_full_pack = (nr_our_refs == want_obj.nr && !have_obj.nr); char data[8193], progress[128]; char abort_msg[] = "aborting due to possible repository " "corruption on the remote side."; @@ -107,7 +82,7 @@ static void create_pack_file(void) use_thin_pack = 0; /* no point doing it */ } else - args = nr_has + nr_needs + 5; + args = have_obj.nr + want_obj.nr + 5; p = xmalloc(args * sizeof(char *)); argv = (const char **) p; buf = xmalloc(args * 45); @@ -118,20 +93,22 @@ static void create_pack_file(void) close(lp_pipe[1]); *p++ = "rev-list"; *p++ = use_thin_pack ? "--objects-edge" : "--objects"; - if (create_full_pack || MAX_NEEDS <= nr_needs) + if (create_full_pack) *p++ = "--all"; else { - for (i = 0; i < nr_needs; i++) { + for (i = 0; i < want_obj.nr; i++) { + struct object *o = want_obj.objects[i].item; *p++ = buf; - memcpy(buf, sha1_to_hex(needs_sha1[i]), 41); + memcpy(buf, sha1_to_hex(o->sha1), 41); buf += 41; } } if (!create_full_pack) - for (i = 0; i < nr_has; i++) { + for (i = 0; i < have_obj.nr; i++) { + struct object *o = have_obj.objects[i].item; *p++ = buf; *buf++ = '^'; - memcpy(buf, sha1_to_hex(has_sha1[i]), 41); + memcpy(buf, sha1_to_hex(o->sha1), 41); buf += 41; } *p++ = NULL; @@ -308,7 +285,8 @@ static void create_pack_file(void) goto fail; fprintf(stderr, "flushed.\n"); } - send_client_data(1, NULL, 0); + if (use_sideband) + packet_flush(1); return; } fail: @@ -322,35 +300,37 @@ static void create_pack_file(void) static int got_sha1(char *hex, unsigned char *sha1) { + struct object *o; + if (get_sha1_hex(hex, sha1)) die("git-upload-pack: expected SHA1 object, got '%s'", hex); if (!has_sha1_file(sha1)) return 0; - if (nr_has < MAX_HAS) { - struct object *o = lookup_object(sha1); - if (!(o && o->parsed)) - o = parse_object(sha1); - if (!o) - die("oops (%s)", sha1_to_hex(sha1)); - if (o->type == OBJ_COMMIT) { - struct commit_list *parents; - if (o->flags & THEY_HAVE) - return 0; - o->flags |= THEY_HAVE; - for (parents = ((struct commit*)o)->parents; - parents; - parents = parents->next) - parents->item->object.flags |= THEY_HAVE; - } - memcpy(has_sha1[nr_has++], sha1, 20); + + o = lookup_object(sha1); + if (!(o && o->parsed)) + o = parse_object(sha1); + if (!o) + die("oops (%s)", sha1_to_hex(sha1)); + if (o->type == OBJ_COMMIT) { + struct commit_list *parents; + if (o->flags & THEY_HAVE) + return 0; + o->flags |= THEY_HAVE; + for (parents = ((struct commit*)o)->parents; + parents; + parents = parents->next) + parents->item->object.flags |= THEY_HAVE; } + add_object_array(o, NULL, &have_obj); return 1; } static int get_common_commits(void) { static char line[1000]; - unsigned char sha1[20], last_sha1[20]; + unsigned char sha1[20]; + char hex[41], last_hex[41]; int len; track_object_refs = 0; @@ -361,29 +341,28 @@ static int get_common_commits(void) reset_timeout(); if (!len) { - if (nr_has == 0 || multi_ack) + if (have_obj.nr == 0 || multi_ack) packet_write(1, "NAK\n"); continue; } len = strip(line, len); if (!strncmp(line, "have ", 5)) { - if (got_sha1(line+5, sha1) && - (multi_ack || nr_has == 1)) { - if (nr_has >= MAX_HAS) - multi_ack = 0; - packet_write(1, "ACK %s%s\n", - sha1_to_hex(sha1), - multi_ack ? " continue" : ""); - if (multi_ack) - memcpy(last_sha1, sha1, 20); + if (got_sha1(line+5, sha1)) { + memcpy(hex, sha1_to_hex(sha1), 41); + if (multi_ack) { + const char *msg = "ACK %s continue\n"; + packet_write(1, msg, hex); + memcpy(last_hex, hex, 41); + } + else if (have_obj.nr == 1) + packet_write(1, "ACK %s\n", hex); } continue; } if (!strcmp(line, "done")) { - if (nr_has > 0) { + if (have_obj.nr > 0) { if (multi_ack) - packet_write(1, "ACK %s\n", - sha1_to_hex(last_sha1)); + packet_write(1, "ACK %s\n", last_hex); return 0; } packet_write(1, "NAK\n"); @@ -393,39 +372,31 @@ static int get_common_commits(void) } } -static int receive_needs(void) +static void receive_needs(void) { static char line[1000]; - int len, needs; + int len; - needs = 0; for (;;) { struct object *o; - unsigned char dummy[20], *sha1_buf; + unsigned char sha1_buf[20]; len = packet_read_line(0, line, sizeof(line)); reset_timeout(); if (!len) - return needs; - - sha1_buf = dummy; - if (needs == MAX_NEEDS) { - fprintf(stderr, - "warning: supporting only a max of %d requests. " - "sending everything instead.\n", - MAX_NEEDS); - } - else if (needs < MAX_NEEDS) - sha1_buf = needs_sha1[needs]; + return; - if (strncmp("want ", line, 5) || get_sha1_hex(line+5, sha1_buf)) + if (strncmp("want ", line, 5) || + get_sha1_hex(line+5, sha1_buf)) die("git-upload-pack: protocol error, " "expected to get sha, not '%s'", line); if (strstr(line+45, "multi_ack")) multi_ack = 1; if (strstr(line+45, "thin-pack")) use_thin_pack = 1; - if (strstr(line+45, "side-band")) - use_sideband = 1; + if (strstr(line+45, "side-band-64k")) + use_sideband = LARGE_PACKET_MAX; + else if (strstr(line+45, "side-band")) + use_sideband = DEFAULT_PACKET_MAX; /* We have sent all our refs already, and the other end * should have chosen out of them; otherwise they are @@ -440,14 +411,14 @@ static int receive_needs(void) die("git-upload-pack: not our ref %s", line+5); if (!(o->flags & WANTED)) { o->flags |= WANTED; - needs++; + add_object_array(o, NULL, &want_obj); } } } static int send_ref(const char *refname, const unsigned char *sha1) { - static const char *capabilities = "multi_ack thin-pack side-band"; + static const char *capabilities = "multi_ack thin-pack side-band side-band-64k"; struct object *o = parse_object(sha1); if (!o) @@ -470,18 +441,17 @@ static int send_ref(const char *refname, const unsigned char *sha1) return 0; } -static int upload_pack(void) +static void upload_pack(void) { reset_timeout(); head_ref(send_ref); for_each_ref(send_ref); packet_flush(1); - nr_needs = receive_needs(); - if (!nr_needs) - return 0; - get_common_commits(); - create_pack_file(); - return 0; + receive_needs(); + if (want_obj.nr) { + get_common_commits(); + create_pack_file(); + } } int main(int argc, char **argv) diff --git a/write_or_die.c b/write_or_die.c new file mode 100644 index 0000000000..bfe4eeb649 --- /dev/null +++ b/write_or_die.c @@ -0,0 +1,45 @@ +#include "cache.h" + +void write_or_die(int fd, const void *buf, size_t count) +{ + const char *p = buf; + ssize_t written; + + while (count > 0) { + written = xwrite(fd, p, count); + if (written == 0) + die("disk full?"); + else if (written < 0) { + if (errno == EPIPE) + exit(0); + die("write error (%s)", strerror(errno)); + } + count -= written; + p += written; + } +} + +int write_or_whine(int fd, const void *buf, size_t count, const char *msg) +{ + const char *p = buf; + ssize_t written; + + while (count > 0) { + written = xwrite(fd, p, count); + if (written == 0) { + fprintf(stderr, "%s: disk full?\n", msg); + return 0; + } + else if (written < 0) { + if (errno == EPIPE) + exit(0); + fprintf(stderr, "%s: write error (%s)\n", + msg, strerror(errno)); + return 0; + } + count -= written; + p += written; + } + + return 1; +} diff --git a/wt-status.c b/wt-status.c new file mode 100644 index 0000000000..4b74e68584 --- /dev/null +++ b/wt-status.c @@ -0,0 +1,276 @@ +#include "wt-status.h" +#include "color.h" +#include "cache.h" +#include "object.h" +#include "dir.h" +#include "commit.h" +#include "diff.h" +#include "revision.h" +#include "diffcore.h" + +int wt_status_use_color = 0; +static char wt_status_colors[][COLOR_MAXLEN] = { + "", /* WT_STATUS_HEADER: normal */ + "\033[32m", /* WT_STATUS_UPDATED: green */ + "\033[31m", /* WT_STATUS_CHANGED: red */ + "\033[31m", /* WT_STATUS_UNTRACKED: red */ +}; + +static int parse_status_slot(const char *var, int offset) +{ + if (!strcasecmp(var+offset, "header")) + return WT_STATUS_HEADER; + if (!strcasecmp(var+offset, "updated")) + return WT_STATUS_UPDATED; + if (!strcasecmp(var+offset, "changed")) + return WT_STATUS_CHANGED; + if (!strcasecmp(var+offset, "untracked")) + return WT_STATUS_UNTRACKED; + die("bad config variable '%s'", var); +} + +static const char* color(int slot) +{ + return wt_status_use_color ? wt_status_colors[slot] : ""; +} + +void wt_status_prepare(struct wt_status *s) +{ + unsigned char sha1[20]; + const char *head; + + s->is_initial = get_sha1("HEAD", sha1) ? 1 : 0; + + head = resolve_ref(git_path("HEAD"), sha1, 0); + s->branch = head ? + strdup(head + strlen(get_git_dir()) + 1) : + NULL; + + s->reference = "HEAD"; + s->amend = 0; + s->verbose = 0; + s->commitable = 0; + s->untracked = 0; +} + +static void wt_status_print_header(const char *main, const char *sub) +{ + const char *c = color(WT_STATUS_HEADER); + color_printf_ln(c, "# %s:", main); + color_printf_ln(c, "# (%s)", sub); + color_printf_ln(c, "#"); +} + +static void wt_status_print_trailer(void) +{ + color_printf_ln(color(WT_STATUS_HEADER), "#"); +} + +static void wt_status_print_filepair(int t, struct diff_filepair *p) +{ + const char *c = color(t); + color_printf(color(WT_STATUS_HEADER), "#\t"); + switch (p->status) { + case DIFF_STATUS_ADDED: + color_printf(c, "new file: %s", p->one->path); break; + case DIFF_STATUS_COPIED: + color_printf(c, "copied: %s -> %s", + p->one->path, p->two->path); + break; + case DIFF_STATUS_DELETED: + color_printf(c, "deleted: %s", p->one->path); break; + case DIFF_STATUS_MODIFIED: + color_printf(c, "modified: %s", p->one->path); break; + case DIFF_STATUS_RENAMED: + color_printf(c, "renamed: %s -> %s", + p->one->path, p->two->path); + break; + case DIFF_STATUS_TYPE_CHANGED: + color_printf(c, "typechange: %s", p->one->path); break; + case DIFF_STATUS_UNKNOWN: + color_printf(c, "unknown: %s", p->one->path); break; + case DIFF_STATUS_UNMERGED: + color_printf(c, "unmerged: %s", p->one->path); break; + default: + die("bug: unhandled diff status %c", p->status); + } + printf("\n"); +} + +static void wt_status_print_updated_cb(struct diff_queue_struct *q, + struct diff_options *options, + void *data) +{ + struct wt_status *s = data; + int shown_header = 0; + int i; + if (q->nr) { + } + for (i = 0; i < q->nr; i++) { + if (q->queue[i]->status == 'U') + continue; + if (!shown_header) { + wt_status_print_header("Updated but not checked in", + "will commit"); + s->commitable = 1; + shown_header = 1; + } + wt_status_print_filepair(WT_STATUS_UPDATED, q->queue[i]); + } + if (shown_header) + wt_status_print_trailer(); +} + +static void wt_status_print_changed_cb(struct diff_queue_struct *q, + struct diff_options *options, + void *data) +{ + int i; + if (q->nr) + wt_status_print_header("Changed but not updated", + "use git-update-index to mark for commit"); + for (i = 0; i < q->nr; i++) + wt_status_print_filepair(WT_STATUS_CHANGED, q->queue[i]); + if (q->nr) + wt_status_print_trailer(); +} + +void wt_status_print_initial(struct wt_status *s) +{ + int i; + read_cache(); + if (active_nr) { + s->commitable = 1; + wt_status_print_header("Updated but not checked in", + "will commit"); + } + for (i = 0; i < active_nr; i++) { + color_printf(color(WT_STATUS_HEADER), "#\t"); + color_printf_ln(color(WT_STATUS_UPDATED), "new file: %s", + active_cache[i]->name); + } + if (active_nr) + wt_status_print_trailer(); +} + +static void wt_status_print_updated(struct wt_status *s) +{ + struct rev_info rev; + const char *argv[] = { NULL, NULL, NULL }; + argv[1] = s->reference; + init_revisions(&rev, NULL); + setup_revisions(2, argv, &rev, NULL); + rev.diffopt.output_format |= DIFF_FORMAT_CALLBACK; + rev.diffopt.format_callback = wt_status_print_updated_cb; + rev.diffopt.format_callback_data = s; + rev.diffopt.detect_rename = 1; + run_diff_index(&rev, 1); +} + +static void wt_status_print_changed(struct wt_status *s) +{ + struct rev_info rev; + const char *argv[] = { NULL, NULL }; + init_revisions(&rev, ""); + setup_revisions(1, argv, &rev, NULL); + rev.diffopt.output_format |= DIFF_FORMAT_CALLBACK; + rev.diffopt.format_callback = wt_status_print_changed_cb; + rev.diffopt.format_callback_data = s; + run_diff_files(&rev, 0); +} + +static void wt_status_print_untracked(const struct wt_status *s) +{ + struct dir_struct dir; + const char *x; + int i; + int shown_header = 0; + + memset(&dir, 0, sizeof(dir)); + + dir.exclude_per_dir = ".gitignore"; + if (!s->untracked) { + dir.show_other_directories = 1; + dir.hide_empty_directories = 1; + } + x = git_path("info/exclude"); + if (file_exists(x)) + add_excludes_from_file(&dir, x); + + read_directory(&dir, ".", "", 0); + for(i = 0; i < dir.nr; i++) { + /* check for matching entry, which is unmerged; lifted from + * builtin-ls-files:show_other_files */ + struct dir_entry *ent = dir.entries[i]; + int pos = cache_name_pos(ent->name, ent->len); + struct cache_entry *ce; + if (0 <= pos) + die("bug in wt_status_print_untracked"); + pos = -pos - 1; + if (pos < active_nr) { + ce = active_cache[pos]; + if (ce_namelen(ce) == ent->len && + !memcmp(ce->name, ent->name, ent->len)) + continue; + } + if (!shown_header) { + wt_status_print_header("Untracked files", + "use \"git add\" to add to commit"); + shown_header = 1; + } + color_printf(color(WT_STATUS_HEADER), "#\t"); + color_printf_ln(color(WT_STATUS_UNTRACKED), "%.*s", + ent->len, ent->name); + } +} + +static void wt_status_print_verbose(struct wt_status *s) +{ + struct rev_info rev; + const char *argv[] = { NULL, NULL, NULL }; + argv[1] = s->reference; + init_revisions(&rev, NULL); + setup_revisions(2, argv, &rev, NULL); + rev.diffopt.output_format |= DIFF_FORMAT_PATCH; + rev.diffopt.detect_rename = 1; + run_diff_index(&rev, 1); +} + +void wt_status_print(struct wt_status *s) +{ + if (s->branch && strcmp(s->branch, "refs/heads/master")) + color_printf_ln(color(WT_STATUS_HEADER), + "# On branch %s", s->branch); + + if (s->is_initial) { + color_printf_ln(color(WT_STATUS_HEADER), "#"); + color_printf_ln(color(WT_STATUS_HEADER), "# Initial commit"); + color_printf_ln(color(WT_STATUS_HEADER), "#"); + wt_status_print_initial(s); + } + else { + wt_status_print_updated(s); + discard_cache(); + } + + wt_status_print_changed(s); + wt_status_print_untracked(s); + + if (s->verbose && !s->is_initial) + wt_status_print_verbose(s); + if (!s->commitable) + printf("%s\n", s->amend ? "# No changes" : "nothing to commit"); +} + +int git_status_config(const char *k, const char *v) +{ + if (!strcmp(k, "status.color")) { + wt_status_use_color = git_config_colorbool(k, v); + return 0; + } + if (!strncmp(k, "status.color.", 13)) { + int slot = parse_status_slot(k, 13); + color_parse(v, k, wt_status_colors[slot]); + } + return git_default_config(k, v); +} diff --git a/wt-status.h b/wt-status.h new file mode 100644 index 0000000000..0a5a5b7ba9 --- /dev/null +++ b/wt-status.h @@ -0,0 +1,25 @@ +#ifndef STATUS_H +#define STATUS_H + +enum color_wt_status { + WT_STATUS_HEADER, + WT_STATUS_UPDATED, + WT_STATUS_CHANGED, + WT_STATUS_UNTRACKED, +}; + +struct wt_status { + int is_initial; + char *branch; + const char *reference; + int commitable; + int verbose; + int amend; + int untracked; +}; + +int git_status_config(const char *var, const char *value); +void wt_status_prepare(struct wt_status *s); +void wt_status_print(struct wt_status *s); + +#endif /* STATUS_H */ diff --git a/xdiff-interface.c b/xdiff-interface.c index 6a82da73b6..08602f5221 100644 --- a/xdiff-interface.c +++ b/xdiff-interface.c @@ -69,9 +69,9 @@ int xdiff_outf(void *priv_, mmbuffer_t *mb, int nbuf) for (i = 0; i < nbuf; i++) { if (mb[i].ptr[mb[i].size-1] != '\n') { /* Incomplete line */ - priv->remainder = realloc(priv->remainder, - priv->remainder_size + - mb[i].size); + priv->remainder = xrealloc(priv->remainder, + priv->remainder_size + + mb[i].size); memcpy(priv->remainder + priv->remainder_size, mb[i].ptr, mb[i].size); priv->remainder_size += mb[i].size; @@ -83,9 +83,9 @@ int xdiff_outf(void *priv_, mmbuffer_t *mb, int nbuf) consume_one(priv, mb[i].ptr, mb[i].size); continue; } - priv->remainder = realloc(priv->remainder, - priv->remainder_size + - mb[i].size); + priv->remainder = xrealloc(priv->remainder, + priv->remainder_size + + mb[i].size); memcpy(priv->remainder + priv->remainder_size, mb[i].ptr, mb[i].size); consume_one(priv, priv->remainder, diff --git a/xdiff/xutils.c b/xdiff/xutils.c index f7bdd395ad..9e4bb47ee9 100644 --- a/xdiff/xutils.c +++ b/xdiff/xutils.c @@ -191,36 +191,30 @@ int xdl_recmatch(const char *l1, long s1, const char *l2, long s2, long flags) int i1, i2; if (flags & XDF_IGNORE_WHITESPACE) { - for (i1 = i2 = 0; i1 < s1 && i2 < s2; i1++, i2++) { + for (i1 = i2 = 0; i1 < s1 && i2 < s2; ) { if (isspace(l1[i1])) while (isspace(l1[i1]) && i1 < s1) i1++; - else if (isspace(l2[i2])) + if (isspace(l2[i2])) while (isspace(l2[i2]) && i2 < s2) i2++; - else if (l1[i1] != l2[i2]) - return l2[i2] - l1[i1]; + if (i1 < s1 && i2 < s2 && l1[i1++] != l2[i2++]) + return 0; } - if (i1 >= s1) - return 1; - else if (i2 >= s2) - return -1; + return (i1 >= s1 && i2 >= s2); } else if (flags & XDF_IGNORE_WHITESPACE_CHANGE) { - for (i1 = i2 = 0; i1 < s1 && i2 < s2; i1++, i2++) { + for (i1 = i2 = 0; i1 < s1 && i2 < s2; ) { if (isspace(l1[i1])) { if (!isspace(l2[i2])) - return -1; + return 0; while (isspace(l1[i1]) && i1 < s1) i1++; while (isspace(l2[i2]) && i2 < s2) i2++; - } else if (l1[i1] != l2[i2]) - return l2[i2] - l1[i1]; + } else if (l1[i1++] != l2[i2++]) + return 0; } - if (i1 >= s1) - return 1; - else if (i2 >= s2) - return -1; + return (i1 >= s1 && i2 >= s2); } else return s1 == s2 && !memcmp(l1, l2, s1); @@ -233,7 +227,8 @@ unsigned long xdl_hash_record(char const **data, char const *top, long flags) { for (; ptr < top && *ptr != '\n'; ptr++) { if (isspace(*ptr) && (flags & XDF_WHITESPACE_FLAGS)) { - while (ptr < top && isspace(*ptr) && ptr[1] != '\n') + while (ptr + 1 < top && isspace(ptr[1]) + && ptr[1] != '\n') ptr++; if (flags & XDF_IGNORE_WHITESPACE_CHANGE) { ha += (ha << 5); |