diff options
138 files changed, 5616 insertions, 3401 deletions
diff --git a/.gitignore b/.gitignore index 52d61f3193..55cd9844d6 100644 --- a/.gitignore +++ b/.gitignore @@ -125,6 +125,7 @@ git-verify-tag git-whatchanged git-write-tree git-core-*/?* +gitweb/gitweb.cgi test-date test-delta test-dump-cache-tree @@ -137,4 +138,10 @@ git-core.spec *.[ao] *.py[co] config.mak +autom4te.cache +config.log +config.status +config.mak.autogen +config.mak.append +configure git-blame diff --git a/Documentation/Makefile b/Documentation/Makefile index 6dbe45b506..0d9ffb4ad9 100644 --- a/Documentation/Makefile +++ b/Documentation/Makefile @@ -25,10 +25,10 @@ DOC_MAN1=$(patsubst %.txt,%.1,$(MAN1_TXT)) DOC_MAN7=$(patsubst %.txt,%.7,$(MAN7_TXT)) prefix?=$(HOME) -bin=$(prefix)/bin -mandir=$(prefix)/man -man1=$(mandir)/man1 -man7=$(mandir)/man7 +bindir?=$(prefix)/bin +mandir?=$(prefix)/man +man1dir=$(mandir)/man1 +man7dir=$(mandir)/man7 # DESTDIR= INSTALL?=install @@ -53,9 +53,9 @@ man1: $(DOC_MAN1) man7: $(DOC_MAN7) install: man - $(INSTALL) -d -m755 $(DESTDIR)$(man1) $(DESTDIR)$(man7) - $(INSTALL) $(DOC_MAN1) $(DESTDIR)$(man1) - $(INSTALL) $(DOC_MAN7) $(DESTDIR)$(man7) + $(INSTALL) -d -m755 $(DESTDIR)$(man1dir) $(DESTDIR)$(man7dir) + $(INSTALL) $(DOC_MAN1) $(DESTDIR)$(man1dir) + $(INSTALL) $(DOC_MAN7) $(DESTDIR)$(man7dir) # diff --git a/Documentation/config.txt b/Documentation/config.txt index 465eb13e76..ce722a2db0 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -83,9 +83,12 @@ core.repositoryFormatVersion:: version. core.sharedRepository:: - If true, the repository is made shareable between several users - in a group (making sure all the files and objects are group-writable). - See gitlink:git-init-db[1]. False by default. + When 'group' (or 'true'), the repository is made shareable between + several users in a group (making sure all the files and objects are + group-writable). When 'all' (or 'world' or 'everybody'), the + repository will be readable by all users, additionally to being + group-shareable. When 'umask' (or 'false'), git will use permissions + reported by umask(2). See gitlink:git-init-db[1]. False by default. core.warnAmbiguousRefs:: If true, git will warn you if the ref name you passed it is ambiguous @@ -116,6 +119,10 @@ apply.whitespace:: Tells `git-apply` how to handle whitespaces, in the same way as the '--whitespace' option. See gitlink:git-apply[1]. +pager.color:: + A boolean to enable/disable colored output when the pager is in + use (default is true). + diff.color:: When true (or `always`), always use colors in patch. When false (or `never`), never. When set to `auto`, use @@ -219,14 +226,14 @@ showbranch.default:: See gitlink:git-show-branch[1]. tar.umask:: - By default, git-link:git-tar-tree[1] sets file and directories modes + 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 such as the Linux Kernel, it might be excessive for other projects. With this variable, it becomes possible to tell - git-link:git-tar-tree[1] to apply a specific umask to the modes above. + gitlink:git-tar-tree[1] to apply a specific umask to the modes above. The special value "user" indicates that the user's current umask will be used. This should be enough for most projects, as it will lead to - the same permissions as git-link:git-checkout[1] would use. The default + the same permissions as gitlink:git-checkout[1] would use. The default value remains 0, which means world read-write. user.email:: diff --git a/Documentation/diff-options.txt b/Documentation/diff-options.txt index 47ba9a403a..b5d9763594 100644 --- a/Documentation/diff-options.txt +++ b/Documentation/diff-options.txt @@ -36,6 +36,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-cvsimport.txt b/Documentation/git-cvsimport.txt index b0c6d7c303..d21d66bfeb 100644 --- a/Documentation/git-cvsimport.txt +++ b/Documentation/git-cvsimport.txt @@ -116,7 +116,7 @@ file each time git-cvsimport is run. + It is not recommended to use this feature if you intend to export changes back to CVS again later with -git-link[1]::git-cvsexportcommit. +gitlink:git-cvsexportcommit[1]. OUTPUT ------ diff --git a/Documentation/git-daemon.txt b/Documentation/git-daemon.txt index 4c357daf6a..0f7d274eab 100644 --- a/Documentation/git-daemon.txt +++ b/Documentation/git-daemon.txt @@ -11,7 +11,7 @@ SYNOPSIS 'git-daemon' [--verbose] [--syslog] [--inetd | --port=n] [--export-all] [--timeout=n] [--init-timeout=n] [--strict-paths] [--base-path=path] [--user-path | --user-path=path] - [directory...] + [--reuseaddr] [--detach] [--pid-file=file] [directory...] DESCRIPTION ----------- @@ -82,6 +82,17 @@ OPTIONS --verbose:: Log details about the incoming connections and requested files. +--reuseaddr:: + Use SO_REUSEADDR when binding the listening socket. + This allows the server to restart without waiting for + old connections to time out. + +--detach:: + Detach from the shell. Implies --syslog. + +--pid-file=file:: + Save the process id in 'file'. + <directory>:: A directory to add to the whitelist of allowed directories. Unless --strict-paths is specified this will also include subdirectories diff --git a/Documentation/git-grep.txt b/Documentation/git-grep.txt index 62a8e7f222..7545dd9a3e 100644 --- a/Documentation/git-grep.txt +++ b/Documentation/git-grep.txt @@ -11,12 +11,12 @@ SYNOPSIS [verse] 'git-grep' [--cached] [-a | --text] [-I] [-i | --ignore-case] [-w | --word-regexp] - [-v | --invert-match] + [-v | --invert-match] [--full-name] [-E | --extended-regexp] [-G | --basic-regexp] [-F | --fixed-strings] [-n] [-l | --files-with-matches] [-L | --files-without-match] [-c | --count] [-A <post-context>] [-B <pre-context>] [-C <context>] - [-f <file>] [-e] <pattern> + [-f <file>] [-e] <pattern> [--and|--or|--not|(|)|-e <pattern>...] [<tree>...] [--] [<path>...] @@ -47,6 +47,12 @@ OPTIONS -v | --invert-match:: Select non-matching lines. +--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. @@ -74,16 +80,30 @@ OPTIONS -e:: The next parameter is the pattern. This option has to be used for patterns starting with - and should be used in - scripts passing user input to grep. + scripts passing user input to grep. Multiple patterns are + combined by 'or'. + +--and | --or | --not | ( | ):: + Specify how multiple patterns are combined using boolean + expressions. `--or` is the default operator. `--and` has + higher precedence than `--or`. `-e` has to be used for all + patterns. `<tree>...`:: Search blobs in the trees for specified patterns. -`--`:: +\--:: Signals the end of options; the rest of the parameters are <path> limiters. +Example +------- + +git grep -e \'#define\' --and \( -e MAX_PATH -e PATH_MAX \):: + Looks for a line that has `#define` and either `MAX_PATH` or + `PATH_MAX`. + Author ------ Originally written by Linus Torvalds <torvalds@osdl.org>, later diff --git a/Documentation/git-http-fetch.txt b/Documentation/git-http-fetch.txt index bc1a132891..3d508094af 100644 --- a/Documentation/git-http-fetch.txt +++ b/Documentation/git-http-fetch.txt @@ -8,7 +8,7 @@ git-http-fetch - downloads a remote git repository via HTTP SYNOPSIS -------- -'git-http-fetch' [-c] [-t] [-a] [-d] [-v] [-w filename] [--recover] <commit> <url> +'git-http-fetch' [-c] [-t] [-a] [-d] [-v] [-w filename] [--recover] [--stdin] <commit> <url> DESCRIPTION ----------- @@ -33,6 +33,12 @@ commit-id:: Writes the commit-id into the filename under $GIT_DIR/refs/<filename> on the local end after the transfer is complete. +--stdin:: + Instead of a commit id on the commandline (which is not expected in this + case), 'git-http-fetch' expects lines on stdin in the format + + <commit-id>['\t'<filename-as-in--w>] + Author ------ Written by Linus Torvalds <torvalds@osdl.org> diff --git a/Documentation/git-init-db.txt b/Documentation/git-init-db.txt index 0a4fc14b97..63cd5dab3f 100644 --- a/Documentation/git-init-db.txt +++ b/Documentation/git-init-db.txt @@ -8,17 +8,47 @@ git-init-db - Creates an empty git repository SYNOPSIS -------- -'git-init-db' [--template=<template_directory>] [--shared] +'git-init-db' [--template=<template_directory>] [--shared[=<permissions>]] OPTIONS ------- + +-- + --template=<template_directory>:: - Provide the directory from which templates will be used. - The default template directory is `/usr/share/git-core/templates`. ---shared:: - Specify that the git repository is to be shared amongst several users. +Provide the directory from which templates will be used. The default template +directory is `/usr/share/git-core/templates`. + +When specified, `<template_directory>` is used as the source of the template +files rather than the default. The template files include some directory +structure, some suggested "exclude patterns", and copies of non-executing +"hook" files. The suggested patterns and hook files are all modifiable and +extensible. + +--shared[={false|true|umask|group|all|world|everybody}]:: + +Specify that the git repository is to be shared amongst several users. This +allows users belonging to the same group to push into that +repository. When specified, the config variable "core.sharedRepository" is +set so that files and directories under `$GIT_DIR` are created with the +requested permissions. When not specified, git will use permissions reported +by umask(2). + +The option can have the following values, defaulting to 'group' if no value +is given: + + - 'umask' (or 'false'): Use permissions reported by umask(2). The default, + when `--shared` is not specified. + + - 'group' (or 'true'): Make the repository group-writable, (and g+sx, since + the git group may be not the primary group of all users). + + - 'all' (or 'world' or 'everybody'): Same as 'group', but make the repository + readable by all users. + +-- DESCRIPTION @@ -29,12 +59,6 @@ template files. An initial `HEAD` file that references the HEAD of the master branch is also created. -If `--template=<template_directory>` is specified, `<template_directory>` -is used as the source of the template files rather than the default. -The template files include some directory structure, some suggested -"exclude patterns", and copies of non-executing "hook" files. The -suggested patterns and hook files are all modifiable and extensible. - If the `$GIT_DIR` environment variable is set then it specifies a path to use instead of `./.git` for the base of the repository. @@ -42,11 +66,6 @@ If the object storage directory is specified via the `$GIT_OBJECT_DIRECTORY` environment variable then the sha1 directories are created underneath - otherwise the default `$GIT_DIR/objects` directory is used. -A shared repository allows users belonging to the same group to push into that -repository. When specifying `--shared` the config variable "core.sharedRepository" -is set to 'true' so that directories under `$GIT_DIR` are made group writable -(and g+sx, since the git group may be not the primary group of all users). - Running `git-init-db` in an existing repository is safe. It will not overwrite things that are already there. The primary reason for rerunning `git-init-db` is to pick up newly added templates. diff --git a/Documentation/git-local-fetch.txt b/Documentation/git-local-fetch.txt index 87abec1c4e..2fbdfe086a 100644 --- a/Documentation/git-local-fetch.txt +++ b/Documentation/git-local-fetch.txt @@ -29,6 +29,12 @@ OPTIONS Writes the commit-id into the filename under $GIT_DIR/refs/<filename> on the local end after the transfer is complete. +--stdin:: + Instead of a commit id on the commandline (which is not expected in this + case), 'git-local-fetch' expects lines on stdin in the format + + <commit-id>['\t'<filename-as-in--w>] + Author ------ Written by Junio C Hamano <junkio@cox.net> diff --git a/Documentation/git-push.txt b/Documentation/git-push.txt index 56afd64f42..d4ae99fa53 100644 --- a/Documentation/git-push.txt +++ b/Documentation/git-push.txt @@ -8,7 +8,7 @@ git-push - Update remote refs along with associated objects SYNOPSIS -------- -'git-push' [--all] [--tags] [--force] <repository> <refspec>... +'git-push' [--all] [--tags] [-f | --force] <repository> <refspec>... DESCRIPTION ----------- diff --git a/Documentation/git-rev-list.txt b/Documentation/git-rev-list.txt index f60eacd93e..dd9fff16d3 100644 --- a/Documentation/git-rev-list.txt +++ b/Documentation/git-rev-list.txt @@ -22,6 +22,7 @@ SYNOPSIS [ [\--objects | \--objects-edge] [ \--unpacked ] ] [ \--pretty | \--header ] [ \--bisect ] + [ \--merge ] <commit>... [ \-- <paths>... ] DESCRIPTION @@ -123,6 +124,10 @@ OPTIONS topological order (i.e. descendant commits are shown before their parents). +--merge:: + After a failed merge, show refs that touch files having a + conflict and don't exist on all heads to merge. + Author ------ Written by Linus Torvalds <torvalds@osdl.org> diff --git a/Documentation/git-status.txt b/Documentation/git-status.txt index e446f4812e..ce7857e5a9 100644 --- a/Documentation/git-status.txt +++ b/Documentation/git-status.txt @@ -8,7 +8,7 @@ git-status - Show working tree status SYNOPSIS -------- -'git-status' +'git-status' <options>... DESCRIPTION ----------- @@ -23,6 +23,10 @@ If there is no path that is different between the index file and the current HEAD commit, the command exits with non-zero status. +The command takes the same set of options as `git-commit`; it +shows what would be committed if the same options are given to +`git-commit`. + OUTPUT ------ diff --git a/Documentation/git-tar-tree.txt b/Documentation/git-tar-tree.txt index 7a99acf2ec..1e1c7fa856 100644 --- a/Documentation/git-tar-tree.txt +++ b/Documentation/git-tar-tree.txt @@ -71,6 +71,11 @@ git tar-tree --remote=example.com:git.git v1.4.0 >git-1.4.0.tar:: Get a tarball v1.4.0 from example.com. +git tar-tree HEAD:Documentation/ git-docs > git-1.4.0-docs.tar:: + + Put everything in the current head's Documentation/ directory + into 'git-1.4.0-docs.tar', with the prefix 'git-docs/'. + Author ------ Written by Rene Scharfe. diff --git a/Documentation/git.txt b/Documentation/git.txt index ce3058182f..3de5fa9c82 100644 --- a/Documentation/git.txt +++ b/Documentation/git.txt @@ -8,7 +8,8 @@ git - the stupid content tracker SYNOPSIS -------- -'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] DESCRIPTION ----------- @@ -41,6 +42,15 @@ OPTIONS environment variable. If no path is given 'git' will print the current setting and then exit. +-p|--paginate:: + Pipe all output into 'less' (or if set, $PAGER). + +--git-dir=<path>:: + Set the path to the repository. This can also be controlled by + setting the GIT_DIR environment variable. + +--bare:: + Same as --git-dir=`pwd`. FURTHER DOCUMENTATION --------------------- @@ -387,6 +397,9 @@ gitlink:git-quiltimport[1]:: gitlink:git-relink[1]:: Hardlink common objects in local repositories. +gitlink:git-svn[1]:: + Bidirectional operation between a single Subversion branch and git. + gitlink:git-svnimport[1]:: Import a SVN repository into git. @@ -432,6 +445,9 @@ gitlink:git-get-tar-commit-id[1]:: gitlink:git-imap-send[1]:: Dump a mailbox from stdin into an imap folder. +gitlink:git-instaweb[1]:: + Instantly browse your working repository in gitweb. + gitlink:git-mailinfo[1]:: Extracts patch and authorship information from a single e-mail message, optionally transliterating the commit @@ -617,6 +633,9 @@ git Diffs other ~~~~~ +'GIT_PAGER':: + This environment variable overrides `$PAGER`. + 'GIT_TRACE':: If this variable is set git will print `trace:` messages on stderr telling about alias expansion, built-in command diff --git a/Documentation/howto/setup-git-server-over-http.txt b/Documentation/howto/setup-git-server-over-http.txt new file mode 100644 index 0000000000..ba191569af --- /dev/null +++ b/Documentation/howto/setup-git-server-over-http.txt @@ -0,0 +1,256 @@ +From: Rutger Nijlunsing <rutger@nospam.com> +Subject: Setting up a git repository which can be pushed into and pulled from over HTTP. +Date: Thu, 10 Aug 2006 22:00:26 +0200 + +Since Apache is one of those packages people like to compile +themselves while others prefer the bureaucrat's dream Debian, it is +impossible to give guidelines which will work for everyone. Just send +some feedback to the mailing list at git@vger.kernel.org to get this +document tailored to your favorite distro. + + +What's needed: + +- Have an Apache web-server + + On Debian: + $ apt-get install apache2 + To get apache2 by default started, + edit /etc/default/apache2 and set NO_START=0 + +- can edit the configuration of it. + + This could be found under /etc/httpd, or refer to your Apache documentation. + + On Debian: this means being able to edit files under /etc/apache2 + +- can restart it. + + 'apachectl --graceful' might do. If it doesn't, just stop and + restart apache. Be warning that active connections to your server + might be aborted by this. + + On Debian: + $ /etc/init.d/apache2 restart + or + $ /etc/init.d/apache2 force-reload + (which seems to do the same) + This adds symlinks from the /etc/apache2/mods-enabled to + /etc/apache2/mods-available. + +- have permissions to chown a directory + +- have git installed at the server _and_ client + +In effect, this probably means you're going to be root. + + +Step 1: setup a bare GIT repository +----------------------------------- + +At the time of writing, git-http-push cannot remotely create a GIT +repository. So we have to do that at the server side with git. Another +option would be to generate an empty repository at the client and copy +it to the server with WebDAV. But then you're probably the first to +try that out :) + +Create the directory under the DocumentRoot of the directories served +by Apache. As an example we take /usr/local/apache2, but try "grep +DocumentRoot /where/ever/httpd.conf" to find your root: + + $ cd /usr/local/apache/htdocs + $ mkdir my-new-repo.git + + On Debian: + + $ cd /var/www + $ mkdir my-new-repo.git + + +Initialize a bare repository + + $ cd my-new-repo.git + $ git --bare init-db + + +Change the ownership to your web-server's credentials. Use "grep ^User +httpd.conf" and "grep ^Group httpd.conf" to find out: + + $ chown -R www.www . + + On Debian: + + $ chown -R www-data.www-data . + + +If you do not know which user Apache runs as, you can alternatively do +a "chmod -R a+w .", inspect the files which are created later on, and +set the permissions appropriately. + +Restart apache2, and check whether http://server/my-new-repo.git gives +a directory listing. If not, check whether apache started up +successfully. + + +Step 2: enable DAV on this repository +------------------------------------- + +First make sure the dav_module is loaded. For this, insert in httpd.conf: + + LoadModule dav_module libexec/httpd/libdav.so + AddModule mod_dav.c + +Also make sure that this line exists which is the file used for +locking DAV operations: + + DAVLockDB "/usr/local/apache2/temp/DAV.lock" + + On Debian these steps can be performed with: + + Enable the dav and dav_fs modules of apache: + $ a2enmod dav_fs + (just to be sure. dav_fs might be unneeded, I don't know) + $ a2enmod dav + The DAV lock is located in /etc/apache2/mods-available/dav_fs.conf: + DAVLockDB /var/lock/apache2/DAVLock + +Of course, it can point somewhere else, but the string is actually just a +prefix in some Apache configurations, and therefore the _directory_ has to +be writable by the user Apache runs as. + +Then, add something like this to your httpd.conf + + <Location /my-new-repo.git> + DAV on + AuthType Basic + AuthName "Git" + AuthUserFile /usr/local/apache2/conf/passwd.git + Require valid-user + </Location> + + On Debian: + Create (or add to) /etc/apache2/conf.d/git.conf : + + <Location /my-new-repo.git> + DAV on + AuthType Basic + AuthName "Git" + AuthUserFile /etc/apache2/passwd.git + Require valid-user + </Location> + + Debian automatically reads all files under /etc/apach2/conf.d. + +The password file can be somewhere else, but it has to be readable by +Apache and preferably not readable by the world. + +Create this file by + $ htpasswd -c /usr/local/apache2/conf/passwd.git <user> + + On Debian: + $ htpasswd -c /etc/apache2/passwd.git <user> + +You will be asked a password, and the file is created. Subsequent calls +to htpasswd should omit the '-c' option, since you want to append to the +existing file. + +You need to restart Apache. + +Now go to http://<username>@<servername>/my-new-repo.git in your +browser to check whether it asks for a password and accepts the right +password. + +On Debian: + + To test the WebDAV part, do: + + $ apt-get install litmus + $ litmus http://<servername>/my-new-repo.git <username> <password> + + Most tests should pass. + +A command line tool to test WebDAV is cadaver. + +If you're into Windows, from XP onwards Internet Explorer supports +WebDAV. For this, do Internet Explorer -> Open Location -> +http://<servername>/my-new-repo.git [x] Open as webfolder -> login . + + +Step 3: setup the client +------------------------ + +Make sure that you have HTTP support, i.e. your git was built with curl. +The easiest way to check is to look for the executable 'git-http-push'. + +Then, add the following to your $HOME/.netrc (you can do without, but will be +asked to input your password a _lot_ of times): + + machine <servername> + login <username> + password <password> + +...and set permissions: + chmod 600 ~/.netrc + +If you want to access the web-server by its IP, you have to type that in, +instead of the server name. + +To check whether all is OK, do: + + curl --netrc --location -v http://<username>@<servername>/my-new-repo.git/ + +...this should give a directory listing in HTML of /var/www/my-new-repo.git . + + +Now, add the remote in your existing repository which contains the project +you want to export: + + $ git-repo-config remote.upload.url \ + http://<username>@<servername>/my-new-repo.git/ + +It is important to put the last '/'; Without it, the server will send +a redirect which git-http-push does not (yet) understand, and git-http-push +will repeat the request infinitely. + + +Step 4: make the initial push +----------------------------- + +From your client repository, do + + $ git push upload master + +This pushes branch 'master' (which is assumed to be the branch you +want to export) to repository called 'upload', which we previously +defined with git-repo-config. + + +Troubleshooting: +---------------- + +If git-http-push says + + Error: no DAV locking support on remote repo http://... + +then it means the web-server did not accept your authentication. Make sure +that the user name and password matches in httpd.conf, .netrc and the URL +you are uploading to. + +If git-http-push shows you an error (22/502) when trying to MOVE a blob, +it means that your web-server somehow does not recognize its name in the +request; This can happen when you start Apache, but then disable the +network interface. A simple restart of Apache helps. + +Errors like (22/502) are of format (curl error code/http error +code). So (22/404) means something like 'not found' at the server. + +Reading /usr/local/apache2/logs/error_log is often helpful. + + On Debian: Read /var/log/apache2/error.log instead. + + +Debian References: http://www.debian-administration.org/articles/285 + +Authors + Johannes Schindelin <Johannes.Schindelin@gmx.de> + Rutger Nijlunsing <git@wingding.demon.nl> diff --git a/Documentation/tutorial-2.txt b/Documentation/tutorial-2.txt index 894ca5e06f..2f4fe1217a 100644 --- a/Documentation/tutorial-2.txt +++ b/Documentation/tutorial-2.txt @@ -244,6 +244,7 @@ $ git ls-files --stage $ git cat-file -t 513feba2 blob $ git cat-file blob 513feba2 +hello world! hello world, again ------------------------------------------------ diff --git a/GIT-VERSION-GEN b/GIT-VERSION-GEN index 5d25b7e12b..14923c973b 100755 --- a/GIT-VERSION-GEN +++ b/GIT-VERSION-GEN @@ -1,11 +1,19 @@ #!/bin/sh GVF=GIT-VERSION-FILE -DEF_VER=v1.4.GIT +DEF_VER=v1.4.2.GIT + +LF=' +' # First try git-describe, then see if there is a version file # (included in release tarballs), then default -if VN=$(git describe --abbrev=4 HEAD 2>/dev/null); then +if VN=$(git describe --abbrev=4 HEAD 2>/dev/null) && + case "$VN" in + *$LF*) (exit 1) ;; + v[0-9]*) : happy ;; + esac +then VN=$(echo "$VN" | sed -e 's/-/./g'); elif test -f version then @@ -13,6 +13,15 @@ that uses $prefix, the built results have some paths encoded, which are derived from $prefix, so "make all; make prefix=/usr 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 + + $ make configure ;# as yourself + $ ./configure --prefix=/usr ;# as yourself + $ make all doc ;# as yourself + # make install install-doc ;# as root + + Issues of note: - git normally installs a helper script wrapper called "git", which @@ -24,6 +24,11 @@ all: # 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). # +# 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. +# # Define NO_STRCASESTR if you don't have strcasestr. # # Define NO_STRLCPY if you don't have strlcpy. @@ -116,6 +121,17 @@ 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_SITENAME = +GITWEB_PROJECTROOT = /pub/git +GITWEB_LIST = +GITWEB_HOMETEXT = indextext.html +GITWEB_CSS = gitweb.css +GITWEB_LOGO = git-logo.png + +export prefix bindir gitexecdir template_dir GIT_PYTHON_DIR + CC = gcc AR = ar TAR = tar @@ -149,7 +165,7 @@ SCRIPT_PERL = \ git-archimport.perl git-cvsimport.perl git-relink.perl \ git-shortlog.perl git-rerere.perl \ git-annotate.perl git-cvsserver.perl \ - git-svnimport.perl git-mv.perl git-cvsexportcommit.perl \ + git-svnimport.perl git-cvsexportcommit.perl \ git-send-email.perl git-svn.perl SCRIPT_PYTHON = \ @@ -166,31 +182,23 @@ 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-peek-remote$X git-prune-packed$X git-receive-pack$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-repo-config$X git-var$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 +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) @@ -219,7 +227,7 @@ 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 \ run-command.h strbuf.h tag.h tree.h git-compat-util.h revision.h \ - tree-walk.h log-tree.h dir.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 \ @@ -234,19 +242,50 @@ LIB_OBJS = \ 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 $(DIFF_OBJS) + alloc.o merge-file.o path-list.o help.o unpack-trees.o $(DIFF_OBJS) 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-add.o \ + builtin-apply.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-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-tar.o \ + builtin-verify-pack.o \ + builtin-write-tree.o GITLIBS = $(LIB_FILE) $(XDIFF_LIB) LIBS = $(GITLIBS) -lz @@ -262,6 +301,9 @@ LIBS = $(GITLIBS) -lz ifeq ($(uname_S),Linux) NO_STRLCPY = YesPlease endif +ifeq ($(uname_S),GNU/kFreeBSD) + NO_STRLCPY = YesPlease +endif ifeq ($(uname_S),Darwin) NEEDS_SSL_WITH_CRYPTO = YesPlease NEEDS_LIBICONV = YesPlease @@ -302,9 +344,9 @@ ifeq ($(uname_O),Cygwin) NO_D_TYPE_IN_DIRENT = YesPlease NO_D_INO_IN_DIRENT = YesPlease NO_STRCASESTR = YesPlease - NO_STRLCPY = YesPlease NO_SYMLINK_HEAD = YesPlease NEEDS_LIBICONV = YesPlease + NO_C99_FORMAT = YesPlease # There are conflicting reports about this. # On some boxes NO_MMAP is needed, and not so elsewhere. # Try uncommenting this if you see things break -- YMMV. @@ -350,6 +392,7 @@ ifneq (,$(findstring arm,$(uname_M))) ARM_SHA1 = YesPlease endif +-include config.mak.autogen -include config.mak ifdef WITH_OWN_SUBPROCESS_PY @@ -425,6 +468,9 @@ endif ifdef NO_D_INO_IN_DIRENT ALL_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 endif @@ -507,7 +553,7 @@ LIB_OBJS += $(COMPAT_OBJS) 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: $(MAKE) -C templates @@ -520,7 +566,7 @@ 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 $(BUILT_INS): git$X rm -f $@ && ln git$X $@ @@ -565,6 +611,22 @@ 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_SITENAME++|$(GITWEB_SITENAME)|g' \ + -e 's|++GITWEB_PROJECTROOT++|$(GITWEB_PROJECTROOT)|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' \ + $< >$@+ + chmod +x $@+ + mv $@+ $@ + git-instaweb: git-instaweb.sh gitweb/gitweb.cgi gitweb/gitweb.css rm -f $@ $@+ sed -e '1s|#!.*/sh|#!$(SHELL_PATH_SQ)|' \ @@ -579,6 +641,13 @@ git-instaweb: git-instaweb.sh gitweb/gitweb.cgi gitweb/gitweb.css 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)) \ @@ -612,6 +681,8 @@ $(SIMPLE_PROGRAMS) : git-%$X : %.o $(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \ $(LIB_FILE) $(SIMPLE_LIB) +ssh-pull.o: ssh-fetch.c +ssh-push.o: ssh-upload.c git-local-fetch$X: fetch.o git-ssh-fetch$X: rsh.o fetch.o git-ssh-upload$X: rsh.o @@ -703,7 +774,7 @@ install: all $(INSTALL) -d -m755 '$(DESTDIR_SQ)$(gitexecdir_SQ)' $(INSTALL) $(ALL_PROGRAMS) '$(DESTDIR_SQ)$(gitexecdir_SQ)' $(INSTALL) git$X gitk '$(DESTDIR_SQ)$(bindir_SQ)' - $(MAKE) -C templates install + $(MAKE) -C templates DESTDIR='$(DESTDIR_SQ)' 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)'; \ @@ -753,8 +824,8 @@ dist-doc: rm -fr .doc-tmp-dir mkdir .doc-tmp-dir .doc-tmp-dir/man1 .doc-tmp-dir/man7 $(MAKE) -C Documentation DESTDIR=./ \ - man1=../.doc-tmp-dir/man1 \ - man7=../.doc-tmp-dir/man7 \ + man1dir=../.doc-tmp-dir/man1 \ + man7dir=../.doc-tmp-dir/man7 \ install cd .doc-tmp-dir && $(TAR) cf ../$(manpages).tar . gzip -n -9 -f $(manpages).tar @@ -767,9 +838,12 @@ clean: $(LIB_FILE) $(XDIFF_LIB) 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 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 $(MAKE) -C t/ clean @@ -39,8 +39,21 @@ DEFINE_ALLOCATOR(tree) DEFINE_ALLOCATOR(commit) DEFINE_ALLOCATOR(tag) +#ifdef NO_C99_FORMAT +#define SZ_FMT "%u" +#else +#define SZ_FMT "%zu" +#endif + +static void report(const char* name, unsigned int count, size_t size) +{ + fprintf(stderr, "%10s: %8u (" SZ_FMT " kB)\n", name, count, size); +} + +#undef SZ_FMT + #define REPORT(name) \ - fprintf(stderr, "%10s: %8u (%zu kB)\n", #name, name##_allocs, name##_allocs*sizeof(struct name) >> 10) + report(#name, name##_allocs, name##_allocs*sizeof(struct name) >> 10) void alloc_report(void) { @@ -20,7 +20,7 @@ #define DEBUG 0 -static const char blame_usage[] = "[-c] [-l] [-t] [-S <revs-file>] [--] file [commit]\n" +static const char blame_usage[] = "git-blame [-c] [-l] [-t] [-S <revs-file>] [--] file [commit]\n" " -c, --compatibility Use the same output mode as git-annotate (Default: off)\n" " -l, --long Show long commit SHA1 (Default: off)\n" " -t, --time Show raw timestamp (Default: off)\n" @@ -834,7 +834,7 @@ int main(int argc, const char **argv) } - init_revisions(&rev); + init_revisions(&rev, setup_git_directory()); rev.remove_empty_trees = 1; rev.topo_order = 1; rev.prune_fn = simplify_commit; diff --git a/builtin-add.c b/builtin-add.c index 3a73a173f7..0cb9c81200 100644 --- a/builtin-add.c +++ b/builtin-add.c @@ -82,60 +82,18 @@ static void fill_directory(struct dir_struct *dir, const char **pathspec) prune_directory(dir, pathspec, baselen); } -static int add_file_to_index(const char *path, int verbose) -{ - int size, namelen; - struct stat st; - struct cache_entry *ce; - - if (lstat(path, &st)) - die("%s: unable to stat (%s)", path, strerror(errno)); - - if (!S_ISREG(st.st_mode) && !S_ISLNK(st.st_mode)) - die("%s: can only add regular files or symbolic links", path); - - namelen = strlen(path); - size = cache_entry_size(namelen); - ce = xcalloc(1, size); - memcpy(ce->name, path, namelen); - ce->ce_flags = htons(namelen); - fill_stat_cache_info(ce, &st); - - 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. - */ - int pos = cache_name_pos(path, namelen); - if (pos >= 0) - ce->ce_mode = active_cache[pos]->ce_mode; - } - - if (index_path(ce->sha1, path, &st, 1)) - die("unable to index file %s", path); - if (add_cache_entry(ce, ADD_CACHE_OK_TO_ADD)) - die("unable to add %s to index",path); - if (verbose) - printf("add '%s'\n", path); - cache_tree_invalidate_path(active_cache_tree, path); - return 0; -} - static struct lock_file lock_file; -int cmd_add(int argc, const char **argv, char **envp) +int cmd_add(int argc, const char **argv, const char *prefix) { int i, newfd; int verbose = 0, show_only = 0; - const char *prefix = setup_git_directory(); const char **pathspec; struct dir_struct dir; git_config(git_default_config); - newfd = hold_lock_file_for_update(&lock_file, get_index_file()); - if (newfd < 0) - die("unable to create new index file"); + newfd = hold_lock_file_for_update(&lock_file, get_index_file(), 1); if (read_cache() < 0) die("index file corrupt"); @@ -157,9 +115,8 @@ int cmd_add(int argc, const char **argv, char **envp) verbose = 1; continue; } - die(builtin_add_usage); + usage(builtin_add_usage); } - git_config(git_default_config); pathspec = get_pathspec(prefix, argv + i); fill_directory(&dir, pathspec); diff --git a/builtin-apply.c b/builtin-apply.c index d924ac3d0a..9cf477c701 100644 --- a/builtin-apply.c +++ b/builtin-apply.c @@ -120,7 +120,7 @@ struct fragment { struct patch { char *new_name, *old_name, *def_name; unsigned int old_mode, new_mode; - int is_rename, is_copy, is_new, is_delete, is_binary; + int is_rename, is_copy, is_new, is_delete, is_binary, is_reverse; #define BINARY_DELTA_DEFLATED 1 #define BINARY_LITERAL_DEFLATED 2 unsigned long deflate_origlen; @@ -1119,6 +1119,34 @@ static int parse_chunk(char *buffer, unsigned long size, struct patch *patch) return offset + hdrsize + patchsize; } +#define swap(a,b) myswap((a),(b),sizeof(a)) + +#define myswap(a, b, size) do { \ + unsigned char mytmp[size]; \ + memcpy(mytmp, &a, size); \ + memcpy(&a, &b, size); \ + memcpy(&b, mytmp, size); \ +} while (0) + +static void reverse_patches(struct patch *p) +{ + for (; p; p = p->next) { + struct fragment *frag = p->fragments; + + swap(p->new_name, p->old_name); + swap(p->new_mode, p->old_mode); + swap(p->is_new, p->is_delete); + swap(p->lines_added, p->lines_deleted); + swap(p->old_sha1_prefix, p->new_sha1_prefix); + + for (; frag; frag = frag->next) { + swap(frag->newpos, frag->oldpos); + swap(frag->newlines, frag->oldlines); + } + p->is_reverse = !p->is_reverse; + } +} + static const char pluses[] = "++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++"; static const char minuses[]= "----------------------------------------------------------------------"; @@ -1336,7 +1364,7 @@ static int apply_line(char *output, const char *patch, int plen) } static int apply_one_fragment(struct buffer_desc *desc, struct fragment *frag, - int inaccurate_eof) + int reverse, int inaccurate_eof) { int match_beginning, match_end; char *buf = desc->buffer; @@ -1350,6 +1378,7 @@ static int apply_one_fragment(struct buffer_desc *desc, struct fragment *frag, int pos, lines; while (size > 0) { + char first; int len = linelen(patch, size); int plen; @@ -1366,16 +1395,23 @@ static int apply_one_fragment(struct buffer_desc *desc, struct fragment *frag, plen = len-1; if (len < size && patch[len] == '\\') plen--; - switch (*patch) { + first = *patch; + if (reverse) { + if (first == '-') + first = '+'; + else if (first == '+') + first = '-'; + } + switch (first) { case ' ': case '-': memcpy(old + oldsize, patch + 1, plen); oldsize += plen; - if (*patch == '-') + if (first == '-') break; /* Fall-through for ' ' */ case '+': - if (*patch != '+' || !no_add) + if (first != '+' || !no_add) newsize += apply_line(new + newsize, patch, plen); break; @@ -1499,6 +1535,12 @@ 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) @@ -1615,7 +1657,8 @@ static int apply_fragments(struct buffer_desc *desc, struct patch *patch) return apply_binary(desc, patch); while (frag) { - if (apply_one_fragment(desc, frag, patch->inaccurate_eof) < 0) + if (apply_one_fragment(desc, frag, patch->is_reverse, + patch->inaccurate_eof) < 0) return error("patch failed: %s:%ld", name, frag->oldpos); frag = frag->next; @@ -1655,6 +1698,12 @@ static int apply_data(struct patch *patch, struct stat *st, struct cache_entry * desc.buffer = buf; if (apply_fragments(&desc, patch) < 0) return -1; + + /* NUL terminate the result */ + if (desc.alloc <= desc.size) + desc.buffer = xrealloc(desc.buffer, desc.size + 1); + desc.buffer[desc.size] = 0; + patch->result = desc.buffer; patch->resultsize = desc.size; @@ -1997,6 +2046,9 @@ static int try_create_file(const char *path, unsigned int mode, const char *buf, int fd; if (S_ISLNK(mode)) + /* Although buf:size is counted string, it also is NUL + * terminated. + */ return symlink(buf, path); fd = open(path, O_CREAT | O_EXCL | O_WRONLY, (mode & 0100) ? 0777 : 0666); if (fd < 0) @@ -2142,7 +2194,8 @@ static int use_patch(struct patch *p) return 1; } -static int apply_patch(int fd, const char *filename, int inaccurate_eof) +static int apply_patch(int fd, const char *filename, + int reverse, int inaccurate_eof) { unsigned long offset, size; char *buffer = read_patch_file(fd, &size); @@ -2162,6 +2215,8 @@ static int apply_patch(int fd, const char *filename, int inaccurate_eof) nr = parse_chunk(buffer + offset, size, patch); if (nr < 0) break; + if (reverse) + reverse_patches(patch); if (use_patch(patch)) { patch_stats(patch); *listp = patch; @@ -2179,12 +2234,9 @@ static int apply_patch(int fd, const char *filename, int inaccurate_eof) apply = 0; write_index = check_index && apply; - if (write_index && newfd < 0) { + if (write_index && newfd < 0) newfd = hold_lock_file_for_update(&lock_file, - get_index_file()); - if (newfd < 0) - die("unable to create new index file"); - } + get_index_file(), 1); if (check_index) { if (read_cache() < 0) die("unable to read index file"); @@ -2222,10 +2274,11 @@ static int git_apply_config(const char *var, const char *value) } -int cmd_apply(int argc, const char **argv, char **envp) +int cmd_apply(int argc, const char **argv, const char *prefix) { int i; int read_stdin = 1; + int reverse = 0; int inaccurate_eof = 0; const char *whitespace_option = NULL; @@ -2236,7 +2289,7 @@ int cmd_apply(int argc, const char **argv, char **envp) int fd; if (!strcmp(arg, "-")) { - apply_patch(0, "<stdin>", inaccurate_eof); + apply_patch(0, "<stdin>", reverse, inaccurate_eof); read_stdin = 0; continue; } @@ -2313,6 +2366,10 @@ int cmd_apply(int argc, const char **argv, char **envp) parse_whitespace_option(arg + 13); continue; } + if (!strcmp(arg, "-R") || !strcmp(arg, "--reverse")) { + reverse = 1; + continue; + } if (!strcmp(arg, "--inaccurate-eof")) { inaccurate_eof = 1; continue; @@ -2333,12 +2390,12 @@ int cmd_apply(int argc, const char **argv, char **envp) usage(apply_usage); read_stdin = 0; set_default_whitespace_mode(whitespace_option); - apply_patch(fd, arg, inaccurate_eof); + apply_patch(fd, arg, reverse, inaccurate_eof); close(fd); } set_default_whitespace_mode(whitespace_option); if (read_stdin) - apply_patch(0, "<stdin>", inaccurate_eof); + apply_patch(0, "<stdin>", reverse, inaccurate_eof); if (whitespace_error) { if (squelch_whitespace_errors && squelch_whitespace_errors < whitespace_error) { diff --git a/builtin-cat-file.c b/builtin-cat-file.c index 4d36817e5f..814fb0743f 100644 --- a/builtin-cat-file.c +++ b/builtin-cat-file.c @@ -94,7 +94,7 @@ static int pprint_tag(const unsigned char *sha1, const char *buf, unsigned long return 0; } -int cmd_cat_file(int argc, const char **argv, char **envp) +int cmd_cat_file(int argc, const char **argv, const char *prefix) { unsigned char sha1[20]; char type[20]; @@ -102,7 +102,6 @@ int cmd_cat_file(int argc, const char **argv, char **envp) unsigned long size; int opt; - setup_git_directory(); git_config(git_default_config); if (argc != 3) usage("git-cat-file [-t|-s|-e|-p|<type>] <sha1>"); diff --git a/builtin-check-ref-format.c b/builtin-check-ref-format.c index 4a23936aff..fe04be77a9 100644 --- a/builtin-check-ref-format.c +++ b/builtin-check-ref-format.c @@ -6,9 +6,9 @@ #include "refs.h" #include "builtin.h" -int cmd_check_ref_format(int argc, const char **argv, char **envp) +int cmd_check_ref_format(int argc, const char **argv, const char *prefix) { if (argc != 2) - usage("git check-ref-format refname"); + usage("git-check-ref-format refname"); return !!check_ref_format(argv[1]); } diff --git a/checkout-index.c b/builtin-checkout-index.c index 61152f34b7..8d0dbad49e 100644 --- a/checkout-index.c +++ b/builtin-checkout-index.c @@ -42,8 +42,6 @@ #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; @@ -51,7 +49,7 @@ static char topath[4][MAXPATHLEN+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 int 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,7 +147,7 @@ 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(). @@ -163,16 +161,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) { @@ -206,7 +204,7 @@ int main(int argc, char **argv) state.refresh_cache = 1; if (newfd < 0) newfd = hold_lock_file_for_update - (&lock_file, get_index_file()); + (&lock_file, get_index_file(), 1); if (newfd < 0) die("cannot open index.lock file."); continue; @@ -270,7 +268,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 +290,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 +299,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 ec082bf754..9c98796671 100644 --- a/builtin-commit-tree.c +++ b/builtin-commit-tree.c @@ -77,7 +77,7 @@ static int new_parent(int idx) return 1; } -int cmd_commit_tree(int argc, const char **argv, char **envp) +int cmd_commit_tree(int argc, const char **argv, const char *prefix) { int i; int parents = 0; @@ -88,8 +88,6 @@ int cmd_commit_tree(int argc, const char **argv, char **envp) unsigned int size; setup_ident(); - setup_git_directory(); - git_config(git_default_config); if (argc < 2) diff --git a/builtin-count.c b/builtin-count-objects.c index 5ee72df247..1d3729aa99 100644 --- a/builtin-count.c +++ b/builtin-count-objects.c @@ -67,7 +67,7 @@ static void count_objects(DIR *d, char *path, int len, int verbose, } } -int cmd_count_objects(int ac, const char **av, char **ep) +int cmd_count_objects(int ac, const char **av, const char *prefix) { int i; int verbose = 0; diff --git a/builtin-diff-files.c b/builtin-diff-files.c index 81ac2fe64a..ac13db70ff 100644 --- a/builtin-diff-files.c +++ b/builtin-diff-files.c @@ -13,13 +13,13 @@ static const char diff_files_usage[] = "git-diff-files [-q] [-0/-1/2/3 |-c|--cc] [<common diff options>] [<path>...]" COMMON_DIFF_OPTIONS_HELP; -int cmd_diff_files(int argc, const char **argv, char **envp) +int cmd_diff_files(int argc, const char **argv, const char *prefix) { struct rev_info rev; int silent = 0; + init_revisions(&rev, prefix); git_config(git_default_config); /* no "diff" UI options */ - init_revisions(&rev); rev.abbrev = 0; argc = setup_revisions(argc, argv, &rev, NULL); diff --git a/builtin-diff-index.c b/builtin-diff-index.c index a1fa1b85cf..95a3db156b 100644 --- a/builtin-diff-index.c +++ b/builtin-diff-index.c @@ -9,14 +9,14 @@ static const char diff_cache_usage[] = "[<common diff options>] <tree-ish> [<path>...]" COMMON_DIFF_OPTIONS_HELP; -int cmd_diff_index(int argc, const char **argv, char **envp) +int cmd_diff_index(int argc, const char **argv, const char *prefix) { struct rev_info rev; int cached = 0; int i; + init_revisions(&rev, prefix); git_config(git_default_config); /* no "diff" UI options */ - init_revisions(&rev); rev.abbrev = 0; argc = setup_revisions(argc, argv, &rev, NULL); diff --git a/builtin-diff-stages.c b/builtin-diff-stages.c index 9c62702941..5960e08997 100644 --- a/builtin-diff-stages.c +++ b/builtin-diff-stages.c @@ -55,10 +55,9 @@ static void diff_stages(int stage1, int stage2, const char **pathspec) } } -int cmd_diff_stages(int ac, const char **av, char **envp) +int cmd_diff_stages(int ac, const char **av, const char *prefix) { int stage1, stage2; - const char *prefix = setup_git_directory(); const char **pathspec = NULL; git_config(git_default_config); /* no "diff" UI options */ diff --git a/builtin-diff-tree.c b/builtin-diff-tree.c index b610668594..24cb2d7f84 100644 --- a/builtin-diff-tree.c +++ b/builtin-diff-tree.c @@ -59,7 +59,7 @@ static const char diff_tree_usage[] = " --root include the initial commit as diff against /dev/null\n" COMMON_DIFF_OPTIONS_HELP; -int cmd_diff_tree(int argc, const char **argv, char **envp) +int cmd_diff_tree(int argc, const char **argv, const char *prefix) { int nr_sha1; char line[1000]; @@ -67,9 +67,9 @@ int cmd_diff_tree(int argc, const char **argv, char **envp) static struct rev_info *opt = &log_tree_opt; int read_stdin = 0; + init_revisions(opt, prefix); git_config(git_default_config); /* no "diff" UI options */ nr_sha1 = 0; - init_revisions(opt); opt->abbrev = 0; opt->diff = 1; argc = setup_revisions(argc, argv, opt, NULL); diff --git a/builtin-diff.c b/builtin-diff.c index cb38f44561..a090e298a5 100644 --- a/builtin-diff.c +++ b/builtin-diff.c @@ -23,7 +23,7 @@ struct blobinfo { }; static const char builtin_diff_usage[] = -"diff <options> <rev>{0,2} -- <path>*"; +"git-diff <options> <rev>{0,2} -- <path>*"; static int builtin_diff_files(struct rev_info *revs, int argc, const char **argv) @@ -125,9 +125,6 @@ static int builtin_diff_blobs(struct rev_info *revs, int argc, const char **argv, struct blobinfo *blob) { - /* Blobs: the arguments are reversed when setup_revisions() - * picked them up. - */ unsigned mode = canon_mode(S_IFREG | 0644); if (argc > 1) @@ -135,8 +132,8 @@ static int builtin_diff_blobs(struct rev_info *revs, stuff_change(&revs->diffopt, mode, mode, - blob[1].sha1, blob[0].sha1, - blob[0].name, blob[0].name); + blob[0].sha1, blob[1].sha1, + blob[0].name, blob[1].name); diffcore_std(&revs->diffopt); diff_flush(&revs->diffopt); return 0; @@ -221,7 +218,7 @@ void add_head(struct rev_info *revs) add_pending_object(revs, obj, "HEAD"); } -int cmd_diff(int argc, const char **argv, char **envp) +int cmd_diff(int argc, const char **argv, const char *prefix) { int i; struct rev_info rev; @@ -251,12 +248,13 @@ int cmd_diff(int argc, const char **argv, char **envp) */ git_config(git_diff_ui_config); - init_revisions(&rev); + init_revisions(&rev, prefix); argc = setup_revisions(argc, argv, &rev, NULL); if (!rev.diffopt.output_format) { rev.diffopt.output_format = DIFF_FORMAT_PATCH; - diff_setup_done(&rev.diffopt); + if (diff_setup_done(&rev.diffopt) < 0) + die("diff_setup_done failed"); } /* Do we have --cached and not have a pending object, then @@ -346,7 +344,16 @@ int cmd_diff(int argc, const char **argv, char **envp) return builtin_diff_index(&rev, argc, argv); else if (ents == 2) return builtin_diff_tree(&rev, argc, argv, ent); + else if ((ents == 3) && (ent[0].item->flags & UNINTERESTING)) { + /* diff A...B where there is one sane merge base between + * A and B. We have ent[0] == merge-base, ent[1] == A, + * and ent[2] == B. Show diff between the base and B. + */ + ent[1] = ent[2]; + return builtin_diff_tree(&rev, argc, argv, ent); + } else - return builtin_diff_combined(&rev, argc, argv, ent, ents); + return builtin_diff_combined(&rev, argc, argv, + ent, ents); usage(builtin_diff_usage); } diff --git a/builtin-fmt-merge-msg.c b/builtin-fmt-merge-msg.c index f20b27b8a9..485ede7cad 100644 --- a/builtin-fmt-merge-msg.c +++ b/builtin-fmt-merge-msg.c @@ -1,3 +1,4 @@ +#include "builtin.h" #include "cache.h" #include "commit.h" #include "diff.h" @@ -242,7 +243,7 @@ static void shortlog(const char *name, unsigned char *sha1, free_list(&subjects); } -int cmd_fmt_merge_msg(int argc, char **argv, char **envp) +int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix) { int limit = 20, i = 0; char line[1024]; @@ -342,7 +343,7 @@ int cmd_fmt_merge_msg(int argc, char **argv, char **envp) struct rev_info rev; head = lookup_commit(head_sha1); - init_revisions(&rev); + init_revisions(&rev, prefix); rev.commit_format = CMIT_FMT_ONELINE; rev.ignore_merges = 1; rev.limited = 1; diff --git a/builtin-grep.c b/builtin-grep.c index a79bac305a..a561612e7e 100644 --- a/builtin-grep.c +++ b/builtin-grep.c @@ -123,6 +123,7 @@ 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; @@ -136,6 +137,7 @@ struct grep_opt { #define GREP_BINARY_TEXT 2 unsigned binary:2; unsigned extended:1; + unsigned relative:1; int regflags; unsigned pre_context; unsigned post_context; @@ -410,8 +412,10 @@ static int fixmatch(const char *pattern, char *line, regmatch_t *match) 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), @@ -422,22 +426,35 @@ static int match_one_pattern(struct grep_opt *opt, struct grep_pat *p, char *bol } if (hit && opt->word_regexp) { - /* Match beginning must be either - * beginning of the line, or at word - * boundary (i.e. the last char must - * not be alnum or underscore). - */ 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"); - if (pmatch[0].rm_so != 0 && - word_char(bol[pmatch[0].rm_so-1])) - hit = 0; - if (pmatch[0].rm_eo != (eol-bol) && - word_char(bol[pmatch[0].rm_eo])) + + /* 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; } @@ -617,19 +634,40 @@ static int grep_buffer(struct grep_opt *opt, const char *name, 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; } @@ -659,6 +697,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; @@ -705,7 +745,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"); @@ -830,7 +870,7 @@ 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); } @@ -845,11 +885,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 { @@ -871,7 +912,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; @@ -892,7 +933,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; @@ -919,18 +960,19 @@ static const char emsg_missing_context_len[] = static const char emsg_missing_argument[] = "option requires an argument -%s"; -int cmd_grep(int argc, const char **argv, char **envp) +int cmd_grep(int argc, const char **argv, const char *prefix) { int hit = 0; int cached = 0; int seen_dashdash = 0; struct grep_opt opt; struct object_array list = { 0, 0, NULL }; - const char *prefix = setup_git_directory(); const char **paths = NULL; int i; memset(&opt, 0, sizeof(opt)); + opt.prefix_length = (prefix && *prefix) ? strlen(prefix) : 0; + opt.relative = 1; opt.pattern_tail = &opt.pattern_list; opt.regflags = REG_NEWLINE; @@ -1104,6 +1146,10 @@ int cmd_grep(int argc, const char **argv, char **envp) } 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--; @@ -1162,8 +1208,15 @@ int cmd_grep(int argc, const char **argv, char **envp) 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; diff --git a/builtin-init-db.c b/builtin-init-db.c index 7fdd2fa9f9..5085018e46 100644 --- a/builtin-init-db.c +++ b/builtin-init-db.c @@ -250,7 +250,7 @@ static const char init_db_usage[] = * On the other hand, it might just make lookup slower and messier. You * be the judge. The default case is to have one DB per managed directory. */ -int cmd_init_db(int argc, const char **argv, char **envp) +int cmd_init_db(int argc, const char **argv, const char *prefix) { const char *git_dir; const char *sha1_dir; @@ -267,7 +267,7 @@ int cmd_init_db(int argc, const char **argv, char **envp) else if (!strncmp(arg, "--shared=", 9)) shared_repository = git_config_perm("arg", arg+9); else - die(init_db_usage); + usage(init_db_usage); } /* diff --git a/builtin-log.c b/builtin-log.c index 4052cc75bd..691cf3aef7 100644 --- a/builtin-log.c +++ b/builtin-log.c @@ -16,7 +16,7 @@ /* this is in builtin-diff.c */ void add_head(struct rev_info *revs); -static void cmd_log_init(int argc, const char **argv, char **envp, +static void cmd_log_init(int argc, const char **argv, const char *prefix, struct rev_info *rev) { rev->abbrev = DEFAULT_ABBREV; @@ -34,7 +34,6 @@ static int cmd_log_walk(struct rev_info *rev) struct commit *commit; prepare_revision_walk(rev); - setup_pager(); while ((commit = get_revision(rev)) != NULL) { log_tree_commit(rev, commit); free(commit->buffer); @@ -45,27 +44,27 @@ static int cmd_log_walk(struct rev_info *rev) return 0; } -int cmd_whatchanged(int argc, const char **argv, char **envp) +int cmd_whatchanged(int argc, const char **argv, const char *prefix) { struct rev_info rev; git_config(git_diff_ui_config); - init_revisions(&rev); + init_revisions(&rev, prefix); rev.diff = 1; rev.diffopt.recursive = 1; rev.simplify_history = 0; - cmd_log_init(argc, argv, envp, &rev); + cmd_log_init(argc, argv, prefix, &rev); if (!rev.diffopt.output_format) rev.diffopt.output_format = DIFF_FORMAT_RAW; return cmd_log_walk(&rev); } -int cmd_show(int argc, const char **argv, char **envp) +int cmd_show(int argc, const char **argv, const char *prefix) { struct rev_info rev; git_config(git_diff_ui_config); - init_revisions(&rev); + init_revisions(&rev, prefix); rev.diff = 1; rev.diffopt.recursive = 1; rev.combine_merges = 1; @@ -73,18 +72,18 @@ int cmd_show(int argc, const char **argv, char **envp) rev.always_show_header = 1; rev.ignore_merges = 0; rev.no_walk = 1; - cmd_log_init(argc, argv, envp, &rev); + cmd_log_init(argc, argv, prefix, &rev); return cmd_log_walk(&rev); } -int cmd_log(int argc, const char **argv, char **envp) +int cmd_log(int argc, const char **argv, const char *prefix) { struct rev_info rev; git_config(git_diff_ui_config); - init_revisions(&rev); + init_revisions(&rev, prefix); rev.always_show_header = 1; - cmd_log_init(argc, argv, envp, &rev); + cmd_log_init(argc, argv, prefix, &rev); return cmd_log_walk(&rev); } @@ -178,7 +177,7 @@ static int get_patch_id(struct commit *commit, struct diff_options *options, return diff_flush_patch_id(options, sha1); } -static void get_patch_ids(struct rev_info *rev, struct diff_options *options) +static void get_patch_ids(struct rev_info *rev, struct diff_options *options, const char *prefix) { struct rev_info check_rev; struct commit *commit; @@ -203,7 +202,7 @@ static void get_patch_ids(struct rev_info *rev, struct diff_options *options) die("diff_setup_done failed"); /* given a range a..b get all patch ids for b..a */ - init_revisions(&check_rev); + init_revisions(&check_rev, prefix); o1->flags ^= UNINTERESTING; o2->flags ^= UNINTERESTING; add_pending_object(&check_rev, o1, "o1"); @@ -240,7 +239,7 @@ static void gen_message_id(char *dest, unsigned int length, char *base) (int)(email_end - email_start - 1), email_start + 1); } -int cmd_format_patch(int argc, const char **argv, char **envp) +int cmd_format_patch(int argc, const char **argv, const char *prefix) { struct commit *commit; struct commit **list = NULL; @@ -258,8 +257,9 @@ int cmd_format_patch(int argc, const char **argv, char **envp) char message_id[1024]; char ref_message_id[1024]; + setup_ident(); git_config(git_format_config); - init_revisions(&rev); + init_revisions(&rev, prefix); rev.commit_format = CMIT_FMT_EMAIL; rev.verbose_header = 1; rev.diff = 1; @@ -307,7 +307,6 @@ int cmd_format_patch(int argc, const char **argv, char **envp) !strcmp(argv[i], "-s")) { const char *committer; const char *endpos; - setup_ident(); committer = git_committer_info(1); endpos = strchr(committer, '>'); if (!endpos) @@ -363,7 +362,7 @@ int cmd_format_patch(int argc, const char **argv, char **envp) } if (ignore_if_in_upstream) - get_patch_ids(&rev, &patch_id_opts); + get_patch_ids(&rev, &patch_id_opts, prefix); if (!use_stdout) realstdout = fdopen(dup(1), "w"); diff --git a/builtin-ls-files.c b/builtin-ls-files.c index 8dae9f70e2..11386c432b 100644 --- a/builtin-ls-files.c +++ b/builtin-ls-files.c @@ -24,7 +24,6 @@ static int show_valid_bit = 0; static int line_terminator = '\n'; static int prefix_len = 0, prefix_offset = 0; -static const char *prefix = NULL; static const char **pathspec = NULL; static int error_unmatch = 0; static char *ps_matched = NULL; @@ -207,7 +206,7 @@ static void show_ce_entry(const char *tag, struct cache_entry *ce) } } -static void show_files(struct dir_struct *dir) +static void show_files(struct dir_struct *dir, const char *prefix) { int i; @@ -253,7 +252,7 @@ static void show_files(struct dir_struct *dir) /* * Prune the index to only contain stuff starting with "prefix" */ -static void prune_cache(void) +static void prune_cache(const char *prefix) { int pos = cache_name_pos(prefix, prefix_len); unsigned int first, last; @@ -276,7 +275,7 @@ static void prune_cache(void) active_nr = last; } -static void verify_pathspec(void) +static const char *verify_pathspec(const char *prefix) { const char **p, *n, *prev; char *real_prefix; @@ -313,7 +312,7 @@ static void verify_pathspec(void) memcpy(real_prefix, prev, max); real_prefix[max] = 0; } - prefix = real_prefix; + return real_prefix; } static const char ls_files_usage[] = @@ -322,14 +321,13 @@ static const char ls_files_usage[] = "[ --exclude-per-directory=<filename> ] [--full-name] [--abbrev] " "[--] [<file>]*"; -int cmd_ls_files(int argc, const char **argv, char** envp) +int cmd_ls_files(int argc, const char **argv, const char *prefix) { int i; int exc_given = 0; struct dir_struct dir; memset(&dir, 0, sizeof(dir)); - prefix = setup_git_directory(); if (prefix) prefix_offset = strlen(prefix); git_config(git_default_config); @@ -454,7 +452,7 @@ int cmd_ls_files(int argc, const char **argv, char** envp) /* Verify that the pathspec matches the prefix */ if (pathspec) - verify_pathspec(); + prefix = verify_pathspec(prefix); /* Treat unmatching pathspec elements as errors */ if (pathspec && error_unmatch) { @@ -477,8 +475,8 @@ int cmd_ls_files(int argc, const char **argv, char** envp) read_cache(); if (prefix) - prune_cache(); - show_files(&dir); + prune_cache(prefix); + show_files(&dir, prefix); if (ps_matched) { /* We need to make sure all pathspec matched otherwise diff --git a/builtin-ls-tree.c b/builtin-ls-tree.c index b8d0d88ba8..261147fdbe 100644 --- a/builtin-ls-tree.c +++ b/builtin-ls-tree.c @@ -18,7 +18,7 @@ static int abbrev = 0; static int ls_options = 0; static const char **pathspec; static int chomp_prefix = 0; -static const char *prefix; +static const char *ls_tree_prefix; static const char ls_tree_usage[] = "git-ls-tree [-d] [-r] [-t] [-z] [--name-only] [--name-status] [--full-name] [--abbrev[=<n>]] <tree-ish> [path...]"; @@ -71,7 +71,7 @@ static int show_tree(const unsigned char *sha1, const char *base, int baselen, return 0; if (chomp_prefix && - (baselen < chomp_prefix || memcmp(prefix, base, chomp_prefix))) + (baselen < chomp_prefix || memcmp(ls_tree_prefix, base, chomp_prefix))) return 0; if (!(ls_options & LS_NAME_ONLY)) @@ -85,13 +85,13 @@ static int show_tree(const unsigned char *sha1, const char *base, int baselen, return retval; } -int cmd_ls_tree(int argc, const char **argv, char **envp) +int cmd_ls_tree(int argc, const char **argv, const char *prefix) { unsigned char sha1[20]; struct tree *tree; - prefix = setup_git_directory(); git_config(git_default_config); + ls_tree_prefix = prefix; if (prefix && *prefix) chomp_prefix = strlen(prefix); while (1 < argc && argv[1][0] == '-') { diff --git a/builtin-mailinfo.c b/builtin-mailinfo.c index ac53f76f68..24a4fc63b3 100644 --- a/builtin-mailinfo.c +++ b/builtin-mailinfo.c @@ -446,7 +446,7 @@ static int read_one_header_line(char *line, int sz, FILE *in) break; } /* Count mbox From headers as headers */ - if (!ofs && !memcmp(line, "From ", 5)) + if (!ofs && (!memcmp(line, "From ", 5) || !memcmp(line, ">From ", 6))) ofs = 1; return ofs; } @@ -836,7 +836,7 @@ int mailinfo(FILE *in, FILE *out, int ks, const char *encoding, static const char mailinfo_usage[] = "git-mailinfo [-k] [-u | --encoding=<encoding>] msg patch <mail >info"; -int cmd_mailinfo(int argc, const char **argv, char **envp) +int cmd_mailinfo(int argc, const char **argv, const char *prefix) { /* NEEDSWORK: might want to do the optional .git/ directory * discovery diff --git a/builtin-mailsplit.c b/builtin-mailsplit.c index e2a0058435..91a699d34d 100644 --- a/builtin-mailsplit.c +++ b/builtin-mailsplit.c @@ -138,7 +138,7 @@ out: free(name); return ret; } -int cmd_mailsplit(int argc, const char **argv, char **envp) +int cmd_mailsplit(int argc, const char **argv, const char *prefix) { int nr = 0, nr_prec = 4, ret; int allow_bare = 0; diff --git a/builtin-mv.c b/builtin-mv.c new file mode 100644 index 0000000000..a731f8d9cf --- /dev/null +++ b/builtin-mv.c @@ -0,0 +1,295 @@ +/* + * "git mv" builtin command + * + * Copyright (C) 2006 Johannes Schindelin + */ +#include <fnmatch.h> + +#include "cache.h" +#include "builtin.h" +#include "dir.h" +#include "cache-tree.h" +#include "path-list.h" + +static const char builtin_mv_usage[] = +"git-mv [-n] [-f] (<source> <destination> | [-k] <source>... <destination>)"; + +static const char **copy_pathspec(const char *prefix, const char **pathspec, + int count, int base_name) +{ + const char **result = xmalloc((count + 1) * sizeof(const char *)); + memcpy(result, pathspec, count * sizeof(const char *)); + result[count] = NULL; + if (base_name) { + int i; + for (i = 0; i < count; i++) { + const char *last_slash = strrchr(result[i], '/'); + if (last_slash) + result[i] = last_slash + 1; + } + } + return get_pathspec(prefix, result); +} + +static void show_list(const char *label, struct path_list *list) +{ + if (list->nr > 0) { + int i; + printf("%s", label); + for (i = 0; i < list->nr; i++) + printf("%s%s", i > 0 ? ", " : "", list->items[i].path); + putchar('\n'); + } +} + +static const char *add_slash(const char *path) +{ + int len = strlen(path); + if (path[len - 1] != '/') { + char *with_slash = xmalloc(len + 2); + memcpy(with_slash, path, len); + with_slash[len++] = '/'; + with_slash[len] = 0; + return with_slash; + } + return path; +} + +static struct lock_file lock_file; + +int cmd_mv(int argc, const char **argv, const char *prefix) +{ + int i, newfd, count; + int verbose = 0, show_only = 0, force = 0, ignore_errors = 0; + const char **source, **destination, **dest_path; + enum update_mode { BOTH = 0, WORKING_DIRECTORY, INDEX } *modes; + struct stat st; + struct path_list overwritten = {NULL, 0, 0, 0}; + struct path_list src_for_dst = {NULL, 0, 0, 0}; + struct path_list added = {NULL, 0, 0, 0}; + struct path_list deleted = {NULL, 0, 0, 0}; + struct path_list changed = {NULL, 0, 0, 0}; + + git_config(git_default_config); + + newfd = hold_lock_file_for_update(&lock_file, get_index_file(), 1); + if (read_cache() < 0) + die("index file corrupt"); + + for (i = 1; i < argc; i++) { + const char *arg = argv[i]; + + if (arg[0] != '-') + break; + if (!strcmp(arg, "--")) { + i++; + break; + } + if (!strcmp(arg, "-n")) { + show_only = 1; + continue; + } + if (!strcmp(arg, "-f")) { + force = 1; + continue; + } + if (!strcmp(arg, "-k")) { + ignore_errors = 1; + continue; + } + usage(builtin_mv_usage); + } + count = argc - i - 1; + if (count < 1) + usage(builtin_mv_usage); + + source = copy_pathspec(prefix, argv + i, count, 0); + modes = xcalloc(count, sizeof(enum update_mode)); + dest_path = copy_pathspec(prefix, argv + argc - 1, 1, 0); + + if (!lstat(dest_path[0], &st) && + S_ISDIR(st.st_mode)) { + dest_path[0] = add_slash(dest_path[0]); + destination = copy_pathspec(dest_path[0], argv + i, count, 1); + } else { + if (count != 1) + usage(builtin_mv_usage); + destination = dest_path; + } + + /* Checking */ + for (i = 0; i < count; i++) { + const char *bad = NULL; + + if (show_only) + printf("Checking rename of '%s' to '%s'\n", + source[i], destination[i]); + + if (lstat(source[i], &st) < 0) + bad = "bad source"; + + if (S_ISDIR(st.st_mode)) { + const char *dir = source[i], *dest_dir = destination[i]; + int first, last, len = strlen(dir); + + if (lstat(dest_dir, &st) == 0) { + bad = "cannot move directory over file"; + goto next; + } + + modes[i] = WORKING_DIRECTORY; + + first = cache_name_pos(source[i], len); + if (first >= 0) + die ("Huh? %s/ is in index?", dir); + + first = -1 - first; + for (last = first; last < active_nr; last++) { + const char *path = active_cache[last]->name; + if (strncmp(path, dir, len) || path[len] != '/') + break; + } + + if (last - first < 1) + bad = "source directory is empty"; + else if (!bad) { + int j, dst_len = strlen(dest_dir); + + if (last - first > 0) { + source = realloc(source, + (count + last - first) + * sizeof(char *)); + destination = realloc(destination, + (count + last - first) + * sizeof(char *)); + modes = realloc(modes, + (count + last - first) + * sizeof(enum update_mode)); + } + + dest_dir = add_slash(dest_dir); + + for (j = 0; j < last - first; j++) { + const char *path = + active_cache[first + j]->name; + source[count + j] = path; + destination[count + j] = + prefix_path(dest_dir, dst_len, + path + len); + modes[count + j] = INDEX; + } + count += last - first; + } + + goto next; + } + + if (!bad && lstat(destination[i], &st) == 0) { + bad = "destination exists"; + if (force) { + /* + * only files can overwrite each other: + * check both source and destination + */ + if (S_ISREG(st.st_mode)) { + fprintf(stderr, "Warning: %s;" + " will overwrite!\n", + bad); + bad = NULL; + path_list_insert(destination[i], + &overwritten); + } else + bad = "Cannot overwrite"; + } + } + + if (!bad && + !strncmp(destination[i], source[i], strlen(source[i]))) + bad = "can not move directory into itself"; + + if (!bad && cache_name_pos(source[i], strlen(source[i])) < 0) + bad = "not under version control"; + + if (!bad) { + if (path_list_has_path(&src_for_dst, destination[i])) + bad = "multiple sources for the same target"; + else + path_list_insert(destination[i], &src_for_dst); + } + +next: + if (bad) { + if (ignore_errors) { + if (--count > 0) { + memmove(source + i, source + i + 1, + (count - i) * sizeof(char *)); + memmove(destination + i, + destination + i + 1, + (count - i) * sizeof(char *)); + } + } else + die ("%s, source=%s, destination=%s", + bad, source[i], destination[i]); + } + } + + for (i = 0; i < count; i++) { + if (show_only || verbose) + printf("Renaming %s to %s\n", + source[i], destination[i]); + if (!show_only && modes[i] != INDEX && + rename(source[i], destination[i]) < 0 && + !ignore_errors) + die ("renaming %s failed: %s", + source[i], strerror(errno)); + + if (modes[i] == WORKING_DIRECTORY) + continue; + + if (cache_name_pos(source[i], strlen(source[i])) >= 0) { + path_list_insert(source[i], &deleted); + + /* destination can be a directory with 1 file inside */ + if (path_list_has_path(&overwritten, destination[i])) + path_list_insert(destination[i], &changed); + else + path_list_insert(destination[i], &added); + } else + path_list_insert(destination[i], &added); + } + + if (show_only) { + show_list("Changed : ", &changed); + show_list("Adding : ", &added); + show_list("Deleting : ", &deleted); + } 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]; + + if (i < 0) + die ("Huh? Cache entry for %s unknown?", path); + refresh_cache_entry(ce, 0); + } + + for (i = 0; i < added.nr; i++) { + const char *path = added.items[i].path; + add_file_to_index(path, verbose); + } + + for (i = 0; i < deleted.nr; i++) { + const char *path = deleted.items[i].path; + remove_file_from_cache(path); + } + + if (active_cache_changed) { + if (write_cache(newfd, active_cache, active_nr) || + close(newfd) || + commit_lock_file(&lock_file)) + die("Unable to write new index file"); + } + } + + return 0; +} diff --git a/name-rev.c b/builtin-name-rev.c index f92f14e32f..571bba4817 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" @@ -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 e52e9774e5..2f9921224d 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" @@ -1242,7 +1243,7 @@ static int git_pack_config(const char *k, const char *v) return git_default_config(k, v); } -int main(int argc, char **argv) +int cmd_pack_objects(int argc, const char **argv, const char *prefix) { SHA_CTX ctx; char line[40 + 1 + PATH_MAX + 2]; @@ -1251,7 +1252,6 @@ int main(int argc, char **argv) int num_preferred_base = 0; int i; - setup_git_directory(); git_config(git_pack_config); progress = isatty(2); diff --git a/prune-packed.c b/builtin-prune-packed.c index d24b097114..d3dd94d9ef 100644 --- a/prune-packed.c +++ b/builtin-prune-packed.c @@ -1,3 +1,4 @@ +#include "builtin.h" #include "cache.h" static const char prune_packed_usage[] = @@ -54,12 +55,10 @@ static void prune_packed_objects(void) } } -int main(int argc, char **argv) +int cmd_prune_packed(int argc, const char **argv, const char *prefix) { int i; - setup_git_directory(); - for (i = 1; i < argc; i++) { const char *arg = argv[i]; diff --git a/builtin-prune.c b/builtin-prune.c index d196c41f13..89ec7f1426 100644 --- a/builtin-prune.c +++ b/builtin-prune.c @@ -10,7 +10,7 @@ #include "builtin.h" #include "cache-tree.h" -static const char prune_usage[] = "git prune [-n]"; +static const char prune_usage[] = "git-prune [-n]"; static int show_only = 0; static struct rev_info revs; @@ -217,7 +217,7 @@ static void add_cache_refs(void) add_cache_tree(active_cache_tree); } -int cmd_prune(int argc, const char **argv, char **envp) +int cmd_prune(int argc, const char **argv, const char *prefix) { int i; @@ -234,7 +234,7 @@ int cmd_prune(int argc, const char **argv, char **envp) * Set up revision parsing, and mark us as being interested * in all object types, not just commits. */ - init_revisions(&revs); + init_revisions(&revs, prefix); revs.tag_objects = 1; revs.blob_objects = 1; revs.tree_objects = 1; diff --git a/builtin-push.c b/builtin-push.c index 31cbfd7386..53bc378f73 100644 --- a/builtin-push.c +++ b/builtin-push.c @@ -8,7 +8,7 @@ #define MAX_URI (16) -static const char push_usage[] = "git push [--all] [--tags] [--force] <repository> [<refspec>...]"; +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; @@ -270,7 +270,7 @@ static int do_push(const char *repo) return 0; } -int cmd_push(int argc, const char **argv, char **envp) +int cmd_push(int argc, const char **argv, const char *prefix) { int i; const char *repo = "origin"; /* default repository */ @@ -291,7 +291,7 @@ int cmd_push(int argc, const char **argv, char **envp) tags = 1; continue; } - if (!strcmp(arg, "--force")) { + if (!strcmp(arg, "--force") || !strcmp(arg, "-f")) { force = 1; continue; } diff --git a/builtin-read-tree.c b/builtin-read-tree.c index 122b6f130b..8da8acbb0a 100644 --- a/builtin-read-tree.c +++ b/builtin-read-tree.c @@ -3,419 +3,17 @@ * * 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 int list_tree(unsigned char *sha1) { struct tree *tree = parse_tree_indirect(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); @@ -870,29 +88,22 @@ static const char read_tree_usage[] = "git-read-tree (<sha> | [[-m [--aggressive static struct lock_file lock_file; -int cmd_read_tree(int argc, const char **argv, char **envp) +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()); - if (newfd < 0) - die("unable to create new index file"); + 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]; @@ -900,12 +111,12 @@ int cmd_read_tree(int argc, const char **argv, char **envp) * 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; } @@ -913,7 +124,7 @@ int cmd_read_tree(int argc, const char **argv, char **envp) * not even look at the working tree. */ if (!strcmp(arg, "-i")) { - index_only = 1; + opts.index_only = 1; continue; } @@ -922,10 +133,10 @@ int cmd_read_tree(int argc, const char **argv, char **envp) * 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"); @@ -937,38 +148,38 @@ int cmd_read_tree(int argc, const char **argv, char **envp) * 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)) @@ -977,52 +188,53 @@ int cmd_read_tree(int argc, const char **argv, char **envp) 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, @@ -1030,7 +242,7 @@ int cmd_read_tree(int argc, const char **argv, char **envp) * 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/repo-config.c b/builtin-repo-config.c index 743f02b7de..c821e22717 100644 --- a/repo-config.c +++ b/builtin-repo-config.c @@ -1,3 +1,4 @@ +#include "builtin.h" #include "cache.h" #include <regex.h> @@ -128,7 +129,7 @@ free_strings: return ret; } -int main(int argc, const char **argv) +int cmd_repo_config(int argc, const char **argv, const char *prefix) { int nongit = 0; setup_git_directory_gently(&nongit); diff --git a/builtin-rev-list.c b/builtin-rev-list.c index 8f32871337..0dee1734a3 100644 --- a/builtin-rev-list.c +++ b/builtin-rev-list.c @@ -306,12 +306,12 @@ static void mark_edges_uninteresting(struct commit_list *list) } } -int cmd_rev_list(int argc, const char **argv, char **envp) +int cmd_rev_list(int argc, const char **argv, const char *prefix) { struct commit_list *list; int i; - init_revisions(&revs); + init_revisions(&revs, prefix); revs.abbrev = 0; revs.commit_format = CMIT_FMT_UNSPECIFIED; argc = setup_revisions(argc, argv, &revs, NULL); diff --git a/builtin-rev-parse.c b/builtin-rev-parse.c index b3e4386c1b..aca4a36032 100644 --- a/builtin-rev-parse.c +++ b/builtin-rev-parse.c @@ -209,11 +209,10 @@ static int try_difference(const char *arg) return 0; } -int cmd_rev_parse(int argc, const char **argv, char **envp) +int cmd_rev_parse(int argc, const char **argv, const char *prefix) { int i, as_is = 0, verify = 0; unsigned char sha1[20]; - const char *prefix = setup_git_directory(); git_config(git_default_config); diff --git a/builtin-rm.c b/builtin-rm.c index bb810ba41a..593d86744c 100644 --- a/builtin-rm.c +++ b/builtin-rm.c @@ -43,19 +43,16 @@ static int remove_file(const char *name) static struct lock_file lock_file; -int cmd_rm(int argc, const char **argv, char **envp) +int cmd_rm(int argc, const char **argv, const char *prefix) { int i, newfd; int verbose = 0, show_only = 0, force = 0; - const char *prefix = setup_git_directory(); const char **pathspec; char *seen; git_config(git_default_config); - newfd = hold_lock_file_for_update(&lock_file, get_index_file()); - if (newfd < 0) - die("unable to create new index file"); + newfd = hold_lock_file_for_update(&lock_file, get_index_file(), 1); if (read_cache() < 0) die("index file corrupt"); @@ -81,7 +78,7 @@ int cmd_rm(int argc, const char **argv, char **envp) force = 1; continue; } - die(builtin_rm_usage); + usage(builtin_rm_usage); } if (argc <= i) usage(builtin_rm_usage); @@ -116,7 +113,7 @@ int cmd_rm(int argc, const char **argv, char **envp) printf("rm '%s'\n", path); if (remove_file_from_cache(path)) - die("git rm: unable to remove %s", path); + die("git-rm: unable to remove %s", path); cache_tree_invalidate_path(active_cache_tree, path); } @@ -140,7 +137,7 @@ int cmd_rm(int argc, const char **argv, char **envp) continue; } if (!removed) - die("git rm: %s: %s", path, strerror(errno)); + die("git-rm: %s: %s", path, strerror(errno)); } } diff --git a/builtin-show-branch.c b/builtin-show-branch.c index 82f75b72de..2a1b848f6c 100644 --- a/builtin-show-branch.c +++ b/builtin-show-branch.c @@ -550,7 +550,7 @@ static int omit_in_dense(struct commit *commit, struct commit **rev, int n) return 0; } -int cmd_show_branch(int ac, const char **av, char **envp) +int cmd_show_branch(int ac, const char **av, const char *prefix) { struct commit *rev[MAX_REVS], *commit; struct commit_list *list = NULL, *seen = NULL; @@ -573,7 +573,6 @@ int cmd_show_branch(int ac, const char **av, char **envp) int topics = 0; int dense = 1; - setup_git_directory(); git_config(git_show_branch_config); /* If nothing is specified, try the default first */ diff --git a/builtin-stripspace.c b/builtin-stripspace.c index 2ce1264f7b..09cc9108cd 100644 --- a/builtin-stripspace.c +++ b/builtin-stripspace.c @@ -54,7 +54,7 @@ void stripspace(FILE *in, FILE *out) fputc('\n', out); } -int cmd_stripspace(int argc, const char **argv, char **envp) +int cmd_stripspace(int argc, const char **argv, const char *prefix) { stripspace(stdin, stdout); return 0; diff --git a/symbolic-ref.c b/builtin-symbolic-ref.c index 193c87c174..b4ec6f28ed 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[] = @@ -17,9 +18,8 @@ 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: diff --git a/builtin-tar-tree.c b/builtin-tar-tree.c index e5aaded820..215892b696 100644 --- a/builtin-tar-tree.c +++ b/builtin-tar-tree.c @@ -308,18 +308,18 @@ int git_tar_config(const char *var, const char *value) return git_default_config(var, value); } -static int generate_tar(int argc, const char **argv, char** envp) +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; - setup_git_directory(); git_config(git_tar_config); switch (argc) { @@ -342,8 +342,8 @@ static int generate_tar(int argc, const char **argv, char** envp) } else archive_time = time(NULL); - tree.buf = read_object_with_reference(sha1, tree_type, &tree.size, - tree_sha1); + 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)); @@ -352,6 +352,7 @@ static int generate_tar(int argc, const char **argv, char** envp) write_entry(tree_sha1, ¤t_path, 040777, NULL, 0); traverse_tree(&tree, ¤t_path); write_trailer(); + free(buffer); free(current_path.buf); return 0; } @@ -402,19 +403,19 @@ static int remote_tar(int argc, const char **argv) return !!ret; } -int cmd_tar_tree(int argc, const char **argv, char **envp) +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, envp); + return generate_tar(argc, argv, prefix); } /* ustar header + extended global header content */ #define HEADERSIZE (2 * RECORDSIZE) -int cmd_get_tar_commit_id(int argc, const char **argv, char **envp) +int cmd_get_tar_commit_id(int argc, const char **argv, const char *prefix) { char buffer[HEADERSIZE]; struct ustar_header *header = (struct ustar_header *)buffer; diff --git a/unpack-objects.c b/builtin-unpack-objects.c index b23fe58ce1..63f4b8e45d 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" @@ -112,7 +113,7 @@ static void write_object(void *buf, unsigned long size, const char *type) } static int resolve_delta(const char *type, - void *base, unsigned long base_size, + void *base, unsigned long base_size, void *delta, unsigned long delta_size) { void *result; @@ -260,12 +261,11 @@ 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); diff --git a/builtin-update-index.c b/builtin-update-index.c index 1a4200d151..d2556f376b 100644 --- a/builtin-update-index.c +++ b/builtin-update-index.c @@ -476,12 +476,11 @@ static int do_reupdate(int ac, const char **av, return 0; } -int cmd_update_index(int argc, const char **argv, char **envp) +int cmd_update_index(int argc, const char **argv, const char *prefix) { int i, newfd, entries, has_errors = 0, line_termination = '\n'; int allow_options = 1; int read_from_stdin = 0; - const char *prefix = setup_git_directory(); int prefix_length = prefix ? strlen(prefix) : 0; char set_executable_bit = 0; unsigned int refresh_flags = 0; @@ -492,9 +491,7 @@ int cmd_update_index(int argc, const char **argv, char **envp) /* We can't free this memory, it becomes part of a linked list parsed atexit() */ lock_file = xcalloc(1, sizeof(struct lock_file)); - newfd = hold_lock_file_for_update(lock_file, get_index_file()); - if (newfd < 0) - die("unable to create new cachefile"); + newfd = hold_lock_file_for_update(lock_file, get_index_file(), 1); entries = read_cache(); if (entries < 0) diff --git a/builtin-update-ref.c b/builtin-update-ref.c index 83094abe0f..5bd71825fd 100644 --- a/builtin-update-ref.c +++ b/builtin-update-ref.c @@ -5,7 +5,7 @@ static const char git_update_ref_usage[] = "git-update-ref <refname> <value> [<oldval>] [-m <reason>]"; -int cmd_update_ref(int argc, const char **argv, char **envp) +int cmd_update_ref(int argc, const char **argv, const char *prefix) { const char *refname=NULL, *value=NULL, *oldval=NULL, *msg=NULL; struct ref_lock *lock; @@ -13,7 +13,6 @@ int cmd_update_ref(int argc, const char **argv, char **envp) int i; setup_ident(); - setup_git_directory(); git_config(git_default_config); for (i = 1; i < argc; i++) { diff --git a/builtin-upload-tar.c b/builtin-upload-tar.c index d4fa7b56c3..7b401bbb77 100644 --- a/builtin-upload-tar.c +++ b/builtin-upload-tar.c @@ -15,7 +15,7 @@ static int nak(const char *reason) return 1; } -int cmd_upload_tar(int argc, const char **argv, char **envp) +int cmd_upload_tar(int argc, const char **argv, const char *prefix) { int len; const char *dir = argv[1]; diff --git a/builtin-verify-pack.c b/builtin-verify-pack.c new file mode 100644 index 0000000000..7d39d9bcd1 --- /dev/null +++ b/builtin-verify-pack.c @@ -0,0 +1,79 @@ +#include "builtin.h" +#include "cache.h" +#include "pack.h" + +static int verify_one_pack(const char *path, int verbose) +{ + char arg[PATH_MAX]; + int len; + struct packed_git *pack; + int err; + + len = strlcpy(arg, path, PATH_MAX); + if (len >= PATH_MAX) + return error("name too long: %s", path); + + /* + * In addition to "foo.idx" we accept "foo.pack" and "foo"; + * normalize these forms to "foo.idx" for add_packed_git(). + */ + if (has_extension(arg, ".pack")) { + strcpy(arg + len - 5, ".idx"); + len--; + } else if (!has_extension(arg, ".idx")) { + if (len + 4 >= PATH_MAX) + return error("name too long: %s.idx", arg); + strcpy(arg + len, ".idx"); + len += 4; + } + + /* + * add_packed_git() uses our buffer (containing "foo.idx") to + * build the pack filename ("foo.pack"). Make sure it fits. + */ + if (len + 1 >= PATH_MAX) { + arg[len - 4] = '\0'; + return error("name too long: %s.pack", arg); + } + + pack = add_packed_git(arg, len, 1); + if (!pack) + return error("packfile %s not found.", arg); + + err = verify_pack(pack, verbose); + free(pack); + + return err; +} + +static const char verify_pack_usage[] = "git-verify-pack [-v] <pack>..."; + +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 < argc) { + if (!no_more_options && argv[1][0] == '-') { + if (!strcmp("-v", argv[1])) + verbose = 1; + else if (!strcmp("--", argv[1])) + no_more_options = 1; + else + usage(verify_pack_usage); + } + else { + if (verify_one_pack(argv[1], verbose)) + err = 1; + nothing_done = 0; + } + argc--; argv++; + } + + if (nothing_done) + usage(verify_pack_usage); + + return err; +} diff --git a/builtin-write-tree.c b/builtin-write-tree.c index 449a4d1b57..ca06149f18 100644 --- a/builtin-write-tree.c +++ b/builtin-write-tree.c @@ -18,7 +18,7 @@ int write_tree(unsigned char *sha1, int missing_ok, const char *prefix) /* We can't free this memory, it becomes part of a linked list parsed atexit() */ struct lock_file *lock_file = xcalloc(1, sizeof(struct lock_file)); - newfd = hold_lock_file_for_update(lock_file, get_index_file()); + newfd = hold_lock_file_for_update(lock_file, get_index_file(), 0); entries = read_cache(); if (entries < 0) @@ -60,14 +60,12 @@ int write_tree(unsigned char *sha1, int missing_ok, const char *prefix) return 0; } -int cmd_write_tree(int argc, const char **argv, char **envp) +int cmd_write_tree(int argc, const char **argv, const char *unused_prefix) { int missing_ok = 0, ret; const char *prefix = NULL; unsigned char sha1[20]; - setup_git_directory(); - while (1 < argc) { const char *arg = argv[1]; if (!strcmp(arg, "--missing-ok")) @@ -75,7 +73,7 @@ int cmd_write_tree(int argc, const char **argv, char **envp) else if (!strncmp(arg, "--prefix=", 9)) prefix = arg + 9; else - die(write_tree_usage); + usage(write_tree_usage); argc--; argv++; } @@ -2,66 +2,63 @@ #define BUILTIN_H #include <stdio.h> - -#ifndef PATH_MAX -# define PATH_MAX 4096 -#endif +#include <limits.h> extern const char git_version_string[]; +extern const char git_usage_string[]; -void cmd_usage(int show_all, const char *exec_path, const char *fmt, ...) -#ifdef __GNUC__ - __attribute__((__format__(__printf__, 3, 4), __noreturn__)) -#endif - ; - -extern int cmd_help(int argc, const char **argv, char **envp); -extern int cmd_version(int argc, const char **argv, char **envp); - -extern int cmd_whatchanged(int argc, const char **argv, char **envp); -extern int cmd_show(int argc, const char **argv, char **envp); -extern int cmd_log(int argc, const char **argv, char **envp); -extern int cmd_diff(int argc, const char **argv, char **envp); -extern int cmd_format_patch(int argc, const char **argv, char **envp); -extern int cmd_count_objects(int argc, const char **argv, char **envp); - -extern int cmd_prune(int argc, const char **argv, char **envp); - -extern int cmd_push(int argc, const char **argv, char **envp); -extern int cmd_grep(int argc, const char **argv, char **envp); -extern int cmd_rm(int argc, const char **argv, char **envp); -extern int cmd_add(int argc, const char **argv, char **envp); -extern int cmd_rev_list(int argc, const char **argv, char **envp); -extern int cmd_check_ref_format(int argc, const char **argv, char **envp); -extern int cmd_init_db(int argc, const char **argv, char **envp); -extern int cmd_tar_tree(int argc, const char **argv, char **envp); -extern int cmd_upload_tar(int argc, const char **argv, char **envp); -extern int cmd_get_tar_commit_id(int argc, const char **argv, char **envp); -extern int cmd_ls_files(int argc, const char **argv, char **envp); -extern int cmd_ls_tree(int argc, const char **argv, char **envp); -extern int cmd_read_tree(int argc, const char **argv, char **envp); -extern int cmd_commit_tree(int argc, const char **argv, char **envp); -extern int cmd_apply(int argc, const char **argv, char **envp); -extern int cmd_show_branch(int argc, const char **argv, char **envp); -extern int cmd_diff_files(int argc, const char **argv, char **envp); -extern int cmd_diff_index(int argc, const char **argv, char **envp); -extern int cmd_diff_stages(int argc, const char **argv, char **envp); -extern int cmd_diff_tree(int argc, const char **argv, char **envp); -extern int cmd_cat_file(int argc, const char **argv, char **envp); -extern int cmd_rev_parse(int argc, const char **argv, char **envp); -extern int cmd_update_index(int argc, const char **argv, char **envp); -extern int cmd_update_ref(int argc, const char **argv, char **envp); -extern int cmd_fmt_merge_msg(int argc, const char **argv, char **envp); - -extern int cmd_write_tree(int argc, const char **argv, char **envp); -extern int write_tree(unsigned char *sha1, int missing_ok, const char *prefix); - -extern int cmd_mailsplit(int argc, const char **argv, char **envp); +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_mailinfo(int argc, const char **argv, char **envp); -extern int mailinfo(FILE *in, FILE *out, int ks, const char *encoding, const char *msg, const char *patch); +extern int cmd_add(int argc, const char **argv, const char *prefix); +extern int cmd_apply(int argc, const char **argv, const char *prefix); +extern int cmd_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_commit_tree(int argc, const char **argv, const char *prefix); +extern int cmd_count_objects(int argc, const char **argv, const char *prefix); +extern int cmd_diff_files(int argc, const char **argv, const char *prefix); +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_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_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_tar(int argc, const char **argv, const char *prefix); +extern int cmd_version(int argc, const char **argv, const char *prefix); +extern int cmd_whatchanged(int argc, const char **argv, const char *prefix); +extern int cmd_write_tree(int argc, const char **argv, const char *prefix); +extern int cmd_verify_pack(int argc, const char **argv, const char *prefix); -extern int cmd_stripspace(int argc, const char **argv, char **envp); -extern void stripspace(FILE *in, FILE *out); #endif @@ -115,6 +115,7 @@ static inline unsigned int create_ce_mode(unsigned int mode) extern struct cache_entry **active_cache; extern unsigned int active_nr, active_alloc, active_cache_changed; extern struct cache_tree *active_cache_tree; +extern int cache_errno; #define GIT_DIR_ENVIRONMENT "GIT_DIR" #define DEFAULT_GIT_DIR_ENVIRONMENT ".git" @@ -142,6 +143,7 @@ extern void verify_non_filename(const char *prefix, const char *name); /* Initialize and use the cache information */ 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 verify_path(const char *path); extern int cache_name_pos(const char *name, int namelen); @@ -149,8 +151,10 @@ extern int cache_name_pos(const char *name, int namelen); #define ADD_CACHE_OK_TO_REPLACE 2 /* Ok to replace file/directory */ #define ADD_CACHE_SKIP_DFCHECK 4 /* Ok to skip DF conflict checks */ extern int add_cache_entry(struct cache_entry *ce, int option); +extern struct cache_entry *refresh_cache_entry(struct cache_entry *ce, int really); extern int remove_cache_entry_at(int pos); extern int remove_file_from_cache(const char *path); +extern int add_file_to_index(const char *path, int verbose); extern int ce_same_name(struct cache_entry *a, struct cache_entry *b); extern int ce_match_stat(struct cache_entry *ce, struct stat *st, int); extern int ce_modified(struct cache_entry *ce, struct stat *st, int); @@ -171,7 +175,7 @@ struct lock_file { struct lock_file *next; char filename[PATH_MAX]; }; -extern int hold_lock_file_for_update(struct lock_file *, const char *path); +extern int hold_lock_file_for_update(struct lock_file *, const char *path, int); extern int commit_lock_file(struct lock_file *); extern void rollback_lock_file(struct lock_file *); @@ -384,6 +388,7 @@ extern int receive_keep_pack(int fd[2], const char *me, int quiet, int); /* pager.c */ extern void setup_pager(void); extern int pager_in_use; +extern int pager_use_color; /* base85 */ int decode_85(char *dst, char *line, int linelen); diff --git a/combine-diff.c b/combine-diff.c index 919112bba9..ba8baca0ab 100644 --- a/combine-diff.c +++ b/combine-diff.c @@ -497,11 +497,17 @@ static void show_parent_lno(struct sline *sline, unsigned long l0, unsigned long printf(" -%lu,%lu", l0, l1-l0); } -static void dump_sline(struct sline *sline, unsigned long cnt, int num_parent) +static void dump_sline(struct sline *sline, unsigned long cnt, int num_parent, + int use_color) { unsigned long mark = (1UL<<num_parent); int i; unsigned long lno = 0; + const char *c_frag = diff_get_color(use_color, DIFF_FRAGINFO); + const char *c_new = diff_get_color(use_color, DIFF_FILE_NEW); + const char *c_old = diff_get_color(use_color, DIFF_FILE_OLD); + const char *c_plain = diff_get_color(use_color, DIFF_PLAIN); + const char *c_reset = diff_get_color(use_color, DIFF_RESET); if (!cnt) return; /* result deleted */ @@ -522,12 +528,13 @@ static void dump_sline(struct sline *sline, unsigned long cnt, int num_parent) rlines = hunk_end - lno; if (cnt < hunk_end) rlines--; /* pointing at the last delete hunk */ + fputs(c_frag, stdout); for (i = 0; i <= num_parent; i++) putchar(combine_marker); for (i = 0; i < num_parent; i++) show_parent_lno(sline, lno, hunk_end, i); printf(" +%lu,%lu ", lno+1, rlines); for (i = 0; i <= num_parent; i++) putchar(combine_marker); - putchar('\n'); + printf("%s\n", c_reset); while (lno < hunk_end) { struct lline *ll; int j; @@ -535,18 +542,23 @@ static void dump_sline(struct sline *sline, unsigned long cnt, int num_parent) sl = &sline[lno++]; ll = sl->lost_head; while (ll) { + fputs(c_old, stdout); for (j = 0; j < num_parent; j++) { if (ll->parent_map & (1UL<<j)) putchar('-'); else putchar(' '); } - puts(ll->line); + printf("%s%s\n", ll->line, c_reset); ll = ll->next; } if (cnt < lno) break; p_mask = 1; + if (!(sl->flag & (mark-1))) + fputs(c_plain, stdout); + else + fputs(c_new, stdout); for (j = 0; j < num_parent; j++) { if (p_mask & sl->flag) putchar('+'); @@ -554,7 +566,7 @@ static void dump_sline(struct sline *sline, unsigned long cnt, int num_parent) putchar(' '); p_mask <<= 1; } - printf("%.*s\n", sl->len, sl->bol); + printf("%.*s%s\n", sl->len, sl->bol, c_reset); } } } @@ -586,14 +598,15 @@ static void reuse_combine_diff(struct sline *sline, unsigned long cnt, sline->p_lno[i] = sline->p_lno[j]; } -static void dump_quoted_path(const char *prefix, const char *path) +static void dump_quoted_path(const char *prefix, const char *path, + const char *c_meta, const char *c_reset) { - fputs(prefix, stdout); + printf("%s%s", c_meta, prefix); if (quote_c_style(path, NULL, NULL, 0)) quote_c_style(path, NULL, stdout, 0); else printf("%s", path); - putchar('\n'); + printf("%s\n", c_reset); } static int show_patch_diff(struct combine_diff_path *elem, int num_parent, @@ -699,18 +712,22 @@ static int show_patch_diff(struct combine_diff_path *elem, int num_parent, if (show_hunks || mode_differs || working_tree_file) { const char *abb; + int use_color = opt->color_diff; + const char *c_meta = diff_get_color(use_color, DIFF_METAINFO); + const char *c_reset = diff_get_color(use_color, DIFF_RESET); if (rev->loginfo) show_log(rev, opt->msg_sep); - dump_quoted_path(dense ? "diff --cc " : "diff --combined ", elem->path); - printf("index "); + dump_quoted_path(dense ? "diff --cc " : "diff --combined ", + elem->path, c_meta, c_reset); + printf("%sindex ", c_meta); for (i = 0; i < num_parent; i++) { abb = find_unique_abbrev(elem->parent[i].sha1, abbrev); printf("%s%s", i ? "," : "", abb); } abb = find_unique_abbrev(elem->sha1, abbrev); - printf("..%s\n", abb); + printf("..%s%s\n", abb, c_reset); if (mode_differs) { int added = !!elem->mode; @@ -719,10 +736,11 @@ static int show_patch_diff(struct combine_diff_path *elem, int num_parent, DIFF_STATUS_ADDED) added = 0; if (added) - printf("new file mode %06o", elem->mode); + printf("%snew file mode %06o", + c_meta, elem->mode); else { if (!elem->mode) - printf("deleted file "); + printf("%sdeleted file ", c_meta); printf("mode "); for (i = 0; i < num_parent; i++) { printf("%s%06o", i ? "," : "", @@ -731,11 +749,11 @@ static int show_patch_diff(struct combine_diff_path *elem, int num_parent, if (elem->mode) printf("..%06o", elem->mode); } - putchar('\n'); + printf("%s\n", c_reset); } - dump_quoted_path("--- a/", elem->path); - dump_quoted_path("+++ b/", elem->path); - dump_sline(sline, cnt, num_parent); + dump_quoted_path("--- a/", elem->path, c_meta, c_reset); + dump_quoted_path("+++ b/", elem->path, c_meta, c_reset); + dump_sline(sline, cnt, num_parent, opt->color_diff); } free(result); @@ -309,6 +309,11 @@ int git_default_config(const char *var, const char *value) return 0; } + if (!strcmp(var, "pager.color")) { + pager_use_color = git_config_bool(var,value); + return 0; + } + /* Add other config variables here and to Documentation/config.txt. */ return 0; } diff --git a/config.mak.in b/config.mak.in new file mode 100644 index 0000000000..369e6116e0 --- /dev/null +++ b/config.mak.in @@ -0,0 +1,40 @@ +# git Makefile configuration, included in main Makefile +# @configure_input@ + +CC = @CC@ +AR = @AR@ +TAR = @TAR@ +#INSTALL = @INSTALL@ # needs install-sh or install.sh in sources + +prefix = @prefix@ +exec_prefix = @exec_prefix@ +bindir = @bindir@ +#gitexecdir = @libexecdir@/git-core/ +datarootdir = @datarootdir@ +template_dir = @datadir@/git-core/templates/ +GIT_PYTHON_DIR = @datadir@/git-core/python + +mandir=@mandir@ + +srcdir = @srcdir@ +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@ + diff --git a/configure.ac b/configure.ac new file mode 100644 index 0000000000..e890131c46 --- /dev/null +++ b/configure.ac @@ -0,0 +1,352 @@ +# -*- Autoconf -*- +# Process this file with autoconf to produce a configure script. + +AC_PREREQ(2.59) +AC_INIT([git], [@@GIT_VERSION@@], [git@vger.kernel.org]) + +AC_CONFIG_SRCDIR([git.c]) + +config_file=config.mak.autogen +config_append=config.mak.append +config_in=config.mak.in + +echo "# ${config_append}. Generated by configure." > "${config_append}" + + +## Definitions of macros +# GIT_CONF_APPEND_LINE(LINE) +# -------------------------- +# 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 NO_PYTHON if you want to lose all benefits of the recursive merge. +# Define PYTHON_PATH to provide path to Python. +AC_ARG_WITH(python,[AS_HELP_STRING([--with-python=PATH], [provide PATH to python]) +AS_HELP_STRING([--without-python], [don't use python scripts])], + [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_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. +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([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], +[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], +[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], +[NEEDS_LIBICONV=], +[NEEDS_LIBICONV=YesPlease]) +AC_SUBST(NEEDS_LIBICONV) +# +# Define NEEDS_SOCKET if linking with libc is not enough (SunOS, +# Patrick Mauritz). +AC_CHECK_LIB([c], [socket], +[NEEDS_SOCKET=], +[NEEDS_SOCKET=YesPlease]) +AC_SUBST(NEEDS_SOCKET) + + +## Checks for header files. + + +## Checks for typedefs, structures, and compiler characteristics. +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, +[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, +[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, +[NO_SOCKADDR_STORAGE=], +[NO_SOCKADDR_STORAGE=YesPlease], +[#include <netinet/in.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. +## (in default C library and libraries checked by AC_CHECK_LIB) +AC_MSG_NOTICE([CHECKS for library functions]) +# +# Define NO_STRCASESTR if you don't have strcasestr. +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, +[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, +[NO_SETENV=], +[NO_SETENV=YesPlease]) +AC_SUBST(NO_SETENV) +# +# Define NO_MMAP if you want to avoid mmap. +# +# Define NO_ICONV if your libc does not properly support iconv. + + +## Other checks. +# Define USE_PIC if you need the main git objects to be built with -fPIC +# in order to build and link perl/Git.so. x86-64 seems to need this. +# +# Define NO_SYMLINK_HEAD if you never want .git/HEAD to be a symbolic link. +# Enable it on Windows. By default, symrefs are still used. +# +# Define WITH_OWN_SUBPROCESS_PY if you want to use with python 2.3. +# +# Define NO_ACCURATE_DIFF if your diff program at least sometimes misses +# a missing newline at the end of the file. + + +## Site configuration (override autodetection) +## --with-PACKAGE[=ARG] and --without-PACKAGE +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. +# +# 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 PPC_SHA1 environment variable when running make to make use of +# a bundled SHA1 routine optimized for PowerPC. +# +# Define ARM_SHA1 environment variable when running make to make use of +# a bundled SHA1 routine optimized for ARM. +# +# 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 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_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. + +## --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. +# +# 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. + + +## Output files +AC_CONFIG_FILES(["${config_file}":"${config_in}":"${config_append}"]) +AC_OUTPUT + + +## Cleanup +rm -f "${config_append}" diff --git a/contrib/emacs/Makefile b/contrib/emacs/Makefile index d3619db510..350846de90 100644 --- a/contrib/emacs/Makefile +++ b/contrib/emacs/Makefile @@ -3,9 +3,9 @@ EMACS = emacs ELC = git.elc vc-git.elc -INSTALL = install +INSTALL ?= install INSTALL_ELC = $(INSTALL) -m 644 -prefix = $(HOME) +prefix ?= $(HOME) emacsdir = $(prefix)/share/emacs/site-lisp all: $(ELC) diff --git a/convert-objects.c b/convert-objects.c index ebea8e472b..168771ed85 100644 --- a/convert-objects.c +++ b/convert-objects.c @@ -1,5 +1,6 @@ #define _XOPEN_SOURCE 500 /* glibc2 and AIX 5.3L need this */ #define _XOPEN_SOURCE_EXTENDED 1 /* AIX 5.3L needs this */ +#define _GNU_SOURCE #include <time.h> #include "cache.h" #include "blob.h" @@ -19,7 +19,7 @@ static const char daemon_usage[] = "git-daemon [--verbose] [--syslog] [--inetd | --port=n] [--export-all]\n" " [--timeout=n] [--init-timeout=n] [--strict-paths]\n" " [--base-path=path] [--user-path | --user-path=path]\n" -" [--reuseaddr] [directory...]"; +" [--reuseaddr] [--detach] [--pid-file=file] [directory...]"; /* List of acceptable pathname prefixes */ static char **ok_paths = NULL; @@ -175,7 +175,7 @@ int git_diff_ui_config(const char *var, const char *value) diff_use_color_default = 1; /* bool */ else if (!strcasecmp(value, "auto")) { diff_use_color_default = 0; - if (isatty(1) || pager_in_use) { + if (isatty(1) || (pager_in_use && pager_use_color)) { char *term = getenv("TERM"); if (term && strcmp(term, "dumb")) diff_use_color_default = 1; @@ -358,12 +358,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) @@ -398,12 +538,31 @@ static void fn_out_consume(void *priv, char *line, unsigned long len) 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; - } + if (ecbdata->diff_words && nparents != 1) + /* fall back to normal diff */ + free_diff_words_data(ecbdata); + if (ecbdata->diff_words) { + if (line[0] == '-') { + diff_words_append(line, len, + &ecbdata->diff_words->minus); + return; + } else if (line[0] == '+') { + diff_words_append(line, len, + &ecbdata->diff_words->plus); + return; + } + if (ecbdata->diff_words->minus.text.size || + ecbdata->diff_words->plus.text.size) + diff_words_show(ecbdata->diff_words); + line++; + len--; + } else + for (i = 0; i < nparents && len; i++) { + if (line[i] == '-') + color = DIFF_FILE_OLD; + else if (line[i] == '+') + color = DIFF_FILE_NEW; + } set = diff_get_color(ecbdata->color_diff, color); } if (len > 0 && line[len-1] == '\n') @@ -836,7 +995,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: @@ -1515,10 +1679,8 @@ void diff_setup(struct diff_options *options) int diff_setup_done(struct diff_options *options) { - if ((options->find_copies_harder && - options->detect_rename != DIFF_DETECT_COPY) || - (0 <= options->rename_limit && !options->detect_rename)) - return -1; + if (options->find_copies_harder) + options->detect_rename = DIFF_DETECT_COPY; if (options->output_format & (DIFF_FORMAT_NAME | DIFF_FORMAT_NAME_STATUS | @@ -1699,6 +1861,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 @@ -1786,13 +1950,9 @@ struct diff_filepair *diff_queue(struct diff_queue_struct *queue, struct diff_filespec *one, struct diff_filespec *two) { - struct diff_filepair *dp = xmalloc(sizeof(*dp)); + struct diff_filepair *dp = xcalloc(1, sizeof(*dp)); dp->one = one; dp->two = two; - dp->score = 0; - dp->status = 0; - dp->source_stays = 0; - dp->broken_pair = 0; if (queue) diff_q(queue, dp); return dp; @@ -46,7 +46,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; diff --git a/diffcore-rename.c b/diffcore-rename.c index 1de8d32502..0ec488a903 100644 --- a/diffcore-rename.c +++ b/diffcore-rename.c @@ -205,6 +205,7 @@ static void record_rename_pair(int dst_index, int src_index, int score) fill_filespec(two, dst->sha1, dst->mode); dp = diff_queue(NULL, one, two); + dp->renamed_pair = 1; if (!strcmp(src->path, dst->path)) dp->score = rename_src[src_index].score; else diff --git a/diffcore.h b/diffcore.h index 73c7842cc7..2249bc2c05 100644 --- a/diffcore.h +++ b/diffcore.h @@ -53,11 +53,12 @@ struct diff_filepair { char status; /* M C R N D U (see Documentation/diff-format.txt) */ unsigned source_stays : 1; /* all of R/C are copies */ unsigned broken_pair : 1; + unsigned renamed_pair : 1; }; #define DIFF_PAIR_UNMERGED(p) \ (!DIFF_FILE_VALID((p)->one) && !DIFF_FILE_VALID((p)->two)) -#define DIFF_PAIR_RENAME(p) (strcmp((p)->one->path, (p)->two->path)) +#define DIFF_PAIR_RENAME(p) ((p)->renamed_pair) #define DIFF_PAIR_BROKEN(p) \ ( (!DIFF_FILE_VALID((p)->one) != !DIFF_FILE_VALID((p)->two)) && \ diff --git a/environment.c b/environment.c index 42f39d657e..87162b2572 100644 --- a/environment.c +++ b/environment.c @@ -23,6 +23,7 @@ int shared_repository = PERM_UMASK; const char *apply_default_whitespace = NULL; 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; diff --git a/fetch-clone.c b/fetch-clone.c index 81d1371296..5e84c4620f 100644 --- a/fetch-clone.c +++ b/fetch-clone.c @@ -129,10 +129,12 @@ static pid_t setup_sideband(int sideband, const char *me, int fd[2], int xd[2]) len--; switch (buf[0] & 0xFF) { case 3: + safe_write(2, "remote: ", 8); safe_write(2, buf+1, len); - fprintf(stderr, "\n"); + safe_write(2, "\n", 1); exit(1); case 2: + safe_write(2, "remote: ", 8); safe_write(2, buf+1, len); continue; case 1: @@ -7,9 +7,7 @@ #include "tag.h" #include "blob.h" #include "refs.h" - -const char *write_ref = NULL; -const char *write_ref_log_details = NULL; +#include "strbuf.h" int get_tree = 0; int get_history = 0; @@ -213,54 +211,106 @@ static int mark_complete(const char *path, const unsigned char *sha1) return 0; } -int pull(char *target) +int pull_targets_stdin(char ***target, const char ***write_ref) +{ + int targets = 0, targets_alloc = 0; + struct strbuf buf; + *target = NULL; *write_ref = NULL; + strbuf_init(&buf); + while (1) { + char *rf_one = NULL; + char *tg_one; + + read_line(&buf, stdin, '\n'); + if (buf.eof) + break; + tg_one = buf.buf; + rf_one = strchr(tg_one, '\t'); + if (rf_one) + *rf_one++ = 0; + + if (targets >= targets_alloc) { + targets_alloc = targets_alloc ? targets_alloc * 2 : 64; + *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; + targets++; + } + return targets; +} + +void pull_targets_free(int targets, char **target, const char **write_ref) { - struct ref_lock *lock = NULL; - unsigned char sha1[20]; + while (targets--) { + free(target[targets]); + if (write_ref && write_ref[targets]) + free((char *) write_ref[targets]); + } +} + +int pull(int targets, char **target, const char **write_ref, + const char *write_ref_log_details) +{ + struct ref_lock **lock = xcalloc(targets, sizeof(struct ref_lock *)); + unsigned char *sha1 = xmalloc(targets * 20); char *msg; int ret; + int i; save_commit_buffer = 0; track_object_refs = 0; - if (write_ref) { - lock = lock_ref_sha1(write_ref, NULL, 0); - if (!lock) { - error("Can't lock ref %s", write_ref); - return -1; + + for (i = 0; i < targets; i++) { + if (!write_ref || !write_ref[i]) + continue; + + lock[i] = lock_ref_sha1(write_ref[i], NULL, 0); + if (!lock[i]) { + error("Can't lock ref %s", write_ref[i]); + goto unlock_and_fail; } } if (!get_recover) for_each_ref(mark_complete); - if (interpret_target(target, sha1)) { - error("Could not interpret %s as something to pull", target); - if (lock) - unlock_ref(lock); - return -1; + for (i = 0; i < targets; i++) { + if (interpret_target(target[i], &sha1[20 * i])) { + error("Could not interpret %s as something to pull", target[i]); + goto unlock_and_fail; + } + if (process(lookup_unknown_object(&sha1[20 * i]))) + goto unlock_and_fail; } - if (process(lookup_unknown_object(sha1))) { - if (lock) - unlock_ref(lock); - return -1; + + if (loop()) + goto unlock_and_fail; + + if (write_ref_log_details) { + msg = xmalloc(strlen(write_ref_log_details) + 12); + sprintf(msg, "fetch from %s", write_ref_log_details); + } else { + msg = NULL; } - if (loop()) { - if (lock) - unlock_ref(lock); - return -1; + for (i = 0; i < targets; i++) { + if (!write_ref || !write_ref[i]) + continue; + ret = write_ref_sha1(lock[i], &sha1[20 * i], msg ? msg : "fetch (unknown)"); + lock[i] = NULL; + if (ret) + goto unlock_and_fail; } + if (msg) + free(msg); - if (write_ref) { - if (write_ref_log_details) { - msg = xmalloc(strlen(write_ref_log_details) + 12); - sprintf(msg, "fetch from %s", write_ref_log_details); - } - else - msg = NULL; - ret = write_ref_sha1(lock, sha1, msg ? msg : "fetch (unknown)"); - if (msg) - free(msg); - return ret; - } return 0; + + +unlock_and_fail: + for (i = 0; i < targets; i++) + if (lock[i]) + unlock_ref(lock[i]); + return -1; } @@ -22,12 +22,6 @@ extern void prefetch(unsigned char *sha1); */ extern int fetch_ref(char *ref, unsigned char *sha1); -/* If set, the ref filename to write the target value to. */ -extern const char *write_ref; - -/* If set additional text will appear in the ref log. */ -extern const char *write_ref_log_details; - /* Set to fetch the target tree. */ extern int get_tree; @@ -46,6 +40,15 @@ extern int get_recover; /* Report what we got under get_verbosely */ extern void pull_say(const char *, const char *); -extern int pull(char *target); +/* Load pull targets from stdin */ +extern int pull_targets_stdin(char ***target, const char ***write_ref); + +/* Free up loaded targets */ +extern void pull_targets_free(int targets, char **target, const char **write_ref); + +/* If write_ref is set, the ref filename to write the target value to. */ +/* If write_ref_log_details is set, additional text will appear in the ref log. */ +extern int pull(int targets, char **target, const char **write_ref, + const char *write_ref_log_details); #endif /* PULL_H */ @@ -45,6 +45,12 @@ go_next () { this=$next } +cannot_fallback () { + echo "$1" + echo "Cannot fall back to three-way merge." + exit 1 +} + fall_back_3way () { O_OBJECT=`cd "$GIT_OBJECT_DIRECTORY" && pwd` @@ -52,19 +58,23 @@ fall_back_3way () { mkdir "$dotest/patch-merge-tmp-dir" # First see if the patch records the index info that we can use. - if git-apply -z --index-info "$dotest/patch" \ - >"$dotest/patch-merge-index-info" 2>/dev/null && - GIT_INDEX_FILE="$dotest/patch-merge-tmp-index" \ - git-update-index -z --index-info <"$dotest/patch-merge-index-info" && - GIT_INDEX_FILE="$dotest/patch-merge-tmp-index" \ - git-write-tree >"$dotest/patch-merge-base+" && - # index has the base tree now. - GIT_INDEX_FILE="$dotest/patch-merge-tmp-index" \ + git-apply -z --index-info "$dotest/patch" \ + >"$dotest/patch-merge-index-info" && + GIT_INDEX_FILE="$dotest/patch-merge-tmp-index" \ + git-update-index -z --index-info <"$dotest/patch-merge-index-info" && + GIT_INDEX_FILE="$dotest/patch-merge-tmp-index" \ + git-write-tree >"$dotest/patch-merge-base+" || + cannot_fallback "Patch does not record usable index information." + + echo Using index info to reconstruct a base tree... + if GIT_INDEX_FILE="$dotest/patch-merge-tmp-index" \ git-apply $binary --cached <"$dotest/patch" then - echo Using index info to reconstruct a base tree... mv "$dotest/patch-merge-base+" "$dotest/patch-merge-base" mv "$dotest/patch-merge-tmp-index" "$dotest/patch-merge-index" + else + cannot_fallback "Did you hand edit your patch? +It does not apply to blobs recorded in its index." fi test -f "$dotest/patch-merge-index" && diff --git a/git-annotate.perl b/git-annotate.perl index 6db2f48241..215ed26f3a 100755 --- a/git-annotate.perl +++ b/git-annotate.perl @@ -147,7 +147,7 @@ sub init_claim { sub handle_rev { - my $i = 0; + my $revseen = 0; my %seen; while (my $rev = shift @revqueue) { next if $seen{$rev}++; @@ -247,22 +247,129 @@ sub git_find_parent { return $parent; } +sub git_find_all_parents { + my ($rev) = @_; + + my $revparent = open_pipe("git-rev-list","--remove-empty", "--parents","--max-count=1","$rev") + or die "Failed to open git-rev-list to find a single parent: $!"; + + my $parentline = <$revparent>; + chomp $parentline; + my ($origrev, @parents) = split m/\s+/, $parentline; + + close($revparent); + + return @parents; +} + +sub git_merge_base { + my ($rev1, $rev2) = @_; + + my $mb = open_pipe("git-merge-base", $rev1, $rev2) + or die "Failed to open git-merge-base: $!"; + + my $base = <$mb>; + chomp $base; + + close($mb); + + return $base; +} + +# Construct a set of pseudo parents that are in the same order, +# and the same quantity as the real parents, +# but whose SHA1s are as similar to the logical parents +# as possible. +sub get_pseudo_parents { + my ($all, $fake) = @_; + + my @all = @$all; + my @fake = @$fake; + + my @pseudo; + + my %fake = map {$_ => 1} @fake; + my %seenfake; + + my $fakeidx = 0; + foreach my $p (@all) { + if (exists $fake{$p}) { + if ($fake[$fakeidx] ne $p) { + die sprintf("parent mismatch: %s != %s\nall:%s\nfake:%s\n", + $fake[$fakeidx], $p, + join(", ", @all), + join(", ", @fake), + ); + } + + push @pseudo, $p; + $fakeidx++; + $seenfake{$p}++; + + } else { + my $base = git_merge_base($fake[$fakeidx], $p); + if ($base ne $fake[$fakeidx]) { + die sprintf("Result of merge-base doesn't match fake: %s,%s != %s\n", + $fake[$fakeidx], $p, $base); + } + + # The details of how we parse the diffs + # mean that we cannot have a duplicate + # revision in the list, so if we've already + # seen the revision we would normally add, just use + # the actual revision. + if ($seenfake{$base}) { + push @pseudo, $p; + } else { + push @pseudo, $base; + $seenfake{$base}++; + } + } + } + + return @pseudo; +} + # Get a diff between the current revision and a parent. # Record the commit information that results. sub git_diff_parse { my ($parents, $rev, %revinfo) = @_; + my @pseudo_parents; + my @command = ("git-diff-tree"); + my $revision_spec; + + if (scalar @$parents == 1) { + + $revision_spec = join("..", $parents->[0], $rev); + @pseudo_parents = @$parents; + } else { + my @all_parents = git_find_all_parents($rev); + + if (@all_parents != @$parents) { + @pseudo_parents = get_pseudo_parents(\@all_parents, $parents); + } else { + @pseudo_parents = @$parents; + } + + $revision_spec = $rev; + push @command, "-c"; + } + my @filenames = ( $revs{$rev}{'filename'} ); + foreach my $parent (@$parents) { push @filenames, $revs{$parent}{'filename'}; } - my $diff = open_pipe("git-diff-tree","-M","-p","-c",$rev,"--", - @filenames ) + push @command, "-p", "-M", $revision_spec, "--", @filenames; + + + my $diff = open_pipe( @command ) or die "Failed to call git-diff for annotation: $!"; - _git_diff_parse($diff, $parents, $rev, %revinfo); + _git_diff_parse($diff, \@pseudo_parents, $rev, %revinfo); close($diff); } @@ -283,6 +390,7 @@ sub _git_diff_parse { $diff_header_regexp .= "@" x @$parents; $diff_header_regexp .= ' -\d+,\d+' x @$parents; $diff_header_regexp .= ' \+(\d+),\d+'; + $diff_header_regexp .= " " . ("@" x @$parents); my %claim_regexps; my $allparentplus = '^' . '\\+' x @$parents . '(.*)$'; @@ -311,13 +419,13 @@ sub _git_diff_parse { DIFF: while(<$diff>) { chomp; + #printf("%d:%s:\n", $gotheader, $_); if (m/$diff_header_regexp/) { $remstart = $1 - 1; # (0-based arrays) $gotheader = 1; - printf("Copying from %d to %d\n", $ri, $remstart); foreach my $parent (@$parents) { for (my $i = $ri; $i < $remstart; $i++) { $plines{$parent}[$pi{$parent}++] = $slines->[$i]; @@ -392,10 +500,17 @@ sub _git_diff_parse { printf("parent %s is on line %d\n", $parent, $pi{$parent}); } + my @context; + for (my $i = -2; $i < 2; $i++) { + push @context, get_line($slines, $ri + $i); + } + my $context = join("\n", @context); + + my $justline = substr($_, scalar @$parents); die sprintf("Line %d, does not match:\n|%s|\n|%s|\n%s\n", $ri, - substr($_,scalar @$parents), - get_line($slines,$ri), $rev); + $justline, + $context); } foreach my $parent (@$parents) { $plines{$parent}[$pi{$parent}++] = $slines->[$ri]; diff --git a/git-checkout.sh b/git-checkout.sh index 5613bfc403..580a9e8a23 100755 --- a/git-checkout.sh +++ b/git-checkout.sh @@ -67,6 +67,10 @@ while [ "$#" != "0" ]; do set x "$arg" "$@" shift fi + case "$1" in + --) + shift ;; + esac break ;; esac diff --git a/git-clone.sh b/git-clone.sh index a92b22a13d..7060bdab01 100755 --- a/git-clone.sh +++ b/git-clone.sh @@ -205,7 +205,7 @@ dir="$2" [ -e "$dir" ] && echo "$dir already exists." && usage mkdir -p "$dir" && D=$(cd "$dir" && pwd) && -trap 'err=$?; cd ..; rm -r "$D"; exit $err' 0 +trap 'err=$?; cd ..; rm -rf "$D"; exit $err' 0 case "$bare" in yes) GIT_DIR="$D" ;; @@ -298,7 +298,7 @@ yes,yes) fi git-ls-remote "$repo" >"$GIT_DIR/CLONE_HEAD" || exit 1 ;; - http://*) + https://*|http://*) if test -z "@@NO_CURL@@" then clone_dumb_http "$repo" "$D" @@ -324,7 +324,8 @@ test -d "$GIT_DIR/refs/reference-tmp" && rm -fr "$GIT_DIR/refs/reference-tmp" if test -f "$GIT_DIR/CLONE_HEAD" then # Read git-fetch-pack -k output and store the remote branches. - @@PERL@@ -e "$copy_refs" "$GIT_DIR" "$use_separate_remote" "$origin" + @@PERL@@ -e "$copy_refs" "$GIT_DIR" "$use_separate_remote" "$origin" || + exit fi cd "$D" || exit diff --git a/git-compat-util.h b/git-compat-util.h index 93f558056d..b2e18954c0 100644 --- a/git-compat-util.h +++ b/git-compat-util.h @@ -91,6 +91,9 @@ static inline void *xmalloc(size_t size) ret = malloc(1); if (!ret) die("Out of memory, malloc failed"); +#ifdef XMALLOC_POISON + memset(ret, 0xA5, size); +#endif return ret; } @@ -136,6 +139,13 @@ static inline ssize_t xwrite(int fd, const void *buf, size_t len) } } +static inline int has_extension(const char *filename, const char *ext) +{ + size_t len = strlen(filename); + size_t extlen = strlen(ext); + return len > extlen && !memcmp(filename + len - extlen, ext, extlen); +} + /* Sane ctype - no locale, and works with signed chars */ #undef isspace #undef isdigit diff --git a/git-cvsserver.perl b/git-cvsserver.perl index 0bbd871c14..2130d57020 100755 --- a/git-cvsserver.perl +++ b/git-cvsserver.perl @@ -547,12 +547,15 @@ sub req_Argument { my ( $cmd, $data ) = @_; - # TODO : Not quite sure how Argument and Argumentx differ, but I assume - # it's for multi-line arguments ... somehow ... + # Argumentx means: append to last Argument (with a newline in front) $log->debug("$cmd : $data"); - push @{$state->{arguments}}, $data; + if ( $cmd eq 'Argumentx') { + ${$state->{arguments}}[$#{$state->{arguments}}] .= "\n" . $data; + } else { + push @{$state->{arguments}}, $data; + } } # expand-modules \n @@ -1139,9 +1142,7 @@ sub req_ci exit; } - open FILE, ">", "$ENV{GIT_DIR}refs/heads/$state->{module}"; - print FILE $commithash; - close FILE; + print LOCKFILE $commithash; $updater->update(); @@ -1168,7 +1169,9 @@ sub req_ci } close LOCKFILE; - unlink($lockfile); + my $reffile = "$ENV{GIT_DIR}refs/heads/$state->{module}"; + unlink($reffile); + rename($lockfile, $reffile); chdir "/"; print "ok\n"; diff --git a/git-instaweb.sh b/git-instaweb.sh index 63b18b99f6..16cd351f7f 100755 --- a/git-instaweb.sh +++ b/git-instaweb.sh @@ -54,6 +54,10 @@ start_httpd () { fi done fi + if test $? != 0; then + echo "Could not execute http daemon $httpd." + exit 1 + fi } stop_httpd () { @@ -183,8 +187,10 @@ PerlPassEnv GIT_EXEC_DIR EOF else # plain-old CGI + list_mods=`echo "$httpd" | sed "s/-f$/-l/"` + $list_mods | grep 'mod_cgi\.c' >/dev/null 2>&1 || \ + echo "LoadModule cgi_module $module_path/mod_cgi.so" >> "$conf" cat >> "$conf" <<EOF -LoadModule cgi_module $module_path/mod_cgi.so AddHandler cgi-script .cgi <Location /gitweb.cgi> Options +ExecCGI @@ -232,4 +238,5 @@ esac start_httpd test -z "$browser" && browser=echo -$browser http://127.0.0.1:$port +url=http://127.0.0.1:$port +$browser $url || echo $url diff --git a/git-lost-found.sh b/git-lost-found.sh index ba6d587f31..b928f2ca52 100755 --- a/git-lost-found.sh +++ b/git-lost-found.sh @@ -12,7 +12,7 @@ fi laf="$GIT_DIR/lost-found" rm -fr "$laf" && mkdir -p "$laf/commit" "$laf/other" || exit -git fsck-objects | +git fsck-objects --full | while read dangling type sha1 do case "$dangling" in diff --git a/git-mv.perl b/git-mv.perl deleted file mode 100755 index 75aa8feeb6..0000000000 --- a/git-mv.perl +++ /dev/null @@ -1,250 +0,0 @@ -#!/usr/bin/perl -# -# Copyright 2005, Ryan Anderson <ryan@michonline.com> -# Josef Weidendorfer <Josef.Weidendorfer@gmx.de> -# -# This file is licensed under the GPL v2, or a later version -# at the discretion of Linus Torvalds. - - -use warnings; -use strict; -use Getopt::Std; - -sub usage() { - print <<EOT; -$0 [-f] [-n] <source> <destination> -$0 [-f] [-n] [-k] <source> ... <destination directory> -EOT - exit(1); -} - -our ($opt_n, $opt_f, $opt_h, $opt_k, $opt_v); -getopts("hnfkv") || usage; -usage() if $opt_h; -@ARGV >= 1 or usage; - -my $GIT_DIR = `git rev-parse --git-dir`; -exit 1 if $?; # rev-parse would have given "not a git dir" message. -chomp($GIT_DIR); - -my (@srcArgs, @dstArgs, @srcs, @dsts); -my ($src, $dst, $base, $dstDir); - -# remove any trailing slash in arguments -for (@ARGV) { s/\/*$//; } - -my $argCount = scalar @ARGV; -if (-d $ARGV[$argCount-1]) { - $dstDir = $ARGV[$argCount-1]; - @srcArgs = @ARGV[0..$argCount-2]; - - foreach $src (@srcArgs) { - $base = $src; - $base =~ s/^.*\///; - $dst = "$dstDir/". $base; - push @dstArgs, $dst; - } -} -else { - if ($argCount < 2) { - print "Error: need at least two arguments\n"; - exit(1); - } - if ($argCount > 2) { - print "Error: moving to directory '" - . $ARGV[$argCount-1] - . "' not possible; not existing\n"; - exit(1); - } - @srcArgs = ($ARGV[0]); - @dstArgs = ($ARGV[1]); - $dstDir = ""; -} - -my $subdir_prefix = `git rev-parse --show-prefix`; -chomp($subdir_prefix); - -# run in git base directory, so that git-ls-files lists all revisioned files -chdir "$GIT_DIR/.."; - -# normalize paths, needed to compare against versioned files and update-index -# also, this is nicer to end-users by doing ".//a/./b/.//./c" ==> "a/b/c" -for (@srcArgs, @dstArgs) { - # prepend git prefix as we run from base directory - $_ = $subdir_prefix.$_; - s|^\./||; - s|/\./|/| while (m|/\./|); - s|//+|/|g; - # Also "a/b/../c" ==> "a/c" - 1 while (s,(^|/)[^/]+/\.\./,$1,); -} - -my (@allfiles,@srcfiles,@dstfiles); -my $safesrc; -my (%overwritten, %srcForDst); - -$/ = "\0"; -open(F, 'git-ls-files -z |') - or die "Failed to open pipe from git-ls-files: " . $!; - -@allfiles = map { chomp; $_; } <F>; -close(F); - - -my ($i, $bad); -while(scalar @srcArgs > 0) { - $src = shift @srcArgs; - $dst = shift @dstArgs; - $bad = ""; - - for ($src, $dst) { - # Be nicer to end-users by doing ".//a/./b/.//./c" ==> "a/b/c" - s|^\./||; - s|/\./|/| while (m|/\./|); - s|//+|/|g; - # Also "a/b/../c" ==> "a/c" - 1 while (s,(^|/)[^/]+/\.\./,$1,); - } - - if ($opt_v) { - print "Checking rename of '$src' to '$dst'\n"; - } - - unless (-f $src || -l $src || -d $src) { - $bad = "bad source '$src'"; - } - - $safesrc = quotemeta($src); - @srcfiles = grep /^$safesrc(\/|$)/, @allfiles; - - $overwritten{$dst} = 0; - if (($bad eq "") && -e $dst) { - $bad = "destination '$dst' already exists"; - if ($opt_f) { - # only files can overwrite each other: check both source and destination - if (-f $dst && (scalar @srcfiles == 1)) { - print "Warning: $bad; will overwrite!\n"; - $bad = ""; - $overwritten{$dst} = 1; - } - else { - $bad = "Can not overwrite '$src' with '$dst'"; - } - } - } - - if (($bad eq "") && ($dst =~ /^$safesrc\//)) { - $bad = "can not move directory '$src' into itself"; - } - - if ($bad eq "") { - if (scalar @srcfiles == 0) { - $bad = "'$src' not under version control"; - } - } - - if ($bad eq "") { - if (defined $srcForDst{$dst}) { - $bad = "can not move '$src' to '$dst'; already target of "; - $bad .= "'".$srcForDst{$dst}."'"; - } - else { - $srcForDst{$dst} = $src; - } - } - - if ($bad ne "") { - if ($opt_k) { - print "Warning: $bad; skipping\n"; - next; - } - print "Error: $bad\n"; - exit(1); - } - push @srcs, $src; - push @dsts, $dst; -} - -# Final pass: rename/move -my (@deletedfiles,@addedfiles,@changedfiles); -$bad = ""; -while(scalar @srcs > 0) { - $src = shift @srcs; - $dst = shift @dsts; - - if ($opt_n || $opt_v) { print "Renaming $src to $dst\n"; } - if (!$opt_n) { - if (!rename($src,$dst)) { - $bad = "renaming '$src' failed: $!"; - if ($opt_k) { - print "Warning: skipped: $bad\n"; - $bad = ""; - next; - } - last; - } - } - - $safesrc = quotemeta($src); - @srcfiles = grep /^$safesrc(\/|$)/, @allfiles; - @dstfiles = @srcfiles; - s/^$safesrc(\/|$)/$dst$1/ for @dstfiles; - - push @deletedfiles, @srcfiles; - if (scalar @srcfiles == 1) { - # $dst can be a directory with 1 file inside - if ($overwritten{$dst} ==1) { - push @changedfiles, $dstfiles[0]; - - } else { - push @addedfiles, $dstfiles[0]; - } - } - else { - push @addedfiles, @dstfiles; - } -} - -if ($opt_n) { - if (@changedfiles) { - print "Changed : ". join(", ", @changedfiles) ."\n"; - } - if (@addedfiles) { - print "Adding : ". join(", ", @addedfiles) ."\n"; - } - if (@deletedfiles) { - print "Deleting : ". join(", ", @deletedfiles) ."\n"; - } -} -else { - if (@changedfiles) { - open(H, "| git-update-index -z --stdin") - or die "git-update-index failed to update changed files with code $!\n"; - foreach my $fileName (@changedfiles) { - print H "$fileName\0"; - } - close(H); - } - if (@addedfiles) { - open(H, "| git-update-index --add -z --stdin") - or die "git-update-index failed to add new names with code $!\n"; - foreach my $fileName (@addedfiles) { - print H "$fileName\0"; - } - close(H); - } - if (@deletedfiles) { - open(H, "| git-update-index --remove -z --stdin") - or die "git-update-index failed to remove old names with code $!\n"; - foreach my $fileName (@deletedfiles) { - print H "$fileName\0"; - } - close(H); - } -} - -if ($bad ne "") { - print "Error: $bad\n"; - exit(1); -} diff --git a/git-push.sh b/git-push.sh deleted file mode 100755 index 21775fc21a..0000000000 --- a/git-push.sh +++ /dev/null @@ -1,87 +0,0 @@ -#!/bin/sh - -USAGE='[--all] [--tags] [--force] <repository> [<refspec>...]' -. git-sh-setup - -# Parse out parameters and then stop at remote, so that we can -# translate it using .git/branches information -has_all= -has_force= -has_exec= -has_thin=--thin -remote= -do_tags= - -while case "$#" in 0) break ;; esac -do - case "$1" in - --all) - has_all=--all ;; - --tags) - do_tags=yes ;; - --force) - has_force=--force ;; - --exec=*) - has_exec="$1" ;; - --thin) - ;; # noop - --no-thin) - has_thin= ;; - -*) - usage ;; - *) - set x "$@" - shift - break ;; - esac - shift -done -case "$#" in -0) - echo "Where would you want to push today?" - usage ;; -esac - -. git-parse-remote -remote=$(get_remote_url "$@") - -case "$has_all" in ---all) - set x ;; -'') - case "$do_tags,$#" in - yes,1) - set x $(cd "$GIT_DIR/refs" && find tags -type f -print) ;; - yes,*) - set x $(cd "$GIT_DIR/refs" && find tags -type f -print) \ - $(get_remote_refs_for_push "$@") ;; - ,*) - set x $(get_remote_refs_for_push "$@") ;; - esac -esac - -shift ;# away the initial 'x' - -# $# is now 0 if there was no explicit refspec on the command line -# and there was no default refspec to push from remotes/ file. -# we will let git-send-pack to do its "matching refs" thing. - -case "$remote" in -git://*) - die "Cannot use READ-ONLY transport to push to $remote" ;; -rsync://*) - die "Pushing with rsync transport is deprecated" ;; -esac - -set x "$remote" "$@"; shift -test "$has_all" && set x "$has_all" "$@" && shift -test "$has_force" && set x "$has_force" "$@" && shift -test "$has_exec" && set x "$has_exec" "$@" && shift -test "$has_thin" && set x "$has_thin" "$@" && shift - -case "$remote" in -http://* | https://*) - exec git-http-push "$@";; -*) - exec git-send-pack "$@";; -esac diff --git a/git-rebase.sh b/git-rebase.sh index 29028dd5fc..7d3a5d0e71 100755 --- a/git-rebase.sh +++ b/git-rebase.sh @@ -266,14 +266,11 @@ onto=$(git-rev-parse --verify "${onto_name}^0") || exit # Check if we are already based on $onto, but this should be # done only when upstream and onto are the same. -if test "$upstream" = "$onto" +mb=$(git-merge-base "$onto" "$branch") +if test "$upstream" = "$onto" && test "$mb" = "$onto" then - mb=$(git-merge-base "$onto" "$branch") - if test "$mb" = "$onto" - then - echo >&2 "Current branch $branch_name is up to date." - exit 0 - fi + echo >&2 "Current branch $branch_name is up to date." + exit 0 fi # Rewind the head to "$onto"; this saves our current head in ORIG_HEAD. @@ -281,9 +278,9 @@ git-reset --hard "$onto" # If the $onto is a proper descendant of the tip of the branch, then # we just fast forwarded. -if test "$mb" = "$onto" +if test "$mb" = "$branch" then - echo >&2 "Fast-forwarded $branch to $newbase." + echo >&2 "Fast-forwarded $branch_name to $onto_name." exit 0 fi diff --git a/git-reset.sh b/git-reset.sh index 5c0224090a..36fc8ce25b 100755 --- a/git-reset.sh +++ b/git-reset.sh @@ -52,7 +52,8 @@ then else rm -f "$GIT_DIR/ORIG_HEAD" fi -git-update-ref -m "reset $reset_type $@" HEAD "$rev" +git-update-ref -m "reset $reset_type $*" HEAD "$rev" +update_ref_status=$? case "$reset_type" in --hard ) @@ -66,3 +67,5 @@ case "$reset_type" in esac rm -f "$GIT_DIR/MERGE_HEAD" "$GIT_DIR/rr-cache/MERGE_RR" "$GIT_DIR/SQUASH_MSG" + +exit $update_ref_status diff --git a/git-sh-setup.sh b/git-sh-setup.sh index d15747f1ed..42f9b1c125 100755 --- a/git-sh-setup.sh +++ b/git-sh-setup.sh @@ -35,17 +35,12 @@ case "$1" in exit esac +# Make sure we are in a valid repository of a vintage we understand. if [ -z "$SUBDIRECTORY_OK" ] then : ${GIT_DIR=.git} - : ${GIT_OBJECT_DIRECTORY="$GIT_DIR/objects"} - - # Make sure we are in a valid repository of a vintage we understand. - GIT_DIR="$GIT_DIR" git repo-config --get core.nosuch >/dev/null - if test $? = 128 - then - exit - fi + GIT_DIR=$(GIT_DIR="$GIT_DIR" git-rev-parse --git-dir) || exit else GIT_DIR=$(git-rev-parse --git-dir) || exit fi +: ${GIT_OBJECT_DIRECTORY="$GIT_DIR/objects"} diff --git a/git-svn.perl b/git-svn.perl index 6453771f9c..0d58bb9b37 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -31,6 +31,7 @@ use File::Basename qw/dirname basename/; use File::Path qw/mkpath/; use Getopt::Long qw/:config gnu_getopt no_ignore_case auto_abbrev pass_through/; use File::Spec qw//; +use File::Copy qw/copy/; use POSIX qw/strftime/; use IPC::Open3; use Memoize; @@ -77,9 +78,6 @@ my %cmt_opts = ( 'edit|e' => \$_edit, 'copy-similarity|C=i'=> \$_cp_similarity ); -# yes, 'native' sets "\n". Patches to fix this for non-*nix systems welcome: -my %EOL = ( CR => "\015", LF => "\012", CRLF => "\015\012", native => "\012" ); - my %cmd = ( fetch => [ \&fetch, "Download new revisions from SVN", { 'revision|r=s' => \$_revision, %fc_opts } ], @@ -1160,27 +1158,24 @@ sub repo_path_split { } } - my ($url, $path) = ($full_url =~ m!^([a-z\+]+://[^/]*)(.*)$!i); - $path =~ s#^/+##; - my @paths = split(m#/+#, $path); - if ($_use_lib) { - while (1) { - $SVN = libsvn_connect($url); - last if (defined $SVN && - defined eval { $SVN->get_latest_revnum }); - my $n = shift @paths || last; - $url .= "/$n"; - } + my $tmp = libsvn_connect($full_url); + my $url = $tmp->get_repos_root; + $full_url =~ s#^\Q$url\E/*##; + push @repo_path_split_cache, qr/^(\Q$url\E)/; + return ($url, $full_url); } else { + my ($url, $path) = ($full_url =~ m!^([a-z\+]+://[^/]*)(.*)$!i); + $path =~ s#^/+##; + my @paths = split(m#/+#, $path); while (quiet_run(qw/svn ls --non-interactive/, $url)) { my $n = shift @paths || last; $url .= "/$n"; } + push @repo_path_split_cache, qr/^(\Q$url\E)/; + $path = join('/',@paths); + return ($url, $path); } - push @repo_path_split_cache, qr/^(\Q$url\E)/; - $path = join('/',@paths); - return ($url, $path); } sub setup_git_svn { @@ -1760,43 +1755,6 @@ sub svn_info { sub sys { system(@_) == 0 or croak $? } -sub eol_cp { - my ($from, $to) = @_; - my $es = svn_propget_base('svn:eol-style', $to); - open my $rfd, '<', $from or croak $!; - binmode $rfd or croak $!; - open my $wfd, '>', $to or croak $!; - binmode $wfd or croak $!; - eol_cp_fd($rfd, $wfd, $es); - close $rfd or croak $!; - close $wfd or croak $!; -} - -sub eol_cp_fd { - my ($rfd, $wfd, $es) = @_; - my $eol = defined $es ? $EOL{$es} : undef; - my $buf; - use bytes; - while (1) { - my ($r, $w, $t); - defined($r = sysread($rfd, $buf, 4096)) or croak $!; - return unless $r; - if ($eol) { - if ($buf =~ /\015$/) { - my $c; - defined($r = sysread($rfd,$c,1)) or croak $!; - $buf .= $c if $r > 0; - } - $buf =~ s/(?:\015\012|\015|\012)/$eol/gs; - $r = length($buf); - } - for ($w = 0; $w < $r; $w += $t) { - $t = syswrite($wfd, $buf, $r - $w, $w) or croak $!; - } - } - no bytes; -} - sub do_update_index { my ($z_cmd, $cmd, $no_text_base) = @_; @@ -1824,9 +1782,11 @@ sub do_update_index { 'text-base',"$f.svn-base"); $tb =~ s#^/##; } + my @s = stat($x); unlink $x or croak $!; - eol_cp($tb, $x); + copy($tb, $x); chmod(($mode &~ umask), $x) or croak $!; + utime $s[8], $s[9], $x; } print $ui $x,"\0"; } @@ -2617,7 +2577,9 @@ sub libsvn_connect { sub libsvn_get_file { my ($gui, $f, $rev) = @_; my $p = $f; - return unless ($p =~ s#^\Q$SVN_PATH\E/##); + if (length $SVN_PATH > 0) { + return unless ($p =~ s#^\Q$SVN_PATH\E/##); + } my ($hash, $pid, $in, $out); my $pool = SVN::Pool->new; @@ -2664,6 +2626,7 @@ sub libsvn_log_entry { if (defined $_authors && ! defined $users{$author}) { die "Author: $author not defined in $_authors file\n"; } + $msg = '' if ($rev == 0 && !defined $msg); return { revision => $rev, date => "+0000 $Y-$m-$d $H:$M:$S", author => $author, msg => $msg."\n", parents => $parents || [] } } @@ -15,6 +15,9 @@ #include "builtin.h" +const char git_usage_string[] = + "git [--version] [--exec-path[=GIT_EXEC_PATH]] [--help] COMMAND [ ARGS ]"; + static void prepend_to_path(const char *dir, int len) { const char *old_path = getenv("PATH"); @@ -35,6 +38,59 @@ static void prepend_to_path(const char *dir, int len) setenv("PATH", path, 1); } +static int handle_options(const char*** argv, int* argc) +{ + int handled = 0; + + while (*argc > 0) { + const char *cmd = (*argv)[0]; + if (cmd[0] != '-') + break; + + /* + * For legacy reasons, the "version" and "help" + * commands can be written with "--" prepended + * to make them look like flags. + */ + if (!strcmp(cmd, "--help") || !strcmp(cmd, "--version")) + break; + + /* + * Check remaining flags. + */ + if (!strncmp(cmd, "--exec-path", 11)) { + cmd += 11; + if (*cmd == '=') + git_set_exec_path(cmd + 1); + else { + puts(git_exec_path()); + exit(0); + } + } else if (!strcmp(cmd, "-p") || !strcmp(cmd, "--paginate")) { + setup_pager(); + } else if (!strcmp(cmd, "--git-dir")) { + if (*argc < 1) + return -1; + setenv("GIT_DIR", (*argv)[1], 1); + (*argv)++; + (*argc)--; + } else if (!strncmp(cmd, "--git-dir=", 10)) { + setenv("GIT_DIR", cmd + 10, 1); + } else if (!strcmp(cmd, "--bare")) { + static char git_dir[1024]; + setenv("GIT_DIR", getcwd(git_dir, 1024), 1); + } else { + fprintf(stderr, "Unknown option: %s\n", cmd); + usage(git_usage_string); + } + + (*argv)++; + (*argc)--; + handled++; + } + return handled; +} + static const char *alias_command; static char *alias_string = NULL; @@ -103,48 +159,48 @@ static int handle_alias(int *argcp, const char ***argv) { int nongit = 0, ret = 0, saved_errno = errno; const char *subdir; + int count, option_count; + const char** new_argv; subdir = setup_git_directory_gently(&nongit); - if (!nongit) { - int count; - const char** new_argv; - alias_command = (*argv)[0]; - git_config(git_alias_config); - if (alias_string) { + alias_command = (*argv)[0]; + git_config(git_alias_config); + if (alias_string) { + count = split_cmdline(alias_string, &new_argv); + option_count = handle_options(&new_argv, &count); + memmove(new_argv - option_count, new_argv, + count * sizeof(char *)); + new_argv -= option_count; - count = split_cmdline(alias_string, &new_argv); + if (count < 1) + die("empty alias for %s", alias_command); - if (count < 1) - die("empty alias for %s", alias_command); + if (!strcmp(alias_command, new_argv[0])) + die("recursive alias: %s", alias_command); - 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); + 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); + } - new_argv = realloc(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; + new_argv = realloc(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; - *argv = new_argv; - *argcp += count - 1; + *argv = new_argv; + *argcp += count - 1; - ret = 1; - } + ret = 1; } if (subdir) @@ -157,51 +213,64 @@ static int handle_alias(int *argcp, const char ***argv) const char git_version_string[] = GIT_VERSION; +#define RUN_SETUP (1<<0) +#define USE_PAGER (1<<1) + static void handle_internal_command(int argc, const char **argv, char **envp) { const char *cmd = argv[0]; static struct cmd_struct { const char *cmd; - int (*fn)(int, const char **, char **); + int (*fn)(int, const char **, const char *); + int option; } commands[] = { - { "version", cmd_version }, - { "help", cmd_help }, - { "log", cmd_log }, - { "whatchanged", cmd_whatchanged }, - { "show", cmd_show }, - { "push", cmd_push }, - { "format-patch", cmd_format_patch }, + { "add", cmd_add, RUN_SETUP }, + { "apply", cmd_apply }, + { "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 }, - { "diff", cmd_diff }, - { "grep", cmd_grep }, - { "rm", cmd_rm }, - { "add", cmd_add }, - { "rev-list", cmd_rev_list }, - { "init-db", cmd_init_db }, + { "diff", cmd_diff, RUN_SETUP }, + { "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 }, - { "upload-tar", cmd_upload_tar }, - { "check-ref-format", cmd_check_ref_format }, - { "ls-files", cmd_ls_files }, - { "ls-tree", cmd_ls_tree }, - { "tar-tree", cmd_tar_tree }, - { "read-tree", cmd_read_tree }, - { "commit-tree", cmd_commit_tree }, - { "apply", cmd_apply }, - { "show-branch", cmd_show_branch }, - { "diff-files", cmd_diff_files }, - { "diff-index", cmd_diff_index }, - { "diff-stages", cmd_diff_stages }, - { "diff-tree", cmd_diff_tree }, - { "cat-file", cmd_cat_file }, - { "rev-parse", cmd_rev_parse }, - { "write-tree", cmd_write_tree }, - { "mailsplit", cmd_mailsplit }, + { "grep", cmd_grep, RUN_SETUP }, + { "help", cmd_help }, + { "init-db", cmd_init_db }, + { "log", cmd_log, RUN_SETUP | USE_PAGER }, + { "ls-files", cmd_ls_files, RUN_SETUP }, + { "ls-tree", cmd_ls_tree, RUN_SETUP }, { "mailinfo", cmd_mailinfo }, + { "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 }, + { "show-branch", cmd_show_branch, RUN_SETUP }, + { "show", cmd_show, RUN_SETUP | USE_PAGER }, { "stripspace", cmd_stripspace }, - { "update-index", cmd_update_index }, - { "update-ref", cmd_update_ref }, - { "fmt-merge-msg", cmd_fmt_merge_msg }, - { "prune", cmd_prune }, + { "symbolic-ref", cmd_symbolic_ref, RUN_SETUP }, + { "tar-tree", cmd_tar_tree, RUN_SETUP }, + { "unpack-objects", cmd_unpack_objects, RUN_SETUP }, + { "update-index", cmd_update_index, RUN_SETUP }, + { "update-ref", cmd_update_ref, RUN_SETUP }, + { "upload-tar", cmd_upload_tar }, + { "version", cmd_version }, + { "whatchanged", cmd_whatchanged, RUN_SETUP | USE_PAGER }, + { "write-tree", cmd_write_tree, RUN_SETUP }, + { "verify-pack", cmd_verify_pack }, }; int i; @@ -213,9 +282,15 @@ static void handle_internal_command(int argc, const char **argv, char **envp) for (i = 0; i < ARRAY_SIZE(commands); i++) { struct cmd_struct *p = commands+i; + const char *prefix; if (strcmp(p->cmd, cmd)) continue; + prefix = NULL; + 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"); @@ -227,7 +302,7 @@ static void handle_internal_command(int argc, const char **argv, char **envp) fflush(stderr); } - exit(p->fn(argc, argv, envp)); + exit(p->fn(argc, argv, prefix)); } } @@ -268,51 +343,19 @@ int main(int argc, const char **argv, char **envp) die("cannot handle %s internally", cmd); } - /* Default command: "help" */ - cmd = "help"; - /* Look for flags.. */ - while (argc > 1) { - cmd = *++argv; - argc--; - - if (!strcmp(cmd, "-p") || !strcmp(cmd, "--paginate")) { - setup_pager(); - continue; - } - - if (strncmp(cmd, "--", 2)) - break; - - cmd += 2; - - /* - * For legacy reasons, the "version" and "help" - * commands can be written with "--" prepended - * to make them look like flags. - */ - if (!strcmp(cmd, "help")) - break; - if (!strcmp(cmd, "version")) - break; - - /* - * Check remaining flags (which by now must be - * "--exec-path", but maybe we will accept - * other arguments some day) - */ - if (!strncmp(cmd, "exec-path", 9)) { - cmd += 9; - if (*cmd == '=') { - git_set_exec_path(cmd + 1); - continue; - } - puts(git_exec_path()); - exit(0); - } - cmd_usage(0, NULL, NULL); + argv++; + argc--; + handle_options(&argv, &argc); + if (argc > 0) { + if (!strncmp(argv[0], "--", 2)) + argv[0] += 2; + } else { + /* Default command: "help" */ + argv[0] = "help"; + argc = 1; } - argv[0] = cmd; + cmd = argv[0]; /* * We search for git commands in the following order: @@ -343,7 +386,7 @@ int main(int argc, const char **argv, char **envp) } if (errno == ENOENT) - cmd_usage(0, exec_path, "'%s' is not a git-command", cmd); + help_unknown_cmd(cmd); fprintf(stderr, "Failed to run command '%s': %s\n", cmd, strerror(errno)); @@ -312,7 +312,7 @@ proc getcommit {id} { proc readrefs {} { global tagids idtags headids idheads tagcontents - global otherrefids idotherrefs + global otherrefids idotherrefs mainhead foreach v {tagids idtags headids idheads otherrefids idotherrefs} { catch {unset $v} @@ -358,6 +358,13 @@ proc readrefs {} { } } close $refd + set mainhead {} + catch { + set thehead [exec git symbolic-ref HEAD] + if {[string match "refs/heads/*" $thehead]} { + set mainhead [string range $thehead 11 end] + } + } } proc show_error {w top msg} { @@ -386,6 +393,7 @@ proc makewindow {} { global rowctxmenu mergemax wrapcomment global highlight_files gdttype global searchstring sstring + global bgcolor fgcolor bglist fglist diffcolors menu .bar .bar add cascade -label "File" -menu .bar.file @@ -446,18 +454,19 @@ proc makewindow {} { .ctop add .ctop.top set canv .ctop.top.clist.canv canvas $canv -height $geometry(canvh) -width $geometry(canv1) \ - -bg white -bd 0 \ + -background $bgcolor -bd 0 \ -yscrollincr $linespc -yscrollcommand "scrollcanv $cscroll" .ctop.top.clist add $canv set canv2 .ctop.top.clist.canv2 canvas $canv2 -height $geometry(canvh) -width $geometry(canv2) \ - -bg white -bd 0 -yscrollincr $linespc + -background $bgcolor -bd 0 -yscrollincr $linespc .ctop.top.clist add $canv2 set canv3 .ctop.top.clist.canv3 canvas $canv3 -height $geometry(canvh) -width $geometry(canv3) \ - -bg white -bd 0 -yscrollincr $linespc + -background $bgcolor -bd 0 -yscrollincr $linespc .ctop.top.clist add $canv3 bind .ctop.top.clist <Configure> {resizeclistpanes %W %w} + lappend bglist $canv $canv2 $canv3 set sha1entry .ctop.top.bar.sha1 set entries $sha1entry @@ -563,19 +572,22 @@ proc makewindow {} { trace add variable searchstring write incrsearch pack $sstring -side left -expand 1 -fill x set ctext .ctop.cdet.left.ctext - text $ctext -bg white -state disabled -font $textfont \ + text $ctext -background $bgcolor -foreground $fgcolor \ + -state disabled -font $textfont \ -width $geometry(ctextw) -height $geometry(ctexth) \ -yscrollcommand scrolltext -wrap none scrollbar .ctop.cdet.left.sb -command "$ctext yview" pack .ctop.cdet.left.sb -side right -fill y pack $ctext -side left -fill both -expand 1 .ctop.cdet add .ctop.cdet.left + lappend bglist $ctext + lappend fglist $ctext $ctext tag conf comment -wrap $wrapcomment $ctext tag conf filesep -font [concat $textfont bold] -back "#aaaaaa" - $ctext tag conf hunksep -fore blue - $ctext tag conf d0 -fore red - $ctext tag conf d1 -fore "#00a000" + $ctext tag conf hunksep -fore [lindex $diffcolors 2] + $ctext tag conf d0 -fore [lindex $diffcolors 0] + $ctext tag conf d1 -fore [lindex $diffcolors 1] $ctext tag conf m0 -fore red $ctext tag conf m1 -fore blue $ctext tag conf m2 -fore green @@ -608,11 +620,15 @@ proc makewindow {} { pack .ctop.cdet.right.mode -side top -fill x set cflist .ctop.cdet.right.cfiles set indent [font measure $mainfont "nn"] - text $cflist -width $geometry(cflistw) -background white -font $mainfont \ + text $cflist -width $geometry(cflistw) \ + -background $bgcolor -foreground $fgcolor \ + -font $mainfont \ -tabs [list $indent [expr {2 * $indent}]] \ -yscrollcommand ".ctop.cdet.right.sb set" \ -cursor [. cget -cursor] \ -spacing1 1 -spacing3 1 + lappend bglist $cflist + lappend fglist $cflist scrollbar .ctop.cdet.right.sb -command "$cflist yview" pack .ctop.cdet.right.sb -side right -fill y pack $cflist -side left -fill both -expand 1 @@ -747,6 +763,7 @@ proc savestuff {w} { global maxwidth showneartags global viewname viewfiles viewargs viewperm nextviewnum global cmitmode wrapcomment + global colors bgcolor fgcolor diffcolors if {$stuffsaved} return if {![winfo viewable .]} return @@ -761,6 +778,10 @@ proc savestuff {w} { puts $f [list set cmitmode $cmitmode] puts $f [list set wrapcomment $wrapcomment] puts $f [list set showneartags $showneartags] + puts $f [list set bgcolor $bgcolor] + puts $f [list set fgcolor $fgcolor] + puts $f [list set colors $colors] + puts $f [list set diffcolors $diffcolors] puts $f "set geometry(width) [winfo width .ctop]" puts $f "set geometry(height) [winfo height .ctop]" puts $f "set geometry(canv1) [expr {[winfo width $canv]-2}]" @@ -2870,11 +2891,11 @@ proc drawlines {id} { } proc drawcmittext {id row col rmx} { - global linespc canv canv2 canv3 canvy0 + global linespc canv canv2 canv3 canvy0 fgcolor global commitlisted commitinfo rowidlist global rowtextx idpos idtags idheads idotherrefs global linehtag linentag linedtag - global mainfont canvxmax boldrows boldnamerows + global mainfont canvxmax boldrows boldnamerows fgcolor set ofill [expr {[lindex $commitlisted $row]? "blue": "white"}] set x [xc $row $col] @@ -2882,7 +2903,7 @@ proc drawcmittext {id row col rmx} { set orad [expr {$linespc / 3}] set t [$canv create oval [expr {$x - $orad}] [expr {$y - $orad}] \ [expr {$x + $orad - 1}] [expr {$y + $orad - 1}] \ - -fill $ofill -outline black -width 1] + -fill $ofill -outline $fgcolor -width 1 -tags circle] $canv raise $t $canv bind $t <1> {selcanvline {} %x %y} set xt [xc $row [llength [lindex $rowidlist $row]]] @@ -2910,13 +2931,13 @@ proc drawcmittext {id row col rmx} { lappend nfont bold } } - set linehtag($row) [$canv create text $xt $y -anchor w \ - -text $headline -font $font] + set linehtag($row) [$canv create text $xt $y -anchor w -fill $fgcolor \ + -text $headline -font $font -tags text] $canv bind $linehtag($row) <Button-3> "rowmenu %X %Y $id" - set linentag($row) [$canv2 create text 3 $y -anchor w \ - -text $name -font $nfont] - set linedtag($row) [$canv3 create text 3 $y -anchor w \ - -text $date -font $mainfont] + set linentag($row) [$canv2 create text 3 $y -anchor w -fill $fgcolor \ + -text $name -font $nfont -tags text] + set linedtag($row) [$canv3 create text 3 $y -anchor w -fill $fgcolor \ + -text $date -font $mainfont -tags text] set xr [expr {$xt + [font measure $mainfont $headline]}] if {$xr > $canvxmax} { set canvxmax $xr @@ -3136,9 +3157,9 @@ proc bindline {t id} { } proc drawtags {id x xt y1} { - global idtags idheads idotherrefs + global idtags idheads idotherrefs mainhead global linespc lthickness - global canv mainfont commitrow rowtextx curview + global canv mainfont commitrow rowtextx curview fgcolor bgcolor set marks {} set ntags 0 @@ -3163,8 +3184,14 @@ proc drawtags {id x xt y1} { set yb [expr {$yt + $linespc - 1}] set xvals {} set wvals {} + set i -1 foreach tag $marks { - set wid [font measure $mainfont $tag] + incr i + if {$i >= $ntags && $i < $ntags + $nheads && $tag eq $mainhead} { + set wid [font measure [concat $mainfont bold] $tag] + } else { + set wid [font measure $mainfont $tag] + } lappend xvals $xt lappend wvals $wid set xt [expr {$xt + $delta + $wid + $lthickness + $linespc}] @@ -3175,6 +3202,7 @@ proc drawtags {id x xt y1} { foreach tag $marks x $xvals wid $wvals { set xl [expr {$x + $delta}] set xr [expr {$x + $delta + $wid + $lthickness}] + set font $mainfont if {[incr ntags -1] >= 0} { # draw a tag set t [$canv create polygon $x [expr {$yt + $delta}] $xl $yt \ @@ -3186,6 +3214,9 @@ proc drawtags {id x xt y1} { # draw a head or other ref if {[incr nheads -1] >= 0} { set col green + if {$tag eq $mainhead} { + lappend font bold + } } else { set col "#ddddff" } @@ -3201,8 +3232,8 @@ proc drawtags {id x xt y1} { -width 0 -fill "#ffddaa" -tags tag.$id } } - set t [$canv create text $xl $y1 -anchor w -text $tag \ - -font $mainfont -tags tag.$id] + set t [$canv create text $xl $y1 -anchor w -text $tag -fill $fgcolor \ + -font $font -tags [list tag.$id text]] if {$ntags >= 0} { $canv bind $t <1> [list showtag $tag 1] } @@ -3223,10 +3254,11 @@ proc xcoord {i level ln} { } proc show_status {msg} { - global canv mainfont + global canv mainfont fgcolor clear_display - $canv create text 3 3 -anchor nw -text $msg -font $mainfont -tags textitems + $canv create text 3 3 -anchor nw -text $msg -font $mainfont \ + -tags text -fill $fgcolor } proc finishcommits {} { @@ -4574,7 +4606,8 @@ proc linehover {} { set t [$canv create rectangle $x0 $y0 $x1 $y1 \ -fill \#ffff80 -outline black -width 1 -tags hover] $canv raise $t - set t [$canv create text $x $y -anchor nw -text $text -tags hover -font $mainfont] + set t [$canv create text $x $y -anchor nw -text $text -tags hover \ + -font $mainfont] $canv raise $t } @@ -4901,7 +4934,7 @@ proc domktag {} { proc redrawtags {id} { global canv linehtag commitrow idpos selectedline curview - global mainfont + global mainfont canvxmax if {![info exists commitrow($curview,$id)]} return drawcmitrow $commitrow($curview,$id) @@ -5242,6 +5275,7 @@ proc doquit {} { proc doprefs {} { global maxwidth maxgraphpct diffopts global oldprefs prefstop showneartags + global bgcolor fgcolor ctext diffcolors set top .gitkprefs set prefstop $top @@ -5265,6 +5299,7 @@ proc doprefs {} { -font optionfont spinbox $top.maxpct -from 1 -to 100 -width 4 -textvariable maxgraphpct grid x $top.maxpctl $top.maxpct -sticky w + label $top.ddisp -text "Diff display options" grid $top.ddisp - -sticky w -pady 10 label $top.diffoptl -text "Options for diff program" \ @@ -5276,6 +5311,34 @@ proc doprefs {} { checkbutton $top.ntag.b -variable showneartags pack $top.ntag.b $top.ntag.l -side left grid x $top.ntag -sticky w + + label $top.cdisp -text "Colors: press to choose" + grid $top.cdisp - -sticky w -pady 10 + label $top.bg -padx 40 -relief sunk -background $bgcolor + button $top.bgbut -text "Background" -font optionfont \ + -command [list choosecolor bgcolor 0 $top.bg background setbg] + grid x $top.bgbut $top.bg -sticky w + label $top.fg -padx 40 -relief sunk -background $fgcolor + button $top.fgbut -text "Foreground" -font optionfont \ + -command [list choosecolor fgcolor 0 $top.fg foreground setfg] + grid x $top.fgbut $top.fg -sticky w + label $top.diffold -padx 40 -relief sunk -background [lindex $diffcolors 0] + button $top.diffoldbut -text "Diff: old lines" -font optionfont \ + -command [list choosecolor diffcolors 0 $top.diffold "diff old lines" \ + [list $ctext tag conf d0 -foreground]] + grid x $top.diffoldbut $top.diffold -sticky w + label $top.diffnew -padx 40 -relief sunk -background [lindex $diffcolors 1] + button $top.diffnewbut -text "Diff: new lines" -font optionfont \ + -command [list choosecolor diffcolors 1 $top.diffnew "diff new lines" \ + [list $ctext tag conf d1 -foreground]] + grid x $top.diffnewbut $top.diffnew -sticky w + label $top.hunksep -padx 40 -relief sunk -background [lindex $diffcolors 2] + button $top.hunksepbut -text "Diff: hunk header" -font optionfont \ + -command [list choosecolor diffcolors 2 $top.hunksep \ + "diff hunk header" \ + [list $ctext tag conf hunksep -foreground]] + grid x $top.hunksepbut $top.hunksep -sticky w + frame $top.buts button $top.buts.ok -text "OK" -command prefsok button $top.buts.can -text "Cancel" -command prefscan @@ -5285,6 +5348,35 @@ proc doprefs {} { grid $top.buts - - -pady 10 -sticky ew } +proc choosecolor {v vi w x cmd} { + global $v + + set c [tk_chooseColor -initialcolor [lindex [set $v] $vi] \ + -title "Gitk: choose color for $x"] + if {$c eq {}} return + $w conf -background $c + lset $v $vi $c + eval $cmd $c +} + +proc setbg {c} { + global bglist + + foreach w $bglist { + $w conf -background $c + } +} + +proc setfg {c} { + global fglist canv + + foreach w $fglist { + $w conf -foreground $c + } + allcanvs itemconf text -fill $c + $canv itemconf circle -outline $c +} + proc prefscan {} { global maxwidth maxgraphpct diffopts global oldprefs prefstop showneartags @@ -5620,6 +5712,9 @@ set wrapcomment "none" set showneartags 1 set colors {green red blue magenta darkgrey brown orange} +set bgcolor white +set fgcolor black +set diffcolors {red "#00a000" blue} catch {source ~/.gitk} diff --git a/gitweb/README b/gitweb/README index 8d672762ea..27c6dac143 100644 --- a/gitweb/README +++ b/gitweb/README @@ -5,5 +5,33 @@ The one working on: 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. + +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-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.css b/gitweb/gitweb.css index fffdb13d09..47c1ade87e 100644 --- a/gitweb/gitweb.css +++ b/gitweb/gitweb.css @@ -171,6 +171,10 @@ tr.dark { background-color: #f6f6f0; } +tr.dark2 { + background-color: #f6f6f0; +} + tr.dark:hover { background-color: #edece6; } @@ -181,12 +185,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 +204,10 @@ td.error { background-color: yellow; } +td.current_head { + text-decoration: underline; +} + table.diff_tree span.file_status.new { color: #008000; } diff --git a/gitweb/gitweb.cgi b/gitweb/gitweb.perl index 5acd66dd6c..626fcc9201 100755 --- a/gitweb/gitweb.cgi +++ b/gitweb/gitweb.perl @@ -14,47 +14,42 @@ use CGI::Util qw(unescape); use CGI::Carp qw(fatalsToBrowser); use Encode; use Fcntl ':mode'; +use File::Find qw(); binmode STDOUT, ':utf8'; our $cgi = new CGI; -our $version = "267"; +our $version = "++GIT_VERSION++"; 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"; +our $GIT = "++GIT_BINDIR++/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"; +our $projectroot = "++GITWEB_PROJECTROOT++"; # 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"; +our $site_name = "++GITWEB_SITENAME++" || $ENV{'SERVER_NAME'} || "Untitled"; # html text to include at home page -our $home_text = "indextext.html"; +our $home_text = "++GITWEB_HOMETEXT++"; # URI of default stylesheet -our $stylesheet = "gitweb.css"; +our $stylesheet = "++GITWEB_CSS++"; +# URI of GIT logo +our $logo = "++GITWEB_LOGO++"; # source of projects list -#our $projects_list = $projectroot; -our $projects_list = "index/index.aux"; +our $projects_list = "++GITWEB_LIST++"; # default blob_plain mimetype and default charset for text/plain blob our $default_blob_plain_mimetype = 'text/plain'; @@ -64,47 +59,46 @@ our $default_text_plain_charset = undef; # (relative to the current git repository) our $mimetypes_file = undef; +our $GITWEB_CONFIG = $ENV{'GITWEB_CONFIG'} || "++GITWEB_CONFIG++"; +require $GITWEB_CONFIG if -e $GITWEB_CONFIG; + +# version of the core git binary +our $git_version = qx($GIT --version) =~ m/git version (.*)$/ ? $1 : "unknown"; + +$projects_list ||= $projectroot; +if (! -d $git_temp) { + mkdir($git_temp, 0700) || die_error(undef, "Couldn't mkdir $git_temp"); +} + +# ====================================================================== # input validation and dispatch our $action = $cgi->param('a'); if (defined $action) { if ($action =~ m/[^0-9a-zA-Z\.\-_]/) { - undef $action; - die_error(undef, "Invalid action parameter."); + die_error(undef, "Invalid action parameter"); } - if ($action eq "git-logo.png") { - git_logo(); - exit; - } elsif ($action eq "opml") { + # action which does not check rest of parameters + if ($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."); + $project =~ s|^/||; + $project =~ s|/$||; +} +if (defined $project && $project) { + if (!validate_input($project)) { + die_error(undef, "Invalid project parameter"); } if (!(-d "$projectroot/$project")) { - undef $project; - die_error(undef, "No such directory."); + die_error(undef, "No such directory"); } if (!(-e "$projectroot/$project/HEAD")) { - undef $project; - die_error(undef, "No such 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(); @@ -113,53 +107,79 @@ if (defined $project) { 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."); + if (!validate_input($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."); + if (!validate_input($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."); + if (!validate_input($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."); + if (!validate_input($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."); + 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."); + die_error(undef, "Invalid search parameter"); } $searchtext = quotemeta $searchtext; } +# 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, +); + +$action = 'summary' if (!defined($action)); +if (!defined($actions{$action})) { + die_error(undef, "Unknown action"); +} +$actions{$action}->(); +exit; + +## ====================================================================== +## validation, quoting/unquoting and escaping + sub validate_input { my $input = shift; @@ -175,66 +195,6 @@ sub validate_input { 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_blame(); - 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 { @@ -250,6 +210,7 @@ sub esc_html { my $str = shift; $str = decode("utf8", $str, Encode::FB_DEFAULT); $str = escapeHTML($str); + $str =~ s/\014/^L/g; # escape FORM FEED (FF) character (e.g. in COPYING file) return $str; } @@ -263,6 +224,43 @@ sub unquote { 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; +} + +## ---------------------------------------------------------------------- +## 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; @@ -276,125 +274,108 @@ sub age_class { } } -sub git_header_html { - my $status = shift || "200 OK"; - my $expires = shift; +# convert age in seconds to "nn units ago" string +sub age_string { + my $age = shift; + my $age_str; - 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'; + 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 { - $content_type = 'text/html'; + $age_str .= " right now"; } - 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; + 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 { - $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"; + return '-rw-r--r--'; + }; + } else { + return '----------'; } - 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"; +# convert file mode in octal to file type string +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 { - print $cgi->a({-href => "$my_uri?" . esc_param("a=opml"), -class => "rss_logo"}, "OPML") . "\n"; + return "unknown"; } - print "</div>\n" . - "</body>\n" . - "</html>"; } -sub die_error { - my $status = shift || "403 Forbidden"; - my $error = shift || "Malformed query, file missing or permission denied"; +## ---------------------------------------------------------------------- +## functions returning short HTML fragments, or transforming HTML fragments +## which don't beling to other sections - git_header_html($status); - print "<div class=\"page_body\">\n" . - "<br/><br/>\n" . - "$status - $error\n" . - "<br/>\n" . - "</div>\n"; - git_footer_html(); - exit; +# 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({-class => "text", -href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash_text")}, $hash_text); + $line =~ s/$hash_text/$link/; + } + } + return $line; } -sub git_get_type { - my $hash = shift; +# format marker of refs pointing to given object +sub git_get_referencing { + my ($refs, $id) = @_; - open my $fd, "-|", "$GIT cat-file -t $hash" or return; - my $type = <$fd>; - close $fd or return; - chomp $type; - return $type; + if (defined $refs->{$id}) { + return ' <span class="tag">' . esc_html($refs->{$id}) . '</span>'; + } else { + return ""; + } } +## ---------------------------------------------------------------------- +## git utility subroutines, invoking git commands + +# get HEAD ref of given project as hash sub git_read_head { my $project = shift; my $oENV = $ENV{'GIT_DIR'}; @@ -413,6 +394,57 @@ sub git_read_head { return $retval; } +# get type of given object +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_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 +} + +# get hash of given path at given ref +sub git_get_hash_by_path { + my $base = shift; + my $path = shift || return undef; + + my $tree = $base; + + open my $fd, "-|", $GIT, "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(.+)$/; + return $3; +} + +## ...................................................................... +## git utility functions, directly accessing git repository + +# assumes that PATH is not symref sub git_read_hash { my $path = shift; @@ -435,12 +467,106 @@ sub git_read_description { return $descr; } +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 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; + # attention: for $type == "" it saves only last path part of ref name + # e.g. from 'refs/heads/jn/gitweb' it would leave only 'gitweb' + if ($line =~ m/^([0-9a-fA-F]{40})\t.*$type\/([^\^]+)/) { + if (defined $refs{$1}) { + $refs{$1} .= " / $2"; + } else { + $refs{$1} = $2; + } + } + } + close $fd or return; + return \%refs; +} + +## ---------------------------------------------------------------------- +## parse to hash functions + +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; +} + sub git_read_tag { my $tag_id = shift; my %tag; my @comment; - open my $fd, "-|", "$GIT cat-file tag $tag_id" or return; + open my $fd, "-|", $GIT, "cat-file", "tag", $tag_id or return; $tag{'id'} = $tag_id; while (my $line = <$fd>) { chomp $line; @@ -470,37 +596,6 @@ sub git_read_tag { 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; @@ -512,7 +607,7 @@ sub git_read_commit { @commit_lines = @$commit_text; } else { $/ = "\0"; - open my $fd, "-|", "$GIT rev-list --header --parents --max-count=1 $commit_id" or return; + open my $fd, "-|", $GIT, "rev-list", "--header", "--parents", "--max-count=1", $commit_id or return; @commit_lines = split '\n', <$fd>; close $fd or return; $/ = "\n"; @@ -595,6 +690,520 @@ sub git_read_commit { return %co; } +## ...................................................................... +## parse to array of hashes functions + +sub git_read_refs { + my $ref_dir = shift; + my @reflist; + + my @refs; + my $pfxlen = length("$projectroot/$project/$ref_dir"); + File::Find::find(sub { + return if (/^\./); + if (-f $_) { + push @refs, substr($File::Find::name, $pfxlen + 1); + } + }, "$projectroot/$project/$ref_dir"); + + foreach my $ref_file (@refs) { + my $ref_id = git_read_hash("$project/$ref_dir/$ref_file"); + my $type = git_get_type($ref_id) || next; + 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'}; + } else { + $ref_item{'reftype'} = $type; + $ref_item{'name'} = $ref_file; + $ref_item{'refid'} = $ref_id; + } + + push @reflist, \%ref_item; + } + # sort tags by age + @reflist = sort {$b->{'epoch'} <=> $a->{'epoch'}} @reflist; + return \@reflist; +} + +## ---------------------------------------------------------------------- +## 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 decode("utf8", $owner, Encode::FB_DEFAULT); +} + +## ...................................................................... +## 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>) { + 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; + #$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'; + } +} + +## ====================================================================== +## 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 .= " - $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 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"/> +EOF + if (defined $project) { + printf('<link rel="alternate" title="%s log" '. + 'href="%s" type="application/rss+xml"/>'."\n", + esc_param($project), + esc_param("$my_uri?p=$project;a=rss")); + } + + print "</head>\n" . + "<body>\n" . + "<div class=\"page_header\">\n" . + "<a href=\"http://www.kernel.org/pub/software/scm/git/docs/\" title=\"git documentation\">" . + "<img src=\"$logo\" width=\"72\" height=\"27\" alt=\"git\" style=\"float:right; border-width:0px;\"/>" . + "</a>\n"; + print $cgi->a({-href => esc_param($home_link)}, "projects") . " / "; + 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; +} + +## ---------------------------------------------------------------------- +## functions printing or outputting HTML: navigation + +sub git_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 { $_, ''} @navs; + if (defined $head) { + for (qw(commit commitdiff)) { + $arg{$_} = ";h=$head"; + } + if ($current =~ m/^(tree | log | shortlog | commit | commitdiff | search)$/x) { + for (qw(shortlog log)) { + $arg{$_} = ";h=$head"; + } + } + } + $arg{tree} .= ";h=$treehead" if defined $treehead; + $arg{tree} .= ";hb=$treebase" if defined $treebase; + + print "<div class=\"page_nav\">\n" . + (join " | ", + map { $_ eq $current + ? $_ + : $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$_$arg{$_}")}, "$_") + } + @navs); + print "<br/>\n$extra<br/>\n" . + "</div>\n"; +} + +sub git_get_paging_nav { + my ($action, $hash, $head, $page, $nrevs) = @_; + my $paging_nav; + + + if ($hash ne $head || $page) { + $paging_nav .= $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$action")}, "HEAD"); + } else { + $paging_nav .= "HEAD"; + } + + if ($page > 0) { + $paging_nav .= " ⋅ " . + $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$action;h=$hash;pg=" . ($page-1)), + -accesskey => "p", -title => "Alt-p"}, "prev"); + } else { + $paging_nav .= " ⋅ prev"; + } + + if ($nrevs >= (100 * ($page+1)-1)) { + $paging_nav .= " ⋅ " . + $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$action;h=$hash;pg=" . ($page+1)), + -accesskey => "n", -title => "Alt-n"}, "next"); + } else { + $paging_nav .= " ⋅ next"; + } + + return $paging_nav; +} + +## ...................................................................... +## functions printing or outputting HTML: div + +sub git_header_div { + my ($action, $title, $hash, $hash_base) = @_; + my $rest = ''; + + $rest .= ";h=$hash" if $hash; + $rest .= ";hb=$hash_base" if $hash_base; + + print "<div class=\"header\">\n" . + $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$action$rest"), + -class => "title"}, $title ? $title : $action) . "\n" . + "</div>\n"; +} + +sub git_print_page_path { + my $name = shift; + my $type = shift; + + if (!defined $name) { + print "<div class=\"page_path\"><b>/</b></div>\n"; + } elsif (defined $type && $type eq 'blob') { + print "<div class=\"page_path\"><b>" . + $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob_plain;f=$file_name")}, esc_html($name)) . "</b><br/></div>\n"; + } else { + print "<div class=\"page_path\"><b>" . esc_html($name) . "</b><br/></div>\n"; + } +} + +## ...................................................................... +## functions printing large fragments of HTML + +sub git_shortlog_body { + # uses global variable $project + my ($revlist, $from, $to, $refs, $extra) = @_; + $from = 0 unless defined $from; + $to = $#{$revlist} if (!defined $to || $#{$revlist} < $to); + + print "<table class=\"shortlog\" cellspacing=\"0\">\n"; + my $alternate = 0; + for (my $i = $from; $i <= $to; $i++) { + my $commit = $revlist->[$i]; + #my $ref = defined $refs ? git_get_referencing($refs, $commit) : ''; + my $ref = git_get_referencing($refs, $commit); + my %co = git_read_commit($commit); + 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>"; + 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>\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 = 0; + 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 => "$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) { + if (length($comment_short) < length($comment)) { + print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tag;h=$tag{'id'}"), + -class => "list", -title => $comment}, $comment_short); + } else { + print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tag;h=$tag{'id'}"), + -class => "list"}, $comment); + } + } + print "</td>\n" . + "<td class=\"selflink\">"; + if ($tag{'type'} eq "tag") { + print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tag;h=$tag{'id'}")}, "tag"); + } else { + print " "; + } + print "</td>\n" . + "<td class=\"link\">" . " | " . + $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"); + } elsif ($tag{'reftype'} eq "blob") { + print " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob_plain;h=$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 ($taglist, $head, $from, $to, $extra) = @_; + $from = 0 unless defined $from; + $to = $#{$taglist} if (!defined $to || $#{$taglist} < $to); + + print "<table class=\"heads\" cellspacing=\"0\">\n"; + my $alternate = 0; + for (my $i = $from; $i <= $to; $i++) { + my $entry = $taglist->[$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 => "$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>"; + } + if (defined $extra) { + print "<tr>\n" . + "<td colspan=\"3\">$extra</td>\n" . + "</tr>\n"; + } + print "</table>\n"; +} + +## ---------------------------------------------------------------------- +## functions printing large fragments, format as one of arguments + sub git_diff_print { my $from = shift; my $from_name = shift; @@ -610,7 +1219,7 @@ sub git_diff_print { if (defined $from) { $from_tmp = "$git_temp/gitweb_" . $$ . "_from"; open my $fd2, "> $from_tmp"; - open my $fd, "-|", "$GIT cat-file blob $from"; + open my $fd, "-|", $GIT, "cat-file", "blob", $from; my @file = <$fd>; print $fd2 @file; close $fd2; @@ -621,7 +1230,7 @@ sub git_diff_print { if (defined $to) { $to_tmp = "$git_temp/gitweb_" . $$ . "_to"; open my $fd2, "> $to_tmp"; - open my $fd, "-|", "$GIT cat-file blob $to"; + open my $fd, "-|", $GIT, "cat-file", "blob", $to; my @file = <$fd>; print $fd2 @file; close $fd2; @@ -635,7 +1244,7 @@ sub git_diff_print { $/ = "\n"; } else { while (my $line = <$fd>) { - chomp($line); + chomp $line; my $char = substr($line, 0, 1); my $diff_class = ""; if ($char eq '+') { @@ -648,12 +1257,7 @@ sub git_diff_print { # 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/; - } - } + $line = untabify($line); print "<div class=\"diff$diff_class\">" . esc_html($line) . "</div>\n"; } } @@ -667,196 +1271,21 @@ sub git_diff_print { } } -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; +## ====================================================================== +## ====================================================================== +## actions - $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); +sub git_project_list { + my $order = $cgi->param('o'); + if (defined $order && $order !~ m/project|descr|owner|age/) { + die_error(undef, "Unknown order parameter"); } - return; # implicit false -} -sub git_project_list { my @list = git_read_projects(); my @projects; if (!@list) { - die_error(undef, "No project found."); + die_error(undef, "No projects found"); } foreach my $pr (@list) { my $head = git_read_head($pr->{'path'}); @@ -878,6 +1307,7 @@ sub git_project_list { } push @projects, $pr; } + git_header_html(); if (-f $home_text) { print "<div class=\"index_include\">\n"; @@ -888,29 +1318,42 @@ sub git_project_list { } print "<table class=\"project_list\">\n" . "<tr>\n"; - if (!defined($order) || (defined($order) && ($order eq "project"))) { + $order ||= "project"; + if ($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"; + print "<th>" . + $cgi->a({-href => "$my_uri?" . esc_param("o=project"), + -class => "header"}, "Project") . + "</th>\n"; } - if (defined($order) && ($order eq "descr")) { + if ($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"; + print "<th>" . + $cgi->a({-href => "$my_uri?" . esc_param("o=descr"), + -class => "header"}, "Description") . + "</th>\n"; } - if (defined($order) && ($order eq "owner")) { + if ($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"; + print "<th>" . + $cgi->a({-href => "$my_uri?" . esc_param("o=owner"), + -class => "header"}, "Owner") . + "</th>\n"; } - if (defined($order) && ($order eq "age")) { + if ($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>" . + $cgi->a({-href => "$my_uri?" . esc_param("o=age"), + -class => "header"}, "Last Change") . + "</th>\n"; } print "<th></th>\n" . "</tr>\n"; @@ -922,14 +1365,16 @@ sub git_project_list { print "<tr class=\"light\">\n"; } $alternate ^= 1; - print "<td>" . $cgi->a({-href => "$my_uri?" . esc_param("p=$pr->{'path'};a=summary"), -class => "list"}, esc_html($pr->{'path'})) . "</td>\n" . - "<td>$pr->{'descr'}</td>\n" . + print "<td>" . $cgi->a({-href => "$my_uri?" . esc_param("p=$pr->{'path'};a=summary"), + -class => "list"}, esc_html($pr->{'path'})) . "</td>\n" . + "<td>" . esc_html($pr->{'descr'}) . "</td>\n" . "<td><i>" . chop_str($pr->{'owner'}, 15) . "</i></td>\n"; - print "<td class=\"". age_class($pr->{'commit'}{'age'}) . "\">" . $pr->{'commit'}{'age_string'} . "</td>\n" . + print "<td class=\"". age_class($pr->{'commit'}{'age'}) . "\">" . + $pr->{'commit'}{'age_string'} . "</td>\n" . "<td class=\"link\">" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$pr->{'path'};a=summary")}, "summary") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$pr->{'path'};a=shortlog")}, "shortlog") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$pr->{'path'};a=log")}, "log") . + $cgi->a({-href => "$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"; } @@ -937,89 +1382,6 @@ sub git_project_list { 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); @@ -1047,174 +1409,46 @@ sub git_summary { 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"; + git_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" . "</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>; + + open my $fd, "-|", $GIT, "rev-list", "--max-count=17", git_read_head($project) + or die_error(undef, "Open git-rev-list 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>"; + git_header_div('shortlog'); + git_shortlog_body(\@revlist, 0, 15, $refs, + $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog")}, "...")); 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>"; + git_header_div('tags'); + git_tags_body($taglist, 0, 15, + $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tags")}, "...")); } 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_header_div('heads'); + git_heads_body($headlist, $head, 0, 15, + $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=heads")}, "...")); } + git_footer_html(); } 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"; + git_page_nav('','', $head,undef,$head); 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"; + git_header_div('commit', esc_html($tag{'name'}), $hash); print "<div class=\"title_text\">\n" . "<table cellspacing=\"0\">\n" . "<tr>\n" . @@ -1238,35 +1472,86 @@ sub git_tag { 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, "Couldn't find base commit") 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 git-blame failed"); + git_header_html(); + my $formats_nav = + $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$hash;hb=$hash_base;f=$file_name")}, "blob") . + " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blame;f=$file_name")}, "head"); + git_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav); + git_header_div('commit', esc_html($co{'title'}), $hash_base); + git_print_page_path($file_name, $ftype); + my @rev_color = (qw(light2 dark2)); + my $num_colors = scalar(@rev_color); + my $current_color = 0; + my $last_rev; + print "<div class=\"page_body\">\n"; + print "<table class=\"blame\">\n"; + print "<tr><th>Commit</th><th>Line</th><th>Data</th></tr>\n"; + while (<$fd>) { + /^([0-9a-fA-F]{40}).*?(\d+)\)\s{1}(\s*.*)/; + my $full_rev = $1; + 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); + die_error('403 Permission denied', "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); + die_error(undef, "Couldn't find base commit") unless ($hash_base); my %co = git_read_commit($hash_base) - or die_error(undef, "Reading commit failed."); + 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."); + or die_error(undef, "Error lookup file"); } open ($fd, "-|", $GIT, "annotate", '-l', '-t', '-r', $file_name, $hash_base) - or die_error(undef, "Open failed."); + or die_error(undef, "Open git-annotate 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"; - print "<div class=\"page_path\"><b>" . esc_html($file_name) . "</b></div>\n"; + my $formats_nav = + $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$hash;hb=$hash_base;f=$file_name")}, "blob") . + " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blame;f=$file_name")}, "head"); + git_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav); + git_header_div('commit', esc_html($co{'title'}), $hash_base); + git_print_page_path($file_name, 'blob'); print "<div class=\"page_body\">\n"; print <<HTML; <table class="blame"> @@ -1312,13 +1597,8 @@ HTML $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 = untabify($data); $data = esc_html ($data); print <<HTML; @@ -1340,199 +1620,42 @@ 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"; + git_page_nav('','', $head,undef,$head); + git_header_div('summary', $project); + 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>"; - } + git_tags_body($taglist); } - 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"; + git_page_nav('','', $head,undef,$head); + git_header_div('summary', $project); + 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>"; - } + git_heads_body($taglist, $head); } - 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; +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") + or die_error(undef, "Error lookup file"); + } else { + die_error(undef, "No file name defined"); } } - 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 { my $type = shift; - open my $fd, "-|", "$GIT cat-file blob $hash" or die_error("Couldn't cat $file_name, $hash"); + open my $fd, "-|", $GIT, "cat-file", "blob", $hash + or die_error(undef, "Couldn't cat $file_name, $hash"); $type ||= git_blob_plain_mimetype($fd, $file_name); @@ -1554,58 +1677,50 @@ sub git_blob_plain { } sub git_blob { - if (!defined $hash && 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."); + 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") + or 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."); + open my $fd, "-|", $GIT, "cat-file", "blob", $hash + or die_error(undef, "Couldn't cat $file_name, $hash"); my $mimetype = git_blob_plain_mimetype($fd, $file_name); if ($mimetype !~ m/^text\//) { close $fd; return git_blob_plain($mimetype); } git_header_html(); + my $formats_nav = ''; 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") . " | "; + $formats_nav .= $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"; + $formats_nav .= + $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob_plain;h=$hash;f=$file_name")}, "plain") . + " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;hb=HEAD;f=$file_name")}, "head"); } else { - print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob_plain;h=$hash")}, "plain") . "<br/>\n"; + $formats_nav .= $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob_plain;h=$hash")}, "plain"); } - 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_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav); + git_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"; } - if (defined $file_name) { - print "<div class=\"page_path\"><b>" . esc_html($file_name) . "</b></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/; - } - } + $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"; @@ -1625,33 +1740,22 @@ sub git_tree { } } $/ = "\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."); + open my $fd, "-|", $GIT, "ls-tree", '-z', $hash + or die_error(undef, "Open git-ls-tree failed"); + my @entries = map { chomp; $_ } <$fd>; + close $fd or die_error(undef, "Reading tree failed"); $/ = "\n"; my $refs = read_info_ref(); - my $ref = ""; - if (defined $refs->{$hash_base}) { - $ref = " <span class=\"tag\">" . esc_html($refs->{$hash_base}) . "</span>"; - } + my $ref = git_get_referencing($refs, $hash_base); git_header_html(); my $base_key = ""; my $base = ""; + my $have_blame = git_get_project_config_bool ('blame'); if (defined $hash_base && (my %co = git_read_commit($hash_base))) { $base_key = ";hb=$hash_base"; - 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"; + git_page_nav('tree','', $hash_base); + git_header_div('commit', esc_html($co{'title'}) . $ref, $hash_base); } else { print "<div class=\"page_nav\">\n"; print "<br/><br/></div>\n"; @@ -1659,10 +1763,8 @@ sub git_tree { } if (defined $file_name) { $base = esc_html("$file_name/"); - print "<div class=\"page_path\"><b>/" . esc_html($file_name) . "</b></div>\n"; - } else { - print "<div class=\"page_path\"><b>/</b></div>\n"; } + git_print_page_path($file_name, 'tree'); print "<div class=\"page_body\">\n"; print "<table cellspacing=\"0\">\n"; my $alternate = 0; @@ -1685,9 +1787,11 @@ sub git_tree { $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=$hash_base;f=$base$t_name")}, "history") . + $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$t_hash$base_key;f=$base$t_name")}, "blob"); + if ($have_blame) { + print " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blame;h=$t_hash$base_key;f=$base$t_name")}, "blame"); + } + print " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=history;h=$t_hash;hb=$hash_base;f=$base$t_name")}, "history") . " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob_plain;h=$t_hash;f=$base$t_name")}, "raw") . "</td>\n"; } elsif ($t_type eq "tree") { @@ -1696,7 +1800,7 @@ sub git_tree { "</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;h=$hash_base;f=$base$t_name")}, "history") . + " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=history;hb=$hash_base;f=$base$t_name")}, "history") . "</td>\n"; } print "</tr>\n"; @@ -1706,97 +1810,6 @@ sub git_tree { 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) { @@ -1806,59 +1819,34 @@ sub git_log { $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>; + open my $fd, "-|", $GIT, "rev-list", $limit, $hash + or die_error(undef, "Open git-rev-list 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"; + my $paging_nav = git_get_paging_nav('log', $hash, $head, $page, $#revlist); + + git_header_html(); + git_page_nav('log','', $hash,undef,undef, $paging_nav); + 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); + + git_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 = ""; - if (defined $refs->{$commit}) { - $ref = " <span class=\"tag\">" . esc_html($refs->{$commit}) . "</span>"; - } + my $ref = git_get_referencing($refs, $commit); 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"; + git_header_div('commit', + "<span class=\"age\">$co{'age_string'}</span>" . + esc_html($co{'title'}) . $ref, + $commit); print "<div class=\"title_text\">\n" . "<div class=\"log_link\">\n" . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$commit")}, "commit") . @@ -1895,21 +1883,19 @@ sub git_log { sub git_commit { my %co = git_read_commit($hash); if (!%co) { - die_error(undef, "Unknown commit object."); + 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 = ""; + $parent = "--root"; } - 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."); + open my $fd, "-|", $GIT, "diff-tree", '-r', '-M', $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; @@ -1917,29 +1903,21 @@ sub git_commit { $expires = "+1d"; } my $refs = read_info_ref(); - my $ref = ""; - if (defined $refs->{$co{'id'}}) { - $ref = " <span class=\"tag\">" . esc_html($refs->{$co{'id'}}) . "</span>"; + my $ref = git_get_referencing($refs, $co{'id'}); + my $formats_nav = ''; + if (defined $file_name && defined $co{'parent'}) { + my $parent = $co{'parent'}; + $formats_nav .= $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blame;hb=$parent;f=$file_name")}, "blame"); } 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/><br/></div>\n"; + git_page_nav('commit', defined $co{'parent'} ? '' : 'commitdiff', + $hash, $co{'tree'}, $hash, + $formats_nav); + 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"; + git_header_div('commitdiff', esc_html($co{'title'}) . $ref, $hash); } 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"; + git_header_div('tree', esc_html($co{'title'}) . $ref, $co{'tree'}, $hash); } print "<div class=\"title_text\">\n" . "<table cellspacing=\"0\">\n"; @@ -2010,7 +1988,7 @@ sub git_commit { 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(.*)$/)) { + 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; @@ -2037,11 +2015,11 @@ sub git_commit { "<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" . + $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$from_id;hb=$parent;f=$file"), -class => "list"}, esc_html($file)) . "</td>\n" . "<td><span class=\"file_status deleted\">[deleted " . file_type($from_mode). "]</span></td>\n" . "<td class=\"link\">" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$from_id;hb=$hash;f=$file")}, "blob") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=history;h=$hash;f=$file")}, "history") . + $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$from_id;hb=$parent;f=$file")}, "blob") . + " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=history;hb=$parent;f=$file")}, "history") . "</td>\n" } elsif ($status eq "M" || $status eq "T") { my $mode_chnge = ""; @@ -2072,7 +2050,7 @@ sub git_commit { 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;h=$hash;f=$file")}, "history") . "\n"; + 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; @@ -2083,7 +2061,7 @@ sub git_commit { 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)) . + $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$from_id;hb=$parent;f=$from_file"), -class => "list"}, esc_html($from_file)) . " with " . (int $similarity) . "% similarity$mode_chng]</span></td>\n" . "<td class=\"link\">" . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$to_id;hb=$hash;f=$to_file")}, "blob"); @@ -2102,27 +2080,16 @@ 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"; + my $formats_nav = + $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blobdiff_plain;h=$hash;hp=$hash_parent")}, "plain"); + git_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav); + git_header_div('commit', esc_html($co{'title'}), $hash_base); } else { print "<div class=\"page_nav\">\n" . "<br/><br/></div>\n" . "<div class=\"title\">$hash vs $hash_parent</div>\n"; } - if (defined $file_name) { - print "<div class=\"page_path\"><b>/" . esc_html($file_name) . "</b></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) . @@ -2144,14 +2111,15 @@ sub git_commitdiff { mkdir($git_temp, 0700); my %co = git_read_commit($hash); if (!%co) { - die_error(undef, "Unknown commit object."); + die_error(undef, "Unknown commit object"); } if (!defined $hash_parent) { - $hash_parent = $co{'parent'}; + $hash_parent = $co{'parent'} || '--root'; } - 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."); + open my $fd, "-|", $GIT, "diff-tree", '-r', $hash_parent, $hash + or die_error(undef, "Open git-diff-tree failed"); + my @difftree = map { chomp; $_ } <$fd>; + close $fd or die_error(undef, "Reading git-diff-tree failed"); # non-textual hash id's can be cached my $expires; @@ -2159,23 +2127,12 @@ sub git_commitdiff { $expires = "+1d"; } my $refs = read_info_ref(); - my $ref = ""; - if (defined $refs->{$co{'id'}}) { - $ref = " <span class=\"tag\">" . esc_html($refs->{$co{'id'}}) . "</span>"; - } + my $ref = git_get_referencing($refs, $co{'id'}); + my $formats_nav = + $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff_plain;h=$hash;hp=$hash_parent")}, "plain"); git_header_html(undef, $expires); - 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"; + git_page_nav('commitdiff','', $hash,$co{'tree'},$hash, $formats_nav); + git_header_div('commit', esc_html($co{'title'}) . $ref, $hash); print "<div class=\"page_body\">\n"; my $comment = $co{'comment'}; my $empty = 0; @@ -2204,7 +2161,9 @@ sub git_commitdiff { 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(.*)$/; + if ($line !~ m/^:([0-7]{6}) ([0-7]{6}) ([0-9a-fA-F]{40}) ([0-9a-fA-F]{40}) (.)\t(.*)$/) { + next; + } my $from_mode = $1; my $to_mode = $2; my $from_id = $3; @@ -2212,21 +2171,23 @@ sub git_commitdiff { my $status = $5; my $file = validate_input(unquote($6)); if ($status eq "A") { - print "<div class=\"diff_info\">" . file_type($to_mode) . ":" . + 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)" . + $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$from_id;hb=$hash_parent;f=$file")}, $from_id) . "(deleted)" . "</div>\n"; git_diff_print($from_id, "a/$file", undef, "/dev/null"); } elsif ($status eq "M") { if ($from_id ne $to_id) { print "<div class=\"diff_info\">" . - file_type($from_mode) . ":" . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$from_id;hb=$hash;f=$file")}, $from_id) . + file_type($from_mode) . ":" . + $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$from_id;hb=$hash_parent;f=$file")}, $from_id) . " -> " . - file_type($to_mode) . ":" . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$to_id;hb=$hash;f=$file")}, $to_id); + 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"); } @@ -2239,15 +2200,23 @@ sub git_commitdiff { 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."); + my %co = git_read_commit($hash); + if (!%co) { + die_error(undef, "Unknown commit object"); + } + if (!defined $hash_parent) { + $hash_parent = $co{'parent'} || '--root'; + } + open my $fd, "-|", $GIT, "diff-tree", '-r', $hash_parent, $hash + or die_error(undef, "Open git-diff-tree failed"); + my @difftree = map { chomp; $_ } <$fd>; + close $fd or die_error(undef, "Reading diff-tree failed"); # try to figure out the next tag after this commit my $tagname; my $refs = read_info_ref("tags"); - open $fd, "-|", "$GIT rev-list HEAD"; - chomp (my (@commits) = <$fd>); + open $fd, "-|", $GIT, "rev-list", "HEAD"; + my @commits = map { chomp; $_ } <$fd>; close $fd; foreach my $commit (@commits) { if (defined $refs->{$commit}) { @@ -2259,14 +2228,13 @@ sub git_commitdiff_plain { } 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-Tag: $tagname\n"; } print "X-Git-Url: $my_url?p=$project;a=commitdiff;h=$hash\n" . "\n"; @@ -2277,7 +2245,9 @@ sub git_commitdiff_plain { 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(.*)$/; + if ($line !~ m/^:([0-7]{6}) ([0-7]{6}) ([0-9a-fA-F]{40}) ([0-9a-fA-F]{40}) (.)\t(.*)$/) { + next; + } my $from_id = $3; my $to_id = $4; my $status = $5; @@ -2293,31 +2263,28 @@ sub git_commitdiff_plain { } sub git_history { - if (!defined $hash) { - $hash = git_read_head($project); + if (!defined $hash_base) { + $hash_base = git_read_head($project); } - my %co = git_read_commit($hash); + my $ftype; + my %co = git_read_commit($hash_base); if (!%co) { - die_error(undef, "Unknown commit object."); + 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")}, "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 "<div class=\"page_path\"><b>/" . esc_html($file_name) . "</b><br/></div>\n"; + git_page_nav('','', $hash_base,$co{'tree'},$hash_base); + git_header_div('commit', esc_html($co{'title'}), $hash_base); + 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 -- \'$file_name\'"; + $GIT, "rev-list", "--full-history", $hash_base, "--", $file_name; print "<table cellspacing=\"0\">\n"; my $alternate = 0; while (my $line = <$fd>) { @@ -2327,10 +2294,7 @@ sub git_history { if (!%co) { next; } - my $ref = ""; - if (defined $refs->{$commit}) { - $ref = " <span class=\"tag\">" . esc_html($refs->{$commit}) . "</span>"; - } + my $ref = git_get_referencing($refs, $commit); if ($alternate) { print "<tr class=\"dark\">\n"; } else { @@ -2344,8 +2308,8 @@ sub git_history { "<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, $file_name); + " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$ftype;hb=$commit;f=$file_name")}, $ftype); + my $blob = git_get_hash_by_path($hash_base, $file_name); my $blob_parent = git_get_hash_by_path($commit, $file_name); if (defined $blob && defined $blob_parent && $blob ne $blob_parent) { print " | " . @@ -2363,14 +2327,14 @@ sub git_history { sub git_search { if (!defined $searchtext) { - die_error("", "Text field empty."); + die_error(undef, "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."); + 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 :) @@ -2387,24 +2351,14 @@ sub git_search { $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"; + git_page_nav('','', $hash,$co{'tree'},$hash); + git_header_div('commit', esc_html($co{'title'}), $hash); - 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; + open my $fd, "-|", $GIT, "rev-list", "--header", "--parents", $hash or next; while (my $commit_text = <$fd>) { if (!grep m/$searchtext/i, $commit_text) { next; @@ -2515,82 +2469,122 @@ sub git_shortlog { $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>; + open my $fd, "-|", $GIT, "rev-list", $limit, $hash + or die_error(undef, "Open git-rev-list 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"; - } + my $paging_nav = git_get_paging_nav('shortlog', $hash, $head, $page, $#revlist); + my $next_link = ''; 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"; + $next_link = + $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog;h=$hash;pg=" . ($page+1)), + -title => "Alt-n"}, "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++) { + + + git_header_html(); + git_page_nav('shortlog','', $hash,$hash,$hash, $paging_nav); + git_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, "rev-list", "--max-count=150", git_read_head($project) + or die_error(undef, "Open git-rev-list failed"); + my @revlist = map { chomp; $_ } <$fd>; + close $fd or die_error(undef, "Reading git-rev-list failed"); + print $cgi->header(-type => 'text/xml', -charset => 'utf-8'); + print "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n". + "<rss version=\"2.0\" xmlns:content=\"http://purl.org/rss/1.0/modules/content/\">\n"; + print "<channel>\n"; + print "<title>$project</title>\n". + "<link>" . esc_html("$my_url?p=$project;a=summary") . "</link>\n". + "<description>$project log</description>\n". + "<language>en</language>\n"; + + for (my $i = 0; $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"; + # 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; } - $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>"); + 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 "</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>"; + 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"; } - 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 "</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 "</table\n>"; - git_footer_html(); + print "</outline>\n". + "</body>\n". + "</opml>\n"; } diff --git a/hash-object.c b/hash-object.c index 43bd93bffb..5f89e64c13 100644 --- a/hash-object.c +++ b/hash-object.c @@ -46,7 +46,7 @@ int main(int argc, char **argv) if (!no_more_flags && argv[i][0] == '-') { if (!strcmp(argv[i], "-t")) { if (argc <= ++i) - die(hash_object_usage); + usage(hash_object_usage); type = argv[i]; } else if (!strcmp(argv[i], "-w")) { @@ -66,8 +66,8 @@ int main(int argc, char **argv) hash_stdin(type, write_object); } else - die(hash_object_usage); - } + usage(hash_object_usage); + } else { const char *arg = argv[i]; if (0 <= prefix_length) diff --git a/builtin-help.c b/help.c index 335fe5fedc..6484cb9df2 100644 --- a/builtin-help.c +++ b/help.c @@ -9,8 +9,6 @@ #include "exec_cmd.h" #include "common-cmds.h" -static const char git_usage[] = - "Usage: git [--version] [--exec-path[=GIT_EXEC_PATH]] [--help] COMMAND [ ARGS ]"; /* most GUI terminals set COLUMNS (although some don't export it) */ static int term_columns(void) @@ -142,7 +140,7 @@ static void list_commands(const char *exec_path, const char *pattern) continue; entlen = strlen(de->d_name); - if (4 < entlen && !strcmp(de->d_name + entlen - 4, ".exe")) + if (has_extension(de->d_name, ".exe")) entlen -= 4; if (longest < entlen) @@ -178,31 +176,6 @@ static void list_common_cmds_help(void) puts("(use 'git help -a' to get a list of all installed git commands)"); } -void cmd_usage(int show_all, const char *exec_path, const char *fmt, ...) -{ - if (fmt) { - va_list ap; - - va_start(ap, fmt); - printf("git: "); - vprintf(fmt, ap); - va_end(ap); - putchar('\n'); - } - else - puts(git_usage); - - if (exec_path) { - putchar('\n'); - if (show_all) - list_commands(exec_path, "git-*"); - else - list_common_cmds_help(); - } - - exit(1); -} - static void show_man_page(const char *git_cmd) { const char *page; @@ -221,21 +194,40 @@ static void show_man_page(const char *git_cmd) execlp("man", "man", page, NULL); } -int cmd_version(int argc, const char **argv, char **envp) +void help_unknown_cmd(const char *cmd) +{ + printf("git: '%s' is not a git-command\n\n", cmd); + list_common_cmds_help(); + exit(1); +} + +int cmd_version(int argc, const char **argv, const char *prefix) { printf("git version %s\n", git_version_string); return 0; } -int cmd_help(int argc, const char **argv, char **envp) +int cmd_help(int argc, const char **argv, const char *prefix) { - const char *help_cmd = argv[1]; - if (!help_cmd) - cmd_usage(0, git_exec_path(), NULL); - else if (!strcmp(help_cmd, "--all") || !strcmp(help_cmd, "-a")) - cmd_usage(1, git_exec_path(), NULL); + const char *help_cmd = argc > 1 ? argv[1] : NULL; + const char *exec_path = git_exec_path(); + + if (!help_cmd) { + printf("usage: %s\n\n", git_usage_string); + list_common_cmds_help(); + exit(1); + } + + else if (!strcmp(help_cmd, "--all") || !strcmp(help_cmd, "-a")) { + printf("usage: %s\n\n", git_usage_string); + if(exec_path) + list_commands(exec_path, "git-*"); + exit(1); + } + else show_man_page(help_cmd); + return 0; } diff --git a/http-fetch.c b/http-fetch.c index 12493fbed2..de5fc44e66 100644 --- a/http-fetch.c +++ b/http-fetch.c @@ -36,6 +36,8 @@ enum XML_Status { #define PREV_BUF_SIZE 4096 #define RANGE_HEADER_SIZE 30 +static int commits_on_stdin = 0; + static int got_alternates = -1; static int corrupt_object_found = 0; @@ -43,7 +45,7 @@ static struct curl_slist *no_pragma_header; struct alt_base { - char *base; + const char *base; int path_len; int got_indices; struct packed_git *packs; @@ -81,7 +83,7 @@ struct object_request }; struct alternates_request { - char *base; + const char *base; char *url; struct buffer *buffer; struct active_request_slot *slot; @@ -142,7 +144,7 @@ static size_t fwrite_sha1_file(void *ptr, size_t eltsize, size_t nmemb, return size; } -static void fetch_alternates(char *base); +static void fetch_alternates(const char *base); static void process_object_response(void *callback_data); @@ -507,7 +509,7 @@ static void process_alternates_response(void *callback_data) (struct alternates_request *)callback_data; struct active_request_slot *slot = alt_req->slot; struct alt_base *tail = alt; - char *base = alt_req->base; + const char *base = alt_req->base; static const char null_byte = '\0'; char *data; int i = 0; @@ -612,7 +614,7 @@ static void process_alternates_response(void *callback_data) got_alternates = 1; } -static void fetch_alternates(char *base) +static void fetch_alternates(const char *base) { struct buffer buffer; char *url; @@ -868,7 +870,7 @@ static void process_ls_pack(struct remote_ls_ctx *ls) if (strlen(ls->dentry_name) == 63 && !strncmp(ls->dentry_name, "objects/pack/pack-", 18) && - !strncmp(ls->dentry_name+58, ".pack", 5)) { + has_extension(ls->dentry_name, ".pack")) { get_sha1_hex(ls->dentry_name + 18, sha1); setup_index(ls->repo, sha1); } @@ -1185,7 +1187,7 @@ int fetch_ref(char *ref, unsigned char *sha1) char *url; char hex[42]; struct buffer buffer; - char *base = alt->base; + const char *base = alt->base; struct active_request_slot *slot; struct slot_results results; buffer.size = 41; @@ -1214,14 +1216,17 @@ int fetch_ref(char *ref, unsigned char *sha1) return 0; } -int main(int argc, char **argv) +int main(int argc, const char **argv) { - char *commit_id; - char *url; + int commits; + const char **write_ref = NULL; + char **commit_id; + const char *url; char *path; int arg = 1; int rc = 0; + setup_ident(); setup_git_directory(); git_config(git_default_config); @@ -1237,20 +1242,26 @@ int main(int argc, char **argv) } else if (argv[arg][1] == 'v') { get_verbosely = 1; } else if (argv[arg][1] == 'w') { - write_ref = argv[arg + 1]; + write_ref = &argv[arg + 1]; arg++; } else if (!strcmp(argv[arg], "--recover")) { get_recover = 1; + } else if (!strcmp(argv[arg], "--stdin")) { + commits_on_stdin = 1; } arg++; } - if (argc < arg + 2) { - usage("git-http-fetch [-c] [-t] [-a] [-d] [-v] [--recover] [-w ref] commit-id url"); + if (argc < arg + 2 - commits_on_stdin) { + usage("git-http-fetch [-c] [-t] [-a] [-v] [--recover] [-w ref] [--stdin] commit-id url"); return 1; } - commit_id = argv[arg]; - url = argv[arg + 1]; - write_ref_log_details = url; + if (commits_on_stdin) { + commits = pull_targets_stdin(&commit_id, &write_ref); + } else { + commit_id = (char **) &argv[arg++]; + commits = 1; + } + url = argv[arg]; http_init(); @@ -1268,13 +1279,16 @@ int main(int argc, char **argv) alt->path_len = strlen(path); } - if (pull(commit_id)) + if (pull(commits, commit_id, write_ref, url)) rc = 1; http_cleanup(); curl_slist_free_all(no_pragma_header); + if (commits_on_stdin) + pull_targets_free(commits, commit_id, write_ref); + if (corrupt_object_found) { fprintf(stderr, "Some loose object were found to be corrupt, but they might be just\n" diff --git a/http-push.c b/http-push.c index 47686195cd..0359ae5b6f 100644 --- a/http-push.c +++ b/http-push.c @@ -530,7 +530,7 @@ static void start_put(struct transfer_request *request) request->dest = xmalloc(strlen(request->url) + 14); sprintf(request->dest, "Destination: %s", request->url); posn += 38; - *(posn++) = '.'; + *(posn++) = '_'; strcpy(posn, request->lock->token); slot = get_active_slot(); @@ -2182,49 +2182,14 @@ 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; + if (merge_bases && !merge_bases->next && merge_bases->item == branch) + return 1; + + return 0; } static int delete_remote_branch(char *pattern, int force) @@ -2521,7 +2486,7 @@ int main(int argc, char **argv) commit_argv[3] = old_sha1_hex; commit_argc++; } - init_revisions(&revs); + init_revisions(&revs, setup_git_directory()); setup_revisions(commit_argc, commit_argv, &revs, NULL); free(new_sha1_hex); if (old_sha1_hex) { diff --git a/index-pack.c b/index-pack.c index b39953dc69..b20659c259 100644 --- a/index-pack.c +++ b/index-pack.c @@ -447,7 +447,7 @@ int main(int argc, char **argv) usage(index_pack_usage); if (!index_name) { int len = strlen(pack_name); - if (len < 5 || strcmp(pack_name + len - 5, ".pack")) + if (!has_extension(pack_name, ".pack")) die("packfile name '%s' does not end with '.pack'", pack_name); index_name_buf = xmalloc(len); diff --git a/local-fetch.c b/local-fetch.c index ffa4887570..7d01845d39 100644 --- a/local-fetch.c +++ b/local-fetch.c @@ -8,8 +8,9 @@ static int use_link = 0; static int use_symlink = 0; static int use_filecopy = 1; +static int commits_on_stdin = 0; -static char *path; /* "Remote" git repository */ +static const char *path; /* "Remote" git repository */ void prefetch(unsigned char *sha1) { @@ -42,8 +43,8 @@ static int setup_indices(void) return -1; while ((de = readdir(dir)) != NULL) { int namelen = strlen(de->d_name); - if (namelen != 50 || - strcmp(de->d_name + namelen - 5, ".pack")) + if (namelen != 50 || + !has_extension(de->d_name, ".pack")) continue; get_sha1_hex(de->d_name + 5, sha1); setup_index(sha1); @@ -194,19 +195,22 @@ int fetch_ref(char *ref, unsigned char *sha1) } static const char local_pull_usage[] = -"git-local-fetch [-c] [-t] [-a] [-d] [-v] [-w filename] [--recover] [-l] [-s] [-n] commit-id path"; +"git-local-fetch [-c] [-t] [-a] [-v] [-w filename] [--recover] [-l] [-s] [-n] [--stdin] commit-id path"; -/* +/* * By default we only use file copy. * If -l is specified, a hard link is attempted. * If -s is specified, then a symlink is attempted. * If -n is _not_ specified, then a regular file-to-file copy is done. */ -int main(int argc, char **argv) +int main(int argc, const char **argv) { - char *commit_id; + int commits; + const char **write_ref = NULL; + char **commit_id; int arg = 1; + setup_ident(); setup_git_directory(); git_config(git_default_config); @@ -229,21 +233,30 @@ int main(int argc, char **argv) else if (argv[arg][1] == 'v') get_verbosely = 1; else if (argv[arg][1] == 'w') - write_ref = argv[++arg]; + write_ref = &argv[++arg]; else if (!strcmp(argv[arg], "--recover")) get_recover = 1; + else if (!strcmp(argv[arg], "--stdin")) + commits_on_stdin = 1; else usage(local_pull_usage); arg++; } - if (argc < arg + 2) + if (argc < arg + 2 - commits_on_stdin) usage(local_pull_usage); - commit_id = argv[arg]; - path = argv[arg + 1]; - write_ref_log_details = path; + if (commits_on_stdin) { + commits = pull_targets_stdin(&commit_id, &write_ref); + } else { + commit_id = (char **) &argv[arg++]; + commits = 1; + } + path = argv[arg]; - if (pull(commit_id)) + if (pull(commits, commit_id, write_ref, path)) return 1; + if (commits_on_stdin) + pull_targets_free(commits, commit_id, write_ref); + return 0; } diff --git a/lockfile.c b/lockfile.c index 2346e0e9ef..2a2fea3cb6 100644 --- a/lockfile.c +++ b/lockfile.c @@ -22,7 +22,7 @@ static void remove_lock_file_on_signal(int signo) raise(signo); } -int hold_lock_file_for_update(struct lock_file *lk, const char *path) +static int lock_file(struct lock_file *lk, const char *path) { int fd; sprintf(lk->filename, "%s.lock", path); @@ -41,6 +41,14 @@ int hold_lock_file_for_update(struct lock_file *lk, const char *path) return fd; } +int hold_lock_file_for_update(struct lock_file *lk, const char *path, int die_on_error) +{ + int fd = lock_file(lk, path); + if (fd < 0 && die_on_error) + die("unable to create '%s': %s", path, strerror(errno)); + return fd; +} + int commit_lock_file(struct lock_file *lk) { char result_file[PATH_MAX]; diff --git a/log-tree.c b/log-tree.c index b67b8dd17a..05ede0c175 100644 --- a/log-tree.c +++ b/log-tree.c @@ -59,7 +59,7 @@ void show_log(struct rev_info *opt, const char *sep) fputs(diff_unique_abbrev(commit->object.sha1, abbrev_commit), stdout); if (opt->parents) show_parents(commit, abbrev_commit); - putchar('\n'); + putchar(opt->diffopt.line_termination); return; } @@ -2,10 +2,11 @@ #include "tag.h" /* - * A signature file has a very simple fixed format: three lines - * of "object <sha1>" + "type <typename>" + "tag <tagname>", - * followed by some free-form signature that git itself doesn't - * care about, but that can be verified with gpg or similar. + * A signature file has a very simple fixed format: four lines + * of "object <sha1>" + "type <typename>" + "tag <tagname>" + + * "tagger <committer>", followed by a blank line, a free-form tag + * message and a signature block that git itself doesn't care about, + * but that can be verified with gpg or similar. * * The first three lines are guaranteed to be at least 63 bytes: * "object <sha1>\n" is 48 bytes, "type tag\n" at 9 bytes is the @@ -38,6 +39,12 @@ static int verify_object(unsigned char *sha1, const char *expected_type) return ret; } +#ifdef NO_C99_FORMAT +#define PD_FMT "%d" +#else +#define PD_FMT "%td" +#endif + static int verify_tag(char *buffer, unsigned long size) { int typelen; @@ -46,45 +53,42 @@ static int verify_tag(char *buffer, unsigned long size) const char *object, *type_line, *tag_line, *tagger_line; if (size < 64) - return error("wanna fool me ? you obviously got the size wrong !\n"); + return error("wanna fool me ? you obviously got the size wrong !"); buffer[size] = 0; /* Verify object line */ object = buffer; if (memcmp(object, "object ", 7)) - return error("char%d: does not start with \"object \"\n", 0); + return error("char%d: does not start with \"object \"", 0); if (get_sha1_hex(object + 7, sha1)) - return error("char%d: could not get SHA1 hash\n", 7); + return error("char%d: could not get SHA1 hash", 7); /* Verify type line */ type_line = object + 48; if (memcmp(type_line - 1, "\ntype ", 6)) - return error("char%d: could not find \"\\ntype \"\n", 47); + return error("char%d: could not find \"\\ntype \"", 47); /* Verify tag-line */ tag_line = strchr(type_line, '\n'); if (!tag_line) - return error("char%td: could not find next \"\\n\"\n", type_line - buffer); + return error("char" PD_FMT ": could not find next \"\\n\"", type_line - buffer); tag_line++; if (memcmp(tag_line, "tag ", 4) || tag_line[4] == '\n') - return error("char%td: no \"tag \" found\n", tag_line - buffer); + return error("char" PD_FMT ": no \"tag \" found", tag_line - buffer); /* Get the actual type */ typelen = tag_line - type_line - strlen("type \n"); if (typelen >= sizeof(type)) - return error("char%td: type too long\n", type_line+5 - buffer); + return error("char" PD_FMT ": type too long", type_line+5 - buffer); memcpy(type, type_line+5, typelen); type[typelen] = 0; /* Verify that the object matches */ - if (get_sha1_hex(object + 7, sha1)) - return error("char%d: could not get SHA1 hash but this is really odd since i got it before !\n", 7); - if (verify_object(sha1, type)) - return error("char%d: could not verify object %s\n", 7, sha1); + return error("char%d: could not verify object %s", 7, sha1_to_hex(sha1)); /* Verify the tag-name: we don't allow control characters or spaces in it */ tag_line += 4; @@ -94,19 +98,24 @@ static int verify_tag(char *buffer, unsigned long size) break; if (c > ' ') continue; - return error("char%td: could not verify tag name\n", tag_line - buffer); + return error("char" PD_FMT ": could not verify tag name", tag_line - buffer); } /* Verify the tagger line */ tagger_line = tag_line; if (memcmp(tagger_line, "tagger", 6) || (tagger_line[6] == '\n')) - return error("char%td: could not find \"tagger\"\n", tagger_line - buffer); + return error("char" PD_FMT ": could not find \"tagger\"", tagger_line - buffer); + + /* TODO: check for committer info + blank line? */ + /* Also, the minimum length is probably + "tagger .", or 63+8=71 */ /* The actual stuff afterwards we don't care about.. */ return 0; } +#undef PD_FMT + int main(int argc, char **argv) { unsigned long size = 4096; @@ -114,7 +123,7 @@ int main(int argc, char **argv) unsigned char result_sha1[20]; if (argc != 1) - usage("cat <signaturefile> | git-mktag"); + usage("git-mktag < signaturefile"); setup_git_directory(); @@ -71,7 +71,7 @@ static void write_tree(unsigned char *sha1) write_sha1_file(buffer, offset, tree_type, sha1); } -static const char mktree_usage[] = "mktree [-z]"; +static const char mktree_usage[] = "git-mktree [-z]"; int main(int ac, char **av) { @@ -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/path-list.c b/path-list.c new file mode 100644 index 0000000000..f15a10de37 --- /dev/null +++ b/path-list.c @@ -0,0 +1,105 @@ +#include <stdio.h> +#include "cache.h" +#include "path-list.h" + +/* if there is no exact match, point to the index where the entry could be + * inserted */ +static int get_entry_index(const struct path_list *list, const char *path, + int *exact_match) +{ + int left = -1, right = list->nr; + + while (left + 1 < right) { + int middle = (left + right) / 2; + int compare = strcmp(path, list->items[middle].path); + if (compare < 0) + right = middle; + else if (compare > 0) + left = middle; + else { + *exact_match = 1; + return middle; + } + } + + *exact_match = 0; + return right; +} + +/* returns -1-index if already exists */ +static int add_entry(struct path_list *list, const char *path) +{ + int exact_match; + int index = get_entry_index(list, path, &exact_match); + + if (exact_match) + return -1 - index; + + if (list->nr + 1 >= list->alloc) { + list->alloc += 32; + list->items = xrealloc(list->items, list->alloc + * sizeof(struct path_list_item)); + } + if (index < list->nr) + memmove(list->items + index + 1, list->items + index, + (list->nr - index) + * sizeof(struct path_list_item)); + list->items[index].path = list->strdup_paths ? + strdup(path) : (char *)path; + list->items[index].util = NULL; + list->nr++; + + return index; +} + +struct path_list_item *path_list_insert(const char *path, struct path_list *list) +{ + int index = add_entry(list, path); + + if (index < 0) + index = 1 - index; + + return list->items + index; +} + +int path_list_has_path(const struct path_list *list, const char *path) +{ + int exact_match; + get_entry_index(list, path, &exact_match); + return exact_match; +} + +struct path_list_item *path_list_lookup(const char *path, struct path_list *list) +{ + int exact_match, i = get_entry_index(list, path, &exact_match); + if (!exact_match) + return NULL; + return list->items + i; +} + +void path_list_clear(struct path_list *list, int free_items) +{ + if (list->items) { + int i; + if (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); + } + list->items = NULL; + list->nr = list->alloc = 0; +} + +void print_path_list(const char *text, const struct path_list *p) +{ + int i; + if ( text ) + printf("%s\n", text); + for (i = 0; i < p->nr; i++) + printf("%s:%p\n", p->items[i].path, p->items[i].util); +} + diff --git a/path-list.h b/path-list.h new file mode 100644 index 0000000000..d6401eaa35 --- /dev/null +++ b/path-list.h @@ -0,0 +1,22 @@ +#ifndef _PATH_LIST_H_ +#define _PATH_LIST_H_ + +struct path_list_item { + char *path; + void *util; +}; +struct path_list +{ + struct path_list_item *items; + unsigned int nr, alloc; + unsigned int strdup_paths:1; +}; + +void print_path_list(const char *text, const struct path_list *p); + +int path_list_has_path(const struct path_list *list, const char *path); +void path_list_clear(struct path_list *list, int free_items); +struct path_list_item *path_list_insert(const char *path, struct path_list *list); +struct path_list_item *path_list_lookup(const char *path, struct path_list *list); + +#endif /* _PATH_LIST_H_ */ diff --git a/read-cache.c b/read-cache.c index a50d3612c8..f92cdaacee 100644 --- a/read-cache.c +++ b/read-cache.c @@ -24,6 +24,11 @@ unsigned int active_nr = 0, active_alloc = 0, active_cache_changed = 0; struct cache_tree *active_cache_tree = NULL; +int cache_errno = 0; + +static void *cache_mmap = NULL; +static size_t cache_mmap_size = 0; + /* * This only updates the "non-critical" parts of the directory * cache, ie the parts that aren't tracked by GIT, and only used @@ -56,7 +61,7 @@ static int ce_compare_data(struct cache_entry *ce, struct stat *st) unsigned char sha1[20]; if (!index_fd(sha1, fd, st, 0, NULL)) match = memcmp(sha1, ce->sha1, 20); - close(fd); + /* index_fd() closed the file descriptor already */ } return match; } @@ -314,6 +319,45 @@ int remove_file_from_cache(const char *path) return 0; } +int add_file_to_index(const char *path, int verbose) +{ + int size, namelen; + struct stat st; + struct cache_entry *ce; + + if (lstat(path, &st)) + die("%s: unable to stat (%s)", path, strerror(errno)); + + if (!S_ISREG(st.st_mode) && !S_ISLNK(st.st_mode)) + die("%s: can only add regular files or symbolic links", path); + + namelen = strlen(path); + size = cache_entry_size(namelen); + ce = xcalloc(1, size); + memcpy(ce->name, path, namelen); + ce->ce_flags = htons(namelen); + fill_stat_cache_info(ce, &st); + + 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. + */ + int pos = cache_name_pos(path, namelen); + if (pos >= 0) + ce->ce_mode = active_cache[pos]->ce_mode; + } + + if (index_path(ce->sha1, path, &st, 1)) + die("unable to index file %s", path); + if (add_cache_entry(ce, ADD_CACHE_OK_TO_ADD)) + die("unable to add %s to index",path); + if (verbose) + printf("add '%s'\n", path); + cache_tree_invalidate_path(active_cache_tree, path); + return 0; +} + int ce_same_name(struct cache_entry *a, struct cache_entry *b) { int len = ce_namelen(a); @@ -577,22 +621,6 @@ int add_cache_entry(struct cache_entry *ce, int option) return 0; } -/* Three functions to allow overloaded pointer return; see linux/err.h */ -static inline void *ERR_PTR(long error) -{ - return (void *) error; -} - -static inline long PTR_ERR(const void *ptr) -{ - return (long) ptr; -} - -static inline long IS_ERR(const void *ptr) -{ - return (unsigned long)ptr > (unsigned long)-1000L; -} - /* * "refresh" does not calculate a new sha1 file or bring the * cache up-to-date for mode/content changes. But what it @@ -604,14 +632,16 @@ static inline long IS_ERR(const void *ptr) * For example, you'd want to do this after doing a "git-read-tree", * to link up the stat cache details with the proper files. */ -static struct cache_entry *refresh_entry(struct cache_entry *ce, int really) +struct cache_entry *refresh_cache_entry(struct cache_entry *ce, int really) { struct stat st; struct cache_entry *updated; int changed, size; - if (lstat(ce->name, &st) < 0) - return ERR_PTR(-errno); + if (lstat(ce->name, &st) < 0) { + cache_errno = errno; + return NULL; + } changed = ce_match_stat(ce, &st, really); if (!changed) { @@ -619,11 +649,13 @@ static struct cache_entry *refresh_entry(struct cache_entry *ce, int really) !(ce->ce_flags & htons(CE_VALID))) ; /* mark this one VALID again */ else - return NULL; + return ce; } - if (ce_modified(ce, &st, really)) - return ERR_PTR(-EINVAL); + if (ce_modified(ce, &st, really)) { + cache_errno = EINVAL; + return NULL; + } size = ce_size(ce); updated = xmalloc(size); @@ -666,13 +698,13 @@ int refresh_cache(unsigned int flags) continue; } - new = refresh_entry(ce, really); - if (!new) + new = refresh_cache_entry(ce, really); + if (new == ce) continue; - if (IS_ERR(new)) { - if (not_new && PTR_ERR(new) == -ENOENT) + if (!new) { + if (not_new && cache_errno == ENOENT) continue; - if (really && PTR_ERR(new) == -EINVAL) { + if (really && cache_errno == EINVAL) { /* If we are doing --really-refresh that * means the index is not valid anymore. */ @@ -729,39 +761,43 @@ static int read_index_extension(const char *ext, void *data, unsigned long sz) int read_cache(void) { + return read_cache_from(get_index_file()); +} + +/* remember to discard_cache() before reading a different cache! */ +int read_cache_from(const char *path) +{ int fd, i; struct stat st; - unsigned long size, offset; - void *map; + unsigned long offset; struct cache_header *hdr; errno = EBUSY; - if (active_cache) + if (cache_mmap) return active_nr; errno = ENOENT; index_file_timestamp = 0; - fd = open(get_index_file(), O_RDONLY); + fd = open(path, O_RDONLY); if (fd < 0) { if (errno == ENOENT) return 0; die("index file open failed (%s)", strerror(errno)); } - size = 0; /* avoid gcc warning */ - map = MAP_FAILED; + cache_mmap = MAP_FAILED; if (!fstat(fd, &st)) { - size = st.st_size; + cache_mmap_size = st.st_size; errno = EINVAL; - if (size >= sizeof(struct cache_header) + 20) - map = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0); + if (cache_mmap_size >= sizeof(struct cache_header) + 20) + cache_mmap = mmap(NULL, cache_mmap_size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0); } close(fd); - if (map == MAP_FAILED) + if (cache_mmap == MAP_FAILED) die("index file mmap failed (%s)", strerror(errno)); - hdr = map; - if (verify_hdr(hdr, size) < 0) + hdr = cache_mmap; + if (verify_hdr(hdr, cache_mmap_size) < 0) goto unmap; active_nr = ntohl(hdr->hdr_entries); @@ -770,12 +806,12 @@ int read_cache(void) offset = sizeof(*hdr); for (i = 0; i < active_nr; i++) { - struct cache_entry *ce = (struct cache_entry *) ((char *) map + offset); + struct cache_entry *ce = (struct cache_entry *) ((char *) cache_mmap + offset); offset = offset + ce_size(ce); active_cache[i] = ce; } index_file_timestamp = st.st_mtime; - while (offset <= size - 20 - 8) { + while (offset <= cache_mmap_size - 20 - 8) { /* After an array of active_nr index entries, * there can be arbitrary number of extended * sections, each of which is prefixed with @@ -783,10 +819,10 @@ int read_cache(void) * in 4-byte network byte order. */ unsigned long extsize; - memcpy(&extsize, (char *) map + offset + 4, 4); + memcpy(&extsize, (char *) cache_mmap + offset + 4, 4); extsize = ntohl(extsize); - if (read_index_extension(((const char *) map) + offset, - (char *) map + offset + 8, + if (read_index_extension(((const char *) cache_mmap) + offset, + (char *) cache_mmap + offset + 8, extsize) < 0) goto unmap; offset += 8; @@ -795,7 +831,7 @@ int read_cache(void) return active_nr; unmap: - munmap(map, size); + munmap(cache_mmap, cache_mmap_size); errno = EINVAL; die("index file corrupt"); } @@ -147,7 +147,7 @@ static int do_for_each_ref(const char *base, int (*fn)(const char *path, const u namelen = strlen(de->d_name); if (namelen > 255) continue; - if (namelen>5 && !strcmp(de->d_name+namelen-5,".lock")) + if (has_extension(de->d_name, ".lock")) continue; memcpy(path + baselen, de->d_name, namelen+1); if (stat(git_path("%s", path), &st) < 0) @@ -294,6 +294,7 @@ static struct ref_lock *lock_ref_sha1_basic(const char *path, int plen, const unsigned char *old_sha1, int mustexist) { + const char *orig_path = path; struct ref_lock *lock; struct stat st; @@ -303,7 +304,11 @@ static struct ref_lock *lock_ref_sha1_basic(const char *path, plen = strlen(path) - plen; path = resolve_ref(path, lock->old_sha1, mustexist); if (!path) { + int last_errno = errno; + error("unable to resolve reference %s: %s", + orig_path, strerror(errno)); unlock_ref(lock); + errno = last_errno; return NULL; } lock->lk = xcalloc(1, sizeof(struct lock_file)); @@ -314,13 +319,7 @@ static struct ref_lock *lock_ref_sha1_basic(const char *path, if (safe_create_leading_directories(lock->ref_file)) die("unable to create directory for %s", lock->ref_file); - lock->lock_fd = hold_lock_file_for_update(lock->lk, lock->ref_file); - if (lock->lock_fd < 0) { - error("Couldn't open lock file %s: %s", - lock->lk->filename, strerror(errno)); - unlock_ref(lock); - return NULL; - } + lock->lock_fd = hold_lock_file_for_update(lock->lk, lock->ref_file, 1); return old_sha1 ? verify_lock(lock, old_sha1, mustexist) : lock; } diff --git a/revision.c b/revision.c index 874e349db8..5a91d06b98 100644 --- a/revision.c +++ b/revision.c @@ -509,7 +509,7 @@ static int add_parents_only(struct rev_info *revs, const char *arg, int flags) return 1; } -void init_revisions(struct rev_info *revs) +void init_revisions(struct rev_info *revs, const char *prefix) { memset(revs, 0, sizeof(*revs)); @@ -521,7 +521,7 @@ void init_revisions(struct rev_info *revs) revs->pruning.change = file_change; revs->lifo = 1; revs->dense = 1; - revs->prefix = setup_git_directory(); + revs->prefix = prefix; revs->max_age = -1; revs->min_age = -1; revs->max_count = -1; @@ -936,7 +936,8 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch revs->diffopt.output_format = DIFF_FORMAT_PATCH; } revs->diffopt.abbrev = revs->abbrev; - diff_setup_done(&revs->diffopt); + if (diff_setup_done(&revs->diffopt) < 0) + die("diff_setup_done failed"); return left; } diff --git a/revision.h b/revision.h index e23ec8f45a..0c3b8d9905 100644 --- a/revision.h +++ b/revision.h @@ -87,7 +87,7 @@ struct rev_info { extern int rev_same_tree_as_empty(struct rev_info *, struct tree *t1); extern int rev_compare_tree(struct rev_info *, struct tree *t1, struct tree *t2); -extern void init_revisions(struct rev_info *revs); +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 void prepare_revision_walk(struct rev_info *revs); extern struct commit *get_revision(struct rev_info *revs); @@ -184,6 +184,10 @@ const char *setup_git_directory_gently(int *nongit_ok) } return NULL; bad_dir_environ: + if (nongit_ok) { + *nongit_ok = 1; + return NULL; + } path[len] = 0; die("Not a git repository: '%s'", path); } diff --git a/sha1_file.c b/sha1_file.c index 51e07d5a45..842a6f3ae8 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -590,7 +590,7 @@ static void prepare_packed_git_one(char *objdir, int local) int namelen = strlen(de->d_name); struct packed_git *p; - if (strcmp(de->d_name + namelen - 4, ".idx")) + if (!has_extension(de->d_name, ".idx")) continue; /* we have .idx. Is it a file we can map? */ diff --git a/sha1_name.c b/sha1_name.c index 5fe8e5d4bf..c5a05faeb6 100644 --- a/sha1_name.c +++ b/sha1_name.c @@ -193,7 +193,7 @@ const char *find_unique_abbrev(const unsigned char *sha1, int len) is_null = !memcmp(sha1, null_sha1, 20); memcpy(hex, sha1_to_hex(sha1), 40); - if (len == 40) + if (len == 40 || !len) return hex; while (len < 40) { unsigned char sha1_ret[20]; diff --git a/ssh-fetch.c b/ssh-fetch.c index 28f7fd9174..c7d8fa80e4 100644 --- a/ssh-fetch.c +++ b/ssh-fetch.c @@ -120,9 +120,10 @@ int fetch_ref(char *ref, unsigned char *sha1) static const char ssh_fetch_usage[] = MY_PROGRAM_NAME - " [-c] [-t] [-a] [-v] [-d] [--recover] [-w ref] commit-id url"; + " [-c] [-t] [-a] [-v] [--recover] [-w ref] commit-id url"; int main(int argc, char **argv) { + const char *write_ref = NULL; char *commit_id; char *url; int arg = 1; @@ -131,6 +132,7 @@ int main(int argc, char **argv) prog = getenv("GIT_SSH_PUSH"); if (!prog) prog = "git-ssh-upload"; + setup_ident(); setup_git_directory(); git_config(git_default_config); @@ -159,7 +161,6 @@ int main(int argc, char **argv) } commit_id = argv[arg]; url = argv[arg + 1]; - write_ref_log_details = url; if (setup_connection(&fd_in, &fd_out, prog, url, arg, argv + 1)) return 1; @@ -167,7 +168,7 @@ int main(int argc, char **argv) if (get_version()) return 1; - if (pull(commit_id)) + if (pull(1, &commit_id, &write_ref, url)) return 1; return 0; diff --git a/t/annotate-tests.sh b/t/annotate-tests.sh index b6a2edd887..8baf2fef69 100644 --- a/t/annotate-tests.sh +++ b/t/annotate-tests.sh @@ -4,7 +4,8 @@ check_count () { head= case "$1" in -h) head="$2"; shift; shift ;; esac - $PROG file $head | perl -e ' + $PROG file $head >.result || return 1 + cat .result | perl -e ' my %expect = (@ARGV); my %count = (); while (<STDIN>) { diff --git a/t/t1003-read-tree-prefix.sh b/t/t1003-read-tree-prefix.sh new file mode 100755 index 0000000000..48ab117d75 --- /dev/null +++ b/t/t1003-read-tree-prefix.sh @@ -0,0 +1,27 @@ +#!/bin/sh +# +# Copyright (c) 2006 Junio C Hamano +# + +test_description='git-read-tree --prefix test. +' + +. ./test-lib.sh + +test_expect_success setup ' + echo hello >one && + git-update-index --add one && + tree=`git-write-tree` && + echo tree is $tree +' + +echo 'one +two/one' >expect + +test_expect_success 'read-tree --prefix' ' + git-read-tree --prefix=two/ $tree && + git-ls-files >actual && + cmp expect actual +' + +test_done diff --git a/t/t1020-subdirectory.sh b/t/t1020-subdirectory.sh new file mode 100755 index 0000000000..4409b87f8d --- /dev/null +++ b/t/t1020-subdirectory.sh @@ -0,0 +1,109 @@ +#!/bin/sh +# +# Copyright (c) 2006 Junio C Hamano +# + +test_description='Try various core-level commands in subdirectory. +' + +. ./test-lib.sh + +test_expect_success setup ' + long="a b c d e f g h i j k l m n o p q r s t u v w x y z" && + for c in $long; do echo $c; done >one && + mkdir dir && + for c in x y z $long a b c; do echo $c; done >dir/two && + cp one original.one && + cp dir/two original.two +' +HERE=`pwd` +LF=' +' + +test_expect_success 'update-index and ls-files' ' + cd $HERE && + git-update-index --add one && + case "`git-ls-files`" in + one) echo ok one ;; + *) echo bad one; exit 1 ;; + esac && + cd dir && + git-update-index --add two && + case "`git-ls-files`" in + two) echo ok two ;; + *) echo bad two; exit 1 ;; + esac && + cd .. && + case "`git-ls-files`" in + dir/two"$LF"one) echo ok both ;; + *) echo bad; exit 1 ;; + esac +' + +test_expect_success 'cat-file' ' + cd $HERE && + two=`git-ls-files -s dir/two` && + two=`expr "$two" : "[0-7]* \\([0-9a-f]*\\)"` && + echo "$two" && + git-cat-file -p "$two" >actual && + cmp dir/two actual && + cd dir && + git-cat-file -p "$two" >actual && + cmp two actual +' +rm -f actual dir/actual + +test_expect_success 'diff-files' ' + cd $HERE && + echo a >>one && + echo d >>dir/two && + case "`git-diff-files --name-only`" in + dir/two"$LF"one) echo ok top ;; + *) echo bad top; exit 1 ;; + esac && + # diff should not omit leading paths + cd dir && + case "`git-diff-files --name-only`" in + dir/two"$LF"one) echo ok subdir ;; + *) echo bad subdir; exit 1 ;; + esac && + case "`git-diff-files --name-only .`" in + dir/two) echo ok subdir limited ;; + *) echo bad subdir limited; exit 1 ;; + esac +' + +test_expect_success 'write-tree' ' + cd $HERE && + top=`git-write-tree` && + echo $top && + cd dir && + sub=`git-write-tree` && + echo $sub && + test "z$top" = "z$sub" +' + +test_expect_success 'checkout-index' ' + cd $HERE && + git-checkout-index -f -u one && + cmp one original.one && + cd dir && + git-checkout-index -f -u two && + cmp two ../original.two +' + +test_expect_success 'read-tree' ' + cd $HERE && + rm -f one dir/two && + tree=`git-write-tree` && + git-read-tree --reset -u "$tree" && + cmp one original.one && + cmp dir/two original.two && + cd dir && + rm -f two && + git-read-tree --reset -u "$tree" && + cmp two ../original.two && + cmp ../one ../original.one +' + +test_done diff --git a/t/t1400-update-ref.sh b/t/t1400-update-ref.sh index 04fab26621..ddc80bbeae 100755 --- a/t/t1400-update-ref.sh +++ b/t/t1400-update-ref.sh @@ -14,6 +14,8 @@ D=4444444444444444444444444444444444444444 E=5555555555555555555555555555555555555555 F=6666666666666666666666666666666666666666 m=refs/heads/master +n_dir=refs/heads/gu +n=$n_dir/fixes test_expect_success \ "create $m" \ @@ -26,6 +28,16 @@ test_expect_success \ rm -f .git/$m test_expect_success \ + "fail to create $n" \ + 'touch .git/$n_dir + git-update-ref $n $A >out 2>err + test $? = 1 && + test "" = "$(cat out)" && + grep "error: unable to resolve reference" err && + grep $n err' +rm -f .git/$n_dir out err + +test_expect_success \ "create $m (by HEAD)" \ 'git-update-ref HEAD $A && test $A = $(cat .git/$m)' diff --git a/t/t3800-mktag.sh b/t/t3800-mktag.sh new file mode 100755 index 0000000000..5b23b7769d --- /dev/null +++ b/t/t3800-mktag.sh @@ -0,0 +1,227 @@ +#!/bin/sh +# +# + +test_description='git-mktag: tag object verify test' + +. ./test-lib.sh + +########################################################### +# check the tag.sig file, expecting verify_tag() to fail, +# and checking that the error message matches the pattern +# given in the expect.pat file. + +check_verify_failure () { + test_expect_success \ + "$1" \ + 'git-mktag <tag.sig 2>message || + egrep -q -f expect.pat message' +} + +########################################################### +# first create a commit, so we have a valid object/type +# for the tag. +echo Hello >A +git-update-index --add A +git-commit -m "Initial commit" +head=$(git-rev-parse --verify HEAD) + +############################################################ +# 1. length check + +cat >tag.sig <<EOF +too short for a tag +EOF + +cat >expect.pat <<EOF +^error: .*size wrong.*$ +EOF + +check_verify_failure 'Tag object length check' + +############################################################ +# 2. object line label check + +cat >tag.sig <<EOF +xxxxxx 139e9b33986b1c2670fff52c5067603117b3e895 +type tag +tag mytag +EOF + +cat >expect.pat <<EOF +^error: char0: .*"object "$ +EOF + +check_verify_failure '"object" line label check' + +############################################################ +# 3. object line SHA1 check + +cat >tag.sig <<EOF +object zz9e9b33986b1c2670fff52c5067603117b3e895 +type tag +tag mytag +EOF + +cat >expect.pat <<EOF +^error: char7: .*SHA1 hash$ +EOF + +check_verify_failure '"object" line SHA1 check' + +############################################################ +# 4. type line label check + +cat >tag.sig <<EOF +object 779e9b33986b1c2670fff52c5067603117b3e895 +xxxx tag +tag mytag +EOF + +cat >expect.pat <<EOF +^error: char47: .*"[\]ntype "$ +EOF + +check_verify_failure '"type" line label check' + +############################################################ +# 5. type line eol check + +echo "object 779e9b33986b1c2670fff52c5067603117b3e895" >tag.sig +echo -n "type tagsssssssssssssssssssssssssssssss" >>tag.sig + +cat >expect.pat <<EOF +^error: char48: .*"[\]n"$ +EOF + +check_verify_failure '"type" line eol check' + +############################################################ +# 6. tag line label check #1 + +cat >tag.sig <<EOF +object 779e9b33986b1c2670fff52c5067603117b3e895 +type tag +xxx mytag +EOF + +cat >expect.pat <<EOF +^error: char57: no "tag " found$ +EOF + +check_verify_failure '"tag" line label check #1' + +############################################################ +# 7. tag line label check #2 + +cat >tag.sig <<EOF +object 779e9b33986b1c2670fff52c5067603117b3e895 +type taggggggggggggggggggggggggggggggg +tag +EOF + +cat >expect.pat <<EOF +^error: char87: no "tag " found$ +EOF + +check_verify_failure '"tag" line label check #2' + +############################################################ +# 8. type line type-name length check + +cat >tag.sig <<EOF +object 779e9b33986b1c2670fff52c5067603117b3e895 +type taggggggggggggggggggggggggggggggg +tag mytag +EOF + +cat >expect.pat <<EOF +^error: char53: type too long$ +EOF + +check_verify_failure '"type" line type-name length check' + +############################################################ +# 9. verify object (SHA1/type) check + +cat >tag.sig <<EOF +object 779e9b33986b1c2670fff52c5067603117b3e895 +type tagggg +tag mytag +EOF + +cat >expect.pat <<EOF +^error: char7: could not verify object.*$ +EOF + +check_verify_failure 'verify object (SHA1/type) check' + +############################################################ +# 10. verify tag-name check + +cat >tag.sig <<EOF +object $head +type commit +tag my tag +EOF + +cat >expect.pat <<EOF +^error: char67: could not verify tag name$ +EOF + +check_verify_failure 'verify tag-name check' + +############################################################ +# 11. tagger line lable check #1 + +cat >tag.sig <<EOF +object $head +type commit +tag mytag +EOF + +cat >expect.pat <<EOF +^error: char70: could not find "tagger"$ +EOF + +check_verify_failure '"tagger" line label check #1' + +############################################################ +# 12. tagger line lable check #2 + +cat >tag.sig <<EOF +object $head +type commit +tag mytag +tagger +EOF + +cat >expect.pat <<EOF +^error: char70: could not find "tagger"$ +EOF + +check_verify_failure '"tagger" line label check #2' + +############################################################ +# 13. create valid tag + +cat >tag.sig <<EOF +object $head +type commit +tag mytag +tagger another@example.com +EOF + +test_expect_success \ + 'create valid tag' \ + 'git-mktag <tag.sig >.git/refs/tags/mytag 2>message' + +############################################################ +# 14. check mytag + +test_expect_success \ + 'check mytag' \ + 'git-tag -l | grep mytag' + + +test_done diff --git a/t/t4013-diff-various.sh b/t/t4013-diff-various.sh index b24c829f0f..71c454356f 100755 --- a/t/t4013-diff-various.sh +++ b/t/t4013-diff-various.sh @@ -88,7 +88,7 @@ test_expect_success setup ' +*+ [initial] Initial EOF -V=`git version | sed -e 's/^git version //'` +V=`git version | sed -e 's/^git version //' -e 's/\./\\./g'` while read cmd do case "$cmd" in @@ -103,7 +103,9 @@ do test_expect_success "git $cmd" ' { echo "\$ git $cmd" - git $cmd | sed -e "s/$V/g-i-t--v-e-r-s-i-o-n/" + git $cmd | + sed -e "s/^\\(-*\\)$V\\(-*\\)\$/\\1g-i-t--v-e-r-s-i-o-n\2/" \ + -e "s/^\\( *boundary=\"-*\\)$V\\(-*\\)\"\$/\\1g-i-t--v-e-r-s-i-o-n\2\"/" echo "\$" } >"$actual" && if test -f "$expect" diff --git a/t/t4102-apply-rename.sh b/t/t4102-apply-rename.sh index fbb508d389..22da6a00cc 100755 --- a/t/t4102-apply-rename.sh +++ b/t/t4102-apply-rename.sh @@ -13,8 +13,8 @@ test_description='git-apply handling copy/rename patch. cat >test-patch <<\EOF diff --git a/foo b/bar similarity index 47% -copy from foo -copy to bar +rename from foo +rename to bar --- a/foo +++ b/bar @@ -1 +1 @@ @@ -39,4 +39,24 @@ else 'test -f bar && ls -l bar | grep "^-..x......"' fi +test_expect_success 'apply reverse' \ + 'git-apply -R --index --stat --summary --apply test-patch && + test "$(cat foo)" = "This is foo"' + +cat >test-patch <<\EOF +diff --git a/foo b/bar +similarity index 47% +copy from foo +copy to bar +--- a/foo ++++ b/bar +@@ -1 +1 @@ +-This is foo ++This is bar +EOF + +test_expect_success 'apply copy' \ + 'git-apply --index --stat --summary --apply test-patch && + test "$(cat bar)" = "This is bar" -a "$(cat foo)" = "This is foo"' + test_done diff --git a/t/t4103-apply-binary.sh b/t/t4103-apply-binary.sh index 00bd8b15c6..ff052699a2 100755 --- a/t/t4103-apply-binary.sh +++ b/t/t4103-apply-binary.sh @@ -35,8 +35,8 @@ git-commit -m 'Second Version' git-diff-tree -p master binary >B.diff git-diff-tree -p -C master binary >C.diff -git-diff-tree -p --full-index master binary >BF.diff -git-diff-tree -p --full-index -C master binary >CF.diff +git-diff-tree -p --binary master binary >BF.diff +git-diff-tree -p --binary -C master binary >CF.diff test_expect_success 'stat binary diff -- should not fail.' \ 'git-checkout master diff --git a/t/t4112-apply-renames.sh b/t/t4112-apply-renames.sh index a06f6956d5..69e9603c78 100755 --- a/t/t4112-apply-renames.sh +++ b/t/t4112-apply-renames.sh @@ -11,31 +11,7 @@ test_description='git-apply should not get confused with rename/copy. # setup -mkdir -p include/arch/x86_64/klibc klibc/arch/x86_64/include/klibc - -cat >include/arch/x86_64/klibc/archsetjmp.h <<\EOF -/* - * arch/x86_64/include/klibc/archsetjmp.h - */ - -#ifndef _KLIBC_ARCHSETJMP_H -#define _KLIBC_ARCHSETJMP_H - -struct __jmp_buf { - unsigned long __rbx; - unsigned long __rsp; - unsigned long __rbp; - unsigned long __r12; - unsigned long __r13; - unsigned long __r14; - unsigned long __r15; - unsigned long __rip; -}; - -typedef struct __jmp_buf jmp_buf[1]; - -#endif /* _SETJMP_H */ -EOF +mkdir -p klibc/arch/x86_64/include/klibc cat >klibc/arch/x86_64/include/klibc/archsetjmp.h <<\EOF /* @@ -139,7 +115,7 @@ rename to include/arch/m32r/klibc/archsetjmp.h +#endif /* _KLIBC_ARCHSETJMP_H */ EOF -find include klibc -type f -print | xargs git-update-index --add -- +find klibc -type f -print | xargs git-update-index --add -- test_expect_success 'check rename/copy patch' 'git-apply --check patch' diff --git a/t/t4115-apply-symlink.sh b/t/t4115-apply-symlink.sh new file mode 100755 index 0000000000..d5f2cfb186 --- /dev/null +++ b/t/t4115-apply-symlink.sh @@ -0,0 +1,49 @@ +#!/bin/sh +# +# Copyright (c) 2005 Junio C Hamano +# + +test_description='git-apply symlinks and partial files + +' + +. ./test-lib.sh + +test_expect_success setup ' + + ln -s path1/path2/path3/path4/path5 link1 && + git add link? && + git commit -m initial && + + git branch side && + + rm -f link? && + + ln -s htap6 link1 && + git update-index link? && + git commit -m second && + + git diff-tree -p HEAD^ HEAD >patch && + git apply --stat --summary patch + +' + +test_expect_success 'apply symlink patch' ' + + git checkout side && + git apply patch && + git diff-files -p >patched && + diff -u patch patched + +' + +test_expect_success 'apply --index symlink patch' ' + + git checkout -f side && + git apply --index patch && + git diff-index --cached -p HEAD >patched && + diff -u patch patched + +' + +test_done diff --git a/t/t7001-mv.sh b/t/t7001-mv.sh index 811a4797a5..900ca93cde 100755 --- a/t/t7001-mv.sh +++ b/t/t7001-mv.sh @@ -38,4 +38,44 @@ test_expect_success \ 'git-diff-tree -r -M --name-status HEAD^ HEAD | \ grep -E "^R100.+path1/COPYING.+path0/COPYING"' +test_expect_success \ + 'adding another file' \ + 'cp ../../README path0/README && + git-add path0/README && + git-commit -m add2 -a' + +test_expect_success \ + 'moving whole subdirectory' \ + 'git-mv path0 path2' + +test_expect_success \ + 'commiting the change' \ + 'git-commit -m dir-move -a' + +test_expect_success \ + 'checking the commit' \ + 'git-diff-tree -r -M --name-status HEAD^ HEAD | \ + grep -E "^R100.+path0/COPYING.+path2/COPYING" && + git-diff-tree -r -M --name-status HEAD^ HEAD | \ + grep -E "^R100.+path0/README.+path2/README"' + +test_expect_success \ + 'moving whole subdirectory into subdirectory' \ + 'git-mv path2 path1' + +test_expect_success \ + 'commiting the change' \ + 'git-commit -m dir-move -a' + +test_expect_success \ + 'checking the commit' \ + 'git-diff-tree -r -M --name-status HEAD^ HEAD | \ + grep -E "^R100.+path2/COPYING.+path1/path2/COPYING" && + git-diff-tree -r -M --name-status HEAD^ HEAD | \ + grep -E "^R100.+path2/README.+path1/path2/README"' + +test_expect_failure \ + 'do not move directory over existing directory' \ + 'mkdir path0 && mkdir path0/path2 && git-mv path2 path0' + test_done diff --git a/t/t7002-grep.sh b/t/t7002-grep.sh new file mode 100755 index 0000000000..6bfb899ed1 --- /dev/null +++ b/t/t7002-grep.sh @@ -0,0 +1,112 @@ +#!/bin/sh +# +# Copyright (c) 2006 Junio C Hamano +# + +test_description='git grep various. +' + +. ./test-lib.sh + +test_expect_success setup ' + { + echo foo mmap bar + echo foo_mmap bar + echo foo_mmap bar mmap + echo foo mmap bar_mmap + echo foo_mmap bar mmap baz + } >file && + echo x x xx x >x && + echo y yy >y && + echo zzz > z && + mkdir t && + echo test >t/t && + git add file x y z t/t && + git commit -m initial +' + +for H in HEAD '' +do + case "$H" in + HEAD) HC='HEAD:' L='HEAD' ;; + '') HC= L='in working tree' ;; + esac + + test_expect_success "grep -w $L" ' + { + echo ${HC}file:1:foo mmap bar + echo ${HC}file:3:foo_mmap bar mmap + echo ${HC}file:4:foo mmap bar_mmap + echo ${HC}file:5:foo_mmap bar mmap baz + } >expected && + git grep -n -w -e mmap $H >actual && + diff expected actual + ' + + test_expect_success "grep -w $L (x)" ' + { + echo ${HC}x:1:x x xx x + } >expected && + git grep -n -w -e "x xx* x" $H >actual && + diff expected actual + ' + + test_expect_success "grep -w $L (y-1)" ' + { + echo ${HC}y:1:y yy + } >expected && + git grep -n -w -e "^y" $H >actual && + diff expected actual + ' + + test_expect_success "grep -w $L (y-2)" ' + : >expected && + if git grep -n -w -e "^y y" $H >actual + then + echo should not have matched + cat actual + false + else + diff expected actual + fi + ' + + test_expect_success "grep -w $L (z)" ' + : >expected && + if git grep -n -w -e "^z" $H >actual + then + echo should not have matched + cat actual + false + else + 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/t9100-git-svn-basic.sh b/t/t9100-git-svn-basic.sh index bf1d6381d9..34a3ccd31c 100755 --- a/t/t9100-git-svn-basic.sh +++ b/t/t9100-git-svn-basic.sh @@ -170,7 +170,7 @@ then test -L $SVN_TREE/exec-2.sh" name='modify a symlink to become a file' - git help > help || true + echo git help > help || true rm exec-2.sh cp help exec-2.sh git update-index exec-2.sh @@ -217,10 +217,10 @@ name='check imported tree checksums expected tree checksums' rm -f expected if test "$have_utf8" = t then - echo tree f735671b89a7eb30cab1d8597de35bd4271ab813 > expected + echo tree bf522353586b1b883488f2bc73dab0d9f774b9a9 > expected fi cat >> expected <<\EOF -tree 4b9af72bb861eaed053854ec502cf7df72618f0f +tree 83654bb36f019ae4fe77a0171f81075972087624 tree 031b8d557afc6fea52894eaebb45bec52f1ba6d1 tree 0b094cbff17168f24c302e297f55bfac65eb8bd3 tree d667270a1f7b109f5eb3aaea21ede14b56bfdd6e @@ -231,4 +231,3 @@ EOF test_expect_success "$name" "diff -u a expected" test_done - diff --git a/templates/Makefile b/templates/Makefile index 8f7f4fec34..9e1ae1a4e0 100644 --- a/templates/Makefile +++ b/templates/Makefile @@ -6,11 +6,9 @@ prefix ?= $(HOME) template_dir ?= $(prefix)/share/git-core/templates/ # DESTDIR= -# Shell quote; -# Result of this needs to be placed inside '' -shq = $(subst ','\'',$(1)) -# This has surrounding '' -shellquote = '$(call shq,$(1))' +# Shell quote (do not use $(call) to accomodate ancient setups); +DESTDIR_SQ = $(subst ','\'',$(DESTDIR)) +template_dir_SQ = $(subst ','\'',$(template_dir)) all: boilerplates.made custom @@ -43,6 +41,6 @@ clean: rm -rf blt boilerplates.made install: all - $(INSTALL) -d -m755 $(call shellquote,$(DESTDIR)$(template_dir)) + $(INSTALL) -d -m755 '$(DESTDIR_SQ)$(template_dir_SQ)' (cd blt && $(TAR) cf - .) | \ - (cd $(call shellquote,$(DESTDIR)$(template_dir)) && $(TAR) xf -) + (cd '$(DESTDIR_SQ)$(template_dir_SQ)' && $(TAR) xf -) diff --git a/unpack-trees.c b/unpack-trees.c new file mode 100644 index 0000000000..a20639be70 --- /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; + + memcpy(ce->sha1, posns[i]->sha1, 20); + 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 int progress_update = 0; + +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 && + !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 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/verify-pack.c b/verify-pack.c deleted file mode 100644 index c99db9dd79..0000000000 --- a/verify-pack.c +++ /dev/null @@ -1,57 +0,0 @@ -#include "cache.h" -#include "pack.h" - -static int verify_one_pack(char *arg, int verbose) -{ - int len = strlen(arg); - struct packed_git *g; - - while (1) { - /* Should name foo.idx, but foo.pack may be named; - * convert it to foo.idx - */ - if (!strcmp(arg + len - 5, ".pack")) { - strcpy(arg + len - 5, ".idx"); - len--; - } - /* Should name foo.idx now */ - if ((g = add_packed_git(arg, len, 1))) - break; - /* No? did you name just foo? */ - strcpy(arg + len, ".idx"); - len += 4; - if ((g = add_packed_git(arg, len, 1))) - break; - return error("packfile %s not found.", arg); - } - return verify_pack(g, verbose); -} - -static const char verify_pack_usage[] = "git-verify-pack [-v] <pack>..."; - -int main(int ac, char **av) -{ - int errs = 0; - int verbose = 0; - int no_more_options = 0; - - while (1 < ac) { - char path[PATH_MAX]; - - if (!no_more_options && av[1][0] == '-') { - if (!strcmp("-v", av[1])) - verbose = 1; - else if (!strcmp("--", av[1])) - no_more_options = 1; - else - usage(verify_pack_usage); - } - else { - strcpy(path, av[1]); - if (verify_one_pack(path, verbose)) - errs++; - } - ac--; av++; - } - return !!errs; -} |