diff options
127 files changed, 4799 insertions, 2865 deletions
diff --git a/.gitignore b/.gitignore index 2299e36ba..3da0e5e80 100644 --- a/.gitignore +++ b/.gitignore @@ -126,6 +126,7 @@ git-verify-tag git-whatchanged git-write-tree git-core-*/?* +gitweb/gitweb.cgi test-date test-delta test-dump-cache-tree @@ -141,7 +142,7 @@ config.mak autom4te.cache config.log config.status -config.mak.in config.mak.autogen +config.mak.append configure git-blame diff --git a/Documentation/diff-options.txt b/Documentation/diff-options.txt index 47ba9a403..b5d976359 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-apply.txt b/Documentation/git-apply.txt index 2ff74949a..c76cfffdc 100644 --- a/Documentation/git-apply.txt +++ b/Documentation/git-apply.txt @@ -10,9 +10,10 @@ SYNOPSIS -------- [verse] 'git-apply' [--stat] [--numstat] [--summary] [--check] [--index] [--apply] - [--no-add] [--index-info] [--allow-binary-replacement] [-z] [-pNUM] - [-CNUM] [--whitespace=<nowarn|warn|error|error-all|strip>] - [<patch>...] + [--no-add] [--index-info] [--allow-binary-replacement | --binary] + [-R | --reverse] [--reject] [-z] [-pNUM] [-CNUM] [--inaccurate-eof] + [--whitespace=<nowarn|warn|error|error-all|strip>] [--exclude=PATH] + [--cached] [--verbose] [<patch>...] DESCRIPTION ----------- @@ -55,6 +56,11 @@ OPTIONS up-to-date, it is flagged as an error. This flag also causes the index file to be updated. +--cached:: + Apply a patch without touching the working tree. Instead, take the + cached data, apply the patch, and store the result in the index, + without using the working tree. This implies '--index'. + --index-info:: Newer git-diff output has embedded 'index information' for each blob to help identify the original version that @@ -62,6 +68,16 @@ OPTIONS the original version of the blob is available locally, outputs information about them to the standard output. +-R, --reverse:: + Apply the patch in reverse. + +--reject:: + For atomicity, gitlink:git-apply[1] by default fails the whole patch and + does not touch the working tree when some of the hunks + do not apply. This option makes it apply + the parts of the patch that are applicable, and leave the + rejected hunks in corresponding *.rej files. + -z:: When showing the index information, do not munge paths, but use NUL terminated machine readable format. Without @@ -80,8 +96,8 @@ OPTIONS ever ignored. --apply:: - If you use any of the options marked ``Turns off - "apply"'' above, git-apply reads and outputs the + If you use any of the options marked "Turns off + 'apply'" above, gitlink:git-apply[1] reads and outputs the information you asked without actually applying the patch. Give this flag after those flags to also apply the patch. @@ -93,7 +109,7 @@ OPTIONS the result with this option, which would apply the deletion part but not addition part. ---allow-binary-replacement:: +--allow-binary-replacement, --binary:: When applying a patch, which is a git-enhanced patch that was prepared to record the pre- and post-image object name in full, and the path being patched exactly matches @@ -104,13 +120,18 @@ OPTIONS result. This allows binary files to be patched in a very limited way. +--exclude=<path-pattern>:: + Don't apply changes to files matching the given path pattern. This can + be useful when importing patchsets, where you want to exclude certain + files or directories. + --whitespace=<option>:: When applying a patch, detect a new or modified line that ends with trailing whitespaces (this includes a line that solely consists of whitespaces). By default, the command outputs warning messages and applies the patch. - When `git-apply` is used for statistics and not applying a + When gitlink:git-apply[1] is used for statistics and not applying a patch, it defaults to `nowarn`. You can use different `<option>` to control this behavior: @@ -124,6 +145,17 @@ OPTIONS * `strip` outputs warnings for a few such errors, strips out the trailing whitespaces and applies the patch. +--inacurate-eof:: + Under certain circumstances, some versions of diff do not correctly + detect a missing new-line at the end of the file. As a result, patches + created by such diff programs do not record incomplete lines + correctly. This option adds support for applying such patches by + working around this bug. + +--verbose:: + Report progress to stderr. By default, only a message about the + current patch being applied will be printed. This option will cause + additional information to be reported. Configuration ------------- diff --git a/Documentation/git-blame.txt b/Documentation/git-blame.txt index bfed94591..e1f89444a 100644 --- a/Documentation/git-blame.txt +++ b/Documentation/git-blame.txt @@ -3,21 +3,38 @@ git-blame(1) NAME ---- -git-blame - Blame file lines on commits +git-blame - Show what revision and author last modified each line of a file SYNOPSIS -------- -git-blame file [options] file [revision] +'git-blame' [-c] [-l] [-t] [-S <revs-file>] [--] <file> [<rev>] DESCRIPTION ----------- -Annotates each line in the given file with information from the commit -which introduced the line. Start annotation from the given revision. + +Annotates each line in the given file with information from the revision which +last modified the line. Optionally, start annotating from the given revision. + +This report doesn't tell you anything about lines which have been deleted or +replaced; you need to use a tool such as gitlink:git-diff[1] or the "pickaxe" +interface briefly mentioned in the following paragraph. + +Apart from supporting file annotation, git also supports searching the +development history for when a code snippet occured in a change. This makes it +possible to track when a code snippet was added to a file, moved or copied +between files, and eventually deleted or replaced. It works by searching for +a text string in the diff. A small example: + +----------------------------------------------------------------------------- +$ git log --pretty=oneline -S'blame_usage' +5040f17eba15504bad66b14a645bddd9b015ebb7 blame -S <ancestry-file> +ea4c7f9bf69e781dd0cd88d2bccb2bf5cc15c9a7 git-blame: Make the output +----------------------------------------------------------------------------- OPTIONS ------- -c, --compatibility:: - Use the same output mode as git-annotate (Default: off). + Use the same output mode as gitlink:git-annotate[1] (Default: off). -l, --long:: Show long rev (Default: off). @@ -26,7 +43,7 @@ OPTIONS Show raw timestamp (Default: off). -S, --rev-file <revs-file>:: - Use revs from revs-file instead of calling git-rev-list. + Use revs from revs-file instead of calling gitlink:git-rev-list[1]. -h, --help:: Show help message. diff --git a/Documentation/git-daemon.txt b/Documentation/git-daemon.txt index 0f7d274ea..17619a3f5 100644 --- a/Documentation/git-daemon.txt +++ b/Documentation/git-daemon.txt @@ -11,7 +11,8 @@ 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] - [--reuseaddr] [--detach] [--pid-file=file] [directory...] + [--reuseaddr] [--detach] [--pid-file=file] + [--user=user [--group=group]] [directory...] DESCRIPTION ----------- @@ -93,6 +94,17 @@ OPTIONS --pid-file=file:: Save the process id in 'file'. +--user=user, --group=group:: + Change daemon's uid and gid before entering the service loop. + When only `--user` is given without `--group`, the + primary group ID for the user is used. The values of + the option are given to `getpwnam(3)` and `getgrnam(3)` + and numeric IDs are not supported. ++ +Giving these options is an error when used with `--inetd`; use +the facility of inet daemon to achieve the same before spawning +`git-daemon` if needed. + <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 dc7683383..7545dd9a3 100644 --- a/Documentation/git-grep.txt +++ b/Documentation/git-grep.txt @@ -11,7 +11,7 @@ SYNOPSIS [verse] 'git-grep' [--cached] [-a | --text] [-I] [-i | --ignore-case] [-w | --word-regexp] - [-v | --invert-match] + [-v | --invert-match] [--full-name] [-E | --extended-regexp] [-G | --basic-regexp] [-F | --fixed-strings] [-n] [-l | --files-with-matches] [-L | --files-without-match] [-c | --count] @@ -47,6 +47,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. diff --git a/Documentation/git-ls-remote.txt b/Documentation/git-ls-remote.txt index ae4c1a250..c8a4c5a4d 100644 --- a/Documentation/git-ls-remote.txt +++ b/Documentation/git-ls-remote.txt @@ -3,16 +3,19 @@ git-ls-remote(1) NAME ---- -git-ls-remote - Look at references other repository has +git-ls-remote - List references in a remote repository SYNOPSIS -------- -'git-ls-remote' [--heads] [--tags] <repository> <refs>... +[verse] +'git-ls-remote' [--heads] [--tags] [-u <exec> | --upload-pack <exec>] + <repository> <refs>... DESCRIPTION ----------- -Displays the references other repository has. +Displays references available in a remote repository along with the associated +commit IDs. OPTIONS @@ -23,9 +26,16 @@ OPTIONS both, references stored in refs/heads and refs/tags are displayed. +-u <exec>, --upload-pack=<exec>:: + Specify the full path of gitlink:git-upload-pack[1] on the remote + host. This allows listing references from repositories accessed via + SSH and where the SSH deamon does not use the PATH configured by the + user. Also see the '--exec' option for gitlink:git-peek-remote[1]. + <repository>:: Location of the repository. The shorthand defined in - $GIT_DIR/branches/ can be used. + $GIT_DIR/branches/ can be used. Use "." (dot) to list references in + the local repository. <refs>...:: When unspecified, all references, after filtering done diff --git a/Documentation/git-svn.txt b/Documentation/git-svn.txt index 7d8680984..b7b63f713 100644 --- a/Documentation/git-svn.txt +++ b/Documentation/git-svn.txt @@ -12,10 +12,8 @@ SYNOPSIS DESCRIPTION ----------- git-svn is a simple conduit for changesets between a single Subversion -branch and git. - -git-svn is not to be confused with git-svnimport. The were designed -with very different goals in mind. +branch and git. It is not to be confused with gitlink:git-svnimport[1]. +They were designed with very different goals in mind. git-svn is designed for an individual developer who wants a bidirectional flow of changesets between a single branch in Subversion @@ -34,26 +32,38 @@ git-svnimport is designed for. COMMANDS -------- -init:: +-- + +'init':: Creates an empty git repository with additional metadata directories for git-svn. The Subversion URL must be specified as a command-line argument. -fetch:: - Fetch unfetched revisions from the Subversion URL we are - tracking. refs/remotes/git-svn will be updated to the - latest revision. +'fetch':: + +Fetch unfetched revisions from the Subversion URL we are +tracking. refs/remotes/git-svn will be updated to the +latest revision. + +Note: You should never attempt to modify the remotes/git-svn +branch outside of git-svn. Instead, create a branch from +remotes/git-svn and work on that branch. Use the 'commit' +command (see below) to write git commits back to +remotes/git-svn. - Note: You should never attempt to modify the remotes/git-svn - branch outside of git-svn. Instead, create a branch from - remotes/git-svn and work on that branch. Use the 'commit' - command (see below) to write git commits back to - remotes/git-svn. +See '<<fetch-args,Additional Fetch Arguments>>' if you are interested in +manually joining branches on commit. - See 'Additional Fetch Arguments' if you are interested in - manually joining branches on commit. +'dcommit':: + Commit all diffs from the current HEAD directly to the SVN + repository, and then rebase or reset (depending on whether or + not there is a diff between SVN and HEAD). It is recommended + that you run git-svn fetch and rebase (not pull) your commits + against the latest changes in the SVN repository. + This is advantageous over 'commit' (below) because it produces + cleaner, more linear history. -commit:: +'commit':: Commit specified commit or tree objects to SVN. This relies on your imported fetch data being up-to-date. This makes absolutely no attempts to do patching when committing to SVN, it @@ -61,9 +71,9 @@ commit:: commit. All merging is assumed to have taken place independently of git-svn functions. -rebuild:: +'rebuild':: Not a part of daily usage, but this is a useful command if - you've just cloned a repository (using git-clone) that was + you've just cloned a repository (using gitlink:git-clone[1]) that was tracked with git-svn. Unfortunately, git-clone does not clone git-svn metadata and the svn working tree that git-svn uses for its operations. This rebuilds the metadata so git-svn can @@ -71,130 +81,170 @@ rebuild:: specified at the command-line if the directory/repository you're tracking has moved or changed protocols. -show-ignore:: +'show-ignore':: Recursively finds and lists the svn:ignore property on directories. The output is suitable for appending to the $GIT_DIR/info/exclude file. +-- + OPTIONS ------- +-- + -r <ARG>:: --revision <ARG>:: - Only used with the 'fetch' command. - Takes any valid -r<argument> svn would accept and passes it - directly to svn. -r<ARG1>:<ARG2> ranges and "{" DATE "}" syntax - is also supported. This is passed directly to svn, see svn - documentation for more details. +Only used with the 'fetch' command. + +Takes any valid -r<argument> svn would accept and passes it +directly to svn. -r<ARG1>:<ARG2> ranges and "{" DATE "}" syntax +is also supported. This is passed directly to svn, see svn +documentation for more details. - This can allow you to make partial mirrors when running fetch. +This can allow you to make partial mirrors when running fetch. -:: --stdin:: - Only used with the 'commit' command. - Read a list of commits from stdin and commit them in reverse - order. Only the leading sha1 is read from each line, so - git-rev-list --pretty=oneline output can be used. +Only used with the 'commit' command. + +Read a list of commits from stdin and commit them in reverse +order. Only the leading sha1 is read from each line, so +git-rev-list --pretty=oneline output can be used. --rmdir:: - Only used with the 'commit' command. - Remove directories from the SVN tree if there are no files left - behind. SVN can version empty directories, and they are not - removed by default if there are no files left in them. git - cannot version empty directories. Enabling this flag will make - the commit to SVN act like git. +Only used with the 'commit' command. + +Remove directories from the SVN tree if there are no files left +behind. SVN can version empty directories, and they are not +removed by default if there are no files left in them. git +cannot version empty directories. Enabling this flag will make +the commit to SVN act like git. - repo-config key: svn.rmdir +repo-config key: svn.rmdir -e:: --edit:: - Only used with the 'commit' command. - Edit the commit message before committing to SVN. This is off by - default for objects that are commits, and forced on when committing - tree objects. +Only used with the 'commit' command. - repo-config key: svn.edit +Edit the commit message before committing to SVN. This is off by +default for objects that are commits, and forced on when committing +tree objects. + +repo-config key: svn.edit -l<num>:: --find-copies-harder:: - Both of these are only used with the 'commit' command. - They are both passed directly to git-diff-tree see - git-diff-tree(1) for more information. +Both of these are only used with the 'commit' command. + +They are both passed directly to git-diff-tree see +gitlink:git-diff-tree[1] for more information. - repo-config key: svn.l - repo-config key: svn.findcopiesharder +[verse] +repo-config key: svn.l +repo-config key: svn.findcopiesharder -A<filename>:: --authors-file=<filename>:: - Syntax is compatible with the files used by git-svnimport and - git-cvsimport: +Syntax is compatible with the files used by git-svnimport and +git-cvsimport: ------------------------------------------------------------------------ -loginname = Joe User <user@example.com> + loginname = Joe User <user@example.com> ------------------------------------------------------------------------ - If this option is specified and git-svn encounters an SVN - committer name that does not exist in the authors-file, git-svn - will abort operation. The user will then have to add the - appropriate entry. Re-running the previous git-svn command - after the authors-file is modified should continue operation. +If this option is specified and git-svn encounters an SVN +committer name that does not exist in the authors-file, git-svn +will abort operation. The user will then have to add the +appropriate entry. Re-running the previous git-svn command +after the authors-file is modified should continue operation. + +repo-config key: svn.authors-file + +-m:: +--merge:: +-s<strategy>:: +--strategy=<strategy>:: + +These are only used with the 'dcommit' command. - repo-config key: svn.authors-file +Passed directly to git-rebase when using 'dcommit' if a +'git-reset' cannot be used (see dcommit). + +-n:: +--dry-run:: + +This is only used with the 'dcommit' command. + +Print out the series of git arguments that would show +which diffs would be committed to SVN. + +-- ADVANCED OPTIONS ---------------- +-- + -b<refname>:: --branch <refname>:: - Used with 'fetch' or 'commit'. +Used with 'fetch' or 'commit'. - This can be used to join arbitrary git branches to remotes/git-svn - on new commits where the tree object is equivalent. +This can be used to join arbitrary git branches to remotes/git-svn +on new commits where the tree object is equivalent. - When used with different GIT_SVN_ID values, tags and branches in - SVN can be tracked this way, as can some merges where the heads - end up having completely equivalent content. This can even be - used to track branches across multiple SVN _repositories_. +When used with different GIT_SVN_ID values, tags and branches in +SVN can be tracked this way, as can some merges where the heads +end up having completely equivalent content. This can even be +used to track branches across multiple SVN _repositories_. - This option may be specified multiple times, once for each - branch. +This option may be specified multiple times, once for each +branch. - repo-config key: svn.branch +repo-config key: svn.branch -i<GIT_SVN_ID>:: --id <GIT_SVN_ID>:: - This sets GIT_SVN_ID (instead of using the environment). See - the section on "Tracking Multiple Repositories or Branches" for - more information on using GIT_SVN_ID. + +This sets GIT_SVN_ID (instead of using the environment). See the +section on +'<<tracking-multiple-repos,Tracking Multiple Repositories or Branches>>' +for more information on using GIT_SVN_ID. + +-- COMPATIBILITY OPTIONS --------------------- +-- + --upgrade:: - Only used with the 'rebuild' command. +Only used with the 'rebuild' command. - Run this if you used an old version of git-svn that used - "git-svn-HEAD" instead of "remotes/git-svn" as the branch - for tracking the remote. +Run this if you used an old version of git-svn that used +"git-svn-HEAD" instead of "remotes/git-svn" as the branch +for tracking the remote. --no-ignore-externals:: - Only used with the 'fetch' and 'rebuild' command. +Only used with the 'fetch' and 'rebuild' command. + +By default, git-svn passes --ignore-externals to svn to avoid +fetching svn:external trees into git. Pass this flag to enable +externals tracking directly via git. - By default, git-svn passes --ignore-externals to svn to avoid - fetching svn:external trees into git. Pass this flag to enable - externals tracking directly via git. +Versions of svn that do not support --ignore-externals are +automatically detected and this flag will be automatically +enabled for them. - Versions of svn that do not support --ignore-externals are - automatically detected and this flag will be automatically - enabled for them. +Otherwise, do not enable this flag unless you know what you're +doing. - Otherwise, do not enable this flag unless you know what you're - doing. +repo-config key: svn.noignoreexternals - repo-config key: svn.noignoreexternals +-- Basic Examples ~~~~~~~~~~~~~~ @@ -212,12 +262,26 @@ Tracking and contributing to an Subversion managed-project: git-svn commit <tree-ish> [<tree-ish_2> ...] # Commit all the git commits from my-branch that don't exist in SVN: git-svn commit remotes/git-svn..my-branch -# Something is committed to SVN, pull the latest into your branch: - git-svn fetch && git pull . remotes/git-svn +# Something is committed to SVN, rebase the latest into your branch: + git-svn fetch && git rebase remotes/git-svn # Append svn:ignore settings to the default git exclude file: git-svn show-ignore >> .git/info/exclude ------------------------------------------------------------------------ +REBASE VS. PULL +--------------- + +Originally, git-svn recommended that the remotes/git-svn branch be +pulled from. This is because the author favored 'git-svn commit B' +to commit a single head rather than the 'git-svn commit A..B' notation +to commit multiple commits. + +If you use 'git-svn commit A..B' to commit several diffs and you do not +have the latest remotes/git-svn merged into my-branch, you should use +'git rebase' to update your work branch instead of 'git pull'. 'pull' +can cause non-linear history to be flattened when committing into SVN, +which can lead to merge commits reversing previous commits in SVN. + DESIGN PHILOSOPHY ----------------- Merge tracking in Subversion is lacking and doing branched development @@ -226,6 +290,7 @@ any automated merge/branch tracking on the Subversion side and leaves it entirely up to the user on the git side. It's simply not worth it to do a useful translation when the original signal is weak. +[[tracking-multiple-repos]] TRACKING MULTIPLE REPOSITORIES OR BRANCHES ------------------------------------------ This is for advanced users, most users should ignore this section. @@ -241,6 +306,7 @@ invocation. The interface branch will be remotes/$GIT_SVN_ID, instead of remotes/git-svn. Any remotes/$GIT_SVN_ID branch should never be modified by the user outside of git-svn commands. +[[fetch-args]] ADDITIONAL FETCH ARGUMENTS -------------------------- This is for advanced users, most users should ignore this section. @@ -251,11 +317,15 @@ optionally be specified in the form of sha1 hex sums at the command-line. Unfetched SVN revisions may also be tied to particular git commits with the following syntax: +------------------------------------------------ svn_revision_number=git_commit_sha1 +------------------------------------------------ -This allows you to tie unfetched SVN revision 375 to your current HEAD:: +This allows you to tie unfetched SVN revision 375 to your current HEAD: - `git-svn fetch 375=$(git-rev-parse HEAD)` +------------------------------------------------ + git-svn fetch 375=$(git-rev-parse HEAD) +------------------------------------------------ Advanced Example: Tracking a Reorganized Repository ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -310,6 +380,10 @@ the possible corner cases (git doesn't do it, either). Renamed and copied files are fully supported if they're similar enough for git to detect them. +SEE ALSO +-------- +gitlink:git-rebase[1] + Author ------ Written by Eric Wong <normalperson@yhbt.net>. diff --git a/Documentation/git-update-index.txt b/Documentation/git-update-index.txt index 3ae6e7457..41bb7e12e 100644 --- a/Documentation/git-update-index.txt +++ b/Documentation/git-update-index.txt @@ -15,7 +15,7 @@ SYNOPSIS [--cacheinfo <mode> <object> <file>]\* [--chmod=(+|-)x] [--assume-unchanged | --no-assume-unchanged] - [--really-refresh] [--unresolve] [--again] + [--really-refresh] [--unresolve] [--again | -g] [--info-only] [--index-info] [-z] [--stdin] [--verbose] @@ -80,7 +80,7 @@ OPTIONS filesystem that has very slow lstat(2) system call (e.g. cifs). ---again:: +--again, -g:: Runs `git-update-index` itself on the paths whose index entries are different from those from the `HEAD` commit. diff --git a/Documentation/git-zip-tree.txt b/Documentation/git-zip-tree.txt new file mode 100644 index 000000000..2e9d98124 --- /dev/null +++ b/Documentation/git-zip-tree.txt @@ -0,0 +1,67 @@ +git-zip-tree(1) +=============== + +NAME +---- +git-zip-tree - Creates a ZIP archive of the files in the named tree + + +SYNOPSIS +-------- +'git-zip-tree' [-0|...|-9] <tree-ish> [ <base> ] + +DESCRIPTION +----------- +Creates a ZIP archive containing the tree structure for the named tree. +When <base> is specified it is added as a leading path to the files in the +generated ZIP archive. + +git-zip-tree behaves differently when given a tree ID versus when given +a commit ID or tag ID. In the first case the current time is used as +modification time of each file in the archive. In the latter case the +commit time as recorded in the referenced commit object is used instead. +Additionally the commit ID is stored as an archive comment. + +Currently git-zip-tree can handle only files and directories, symbolic +links are not supported. + +OPTIONS +------- + +-0:: + Store the files instead of deflating them. + +-9:: + Highest and slowest compression level. You can specify any + number from 1 to 9 to adjust compression speed and ratio. + +<tree-ish>:: + The tree or commit to produce ZIP archive for. If it is + the object name of a commit object. + +<base>:: + Leading path to the files in the resulting ZIP archive. + +EXAMPLES +-------- +git zip-tree v1.4.0 git-1.4.0 >git-1.4.0.zip:: + + Create a ZIP file for v1.4.0 release. + +git zip-tree HEAD:Documentation/ git-docs >docs.zip:: + + Put everything in the current head's Documentation/ directory + into 'docs.zip', with the prefix 'git-docs/'. + +Author +------ +Written by Rene Scharfe. + +Documentation +-------------- +Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>. + +GIT +--- +Part of the gitlink:git[7] suite + diff --git a/Documentation/git.txt b/Documentation/git.txt index bcf187a11..a9c87e38d 100644 --- a/Documentation/git.txt +++ b/Documentation/git.txt @@ -8,8 +8,9 @@ git - the stupid content tracker SYNOPSIS -------- +[verse] 'git' [--version] [--exec-path[=GIT_EXEC_PATH]] [-p|--paginate] - [--bare] [--git-dir=GIT_DIR] [--help] COMMAND [ARGS] + [--bare] [--git-dir=GIT_DIR] [--help] COMMAND [ARGS] DESCRIPTION ----------- @@ -633,6 +634,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/gitk.txt b/Documentation/gitk.txt index cb482bf98..23be0050b 100644 --- a/Documentation/gitk.txt +++ b/Documentation/gitk.txt @@ -3,26 +3,52 @@ gitk(1) NAME ---- -gitk - Some git command not yet documented. - +gitk - git repository browser SYNOPSIS -------- -'gitk' [ --option ] <args>... +'gitk' [<option>...] [<revs>] [--] [<path>...] DESCRIPTION ----------- -Does something not yet documented. +Displays changes in a repository or a selected set of commits. This includes +visualizing the commit graph, showing information related to each commit, and +the files in the trees of each revision. +Historically, gitk was the first repository browser. It's written in tcl/tk +and started off in a separate repository but was later merged into the main +git repository. OPTIONS ------- ---option:: - Some option not yet documented. +To control which revisions to shown, the command takes options applicable to +the gitlink:git-rev-list[1] command. This manual page describes only the most +frequently used options. + +-n <number>, --max-count=<number>:: + + Limits the number of commits to show. + +--since=<date>:: + + Show commits more recent than a specific date. + +--until=<date>:: + + Show commits older than a specific date. -<args>...:: - Some argument not yet documented. +<revs>:: + Limit the revisions to show. This can be either a single revision + meaning show from the given revision and back, or it can be a range in + the form "'<from>'..'<to>'" to show all revisions between '<from>' and + back to '<to>'. Note, more advanced revision selection can be applied. + +<path>:: + + Limit commits to the ones touching files in the given paths. Note, to + avoid ambiguity wrt. revision names use "--" to separate the paths + from any preceeding options. Examples -------- @@ -37,13 +63,27 @@ gitk --since="2 weeks ago" \-- gitk:: The "--" is necessary to avoid confusion with the *branch* named 'gitk' +See Also +-------- +'qgit(1)':: + A repository browser written in C++ using Qt. + +'gitview(1)':: + A repository browser written in Python using Gtk. It's based on + 'bzrk(1)' and distributed in the contrib area of the git repository. + +'tig(1)':: + A minimal repository browser and git tool output highlighter written + in C using Ncurses. + Author ------ -Written by Paul Mackerras <paulus@samba.org> +Written by Paul Mackerras <paulus@samba.org>. Documentation -------------- -Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>. +Documentation by Junio C Hamano, Jonas Fonseca, and the git-list +<git@vger.kernel.org>. GIT --- diff --git a/Documentation/technical/racy-git.txt b/Documentation/technical/racy-git.txt new file mode 100644 index 000000000..7597d0414 --- /dev/null +++ b/Documentation/technical/racy-git.txt @@ -0,0 +1,193 @@ +Use of index and Racy git problem +================================= + +Background +---------- + +The index is one of the most important data structure in git. +It represents a virtual working tree state by recording list of +paths and their object names and serves as a staging area to +write out the next tree object to be committed. The state is +"virtual" in the sense that it does not necessarily have to, and +often does not, match the files in the working tree. + +There are cases git needs to examine the differences between the +virtual working tree state in the index and the files in the +working tree. The most obvious case is when the user asks `git +diff` (or its low level implementation, `git diff-files`) or +`git-ls-files --modified`. In addition, git internally checks +if the files in the working tree is different from what are +recorded in the index to avoid stomping on local changes in them +during patch application, switching branches, and merging. + +In order to speed up this comparison between the files in the +working tree and the index entries, the index entries record the +information obtained from the filesystem via `lstat(2)` system +call when they were last updated. When checking if they differ, +git first runs `lstat(2)` on the files and compare the result +with this information (this is what was originally done by the +`ce_match_stat()` function, which the current code does in +`ce_match_stat_basic()` function). If some of these "cached +stat information" fields do not match, git can tell that the +files are modified without even looking at their contents. + +Note: not all members in `struct stat` obtained via `lstat(2)` +are used for this comparison. For example, `st_atime` obviously +is not useful. Currently, git compares the file type (regular +files vs symbolic links) and executable bits (only for regular +files) from `st_mode` member, `st_mtime` and `st_ctime` +timestamps, `st_uid`, `st_gid`, `st_ino`, and `st_size` members. +With a `USE_STDEV` compile-time option, `st_dev` is also +compared, but this is not enabled by default because this member +is not stable on network filesystems. With `USE_NSEC` +compile-time option, `st_mtim.tv_nsec` and `st_ctim.tv_nsec` +members are also compared, but this is not enabled by default +because the value of this member becomes meaningless once the +inode is evicted from the inode cache on filesystems that do not +store it on disk. + + +Racy git +-------- + +There is one slight problem with the optimization based on the +cached stat information. Consider this sequence: + + $ git update-index 'foo' + : modify 'foo' in-place without changing its size + +The first `update-index` computes the object name of the +contents of file `foo` and updates the index entry for `foo` +along with the `struct stat` information. If the modification +that follows it happens very fast so that the file's `st_mtime` +timestamp does not change, after this sequence, the cached stat +information the index entry records still exactly match what you +can obtain from the filesystem, but the file `foo` is modified. +This way, git can incorrectly think files in the working tree +are unmodified even though they actually are. This is called +the "racy git" problem (discovered by Pasky), and the entries +that appear clean when they may not be because of this problem +are called "racily clean". + +To avoid this problem, git does two things: + +. When the cached stat information says the file has not been + modified, and the `st_mtime` is the same as (or newer than) + the timestamp of the index file itself (which is the time `git + update-index foo` finished running in the above example), it + also compares the contents with the object registered in the + index entry to make sure they match. + +. When the index file is updated that contains racily clean + entries, cached `st_size` information is truncated to zero + before writing a new version of the index file. + +Because the index file itself is written after collecting all +the stat information from updated paths, `st_mtime` timestamp of +it is usually the same as or newer than any of the paths the +index contains. And no matter how quick the modification that +follows `git update-index foo` finishes, the resulting +`st_mtime` timestamp on `foo` cannot get the timestamp earlier +than the index file. Therefore, index entries that can be +racily clean are limited to the ones that have the same +timestamp as the index file itself. + +The callers that want to check if an index entry matches the +corresponding file in the working tree continue to call +`ce_match_stat()`, but with this change, `ce_match_stat()` uses +`ce_modified_check_fs()` to see if racily clean ones are +actually clean after comparing the cached stat information using +`ce_match_stat_basic()`. + +The problem the latter solves is this sequence: + + $ git update-index 'foo' + : modify 'foo' in-place without changing its size + : wait for enough time + $ git update-index 'bar' + +Without the latter, the timestamp of the index file gets a newer +value, and falsely clean entry `foo` would not be caught by the +timestamp comparison check done with the former logic anymore. +The latter makes sure that the cached stat information for `foo` +would never match with the file in the working tree, so later +checks by `ce_match_stat_basic()` would report the index entry +does not match the file and git does not have to fall back on more +expensive `ce_modified_check_fs()`. + + +Runtime penalty +--------------- + +The runtime penalty of falling back to `ce_modified_check_fs()` +from `ce_match_stat()` can be very expensive when there are many +racily clean entries. An obvious way to artificially create +this situation is to give the same timestamp to all the files in +the working tree in a large project, run `git update-index` on +them, and give the same timestamp to the index file: + + $ date >.datestamp + $ git ls-files | xargs touch -r .datestamp + $ git ls-files | git update-index --stdin + $ touch -r .datestamp .git/index + +This will make all index entries racily clean. The linux-2.6 +project, for example, there are over 20,000 files in the working +tree. On my Athron 64X2 3800+, after the above: + + $ /usr/bin/time git diff-files + 1.68user 0.54system 0:02.22elapsed 100%CPU (0avgtext+0avgdata 0maxresident)k + 0inputs+0outputs (0major+67111minor)pagefaults 0swaps + $ git update-index MAINTAINERS + $ /usr/bin/time git diff-files + 0.02user 0.12system 0:00.14elapsed 100%CPU (0avgtext+0avgdata 0maxresident)k + 0inputs+0outputs (0major+935minor)pagefaults 0swaps + +Running `git update-index` in the middle checked the racily +clean entries, and left the cached `st_mtime` for all the paths +intact because they were actually clean (so this step took about +the same amount of time as the first `git diff-files`). After +that, they are not racily clean anymore but are truly clean, so +the second invocation of `git diff-files` fully took advantage +of the cached stat information. + + +Avoiding runtime penalty +------------------------ + +In order to avoid the above runtime penalty, the recent "master" +branch (post 1.4.2) has a code that makes sure the index file +gets timestamp newer than the youngest files in the index when +there are many young files with the same timestamp as the +resulting index file would otherwise would have by waiting +before finishing writing the index file out. + +I suspect that in practice the situation where many paths in the +index are all racily clean is quite rare. The only code paths +that can record recent timestamp for large number of paths I +know of are: + +. Initial `git add .` of a large project. + +. `git checkout` of a large project from an empty index into an + unpopulated working tree. + +Note: switching branches with `git checkout` keeps the cached +stat information of existing working tree files that are the +same between the current branch and the new branch, which are +all older than the resulting index file, and they will not +become racily clean. Only the files that are actually checked +out can become racily clean. + +In a large project where raciness avoidance cost really matters, +however, the initial computation of all object names in the +index takes more than one second, and the index file is written +out after all that happens. Therefore the timestamp of the +index file will be more than one seconds later than the the +youngest file in the working tree. This means that in these +cases there actually will not be any racily clean entry in +the resulting index. + +So in summary I think we should not worry about avoiding the +runtime penalty and get rid of the "wait before finishing +writing" code out. @@ -16,7 +16,7 @@ install" would not work. Alternatively you can use autoconf generated ./configure script to set up install paths (via config.mak.autogen), so you can write instead - $ autoconf ;# as yourself if ./configure doesn't exist yet + $ make configure ;# as yourself $ ./configure --prefix=/usr ;# as yourself $ make all doc ;# as yourself # make install install-doc ;# as root @@ -27,7 +27,7 @@ all: # Define NO_C99_FORMAT if your formatted IO functions (printf/scanf et.al.) # do not support the 'size specifiers' introduced by C99, namely ll, hh, # j, z, t. (representing long long int, char, intmax_t, size_t, ptrdiff_t). -# some c compilers supported these specifiers prior to C99 as an extension. +# some C compilers supported these specifiers prior to C99 as an extension. # # Define NO_STRCASESTR if you don't have strcasestr. # @@ -121,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_HOME_LINK_STR = projects +GITWEB_SITENAME = +GITWEB_PROJECTROOT = /pub/git +GITWEB_BASE_URL = +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 @@ -173,33 +184,28 @@ SIMPLE_PROGRAMS = \ # ... and all the rest that could be moved out of bindir to gitexecdir PROGRAMS = \ - git-checkout-index$X \ git-convert-objects$X git-fetch-pack$X git-fsck-objects$X \ git-hash-object$X git-index-pack$X git-local-fetch$X \ git-merge-base$X \ - git-merge-index$X git-mktag$X git-mktree$X git-pack-objects$X git-patch-id$X \ + git-merge-index$X git-mktag$X git-mktree$X git-patch-id$X \ git-peek-remote$X git-receive-pack$X \ git-send-pack$X git-shell$X \ git-show-index$X git-ssh-fetch$X \ git-ssh-upload$X git-unpack-file$X \ - git-unpack-objects$X git-update-server-info$X \ + git-update-server-info$X \ git-upload-pack$X git-verify-pack$X \ - git-symbolic-ref$X \ - git-name-rev$X git-pack-redundant$X git-var$X \ + git-pack-redundant$X git-var$X \ git-describe$X git-merge-tree$X git-blame$X git-imap-send$X \ - git-merge-recur$X - -BUILT_INS = git-log$X git-whatchanged$X git-show$X git-update-ref$X \ - git-count-objects$X git-diff$X git-push$X git-mailsplit$X \ - git-grep$X git-add$X git-rm$X git-rev-list$X git-stripspace$X \ - git-check-ref-format$X git-rev-parse$X git-mailinfo$X \ - git-init-db$X git-tar-tree$X git-upload-tar$X git-format-patch$X \ - git-ls-files$X git-ls-tree$X git-get-tar-commit-id$X \ - git-read-tree$X git-commit-tree$X git-write-tree$X \ - git-apply$X git-show-branch$X git-diff-files$X git-update-index$X \ - git-diff-index$X git-diff-stages$X git-diff-tree$X git-cat-file$X \ - git-fmt-merge-msg$X git-prune$X git-mv$X git-prune-packed$X \ - git-repo-config$X + git-merge-recur$X \ + $(EXTRA_PROGRAMS) + +# Empty... +EXTRA_PROGRAMS = + +BUILT_INS = \ + git-format-patch$X git-show$X git-whatchanged$X \ + git-get-tar-commit-id$X \ + $(patsubst builtin-%.o,git-%$X,$(BUILTIN_OBJS)) # what 'all' will build and 'install' will install, in gitexecdir ALL_PROGRAMS = $(PROGRAMS) $(SIMPLE_PROGRAMS) $(SCRIPTS) @@ -228,7 +234,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 path-list.h unpack-trees.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 \ @@ -243,20 +249,52 @@ 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 path-list.o unpack-trees.o $(DIFF_OBJS) + write_or_die.o \ + 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-mv.o builtin-prune-packed.o builtin-repo-config.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 \ + builtin-zip-tree.o GITLIBS = $(LIB_FILE) $(XDIFF_LIB) LIBS = $(GITLIBS) -lz @@ -297,15 +335,16 @@ ifeq ($(uname_S),SunOS) NEEDS_NSL = YesPlease SHELL_PATH = /bin/bash NO_STRCASESTR = YesPlease - NO_STRLCPY = YesPlease ifeq ($(uname_R),5.8) NEEDS_LIBICONV = YesPlease NO_UNSETENV = YesPlease NO_SETENV = YesPlease + NO_C99_FORMAT = YesPlease endif ifeq ($(uname_R),5.9) NO_UNSETENV = YesPlease NO_SETENV = YesPlease + NO_C99_FORMAT = YesPlease endif INSTALL = ginstall TAR = gtar @@ -524,7 +563,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 @@ -537,7 +576,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 $@ @@ -582,6 +621,24 @@ git-status: git-commit cp $< $@+ mv $@+ $@ +gitweb/gitweb.cgi: gitweb/gitweb.perl + rm -f $@ $@+ + sed -e '1s|#!.*perl|#!$(PERL_PATH_SQ)|' \ + -e 's|++GIT_VERSION++|$(GIT_VERSION)|g' \ + -e 's|++GIT_BINDIR++|$(bindir)|g' \ + -e 's|++GITWEB_CONFIG++|$(GITWEB_CONFIG)|g' \ + -e 's|++GITWEB_HOME_LINK_STR++|$(GITWEB_HOME_LINK_STR)|g' \ + -e 's|++GITWEB_SITENAME++|$(GITWEB_SITENAME)|g' \ + -e 's|++GITWEB_PROJECTROOT++|$(GITWEB_PROJECTROOT)|g' \ + -e 's|++GITWEB_BASE_URL++|$(GITWEB_BASE_URL)|g' \ + -e 's|++GITWEB_LIST++|$(GITWEB_LIST)|g' \ + -e 's|++GITWEB_HOMETEXT++|$(GITWEB_HOMETEXT)|g' \ + -e 's|++GITWEB_CSS++|$(GITWEB_CSS)|g' \ + -e 's|++GITWEB_LOGO++|$(GITWEB_LOGO)|g' \ + $< >$@+ + chmod +x $@+ + mv $@+ $@ + git-instaweb: git-instaweb.sh gitweb/gitweb.cgi gitweb/gitweb.css rm -f $@ $@+ sed -e '1s|#!.*/sh|#!$(SHELL_PATH_SQ)|' \ @@ -592,10 +649,17 @@ git-instaweb: git-instaweb.sh gitweb/gitweb.cgi gitweb/gitweb.css -e '/@@GITWEB_CGI@@/d' \ -e '/@@GITWEB_CSS@@/r gitweb/gitweb.css' \ -e '/@@GITWEB_CSS@@/d' \ - $@.sh | sed "s|/usr/bin/git|$(bindir)/git|" > $@+ + $@.sh > $@+ chmod +x $@+ mv $@+ $@ +configure: configure.ac + rm -f $@ $<+ + sed -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \ + $< > $<+ + autoconf -o $@ $<+ + rm -f $<+ + # These can record GIT_VERSION git$X git.spec \ $(patsubst %.sh,%,$(SCRIPT_SH)) \ @@ -792,10 +856,11 @@ clean: rm -f $(ALL_PROGRAMS) $(BUILT_INS) git$X rm -f *.spec *.pyc *.pyo */*.pyc */*.pyo common-cmds.h TAGS tags rm -rf autom4te.cache - rm -f config.log config.mak.autogen configure config.status config.cache + rm -f configure config.log config.mak.autogen config.mak.append config.status config.cache rm -rf $(GIT_TARNAME) .doc-tmp-dir rm -f $(GIT_TARNAME).tar.gz git-core_$(GIT_VERSION)-*.tar.gz rm -f $(htmldocs).tar.gz $(manpages).tar.gz + rm -f gitweb/gitweb.cgi $(MAKE) -C Documentation/ clean $(MAKE) -C templates clean $(MAKE) -C t/ clean @@ -1,14 +0,0 @@ -#!/bin/sh -x - -cd t || exit -GIT_USE_RECUR_FOR_RECURSIVE=LetsTryIt -export GIT_USE_RECUR_FOR_RECURSIVE - -./t3400-rebase.sh "$@" && -./t6020-merge-df.sh "$@" && -./t3401-rebase-partial.sh "$@" && -./t6021-merge-criss-cross.sh "$@" && -./t3402-rebase-merge.sh "$@" && -./t6022-merge-rename.sh "$@" && -./t6010-merge-base.sh "$@" && -: @@ -56,9 +56,9 @@ struct patch { static void get_blob(struct commit *commit); /* Only used for statistics */ -static int num_get_patch = 0; -static int num_commits = 0; -static int patch_time = 0; +static int num_get_patch; +static int num_commits; +static int patch_time; struct blame_diff_state { struct xdiff_emit_state xm; @@ -165,7 +165,7 @@ static int get_blob_sha1(struct tree *t, const char *pathname, blame_file = pathname; pathspec[0] = pathname; pathspec[1] = NULL; - memset(blob_sha1, 0, sizeof(blob_sha1)); + hashclr(blob_sha1); read_tree_recursive(t, "", 0, 0, pathspec, get_blob_sha1_internal); for (i = 0; i < 20; i++) { @@ -176,7 +176,7 @@ static int get_blob_sha1(struct tree *t, const char *pathname, if (i == 20) return -1; - memcpy(sha1, blob_sha1, 20); + hashcpy(sha1, blob_sha1); return 0; } @@ -191,7 +191,7 @@ static int get_blob_sha1_internal(const unsigned char *sha1, const char *base, strcmp(blame_file + baselen, pathname)) return -1; - memcpy(blob_sha1, sha1, 20); + hashcpy(blob_sha1, sha1); return -1; } @@ -351,10 +351,7 @@ static int fill_util_info(struct commit *commit) assert(util); assert(util->pathname); - if (get_blob_sha1(commit->tree, util->pathname, util->sha1)) - return 1; - else - return 0; + return !!get_blob_sha1(commit->tree, util->pathname, util->sha1); } static void alloc_line_map(struct commit *commit) diff --git a/builtin-apply.c b/builtin-apply.c index 9cf477c70..b47ccacc2 100644 --- a/builtin-apply.c +++ b/builtin-apply.c @@ -28,21 +28,24 @@ static int prefix_length = -1; static int newfd = -1; static int p_value = 1; -static int allow_binary_replacement = 0; -static int check_index = 0; -static int write_index = 0; -static int cached = 0; -static int diffstat = 0; -static int numstat = 0; -static int summary = 0; -static int check = 0; +static int allow_binary_replacement; +static int check_index; +static int write_index; +static int cached; +static int diffstat; +static int numstat; +static int summary; +static int check; static int apply = 1; -static int no_add = 0; -static int show_index_info = 0; +static int apply_in_reverse; +static int apply_with_reject; +static int apply_verbosely; +static int no_add; +static int show_index_info; static int line_termination = '\n'; static unsigned long p_context = -1; static const char apply_usage[] = -"git-apply [--stat] [--numstat] [--summary] [--check] [--index] [--cached] [--apply] [--no-add] [--index-info] [--allow-binary-replacement] [-z] [-pNUM] [-CNUM] [--whitespace=<nowarn|warn|error|error-all|strip>] <patch>..."; +"git-apply [--stat] [--numstat] [--summary] [--check] [--index] [--cached] [--apply] [--no-add] [--index-info] [--allow-binary-replacement] [--reverse] [--reject] [--verbose] [-z] [-pNUM] [-CNUM] [--whitespace=<nowarn|warn|error|error-all|strip>] <patch>..."; static enum whitespace_eol { nowarn_whitespace, @@ -50,10 +53,10 @@ static enum whitespace_eol { error_on_whitespace, strip_whitespace, } new_whitespace = warn_on_whitespace; -static int whitespace_error = 0; +static int whitespace_error; static int squelch_whitespace_errors = 5; -static int applied_after_stripping = 0; -static const char *patch_input_file = NULL; +static int applied_after_stripping; +static const char *patch_input_file; static void parse_whitespace_option(const char *option) { @@ -108,21 +111,37 @@ static int max_change, max_len; */ static int linenr = 1; +/* + * This represents one "hunk" from a patch, starting with + * "@@ -oldpos,oldlines +newpos,newlines @@" marker. The + * patch text is pointed at by patch, and its byte length + * is stored in size. leading and trailing are the number + * of context lines. + */ struct fragment { unsigned long leading, trailing; unsigned long oldpos, oldlines; unsigned long newpos, newlines; const char *patch; int size; + int rejected; struct fragment *next; }; +/* + * When dealing with a binary patch, we reuse "leading" field + * to store the type of the binary hunk, either deflated "delta" + * or deflated "literal". + */ +#define binary_patch_method leading +#define BINARY_DELTA_DEFLATED 1 +#define BINARY_LITERAL_DEFLATED 2 + struct patch { char *new_name, *old_name, *def_name; unsigned int old_mode, new_mode; - int is_rename, is_copy, is_new, is_delete, is_binary, is_reverse; -#define BINARY_DELTA_DEFLATED 1 -#define BINARY_LITERAL_DEFLATED 2 + int is_rename, is_copy, is_new, is_delete, is_binary; + int rejected; unsigned long deflate_origlen; int lines_added, lines_deleted; int score; @@ -135,6 +154,24 @@ struct patch { struct patch *next; }; +static void say_patch_name(FILE *output, const char *pre, struct patch *patch, const char *post) +{ + fputs(pre, output); + if (patch->old_name && patch->new_name && + strcmp(patch->old_name, patch->new_name)) { + write_name_quoted(NULL, 0, patch->old_name, 1, output); + fputs(" => ", output); + write_name_quoted(NULL, 0, patch->new_name, 1, output); + } + else { + const char *n = patch->new_name; + if (!n) + n = patch->old_name; + write_name_quoted(NULL, 0, n, 1, output); + } + fputs(post, output); +} + #define CHUNKSIZE (8192) #define SLOP (16) @@ -591,9 +628,7 @@ static char *git_header_name(char *line, int llen) * form. */ for (len = 0 ; ; len++) { - char c = name[len]; - - switch (c) { + switch (name[len]) { default: continue; case '\n': @@ -978,51 +1013,82 @@ static inline int metadata_changes(struct patch *patch) patch->old_mode != patch->new_mode); } -static int parse_binary(char *buffer, unsigned long size, struct patch *patch) +static char *inflate_it(const void *data, unsigned long size, + unsigned long inflated_size) { - /* We have read "GIT binary patch\n"; what follows is a line - * that says the patch method (currently, either "deflated - * literal" or "deflated delta") and the length of data before - * deflating; a sequence of 'length-byte' followed by base-85 - * encoded data follows. + z_stream stream; + void *out; + int st; + + memset(&stream, 0, sizeof(stream)); + + stream.next_in = (unsigned char *)data; + stream.avail_in = size; + stream.next_out = out = xmalloc(inflated_size); + stream.avail_out = inflated_size; + inflateInit(&stream); + st = inflate(&stream, Z_FINISH); + if ((st != Z_STREAM_END) || stream.total_out != inflated_size) { + free(out); + return NULL; + } + return out; +} + +static struct fragment *parse_binary_hunk(char **buf_p, + unsigned long *sz_p, + int *status_p, + int *used_p) +{ + /* Expect a line that begins with binary patch method ("literal" + * or "delta"), followed by the length of data before deflating. + * a sequence of 'length-byte' followed by base-85 encoded data + * should follow, terminated by a newline. * * Each 5-byte sequence of base-85 encodes up to 4 bytes, * and we would limit the patch line to 66 characters, * so one line can fit up to 13 groups that would decode * to 52 bytes max. The length byte 'A'-'Z' corresponds * to 1-26 bytes, and 'a'-'z' corresponds to 27-52 bytes. - * The end of binary is signaled with an empty line. */ int llen, used; - struct fragment *fragment; + unsigned long size = *sz_p; + char *buffer = *buf_p; + int patch_method; + unsigned long origlen; char *data = NULL; + int hunk_size = 0; + struct fragment *frag; - patch->fragments = fragment = xcalloc(1, sizeof(*fragment)); - - /* Grab the type of patch */ llen = linelen(buffer, size); used = llen; - linenr++; + + *status_p = 0; if (!strncmp(buffer, "delta ", 6)) { - patch->is_binary = BINARY_DELTA_DEFLATED; - patch->deflate_origlen = strtoul(buffer + 6, NULL, 10); + patch_method = BINARY_DELTA_DEFLATED; + origlen = strtoul(buffer + 6, NULL, 10); } else if (!strncmp(buffer, "literal ", 8)) { - patch->is_binary = BINARY_LITERAL_DEFLATED; - patch->deflate_origlen = strtoul(buffer + 8, NULL, 10); + patch_method = BINARY_LITERAL_DEFLATED; + origlen = strtoul(buffer + 8, NULL, 10); } else - return error("unrecognized binary patch at line %d: %.*s", - linenr-1, llen-1, buffer); + return NULL; + + linenr++; buffer += llen; while (1) { int byte_length, max_byte_length, newsize; llen = linelen(buffer, size); used += llen; linenr++; - if (llen == 1) + if (llen == 1) { + /* consume the blank line */ + buffer++; + size--; break; + } /* Minimum line is "A00000\n" which is 7-byte long, * and the line length must be multiple of 5 plus 2. */ @@ -1043,21 +1109,79 @@ static int parse_binary(char *buffer, unsigned long size, struct patch *patch) if (max_byte_length < byte_length || byte_length <= max_byte_length - 4) goto corrupt; - newsize = fragment->size + byte_length; + newsize = hunk_size + byte_length; data = xrealloc(data, newsize); - if (decode_85(data + fragment->size, - buffer + 1, - byte_length)) + if (decode_85(data + hunk_size, buffer + 1, byte_length)) goto corrupt; - fragment->size = newsize; + hunk_size = newsize; buffer += llen; size -= llen; } - fragment->patch = data; - return used; + + frag = xcalloc(1, sizeof(*frag)); + frag->patch = inflate_it(data, hunk_size, origlen); + if (!frag->patch) + goto corrupt; + free(data); + frag->size = origlen; + *buf_p = buffer; + *sz_p = size; + *used_p = used; + frag->binary_patch_method = patch_method; + return frag; + corrupt: - return error("corrupt binary patch at line %d: %.*s", - linenr-1, llen-1, buffer); + if (data) + free(data); + *status_p = -1; + error("corrupt binary patch at line %d: %.*s", + linenr-1, llen-1, buffer); + return NULL; +} + +static int parse_binary(char *buffer, unsigned long size, struct patch *patch) +{ + /* We have read "GIT binary patch\n"; what follows is a line + * that says the patch method (currently, either "literal" or + * "delta") and the length of data before deflating; a + * sequence of 'length-byte' followed by base-85 encoded data + * follows. + * + * When a binary patch is reversible, there is another binary + * hunk in the same format, starting with patch method (either + * "literal" or "delta") with the length of data, and a sequence + * of length-byte + base-85 encoded data, terminated with another + * empty line. This data, when applied to the postimage, produces + * the preimage. + */ + struct fragment *forward; + struct fragment *reverse; + int status; + int used, used_1; + + forward = parse_binary_hunk(&buffer, &size, &status, &used); + if (!forward && !status) + /* there has to be one hunk (forward hunk) */ + return error("unrecognized binary patch at line %d", linenr-1); + if (status) + /* otherwise we already gave an error message */ + return status; + + reverse = parse_binary_hunk(&buffer, &size, &status, &used_1); + if (reverse) + used += used_1; + else if (status) { + /* not having reverse hunk is not an error, but having + * a corrupt reverse hunk is. + */ + free((void*) forward->patch); + free(forward); + return status; + } + forward->next = reverse; + patch->fragments = forward; + patch->is_binary = 1; + return used; } static int parse_chunk(char *buffer, unsigned long size, struct patch *patch) @@ -1143,7 +1267,6 @@ static void reverse_patches(struct patch *p) swap(frag->newpos, frag->oldpos); swap(frag->newlines, frag->oldlines); } - p->is_reverse = !p->is_reverse; } } @@ -1363,8 +1486,7 @@ static int apply_line(char *output, const char *patch, int plen) return plen; } -static int apply_one_fragment(struct buffer_desc *desc, struct fragment *frag, - int reverse, int inaccurate_eof) +static int apply_one_fragment(struct buffer_desc *desc, struct fragment *frag, int inaccurate_eof) { int match_beginning, match_end; char *buf = desc->buffer; @@ -1396,7 +1518,7 @@ static int apply_one_fragment(struct buffer_desc *desc, struct fragment *frag, if (len < size && patch[len] == '\\') plen--; first = *patch; - if (reverse) { + if (apply_in_reverse) { if (first == '-') first = '+'; else if (first == '+') @@ -1446,7 +1568,8 @@ static int apply_one_fragment(struct buffer_desc *desc, struct fragment *frag, lines = 0; pos = frag->newpos; for (;;) { - offset = find_offset(buf, desc->size, oldlines, oldsize, pos, &lines); + offset = find_offset(buf, desc->size, + oldlines, oldsize, pos, &lines); if (match_end && offset + oldsize != desc->size) offset = -1; if (match_beginning && offset) @@ -1459,8 +1582,10 @@ static int apply_one_fragment(struct buffer_desc *desc, struct fragment *frag, /* Warn if it was necessary to reduce the number * of context lines. */ - if ((leading != frag->leading) || (trailing != frag->trailing)) - fprintf(stderr, "Context reduced to (%ld/%ld) to apply fragment at %d\n", + if ((leading != frag->leading) || + (trailing != frag->trailing)) + fprintf(stderr, "Context reduced to (%ld/%ld)" + " to apply fragment at %d\n", leading, trailing, pos + lines); if (size > alloc) { @@ -1470,7 +1595,9 @@ static int apply_one_fragment(struct buffer_desc *desc, struct fragment *frag, desc->buffer = buf; } desc->size = size; - memmove(buf + offset + newsize, buf + offset + oldsize, size - offset - newsize); + memmove(buf + offset + newsize, + buf + offset + oldsize, + size - offset - newsize); memcpy(buf + offset, newlines, newsize); offset = 0; @@ -1506,28 +1633,6 @@ static int apply_one_fragment(struct buffer_desc *desc, struct fragment *frag, return offset; } -static char *inflate_it(const void *data, unsigned long size, - unsigned long inflated_size) -{ - z_stream stream; - void *out; - int st; - - memset(&stream, 0, sizeof(stream)); - - stream.next_in = (unsigned char *)data; - stream.avail_in = size; - stream.next_out = out = xmalloc(inflated_size); - stream.avail_out = inflated_size; - inflateInit(&stream); - st = inflate(&stream, Z_FINISH); - if ((st != Z_STREAM_END) || stream.total_out != inflated_size) { - free(out); - return NULL; - } - return out; -} - static int apply_binary_fragment(struct buffer_desc *desc, struct patch *patch) { unsigned long dst_size; @@ -1535,30 +1640,29 @@ static int apply_binary_fragment(struct buffer_desc *desc, struct patch *patch) void *data; void *result; - /* Binary patch is irreversible */ - if (patch->is_reverse) - return error("cannot reverse-apply a binary patch to '%s'", - patch->new_name - ? patch->new_name : patch->old_name); - - data = inflate_it(fragment->patch, fragment->size, - patch->deflate_origlen); - if (!data) - return error("corrupt patch data"); - switch (patch->is_binary) { + /* Binary patch is irreversible without the optional second hunk */ + if (apply_in_reverse) { + if (!fragment->next) + return error("cannot reverse-apply a binary patch " + "without the reverse hunk to '%s'", + patch->new_name + ? patch->new_name : patch->old_name); + fragment = fragment->next; + } + data = (void*) fragment->patch; + switch (fragment->binary_patch_method) { case BINARY_DELTA_DEFLATED: result = patch_delta(desc->buffer, desc->size, data, - patch->deflate_origlen, + fragment->size, &dst_size); free(desc->buffer); desc->buffer = result; - free(data); break; case BINARY_LITERAL_DEFLATED: free(desc->buffer); desc->buffer = data; - dst_size = patch->deflate_origlen; + dst_size = fragment->size; break; } if (!desc->buffer) @@ -1609,7 +1713,7 @@ static int apply_binary(struct buffer_desc *desc, struct patch *patch) } get_sha1_hex(patch->new_sha1_prefix, sha1); - if (!memcmp(sha1, null_sha1, 20)) { + if (is_null_sha1(sha1)) { free(desc->buffer); desc->alloc = desc->size = 0; desc->buffer = NULL; @@ -1642,7 +1746,7 @@ static int apply_binary(struct buffer_desc *desc, struct patch *patch) write_sha1_file_prepare(desc->buffer, desc->size, blob_type, sha1, hdr, &hdrlen); if (strcmp(sha1_to_hex(sha1), patch->new_sha1_prefix)) - return error("binary patch to '%s' creates incorrect result", name); + return error("binary patch to '%s' creates incorrect result (expecting %s, got %s)", name, patch->new_sha1_prefix, sha1_to_hex(sha1)); } return 0; @@ -1657,10 +1761,12 @@ static int apply_fragments(struct buffer_desc *desc, struct patch *patch) return apply_binary(desc, patch); while (frag) { - if (apply_one_fragment(desc, frag, patch->is_reverse, - patch->inaccurate_eof) < 0) - return error("patch failed: %s:%ld", - name, frag->oldpos); + if (apply_one_fragment(desc, frag, patch->inaccurate_eof)) { + error("patch failed: %s:%ld", name, frag->oldpos); + if (!apply_with_reject) + return -1; + frag->rejected = 1; + } frag = frag->next; } return 0; @@ -1696,8 +1802,9 @@ static int apply_data(struct patch *patch, struct stat *st, struct cache_entry * desc.size = size; desc.alloc = alloc; desc.buffer = buf; + if (apply_fragments(&desc, patch) < 0) - return -1; + return -1; /* note with --reject this succeeds. */ /* NUL terminate the result */ if (desc.alloc <= desc.size) @@ -1722,6 +1829,7 @@ static int check_patch(struct patch *patch, struct patch *prev_patch) struct cache_entry *ce = NULL; int ok_if_exists; + patch->rejected = 1; /* we will drop this after we succeed */ if (old_name) { int changed = 0; int stat_ret = 0; @@ -1827,24 +1935,23 @@ static int check_patch(struct patch *patch, struct patch *prev_patch) if (apply_data(patch, &st, ce) < 0) return error("%s: patch does not apply", name); + patch->rejected = 0; return 0; } static int check_patch_list(struct patch *patch) { struct patch *prev_patch = NULL; - int error = 0; + int err = 0; for (prev_patch = NULL; patch ; patch = patch->next) { - error |= check_patch(patch, prev_patch); + if (apply_verbosely) + say_patch_name(stderr, + "Checking patch ", patch, "...\n"); + err |= check_patch(patch, prev_patch); prev_patch = patch; } - return error; -} - -static inline int is_null_sha1(const unsigned char *sha1) -{ - return !memcmp(sha1, null_sha1, 20); + return err; } static void show_index_list(struct patch *list) @@ -2150,23 +2257,99 @@ static void write_out_one_result(struct patch *patch, int phase) if (phase == 0) remove_file(patch); if (phase == 1) - create_file(patch); + create_file(patch); +} + +static int write_out_one_reject(struct patch *patch) +{ + FILE *rej; + char namebuf[PATH_MAX]; + struct fragment *frag; + int cnt = 0; + + for (cnt = 0, frag = patch->fragments; frag; frag = frag->next) { + if (!frag->rejected) + continue; + cnt++; + } + + if (!cnt) { + if (apply_verbosely) + say_patch_name(stderr, + "Applied patch ", patch, " cleanly.\n"); + return 0; + } + + /* This should not happen, because a removal patch that leaves + * contents are marked "rejected" at the patch level. + */ + if (!patch->new_name) + die("internal error"); + + /* Say this even without --verbose */ + say_patch_name(stderr, "Applying patch ", patch, " with"); + fprintf(stderr, " %d rejects...\n", cnt); + + cnt = strlen(patch->new_name); + if (ARRAY_SIZE(namebuf) <= cnt + 5) { + cnt = ARRAY_SIZE(namebuf) - 5; + fprintf(stderr, + "warning: truncating .rej filename to %.*s.rej", + cnt - 1, patch->new_name); + } + memcpy(namebuf, patch->new_name, cnt); + memcpy(namebuf + cnt, ".rej", 5); + + rej = fopen(namebuf, "w"); + if (!rej) + return error("cannot open %s: %s", namebuf, strerror(errno)); + + /* Normal git tools never deal with .rej, so do not pretend + * this is a git patch by saying --git nor give extended + * headers. While at it, maybe please "kompare" that wants + * the trailing TAB and some garbage at the end of line ;-). + */ + fprintf(rej, "diff a/%s b/%s\t(rejected hunks)\n", + patch->new_name, patch->new_name); + for (cnt = 1, frag = patch->fragments; + frag; + cnt++, frag = frag->next) { + if (!frag->rejected) { + fprintf(stderr, "Hunk #%d applied cleanly.\n", cnt); + continue; + } + fprintf(stderr, "Rejected hunk #%d.\n", cnt); + fprintf(rej, "%.*s", frag->size, frag->patch); + if (frag->patch[frag->size-1] != '\n') + fputc('\n', rej); + } + fclose(rej); + return -1; } -static void write_out_results(struct patch *list, int skipped_patch) +static int write_out_results(struct patch *list, int skipped_patch) { int phase; + int errs = 0; + struct patch *l; if (!list && !skipped_patch) - die("No changes"); + return error("No changes"); for (phase = 0; phase < 2; phase++) { - struct patch *l = list; + l = list; while (l) { - write_out_one_result(l, phase); + if (l->rejected) + errs = 1; + else { + write_out_one_result(l, phase); + if (phase == 1 && write_out_one_reject(l)) + errs = 1; + } l = l->next; } } + return errs; } static struct lock_file lock_file; @@ -2194,8 +2377,7 @@ static int use_patch(struct patch *p) return 1; } -static int apply_patch(int fd, const char *filename, - int reverse, int inaccurate_eof) +static int apply_patch(int fd, const char *filename, int inaccurate_eof) { unsigned long offset, size; char *buffer = read_patch_file(fd, &size); @@ -2215,7 +2397,7 @@ static int apply_patch(int fd, const char *filename, nr = parse_chunk(buffer + offset, size, patch); if (nr < 0) break; - if (reverse) + if (apply_in_reverse) reverse_patches(patch); if (use_patch(patch)) { patch_stats(patch); @@ -2242,11 +2424,13 @@ static int apply_patch(int fd, const char *filename, die("unable to read index file"); } - if ((check || apply) && check_patch_list(list) < 0) + if ((check || apply) && + check_patch_list(list) < 0 && + !apply_with_reject) exit(1); - if (apply) - write_out_results(list, skipped_patch); + if (apply && write_out_results(list, skipped_patch)) + exit(1); if (show_index_info) show_index_list(list); @@ -2278,8 +2462,8 @@ int cmd_apply(int argc, const char **argv, const char *prefix) { int i; int read_stdin = 1; - int reverse = 0; int inaccurate_eof = 0; + int errs = 0; const char *whitespace_option = NULL; @@ -2289,7 +2473,7 @@ int cmd_apply(int argc, const char **argv, const char *prefix) int fd; if (!strcmp(arg, "-")) { - apply_patch(0, "<stdin>", reverse, inaccurate_eof); + errs |= apply_patch(0, "<stdin>", inaccurate_eof); read_stdin = 0; continue; } @@ -2367,7 +2551,15 @@ int cmd_apply(int argc, const char **argv, const char *prefix) continue; } if (!strcmp(arg, "-R") || !strcmp(arg, "--reverse")) { - reverse = 1; + apply_in_reverse = 1; + continue; + } + if (!strcmp(arg, "--reject")) { + apply = apply_with_reject = apply_verbosely = 1; + continue; + } + if (!strcmp(arg, "--verbose")) { + apply_verbosely = 1; continue; } if (!strcmp(arg, "--inaccurate-eof")) { @@ -2390,18 +2582,19 @@ int cmd_apply(int argc, const char **argv, const char *prefix) usage(apply_usage); read_stdin = 0; set_default_whitespace_mode(whitespace_option); - apply_patch(fd, arg, reverse, inaccurate_eof); + errs |= apply_patch(fd, arg, inaccurate_eof); close(fd); } set_default_whitespace_mode(whitespace_option); if (read_stdin) - apply_patch(0, "<stdin>", reverse, inaccurate_eof); + errs |= apply_patch(0, "<stdin>", inaccurate_eof); if (whitespace_error) { if (squelch_whitespace_errors && squelch_whitespace_errors < whitespace_error) { int squelched = whitespace_error - squelch_whitespace_errors; - fprintf(stderr, "warning: squelched %d whitespace error%s\n", + fprintf(stderr, "warning: squelched %d " + "whitespace error%s\n", squelched, squelched == 1 ? "" : "s"); } @@ -2429,5 +2622,5 @@ int cmd_apply(int argc, const char **argv, const char *prefix) die("Unable to write new index file"); } - return 0; + return !!errs; } diff --git a/builtin-cat-file.c b/builtin-cat-file.c index 814fb0743..6c16bfa1a 100644 --- a/builtin-cat-file.c +++ b/builtin-cat-file.c @@ -9,24 +9,7 @@ #include "tree.h" #include "builtin.h" -static void flush_buffer(const char *buf, unsigned long size) -{ - while (size > 0) { - long ret = xwrite(1, buf, size); - if (ret < 0) { - /* Ignore epipe */ - if (errno == EPIPE) - break; - die("git-cat-file: %s", strerror(errno)); - } else if (!ret) { - die("git-cat-file: disk full?"); - } - size -= ret; - buf += ret; - } -} - -static int pprint_tag(const unsigned char *sha1, const char *buf, unsigned long size) +static void pprint_tag(const unsigned char *sha1, const char *buf, unsigned long size) { /* the parser in tag.c is useless here. */ const char *endp = buf + size; @@ -42,7 +25,7 @@ static int pprint_tag(const unsigned char *sha1, const char *buf, unsigned long /* Found the tagger line. Copy out the contents * of the buffer so far. */ - flush_buffer(buf, cp - buf); + write_or_die(1, buf, cp - buf); /* * Do something intelligent, like pretty-printing @@ -61,18 +44,18 @@ static int pprint_tag(const unsigned char *sha1, const char *buf, unsigned long sp++; if (sp == cp) { /* give up */ - flush_buffer(tagger, + write_or_die(1, tagger, cp - tagger); break; } while (sp < cp && !('0' <= *sp && *sp <= '9')) sp++; - flush_buffer(tagger, sp - tagger); + write_or_die(1, tagger, sp - tagger); date = strtoul(sp, &ep, 10); tz = strtol(ep, NULL, 10); - sp = show_date(date, tz); - flush_buffer(sp, strlen(sp)); + sp = show_date(date, tz, 0); + write_or_die(1, sp, strlen(sp)); xwrite(1, "\n", 1); break; } @@ -90,8 +73,7 @@ static int pprint_tag(const unsigned char *sha1, const char *buf, unsigned long * remainder as is. */ if (cp < endp) - flush_buffer(cp, endp - cp); - return 0; + write_or_die(1, cp, endp - cp); } int cmd_cat_file(int argc, const char **argv, const char *prefix) @@ -145,8 +127,10 @@ int cmd_cat_file(int argc, const char **argv, const char *prefix) buf = read_sha1_file(sha1, type, &size); if (!buf) die("Cannot read object %s", argv[2]); - if (!strcmp(type, tag_type)) - return pprint_tag(sha1, buf, size); + if (!strcmp(type, tag_type)) { + pprint_tag(sha1, buf, size); + return 0; + } /* otherwise just spit out the data */ break; @@ -161,6 +145,6 @@ int cmd_cat_file(int argc, const char **argv, const char *prefix) if (!buf) die("git-cat-file %s: bad file", argv[2]); - flush_buffer(buf, size); + write_or_die(1, buf, size); return 0; } diff --git a/checkout-index.c b/builtin-checkout-index.c index dfb1c4441..b097c888a 100644 --- a/checkout-index.c +++ b/builtin-checkout-index.c @@ -42,16 +42,14 @@ #include "cache-tree.h" #define CHECKOUT_ALL 4 -static const char *prefix; -static int prefix_length; static int line_termination = '\n'; static int checkout_stage; /* default to checkout stage0 */ static int to_tempfile; -static char topath[4][MAXPATHLEN+1]; +static char topath[4][PATH_MAX + 1]; static struct checkout state; -static void write_tempfile_record (const char *name) +static void write_tempfile_record(const char *name, int prefix_length) { int i; @@ -77,7 +75,7 @@ static void write_tempfile_record (const char *name) } } -static int checkout_file(const char *name) +static int checkout_file(const char *name, int prefix_length) { int namelen = strlen(name); int pos = cache_name_pos(name, namelen); @@ -106,7 +104,7 @@ static int checkout_file(const char *name) if (did_checkout) { if (to_tempfile) - write_tempfile_record(name); + write_tempfile_record(name, prefix_length); return errs > 0 ? -1 : 0; } @@ -124,7 +122,7 @@ static int checkout_file(const char *name) return -1; } -static int checkout_all(void) +static void checkout_all(const char *prefix, int prefix_length) { int i, errs = 0; struct cache_entry* last_ce = NULL; @@ -141,7 +139,7 @@ static int checkout_all(void) if (last_ce && to_tempfile) { if (ce_namelen(last_ce) != ce_namelen(ce) || memcmp(last_ce->name, ce->name, ce_namelen(ce))) - write_tempfile_record(last_ce->name); + write_tempfile_record(last_ce->name, prefix_length); } if (checkout_entry(ce, &state, to_tempfile ? topath[ce_stage(ce)] : NULL) < 0) @@ -149,13 +147,12 @@ static int checkout_all(void) last_ce = ce; } if (last_ce && to_tempfile) - write_tempfile_record(last_ce->name); + write_tempfile_record(last_ce->name, prefix_length); if (errs) /* we have already done our error reporting. * exit with the same code as die(). */ exit(128); - return 0; } static const char checkout_cache_usage[] = @@ -163,16 +160,16 @@ static const char checkout_cache_usage[] = static struct lock_file lock_file; -int main(int argc, char **argv) +int cmd_checkout_index(int argc, const char **argv, const char *prefix) { int i; int newfd = -1; int all = 0; int read_from_stdin = 0; + int prefix_length; - state.base_dir = ""; - prefix = setup_git_directory(); git_config(git_default_config); + state.base_dir = ""; prefix_length = prefix ? strlen(prefix) : 0; if (read_cache() < 0) { @@ -270,7 +267,7 @@ int main(int argc, char **argv) if (read_from_stdin) die("git-checkout-index: don't mix '--stdin' and explicit filenames"); p = prefix_path(prefix, prefix_length, arg); - checkout_file(p); + checkout_file(p, prefix_length); if (p < arg || p > arg + strlen(arg)) free((char*)p); } @@ -292,7 +289,7 @@ int main(int argc, char **argv) else path_name = buf.buf; p = prefix_path(prefix, prefix_length, path_name); - checkout_file(p); + checkout_file(p, prefix_length); if (p < path_name || p > path_name + strlen(path_name)) free((char *)p); if (path_name != buf.buf) @@ -301,7 +298,7 @@ int main(int argc, char **argv) } if (all) - checkout_all(); + checkout_all(prefix, prefix_length); if (0 <= newfd && (write_cache(newfd, active_cache, active_nr) || diff --git a/builtin-commit-tree.c b/builtin-commit-tree.c index 9c9879667..e2e690a1a 100644 --- a/builtin-commit-tree.c +++ b/builtin-commit-tree.c @@ -69,7 +69,7 @@ static int new_parent(int idx) int i; unsigned char *sha1 = parent_sha1[idx]; for (i = 0; i < idx; i++) { - if (!memcmp(parent_sha1[i], sha1, 20)) { + if (!hashcmp(parent_sha1[i], sha1)) { error("duplicate parent %s ignored", sha1_to_hex(sha1)); return 0; } diff --git a/builtin-count.c b/builtin-count-objects.c index 1d3729aa9..1d3729aa9 100644 --- a/builtin-count.c +++ b/builtin-count-objects.c diff --git a/builtin-diff-files.c b/builtin-diff-files.c index ac13db70f..5d4a5c582 100644 --- a/builtin-diff-files.c +++ b/builtin-diff-files.c @@ -47,12 +47,5 @@ int cmd_diff_files(int argc, const char **argv, const char *prefix) if (rev.pending.nr || rev.min_age != -1 || rev.max_age != -1) usage(diff_files_usage); - /* - * Backward compatibility wart - "diff-files -s" used to - * defeat the common diff option "-s" which asked for - * DIFF_FORMAT_NO_OUTPUT. - */ - if (rev.diffopt.output_format == DIFF_FORMAT_NO_OUTPUT) - rev.diffopt.output_format = DIFF_FORMAT_RAW; return run_diff_files(&rev, silent); } diff --git a/builtin-diff-stages.c b/builtin-diff-stages.c index 5960e0899..70bb89808 100644 --- a/builtin-diff-stages.c +++ b/builtin-diff-stages.c @@ -46,7 +46,7 @@ static void diff_stages(int stage1, int stage2, const char **pathspec) else if (!two) diff_addremove(&diff_options, '-', ntohl(one->ce_mode), one->sha1, name, NULL); - else if (memcmp(one->sha1, two->sha1, 20) || + else if (hashcmp(one->sha1, two->sha1) || (one->ce_mode != two->ce_mode) || diff_options.find_copies_harder) diff_change(&diff_options, diff --git a/builtin-diff.c b/builtin-diff.c index a090e298a..a6590205e 100644 --- a/builtin-diff.c +++ b/builtin-diff.c @@ -56,13 +56,6 @@ static int builtin_diff_files(struct rev_info *revs, if (revs->max_count < 0 && (revs->diffopt.output_format & DIFF_FORMAT_PATCH)) revs->combine_merges = revs->dense_combined_merges = 1; - /* - * Backward compatibility wart - "diff-files -s" used to - * defeat the common diff option "-s" which asked for - * DIFF_FORMAT_NO_OUTPUT. - */ - if (revs->diffopt.output_format == DIFF_FORMAT_NO_OUTPUT) - revs->diffopt.output_format = DIFF_FORMAT_RAW; return run_diff_files(revs, silent); } @@ -75,9 +68,8 @@ static void stuff_change(struct diff_options *opt, { struct diff_filespec *one, *two; - if (memcmp(null_sha1, old_sha1, 20) && - memcmp(null_sha1, new_sha1, 20) && - !memcmp(old_sha1, new_sha1, 20)) + if (!is_null_sha1(old_sha1) && !is_null_sha1(new_sha1) && + !hashcmp(old_sha1, new_sha1)) return; if (opt->reverse_diff) { @@ -200,7 +192,7 @@ static int builtin_diff_combined(struct rev_info *revs, parent = xmalloc(ents * sizeof(*parent)); /* Again, the revs are all reverse */ for (i = 0; i < ents; i++) - memcpy(parent + i, ent[ents - 1 - i].item->sha1, 20); + hashcpy((unsigned char*)parent + i, ent[ents - 1 - i].item->sha1); diff_tree_combined(parent[0], parent + 1, ents - 1, revs->dense_combined_merges, revs); return 0; @@ -298,7 +290,7 @@ int cmd_diff(int argc, const char **argv, const char *prefix) if (obj->type == OBJ_BLOB) { if (2 <= blobs) die("more than two blobs given: '%s'", name); - memcpy(blob[blobs].sha1, obj->sha1, 20); + hashcpy(blob[blobs].sha1, obj->sha1); blob[blobs].name = name; blobs++; continue; diff --git a/builtin-fmt-merge-msg.c b/builtin-fmt-merge-msg.c index 485ede7ca..a5ed8dbba 100644 --- a/builtin-fmt-merge-msg.c +++ b/builtin-fmt-merge-msg.c @@ -8,7 +8,7 @@ static const char *fmt_merge_msg_usage = "git-fmt-merge-msg [--summary] [--no-summary] [--file <file>]"; -static int merge_summary = 0; +static int merge_summary; static int fmt_merge_msg_config(const char *key, const char *value) { @@ -27,8 +27,8 @@ static void append_to_list(struct list *list, char *value, void *payload) { if (list->nr == list->alloc) { list->alloc += 32; - list->list = realloc(list->list, sizeof(char *) * list->alloc); - list->payload = realloc(list->payload, + list->list = xrealloc(list->list, sizeof(char *) * list->alloc); + list->payload = xrealloc(list->payload, sizeof(char *) * list->alloc); } list->payload[list->nr] = payload; diff --git a/builtin-grep.c b/builtin-grep.c index 93b7e07b3..8213ce240 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; @@ -173,61 +175,12 @@ static void compile_regexp(struct grep_pat *p, struct grep_opt *opt) } } -#if DEBUG -static inline void indent(int in) -{ - int i; - for (i = 0; i < in; i++) putchar(' '); -} - -static void dump_pattern_exp(struct grep_expr *x, int in) -{ - switch (x->node) { - case GREP_NODE_ATOM: - indent(in); - puts(x->u.atom->pattern); - break; - case GREP_NODE_NOT: - indent(in); - puts("--not"); - dump_pattern_exp(x->u.unary, in+1); - break; - case GREP_NODE_AND: - dump_pattern_exp(x->u.binary.left, in+1); - indent(in); - puts("--and"); - dump_pattern_exp(x->u.binary.right, in+1); - break; - case GREP_NODE_OR: - dump_pattern_exp(x->u.binary.left, in+1); - indent(in); - puts("--or"); - dump_pattern_exp(x->u.binary.right, in+1); - break; - } -} - -static void looking_at(const char *msg, struct grep_pat **list) -{ - struct grep_pat *p = *list; - fprintf(stderr, "%s: looking at ", msg); - if (!p) - fprintf(stderr, "empty\n"); - else - fprintf(stderr, "<%s>\n", p->pattern); -} -#else -#define looking_at(a,b) do {} while(0) -#endif - static struct grep_expr *compile_pattern_expr(struct grep_pat **); static struct grep_expr *compile_pattern_atom(struct grep_pat **list) { struct grep_pat *p; struct grep_expr *x; - looking_at("atom", list); - p = *list; switch (p->token) { case GREP_PATTERN: /* atom */ @@ -255,8 +208,6 @@ static struct grep_expr *compile_pattern_not(struct grep_pat **list) struct grep_pat *p; struct grep_expr *x; - looking_at("not", list); - p = *list; switch (p->token) { case GREP_NOT: @@ -279,8 +230,6 @@ static struct grep_expr *compile_pattern_and(struct grep_pat **list) struct grep_pat *p; struct grep_expr *x, *y, *z; - looking_at("and", list); - x = compile_pattern_not(list); p = *list; if (p && p->token == GREP_AND) { @@ -304,8 +253,6 @@ static struct grep_expr *compile_pattern_or(struct grep_pat **list) struct grep_pat *p; struct grep_expr *x, *y, *z; - looking_at("or", list); - x = compile_pattern_and(list); p = *list; if (x && p && p->token != GREP_CLOSE_PAREN) { @@ -323,8 +270,6 @@ static struct grep_expr *compile_pattern_or(struct grep_pat **list) static struct grep_expr *compile_pattern_expr(struct grep_pat **list) { - looking_at("expr", list); - return compile_pattern_or(list); } @@ -348,9 +293,6 @@ static void compile_patterns(struct grep_opt *opt) */ p = opt->pattern_list; opt->pattern_expression = compile_pattern_expr(&p); -#if DEBUG - dump_pattern_exp(opt->pattern_expression, 0); -#endif if (p) die("incomplete pattern expression: %s", p->pattern); } @@ -388,9 +330,7 @@ static int buffer_is_binary(const char *ptr, unsigned long size) { if (FIRST_FEW_BYTES < size) size = FIRST_FEW_BYTES; - if (memchr(ptr, 0, size)) - return 1; - return 0; + return !!memchr(ptr, 0, size); } static int fixmatch(const char *pattern, char *line, regmatch_t *match) @@ -632,19 +572,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; } @@ -674,6 +635,8 @@ static int grep_file(struct grep_opt *opt, const char *filename) return 0; } close(i); + if (opt->relative && opt->prefix_length) + filename += opt->prefix_length; i = grep_buffer(opt, filename, data, st.st_size); free(data); return i; @@ -720,7 +683,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"); @@ -845,7 +808,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); } @@ -860,11 +823,12 @@ static int grep_tree(struct grep_opt *opt, const char **paths, int hit = 0; struct name_entry entry; char *down; - char *path_buf = xmalloc(PATH_MAX + strlen(tree_name) + 100); + int tn_len = strlen(tree_name); + char *path_buf = xmalloc(PATH_MAX + tn_len + 100); - if (tree_name[0]) { - int offset = sprintf(path_buf, "%s:", tree_name); - down = path_buf + offset; + if (tn_len) { + tn_len = sprintf(path_buf, "%s:", tree_name); + down = path_buf + tn_len; strcat(down, base); } else { @@ -886,7 +850,7 @@ static int grep_tree(struct grep_opt *opt, const char **paths, if (!pathspec_matches(paths, down)) ; else if (S_ISREG(entry.mode)) - hit |= grep_sha1(opt, entry.sha1, path_buf); + hit |= grep_sha1(opt, entry.sha1, path_buf, tn_len); else if (S_ISDIR(entry.mode)) { char type[20]; struct tree_desc sub; @@ -907,7 +871,7 @@ static int grep_object(struct grep_opt *opt, const char **paths, struct object *obj, const char *name) { if (obj->type == OBJ_BLOB) - return grep_sha1(opt, obj->sha1, name); + return grep_sha1(opt, obj->sha1, name, 0); if (obj->type == OBJ_COMMIT || obj->type == OBJ_TREE) { struct tree_desc tree; void *data; @@ -945,6 +909,8 @@ int cmd_grep(int argc, const char **argv, const char *prefix) int i; memset(&opt, 0, sizeof(opt)); + opt.prefix_length = (prefix && *prefix) ? strlen(prefix) : 0; + opt.relative = 1; opt.pattern_tail = &opt.pattern_list; opt.regflags = REG_NEWLINE; @@ -1118,6 +1084,10 @@ int cmd_grep(int argc, const char **argv, const char *prefix) } 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--; @@ -1176,8 +1146,15 @@ int cmd_grep(int argc, const char **argv, const char *prefix) verify_filename(prefix, argv[j]); } - if (i < argc) + if (i < argc) { paths = get_pathspec(prefix, argv + i); + if (opt.prefix_length && opt.relative) { + /* Make sure we do not get outside of paths */ + for (i = 0; paths[i]; i++) + if (strncmp(prefix, paths[i], opt.prefix_length)) + die("git-grep: cannot generate relative filenames containing '..'"); + } + } else if (prefix) { paths = xcalloc(2, sizeof(const char *)); paths[0] = prefix; diff --git a/builtin-log.c b/builtin-log.c index 691cf3aef..fbc58bbca 100644 --- a/builtin-log.c +++ b/builtin-log.c @@ -101,7 +101,7 @@ static int git_format_config(const char *var, const char *value) if (!strcmp(var, "format.headers")) { int len = strlen(value); extra_headers_size += len + 1; - extra_headers = realloc(extra_headers, extra_headers_size); + extra_headers = xrealloc(extra_headers, extra_headers_size); extra_headers[extra_headers_size - len - 1] = 0; strcat(extra_headers, value); return 0; @@ -381,7 +381,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) continue; nr++; - list = realloc(list, nr * sizeof(list[0])); + list = xrealloc(list, nr * sizeof(list[0])); list[nr - 1] = commit; } total = nr; diff --git a/builtin-ls-files.c b/builtin-ls-files.c index 11386c432..ad8c41e73 100644 --- a/builtin-ls-files.c +++ b/builtin-ls-files.c @@ -12,21 +12,22 @@ #include "dir.h" #include "builtin.h" -static int abbrev = 0; -static int show_deleted = 0; -static int show_cached = 0; -static int show_others = 0; -static int show_stage = 0; -static int show_unmerged = 0; -static int show_modified = 0; -static int show_killed = 0; -static int show_valid_bit = 0; +static int abbrev; +static int show_deleted; +static int show_cached; +static int show_others; +static int show_stage; +static int show_unmerged; +static int show_modified; +static int show_killed; +static int show_valid_bit; static int line_terminator = '\n'; -static int prefix_len = 0, prefix_offset = 0; -static const char **pathspec = NULL; -static int error_unmatch = 0; -static char *ps_matched = NULL; +static int prefix_len; +static int prefix_offset; +static const char **pathspec; +static int error_unmatch; +static char *ps_matched; static const char *tag_cached = ""; static const char *tag_unmerged = ""; diff --git a/builtin-ls-tree.c b/builtin-ls-tree.c index 261147fdb..201defd93 100644 --- a/builtin-ls-tree.c +++ b/builtin-ls-tree.c @@ -14,10 +14,10 @@ static int line_termination = '\n'; #define LS_TREE_ONLY 2 #define LS_SHOW_TREES 4 #define LS_NAME_ONLY 8 -static int abbrev = 0; -static int ls_options = 0; +static int abbrev; +static int ls_options; static const char **pathspec; -static int chomp_prefix = 0; +static int chomp_prefix; static const char *ls_tree_prefix; static const char ls_tree_usage[] = diff --git a/builtin-mailinfo.c b/builtin-mailinfo.c index 24a4fc63b..0c65f9314 100644 --- a/builtin-mailinfo.c +++ b/builtin-mailinfo.c @@ -16,8 +16,8 @@ static FILE *cmitmsg, *patchfile, *fin, *fout; -static int keep_subject = 0; -static const char *metainfo_charset = NULL; +static int keep_subject; +static const char *metainfo_charset; static char line[1000]; static char date[1000]; static char name[1000]; @@ -31,7 +31,7 @@ static char charset[256]; static char multipart_boundary[1000]; static int multipart_boundary_len; -static int patch_lines = 0; +static int patch_lines; static char *sanity_check(char *name, char *email) { diff --git a/builtin-mv.c b/builtin-mv.c index a731f8d9c..4d21d8841 100644 --- a/builtin-mv.c +++ b/builtin-mv.c @@ -17,12 +17,19 @@ static const char builtin_mv_usage[] = static const char **copy_pathspec(const char *prefix, const char **pathspec, int count, int base_name) { + int i; const char **result = xmalloc((count + 1) * sizeof(const char *)); memcpy(result, pathspec, count * sizeof(const char *)); result[count] = NULL; - if (base_name) { - int i; - for (i = 0; i < count; i++) { + for (i = 0; i < count; i++) { + int length = strlen(result[i]); + if (length > 0 && result[i][length - 1] == '/') { + char *without_slash = xmalloc(length); + memcpy(without_slash, result[i], length - 1); + without_slash[length - 1] = '\0'; + result[i] = without_slash; + } + if (base_name) { const char *last_slash = strrchr(result[i], '/'); if (last_slash) result[i] = last_slash + 1; @@ -107,7 +114,10 @@ int cmd_mv(int argc, const char **argv, const char *prefix) modes = xcalloc(count, sizeof(enum update_mode)); dest_path = copy_pathspec(prefix, argv + argc - 1, 1, 0); - if (!lstat(dest_path[0], &st) && + if (dest_path[0][0] == '\0') + /* special case: "." was normalized to "" */ + destination = copy_pathspec(dest_path[0], argv + i, count, 1); + else if (!lstat(dest_path[0], &st) && S_ISDIR(st.st_mode)) { dest_path[0] = add_slash(dest_path[0]); destination = copy_pathspec(dest_path[0], argv + i, count, 1); @@ -119,72 +129,71 @@ int cmd_mv(int argc, const char **argv, const char *prefix) /* Checking */ for (i = 0; i < count; i++) { + const char *src = source[i], *dst = destination[i]; + int length, src_is_dir; const char *bad = NULL; if (show_only) - printf("Checking rename of '%s' to '%s'\n", - source[i], destination[i]); + printf("Checking rename of '%s' to '%s'\n", src, dst); - if (lstat(source[i], &st) < 0) + length = strlen(src); + if (lstat(src, &st) < 0) bad = "bad source"; - - if (S_ISDIR(st.st_mode)) { - const char *dir = source[i], *dest_dir = destination[i]; - int first, last, len = strlen(dir); - - if (lstat(dest_dir, &st) == 0) { - bad = "cannot move directory over file"; - goto next; - } + else if (!strncmp(src, dst, length) && + (dst[length] == 0 || dst[length] == '/')) { + bad = "can not move directory into itself"; + } else if ((src_is_dir = S_ISDIR(st.st_mode)) + && lstat(dst, &st) == 0) + bad = "cannot move directory over file"; + else if (src_is_dir) { + int first, last; modes[i] = WORKING_DIRECTORY; - first = cache_name_pos(source[i], len); + first = cache_name_pos(src, length); if (first >= 0) - die ("Huh? %s/ is in index?", dir); + die ("Huh? %s/ is in index?", src); first = -1 - first; for (last = first; last < active_nr; last++) { const char *path = active_cache[last]->name; - if (strncmp(path, dir, len) || path[len] != '/') + if (strncmp(path, src, length) + || path[length] != '/') break; } if (last - first < 1) bad = "source directory is empty"; - else if (!bad) { - int j, dst_len = strlen(dest_dir); + else { + int j, dst_len; if (last - first > 0) { - source = realloc(source, + source = xrealloc(source, (count + last - first) * sizeof(char *)); - destination = realloc(destination, + destination = xrealloc(destination, (count + last - first) * sizeof(char *)); - modes = realloc(modes, + modes = xrealloc(modes, (count + last - first) * sizeof(enum update_mode)); } - dest_dir = add_slash(dest_dir); + dst = add_slash(dst); + dst_len = strlen(dst) - 1; for (j = 0; j < last - first; j++) { const char *path = active_cache[first + j]->name; source[count + j] = path; destination[count + j] = - prefix_path(dest_dir, dst_len, - path + len); + prefix_path(dst, dst_len, + path + length); modes[count + j] = INDEX; } count += last - first; } - - goto next; - } - - if (!bad && lstat(destination[i], &st) == 0) { + } else if (lstat(dst, &st) == 0) { bad = "destination exists"; if (force) { /* @@ -196,28 +205,17 @@ int cmd_mv(int argc, const char **argv, const char *prefix) " will overwrite!\n", bad); bad = NULL; - path_list_insert(destination[i], - &overwritten); + path_list_insert(dst, &overwritten); } else bad = "Cannot overwrite"; } - } - - if (!bad && - !strncmp(destination[i], source[i], strlen(source[i]))) - bad = "can not move directory into itself"; - - if (!bad && cache_name_pos(source[i], strlen(source[i])) < 0) + } else if (cache_name_pos(src, length) < 0) bad = "not under version control"; + else if (path_list_has_path(&src_for_dst, dst)) + bad = "multiple sources for the same target"; + else + path_list_insert(dst, &src_for_dst); - if (!bad) { - if (path_list_has_path(&src_for_dst, destination[i])) - bad = "multiple sources for the same target"; - else - path_list_insert(destination[i], &src_for_dst); - } - -next: if (bad) { if (ignore_errors) { if (--count > 0) { @@ -229,33 +227,32 @@ next: } } else die ("%s, source=%s, destination=%s", - bad, source[i], destination[i]); + bad, src, dst); } } for (i = 0; i < count; i++) { + const char *src = source[i], *dst = destination[i]; + enum update_mode mode = modes[i]; if (show_only || verbose) - printf("Renaming %s to %s\n", - source[i], destination[i]); - if (!show_only && modes[i] != INDEX && - rename(source[i], destination[i]) < 0 && - !ignore_errors) - die ("renaming %s failed: %s", - source[i], strerror(errno)); - - if (modes[i] == WORKING_DIRECTORY) + printf("Renaming %s to %s\n", src, dst); + if (!show_only && mode != INDEX && + rename(src, dst) < 0 && !ignore_errors) + die ("renaming %s failed: %s", src, strerror(errno)); + + if (mode == WORKING_DIRECTORY) continue; - if (cache_name_pos(source[i], strlen(source[i])) >= 0) { - path_list_insert(source[i], &deleted); + if (cache_name_pos(src, strlen(src)) >= 0) { + path_list_insert(src, &deleted); /* destination can be a directory with 1 file inside */ - if (path_list_has_path(&overwritten, destination[i])) - path_list_insert(destination[i], &changed); + if (path_list_has_path(&overwritten, dst)) + path_list_insert(dst, &changed); else - path_list_insert(destination[i], &added); + path_list_insert(dst, &added); } else - path_list_insert(destination[i], &added); + path_list_insert(dst, &added); } if (show_only) { @@ -265,10 +262,10 @@ next: } else { for (i = 0; i < changed.nr; i++) { const char *path = changed.items[i].path; - int i = cache_name_pos(path, strlen(path)); - struct cache_entry *ce = active_cache[i]; + int j = cache_name_pos(path, strlen(path)); + struct cache_entry *ce = active_cache[j]; - if (i < 0) + if (j < 0) die ("Huh? Cache entry for %s unknown?", path); refresh_cache_entry(ce, 0); } diff --git a/name-rev.c b/builtin-name-rev.c index f92f14e32..d44e782c9 100644 --- a/name-rev.c +++ b/builtin-name-rev.c @@ -1,4 +1,5 @@ #include <stdlib.h> +#include "builtin.h" #include "cache.h" #include "commit.h" #include "tag.h" @@ -74,7 +75,7 @@ copy_data: } } -static int tags_only = 0; +static int tags_only; static int name_ref(const char *path, const unsigned char *sha1) { @@ -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 861c7f08f..46f524dfc 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" @@ -52,17 +53,17 @@ struct object_entry { */ static unsigned char object_list_sha1[20]; -static int non_empty = 0; -static int no_reuse_delta = 0; -static int local = 0; -static int incremental = 0; +static int non_empty; +static int no_reuse_delta; +static int local; +static int incremental; static struct object_entry **sorted_by_sha, **sorted_by_type; -static struct object_entry *objects = NULL; -static int nr_objects = 0, nr_alloc = 0, nr_result = 0; +static struct object_entry *objects; +static int nr_objects, nr_alloc, nr_result; static const char *base_name; static unsigned char pack_file_sha1[20]; static int progress = 1; -static volatile sig_atomic_t progress_update = 0; +static volatile sig_atomic_t progress_update; static int window = 10; /* @@ -71,8 +72,8 @@ static int window = 10; * sorted_by_sha is also possible but this was easier to code and faster. * This hashtable is built after all the objects are seen. */ -static int *object_ix = NULL; -static int object_ix_hashsz = 0; +static int *object_ix; +static int object_ix_hashsz; /* * Pack index for existing packs give us easy access to the offsets into @@ -89,15 +90,15 @@ struct pack_revindex { struct packed_git *p; unsigned long *revindex; } *pack_revindex = NULL; -static int pack_revindex_hashsz = 0; +static int pack_revindex_hashsz; /* * stats */ -static int written = 0; -static int written_delta = 0; -static int reused = 0; -static int reused_delta = 0; +static int written; +static int written_delta; +static int reused; +static int reused_delta; static int pack_revindex_ix(struct packed_git *p) { @@ -269,6 +270,22 @@ static unsigned long write_object(struct sha1file *f, * and we do not need to deltify it. */ + if (!entry->in_pack && !entry->delta) { + unsigned char *map; + unsigned long mapsize; + map = map_sha1_file(entry->sha1, &mapsize); + if (map && !legacy_loose_object(map)) { + /* We can copy straight into the pack file */ + sha1write(f, map, mapsize); + munmap(map, mapsize); + written++; + reused++; + return mapsize; + } + if (map) + munmap(map, mapsize); + } + if (! to_reuse) { buf = read_sha1_file(entry->sha1, type, &size); if (!buf) @@ -424,7 +441,7 @@ static int locate_object_entry_hash(const unsigned char *sha1) memcpy(&ui, sha1, sizeof(unsigned int)); i = ui % object_ix_hashsz; while (0 < object_ix[i]) { - if (!memcmp(sha1, objects[object_ix[i]-1].sha1, 20)) + if (!hashcmp(sha1, objects[object_ix[i] - 1].sha1)) return i; if (++i == object_ix_hashsz) i = 0; @@ -517,7 +534,7 @@ static int add_object_entry(const unsigned char *sha1, unsigned hash, int exclud entry = objects + idx; nr_objects = idx + 1; memset(entry, 0, sizeof(*entry)); - memcpy(entry->sha1, sha1, 20); + hashcpy(entry->sha1, sha1); entry->hash = hash; if (object_ix_hashsz * 3 <= nr_objects * 4) @@ -590,7 +607,7 @@ static struct pbase_tree_cache *pbase_tree_get(const unsigned char *sha1) */ for (neigh = 0; neigh < 8; neigh++) { ent = pbase_tree_cache[my_ix]; - if (ent && !memcmp(ent->sha1, sha1, 20)) { + if (ent && !hashcmp(ent->sha1, sha1)) { ent->ref++; return ent; } @@ -632,7 +649,7 @@ static struct pbase_tree_cache *pbase_tree_get(const unsigned char *sha1) free(ent->tree_data); nent = ent; } - memcpy(nent->sha1, sha1, 20); + hashcpy(nent->sha1, sha1); nent->tree_data = data; nent->tree_size = size; nent->ref = 1; @@ -772,7 +789,7 @@ static void add_preferred_base(unsigned char *sha1) return; for (it = pbase_tree; it; it = it->next) { - if (!memcmp(it->pcache.sha1, tree_sha1, 20)) { + if (!hashcmp(it->pcache.sha1, tree_sha1)) { free(data); return; } @@ -782,7 +799,7 @@ static void add_preferred_base(unsigned char *sha1) it->next = pbase_tree; pbase_tree = it; - memcpy(it->pcache.sha1, tree_sha1, 20); + hashcpy(it->pcache.sha1, tree_sha1); it->pcache.tree_data = data; it->pcache.tree_size = size; } @@ -914,7 +931,7 @@ static struct object_entry **create_sorted_list(entry_sort_t sort) static int sha1_sort(const struct object_entry *a, const struct object_entry *b) { - return memcmp(a->sha1, b->sha1, 20); + return hashcmp(a->sha1, b->sha1); } static struct object_entry **create_final_object_list(void) @@ -1226,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]; @@ -1235,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/builtin-prune.c b/builtin-prune.c index 89ec7f142..fc885ce55 100644 --- a/builtin-prune.c +++ b/builtin-prune.c @@ -11,7 +11,7 @@ #include "cache-tree.h" static const char prune_usage[] = "git-prune [-n]"; -static int show_only = 0; +static int show_only; static struct rev_info revs; static int prune_object(char *path, const char *filename, const unsigned char *sha1) diff --git a/builtin-push.c b/builtin-push.c index 53bc378f7..ada8338cc 100644 --- a/builtin-push.c +++ b/builtin-push.c @@ -10,14 +10,14 @@ static const char push_usage[] = "git-push [--all] [--tags] [-f | --force] <repository> [<refspec>...]"; -static int all = 0, tags = 0, force = 0, thin = 1; -static const char *execute = NULL; +static int all, tags, force, thin = 1; +static const char *execute; #define BUF_SIZE (2084) static char buffer[BUF_SIZE]; -static const char **refspec = NULL; -static int refspec_nr = 0; +static const char **refspec; +static int refspec_nr; static void add_refspec(const char *ref) { @@ -32,10 +32,8 @@ static int expand_one_ref(const char *ref, const unsigned char *sha1) /* Ignore the "refs/" at the beginning of the refname */ ref += 5; - if (strncmp(ref, "tags/", 5)) - return 0; - - add_refspec(strdup(ref)); + if (!strncmp(ref, "tags/", 5)) + add_refspec(strdup(ref)); return 0; } @@ -234,7 +232,7 @@ static int do_push(const char *repo) common_argc = argc; for (i = 0; i < n; i++) { - int error; + int err; int dest_argc = common_argc; int dest_refspec_nr = refspec_nr; const char **dest_refspec = refspec; @@ -250,10 +248,10 @@ static int do_push(const char *repo) while (dest_refspec_nr--) argv[dest_argc++] = *dest_refspec++; argv[dest_argc] = NULL; - error = run_command_v(argc, argv); - if (!error) + err = run_command_v(argc, argv); + if (!err) continue; - switch (error) { + switch (err) { case -ERR_RUN_COMMAND_FORK: die("unable to fork for %s", sender); case -ERR_RUN_COMMAND_EXEC: @@ -264,7 +262,7 @@ static int do_push(const char *repo) case -ERR_RUN_COMMAND_WAITPID_NOEXIT: die("%s died with strange error", sender); default: - return -error; + return -err; } } return 0; diff --git a/builtin-read-tree.c b/builtin-read-tree.c index 8da8acbb0..c1867d2a0 100644 --- a/builtin-read-tree.c +++ b/builtin-read-tree.c @@ -12,7 +12,7 @@ #include "unpack-trees.h" #include "builtin.h" -static struct object_list *trees = NULL; +static struct object_list *trees; static int list_tree(unsigned char *sha1) { @@ -53,7 +53,7 @@ static void prime_cache_tree_rec(struct cache_tree *it, struct tree *tree) struct name_entry entry; int cnt; - memcpy(it->sha1, tree->object.sha1, 20); + hashcpy(it->sha1, tree->object.sha1); desc.buf = tree->buffer; desc.size = tree->size; cnt = 0; diff --git a/builtin-repo-config.c b/builtin-repo-config.c index c821e2271..c41648020 100644 --- a/builtin-repo-config.c +++ b/builtin-repo-config.c @@ -5,14 +5,14 @@ static const char git_config_set_usage[] = "git-repo-config [ --bool | --int ] [--get | --get-all | --get-regexp | --replace-all | --unset | --unset-all] name [value [value_regex]] | --list"; -static char* key = NULL; -static regex_t* key_regexp = NULL; -static regex_t* regexp = NULL; -static int show_keys = 0; -static int use_key_regexp = 0; -static int do_all = 0; -static int do_not_match = 0; -static int seen = 0; +static char *key; +static regex_t *key_regexp; +static regex_t *regexp; +static int show_keys; +static int use_key_regexp; +static int do_all; +static int do_not_match; +static int seen; static enum { T_RAW, T_INT, T_BOOL } type = T_RAW; static int show_all_config(const char *key_, const char *value_) diff --git a/builtin-rev-list.c b/builtin-rev-list.c index 0dee1734a..bc48a3e23 100644 --- a/builtin-rev-list.c +++ b/builtin-rev-list.c @@ -39,9 +39,9 @@ static const char rev_list_usage[] = static struct rev_info revs; -static int bisect_list = 0; -static int show_timestamp = 0; -static int hdr_termination = 0; +static int bisect_list; +static int show_timestamp; +static int hdr_termination; static const char *header_prefix; static void show_commit(struct commit *commit) diff --git a/builtin-rev-parse.c b/builtin-rev-parse.c index aca4a3603..fd3ccc854 100644 --- a/builtin-rev-parse.c +++ b/builtin-rev-parse.c @@ -15,16 +15,16 @@ #define DO_NONFLAGS 8 static int filter = ~0; -static const char *def = NULL; +static const char *def; #define NORMAL 0 #define REVERSED 1 static int show_type = NORMAL; -static int symbolic = 0; -static int abbrev = 0; -static int output_sq = 0; +static int symbolic; +static int abbrev; +static int output_sq; -static int revs_count = 0; +static int revs_count; /* * Some arguments are relevant "revision" arguments, diff --git a/builtin-show-branch.c b/builtin-show-branch.c index 2a1b848f6..18786f88e 100644 --- a/builtin-show-branch.c +++ b/builtin-show-branch.c @@ -8,9 +8,9 @@ static const char show_branch_usage[] = "git-show-branch [--sparse] [--current] [--all] [--heads] [--tags] [--topo-order] [--more=count | --list | --independent | --merge-base ] [--topics] [<refs>...]"; -static int default_num = 0; -static int default_alloc = 0; -static const char **default_arg = NULL; +static int default_num; +static int default_alloc; +static const char **default_arg; #define UNINTERESTING 01 @@ -378,7 +378,7 @@ static int append_head_ref(const char *refname, const unsigned char *sha1) /* If both heads/foo and tags/foo exists, get_sha1 would * get confused. */ - if (get_sha1(refname + ofs, tmp) || memcmp(tmp, sha1, 20)) + if (get_sha1(refname + ofs, tmp) || hashcmp(tmp, sha1)) ofs = 5; return append_ref(refname + ofs, sha1); } @@ -442,7 +442,7 @@ static int rev_is_head(char *head_path, int headlen, char *name, { int namelen; if ((!head_path[0]) || - (head_sha1 && sha1 && memcmp(head_sha1, sha1, 20))) + (head_sha1 && sha1 && hashcmp(head_sha1, sha1))) return 0; namelen = strlen(name); if ((headlen < namelen) || diff --git a/symbolic-ref.c b/builtin-symbolic-ref.c index 193c87c17..b4ec6f28e 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 215892b69..61a413590 100644 --- a/builtin-tar-tree.c +++ b/builtin-tar-tree.c @@ -14,7 +14,7 @@ #define BLOCKSIZE (RECORDSIZE * 20) static const char tar_tree_usage[] = -"git-tar-tree [--remote=<repo>] <ent> [basedir]"; +"git-tar-tree [--remote=<repo>] <tree-ish> [basedir]"; static char block[BLOCKSIZE]; static unsigned long offset; @@ -22,30 +22,11 @@ static unsigned long offset; static time_t archive_time; static int tar_umask; -/* tries hard to write, either succeeds or dies in the attempt */ -static void reliable_write(const void *data, unsigned long size) -{ - const char *buf = data; - - while (size > 0) { - long ret = xwrite(1, buf, size); - if (ret < 0) { - if (errno == EPIPE) - exit(0); - die("git-tar-tree: %s", strerror(errno)); - } else if (!ret) { - die("git-tar-tree: disk full?"); - } - size -= ret; - buf += ret; - } -} - /* writes out the whole block, but only if it is full */ static void write_if_needed(void) { if (offset == BLOCKSIZE) { - reliable_write(block, BLOCKSIZE); + write_or_die(1, block, BLOCKSIZE); offset = 0; } } @@ -70,7 +51,7 @@ static void write_blocked(const void *data, unsigned long size) write_if_needed(); } while (size >= BLOCKSIZE) { - reliable_write(buf, BLOCKSIZE); + write_or_die(1, buf, BLOCKSIZE); size -= BLOCKSIZE; buf += BLOCKSIZE; } @@ -94,10 +75,10 @@ static void write_trailer(void) { int tail = BLOCKSIZE - offset; memset(block + offset, 0, tail); - reliable_write(block, BLOCKSIZE); + write_or_die(1, block, BLOCKSIZE); if (tail < 2 * RECORDSIZE) { memset(block, 0, offset); - reliable_write(block, BLOCKSIZE); + write_or_die(1, block, BLOCKSIZE); } } @@ -294,7 +275,7 @@ static void traverse_tree(struct tree_desc *tree, struct strbuf *path) } } -int git_tar_config(const char *var, const char *value) +static int git_tar_config(const char *var, const char *value) { if (!strcmp(var, "tar.umask")) { if (!strcmp(value, "user")) { diff --git a/unpack-objects.c b/builtin-unpack-objects.c index 48c1ee796..ca0ebc258 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" @@ -94,7 +95,7 @@ static void add_delta_to_list(unsigned char *base_sha1, void *delta, unsigned lo { struct delta_info *info = xmalloc(sizeof(*info)); - memcpy(info->base_sha1, base_sha1, 20); + hashcpy(info->base_sha1, base_sha1); info->size = size; info->delta = delta; info->next = delta_list; @@ -112,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; @@ -135,7 +136,7 @@ static void added_object(unsigned char *sha1, const char *type, void *data, unsi struct delta_info *info; while ((info = *p) != NULL) { - if (!memcmp(info->base_sha1, sha1, 20)) { + if (!hashcmp(info->base_sha1, sha1)) { *p = info->next; p = &delta_list; resolve_delta(type, data, size, info->delta, info->size); @@ -172,7 +173,7 @@ static int unpack_delta_entry(unsigned long delta_size) unsigned char base_sha1[20]; int result; - memcpy(base_sha1, fill(20), 20); + hashcpy(base_sha1, fill(20)); use(20); delta_data = get_data(delta_size); @@ -260,12 +261,12 @@ static void unpack_all(void) die("unresolved deltas left after unpacking"); } -int main(int argc, char **argv) +int cmd_unpack_objects(int argc, const char **argv, const char *prefix) { int i; unsigned char sha1[20]; - setup_git_directory(); + git_config(git_default_config); quiet = !isatty(2); @@ -291,7 +292,7 @@ int main(int argc, char **argv) unpack_all(); SHA1_Update(&ctx, buffer, offset); SHA1_Final(sha1, &ctx); - if (memcmp(fill(20), sha1, 20)) + if (hashcmp(fill(20), sha1)) die("final sha1 did not match"); use(20); diff --git a/builtin-update-index.c b/builtin-update-index.c index d2556f376..0620e779b 100644 --- a/builtin-update-index.c +++ b/builtin-update-index.c @@ -23,7 +23,7 @@ static int allow_replace; static int info_only; static int force_remove; static int verbose; -static int mark_valid_only = 0; +static int mark_valid_only; #define MARK_VALID 1 #define UNMARK_VALID 2 @@ -142,7 +142,7 @@ static int add_cacheinfo(unsigned int mode, const unsigned char *sha1, size = cache_entry_size(len); ce = xcalloc(1, size); - memcpy(ce->sha1, sha1, 20); + hashcpy(ce->sha1, sha1); memcpy(ce->name, path, len); ce->ce_flags = create_ce_flags(len, stage); ce->ce_mode = create_ce_mode(mode); @@ -306,7 +306,7 @@ static void read_index_info(int line_termination) } static const char update_index_usage[] = -"git-update-index [-q] [--add] [--replace] [--remove] [--unmerged] [--refresh] [--really-refresh] [--cacheinfo] [--chmod=(+|-)x] [--assume-unchanged] [--info-only] [--force-remove] [--stdin] [--index-info] [--unresolve] [--again] [--ignore-missing] [-z] [--verbose] [--] <file>..."; +"git-update-index [-q] [--add] [--replace] [--remove] [--unmerged] [--refresh] [--really-refresh] [--cacheinfo] [--chmod=(+|-)x] [--assume-unchanged] [--info-only] [--force-remove] [--stdin] [--index-info] [--unresolve] [--again | -g] [--ignore-missing] [-z] [--verbose] [--] <file>..."; static unsigned char head_sha1[20]; static unsigned char merge_head_sha1[20]; @@ -333,7 +333,7 @@ static struct cache_entry *read_one_ent(const char *which, size = cache_entry_size(namelen); ce = xcalloc(1, size); - memcpy(ce->sha1, sha1, 20); + hashcpy(ce->sha1, sha1); memcpy(ce->name, path, namelen); ce->ce_flags = create_ce_flags(namelen, stage); ce->ce_mode = create_ce_mode(mode); @@ -378,7 +378,7 @@ static int unresolve_one(const char *path) ret = -1; goto free_return; } - if (!memcmp(ce_2->sha1, ce_3->sha1, 20) && + if (!hashcmp(ce_2->sha1, ce_3->sha1) && ce_2->ce_mode == ce_3->ce_mode) { fprintf(stderr, "%s: identical in both, skipping.\n", path); @@ -460,7 +460,7 @@ static int do_reupdate(int ac, const char **av, old = read_one_ent(NULL, head_sha1, ce->name, ce_namelen(ce), 0); if (old && ce->ce_mode == old->ce_mode && - !memcmp(ce->sha1, old->sha1, 20)) { + !hashcmp(ce->sha1, old->sha1)) { free(old); continue; /* unchanged */ } @@ -595,7 +595,7 @@ int cmd_update_index(int argc, const char **argv, const char *prefix) active_cache_changed = 0; goto finish; } - if (!strcmp(path, "--again")) { + if (!strcmp(path, "--again") || !strcmp(path, "-g")) { has_errors = do_reupdate(argc - i, argv + i, prefix, prefix_length); if (has_errors) diff --git a/builtin-update-ref.c b/builtin-update-ref.c index 5bd71825f..90a3da53a 100644 --- a/builtin-update-ref.c +++ b/builtin-update-ref.c @@ -44,7 +44,7 @@ int cmd_update_ref(int argc, const char **argv, const char *prefix) if (get_sha1(value, sha1)) die("%s: not a valid SHA1", value); - memset(oldsha1, 0, 20); + hashclr(oldsha1); if (oldval && get_sha1(oldval, oldsha1)) die("%s: not a valid old SHA1", oldval); diff --git a/verify-pack.c b/builtin-verify-pack.c index 357970da3..7d39d9bcd 100644 --- a/verify-pack.c +++ b/builtin-verify-pack.c @@ -1,3 +1,4 @@ +#include "builtin.h" #include "cache.h" #include "pack.h" @@ -47,28 +48,28 @@ static int verify_one_pack(const char *path, int verbose) static const char verify_pack_usage[] = "git-verify-pack [-v] <pack>..."; -int main(int ac, char **av) +int cmd_verify_pack(int argc, const char **argv, const char *prefix) { int err = 0; int verbose = 0; int no_more_options = 0; int nothing_done = 1; - while (1 < ac) { - if (!no_more_options && av[1][0] == '-') { - if (!strcmp("-v", av[1])) + while (1 < argc) { + if (!no_more_options && argv[1][0] == '-') { + if (!strcmp("-v", argv[1])) verbose = 1; - else if (!strcmp("--", av[1])) + else if (!strcmp("--", argv[1])) no_more_options = 1; else usage(verify_pack_usage); } else { - if (verify_one_pack(av[1], verbose)) + if (verify_one_pack(argv[1], verbose)) err = 1; nothing_done = 0; } - ac--; av++; + argc--; argv++; } if (nothing_done) diff --git a/builtin-write-tree.c b/builtin-write-tree.c index ca06149f1..50670dc7b 100644 --- a/builtin-write-tree.c +++ b/builtin-write-tree.c @@ -50,10 +50,10 @@ int write_tree(unsigned char *sha1, int missing_ok, const char *prefix) if (prefix) { struct cache_tree *subtree = cache_tree_find(active_cache_tree, prefix); - memcpy(sha1, subtree->sha1, 20); + hashcpy(sha1, subtree->sha1); } else - memcpy(sha1, active_cache_tree->sha1, 20); + hashcpy(sha1, active_cache_tree->sha1); rollback_lock_file(lock_file); diff --git a/builtin-zip-tree.c b/builtin-zip-tree.c new file mode 100644 index 000000000..a5b834d36 --- /dev/null +++ b/builtin-zip-tree.c @@ -0,0 +1,353 @@ +/* + * Copyright (c) 2006 Rene Scharfe + */ +#include <time.h> +#include "cache.h" +#include "commit.h" +#include "blob.h" +#include "tree.h" +#include "quote.h" +#include "builtin.h" + +static const char zip_tree_usage[] = +"git-zip-tree [-0|...|-9] <tree-ish> [ <base> ]"; + +static int zip_date; +static int zip_time; + +static unsigned char *zip_dir; +static unsigned int zip_dir_size; + +static unsigned int zip_offset; +static unsigned int zip_dir_offset; +static unsigned int zip_dir_entries; + +#define ZIP_DIRECTORY_MIN_SIZE (1024 * 1024) + +struct zip_local_header { + unsigned char magic[4]; + unsigned char version[2]; + unsigned char flags[2]; + unsigned char compression_method[2]; + unsigned char mtime[2]; + unsigned char mdate[2]; + unsigned char crc32[4]; + unsigned char compressed_size[4]; + unsigned char size[4]; + unsigned char filename_length[2]; + unsigned char extra_length[2]; +}; + +struct zip_dir_header { + unsigned char magic[4]; + unsigned char creator_version[2]; + unsigned char version[2]; + unsigned char flags[2]; + unsigned char compression_method[2]; + unsigned char mtime[2]; + unsigned char mdate[2]; + unsigned char crc32[4]; + unsigned char compressed_size[4]; + unsigned char size[4]; + unsigned char filename_length[2]; + unsigned char extra_length[2]; + unsigned char comment_length[2]; + unsigned char disk[2]; + unsigned char attr1[2]; + unsigned char attr2[4]; + unsigned char offset[4]; +}; + +struct zip_dir_trailer { + unsigned char magic[4]; + unsigned char disk[2]; + unsigned char directory_start_disk[2]; + unsigned char entries_on_this_disk[2]; + unsigned char entries[2]; + unsigned char size[4]; + unsigned char offset[4]; + unsigned char comment_length[2]; +}; + +static void copy_le16(unsigned char *dest, unsigned int n) +{ + dest[0] = 0xff & n; + dest[1] = 0xff & (n >> 010); +} + +static void copy_le32(unsigned char *dest, unsigned int n) +{ + dest[0] = 0xff & n; + dest[1] = 0xff & (n >> 010); + dest[2] = 0xff & (n >> 020); + dest[3] = 0xff & (n >> 030); +} + +static void *zlib_deflate(void *data, unsigned long size, + unsigned long *compressed_size) +{ + z_stream stream; + unsigned long maxsize; + void *buffer; + int result; + + memset(&stream, 0, sizeof(stream)); + deflateInit(&stream, zlib_compression_level); + maxsize = deflateBound(&stream, size); + buffer = xmalloc(maxsize); + + stream.next_in = data; + stream.avail_in = size; + stream.next_out = buffer; + stream.avail_out = maxsize; + + do { + result = deflate(&stream, Z_FINISH); + } while (result == Z_OK); + + if (result != Z_STREAM_END) { + free(buffer); + return NULL; + } + + deflateEnd(&stream); + *compressed_size = stream.total_out; + + return buffer; +} + +static char *construct_path(const char *base, int baselen, + const char *filename, int isdir, int *pathlen) +{ + int filenamelen = strlen(filename); + int len = baselen + filenamelen; + char *path, *p; + + if (isdir) + len++; + p = path = xmalloc(len + 1); + + memcpy(p, base, baselen); + p += baselen; + memcpy(p, filename, filenamelen); + p += filenamelen; + if (isdir) + *p++ = '/'; + *p = '\0'; + + *pathlen = len; + + return path; +} + +static int write_zip_entry(const unsigned char *sha1, + const char *base, int baselen, + const char *filename, unsigned mode, int stage) +{ + struct zip_local_header header; + struct zip_dir_header dirent; + unsigned long compressed_size; + unsigned long uncompressed_size; + unsigned long crc; + unsigned long direntsize; + unsigned long size; + int method; + int result = -1; + int pathlen; + unsigned char *out; + char *path; + char type[20]; + void *buffer = NULL; + void *deflated = NULL; + + crc = crc32(0, Z_NULL, 0); + + path = construct_path(base, baselen, filename, S_ISDIR(mode), &pathlen); + if (pathlen > 0xffff) { + error("path too long (%d chars, SHA1: %s): %s", pathlen, + sha1_to_hex(sha1), path); + goto out; + } + + if (S_ISDIR(mode)) { + method = 0; + result = READ_TREE_RECURSIVE; + out = NULL; + uncompressed_size = 0; + compressed_size = 0; + } else if (S_ISREG(mode)) { + method = zlib_compression_level == 0 ? 0 : 8; + result = 0; + buffer = read_sha1_file(sha1, type, &size); + if (!buffer) + die("cannot read %s", sha1_to_hex(sha1)); + crc = crc32(crc, buffer, size); + out = buffer; + uncompressed_size = size; + compressed_size = size; + } else { + error("unsupported file mode: 0%o (SHA1: %s)", mode, + sha1_to_hex(sha1)); + goto out; + } + + if (method == 8) { + deflated = zlib_deflate(buffer, size, &compressed_size); + if (deflated && compressed_size - 6 < size) { + /* ZLIB --> raw compressed data (see RFC 1950) */ + /* CMF and FLG ... */ + out = (unsigned char *)deflated + 2; + compressed_size -= 6; /* ... and ADLER32 */ + } else { + method = 0; + compressed_size = size; + } + } + + /* make sure we have enough free space in the dictionary */ + direntsize = sizeof(struct zip_dir_header) + pathlen; + while (zip_dir_size < zip_dir_offset + direntsize) { + zip_dir_size += ZIP_DIRECTORY_MIN_SIZE; + zip_dir = xrealloc(zip_dir, zip_dir_size); + } + + copy_le32(dirent.magic, 0x02014b50); + copy_le16(dirent.creator_version, 0); + copy_le16(dirent.version, 20); + copy_le16(dirent.flags, 0); + copy_le16(dirent.compression_method, method); + copy_le16(dirent.mtime, zip_time); + copy_le16(dirent.mdate, zip_date); + copy_le32(dirent.crc32, crc); + copy_le32(dirent.compressed_size, compressed_size); + copy_le32(dirent.size, uncompressed_size); + copy_le16(dirent.filename_length, pathlen); + copy_le16(dirent.extra_length, 0); + copy_le16(dirent.comment_length, 0); + copy_le16(dirent.disk, 0); + copy_le16(dirent.attr1, 0); + copy_le32(dirent.attr2, 0); + copy_le32(dirent.offset, zip_offset); + memcpy(zip_dir + zip_dir_offset, &dirent, sizeof(struct zip_dir_header)); + zip_dir_offset += sizeof(struct zip_dir_header); + memcpy(zip_dir + zip_dir_offset, path, pathlen); + zip_dir_offset += pathlen; + zip_dir_entries++; + + copy_le32(header.magic, 0x04034b50); + copy_le16(header.version, 20); + copy_le16(header.flags, 0); + copy_le16(header.compression_method, method); + copy_le16(header.mtime, zip_time); + copy_le16(header.mdate, zip_date); + copy_le32(header.crc32, crc); + copy_le32(header.compressed_size, compressed_size); + copy_le32(header.size, uncompressed_size); + copy_le16(header.filename_length, pathlen); + copy_le16(header.extra_length, 0); + write_or_die(1, &header, sizeof(struct zip_local_header)); + zip_offset += sizeof(struct zip_local_header); + write_or_die(1, path, pathlen); + zip_offset += pathlen; + if (compressed_size > 0) { + write_or_die(1, out, compressed_size); + zip_offset += compressed_size; + } + +out: + free(buffer); + free(deflated); + free(path); + + return result; +} + +static void write_zip_trailer(const unsigned char *sha1) +{ + struct zip_dir_trailer trailer; + + copy_le32(trailer.magic, 0x06054b50); + copy_le16(trailer.disk, 0); + copy_le16(trailer.directory_start_disk, 0); + copy_le16(trailer.entries_on_this_disk, zip_dir_entries); + copy_le16(trailer.entries, zip_dir_entries); + copy_le32(trailer.size, zip_dir_offset); + copy_le32(trailer.offset, zip_offset); + copy_le16(trailer.comment_length, sha1 ? 40 : 0); + + write_or_die(1, zip_dir, zip_dir_offset); + write_or_die(1, &trailer, sizeof(struct zip_dir_trailer)); + if (sha1) + write_or_die(1, sha1_to_hex(sha1), 40); +} + +static void dos_time(time_t *time, int *dos_date, int *dos_time) +{ + struct tm *t = localtime(time); + + *dos_date = t->tm_mday + (t->tm_mon + 1) * 32 + + (t->tm_year + 1900 - 1980) * 512; + *dos_time = t->tm_sec / 2 + t->tm_min * 32 + t->tm_hour * 2048; +} + +int cmd_zip_tree(int argc, const char **argv, const char *prefix) +{ + unsigned char sha1[20]; + struct tree *tree; + struct commit *commit; + time_t archive_time; + char *base; + int baselen; + + git_config(git_default_config); + + if (argc > 1 && argv[1][0] == '-') { + if (isdigit(argv[1][1]) && argv[1][2] == '\0') { + zlib_compression_level = argv[1][1] - '0'; + argc--; + argv++; + } + } + + switch (argc) { + case 3: + base = strdup(argv[2]); + baselen = strlen(base); + break; + case 2: + base = strdup(""); + baselen = 0; + break; + default: + usage(zip_tree_usage); + } + + if (get_sha1(argv[1], sha1)) + die("Not a valid object name %s", argv[1]); + + commit = lookup_commit_reference_gently(sha1, 1); + archive_time = commit ? commit->date : time(NULL); + dos_time(&archive_time, &zip_date, &zip_time); + + zip_dir = xmalloc(ZIP_DIRECTORY_MIN_SIZE); + zip_dir_size = ZIP_DIRECTORY_MIN_SIZE; + + tree = parse_tree_indirect(sha1); + if (!tree) + die("not a tree object"); + + if (baselen > 0) { + write_zip_entry(tree->object.sha1, "", 0, base, 040777, 0); + base = xrealloc(base, baselen + 1); + base[baselen] = '/'; + baselen++; + base[baselen] = '\0'; + } + read_tree_recursive(tree, base, baselen, 0, NULL, write_zip_entry); + write_zip_trailer(commit ? commit->object.sha1 : NULL); + + free(zip_dir); + free(base); + + return 0; +} @@ -8,57 +8,58 @@ extern const char git_version_string[]; extern const char git_usage_string[]; extern void help_unknown_cmd(const char *cmd); +extern int mailinfo(FILE *in, FILE *out, int ks, const char *encoding, const char *msg, const char *patch); +extern int split_mbox(const char **mbox, const char *dir, int allow_bare, int nr_prec, int skip); +extern void stripspace(FILE *in, FILE *out); +extern int write_tree(unsigned char *sha1, int missing_ok, const char *prefix); -extern int cmd_help(int argc, const char **argv, const char *prefix); -extern int cmd_version(int argc, const char **argv, const char *prefix); - -extern int cmd_whatchanged(int argc, const char **argv, const char *prefix); -extern int cmd_show(int argc, const char **argv, const char *prefix); -extern int cmd_log(int argc, const char **argv, const char *prefix); -extern int cmd_diff(int argc, const char **argv, const char *prefix); -extern int cmd_format_patch(int argc, const char **argv, const char *prefix); -extern int cmd_count_objects(int argc, const char **argv, const char *prefix); - -extern int cmd_prune(int argc, const char **argv, const char *prefix); -extern int cmd_prune_packed(int argc, const char **argv, const char *prefix); - -extern int cmd_push(int argc, const char **argv, const char *prefix); -extern int cmd_grep(int argc, const char **argv, const char *prefix); -extern int cmd_rm(int argc, const char **argv, const char *prefix); extern int cmd_add(int argc, const char **argv, const char *prefix); -extern int cmd_rev_list(int argc, const char **argv, const char *prefix); +extern int cmd_apply(int argc, const char **argv, const char *prefix); +extern int cmd_cat_file(int argc, const char **argv, const char *prefix); +extern int cmd_checkout_index(int argc, const char **argv, const char *prefix); extern int cmd_check_ref_format(int argc, const char **argv, const char *prefix); -extern int cmd_init_db(int argc, const char **argv, const char *prefix); -extern int cmd_tar_tree(int argc, const char **argv, const char *prefix); -extern int cmd_upload_tar(int argc, const char **argv, const char *prefix); -extern int cmd_get_tar_commit_id(int argc, const char **argv, const char *prefix); -extern int cmd_ls_files(int argc, const char **argv, const char *prefix); -extern int cmd_ls_tree(int argc, const char **argv, const char *prefix); -extern int cmd_read_tree(int argc, const char **argv, const char *prefix); extern int cmd_commit_tree(int argc, const char **argv, const char *prefix); -extern int cmd_apply(int argc, const char **argv, const char *prefix); -extern int cmd_show_branch(int argc, const char **argv, const char *prefix); +extern int cmd_count_objects(int argc, const char **argv, const char *prefix); extern int cmd_diff_files(int argc, const char **argv, const char *prefix); extern int cmd_diff_index(int argc, const char **argv, const char *prefix); +extern int cmd_diff(int argc, const char **argv, const char *prefix); extern int cmd_diff_stages(int argc, const char **argv, const char *prefix); extern int cmd_diff_tree(int argc, const char **argv, const char *prefix); -extern int cmd_cat_file(int argc, const char **argv, const char *prefix); -extern int cmd_rev_parse(int argc, const char **argv, const char *prefix); -extern int cmd_update_index(int argc, const char **argv, const char *prefix); -extern int cmd_update_ref(int argc, const char **argv, const char *prefix); extern int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix); +extern int cmd_format_patch(int argc, const char **argv, const char *prefix); +extern int cmd_get_tar_commit_id(int argc, const char **argv, const char *prefix); +extern int cmd_grep(int argc, const char **argv, const char *prefix); +extern int cmd_help(int argc, const char **argv, const char *prefix); +extern int cmd_init_db(int argc, const char **argv, const char *prefix); +extern int cmd_log(int argc, const char **argv, const char *prefix); +extern int cmd_ls_files(int argc, const char **argv, const char *prefix); +extern int cmd_ls_tree(int argc, const char **argv, const char *prefix); +extern int cmd_mailinfo(int argc, const char **argv, const char *prefix); +extern int cmd_mailsplit(int argc, const char **argv, const char *prefix); extern int cmd_mv(int argc, const char **argv, const char *prefix); +extern int cmd_name_rev(int argc, const char **argv, const char *prefix); +extern int cmd_pack_objects(int argc, const char **argv, const char *prefix); +extern int cmd_prune(int argc, const char **argv, const char *prefix); +extern int cmd_prune_packed(int argc, const char **argv, const char *prefix); +extern int cmd_push(int argc, const char **argv, const char *prefix); +extern int cmd_read_tree(int argc, const char **argv, const char *prefix); extern int cmd_repo_config(int argc, const char **argv, const char *prefix); - +extern int cmd_rev_list(int argc, const char **argv, const char *prefix); +extern int cmd_rev_parse(int argc, const char **argv, const char *prefix); +extern int cmd_rm(int argc, const char **argv, const char *prefix); +extern int cmd_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_zip_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 write_tree(unsigned char *sha1, int missing_ok, const char *prefix); +extern int cmd_verify_pack(int argc, const char **argv, const char *prefix); -extern int cmd_mailsplit(int argc, const char **argv, const char *prefix); -extern int split_mbox(const char **mbox, const char *dir, int allow_bare, int nr_prec, int skip); - -extern int cmd_mailinfo(int argc, const char **argv, const char *prefix); -extern int mailinfo(FILE *in, FILE *out, int ks, const char *encoding, const char *msg, const char *patch); - -extern int cmd_stripspace(int argc, const char **argv, const char *prefix); -extern void stripspace(FILE *in, FILE *out); #endif diff --git a/cache-tree.c b/cache-tree.c index d9f7e1e3d..323c68a67 100644 --- a/cache-tree.c +++ b/cache-tree.c @@ -335,7 +335,7 @@ static int update_one(struct cache_tree *it, offset += sprintf(buffer + offset, "%o %.*s", mode, entlen, path + baselen); buffer[offset++] = 0; - memcpy(buffer + offset, sha1, 20); + hashcpy((unsigned char*)buffer + offset, sha1); offset += 20; #if DEBUG @@ -412,7 +412,7 @@ static void *write_one(struct cache_tree *it, #endif if (0 <= it->entry_count) { - memcpy(buffer + *offset, it->sha1, 20); + hashcpy((unsigned char*)buffer + *offset, it->sha1); *offset += 20; } for (i = 0; i < it->subtree_nr; i++) { @@ -478,7 +478,7 @@ static struct cache_tree *read_one(const char **buffer, unsigned long *size_p) if (0 <= it->entry_count) { if (size < 20) goto free_return; - memcpy(it->sha1, buf, 20); + hashcpy(it->sha1, (unsigned char*)buf); buf += 20; size -= 20; } @@ -123,7 +123,7 @@ extern int cache_errno; #define INDEX_ENVIRONMENT "GIT_INDEX_FILE" #define GRAFT_ENVIRONMENT "GIT_GRAFT_FILE" -extern char *get_git_dir(void); +extern const char *get_git_dir(void); extern char *get_object_directory(void); extern char *get_refs_directory(void); extern char *get_index_file(void); @@ -211,6 +211,22 @@ extern char *sha1_pack_name(const unsigned char *sha1); extern char *sha1_pack_index_name(const unsigned char *sha1); extern const char *find_unique_abbrev(const unsigned char *sha1, int); extern const unsigned char null_sha1[20]; +static inline int is_null_sha1(const unsigned char *sha1) +{ + return !memcmp(sha1, null_sha1, 20); +} +static inline int hashcmp(const unsigned char *sha1, const unsigned char *sha2) +{ + return memcmp(sha1, sha2, 20); +} +static inline void hashcpy(unsigned char *sha_dst, const unsigned char *sha_src) +{ + memcpy(sha_dst, sha_src, 20); +} +static inline void hashclr(unsigned char *hash) +{ + memset(hash, 0, 20); +} int git_mkstemp(char *path, size_t n, const char *template); @@ -245,6 +261,8 @@ extern int move_temp_to_file(const char *tmpfile, char *filename); extern int has_sha1_pack(const unsigned char *sha1); extern int has_sha1_file(const unsigned char *sha1); +extern void *map_sha1_file(const unsigned char *sha1, unsigned long *); +extern int legacy_loose_object(unsigned char *); extern int has_pack_file(const unsigned char *sha1); extern int has_pack_index(const unsigned char *sha1); @@ -269,7 +287,7 @@ extern void *read_object_with_reference(const unsigned char *sha1, unsigned long *size, unsigned char *sha1_ret); -const char *show_date(unsigned long time, int timezone); +const char *show_date(unsigned long time, int timezone, int relative); const char *show_rfc2822_date(unsigned long time, int timezone); int parse_date(const char *date, char *buf, int bufsize); void datestamp(char *buf, int bufsize); @@ -379,6 +397,7 @@ extern char git_default_name[MAX_GITNAME]; extern char git_commit_encoding[MAX_ENCODING_LENGTH]; extern int copy_fd(int ifd, int ofd); +extern void write_or_die(int fd, const void *buf, size_t count); /* Finish off pack transfer receiving end */ extern int receive_unpack_pack(int fd[2], const char *me, int quiet, int); diff --git a/check-racy.c b/check-racy.c new file mode 100644 index 000000000..d6a08b4a5 --- /dev/null +++ b/check-racy.c @@ -0,0 +1,28 @@ +#include "cache.h" + +int main(int ac, char **av) +{ + int i; + int dirty, clean, racy; + + dirty = clean = racy = 0; + read_cache(); + for (i = 0; i < active_nr; i++) { + struct cache_entry *ce = active_cache[i]; + struct stat st; + + if (lstat(ce->name, &st)) { + error("lstat(%s): %s", ce->name, strerror(errno)); + continue; + } + + if (ce_match_stat(ce, &st, 0)) + dirty++; + else if (ce_match_stat(ce, &st, 2)) + racy++; + else + clean++; + } + printf("dirty %d, clean %d, racy %d\n", dirty, clean, racy); + return 0; +} diff --git a/combine-diff.c b/combine-diff.c index ba8baca0a..46d9121ba 100644 --- a/combine-diff.c +++ b/combine-diff.c @@ -7,13 +7,6 @@ #include "xdiff-interface.h" #include "log-tree.h" -static int uninteresting(struct diff_filepair *p) -{ - if (diff_unmodified_pair(p)) - return 1; - return 0; -} - static struct combine_diff_path *intersect_paths(struct combine_diff_path *curr, int n, int num_parent) { struct diff_queue_struct *q = &diff_queued_diff; @@ -25,7 +18,7 @@ static struct combine_diff_path *intersect_paths(struct combine_diff_path *curr, for (i = 0; i < q->nr; i++) { int len; const char *path; - if (uninteresting(q->queue[i])) + if (diff_unmodified_pair(q->queue[i])) continue; path = q->queue[i]->two->path; len = strlen(path); @@ -38,9 +31,9 @@ static struct combine_diff_path *intersect_paths(struct combine_diff_path *curr, memset(p->parent, 0, sizeof(p->parent[0]) * num_parent); - memcpy(p->sha1, q->queue[i]->two->sha1, 20); + hashcpy(p->sha1, q->queue[i]->two->sha1); p->mode = q->queue[i]->two->mode; - memcpy(p->parent[n].sha1, q->queue[i]->one->sha1, 20); + hashcpy(p->parent[n].sha1, q->queue[i]->one->sha1); p->parent[n].mode = q->queue[i]->one->mode; p->parent[n].status = q->queue[i]->status; *tail = p; @@ -57,14 +50,13 @@ static struct combine_diff_path *intersect_paths(struct combine_diff_path *curr, const char *path; int len; - if (uninteresting(q->queue[i])) + if (diff_unmodified_pair(q->queue[i])) continue; path = q->queue[i]->two->path; len = strlen(path); if (len == p->len && !memcmp(path, p->path, len)) { found = 1; - memcpy(p->parent[n].sha1, - q->queue[i]->one->sha1, 20); + hashcpy(p->parent[n].sha1, q->queue[i]->one->sha1); p->parent[n].mode = q->queue[i]->one->mode; p->parent[n].status = q->queue[i]->status; break; @@ -101,7 +93,7 @@ static char *grab_blob(const unsigned char *sha1, unsigned long *size) { char *blob; char type[20]; - if (!memcmp(sha1, null_sha1, 20)) { + if (is_null_sha1(sha1)) { /* deleted blob */ *size = 0; return xcalloc(1, 1); @@ -609,16 +601,16 @@ static void dump_quoted_path(const char *prefix, const char *path, printf("%s\n", c_reset); } -static int show_patch_diff(struct combine_diff_path *elem, int num_parent, - int dense, struct rev_info *rev) +static void show_patch_diff(struct combine_diff_path *elem, int num_parent, + int dense, struct rev_info *rev) { struct diff_options *opt = &rev->diffopt; unsigned long result_size, cnt, lno; char *result, *cp; struct sline *sline; /* survived lines */ int mode_differs = 0; - int i, show_hunks, shown_header = 0; - int working_tree_file = !memcmp(elem->sha1, null_sha1, 20); + int i, show_hunks; + int working_tree_file = is_null_sha1(elem->sha1); int abbrev = opt->full_index ? 40 : DEFAULT_ABBREV; mmfile_t result_file; @@ -695,8 +687,8 @@ static int show_patch_diff(struct combine_diff_path *elem, int num_parent, for (i = 0; i < num_parent; i++) { int j; for (j = 0; j < i; j++) { - if (!memcmp(elem->parent[i].sha1, - elem->parent[j].sha1, 20)) { + if (!hashcmp(elem->parent[i].sha1, + elem->parent[j].sha1)) { reuse_combine_diff(sline, cnt, i, j); break; } @@ -769,7 +761,6 @@ static int show_patch_diff(struct combine_diff_path *elem, int num_parent, } free(sline[0].p_lno); free(sline); - return shown_header; } #define COLONS "::::::::::::::::::::::::::::::::" @@ -837,11 +828,10 @@ void show_combined_diff(struct combine_diff_path *p, return; if (opt->output_format & (DIFF_FORMAT_RAW | DIFF_FORMAT_NAME | - DIFF_FORMAT_NAME_STATUS)) { + DIFF_FORMAT_NAME_STATUS)) show_raw_diff(p, num_parent, rev); - } else if (opt->output_format & DIFF_FORMAT_PATCH) { + else if (opt->output_format & DIFF_FORMAT_PATCH) show_patch_diff(p, num_parent, dense, rev); - } } void diff_tree_combined(const unsigned char *sha1, @@ -936,6 +926,7 @@ void diff_tree_combined_merge(const unsigned char *sha1, for (parents = commit->parents, num_parent = 0; parents; parents = parents->next, num_parent++) - memcpy(parent + num_parent, parents->item->object.sha1, 20); + hashcpy((unsigned char*)(parent + num_parent), + parents->item->object.sha1); diff_tree_combined(sha1, parent, num_parent, dense, rev); } @@ -7,15 +7,15 @@ int save_commit_buffer = 1; struct sort_node { /* - * the number of children of the associated commit - * that also occur in the list being sorted. - */ + * the number of children of the associated commit + * that also occur in the list being sorted. + */ unsigned int indegree; /* - * reference to original list item that we will re-use - * on output. - */ + * reference to original list item that we will re-use + * on output. + */ struct commit_list * list_item; }; @@ -123,7 +123,7 @@ static int commit_graft_pos(const unsigned char *sha1) while (lo < hi) { int mi = (lo + hi) / 2; struct commit_graft *graft = commit_graft[mi]; - int cmp = memcmp(sha1, graft->sha1, 20); + int cmp = hashcmp(sha1, graft->sha1); if (!cmp) return mi; if (cmp < 0) @@ -507,14 +507,14 @@ static int add_user_info(const char *what, enum cmit_fmt fmt, char *buf, const c } switch (fmt) { case CMIT_FMT_MEDIUM: - ret += sprintf(buf + ret, "Date: %s\n", show_date(time, tz)); + ret += sprintf(buf + ret, "Date: %s\n", show_date(time, tz, 0)); break; case CMIT_FMT_EMAIL: ret += sprintf(buf + ret, "Date: %s\n", show_rfc2822_date(time, tz)); break; case CMIT_FMT_FULLER: - ret += sprintf(buf + ret, "%sDate: %s\n", what, show_date(time, tz)); + ret += sprintf(buf + ret, "%sDate: %s\n", what, show_date(time, tz, 0)); break; default: /* notin' */ @@ -727,10 +727,10 @@ struct commit *pop_commit(struct commit_list **stack) int count_parents(struct commit * commit) { - int count = 0; + int count; struct commit_list * parents = commit->parents; - for (count=0;parents; parents=parents->next,count++) - ; + for (count = 0; parents; parents = parents->next,count++) + ; return count; } diff --git a/config.mak.in b/config.mak.in index 04f508ab9..369e6116e 100644 --- a/config.mak.in +++ b/config.mak.in @@ -22,3 +22,19 @@ 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 index a9c88c6a4..36f9cd94d 100644 --- a/configure.ac +++ b/configure.ac @@ -2,7 +2,7 @@ # Process this file with autoconf to produce a configure script. AC_PREREQ(2.59) -AC_INIT([git], [1.4.2], [git@vger.kernel.org]) +AC_INIT([git], [@@GIT_VERSION@@], [git@vger.kernel.org]) AC_CONFIG_SRCDIR([git.c]) @@ -19,6 +19,77 @@ echo "# ${config_append}. Generated by configure." > "${config_append}" # Append LINE to file ${config_append} AC_DEFUN([GIT_CONF_APPEND_LINE], [echo "$1" >> "${config_append}"])# GIT_CONF_APPEND_LINE +# +# GIT_ARG_SET_PATH(PROGRAM) +# ------------------------- +# Provide --with-PROGRAM=PATH option to set PATH to PROGRAM +AC_DEFUN([GIT_ARG_SET_PATH], +[AC_ARG_WITH([$1], + [AS_HELP_STRING([--with-$1=PATH], + [provide PATH to $1])], + [GIT_CONF_APPEND_PATH($1)],[]) +])# GIT_ARG_SET_PATH +# +# GIT_CONF_APPEND_PATH(PROGRAM) +# ------------------------------ +# Parse --with-PROGRAM=PATH option to set PROGRAM_PATH=PATH +# Used by GIT_ARG_SET_PATH(PROGRAM) +AC_DEFUN([GIT_CONF_APPEND_PATH], +[PROGRAM=m4_toupper($1); \ +if test "$withval" = "no"; then \ + AC_MSG_ERROR([You cannot use git without $1]); \ +else \ + if test "$withval" = "yes"; then \ + AC_MSG_WARN([You should provide path for --with-$1=PATH]); \ + else \ + GIT_CONF_APPEND_LINE(${PROGRAM}_PATH=$withval); \ + fi; \ +fi; \ +]) # GIT_CONF_APPEND_PATH +# +# GIT_PARSE_WITH(PACKAGE) +# ----------------------- +# For use in AC_ARG_WITH action-if-found, for packages default ON. +# * Set NO_PACKAGE=YesPlease for --without-PACKAGE +# * Set PACKAGEDIR=PATH for --with-PACKAGE=PATH +# * Unset NO_PACKAGE for --with-PACKAGE without ARG +AC_DEFUN([GIT_PARSE_WITH], +[PACKAGE=m4_toupper($1); \ +if test "$withval" = "no"; then \ + m4_toupper(NO_$1)=YesPlease; \ +elif test "$withval" = "yes"; then \ + m4_toupper(NO_$1)=; \ +else \ + m4_toupper(NO_$1)=; \ + GIT_CONF_APPEND_LINE(${PACKAGE}DIR=$withval); \ +fi \ +])# GIT_PARSE_WITH + + +## Site configuration related to programs (before tests) +## --with-PACKAGE[=ARG] and --without-PACKAGE +# +# Define SHELL_PATH to provide path to shell. +GIT_ARG_SET_PATH(shell) +# +# Define PERL_PATH to provide path to Perl. +GIT_ARG_SET_PATH(perl) +# +# Define 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. @@ -30,6 +101,16 @@ AC_CHECK_TOOL(AR, ar, :) AC_CHECK_PROGS(TAR, [gtar tar]) # # Define NO_PYTHON if you want to lose all benefits of the recursive merge. +# Define PYTHON_PATH to provide path to Python. +if test -z "$NO_PYTHON"; then + if test -z "$PYTHON_PATH"; then + AC_PATH_PROGS(PYTHON_PATH, [python python2.4 python2.3 python2]) + fi + if test -n "$PYTHON_PATH"; then + GIT_CONF_APPEND_LINE([PYTHON_PATH=@PYTHON_PATH@]) + NO_PYTHON="" + fi +fi ## Checks for libraries. @@ -37,32 +118,43 @@ AC_MSG_NOTICE([CHECKS for libraries]) # # Define NO_OPENSSL environment variable if you do not have OpenSSL. # Define NEEDS_SSL_WITH_CRYPTO if you need -lcrypto with -lssl (Darwin). -AC_CHECK_LIB([ssl], [SHA1_Init],[], -[AC_CHECK_LIB([crypto], [SHA1_INIT], - [GIT_CONF_APPEND_LINE(NEEDS_SSL_WITH_CRYPTO=YesPlease)], - [GIT_CONF_APPEND_LINE(NO_OPENSSL=YesPlease)])]) +AC_CHECK_LIB([crypto], [SHA1_Init], +[NEEDS_SSL_WITH_CRYPTO=], +[AC_CHECK_LIB([ssl], [SHA1_Init], + [NEEDS_SSL_WITH_CRYPTO=YesPlease + NEEDS_SSL_WITH_CRYPTO=], + [NO_OPENSSL=YesPlease])]) +AC_SUBST(NEEDS_SSL_WITH_CRYPTO) +AC_SUBST(NO_OPENSSL) # # Define NO_CURL if you do not have curl installed. git-http-pull and # git-http-push are not built, and you cannot use http:// and https:// # transports. -AC_CHECK_LIB([curl], [curl_global_init],[], -[GIT_CONF_APPEND_LINE(NO_CURL=YesPlease)]) +AC_CHECK_LIB([curl], [curl_global_init], +[NO_CURL=], +[NO_CURL=YesPlease]) +AC_SUBST(NO_CURL) # # Define NO_EXPAT if you do not have expat installed. git-http-push is # not built, and you cannot push using http:// and https:// transports. -AC_CHECK_LIB([expat], [XML_ParserCreate],[], -[GIT_CONF_APPEND_LINE(NO_EXPAT=YesPlease)]) +AC_CHECK_LIB([expat], [XML_ParserCreate], +[NO_EXPAT=], +[NO_EXPAT=YesPlease]) +AC_SUBST(NO_EXPAT) # # Define NEEDS_LIBICONV if linking with libc is not enough (Darwin). -AC_CHECK_LIB([c], [iconv],[], -[AC_CHECK_LIB([iconv],[iconv], - [GIT_CONF_APPEND_LINE(NEEDS_LIBICONV=YesPlease)],[])]) +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],[], -[AC_CHECK_LIB([socket],[socket], - [GIT_CONF_APPEND_LINE(NEEDS_SOCKET=YesPlease)],[])]) +AC_CHECK_LIB([c], [socket], +[NEEDS_SOCKET=], +[NEEDS_SOCKET=YesPlease]) +AC_SUBST(NEEDS_SOCKET) +test -n "$NEEDS_SOCKET" && LIBS="$LIBS -lsocket" ## Checks for header files. @@ -72,21 +164,65 @@ AC_CHECK_LIB([c], [socket],[], AC_MSG_NOTICE([CHECKS for typedefs, structures, and compiler characteristics]) # # Define NO_D_INO_IN_DIRENT if you don't have d_ino in your struct dirent. -AC_CHECK_MEMBER(struct dirent.d_ino,[], -[GIT_CONF_APPEND_LINE(NO_D_INO_IN_DIRENT=YesPlease)], +AC_CHECK_MEMBER(struct dirent.d_ino, +[NO_D_INO_IN_DIRENT=], +[NO_D_INO_IN_DIRENT=YesPlease], [#include <dirent.h>]) +AC_SUBST(NO_D_INO_IN_DIRENT) # # Define NO_D_TYPE_IN_DIRENT if your platform defines DT_UNKNOWN but lacks # d_type in struct dirent (latest Cygwin -- will be fixed soonish). -AC_CHECK_MEMBER(struct dirent.d_type,[], -[GIT_CONF_APPEND_LINE(NO_D_TYPE_IN_DIRENT=YesPlease)], +AC_CHECK_MEMBER(struct dirent.d_type, +[NO_D_TYPE_IN_DIRENT=], +[NO_D_TYPE_IN_DIRENT=YesPlease], [#include <dirent.h>]) +AC_SUBST(NO_D_TYPE_IN_DIRENT) # # Define NO_SOCKADDR_STORAGE if your platform does not have struct # sockaddr_storage. -AC_CHECK_TYPE(struct sockaddr_storage,[], -[GIT_CONF_APPEND_LINE(NO_SOCKADDR_STORAGE=YesPlease)], -[#include <netinet/in.h>]) +AC_CHECK_TYPE(struct sockaddr_storage, +[NO_SOCKADDR_STORAGE=], +[NO_SOCKADDR_STORAGE=YesPlease],[ +#include <sys/types.h> +#include <sys/socket.h> +]) +AC_SUBST(NO_SOCKADDR_STORAGE) +# +# Define NO_IPV6 if you lack IPv6 support and getaddrinfo(). +AC_CHECK_TYPE([struct addrinfo],[ + AC_CHECK_FUNC([getaddrinfo], + [NO_IPV6=], + [NO_IPV6=YesPlease]) +],[NO_IPV6=YesPlease],[ +#include <sys/types.h> +#include <sys/socket.h> +#include <netdb.h> +]) +AC_SUBST(NO_IPV6) +# +# Define NO_C99_FORMAT if your formatted IO functions (printf/scanf et.al.) +# do not support the 'size specifiers' introduced by C99, namely ll, hh, +# j, z, t. (representing long long int, char, intmax_t, size_t, ptrdiff_t). +# some C compilers supported these specifiers prior to C99 as an extension. +AC_CACHE_CHECK(whether formatted IO functions support C99 size specifiers, + ac_cv_c_c99_format, +[# Actually git uses only %z (%zu) in alloc.c, and %t (%td) in mktag.c +AC_RUN_IFELSE( + [AC_LANG_PROGRAM([AC_INCLUDES_DEFAULT], + [[char buf[64]; + if (sprintf(buf, "%lld%hhd%jd%zd%td", (long long int)1, (char)2, (intmax_t)3, (size_t)4, (ptrdiff_t)5) != 5) + exit(1); + else if (strcmp(buf, "12345")) + exit(2);]])], + [ac_cv_c_c99_format=yes], + [ac_cv_c_c99_format=no]) +]) +if test $ac_cv_c_c99_format = no; then + NO_C99_FORMAT=YesPlease +else + NO_C99_FORMAT= +fi +AC_SUBST(NO_C99_FORMAT) ## Checks for library functions. @@ -94,21 +230,25 @@ AC_CHECK_TYPE(struct sockaddr_storage,[], AC_MSG_NOTICE([CHECKS for library functions]) # # Define NO_STRCASESTR if you don't have strcasestr. -AC_CHECK_FUNC(strcasestr,[], -[GIT_CONF_APPEND_LINE(NO_STRCASESTR=YesPlease)]) +AC_CHECK_FUNC(strcasestr, +[NO_STRCASESTR=], +[NO_STRCASESTR=YesPlease]) +AC_SUBST(NO_STRCASESTR) # # Define NO_STRLCPY if you don't have strlcpy. -AC_CHECK_FUNC(strlcpy,[], -[GIT_CONF_APPEND_LINE(NO_STRLCPY=YesPlease)]) +AC_CHECK_FUNC(strlcpy, +[NO_STRLCPY=], +[NO_STRLCPY=YesPlease]) +AC_SUBST(NO_STRLCPY) # # Define NO_SETENV if you don't have setenv in the C library. -AC_CHECK_FUNC(setenv,[], -[GIT_CONF_APPEND_LINE(NO_SETENV=YesPlease)]) +AC_CHECK_FUNC(setenv, +[NO_SETENV=], +[NO_SETENV=YesPlease]) +AC_SUBST(NO_SETENV) # # Define NO_MMAP if you want to avoid mmap. # -# Define NO_IPV6 if you lack IPv6 support and getaddrinfo(). -# # Define NO_ICONV if your libc does not properly support iconv. @@ -125,9 +265,11 @@ AC_CHECK_FUNC(setenv,[], # a missing newline at the end of the file. -## Site configuration +## Site configuration (override autodetection) ## --with-PACKAGE[=ARG] and --without-PACKAGE -# Define NO_SVN_TESTS if you want to skip time-consuming SVN interopability +AC_MSG_NOTICE([CHECKS for site configuration]) +# +# Define NO_SVN_TESTS if you want to skip time-consuming SVN interoperability # tests. These tests take up a significant amount of the total test time # but are not needed unless you plan to talk to SVN repos. # @@ -145,21 +287,51 @@ AC_CHECK_FUNC(setenv,[], # Define NO_OPENSSL environment variable if you do not have OpenSSL. # This also implies MOZILLA_SHA1. # +# Define OPENSSLDIR=/foo/bar if your openssl header and library files are in +# /foo/bar/include and /foo/bar/lib directories. +AC_ARG_WITH(openssl, +AS_HELP_STRING([--with-openssl],[use OpenSSL library (default is YES)]) +AS_HELP_STRING([], [ARG can be prefix for openssl library and headers]),\ +GIT_PARSE_WITH(openssl)) +# # Define NO_CURL if you do not have curl installed. git-http-pull and # git-http-push are not built, and you cannot use http:// and https:// # transports. # # Define CURLDIR=/foo/bar if your curl header and library files are in # /foo/bar/include and /foo/bar/lib directories. +AC_ARG_WITH(curl, +AS_HELP_STRING([--with-curl],[support http(s):// transports (default is YES)]) +AS_HELP_STRING([], [ARG can be also prefix for curl library and headers]), +GIT_PARSE_WITH(curl)) # # Define NO_EXPAT if you do not have expat installed. git-http-push is # not built, and you cannot push using http:// and https:// transports. # -# Define NO_MMAP if you want to avoid mmap. +# Define EXPATDIR=/foo/bar if your expat header and library files are in +# /foo/bar/include and /foo/bar/lib directories. +AC_ARG_WITH(expat, +AS_HELP_STRING([--with-expat], +[support git-push using http:// and https:// transports via WebDAV (default is YES)]) +AS_HELP_STRING([], [ARG can be also prefix for expat library and headers]), +GIT_PARSE_WITH(expat)) +# +# Define NO_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_PYTHON if you want to loose all benefits of the recursive merge. +# 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. @@ -10,7 +10,7 @@ #include <netdb.h> #include <signal.h> -static char *server_capabilities = NULL; +static char *server_capabilities; static int check_ref(const char *name, int len, unsigned int flags) { @@ -77,7 +77,7 @@ struct ref **get_remote_heads(int in, struct ref **list, if (nr_match && !path_match(name, nr_match, match)) continue; ref = xcalloc(1, sizeof(*ref) + len - 40); - memcpy(ref->old_sha1, old_sha1, 20); + hashcpy(ref->old_sha1, old_sha1); memcpy(ref->name, buffer + 41, len - 40); *list = ref; list = &ref->next; @@ -208,7 +208,7 @@ static struct ref *try_explicit_object_name(const char *name) len = strlen(name) + 1; ref = xcalloc(1, sizeof(*ref) + len); memcpy(ref->name, name, len); - memcpy(ref->new_sha1, sha1, 20); + hashcpy(ref->new_sha1, sha1); return ref; } @@ -318,7 +318,7 @@ int match_refs(struct ref *src, struct ref *dst, struct ref ***dst_tail, int len = strlen(src->name) + 1; dst_peer = xcalloc(1, sizeof(*dst_peer) + len); memcpy(dst_peer->name, src->name, len); - memcpy(dst_peer->new_sha1, src->new_sha1, 20); + hashcpy(dst_peer->new_sha1, src->new_sha1); link_dst_tail(dst_peer, dst_tail); } dst_peer->peer_ref = src; @@ -493,8 +493,8 @@ static void git_tcp_connect(int fd[2], char *host) } -static char *git_proxy_command = NULL; -static const char *rhost_name = NULL; +static char *git_proxy_command; +static const char *rhost_name; static int rhost_len; static int git_proxy_command_options(const char *var, const char *value) @@ -737,14 +737,9 @@ int git_connect(int fd[2], char *url, const char *prog) int finish_connect(pid_t pid) { - int ret; - - for (;;) { - ret = waitpid(pid, NULL, 0); - if (!ret) - break; + while (waitpid(pid, NULL, 0) < 0) { if (errno != EINTR) - break; + return -1; } - return ret; + return 0; } diff --git a/contrib/emacs/vc-git.el b/contrib/emacs/vc-git.el index 3f6ed699f..4a8f79092 100644 --- a/contrib/emacs/vc-git.el +++ b/contrib/emacs/vc-git.el @@ -54,7 +54,7 @@ (let* ((dir (file-name-directory file)) (name (file-relative-name file dir))) (when dir (cd dir)) - (and (eq 0 (call-process "git" nil '(t nil) nil "ls-files" "-c" "-z" "--" name)) + (and (ignore-errors (eq 0 (call-process "git" nil '(t nil) nil "ls-files" "-c" "-z" "--" name))) (let ((str (buffer-string))) (and (> (length str) (length name)) (string= (substring str 0 (1+ (length name))) (concat name "\0")))))))) diff --git a/contrib/gitview/gitview.txt b/contrib/gitview/gitview.txt index 6924df286..77c29de30 100644 --- a/contrib/gitview/gitview.txt +++ b/contrib/gitview/gitview.txt @@ -7,40 +7,50 @@ gitview - A GTK based repository browser for git SYNOPSIS -------- -'gitview' [options] [args] +'gitview' [options] [args] DESCRIPTION --------- -Dependencies +Dependencies: * Python 2.4 * PyGTK 2.8 or later * PyCairo 1.0 or later OPTIONS ------- - --without-diff - If the user doesn't want to list the commit diffs in the main window. This may speed up the repository browsing. - - <args> - All the valid option for git-rev-list(1) - Key Bindings: - F4: - To maximize the window - F5: - To reread references. - F11: - Full screen - F12: - Leave full screen +------- +--without-diff:: + + If the user doesn't want to list the commit diffs in the main window. + This may speed up the repository browsing. + +<args>:: + + All the valid option for gitlink:git-rev-list[1]. + +Key Bindings +------------ +F4:: + To maximize the window + +F5:: + To reread references. + +F11:: + Full screen + +F12:: + Leave full screen EXAMPLES ------- - gitview v2.6.12.. include/scsi drivers/scsi - Show as the changes since version v2.6.12 that changed any file in the include/scsi - or drivers/scsi subdirectories +-------- + +gitview v2.6.12.. include/scsi drivers/scsi:: + + Show as the changes since version v2.6.12 that changed any file in the + include/scsi or drivers/scsi subdirectories - gitview --since=2.weeks.ago - Show the changes during the last two weeks +gitview --since=2.weeks.ago:: + Show the changes during the last two weeks diff --git a/convert-objects.c b/convert-objects.c index 168771ed8..631678b08 100644 --- a/convert-objects.c +++ b/convert-objects.c @@ -23,7 +23,7 @@ static struct entry * convert_entry(unsigned char *sha1); static struct entry *insert_new(unsigned char *sha1, int pos) { struct entry *new = xcalloc(1, sizeof(struct entry)); - memcpy(new->old_sha1, sha1, 20); + hashcpy(new->old_sha1, sha1); memmove(convert + pos + 1, convert + pos, (nr_convert - pos) * sizeof(struct entry *)); convert[pos] = new; nr_convert++; @@ -39,7 +39,7 @@ static struct entry *lookup_entry(unsigned char *sha1) while (low < high) { int next = (low + high) / 2; struct entry *n = convert[next]; - int cmp = memcmp(sha1, n->old_sha1, 20); + int cmp = hashcmp(sha1, n->old_sha1); if (!cmp) return n; if (cmp < 0) { @@ -54,7 +54,7 @@ static struct entry *lookup_entry(unsigned char *sha1) static void convert_binary_sha1(void *buffer) { struct entry *entry = convert_entry(buffer); - memcpy(buffer, entry->new_sha1, 20); + hashcpy(buffer, entry->new_sha1); } static void convert_ascii_sha1(void *buffer) @@ -104,7 +104,7 @@ static int write_subdirectory(void *buffer, unsigned long size, const char *base if (!slash) { newlen += sprintf(new + newlen, "%o %s", mode, path); new[newlen++] = '\0'; - memcpy(new + newlen, (char *) buffer + len - 20, 20); + hashcpy((unsigned char*)new + newlen, (unsigned char *) buffer + len - 20); newlen += 20; used += len; diff --git a/csum-file.c b/csum-file.c index 6a7b40fd0..b7174c6c0 100644 --- a/csum-file.c +++ b/csum-file.c @@ -10,7 +10,7 @@ #include "cache.h" #include "csum-file.h" -static int sha1flush(struct sha1file *f, unsigned int count) +static void sha1flush(struct sha1file *f, unsigned int count) { void *buf = f->buffer; @@ -21,7 +21,7 @@ static int sha1flush(struct sha1file *f, unsigned int count) count -= ret; if (count) continue; - return 0; + return; } if (!ret) die("sha1 file '%s' write error. Out of diskspace", f->name); @@ -38,7 +38,7 @@ int sha1close(struct sha1file *f, unsigned char *result, int update) } SHA1_Final(f->buffer, &f->ctx); if (result) - memcpy(result, f->buffer, 20); + hashcpy(result, f->buffer); if (update) sha1flush(f, 20); if (close(f->fd)) @@ -7,6 +7,8 @@ #include <netinet/in.h> #include <arpa/inet.h> #include <syslog.h> +#include <pwd.h> +#include <grp.h> #include "pkt-line.h" #include "cache.h" #include "exec_cmd.h" @@ -19,27 +21,28 @@ 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] [--detach] [--pid-file=file] [directory...]"; +" [--reuseaddr] [--detach] [--pid-file=file]\n" +" [--user=user [[--group=group]] [directory...]"; /* List of acceptable pathname prefixes */ -static char **ok_paths = NULL; -static int strict_paths = 0; +static char **ok_paths; +static int strict_paths; /* If this is set, git-daemon-export-ok is not required */ -static int export_all_trees = 0; +static int export_all_trees; /* Take all paths relative to this one if non-NULL */ -static char *base_path = NULL; +static char *base_path; /* If defined, ~user notation is allowed and the string is inserted * after ~user/. E.g. a request to git://host/~alice/frotz would * go to /home/alice/pub_git/frotz with --user-path=pub_git. */ -static const char *user_path = NULL; +static const char *user_path; /* Timeout, and initial timeout */ -static unsigned int timeout = 0; -static unsigned int init_timeout = 0; +static unsigned int timeout; +static unsigned int init_timeout; static void logreport(int priority, const char *err, va_list params) { @@ -333,12 +336,12 @@ static int execute(struct sockaddr *addr) static int max_connections = 25; /* These are updated by the signal handler */ -static volatile unsigned int children_reaped = 0; +static volatile unsigned int children_reaped; static pid_t dead_child[MAX_CHILDREN]; /* These are updated by the main loop */ -static unsigned int children_spawned = 0; -static unsigned int children_deleted = 0; +static unsigned int children_spawned; +static unsigned int children_deleted; static struct child { pid_t pid; @@ -526,7 +529,6 @@ static int socksetup(int port, int **socklist_p) for (ai = ai0; ai; ai = ai->ai_next) { int sockfd; - int *newlist; sockfd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); if (sockfd < 0) @@ -560,11 +562,7 @@ static int socksetup(int port, int **socklist_p) continue; /* not fatal */ } - newlist = realloc(socklist, sizeof(int) * (socknum + 1)); - if (!newlist) - die("memory allocation failed: %s", strerror(errno)); - - socklist = newlist; + socklist = xrealloc(socklist, sizeof(int) * (socknum + 1)); socklist[socknum++] = sockfd; if (maxfd < sockfd) @@ -701,7 +699,7 @@ static void store_pid(const char *path) fclose(f); } -static int serve(int port) +static int serve(int port, struct passwd *pass, gid_t gid) { int socknum, *socklist; @@ -709,6 +707,11 @@ static int serve(int port) if (socknum == 0) die("unable to allocate any listen sockets on port %u", port); + if (pass && gid && + (initgroups(pass->pw_name, gid) || setgid (gid) || + setuid(pass->pw_uid))) + die("cannot drop privileges"); + return service_loop(socknum, socklist); } @@ -716,8 +719,11 @@ int main(int argc, char **argv) { int port = DEFAULT_GIT_PORT; int inetd_mode = 0; - const char *pid_file = NULL; + const char *pid_file = NULL, *user_name = NULL, *group_name = NULL; int detach = 0; + struct passwd *pass = NULL; + struct group *group; + gid_t gid = 0; int i; /* Without this we cannot rely on waitpid() to tell @@ -791,6 +797,14 @@ int main(int argc, char **argv) log_syslog = 1; continue; } + if (!strncmp(arg, "--user=", 7)) { + user_name = arg + 7; + continue; + } + if (!strncmp(arg, "--group=", 8)) { + group_name = arg + 8; + continue; + } if (!strcmp(arg, "--")) { ok_paths = &argv[i+1]; break; @@ -802,6 +816,28 @@ int main(int argc, char **argv) usage(daemon_usage); } + if (inetd_mode && (group_name || user_name)) + die("--user and --group are incompatible with --inetd"); + + if (group_name && !user_name) + die("--group supplied without --user"); + + if (user_name) { + pass = getpwnam(user_name); + if (!pass) + die("user not found - %s", user_name); + + if (!group_name) + gid = pass->pw_gid; + else { + group = getgrnam(group_name); + if (!group) + die("group not found - %s", group_name); + + gid = group->gr_gid; + } + } + if (log_syslog) { openlog("git-daemon", 0, LOG_DAEMON); set_die_routine(daemon_die); @@ -831,5 +867,5 @@ int main(int argc, char **argv) if (pid_file) store_pid(pid_file); - return serve(port); + return serve(port, pass, gid); } @@ -37,6 +37,16 @@ static const char *weekday_names[] = { "Sundays", "Mondays", "Tuesdays", "Wednesdays", "Thursdays", "Fridays", "Saturdays" }; +static time_t gm_time_t(unsigned long time, int tz) +{ + int minutes; + + minutes = tz < 0 ? -tz : tz; + minutes = (minutes / 100)*60 + (minutes % 100); + minutes = tz < 0 ? -minutes : minutes; + return time + minutes * 60; +} + /* * The "tz" thing is passed in as this strange "decimal parse of tz" * thing, which means that tz -0100 is passed in as the integer -100, @@ -44,21 +54,58 @@ static const char *weekday_names[] = { */ static struct tm *time_to_tm(unsigned long time, int tz) { - time_t t; - int minutes; - - minutes = tz < 0 ? -tz : tz; - minutes = (minutes / 100)*60 + (minutes % 100); - minutes = tz < 0 ? -minutes : minutes; - t = time + minutes * 60; + time_t t = gm_time_t(time, tz); return gmtime(&t); } -const char *show_date(unsigned long time, int tz) +const char *show_date(unsigned long time, int tz, int relative) { struct tm *tm; static char timebuf[200]; + if (relative) { + unsigned long diff; + time_t t = gm_time_t(time, tz); + struct timeval now; + gettimeofday(&now, NULL); + if (now.tv_sec < t) + return "in the future"; + diff = now.tv_sec - t; + if (diff < 90) { + snprintf(timebuf, sizeof(timebuf), "%lu seconds ago", diff); + return timebuf; + } + /* Turn it into minutes */ + diff = (diff + 30) / 60; + if (diff < 90) { + snprintf(timebuf, sizeof(timebuf), "%lu minutes ago", diff); + return timebuf; + } + /* Turn it into hours */ + diff = (diff + 30) / 60; + if (diff < 36) { + snprintf(timebuf, sizeof(timebuf), "%lu hours ago", diff); + return timebuf; + } + /* We deal with number of days from here on */ + diff = (diff + 12) / 24; + if (diff < 14) { + snprintf(timebuf, sizeof(timebuf), "%lu days ago", diff); + return timebuf; + } + /* Say weeks for the past 10 weeks or so */ + if (diff < 70) { + snprintf(timebuf, sizeof(timebuf), "%lu weeks ago", (diff + 3) / 7); + return timebuf; + } + /* Say months for the past 12 months or so */ + if (diff < 360) { + snprintf(timebuf, sizeof(timebuf), "%lu months ago", (diff + 15) / 30); + return timebuf; + } + /* Else fall back on absolute format.. */ + } + tm = time_to_tm(time, tz); if (!tm) return NULL; @@ -584,10 +631,10 @@ static const char *approxidate_alpha(const char *date, struct tm *tm, int *num) const struct typelen *tl; const struct special *s; const char *end = date; - int n = 1, i; + int i; - while (isalpha(*++end)) - n++; + while (isalpha(*++end)); + ; for (i = 0; i < 12; i++) { int match = match_string(date, month_names[i]); diff --git a/describe.c b/describe.c index 324ca8965..5dd8b2e39 100644 --- a/describe.c +++ b/describe.c @@ -8,12 +8,12 @@ static const char describe_usage[] = "git-describe [--all] [--tags] [--abbrev=<n>] <committish>*"; -static int all = 0; /* Default to annotated tags only */ -static int tags = 0; /* But allow any tags if --tags is specified */ +static int all; /* Default to annotated tags only */ +static int tags; /* But allow any tags if --tags is specified */ static int abbrev = DEFAULT_ABBREV; -static int names = 0, allocs = 0; +static int names, allocs; static struct commit_name { const struct commit *commit; int prio; /* annotated tag = 2, tag = 1, head = 0 */ @@ -42,7 +42,7 @@ static void add_to_known_names(const char *path, struct commit_name *name = xmalloc(sizeof(struct commit_name) + len); name->commit = commit; - name->prio = prio; + name->prio = prio; memcpy(name->path, path, len); idx = names; if (idx >= allocs) { @@ -154,7 +154,7 @@ int main(int argc, char **argv) tags = 1; else if (!strncmp(arg, "--abbrev=", 9)) { abbrev = strtoul(arg + 9, NULL, 10); - if (abbrev < MINIMUM_ABBREV || 40 <= abbrev) + if (abbrev < MINIMUM_ABBREV || 40 < abbrev) abbrev = DEFAULT_ABBREV; } else diff --git a/diff-delta.c b/diff-delta.c index 7da9205a5..fa16d06c8 100644 --- a/diff-delta.c +++ b/diff-delta.c @@ -152,7 +152,7 @@ struct delta_index * create_delta_index(const void *buf, unsigned long bufsize) initialization in create_delta(). */ entries = (bufsize - 1) / RABIN_WINDOW; hsize = entries / 4; - for (i = 4; (1 << i) < hsize && i < 31; i++); + for (i = 4; (1u << i) < hsize && i < 31; i++); hsize = 1 << i; hmask = hsize - 1; @@ -392,7 +392,7 @@ create_delta(const struct delta_index *index, outsize = max_size + MAX_OP_SIZE + 1; if (max_size && outpos > max_size) break; - out = realloc(out, outsize); + out = xrealloc(out, outsize); if (!out) { free(tmp); return NULL; diff --git a/diff-lib.c b/diff-lib.c index 116b5a9d6..9edfa9262 100644 --- a/diff-lib.c +++ b/diff-lib.c @@ -48,7 +48,7 @@ int run_diff_files(struct rev_info *revs, int silent_on_removed) memcpy(dpath->path, ce->name, path_len); dpath->path[path_len] = '\0'; dpath->mode = 0; - memset(dpath->sha1, 0, 20); + hashclr(dpath->sha1); memset(&(dpath->parent[0]), 0, sizeof(struct combine_diff_parent)*5); @@ -66,8 +66,7 @@ int run_diff_files(struct rev_info *revs, int silent_on_removed) if (2 <= stage) { int mode = ntohl(nce->ce_mode); num_compare_stages++; - memcpy(dpath->parent[stage-2].sha1, - nce->sha1, 20); + hashcpy(dpath->parent[stage-2].sha1, nce->sha1); dpath->parent[stage-2].mode = canon_mode(mode); dpath->parent[stage-2].status = @@ -215,7 +214,7 @@ static int show_modified(struct rev_info *revs, } oldmode = old->ce_mode; - if (mode == oldmode && !memcmp(sha1, old->sha1, 20) && + if (mode == oldmode && !hashcmp(sha1, old->sha1) && !revs->diffopt.find_copies_harder) return 0; @@ -13,9 +13,9 @@ static int use_size_cache; -static int diff_detect_rename_default = 0; +static int diff_detect_rename_default; static int diff_rename_limit_default = -1; -static int diff_use_color_default = 0; +static int diff_use_color_default; /* "\033[1;38;5;2xx;48;5;2xxm\0" is 23 bytes */ static char diff_colors[][24] = { @@ -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') @@ -679,7 +838,7 @@ static unsigned char *deflate_it(char *data, return deflated; } -static void emit_binary_diff(mmfile_t *one, mmfile_t *two) +static void emit_binary_diff_body(mmfile_t *one, mmfile_t *two) { void *cp; void *delta; @@ -690,7 +849,6 @@ static void emit_binary_diff(mmfile_t *one, mmfile_t *two) unsigned long deflate_size; unsigned long data_size; - printf("GIT binary patch\n"); /* We could do deflated delta, or we could do just deflated two, * whichever is smaller. */ @@ -739,15 +897,20 @@ static void emit_binary_diff(mmfile_t *one, mmfile_t *two) free(data); } +static void emit_binary_diff(mmfile_t *one, mmfile_t *two) +{ + printf("GIT binary patch\n"); + emit_binary_diff_body(one, two); + emit_binary_diff_body(two, one); +} + #define FIRST_FEW_BYTES 8000 static int mmfile_is_binary(mmfile_t *mf) { long sz = mf->size; if (FIRST_FEW_BYTES < sz) sz = FIRST_FEW_BYTES; - if (memchr(mf->ptr, 0, sz)) - return 1; - return 0; + return !!memchr(mf->ptr, 0, sz); } static void builtin_diff(const char *name_a, @@ -836,7 +999,12 @@ static void builtin_diff(const char *name_a, ecb.outf = xdiff_outf; ecb.priv = &ecbdata; ecbdata.xm.consume = fn_out_consume; + if (o->color_diff_words) + ecbdata.diff_words = + xcalloc(1, sizeof(struct diff_words_data)); xdl_diff(&mf1, &mf2, &xpp, &xecfg, &ecb); + if (o->color_diff_words) + free_diff_words_data(&ecbdata); } free_ab_and_return: @@ -939,8 +1107,8 @@ void fill_filespec(struct diff_filespec *spec, const unsigned char *sha1, { if (mode) { spec->mode = canon_mode(mode); - memcpy(spec->sha1, sha1, 20); - spec->sha1_valid = !!memcmp(sha1, null_sha1, 20); + hashcpy(spec->sha1, sha1); + spec->sha1_valid = !is_null_sha1(sha1); } } @@ -978,7 +1146,7 @@ static int work_tree_matches(const char *name, const unsigned char *sha1) if ((lstat(name, &st) < 0) || !S_ISREG(st.st_mode) || /* careful! */ ce_match_stat(ce, &st, 0) || - memcmp(sha1, ce->sha1, 20)) + hashcmp(sha1, ce->sha1)) return 0; /* we return 1 only when we can stat, it is a regular file, * stat information matches, and sha1 recorded in the cache @@ -1006,7 +1174,7 @@ static struct sha1_size_cache *locate_size_cache(unsigned char *sha1, while (last > first) { int cmp, next = (last + first) >> 1; e = sha1_size_cache[next]; - cmp = memcmp(e->sha1, sha1, 20); + cmp = hashcmp(e->sha1, sha1); if (!cmp) return e; if (cmp < 0) { @@ -1032,7 +1200,7 @@ static struct sha1_size_cache *locate_size_cache(unsigned char *sha1, sizeof(*sha1_size_cache)); e = xmalloc(sizeof(struct sha1_size_cache)); sha1_size_cache[first] = e; - memcpy(e->sha1, sha1, 20); + hashcpy(e->sha1, sha1); e->size = size; return e; } @@ -1354,7 +1522,7 @@ static void diff_fill_sha1_info(struct diff_filespec *one) } } else - memset(one->sha1, 0, 20); + hashclr(one->sha1); } static void run_diff(struct diff_filepair *p, struct diff_options *o) @@ -1417,7 +1585,7 @@ static void run_diff(struct diff_filepair *p, struct diff_options *o) ; } - if (memcmp(one->sha1, two->sha1, 20)) { + if (hashcmp(one->sha1, two->sha1)) { int abbrev = o->full_index ? 40 : DEFAULT_ABBREV; len += snprintf(msg + len, sizeof(msg) - len, @@ -1515,6 +1683,19 @@ void diff_setup(struct diff_options *options) int diff_setup_done(struct diff_options *options) { + int count = 0; + + if (options->output_format & DIFF_FORMAT_NAME) + count++; + if (options->output_format & DIFF_FORMAT_NAME_STATUS) + count++; + if (options->output_format & DIFF_FORMAT_CHECKDIFF) + count++; + if (options->output_format & DIFF_FORMAT_NO_OUTPUT) + count++; + if (count > 1) + die("--name-only, --name-status, --check and -s are mutually exclusive"); + if (options->find_copies_harder) options->detect_rename = DIFF_DETECT_COPY; @@ -1697,6 +1878,8 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac) options->xdl_opts |= XDF_IGNORE_WHITESPACE; else if (!strcmp(arg, "-b") || !strcmp(arg, "--ignore-space-change")) options->xdl_opts |= XDF_IGNORE_WHITESPACE_CHANGE; + else if (!strcmp(arg, "--color-words")) + options->color_diff = options->color_diff_words = 1; else if (!strcmp(arg, "--no-renames")) options->detect_rename = 0; else @@ -1921,7 +2104,7 @@ int diff_unmodified_pair(struct diff_filepair *p) * dealing with a change. */ if (one->sha1_valid && two->sha1_valid && - !memcmp(one->sha1, two->sha1, sizeof(one->sha1))) + !hashcmp(one->sha1, two->sha1)) return 1; /* no change */ if (!one->sha1_valid && !two->sha1_valid) return 1; /* both look at the same file on the filesystem. */ @@ -2060,7 +2243,7 @@ static void diff_resolve_rename_copy(void) if (!p->status) p->status = DIFF_STATUS_RENAMED; } - else if (memcmp(p->one->sha1, p->two->sha1, 20) || + else if (hashcmp(p->one->sha1, p->two->sha1) || p->one->mode != p->two->mode) p->status = DIFF_STATUS_MODIFIED; else { @@ -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-break.c b/diffcore-break.c index ed0e14c6d..acb18db1d 100644 --- a/diffcore-break.c +++ b/diffcore-break.c @@ -56,7 +56,7 @@ static int should_break(struct diff_filespec *src, return 0; /* leave symlink rename alone */ if (src->sha1_valid && dst->sha1_valid && - !memcmp(src->sha1, dst->sha1, 20)) + !hashcmp(src->sha1, dst->sha1)) return 0; /* they are the same */ if (diff_populate_filespec(src, 0) || diff_populate_filespec(dst, 0)) diff --git a/diffcore-rename.c b/diffcore-rename.c index 0ec488a90..ef239012b 100644 --- a/diffcore-rename.c +++ b/diffcore-rename.c @@ -101,7 +101,7 @@ static int is_exact_match(struct diff_filespec *src, int contents_too) { if (src->sha1_valid && dst->sha1_valid && - !memcmp(src->sha1, dst->sha1, 20)) + !hashcmp(src->sha1, dst->sha1)) return 1; if (!contents_too) return 0; @@ -101,8 +101,8 @@ void add_exclude(const char *string, const char *base, x->baselen = baselen; if (which->nr == which->alloc) { which->alloc = alloc_nr(which->alloc); - which->excludes = realloc(which->excludes, - which->alloc * sizeof(x)); + which->excludes = xrealloc(which->excludes, + which->alloc * sizeof(x)); } which->excludes[which->nr++] = x; } @@ -293,7 +293,7 @@ static int read_directory_recursive(struct dir_struct *dir, const char *path, co if (fdir) { int exclude_stk; struct dirent *de; - char fullname[MAXPATHLEN + 1]; + char fullname[PATH_MAX + 1]; memcpy(fullname, base, baselen); exclude_stk = push_exclude_per_directory(dir, base, baselen); diff --git a/dump-cache-tree.c b/dump-cache-tree.c index 1ccaf5177..1f73f1ea7 100644 --- a/dump-cache-tree.c +++ b/dump-cache-tree.c @@ -33,7 +33,7 @@ static int dump_cache_tree(struct cache_tree *it, } else { dump_one(it, pfx, ""); - if (memcmp(it->sha1, ref->sha1, 20) || + if (hashcmp(it->sha1, ref->sha1) || ref->entry_count != it->entry_count || ref->subtree_nr != it->subtree_nr) { dump_one(ref, pfx, "#(ref) "); @@ -135,7 +135,7 @@ static int write_entry(struct cache_entry *ce, char *path, struct checkout *stat int checkout_entry(struct cache_entry *ce, struct checkout *state, char *topath) { - static char path[MAXPATHLEN+1]; + static char path[PATH_MAX + 1]; struct stat st; int len = state->base_dir_len; @@ -172,5 +172,3 @@ int checkout_entry(struct cache_entry *ce, struct checkout *state, char *topath) create_directories(path, state); return write_entry(ce, path, state, 0); } - - diff --git a/environment.c b/environment.c index 87162b257..5fae9ac30 100644 --- a/environment.c +++ b/environment.c @@ -13,20 +13,21 @@ char git_default_email[MAX_GITNAME]; char git_default_name[MAX_GITNAME]; int use_legacy_headers = 1; int trust_executable_bit = 1; -int assume_unchanged = 0; -int prefer_symlink_refs = 0; -int log_all_ref_updates = 0; +int assume_unchanged; +int prefer_symlink_refs; +int log_all_ref_updates; int warn_ambiguous_refs = 1; -int repository_format_version = 0; +int repository_format_version; char git_commit_encoding[MAX_ENCODING_LENGTH] = "utf-8"; int shared_repository = PERM_UMASK; -const char *apply_default_whitespace = NULL; +const char *apply_default_whitespace; int zlib_compression_level = Z_DEFAULT_COMPRESSION; int pager_in_use; int pager_use_color = 1; -static char *git_dir, *git_object_dir, *git_index_file, *git_refs_dir, - *git_graft_file; +static const char *git_dir; +static char *git_object_dir, *git_index_file, *git_refs_dir, *git_graft_file; + static void setup_git_env(void) { git_dir = getenv(GIT_DIR_ENVIRONMENT); @@ -49,7 +50,7 @@ static void setup_git_env(void) git_graft_file = strdup(git_path("info/grafts")); } -char *get_git_dir(void) +const char *get_git_dir(void) { if (!git_dir) setup_git_env(); diff --git a/exec_cmd.c b/exec_cmd.c index 62f51fcd6..e30936d72 100644 --- a/exec_cmd.c +++ b/exec_cmd.c @@ -5,7 +5,7 @@ extern char **environ; static const char *builtin_exec_path = GIT_EXEC_PATH; -static const char *current_exec_path = NULL; +static const char *current_exec_path; void git_set_exec_path(const char *exec_path) { diff --git a/fetch-clone.c b/fetch-clone.c index 5e84c4620..c5cf4776f 100644 --- a/fetch-clone.c +++ b/fetch-clone.c @@ -44,9 +44,8 @@ static int finish_pack(const char *pack_tmp_name, const char *me) for (;;) { int status, code; - int retval = waitpid(pid, &status, 0); - if (retval < 0) { + if (waitpid(pid, &status, 0) < 0) { if (errno == EINTR) continue; error("waitpid failed (%s)", strerror(errno)); diff --git a/fetch-pack.c b/fetch-pack.c index b7824dbed..377feded1 100644 --- a/fetch-pack.c +++ b/fetch-pack.c @@ -24,8 +24,8 @@ static const char *exec = "git-upload-pack"; */ #define MAX_IN_VAIN 256 -static struct commit_list *rev_list = NULL; -static int non_common_revs = 0, multi_ack = 0, use_thin_pack = 0, use_sideband; +static struct commit_list *rev_list; +static int non_common_revs, multi_ack, use_thin_pack, use_sideband; static void rev_list_push(struct commit *commit, int mark) { @@ -250,7 +250,7 @@ done: return retval; } -static struct commit_list *complete = NULL; +static struct commit_list *complete; static int mark_complete(const char *path, const unsigned char *sha1) { @@ -404,7 +404,7 @@ static int everything_local(struct ref **refs, int nr_match, char **match) continue; } - memcpy(ref->new_sha1, local, 20); + hashcpy(ref->new_sha1, local); if (!verbose) continue; fprintf(stderr, @@ -84,7 +84,7 @@ static int process_commit(struct commit *commit) if (commit->object.flags & COMPLETE) return 0; - memcpy(current_commit_sha1, commit->object.sha1, 20); + hashcpy(current_commit_sha1, commit->object.sha1); pull_say("walk %s\n", sha1_to_hex(commit->object.sha1)); diff --git a/fsck-objects.c b/fsck-objects.c index e167f4105..ae0ec8d03 100644 --- a/fsck-objects.c +++ b/fsck-objects.c @@ -14,12 +14,12 @@ #define REACHABLE 0x0001 #define SEEN 0x0002 -static int show_root = 0; -static int show_tags = 0; -static int show_unreachable = 0; -static int check_full = 0; -static int check_strict = 0; -static int keep_cache_objects = 0; +static int show_root; +static int show_tags; +static int show_unreachable; +static int check_full; +static int check_strict; +static int keep_cache_objects; static unsigned char head_sha1[20]; #ifdef NO_D_INO_IN_DIRENT @@ -356,7 +356,7 @@ static void add_sha1_list(unsigned char *sha1, unsigned long ino) int nr; entry->ino = ino; - memcpy(entry->sha1, sha1, 20); + hashcpy(entry->sha1, sha1); nr = sha1_list.nr; if (nr == MAX_SHA1_ENTRIES) { fsck_sha1_list(); @@ -366,13 +366,13 @@ static void add_sha1_list(unsigned char *sha1, unsigned long ino) sha1_list.nr = ++nr; } -static int fsck_dir(int i, char *path) +static void fsck_dir(int i, char *path) { DIR *dir = opendir(path); struct dirent *de; if (!dir) - return 0; + return; while ((de = readdir(dir)) != NULL) { char name[100]; @@ -398,10 +398,9 @@ static int fsck_dir(int i, char *path) fprintf(stderr, "bad sha1 file: %s/%s\n", path, de->d_name); } closedir(dir); - return 0; } -static int default_refs = 0; +static int default_refs; static int fsck_handle_ref(const char *refname, const unsigned char *sha1) { @@ -453,7 +452,7 @@ static int fsck_head_link(void) if (strncmp(git_refs_heads_master + pfxlen, "refs/heads/", 11)) return error("HEAD points to something strange (%s)", git_refs_heads_master + pfxlen); - if (!memcmp(null_sha1, sha1, 20)) + if (is_null_sha1(sha1)) return error("HEAD: not a valid git pointer"); return 0; } diff --git a/git-cherry.sh b/git-cherry.sh index f0e8831fa..8832573fe 100755 --- a/git-cherry.sh +++ b/git-cherry.sh @@ -51,9 +51,6 @@ patch=$tmp-patch mkdir $patch trap "rm -rf $tmp-*" 0 1 2 3 15 -_x40='[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]' -_x40="$_x40$_x40$_x40$_x40$_x40$_x40$_x40$_x40" - for c in $inup do git-diff-tree -p $c diff --git a/git-compat-util.h b/git-compat-util.h index b2e18954c..91f2b0d3f 100644 --- a/git-compat-util.h +++ b/git-compat-util.h @@ -172,7 +172,4 @@ static inline int sane_case(int x, int high) return x; } -#ifndef MAXPATHLEN -#define MAXPATHLEN 256 -#endif #endif diff --git a/git-reset.sh b/git-reset.sh index 36fc8ce25..3133b5bd2 100755 --- a/git-reset.sh +++ b/git-reset.sh @@ -3,9 +3,6 @@ USAGE='[--mixed | --soft | --hard] [<commit-ish>]' . git-sh-setup -tmp=${GIT_DIR}/reset.$$ -trap 'rm -f $tmp-*' 0 1 2 3 15 - update= reset_type=--mixed case "$1" in diff --git a/git-send-email.perl b/git-send-email.perl index a83c7e909..746c52507 100755 --- a/git-send-email.perl +++ b/git-send-email.perl @@ -510,7 +510,7 @@ foreach my $t (@files) { if ($2 eq $from) { next if ($suppress_from); } - else { + elsif ($1 eq 'From') { $author_not_sender = $2; } printf("(mbox) Adding cc: %s from line '%s'\n", diff --git a/git-svn.perl b/git-svn.perl index 0d58bb9b3..0290850b6 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -51,7 +51,8 @@ my ($_revision,$_stdin,$_no_ignore_ext,$_no_stop_copy,$_help,$_rmdir,$_edit, $_message, $_file, $_follow_parent, $_no_metadata, $_template, $_shared, $_no_default_regex, $_no_graft_copy, $_limit, $_verbose, $_incremental, $_oneline, $_l_fmt, $_show_commit, - $_version, $_upgrade, $_authors, $_branch_all_refs, @_opt_m); + $_version, $_upgrade, $_authors, $_branch_all_refs, @_opt_m, + $_merge, $_strategy, $_dry_run); my (@_branch_from, %tree_map, %users, %rusers, %equiv); my ($_svn_co_url_revs, $_svn_pg_peg_revs); my @repo_path_split_cache; @@ -118,6 +119,11 @@ my %cmd = ( { 'message|m=s' => \$_message, 'file|F=s' => \$_file, %cmt_opts } ], + dcommit => [ \&dcommit, 'Commit several diffs to merge with upstream', + { 'merge|m|M' => \$_merge, + 'strategy|s=s' => \$_strategy, + 'dry-run|n' => \$_dry_run, + %cmt_opts } ], ); my $cmd; @@ -500,6 +506,8 @@ sub commit_lib { my @lock = $SVN::Core::VERSION ge '1.2.0' ? (undef, 0) : (); my $commit_msg = "$GIT_SVN_DIR/.svn-commit.tmp.$$"; + my $repo; + ($repo, $SVN_PATH) = repo_path_split($SVN_URL); set_svn_commit_env(); foreach my $c (@revs) { my $log_msg = get_commit_message($c, $commit_msg); @@ -508,6 +516,8 @@ sub commit_lib { # can't track down... (it's probably in the SVN code) defined(my $pid = open my $fh, '-|') or croak $!; if (!$pid) { + $SVN_LOG = libsvn_connect($repo); + $SVN = libsvn_connect($repo); my $ed = SVN::Git::Editor->new( { r => $r_last, ra => $SVN, @@ -557,6 +567,33 @@ sub commit_lib { unlink $commit_msg; } +sub dcommit { + my $gs = "refs/remotes/$GIT_SVN"; + chomp(my @refs = safe_qx(qw/git-rev-list --no-merges/, "$gs..HEAD")); + foreach my $d (reverse @refs) { + if ($_dry_run) { + print "diff-tree $d~1 $d\n"; + } else { + commit_diff("$d~1", $d); + } + } + return if $_dry_run; + fetch(); + my @diff = safe_qx(qw/git-diff-tree HEAD/, $gs); + my @finish; + if (@diff) { + @finish = qw/rebase/; + push @finish, qw/--merge/ if $_merge; + push @finish, "--strategy=$_strategy" if $_strategy; + print STDERR "W: HEAD and $gs differ, using @finish:\n", @diff; + } else { + print "No changes between current HEAD and $gs\n", + "Hard resetting to the latest $gs\n"; + @finish = qw/reset --hard/; + } + sys('git', @finish, $gs); +} + sub show_ignore { $SVN_URL ||= file_to_s("$GIT_SVN_DIR/info/url"); $_use_lib ? show_ignore_lib() : show_ignore_cmd(); @@ -782,6 +819,7 @@ sub commit_diff { } else { $ed->close_edit; } + $_message = $_file = undef; } ########################### utility functions ######################### @@ -92,7 +92,7 @@ static int handle_options(const char*** argv, int* argc) } static const char *alias_command; -static char *alias_string = NULL; +static char *alias_string; static int git_alias_config(const char *var, const char *value) { @@ -120,7 +120,7 @@ static int split_cmdline(char *cmdline, const char ***argv) ; /* skip */ if (count >= size) { size += 16; - *argv = realloc(*argv, sizeof(char*) * size); + *argv = xrealloc(*argv, sizeof(char*) * size); } (*argv)[count++] = cmdline + dst; } else if(!quoted && (c == '\'' || c == '"')) { @@ -191,8 +191,8 @@ static int handle_alias(int *argcp, const char ***argv) fflush(stderr); } - new_argv = realloc(new_argv, sizeof(char*) * - (count + *argcp + 1)); + new_argv = xrealloc(new_argv, sizeof(char*) * + (count + *argcp + 1)); /* insert after command name */ memcpy(new_argv + count, *argv + 1, sizeof(char*) * *argcp); new_argv[count+*argcp] = NULL; @@ -213,8 +213,8 @@ static int handle_alias(int *argcp, const char ***argv) const char git_version_string[] = GIT_VERSION; -#define NEEDS_PREFIX 1 -#define USE_PAGER 2 +#define RUN_SETUP (1<<0) +#define USE_PAGER (1<<1) static void handle_internal_command(int argc, const char **argv, char **envp) { @@ -224,47 +224,54 @@ static void handle_internal_command(int argc, const char **argv, char **envp) int (*fn)(int, const char **, const char *); int option; } commands[] = { - { "version", cmd_version }, - { "help", cmd_help }, - { "log", cmd_log, NEEDS_PREFIX | USE_PAGER }, - { "whatchanged", cmd_whatchanged, NEEDS_PREFIX | USE_PAGER }, - { "show", cmd_show, NEEDS_PREFIX | USE_PAGER }, - { "push", cmd_push, NEEDS_PREFIX }, - { "format-patch", cmd_format_patch, NEEDS_PREFIX }, + { "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, NEEDS_PREFIX }, - { "grep", cmd_grep, NEEDS_PREFIX }, - { "rm", cmd_rm, NEEDS_PREFIX }, - { "add", cmd_add, NEEDS_PREFIX }, - { "rev-list", cmd_rev_list, NEEDS_PREFIX }, - { "init-db", cmd_init_db }, + { "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, NEEDS_PREFIX }, - { "ls-tree", cmd_ls_tree, NEEDS_PREFIX }, - { "tar-tree", cmd_tar_tree, NEEDS_PREFIX }, - { "read-tree", cmd_read_tree, NEEDS_PREFIX }, - { "commit-tree", cmd_commit_tree, NEEDS_PREFIX }, - { "apply", cmd_apply }, - { "show-branch", cmd_show_branch, NEEDS_PREFIX }, - { "diff-files", cmd_diff_files, NEEDS_PREFIX }, - { "diff-index", cmd_diff_index, NEEDS_PREFIX }, - { "diff-stages", cmd_diff_stages, NEEDS_PREFIX }, - { "diff-tree", cmd_diff_tree, NEEDS_PREFIX }, - { "cat-file", cmd_cat_file, NEEDS_PREFIX }, - { "rev-parse", cmd_rev_parse, NEEDS_PREFIX }, - { "write-tree", cmd_write_tree, NEEDS_PREFIX }, - { "mailsplit", cmd_mailsplit }, + { "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 }, - { "stripspace", cmd_stripspace }, - { "update-index", cmd_update_index, NEEDS_PREFIX }, - { "update-ref", cmd_update_ref, NEEDS_PREFIX }, - { "fmt-merge-msg", cmd_fmt_merge_msg, NEEDS_PREFIX }, - { "prune", cmd_prune, NEEDS_PREFIX }, - { "mv", cmd_mv, NEEDS_PREFIX }, - { "prune-packed", cmd_prune_packed, NEEDS_PREFIX }, + { "mailsplit", cmd_mailsplit }, + { "mv", cmd_mv, RUN_SETUP }, + { "name-rev", cmd_name_rev, RUN_SETUP }, + { "pack-objects", cmd_pack_objects, RUN_SETUP }, + { "prune", cmd_prune, RUN_SETUP }, + { "prune-packed", cmd_prune_packed, RUN_SETUP }, + { "push", cmd_push, RUN_SETUP }, + { "read-tree", cmd_read_tree, RUN_SETUP }, { "repo-config", cmd_repo_config }, + { "rev-list", cmd_rev_list, RUN_SETUP }, + { "rev-parse", cmd_rev_parse, RUN_SETUP }, + { "rm", cmd_rm, RUN_SETUP }, + { "show-branch", cmd_show_branch, RUN_SETUP }, + { "show", cmd_show, RUN_SETUP | USE_PAGER }, + { "stripspace", cmd_stripspace }, + { "symbolic-ref", cmd_symbolic_ref, RUN_SETUP }, + { "tar-tree", cmd_tar_tree, RUN_SETUP }, + { "zip-tree", cmd_zip_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; @@ -281,16 +288,16 @@ static void handle_internal_command(int argc, const char **argv, char **envp) continue; prefix = NULL; - if (p->option & NEEDS_PREFIX) + if (p->option & RUN_SETUP) prefix = setup_git_directory(); if (p->option & USE_PAGER) setup_pager(); if (getenv("GIT_TRACE")) { - int i; + int j; fprintf(stderr, "trace: built-in: git"); - for (i = 0; i < argc; ++i) { + for (j = 0; j < argc; ++j) { fputc(' ', stderr); - sq_quote_print(stderr, argv[i]); + sq_quote_print(stderr, argv[j]); } putc('\n', stderr); fflush(stderr); diff --git a/gitweb/README b/gitweb/README index 8d672762e..27c6dac14 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 000000000..16ae8d538 --- /dev/null +++ b/gitweb/git-logo.png diff --git a/gitweb/gitweb.css b/gitweb/gitweb.css index fffdb13d0..901389585 100644 --- a/gitweb/gitweb.css +++ b/gitweb/gitweb.css @@ -117,9 +117,14 @@ div.list_head { a.list { text-decoration: none; + font-weight: bold; color: #000000; } +table.tags a.list { + font-weight: normal; +} + a.list:hover { text-decoration: underline; color: #880000; @@ -171,6 +176,10 @@ tr.dark { background-color: #f6f6f0; } +tr.dark2 { + background-color: #f6f6f0; +} + tr.dark:hover { background-color: #edece6; } @@ -181,12 +190,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 +209,10 @@ td.error { background-color: yellow; } +td.current_head { + text-decoration: underline; +} + table.diff_tree span.file_status.new { color: #008000; } @@ -209,6 +226,10 @@ table.diff_tree span.file_status.mode_chnge { color: #777777; } +table.diff_tree span.file_status.copied { + color: #70a070; +} + /* age2: 60*60*24*2 <= age */ table.project_list td.age2, table.blame td.age2 { font-style: italic; @@ -309,15 +330,30 @@ a.rss_logo:hover { background-color: #ee5500; } -span.tag { +span.refs span { padding: 0px 4px; font-size: 10px; font-weight: normal; - background-color: #ffffaa; border: 1px solid; + background-color: #ffaaff; + border-color: #ffccff #ff00ee #ff00ee #ffccff; +} + +span.refs span.ref { + background-color: #aaaaff; + border-color: #ccccff #0033cc #0033cc #ccccff; +} + +span.refs span.tag { + background-color: #ffffaa; border-color: #ffffcc #ffee00 #ffee00 #ffffcc; } +span.refs span.head { + background-color: #aaffaa; + border-color: #ccffcc #00cc33 #00cc33 #ccffcc; +} + span.atnight { color: #cc0000; } diff --git a/gitweb/gitweb.cgi b/gitweb/gitweb.perl index e5fca63b9..966c54a63 100755 --- a/gitweb/gitweb.cgi +++ b/gitweb/gitweb.perl @@ -14,47 +14,49 @@ 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; +# string of the home link on top of all pages +our $home_link_str = "++GITWEB_HOME_LINK_STR++"; + # name of your site or organization to appear in page titles # replace this with something more descriptive for clearer bookmarks -our $site_name = $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++"; + +# list of git base URLs used for URL to where fetch project from, +# i.e. full URL is "$git_base_url/$project" +our @git_base_url_list = ("++GITWEB_BASE_URL++"); # default blob_plain mimetype and default charset for text/plain blob our $default_blob_plain_mimetype = 'text/plain'; @@ -64,47 +66,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 +114,106 @@ 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; + +## ====================================================================== +## action links + +sub href(%) { + my %mapping = ( + action => "a", + project => "p", + file_name => "f", + hash => "h", + hash_parent => "hp", + hash_base => "hb", + page => "pg", + searchtext => "s", + ); + + my %params = @_; + $params{"project"} ||= $project; + + my $href = "$my_uri?"; + $href .= esc_param( join(";", + map { "$mapping{$_}=$params{$_}" } keys %params + ) ); + + return $href; +} + + +## ====================================================================== +## validation, quoting/unquoting and escaping + sub validate_input { my $input = shift; @@ -175,66 +229,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_blame2(); - exit; -} else { - undef $action; - die_error(undef, "Unknown action."); - exit; -} - # quote unsafe chars, but keep the slash, even when it's not # correct, but quoted slashes look too horrible in bookmarks sub esc_param { @@ -250,6 +244,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 +258,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,126 +308,141 @@ 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 => href(action=>"commit", hash=>$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 format_ref_marker { + my ($refs, $id) = @_; + my $markers = ''; - 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}) { + foreach my $ref (@{$refs->{$id}}) { + my ($type, $name) = qw(); + # e.g. tags/v2.6.11 or heads/next + if ($ref =~ m!^(.*?)s?/(.*)$!) { + $type = $1; + $name = $2; + } else { + $type = "ref"; + $name = $ref; + } + + $markers .= " <span class=\"$type\">" . esc_html($name) . "</span>"; + } + } + + if ($markers) { + return ' <span class="refs">'. $markers . '</span>'; + } else { + return ""; + } } -sub git_read_head { +# format, perhaps shortened and with markers, title line +sub format_subject_html { + my ($long, $short, $href, $extra) = @_; + $extra = '' unless defined($extra); + + if (length($short) < length($long)) { + return $cgi->a({-href => $href, -class => "list", + -title => $long}, + esc_html($short) . $extra); + } else { + return $cgi->a({-href => $href, -class => "list"}, + esc_html($long) . $extra); + } +} + +## ---------------------------------------------------------------------- +## git utility subroutines, invoking git commands + +# get HEAD ref of given project as hash +sub git_get_head_hash { my $project = shift; my $oENV = $ENV{'GIT_DIR'}; my $retval = undef; @@ -413,7 +460,58 @@ sub git_read_head { return $retval; } -sub git_read_hash { +# 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_get_hash_by_ref { my $path = shift; open my $fd, "$projectroot/$path" or return undef; @@ -425,7 +523,7 @@ sub git_read_hash { } } -sub git_read_description { +sub git_get_project_description { my $path = shift; open my $fd, "$projectroot/$path/description" or return undef; @@ -435,12 +533,153 @@ sub git_read_description { return $descr; } -sub git_read_tag { +sub git_get_project_url_list { + my $path = shift; + + open my $fd, "$projectroot/$path/cloneurl" or return undef; + my @git_project_url_list = map { chomp; $_ } <$fd>; + close $fd; + + return wantarray ? @git_project_url_list : \@git_project_url_list; +} + +sub git_get_projects_list { + my @list; + + if (-d $projects_list) { + # search in directory + my $dir = $projects_list; + opendir my ($dh), $dir or return undef; + while (my $dir = readdir($dh)) { + if (-e "$projectroot/$dir/HEAD") { + my $pr = { + path => $dir, + }; + push @list, $pr + } + } + closedir($dh); + } 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_owner { + my $project = shift; + my $owner; + + return undef unless $project; + + # read from file (url-encoded): + # 'git%2Fgit.git Linus+Torvalds' + # 'libs%2Fklibc%2Fklibc.git H.+Peter+Anvin' + # 'linux%2Fhotplug%2Fudev.git Greg+Kroah-Hartman' + if (-f $projects_list) { + open (my $fd , $projects_list); + while (my $line = <$fd>) { + chomp $line; + my ($pr, $ow) = split ' ', $line; + $pr = unescape($pr); + $ow = unescape($ow); + if ($pr eq $project) { + $owner = decode("utf8", $ow, Encode::FB_DEFAULT); + last; + } + } + close $fd; + } + if (!defined $owner) { + $owner = get_file_owner("$projectroot/$project"); + } + + return $owner; +} + +sub git_get_references { + my $type = shift || ""; + my %refs; + my $fd; + # 5dc01c595e6c6ec9ccda4f6f69c131c0dd945f8c refs/tags/v2.6.11 + # c39ae07f393806ccf406ef966e9a15afc43cc36a refs/tags/v2.6.11^{} + if (-f "$projectroot/$project/info/refs") { + open $fd, "$projectroot/$project/info/refs" + or return; + } else { + open $fd, "-|", $GIT, "ls-remote", "." + or return; + } + + while (my $line = <$fd>) { + chomp $line; + if ($line =~ m/^([0-9a-fA-F]{40})\trefs\/($type\/?[^\^]+)/) { + if (defined $refs{$1}) { + push @{$refs{$1}}, $2; + } else { + $refs{$1} = [ $2 ]; + } + } + } + close $fd or return; + return \%refs; +} + +## ---------------------------------------------------------------------- +## parse to hash functions + +sub parse_date { + my $epoch = shift; + my $tz = shift || "-0000"; + + my %date; + my @months = ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"); + my @days = ("Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"); + my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday) = gmtime($epoch); + $date{'hour'} = $hour; + $date{'minute'} = $min; + $date{'mday'} = $mday; + $date{'day'} = $days[$wday]; + $date{'month'} = $months[$mon]; + $date{'rfc2822'} = sprintf "%s, %d %s %4d %02d:%02d:%02d +0000", $days[$wday], $mday, $months[$mon], 1900+$year, $hour ,$min, $sec; + $date{'mday-time'} = sprintf "%d %s %02d:%02d", $mday, $months[$mon], $hour ,$min; + + $tz =~ m/^([+\-][0-9][0-9])([0-9][0-9])$/; + my $local = $epoch + ((int $1 + ($2/60)) * 3600); + ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday) = gmtime($local); + $date{'hour_local'} = $hour; + $date{'minute_local'} = $min; + $date{'tz_local'} = $tz; + return %date; +} + +sub parse_tag { my $tag_id = shift; my %tag; my @comment; - open my $fd, "-|", "$GIT 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,38 +709,7 @@ 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 { +sub parse_commit { my $commit_id = shift; my $commit_text = shift; @@ -512,7 +720,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 +803,716 @@ sub git_read_commit { return %co; } +# parse ref from ref_file, given by ref_id, with given type +sub parse_ref { + my $ref_file = shift; + my $ref_id = shift; + my $type = shift || git_get_type($ref_id); + my %ref_item; + + $ref_item{'type'} = $type; + $ref_item{'id'} = $ref_id; + $ref_item{'epoch'} = 0; + $ref_item{'age'} = "unknown"; + if ($type eq "tag") { + my %tag = parse_tag($ref_id); + $ref_item{'comment'} = $tag{'comment'}; + if ($tag{'type'} eq "commit") { + my %co = parse_commit($tag{'object'}); + $ref_item{'epoch'} = $co{'committer_epoch'}; + $ref_item{'age'} = $co{'age_string'}; + } elsif (defined($tag{'epoch'})) { + my $age = time - $tag{'epoch'}; + $ref_item{'epoch'} = $tag{'epoch'}; + $ref_item{'age'} = age_string($age); + } + $ref_item{'reftype'} = $tag{'type'}; + $ref_item{'name'} = $tag{'name'}; + $ref_item{'refid'} = $tag{'object'}; + } elsif ($type eq "commit"){ + my %co = parse_commit($ref_id); + $ref_item{'reftype'} = "commit"; + $ref_item{'name'} = $ref_file; + $ref_item{'title'} = $co{'title'}; + $ref_item{'refid'} = $ref_id; + $ref_item{'epoch'} = $co{'committer_epoch'}; + $ref_item{'age'} = $co{'age_string'}; + } else { + $ref_item{'reftype'} = $type; + $ref_item{'name'} = $ref_file; + $ref_item{'refid'} = $ref_id; + } + + return %ref_item; +} + +## ...................................................................... +## parse to array of hashes functions + +sub git_get_refs_list { + 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_get_hash_by_ref("$project/$ref_dir/$ref_file"); + my $type = git_get_type($ref_id) || next; + my %ref_item = parse_ref($ref_file, $ref_id, $type); + + push @reflist, \%ref_item; + } + # sort refs by age + @reflist = sort {$b->{'epoch'} <=> $a->{'epoch'}} @reflist; + return \@reflist; +} + +## ---------------------------------------------------------------------- +## 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>) { + next if m/^#/; # skip comments + my ($mime, $exts) = split(/\t+/); + if (defined $exts) { + my @exts = split(/\s+/, $exts); + foreach my $ext (@exts) { + $mimemap{$ext} = $mime; + } + } + } + close(MIME); + + $filename =~ /\.(.*?)$/; + return $mimemap{$1}; +} + +sub mimetype_guess { + my $filename = shift; + my $mime; + $filename =~ /\./ or return undef; + + if ($mimetypes_file) { + my $file = $mimetypes_file; + if ($file !~ m!^/!) { # if it is relative path + # it is relative to project + $file = "$projectroot/$project/$file"; + } + $mime = mimetype_guess_file($filename, $file); + } + $mime ||= mimetype_guess_file($filename, '/etc/mime.types'); + return $mime; +} + +sub blob_mimetype { + my $fd = shift; + my $filename = shift; + + if ($filename) { + my $mime = mimetype_guess($filename); + $mime and return $mime; + } + + # just in case + return $default_blob_plain_mimetype unless $fd; + + if (-T $fd) { + return 'text/plain' . + ($default_text_plain_charset ? '; charset='.$default_text_plain_charset : ''); + } elsif (! $filename) { + return 'application/octet-stream'; + } elsif ($filename =~ m/\.png$/i) { + return 'image/png'; + } elsif ($filename =~ m/\.gif$/i) { + return 'image/gif'; + } elsif ($filename =~ m/\.jpe?g$/i) { + return 'image/jpeg'; + } else { + return 'application/octet-stream'; + } +} + +## ====================================================================== +## functions printing HTML: header, footer, error page + +sub git_header_html { + my $status = shift || "200 OK"; + my $expires = shift; + + my $title = "$site_name git"; + if (defined $project) { + $title .= " - $project"; + if (defined $action) { + $title .= "/$action"; + if (defined $file_name) { + $title .= " - $file_name"; + if ($action eq "tree" && $file_name !~ m|/$|) { + $title .= "/"; + } + } + } + } + my $content_type; + # require explicit support from the UA if we are to send the page as + # 'application/xhtml+xml', otherwise send it as plain old 'text/html'. + # we have to do this because MSIE sometimes globs '*/*', pretending to + # support xhtml+xml but choking when it gets what it asked for. + if (defined $cgi->http('HTTP_ACCEPT') && $cgi->http('HTTP_ACCEPT') =~ m/(,|;|\s|^)application\/xhtml\+xml(,|;|\s|$)/ && $cgi->Accept('application/xhtml+xml') != 0) { + $content_type = 'application/xhtml+xml'; + } else { + $content_type = 'text/html'; + } + print $cgi->header(-type=>$content_type, -charset => 'utf-8', -status=> $status, -expires => $expires); + print <<EOF; +<?xml version="1.0" encoding="utf-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US" lang="en-US"> +<!-- git web interface version $version, (C) 2005-2006, Kay Sievers <kay.sievers\@vrfy.org>, Christian Gierke --> +<!-- git core binaries version $git_version --> +<head> +<meta http-equiv="content-type" content="$content_type; charset=utf-8"/> +<meta name="generator" content="gitweb/$version git/$git_version"/> +<meta name="robots" content="index, nofollow"/> +<title>$title</title> +<link rel="stylesheet" type="text/css" href="$stylesheet"/> +EOF + if (defined $project) { + printf('<link rel="alternate" title="%s log" '. + 'href="%s" type="application/rss+xml"/>'."\n", + esc_param($project), href(action=>"rss")); + } + + 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)}, $home_link_str) . " / "; + if (defined $project) { + print $cgi->a({-href => href(action=>"summary")}, esc_html($project)); + if (defined $action) { + print " / $action"; + } + print "\n"; + if (!defined $searchtext) { + $searchtext = ""; + } + my $search_hash; + if (defined $hash_base) { + $search_hash = $hash_base; + } elsif (defined $hash) { + $search_hash = $hash; + } else { + $search_hash = "HEAD"; + } + $cgi->param("a", "search"); + $cgi->param("h", $search_hash); + print $cgi->startform(-method => "get", -action => $my_uri) . + "<div class=\"search\">\n" . + $cgi->hidden(-name => "p") . "\n" . + $cgi->hidden(-name => "a") . "\n" . + $cgi->hidden(-name => "h") . "\n" . + $cgi->textfield(-name => "s", -value => $searchtext) . "\n" . + "</div>" . + $cgi->end_form() . "\n"; + } + print "</div>\n"; +} + +sub git_footer_html { + print "<div class=\"page_footer\">\n"; + if (defined $project) { + my $descr = git_get_project_description($project); + if (defined $descr) { + print "<div class=\"page_footer_text\">" . esc_html($descr) . "</div>\n"; + } + print $cgi->a({-href => href(action=>"rss"), -class => "rss_logo"}, "RSS") . "\n"; + } else { + print $cgi->a({-href => "$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_print_page_nav { + my ($current, $suppress, $head, $treehead, $treebase, $extra) = @_; + $extra = '' if !defined $extra; # pager or formats + + my @navs = qw(summary shortlog log commit commitdiff tree); + if ($suppress) { + @navs = grep { $_ ne $suppress } @navs; + } + + my %arg = map { $_ => {action=>$_} } @navs; + if (defined $head) { + for (qw(commit commitdiff)) { + $arg{$_}{hash} = $head; + } + if ($current =~ m/^(tree | log | shortlog | commit | commitdiff | search)$/x) { + for (qw(shortlog log)) { + $arg{$_}{hash} = $head; + } + } + } + $arg{tree}{hash} = $treehead if defined $treehead; + $arg{tree}{hash_base} = $treebase if defined $treebase; + + print "<div class=\"page_nav\">\n" . + (join " | ", + map { $_ eq $current ? + $_ : $cgi->a({-href => href(%{$arg{$_}})}, "$_") + } @navs); + print "<br/>\n$extra<br/>\n" . + "</div>\n"; +} + +sub format_paging_nav { + my ($action, $hash, $head, $page, $nrevs) = @_; + my $paging_nav; + + + if ($hash ne $head || $page) { + $paging_nav .= $cgi->a({-href => href(action=>$action)}, "HEAD"); + } else { + $paging_nav .= "HEAD"; + } + + if ($page > 0) { + $paging_nav .= " ⋅ " . + $cgi->a({-href => href(action=>$action, hash=>$hash, page=>$page-1), + -accesskey => "p", -title => "Alt-p"}, "prev"); + } else { + $paging_nav .= " ⋅ prev"; + } + + if ($nrevs >= (100 * ($page+1)-1)) { + $paging_nav .= " ⋅ " . + $cgi->a({-href => href(action=>$action, hash=>$hash, page=>$page+1), + -accesskey => "n", -title => "Alt-n"}, "next"); + } else { + $paging_nav .= " ⋅ next"; + } + + return $paging_nav; +} + +## ...................................................................... +## functions printing or outputting HTML: div + +sub git_print_header_div { + my ($action, $title, $hash, $hash_base) = @_; + my %args = (); + + $args{action} = $action; + $args{hash} = $hash if $hash; + $args{hash_base} = $hash_base if $hash_base; + + print "<div class=\"header\">\n" . + $cgi->a({-href => href(%args), -class => "title"}, + $title ? $title : $action) . + "\n</div>\n"; +} + +sub git_print_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 => href(action=>"blob_plain", file_name=>$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_difftree_body { + my ($difftree, $parent) = @_; + + print "<div class=\"list_head\">\n"; + if ($#{$difftree} > 10) { + print(($#{$difftree} + 1) . " files changed:\n"); + } + print "</div>\n"; + + print "<table class=\"diff_tree\">\n"; + my $alternate = 0; + foreach my $line (@{$difftree}) { + # ':100644 100644 03b218260e99b78c6df0ed378e59ed9205ccc96d 3b93d5e7cc7f7dd4ebed13a5cc1a4ad976fc94d8 M ls-files.c' + # ':100644 100644 7f9281985086971d3877aca27704f2aaf9c448ce bc190ebc71bbd923f2b728e505408f5e54bd073a M rev-tree.c' + if ($line !~ m/^:([0-7]{6}) ([0-7]{6}) ([0-9a-fA-F]{40}) ([0-9a-fA-F]{40}) (.)([0-9]{0,3})\t(.*)$/) { + next; + } + my $from_mode = $1; + my $to_mode = $2; + my $from_id = $3; + my $to_id = $4; + my $status = $5; + my $similarity = $6; # score + my $file = validate_input(unquote($7)); + + if ($alternate) { + print "<tr class=\"dark\">\n"; + } else { + print "<tr class=\"light\">\n"; + } + $alternate ^= 1; + + if ($status eq "A") { # created + my $mode_chng = ""; + if (S_ISREG(oct $to_mode)) { + $mode_chng = sprintf(" with mode: %04o", (oct $to_mode) & 0777); + } + print "<td>" . + $cgi->a({-href => href(action=>"blob", hash=>$to_id, hash_base=>$hash, file_name=>$file), + -class => "list"}, esc_html($file)) . + "</td>\n" . + "<td><span class=\"file_status new\">[new " . file_type($to_mode) . "$mode_chng]</span></td>\n" . + "<td class=\"link\">" . + $cgi->a({-href => href(action=>"blob", hash=>$to_id, hash_base=>$hash, file_name=>$file)}, "blob") . + "</td>\n"; + + } elsif ($status eq "D") { # deleted + print "<td>" . + $cgi->a({-href => href(action=>"blob", hash=>$from_id, hash_base=>$parent, file_name=>$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 => href(action=>"blob", hash=>$from_id, hash_base=>$parent, file_name=>$file)}, "blob") . " | " . + $cgi->a({-href => href(action=>"history", hash_base=>$parent, file_name=>$file)}, "history") . + "</td>\n" + + } elsif ($status eq "M" || $status eq "T") { # modified, or type changed + my $mode_chnge = ""; + if ($from_mode != $to_mode) { + $mode_chnge = " <span class=\"file_status mode_chnge\">[changed"; + if (((oct $from_mode) & S_IFMT) != ((oct $to_mode) & S_IFMT)) { + $mode_chnge .= " from " . file_type($from_mode) . " to " . file_type($to_mode); + } + if (((oct $from_mode) & 0777) != ((oct $to_mode) & 0777)) { + if (S_ISREG($from_mode) && S_ISREG($to_mode)) { + $mode_chnge .= sprintf(" mode: %04o->%04o", (oct $from_mode) & 0777, (oct $to_mode) & 0777); + } elsif (S_ISREG($to_mode)) { + $mode_chnge .= sprintf(" mode: %04o", (oct $to_mode) & 0777); + } + } + $mode_chnge .= "]</span>\n"; + } + print "<td>"; + if ($to_id ne $from_id) { # modified + print $cgi->a({-href => href(action=>"blobdiff", hash=>$to_id, hash_parent=>$from_id, hash_base=>$hash, file_name=>$file), + -class => "list"}, esc_html($file)); + } else { # mode changed + print $cgi->a({-href => href(action=>"blob", hash=>$to_id, hash_base=>$hash, file_name=>$file), + -class => "list"}, esc_html($file)); + } + print "</td>\n" . + "<td>$mode_chnge</td>\n" . + "<td class=\"link\">" . + $cgi->a({-href => href(action=>"blob", hash=>$to_id, hash_base=>$hash, file_name=>$file)}, "blob"); + if ($to_id ne $from_id) { # modified + print $cgi->a({-href => href(action=>"blobdiff", hash=>$to_id, hash_parent=>$from_id, hash_base=>$hash, file_name=>$file)}, "diff"); + } + print " | " . $cgi->a({-href => href(action=>"history", hash_base=>$hash, file_name=>$file)}, "history") . "\n"; + print "</td>\n"; + + } elsif ($status eq "R") { # renamed + my ($from_file, $to_file) = split "\t", $file; + my $mode_chng = ""; + if ($from_mode != $to_mode) { + $mode_chng = sprintf(", mode: %04o", (oct $to_mode) & 0777); + } + print "<td>" . + $cgi->a({-href => href(action=>"blob", hash=>$to_id, hash_base=>$hash, file_name=>$to_file), + -class => "list"}, esc_html($to_file)) . "</td>\n" . + "<td><span class=\"file_status moved\">[moved from " . + $cgi->a({-href => href(action=>"blob", hash=>$from_id, hash_base=>$parent, file_name=>$from_file), + -class => "list"}, esc_html($from_file)) . + " with " . (int $similarity) . "% similarity$mode_chng]</span></td>\n" . + "<td class=\"link\">" . + $cgi->a({-href => href(action=>"blob", hash=>$to_id, hash_base=>$hash, file_name=>$to_file)}, "blob"); + if ($to_id ne $from_id) { + print " | " . + $cgi->a({-href => "$my_uri?" . + esc_param("p=$project;a=blobdiff;h=$to_id;hp=$from_id;hb=$hash;f=$to_file;fp=$from_file")}, "diff"); + } + print "</td>\n"; + + } elsif ($status eq "C") { # copied + my ($from_file, $to_file) = split "\t", $file; + my $mode_chng = ""; + if ($from_mode != $to_mode) { + $mode_chng = sprintf(", mode: %04o", (oct $to_mode) & 0777); + } + print "<td>" . + $cgi->a({-href => href(action=>"blob", hash=>$to_id, hash_base=>$hash, file_name=>$to_file), + -class => "list"}, esc_html($to_file)) . "</td>\n" . + "<td><span class=\"file_status copied\">[copied from " . + $cgi->a({-href => href(action=>"blob", hash=>$from_id, hash_base=>$parent, file_name=>$from_file), + -class => "list"}, esc_html($from_file)) . + " with " . (int $similarity) . "% similarity$mode_chng]</span></td>\n" . + "<td class=\"link\">" . + $cgi->a({-href => href(action=>"blob", hash=>$to_id, hash_base=>$hash, file_name=>$to_file)}, "blob"); + if ($to_id ne $from_id) { + print " | " . + $cgi->a({-href => "$my_uri?" . + esc_param("p=$project;a=blobdiff;h=$to_id;hp=$from_id;hb=$hash;f=$to_file;fp=$from_file")}, "diff"); + } + print "</td>\n"; + } # we should not encounter Unmerged (U) or Unknown (X) status + print "</tr>\n"; + } + print "</table>\n"; +} + +sub git_shortlog_body { + # uses global variable $project + my ($revlist, $from, $to, $refs, $extra) = @_; + $from = 0 unless defined $from; + $to = $#{$revlist} if (!defined $to || $#{$revlist} < $to); + + print "<table class=\"shortlog\" cellspacing=\"0\">\n"; + my $alternate = 0; + for (my $i = $from; $i <= $to; $i++) { + my $commit = $revlist->[$i]; + #my $ref = defined $refs ? format_ref_marker($refs, $commit) : ''; + my $ref = format_ref_marker($refs, $commit); + my %co = parse_commit($commit); + if ($alternate) { + print "<tr class=\"dark\">\n"; + } else { + print "<tr class=\"light\">\n"; + } + $alternate ^= 1; + # git_summary() used print "<td><i>$co{'age_string'}</i></td>\n" . + print "<td title=\"$co{'age_string_age'}\"><i>$co{'age_string_date'}</i></td>\n" . + "<td><i>" . esc_html(chop_str($co{'author_name'}, 10)) . "</i></td>\n" . + "<td>"; + print format_subject_html($co{'title'}, $co{'title_short'}, href(action=>"commit", hash=>$commit), $ref); + print "</td>\n" . + "<td class=\"link\">" . + $cgi->a({-href => href(action=>"commit", hash=>$commit)}, "commit") . " | " . + $cgi->a({-href => href(action=>"commitdiff", hash=>$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_history_body { + # Warning: assumes constant type (blob or tree) during history + my ($fd, $refs, $hash_base, $ftype, $extra) = @_; + + print "<table class=\"history\" cellspacing=\"0\">\n"; + my $alternate = 0; + while (my $line = <$fd>) { + if ($line !~ m/^([0-9a-fA-F]{40})/) { + next; + } + + my $commit = $1; + my %co = parse_commit($commit); + if (!%co) { + next; + } + + my $ref = format_ref_marker($refs, $commit); + + if ($alternate) { + print "<tr class=\"dark\">\n"; + } else { + print "<tr class=\"light\">\n"; + } + $alternate ^= 1; + print "<td title=\"$co{'age_string_age'}\"><i>$co{'age_string_date'}</i></td>\n" . + # shortlog uses chop_str($co{'author_name'}, 10) + "<td><i>" . esc_html(chop_str($co{'author_name'}, 15, 3)) . "</i></td>\n" . + "<td>"; + # originally git_history used chop_str($co{'title'}, 50) + print format_subject_html($co{'title'}, $co{'title_short'}, href(action=>"commit", hash=>$commit), $ref); + print "</td>\n" . + "<td class=\"link\">" . + $cgi->a({-href => href(action=>"commit", hash=>$commit)}, "commit") . " | " . + $cgi->a({-href => href(action=>"commitdiff", hash=>$commit)}, "commitdiff") . " | " . + $cgi->a({-href => href(action=>$ftype, hash_base=>$commit, file_name=>$file_name)}, $ftype); + + if ($ftype eq 'blob') { + my $blob_current = git_get_hash_by_path($hash_base, $file_name); + my $blob_parent = git_get_hash_by_path($commit, $file_name); + if (defined $blob_current && defined $blob_parent && + $blob_current ne $blob_parent) { + print " | " . + $cgi->a({-href => href(action=>"blobdiff", hash=>$blob_current, hash_parent=>$blob_parent, hash_base=>$commit, file_name=>$file_name)}, + "diff to current"); + } + } + print "</td>\n" . + "</tr>\n"; + } + if (defined $extra) { + print "<tr>\n" . + "<td colspan=\"4\">$extra</td>\n" . + "</tr>\n"; + } + print "</table>\n"; +} + +sub git_tags_body { + # uses global variable $project + my ($taglist, $from, $to, $extra) = @_; + $from = 0 unless defined $from; + $to = $#{$taglist} if (!defined $to || $#{$taglist} < $to); + + print "<table class=\"tags\" cellspacing=\"0\">\n"; + my $alternate = 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 => href(action=>$tag{'reftype'}, hash=>$tag{'refid'}), + -class => "list"}, "<b>" . esc_html($tag{'name'}) . "</b>") . + "</td>\n" . + "<td>"; + if (defined $comment) { + print format_subject_html($comment, $comment_short, href(action=>"tag", hash=>$tag{'id'})); + } + print "</td>\n" . + "<td class=\"selflink\">"; + if ($tag{'type'} eq "tag") { + print $cgi->a({-href => href(action=>"tag", hash=>$tag{'id'})}, "tag"); + } else { + print " "; + } + print "</td>\n" . + "<td class=\"link\">" . " | " . + $cgi->a({-href => href(action=>$tag{'reftype'}, hash=>$tag{'refid'})}, $tag{'reftype'}); + if ($tag{'reftype'} eq "commit") { + print " | " . $cgi->a({-href => href(action=>"shortlog", hash=>$tag{'name'})}, "shortlog") . + " | " . $cgi->a({-href => href(action=>"log", hash=>$tag{'refid'})}, "log"); + } elsif ($tag{'reftype'} eq "blob") { + print " | " . $cgi->a({-href => href(action=>"blob_plain", hash=>$tag{'refid'})}, "raw"); + } + print "</td>\n" . + "</tr>"; + } + if (defined $extra) { + print "<tr>\n" . + "<td colspan=\"5\">$extra</td>\n" . + "</tr>\n"; + } + print "</table>\n"; +} + +sub git_heads_body { + # uses global variable $project + my ($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 => href(action=>"shortlog", hash=>$tag{'name'}), + -class => "list"}, "<b>" . esc_html($tag{'name'}) . "</b>") . + "</td>\n" . + "<td class=\"link\">" . + $cgi->a({-href => href(action=>"shortlog", hash=>$tag{'name'})}, "shortlog") . " | " . + $cgi->a({-href => href(action=>"log", hash=>$tag{'name'})}, "log") . + "</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 +1528,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 +1539,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 +1553,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 +1566,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,210 +1580,35 @@ 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; - - $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); -} +## ====================================================================== +## ====================================================================== +## actions -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 @list = git_get_projects_list(); 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'}); + my $head = git_get_head_hash($pr->{'path'}); if (!defined $head) { next; } $ENV{'GIT_DIR'} = "$projectroot/$pr->{'path'}"; - my %co = git_read_commit($head); + my %co = parse_commit($head); if (!%co) { next; } $pr->{'commit'} = \%co; if (!defined $pr->{'descr'}) { - my $descr = git_read_description($pr->{'path'}) || ""; + my $descr = git_get_project_description($pr->{'path'}) || ""; $pr->{'descr'} = chop_str($descr, 25, 5); } if (!defined $pr->{'owner'}) { @@ -878,6 +1616,7 @@ sub git_project_list { } push @projects, $pr; } + git_header_html(); if (-f $home_text) { print "<div class=\"index_include\">\n"; @@ -888,29 +1627,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 +1674,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,307 +1691,75 @@ 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); - my %co = git_read_commit($head); - my %cd = date_str($co{'committer_epoch'}, $co{'committer_tz'}); + my $descr = git_get_project_description($project) || "none"; + my $head = git_get_head_hash($project); + my %co = parse_commit($head); + my %cd = parse_date($co{'committer_epoch'}, $co{'committer_tz'}); - my $owner; - if (-f $projects_list) { - open (my $fd , $projects_list); - while (my $line = <$fd>) { - chomp $line; - my ($pr, $ow) = split ' ', $line; - $pr = unescape($pr); - $ow = unescape($ow); - if ($pr eq $project) { - $owner = decode("utf8", $ow, Encode::FB_DEFAULT); - last; - } - } - close $fd; - } - if (!defined $owner) { - $owner = get_file_owner("$projectroot/$project"); - } + my $owner = git_get_project_owner($project); - my $refs = read_info_ref(); + my $refs = git_get_references(); 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_print_page_nav('summary','', $head); + print "<div class=\"title\"> </div>\n"; print "<table cellspacing=\"0\">\n" . "<tr><td>description</td><td>" . esc_html($descr) . "</td></tr>\n" . "<tr><td>owner</td><td>$owner</td></tr>\n" . - "<tr><td>last change</td><td>$cd{'rfc2822'}</td></tr>\n" . - "</table>\n"; - open my $fd, "-|", "$GIT rev-list --max-count=17 " . git_read_head($project) or die_error(undef, "Open failed."); - my (@revlist) = map { chomp; $_ } <$fd>; - close $fd; - print "<div>\n" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog"), -class => "title"}, "shortlog") . - "</div>\n"; - my $i = 16; - print "<table cellspacing=\"0\">\n"; - my $alternate = 0; - foreach my $commit (@revlist) { - my %co = git_read_commit($commit); - my %ad = date_str($co{'author_epoch'}); - if ($alternate) { - print "<tr class=\"dark\">\n"; - } else { - print "<tr class=\"light\">\n"; - } - $alternate ^= 1; - if ($i-- > 0) { - my $ref = ""; - if (defined $refs->{$commit}) { - $ref = " <span class=\"tag\">" . esc_html($refs->{$commit}) . "</span>"; - } - print "<td><i>$co{'age_string'}</i></td>\n" . - "<td><i>" . esc_html(chop_str($co{'author_name'}, 10)) . "</i></td>\n" . - "<td>"; - if (length($co{'title_short'}) < length($co{'title'})) { - print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$commit"), -class => "list", -title => "$co{'title'}"}, - "<b>" . esc_html($co{'title_short'}) . "$ref</b>"); - } else { - print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$commit"), -class => "list"}, - "<b>" . esc_html($co{'title'}) . "$ref</b>"); - } - print "</td>\n" . - "<td class=\"link\">" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$commit")}, "commit") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$commit")}, "commitdiff") . - "</td>\n" . - "</tr>"; - } else { - print "<td>" . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog")}, "...") . "</td>\n" . - "</tr>"; - last; - } + "<tr><td>last change</td><td>$cd{'rfc2822'}</td></tr>\n"; + # use per project git URL list in $projectroot/$project/cloneurl + # or make project git URL from git base URL and project name + my $url_tag = "URL"; + my @url_list = git_get_project_url_list($project); + @url_list = map { "$_/$project" } @git_base_url_list unless @url_list; + foreach my $git_url (@url_list) { + next unless $git_url; + print "<tr><td>$url_tag</td><td>$git_url</td></tr>\n"; + $url_tag = ""; } - print "</table\n>"; + print "</table>\n"; + + open my $fd, "-|", $GIT, "rev-list", "--max-count=17", git_get_head_hash($project) + or die_error(undef, "Open git-rev-list failed"); + my @revlist = map { chomp; $_ } <$fd>; + close $fd; + git_print_header_div('shortlog'); + git_shortlog_body(\@revlist, 0, 15, $refs, + $cgi->a({-href => href(action=>"shortlog")}, "...")); - my $taglist = git_read_refs("refs/tags"); + my $taglist = git_get_refs_list("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_print_header_div('tags'); + git_tags_body($taglist, 0, 15, + $cgi->a({-href => href(action=>"tags")}, "...")); } - my $headlist = git_read_refs("refs/heads"); + my $headlist = git_get_refs_list("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_print_header_div('heads'); + git_heads_body($headlist, $head, 0, 15, + $cgi->a({-href => href(action=>"heads")}, "...")); } - git_footer_html(); -} -sub git_print_page_path { - my $name = shift; - my $type = shift; - - if (!defined $name) { - print "<div class=\"page_path\"><b>/</b></div>\n"; - } elsif ($type =~ "blob") { - print "<div class=\"page_path\"><b>" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob_plain;f=$file_name")}, esc_html($name)) . "</b><br/></div>\n"; - } else { - print "<div class=\"page_path\"><b>" . esc_html($name) . "</b><br/></div>\n"; - } + git_footer_html(); } sub git_tag { - my $head = git_read_head($project); + my $head = git_get_head_hash($project); git_header_html(); - print "<div class=\"page_nav\">\n" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary")}, "summary") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog")}, "shortlog") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log")}, "log") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$head")}, "commit") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$head")}, "commitdiff") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;hb=$head")}, "tree") . "<br/>\n" . - "<br/>\n" . - "</div>\n"; - my %tag = git_read_tag($hash); - print "<div>\n" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash"), -class => "title"}, esc_html($tag{'name'})) . "\n" . - "</div>\n"; + git_print_page_nav('','', $head,undef,$head); + my %tag = parse_tag($hash); + git_print_header_div('commit', esc_html($tag{'name'}), $hash); print "<div class=\"title_text\">\n" . "<table cellspacing=\"0\">\n" . "<tr>\n" . "<td>object</td>\n" . - "<td>" . $cgi->a({-class => "list", -href => "$my_uri?" . esc_param("p=$project;a=$tag{'type'};h=$tag{'object'}")}, $tag{'object'}) . "</td>\n" . - "<td class=\"link\">" . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$tag{'type'};h=$tag{'object'}")}, $tag{'type'}) . "</td>\n" . + "<td>" . $cgi->a({-class => "list", -href => href(action=>$tag{'type'}, hash=>$tag{'object'})}, $tag{'object'}) . "</td>\n" . + "<td class=\"link\">" . $cgi->a({-href => href(action=>$tag{'type'}, hash=>$tag{'object'})}, $tag{'type'}) . "</td>\n" . "</tr>\n"; if (defined($tag{'author'})) { - my %ad = date_str($tag{'epoch'}, $tag{'tz'}); + my %ad = parse_date($tag{'epoch'}, $tag{'tz'}); print "<tr><td>author</td><td>" . esc_html($tag{'author'}) . "</td></tr>\n"; print "<tr><td></td><td>" . $ad{'rfc2822'} . sprintf(" (%02d:%02d %s)", $ad{'hour_local'}, $ad{'minute_local'}, $ad{'tz_local'}) . "</td></tr>\n"; } @@ -1255,11 +1777,11 @@ sub git_tag { sub git_blame2 { my $fd; my $ftype; - die_error(undef, "Permission denied.") if (!git_get_project_config_bool ('blame')); + die_error(undef, "Permission denied") if (!git_get_project_config_bool ('blame')); die_error('404 Not Found', "File name not defined") if (!$file_name); - $hash_base ||= git_read_head($project); - die_error(undef, "Reading commit failed") unless ($hash_base); - my %co = git_read_commit($hash_base) + $hash_base ||= git_get_head_hash($project); + die_error(undef, "Couldn't find base commit") unless ($hash_base); + my %co = parse_commit($hash_base) or die_error(undef, "Reading commit failed"); if (!defined $hash) { $hash = git_get_hash_by_path($hash_base, $file_name, "blob") @@ -1267,26 +1789,18 @@ sub git_blame2 { } $ftype = git_get_type($hash); if ($ftype !~ "blob") { - die_error("400 Bad Request", "object is not a blob"); + die_error("400 Bad Request", "Object is not a blob"); } open ($fd, "-|", $GIT, "blame", '-l', $file_name, $hash_base) - or die_error(undef, "Open failed"); + or die_error(undef, "Open git-blame 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"; + my $formats_nav = + $cgi->a({-href => href(action=>"blobl", hash=>$hash, hash_base=>$hash_base, file_name=>$file_name)}, "blob") . + " | " . $cgi->a({-href => href(action=>"blame", file_name=>$file_name)}, "head"); + git_print_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav); + git_print_header_div('commit', esc_html($co{'title'}), $hash_base); git_print_page_path($file_name, $ftype); - my @rev_color = (qw(light dark)); + my @rev_color = (qw(light2 dark2)); my $num_colors = scalar(@rev_color); my $current_color = 0; my $last_rev; @@ -1308,7 +1822,7 @@ sub git_blame2 { } print "<tr class=\"$rev_color[$current_color]\">\n"; print "<td class=\"sha1\">" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$full_rev;f=$file_name")}, esc_html($rev)) . "</td>\n"; + $cgi->a({-href => href(action=>"commit", hash=>$full_rev, file_name=>$file_name)}, esc_html($rev)) . "</td>\n"; print "<td class=\"linenr\"><a id=\"l$lineno\" href=\"#l$lineno\" class=\"linenr\">" . esc_html($lineno) . "</a></td>\n"; print "<td class=\"pre\">" . esc_html($data) . "</td>\n"; print "</tr>\n"; @@ -1321,33 +1835,25 @@ sub git_blame2 { sub git_blame { my $fd; - die_error('403 Permission denied', "Permission denied.") if (!git_get_project_config_bool ('blame')); - die_error('404 Not Found', "What file will it be, master?") if (!$file_name); - $hash_base ||= git_read_head($project); - die_error(undef, "Reading commit failed.") unless ($hash_base); - my %co = git_read_commit($hash_base) - or die_error(undef, "Reading commit failed."); + 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_get_head_hash($project); + die_error(undef, "Couldn't find base commit") unless ($hash_base); + my %co = parse_commit($hash_base) + or die_error(undef, "Reading commit failed"); if (!defined $hash) { $hash = git_get_hash_by_path($hash_base, $file_name, "blob") - or die_error(undef, "Error lookup file."); + 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"; - git_print_page_path($file_name); + my $formats_nav = + $cgi->a({-href => href(action=>"blobl", hash=>$hash, hash_base=>$hash_base, file_name=>$file_name)}, "blob") . + " | " . $cgi->a({-href => href(action=>"blame", file_name=>$file_name)}, "head"); + git_print_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav); + git_print_header_div('commit', esc_html($co{'title'}), $hash_base); + git_print_page_path($file_name, 'blob'); print "<div class=\"page_body\">\n"; print <<HTML; <table class="blame"> @@ -1376,7 +1882,7 @@ HTML chomp $line; $line_class_num = ($line_class_num + 1) % $line_class_len; - if ($line =~ m/^([0-9a-fA-F]{40})\t\(\s*([^\t]+)\t(\d+) \+\d\d\d\d\t(\d+)\)(.*)$/) { + if ($line =~ m/^([0-9a-fA-F]{40})\t\(\s*([^\t]+)\t(\d+) [+-]\d\d\d\d\t(\d+)\)(.*)$/) { $long_rev = $1; $author = $2; $time = $3; @@ -1393,18 +1899,13 @@ 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; <tr class="$line_class[$line_class_num]"> - <td class="sha1"><a href="$my_uri?${\esc_param ("p=$project;a=commit;h=$long_rev")}" class="text">$short_rev..</a></td> + <td class="sha1"><a href="${\href (action=>"commit", hash=>$long_rev)}" class="text">$short_rev..</a></td> <td class="$age_class">$age_str</td> <td>$author</td> <td class="linenr"><a id="$lineno" href="#$lineno" class="linenr">$lineno</a></td> @@ -1419,211 +1920,46 @@ HTML } sub git_tags { - my $head = git_read_head($project); + my $head = git_get_head_hash($project); git_header_html(); - print "<div class=\"page_nav\">\n" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary")}, "summary") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog")}, "shortlog") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log")}, "log") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$head")}, "commit") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$head")}, "commitdiff") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;hb=$head")}, "tree") . "<br/>\n" . - "<br/>\n" . - "</div>\n"; - my $taglist = git_read_refs("refs/tags"); - print "<div>\n" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary"), -class => "title"}, " ") . - "</div>\n"; - print "<table cellspacing=\"0\">\n"; - my $alternate = 0; + git_print_page_nav('','', $head,undef,$head); + git_print_header_div('summary', $project); + + my $taglist = git_get_refs_list("refs/tags"); 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); + my $head = git_get_head_hash($project); git_header_html(); - print "<div class=\"page_nav\">\n" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary")}, "summary") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog")}, "shortlog") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log")}, "log") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$head")}, "commit") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$head")}, "commitdiff") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;hb=$head")}, "tree") . "<br/>\n" . - "<br/>\n" . - "</div>\n"; - my $taglist = git_read_refs("refs/heads"); - print "<div>\n" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary"), -class => "title"}, " ") . - "</div>\n"; - print "<table cellspacing=\"0\">\n"; - my $alternate = 0; + git_print_page_nav('','', $head,undef,$head); + git_print_header_div('summary', $project); + + my $taglist = git_get_refs_list("refs/heads"); 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; - } - } - close(MIME); - - $filename =~ /\.(.*?)$/; - return $mimemap{$1}; -} - -sub mimetype_guess { - my $filename = shift; - my $mime; - $filename =~ /\./ or return undef; - - if ($mimetypes_file) { - my $file = $mimetypes_file; - #$file =~ m#^/# or $file = "$projectroot/$path/$file"; - $mime = mimetype_guess_file($filename, $file); - } - $mime ||= mimetype_guess_file($filename, '/etc/mime.types'); - return $mime; -} - -sub git_blob_plain_mimetype { - my $fd = shift; - my $filename = shift; - - if ($filename) { - my $mime = mimetype_guess($filename); - $mime and return $mime; - } - - # just in case - return $default_blob_plain_mimetype unless $fd; - - if (-T $fd) { - return 'text/plain' . - ($default_text_plain_charset ? '; charset='.$default_text_plain_charset : ''); - } elsif (! $filename) { - return 'application/octet-stream'; - } elsif ($filename =~ m/\.png$/i) { - return 'image/png'; - } elsif ($filename =~ m/\.gif$/i) { - return 'image/gif'; - } elsif ($filename =~ m/\.jpe?g$/i) { - return 'image/jpeg'; - } else { - return 'application/octet-stream'; - } -} - sub git_blob_plain { if (!defined $hash) { - if (defined $file_name) { - my $base = $hash_base || git_read_head($project); - $hash = git_get_hash_by_path($base, $file_name, "blob") || die_error(undef, "Error lookup file."); - } else { - die_error(undef, "No file name defined."); - } - } + if (defined $file_name) { + my $base = $hash_base || git_get_head_hash($project); + $hash = git_get_hash_by_path($base, $file_name, "blob") + or die_error(undef, "Error lookup file"); + } else { + die_error(undef, "No file name defined"); + } + } 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); + $type ||= blob_mimetype($fd, $file_name); # save as filename, even when no $file_name is given my $save_as = "$hash"; @@ -1644,42 +1980,37 @@ sub git_blob_plain { sub git_blob { if (!defined $hash) { - if (defined $file_name) { - my $base = $hash_base || git_read_head($project); - $hash = git_get_hash_by_path($base, $file_name, "blob") || die_error(undef, "Error lookup file."); - } else { - die_error(undef, "No file name defined."); - } - } + if (defined $file_name) { + my $base = $hash_base || git_get_head_hash($project); + $hash = git_get_hash_by_path($base, $file_name, "blob") + or die_error(undef, "Error lookup file"); + } else { + die_error(undef, "No file name defined"); + } + } my $have_blame = git_get_project_config_bool ('blame'); - open my $fd, "-|", "$GIT cat-file blob $hash" or die_error(undef, "Open failed."); - my $mimetype = git_blob_plain_mimetype($fd, $file_name); + open my $fd, "-|", $GIT, "cat-file", "blob", $hash + or die_error(undef, "Couldn't cat $file_name, $hash"); + my $mimetype = blob_mimetype($fd, $file_name); if ($mimetype !~ m/^text\//) { close $fd; return git_blob_plain($mimetype); } git_header_html(); - 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"; + my $formats_nav = ''; + if (defined $hash_base && (my %co = parse_commit($hash_base))) { 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 => href(action=>"blame", hash=>$hash, hash_base=>$hash_base, file_name=>$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 => href(action=>"blob_plain", hash=>$hash, file_name=>$file_name)}, "plain") . + " | " . $cgi->a({-href => href(action=>"blob", hash_base=>"HEAD", file_name=>$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 => href(action=>"blob_plain", hash=>$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_print_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav); + git_print_header_div('commit', esc_html($co{'title'}), $hash_base); } else { print "<div class=\"page_nav\">\n" . "<br/><br/></div>\n" . @@ -1691,12 +2022,7 @@ sub git_blob { 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"; @@ -1706,7 +2032,7 @@ sub git_blob { sub git_tree { if (!defined $hash) { - $hash = git_read_head($project); + $hash = git_get_head_hash($project); if (defined $file_name) { my $base = $hash_base || $hash; $hash = git_get_hash_by_path($base, $file_name, "tree"); @@ -1716,33 +2042,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 $refs = git_get_references(); + my $ref = format_ref_marker($refs, $hash_base); git_header_html(); my $base_key = ""; my $base = ""; - if (defined $hash_base && (my %co = git_read_commit($hash_base))) { + my $have_blame = git_get_project_config_bool ('blame'); + if (defined $hash_base && (my %co = parse_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_print_page_nav('tree','', $hash_base); + git_print_header_div('commit', esc_html($co{'title'}) . $ref, $hash_base); } else { print "<div class=\"page_nav\">\n"; print "<br/><br/></div>\n"; @@ -1751,7 +2066,7 @@ sub git_tree { if (defined $file_name) { $base = esc_html("$file_name/"); } - git_print_page_path($file_name); + git_print_page_path($file_name, 'tree'); print "<div class=\"page_body\">\n"; print "<table cellspacing=\"0\">\n"; my $alternate = 0; @@ -1774,9 +2089,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=$t_hash;hb=$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") { @@ -1795,163 +2112,47 @@ 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); + my $head = git_get_head_hash($project); if (!defined $hash) { $hash = $head; } if (!defined $page) { $page = 0; } - my $refs = read_info_ref(); - git_header_html(); - print "<div class=\"page_nav\">\n"; - print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary")}, "summary") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog;h=$hash")}, "shortlog") . - " | log" . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash")}, "commit") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$hash")}, "commitdiff") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;h=$hash;hb=$hash")}, "tree") . "<br/>\n"; + my $refs = git_get_references(); 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 = format_paging_nav('log', $hash, $head, $page, $#revlist); + + git_header_html(); + git_print_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); + my %co = parse_commit($hash); + + git_print_header_div('summary', $project); print "<div class=\"page_body\"> Last change $co{'age_string'}.<br/><br/></div>\n"; } for (my $i = ($page * 100); $i <= $#revlist; $i++) { my $commit = $revlist[$i]; - my $ref = ""; - if (defined $refs->{$commit}) { - $ref = " <span class=\"tag\">" . esc_html($refs->{$commit}) . "</span>"; - } - my %co = git_read_commit($commit); + my $ref = format_ref_marker($refs, $commit); + my %co = parse_commit($commit); next if !%co; - my %ad = date_str($co{'author_epoch'}); - 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"; + my %ad = parse_date($co{'author_epoch'}); + git_print_header_div('commit', + "<span class=\"age\">$co{'age_string'}</span>" . + esc_html($co{'title'}) . $ref, + $commit); print "<div class=\"title_text\">\n" . "<div class=\"log_link\">\n" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$commit")}, "commit") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$commit")}, "commitdiff") . + $cgi->a({-href => href(action=>"commit", hash=>$commit)}, "commit") . + " | " . $cgi->a({-href => href(action=>"commitdiff", hash=>$commit)}, "commitdiff") . "<br/>\n" . "</div>\n" . "<i>" . esc_html($co{'author_name'}) . " [$ad{'rfc2822'}]</i><br/>\n" . @@ -1982,59 +2183,43 @@ sub git_log { } sub git_commit { - my %co = git_read_commit($hash); + my %co = parse_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 %ad = parse_date($co{'author_epoch'}, $co{'author_tz'}); + my %cd = parse_date($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; if ($hash =~ m/^[0-9a-fA-F]{40}$/) { $expires = "+1d"; } - my $refs = read_info_ref(); - my $ref = ""; - if (defined $refs->{$co{'id'}}) { - $ref = " <span class=\"tag\">" . esc_html($refs->{$co{'id'}}) . "</span>"; - } - git_header_html(undef, $expires); - print "<div class=\"page_nav\">\n" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary")}, "summary") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog;h=$hash")}, "shortlog") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log;h=$hash")}, "log") . - " | commit"; - if (defined $co{'parent'}) { - print " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$hash")}, "commitdiff"); - } - print " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;h=$co{'tree'};hb=$hash")}, "tree") . "\n" . - "<br/>\n"; + my $refs = git_get_references(); + my $ref = format_ref_marker($refs, $co{'id'}); + my $formats_nav = ''; if (defined $file_name && defined $co{'parent'}) { my $parent = $co{'parent'}; - print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blame;hb=$parent;f=$file_name")}, "blame") . "\n"; + $formats_nav .= $cgi->a({-href => href(action=>"blame", hash_parent=>$parent, file_name=>$file_name)}, "blame"); } - print "<br/></div>\n"; + git_header_html(undef, $expires); + git_print_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_print_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_print_header_div('tree', esc_html($co{'title'}) . $ref, $co{'tree'}, $hash); } print "<div class=\"title_text\">\n" . "<table cellspacing=\"0\">\n"; @@ -2054,19 +2239,19 @@ sub git_commit { print "<tr>" . "<td>tree</td>" . "<td class=\"sha1\">" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;h=$co{'tree'};hb=$hash"), class => "list"}, $co{'tree'}) . + $cgi->a({-href => href(action=>"tree", hash=>$co{'tree'}, hash_base=>$hash), class => "list"}, $co{'tree'}) . "</td>" . - "<td class=\"link\">" . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;h=$co{'tree'};hb=$hash")}, "tree") . + "<td class=\"link\">" . $cgi->a({-href => href(action=>"tree", hash=>$co{'tree'}, hash_base=>$hash)}, "tree") . "</td>" . "</tr>\n"; my $parents = $co{'parents'}; foreach my $par (@$parents) { print "<tr>" . "<td>parent</td>" . - "<td class=\"sha1\">" . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$par"), class => "list"}, $par) . "</td>" . + "<td class=\"sha1\">" . $cgi->a({-href => href(action=>"commit", hash=>$par), class => "list"}, $par) . "</td>" . "<td class=\"link\">" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$par")}, "commit") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$hash;hp=$par")}, "commitdiff") . + $cgi->a({-href => href(action=>"commit", hash=>$par)}, "commit") . + " | " . $cgi->a({-href => href(action=>"commitdiff", hash=>$hash, hash_parent=>$par)}, "commitdiff") . "</td>" . "</tr>\n"; } @@ -2095,121 +2280,20 @@ sub git_commit { } } print "</div>\n"; - print "<div class=\"list_head\">\n"; - if ($#difftree > 10) { - print(($#difftree + 1) . " files changed:\n"); - } - print "</div>\n"; - print "<table class=\"diff_tree\">\n"; - my $alternate = 0; - foreach my $line (@difftree) { - # ':100644 100644 03b218260e99b78c6df0ed378e59ed9205ccc96d 3b93d5e7cc7f7dd4ebed13a5cc1a4ad976fc94d8 M ls-files.c' - # ':100644 100644 7f9281985086971d3877aca27704f2aaf9c448ce bc190ebc71bbd923f2b728e505408f5e54bd073a M rev-tree.c' - if (!($line =~ m/^:([0-7]{6}) ([0-7]{6}) ([0-9a-fA-F]{40}) ([0-9a-fA-F]{40}) (.)([0-9]{0,3})\t(.*)$/)) { - next; - } - my $from_mode = $1; - my $to_mode = $2; - my $from_id = $3; - my $to_id = $4; - my $status = $5; - my $similarity = $6; - my $file = validate_input(unquote($7)); - if ($alternate) { - print "<tr class=\"dark\">\n"; - } else { - print "<tr class=\"light\">\n"; - } - $alternate ^= 1; - if ($status eq "A") { - my $mode_chng = ""; - if (S_ISREG(oct $to_mode)) { - $mode_chng = sprintf(" with mode: %04o", (oct $to_mode) & 0777); - } - print "<td>" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$to_id;hb=$hash;f=$file"), -class => "list"}, esc_html($file)) . "</td>\n" . - "<td><span class=\"file_status new\">[new " . file_type($to_mode) . "$mode_chng]</span></td>\n" . - "<td class=\"link\">" . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$to_id;hb=$hash;f=$file")}, "blob") . "</td>\n"; - } elsif ($status eq "D") { - print "<td>" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$from_id;hb=$hash;f=$file"), -class => "list"}, esc_html($file)) . "</td>\n" . - "<td><span class=\"file_status deleted\">[deleted " . file_type($from_mode). "]</span></td>\n" . - "<td class=\"link\">" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$from_id;hb=$hash;f=$file")}, "blob") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=history;hb=$hash;f=$file")}, "history") . - "</td>\n" - } elsif ($status eq "M" || $status eq "T") { - my $mode_chnge = ""; - if ($from_mode != $to_mode) { - $mode_chnge = " <span class=\"file_status mode_chnge\">[changed"; - if (((oct $from_mode) & S_IFMT) != ((oct $to_mode) & S_IFMT)) { - $mode_chnge .= " from " . file_type($from_mode) . " to " . file_type($to_mode); - } - if (((oct $from_mode) & 0777) != ((oct $to_mode) & 0777)) { - if (S_ISREG($from_mode) && S_ISREG($to_mode)) { - $mode_chnge .= sprintf(" mode: %04o->%04o", (oct $from_mode) & 0777, (oct $to_mode) & 0777); - } elsif (S_ISREG($to_mode)) { - $mode_chnge .= sprintf(" mode: %04o", (oct $to_mode) & 0777); - } - } - $mode_chnge .= "]</span>\n"; - } - print "<td>"; - if ($to_id ne $from_id) { - print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blobdiff;h=$to_id;hp=$from_id;hb=$hash;f=$file"), -class => "list"}, esc_html($file)); - } else { - print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$to_id;hb=$hash;f=$file"), -class => "list"}, esc_html($file)); - } - print "</td>\n" . - "<td>$mode_chnge</td>\n" . - "<td class=\"link\">"; - print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$to_id;hb=$hash;f=$file")}, "blob"); - if ($to_id ne $from_id) { - print " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blobdiff;h=$to_id;hp=$from_id;hb=$hash;f=$file")}, "diff"); - } - print " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=history;hb=$hash;f=$file")}, "history") . "\n"; - print "</td>\n"; - } elsif ($status eq "R") { - my ($from_file, $to_file) = split "\t", $file; - my $mode_chng = ""; - if ($from_mode != $to_mode) { - $mode_chng = sprintf(", mode: %04o", (oct $to_mode) & 0777); - } - print "<td>" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$to_id;hb=$hash;f=$to_file"), -class => "list"}, esc_html($to_file)) . "</td>\n" . - "<td><span class=\"file_status moved\">[moved from " . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$from_id;hb=$hash;f=$from_file"), -class => "list"}, esc_html($from_file)) . - " with " . (int $similarity) . "% similarity$mode_chng]</span></td>\n" . - "<td class=\"link\">" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$to_id;hb=$hash;f=$to_file")}, "blob"); - if ($to_id ne $from_id) { - print " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blobdiff;h=$to_id;hp=$from_id;hb=$hash;f=$to_file")}, "diff"); - } - print "</td>\n"; - } - print "</tr>\n"; - } - print "</table>\n"; + + git_difftree_body(\@difftree, $parent); + git_footer_html(); } sub git_blobdiff { mkdir($git_temp, 0700); git_header_html(); - if (defined $hash_base && (my %co = git_read_commit($hash_base))) { - print "<div class=\"page_nav\">\n" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary")}, "summary") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog")}, "shortlog") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log")}, "log") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash_base")}, "commit") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$hash_base")}, "commitdiff") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;h=$co{'tree'};hb=$hash_base")}, "tree") . - "<br/>\n"; - print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blobdiff_plain;h=$hash;hp=$hash_parent")}, "plain") . - "</div>\n"; - print "<div>\n" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash_base"), -class => "title"}, esc_html($co{'title'})) . "\n" . - "</div>\n"; + if (defined $hash_base && (my %co = parse_commit($hash_base))) { + my $formats_nav = + $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blobdiff_plain;h=$hash;hp=$hash_parent")}, "plain"); + git_print_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav); + git_print_header_div('commit', esc_html($co{'title'}), $hash_base); } else { print "<div class=\"page_nav\">\n" . "<br/><br/></div>\n" . @@ -2235,40 +2319,30 @@ sub git_blobdiff_plain { sub git_commitdiff { mkdir($git_temp, 0700); - my %co = git_read_commit($hash); + my %co = parse_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; if ($hash =~ m/^[0-9a-fA-F]{40}$/) { $expires = "+1d"; } - my $refs = read_info_ref(); - my $ref = ""; - if (defined $refs->{$co{'id'}}) { - $ref = " <span class=\"tag\">" . esc_html($refs->{$co{'id'}}) . "</span>"; - } + my $refs = git_get_references(); + my $ref = format_ref_marker($refs, $co{'id'}); + my $formats_nav = + $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff_plain;h=$hash;hp=$hash_parent")}, "plain"); git_header_html(undef, $expires); - 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_print_page_nav('commitdiff','', $hash,$co{'tree'},$hash, $formats_nav); + git_print_header_div('commit', esc_html($co{'title'}) . $ref, $hash); print "<div class=\"page_body\">\n"; my $comment = $co{'comment'}; my $empty = 0; @@ -2297,7 +2371,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; @@ -2305,21 +2381,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"); } @@ -2332,15 +2410,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 = parse_commit($hash); + if (!%co) { + die_error(undef, "Unknown commit object"); + } + if (!defined $hash_parent) { + $hash_parent = $co{'parent'} || '--root'; + } + open my $fd, "-|", $GIT, "diff-tree", '-r', $hash_parent, $hash + or die_error(undef, "Open git-diff-tree failed"); + my @difftree = map { chomp; $_ } <$fd>; + close $fd or die_error(undef, "Reading 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>); + my $refs = git_get_references("tags"); + open $fd, "-|", $GIT, "rev-list", "HEAD"; + my @commits = map { chomp; $_ } <$fd>; close $fd; foreach my $commit (@commits) { if (defined $refs->{$commit}) { @@ -2352,14 +2438,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 %ad = parse_date($co{'author_epoch'}, $co{'author_tz'}); my $comment = $co{'comment'}; print "From: $co{'author'}\n" . "Date: $ad{'rfc2822'} ($ad{'tz_local'})\n". "Subject: $co{'title'}\n"; if (defined $tagname) { - print "X-Git-Tag: $tagname\n"; + print "X-Git-Tag: $tagname\n"; } print "X-Git-Url: $my_url?p=$project;a=commitdiff;h=$hash\n" . "\n"; @@ -2370,7 +2455,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; @@ -2387,27 +2474,17 @@ sub git_commitdiff_plain { sub git_history { if (!defined $hash_base) { - $hash_base = git_read_head($project); + $hash_base = git_get_head_hash($project); } my $ftype; - my %co = git_read_commit($hash_base); + my %co = parse_commit($hash_base); if (!%co) { - die_error(undef, "Unknown commit object."); + die_error(undef, "Unknown commit object"); } - my $refs = read_info_ref(); + my $refs = git_get_references(); git_header_html(); - print "<div class=\"page_nav\">\n" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary")}, "summary") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog")}, "shortlog") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log")}, "log") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash_base")}, "commit") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$hash_base")}, "commitdiff") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;h=$co{'tree'};hb=$hash_base")}, "tree") . - "<br/><br/>\n" . - "</div>\n"; - print "<div>\n" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash_base"), -class => "title"}, esc_html($co{'title'})) . "\n" . - "</div>\n"; + git_print_page_nav('','', $hash_base,$co{'tree'},$hash_base); + git_print_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); } @@ -2417,60 +2494,23 @@ sub git_history { git_print_page_path($file_name, $ftype); open my $fd, "-|", - "$GIT rev-list --full-history $hash_base -- \'$file_name\'"; - print "<table cellspacing=\"0\">\n"; - my $alternate = 0; - while (my $line = <$fd>) { - if ($line =~ m/^([0-9a-fA-F]{40})/){ - my $commit = $1; - my %co = git_read_commit($commit); - if (!%co) { - next; - } - my $ref = ""; - if (defined $refs->{$commit}) { - $ref = " <span class=\"tag\">" . esc_html($refs->{$commit}) . "</span>"; - } - if ($alternate) { - print "<tr class=\"dark\">\n"; - } else { - print "<tr class=\"light\">\n"; - } - $alternate ^= 1; - print "<td title=\"$co{'age_string_age'}\"><i>$co{'age_string_date'}</i></td>\n" . - "<td><i>" . esc_html(chop_str($co{'author_name'}, 15, 3)) . "</i></td>\n" . - "<td>" . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$commit"), -class => "list"}, "<b>" . - esc_html(chop_str($co{'title'}, 50)) . "$ref</b>") . "</td>\n" . - "<td class=\"link\">" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$commit")}, "commit") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$commit")}, "commitdiff") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;hb=$commit;f=$file_name")}, "blob"); - my $blob = git_get_hash_by_path($hash_base, $file_name); - my $blob_parent = git_get_hash_by_path($commit, $file_name); - if (defined $blob && defined $blob_parent && $blob ne $blob_parent) { - print " | " . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blobdiff;h=$blob;hp=$blob_parent;hb=$commit;f=$file_name")}, - "diff to current"); - } - print "</td>\n" . - "</tr>\n"; - } - } - print "</table>\n"; + $GIT, "rev-list", "--full-history", $hash_base, "--", $file_name; + git_history_body($fd, $refs, $hash_base, $ftype); + close $fd; git_footer_html(); } 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); + $hash = git_get_head_hash($project); } - my %co = git_read_commit($hash); + my %co = parse_commit($hash); if (!%co) { - die_error(undef, "Unknown commit object."); + 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 :) @@ -2487,24 +2527,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_print_page_nav('','', $hash,$co{'tree'},$hash); + git_print_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; @@ -2516,7 +2546,7 @@ sub git_search { next; } my @commit_lines = split "\n", $commit_text; - my %co = git_read_commit(undef, \@commit_lines); + my %co = parse_commit(undef, \@commit_lines); if (!%co) { next; } @@ -2597,7 +2627,7 @@ sub git_search { print "</td>\n" . "</tr>\n"; } - %co = git_read_commit($1); + %co = parse_commit($1); } } close $fd; @@ -2607,90 +2637,130 @@ sub git_search { } sub git_shortlog { - my $head = git_read_head($project); + my $head = git_get_head_hash($project); if (!defined $hash) { $hash = $head; } if (!defined $page) { $page = 0; } - my $refs = read_info_ref(); - 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 $refs = git_get_references(); 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 = format_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_print_page_nav('shortlog','', $hash,$hash,$hash, $paging_nav); + git_print_header_div('summary', $project); + + git_shortlog_body(\@revlist, ($page * 100), $#revlist, $refs, $next_link); + + git_footer_html(); +} + +## ...................................................................... +## feeds (RSS, OPML) + +sub git_rss { + # http://www.notestips.com/80256B3A007F2692/1/NAMO5P9UPQ + open my $fd, "-|", $GIT, "rev-list", "--max-count=150", git_get_head_hash($project) + or die_error(undef, "Open git-rev-list failed"); + my @revlist = map { chomp; $_ } <$fd>; + close $fd or die_error(undef, "Reading git-rev-list failed"); + print $cgi->header(-type => 'text/xml', -charset => 'utf-8'); + print "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n". + "<rss version=\"2.0\" xmlns:content=\"http://purl.org/rss/1.0/modules/content/\">\n"; + print "<channel>\n"; + print "<title>$project</title>\n". + "<link>" . esc_html("$my_url?p=$project;a=summary") . "</link>\n". + "<description>$project log</description>\n". + "<language>en</language>\n"; + + 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 = parse_commit($commit); + # we read 150, we always show 30 and the ones more recent than 48 hours + if (($i >= 20) && ((time - $co{'committer_epoch'}) > 48*60*60)) { + last; } - my %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"; + my %cd = parse_date($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"; } - $alternate ^= 1; - print "<td title=\"$co{'age_string_age'}\"><i>$co{'age_string_date'}</i></td>\n" . - "<td><i>" . esc_html(chop_str($co{'author_name'}, 10)) . "</i></td>\n" . - "<td>"; - if (length($co{'title_short'}) < length($co{'title'})) { - print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$commit"), -class => "list", -title => "$co{'title'}"}, - "<b>" . esc_html($co{'title_short'}) . "$ref</b>"); - } else { - print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$commit"), -class => "list"}, - "<b>" . esc_html($co{'title_short'}) . "$ref</b>"); + print "<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 "</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 "]]>\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_get_projects_list(); + + print $cgi->header(-type => 'text/xml', -charset => 'utf-8'); + print "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n". + "<opml version=\"1.0\">\n". + "<head>". + " <title>$site_name Git OPML Export</title>\n". + "</head>\n". + "<body>\n". + "<outline text=\"git RSS feeds\">\n"; + + foreach my $pr (@list) { + my %proj = %$pr; + my $head = git_get_head_hash($proj{'path'}); + if (!defined $head) { + next; + } + $ENV{'GIT_DIR'} = "$projectroot/$proj{'path'}"; + my %co = parse_commit($head); + if (!%co) { + next; + } + + my $path = esc_html(chop_str($proj{'path'}, 25, 5)); + my $rss = "$my_url?p=$proj{'path'};a=rss"; + my $html = "$my_url?p=$proj{'path'};a=summary"; + print "<outline type=\"rss\" text=\"$path\" title=\"$path\" xmlUrl=\"$rss\" htmlUrl=\"$html\"/>\n"; } - print "</table\n>"; - git_footer_html(); + print "</outline>\n". + "</body>\n". + "</opml>\n"; } diff --git a/builtin-help.c b/help.c index 6484cb9df..9ecdefdb0 100644 --- a/builtin-help.c +++ b/help.c @@ -14,7 +14,7 @@ static int term_columns(void) { char *col_string = getenv("COLUMNS"); - int n_cols = 0; + int n_cols; if (col_string && (n_cols = atoi(col_string)) > 0) return n_cols; diff --git a/http-fetch.c b/http-fetch.c index de5fc44e6..7619b338f 100644 --- a/http-fetch.c +++ b/http-fetch.c @@ -36,10 +36,10 @@ enum XML_Status { #define PREV_BUF_SIZE 4096 #define RANGE_HEADER_SIZE 30 -static int commits_on_stdin = 0; +static int commits_on_stdin; static int got_alternates = -1; -static int corrupt_object_found = 0; +static int corrupt_object_found; static struct curl_slist *no_pragma_header; @@ -52,7 +52,7 @@ struct alt_base struct alt_base *next; }; -static struct alt_base *alt = NULL; +static struct alt_base *alt; enum object_request_state { WAITING, @@ -114,7 +114,7 @@ struct remote_ls_ctx }; #endif -static struct object_request *object_queue_head = NULL; +static struct object_request *object_queue_head; static size_t fwrite_sha1_file(void *ptr, size_t eltsize, size_t nmemb, void *data) @@ -301,7 +301,7 @@ static void finish_object_request(struct object_request *obj_req) unlink(obj_req->tmpfile); return; } - if (memcmp(obj_req->sha1, obj_req->real_sha1, 20)) { + if (hashcmp(obj_req->sha1, obj_req->real_sha1)) { unlink(obj_req->tmpfile); return; } @@ -393,7 +393,7 @@ void prefetch(unsigned char *sha1) char *filename = sha1_file_name(sha1); newreq = xmalloc(sizeof(*newreq)); - memcpy(newreq->sha1, sha1, 20); + hashcpy(newreq->sha1, sha1); newreq->repo = alt; newreq->url = NULL; newreq->local = -1; @@ -1070,7 +1070,7 @@ static int fetch_object(struct alt_base *repo, unsigned char *sha1) int ret = 0; struct object_request *obj_req = object_queue_head; - while (obj_req != NULL && memcmp(obj_req->sha1, sha1, 20)) + while (obj_req != NULL && hashcmp(obj_req->sha1, sha1)) obj_req = obj_req->next; if (obj_req == NULL) return error("Couldn't find request for %s in the queue", hex); @@ -1109,7 +1109,7 @@ static int fetch_object(struct alt_base *repo, unsigned char *sha1) } else if (obj_req->zret != Z_STREAM_END) { corrupt_object_found++; ret = error("File %s (%s) corrupt", hex, obj_req->url); - } else if (memcmp(obj_req->sha1, obj_req->real_sha1, 20)) { + } else if (hashcmp(obj_req->sha1, obj_req->real_sha1)) { ret = error("File %s has bad hash", hex); } else if (obj_req->rename < 0) { ret = error("unable to write sha1 filename %s", diff --git a/http-push.c b/http-push.c index d45733ef6..04cb238e9 100644 --- a/http-push.c +++ b/http-push.c @@ -70,18 +70,18 @@ enum XML_Status { /* We allow "recursive" symbolic refs. Only within reason, though */ #define MAXDEPTH 5 -static int pushing = 0; -static int aborted = 0; +static int pushing; +static int aborted; static signed char remote_dir_exists[256]; static struct curl_slist *no_pragma_header; static struct curl_slist *default_headers; -static int push_verbosely = 0; -static int push_all = 0; -static int force_all = 0; +static int push_verbosely; +static int push_all; +static int force_all; -static struct object_list *objects = NULL; +static struct object_list *objects; struct repo { @@ -94,7 +94,7 @@ struct repo struct remote_lock *locks; }; -static struct repo *remote = NULL; +static struct repo *remote; enum transfer_state { NEED_FETCH, @@ -134,7 +134,7 @@ struct transfer_request struct transfer_request *next; }; -static struct transfer_request *request_queue_head = NULL; +static struct transfer_request *request_queue_head; struct xml_ctx { @@ -745,7 +745,7 @@ static void finish_request(struct transfer_request *request) SHA1_Final(request->real_sha1, &request->c); if (request->zret != Z_STREAM_END) { unlink(request->tmpfile); - } else if (memcmp(request->obj->sha1, request->real_sha1, 20)) { + } else if (hashcmp(request->obj->sha1, request->real_sha1)) { unlink(request->tmpfile); } else { request->rename = @@ -1700,7 +1700,7 @@ static int locking_available(void) return lock_flags; } -struct object_list **add_one_object(struct object *obj, struct object_list **p) +static struct object_list **add_one_object(struct object *obj, struct object_list **p) { struct object_list *entry = xmalloc(sizeof(struct object_list)); entry->item = obj; @@ -1874,7 +1874,7 @@ static int one_local_ref(const char *refname, const unsigned char *sha1) struct ref *ref; int len = strlen(refname) + 1; ref = xcalloc(1, sizeof(*ref) + len); - memcpy(ref->new_sha1, sha1, 20); + hashcpy(ref->new_sha1, sha1); memcpy(ref->name, refname, len); *local_tail = ref; local_tail = &ref->next; @@ -1909,7 +1909,7 @@ static void one_remote_ref(char *refname) } ref = xcalloc(1, sizeof(*ref) + len); - memcpy(ref->old_sha1, remote_sha1, 20); + hashcpy(ref->old_sha1, remote_sha1); memcpy(ref->name, refname, len); *remote_tail = ref; remote_tail = &ref->next; @@ -2164,7 +2164,7 @@ static void fetch_symref(const char *path, char **symref, unsigned char *sha1) if (*symref != NULL) free(*symref); *symref = NULL; - memset(sha1, 0, 20); + hashclr(sha1); if (buffer.posn == 0) return; @@ -2182,49 +2182,11 @@ static void fetch_symref(const char *path, char **symref, unsigned char *sha1) static int verify_merge_base(unsigned char *head_sha1, unsigned char *branch_sha1) { - int pipe_fd[2]; - pid_t merge_base_pid; - char line[PATH_MAX + 20]; - unsigned char merge_sha1[20]; - int verified = 0; - - if (pipe(pipe_fd) < 0) - die("Verify merge base: pipe failed"); - - merge_base_pid = fork(); - if (!merge_base_pid) { - static const char *args[] = { - "merge-base", - "-a", - NULL, - NULL, - NULL - }; - args[2] = strdup(sha1_to_hex(head_sha1)); - args[3] = sha1_to_hex(branch_sha1); - - dup2(pipe_fd[1], 1); - close(pipe_fd[0]); - close(pipe_fd[1]); - execv_git_cmd(args); - die("merge-base setup failed"); - } - if (merge_base_pid < 0) - die("merge-base fork failed"); - - dup2(pipe_fd[0], 0); - close(pipe_fd[0]); - close(pipe_fd[1]); - while (fgets(line, sizeof(line), stdin) != NULL) { - if (get_sha1_hex(line, merge_sha1)) - die("expected sha1, got garbage:\n %s", line); - if (!memcmp(branch_sha1, merge_sha1, 20)) { - verified = 1; - break; - } - } + struct commit *head = lookup_commit(head_sha1); + struct commit *branch = lookup_commit(branch_sha1); + struct commit_list *merge_bases = get_merge_bases(head, branch, 1); - return verified; + return (merge_bases && !merge_bases->next && merge_bases->item == branch); } static int delete_remote_branch(char *pattern, int force) @@ -2454,7 +2416,7 @@ int main(int argc, char **argv) if (!ref->peer_ref) continue; - if (!memcmp(ref->old_sha1, ref->peer_ref->new_sha1, 20)) { + if (!hashcmp(ref->old_sha1, ref->peer_ref->new_sha1)) { if (push_verbosely || 1) fprintf(stderr, "'%s': up-to-date\n", ref->name); continue; @@ -2483,7 +2445,7 @@ int main(int argc, char **argv) continue; } } - memcpy(ref->new_sha1, ref->peer_ref->new_sha1, 20); + hashcpy(ref->new_sha1, ref->peer_ref->new_sha1); if (is_zero_sha1(ref->new_sha1)) { error("cannot happen anymore"); rc = -3; diff --git a/index-pack.c b/index-pack.c index b20659c25..80bc6cb45 100644 --- a/index-pack.c +++ b/index-pack.c @@ -82,7 +82,7 @@ static void parse_pack_header(void) SHA1_Init(&ctx); SHA1_Update(&ctx, pack_base, pack_size - 20); SHA1_Final(sha1, &ctx); - if (memcmp(sha1, pack_base + pack_size - 20, 20)) + if (hashcmp(sha1, pack_base + pack_size - 20)) die("packfile '%s' SHA1 mismatch", pack_name); } @@ -161,7 +161,7 @@ static void *unpack_raw_entry(unsigned long offset, case OBJ_DELTA: if (pos + 20 >= pack_limit) bad_object(offset, "object extends past end of pack"); - memcpy(delta_base, pack_base + pos, 20); + hashcpy(delta_base, pack_base + pos); pos += 20; /* fallthru */ case OBJ_COMMIT: @@ -189,7 +189,7 @@ static int find_delta(const unsigned char *base_sha1) struct delta_entry *delta = &deltas[next]; int cmp; - cmp = memcmp(base_sha1, delta->base_sha1, 20); + cmp = hashcmp(base_sha1, delta->base_sha1); if (!cmp) return next; if (cmp < 0) { @@ -210,9 +210,9 @@ static int find_deltas_based_on_sha1(const unsigned char *base_sha1, if (first < 0) return -1; - while (first > 0 && !memcmp(deltas[first-1].base_sha1, base_sha1, 20)) + while (first > 0 && !hashcmp(deltas[first - 1].base_sha1, base_sha1)) --first; - while (last < end && !memcmp(deltas[last+1].base_sha1, base_sha1, 20)) + while (last < end && !hashcmp(deltas[last + 1].base_sha1, base_sha1)) ++last; *first_index = first; *last_index = last; @@ -278,7 +278,7 @@ static int compare_delta_entry(const void *a, const void *b) { const struct delta_entry *delta_a = a; const struct delta_entry *delta_b = b; - return memcmp(delta_a->base_sha1, delta_b->base_sha1, 20); + return hashcmp(delta_a->base_sha1, delta_b->base_sha1); } static void parse_pack_objects(void) @@ -304,7 +304,7 @@ static void parse_pack_objects(void) if (obj->type == OBJ_DELTA) { struct delta_entry *delta = &deltas[nr_deltas++]; delta->obj = obj; - memcpy(delta->base_sha1, base_sha1, 20); + hashcpy(delta->base_sha1, base_sha1); } else sha1_object(data, data_size, obj->type, obj->sha1); free(data); @@ -350,7 +350,7 @@ static int sha1_compare(const void *_a, const void *_b) { struct object_entry *a = *(struct object_entry **)_a; struct object_entry *b = *(struct object_entry **)_b; - return memcmp(a->sha1, b->sha1, 20); + return hashcmp(a->sha1, b->sha1); } static void write_index_file(const char *index_name, unsigned char *sha1) diff --git a/local-fetch.c b/local-fetch.c index 7d01845d3..7b6875cce 100644 --- a/local-fetch.c +++ b/local-fetch.c @@ -5,10 +5,10 @@ #include "commit.h" #include "fetch.h" -static int use_link = 0; -static int use_symlink = 0; +static int use_link; +static int use_symlink; static int use_filecopy = 1; -static int commits_on_stdin = 0; +static int commits_on_stdin; static const char *path; /* "Remote" git repository */ @@ -16,7 +16,7 @@ void prefetch(unsigned char *sha1) { } -static struct packed_git *packs = NULL; +static struct packed_git *packs; static void setup_index(unsigned char *sha1) { diff --git a/log-tree.c b/log-tree.c index 05ede0c17..031af8893 100644 --- a/log-tree.c +++ b/log-tree.c @@ -19,7 +19,7 @@ static int append_signoff(char *buf, int buf_sz, int at, const char *signoff) char *cp = buf; /* Do we have enough space to add it? */ - if (buf_sz - at <= strlen(signed_off_by) + signoff_len + 2) + if (buf_sz - at <= strlen(signed_off_by) + signoff_len + 3) return at; /* First see if we already have the sign-off by the signer */ @@ -34,6 +34,48 @@ static int append_signoff(char *buf, int buf_sz, int at, const char *signoff) return at; /* we already have him */ } + /* Does the last line already end with "^[-A-Za-z]+: [^@]+@"? + * If not, add a blank line to separate the message from + * the run of Signed-off-by: and Acked-by: lines. + */ + { + char ch; + int seen_colon, seen_at, seen_name, seen_head, not_signoff; + seen_colon = 0; + seen_at = 0; + seen_name = 0; + seen_head = 0; + not_signoff = 0; + cp = buf + at; + while (buf <= --cp && (ch = *cp) == '\n') + ; + while (!not_signoff && buf <= cp && (ch = *cp--) != '\n') { + if (!seen_at) { + if (ch == '@') + seen_at = 1; + continue; + } + if (!seen_colon) { + if (ch == '@') + not_signoff = 1; + else if (ch == ':') + seen_colon = 1; + else + seen_name = 1; + continue; + } + if (('A' <= ch && ch <= 'Z') || + ('a' <= ch && ch <= 'z') || + ch == '-') { + seen_head = 1; + continue; + } + not_signoff = 1; + } + if (not_signoff || !seen_head || !seen_name) + buf[at++] = '\n'; + } + strcpy(buf + at, signed_off_by); at += strlen(signed_off_by); strcpy(buf + at, signoff); diff --git a/merge-base.c b/merge-base.c index 59f723f40..009caf804 100644 --- a/merge-base.c +++ b/merge-base.c @@ -2,7 +2,7 @@ #include "cache.h" #include "commit.h" -static int show_all = 0; +static int show_all; static int merge_base(struct commit *rev1, struct commit *rev2) { diff --git a/merge-index.c b/merge-index.c index 0498a6f45..646d090c5 100644 --- a/merge-index.c +++ b/merge-index.c @@ -4,14 +4,15 @@ #include "cache.h" -static const char *pgm = NULL; +static const char *pgm; static const char *arguments[8]; static int one_shot, quiet; static int err; static void run_program(void) { - int pid = fork(), status; + pid_t pid = fork(); + int status; if (pid < 0) die("unable to fork"); diff --git a/merge-tree.c b/merge-tree.c index 7cf00be6d..c2e9a867e 100644 --- a/merge-tree.c +++ b/merge-tree.c @@ -152,7 +152,7 @@ static int same_entry(struct name_entry *a, struct name_entry *b) { return a->sha1 && b->sha1 && - !memcmp(a->sha1, b->sha1, 20) && + !hashcmp(a->sha1, b->sha1) && a->mode == b->mode; } @@ -30,7 +30,7 @@ static void append_to_tree(unsigned mode, unsigned char *sha1, char *path) ent = entries[used++] = xmalloc(sizeof(**entries) + len + 1); ent->mode = mode; ent->len = len; - memcpy(ent->sha1, sha1, 20); + hashcpy(ent->sha1, sha1); memcpy(ent->name, path, len+1); } @@ -49,7 +49,6 @@ static void write_tree(unsigned char *sha1) int i; qsort(entries, used, sizeof(*entries), ent_compare); - size = 100; for (size = i = 0; i < used; i++) size += 32 + entries[i]->len; buffer = xmalloc(size); @@ -65,7 +64,7 @@ static void write_tree(unsigned char *sha1) offset += sprintf(buffer + offset, "%o ", ent->mode); offset += sprintf(buffer + offset, "%s", ent->name); buffer[offset++] = 0; - memcpy(buffer + offset, ent->sha1, 20); + hashcpy((unsigned char*)buffer + offset, ent->sha1); offset += 20; } write_sha1_file(buffer, offset, tree_type, sha1); @@ -58,7 +58,7 @@ struct object *lookup_object(const unsigned char *sha1) i = hashtable_index(sha1); while ((obj = obj_hash[i]) != NULL) { - if (!memcmp(sha1, obj->sha1, 20)) + if (!hashcmp(sha1, obj->sha1)) break; i++; if (i == obj_hash_size) @@ -91,7 +91,7 @@ void created_object(const unsigned char *sha1, struct object *obj) obj->used = 0; obj->type = OBJ_NONE; obj->flags = 0; - memcpy(obj->sha1, sha1, 20); + hashcpy(obj->sha1, sha1); if (obj_hash_size - 1 <= nr_objs * 2) grow_object_hash(); diff --git a/pack-check.c b/pack-check.c index 3a62e1b7e..04c6c0082 100644 --- a/pack-check.c +++ b/pack-check.c @@ -29,10 +29,10 @@ static int verify_packfile(struct packed_git *p) pack_base = p->pack_base; SHA1_Update(&ctx, pack_base, pack_size - 20); SHA1_Final(sha1, &ctx); - if (memcmp(sha1, (char *) pack_base + pack_size - 20, 20)) + if (hashcmp(sha1, (unsigned char *)pack_base + pack_size - 20)) return error("Packfile %s SHA1 mismatch with itself", p->pack_name); - if (memcmp(sha1, (char *) index_base + index_size - 40, 20)) + if (hashcmp(sha1, (unsigned char *)index_base + index_size - 40)) return error("Packfile %s SHA1 mismatch with idx", p->pack_name); @@ -135,7 +135,7 @@ int verify_pack(struct packed_git *p, int verbose) SHA1_Init(&ctx); SHA1_Update(&ctx, index_base, index_size - 20); SHA1_Final(sha1, &ctx); - if (memcmp(sha1, (char *) index_base + index_size - 20, 20)) + if (hashcmp(sha1, (unsigned char *)index_base + index_size - 20)) ret = error("Packfile index for %s SHA1 mismatch", p->pack_name); diff --git a/pack-redundant.c b/pack-redundant.c index 41fb96056..edb5524fc 100644 --- a/pack-redundant.c +++ b/pack-redundant.c @@ -13,7 +13,7 @@ static const char pack_redundant_usage[] = "git-pack-redundant [ --verbose ] [ --alt-odb ] < --all | <.pack filename> ...>"; -static int load_all_packs = 0, verbose = 0, alt_odb = 0; +static int load_all_packs, verbose, alt_odb; struct llist_item { struct llist_item *next; @@ -37,7 +37,7 @@ struct pll { struct pack_list *pl; }; -static struct llist_item *free_nodes = NULL; +static struct llist_item *free_nodes; static inline void llist_item_put(struct llist_item *item) { @@ -139,7 +139,7 @@ static inline struct llist_item *llist_insert_sorted_unique(struct llist *list, l = (hint == NULL) ? list->front : hint; while (l) { - int cmp = memcmp(l->sha1, sha1, 20); + int cmp = hashcmp(l->sha1, sha1); if (cmp > 0) { /* we insert before this entry */ return llist_insert(list, prev, sha1); } @@ -162,7 +162,7 @@ redo_from_start: l = (hint == NULL) ? list->front : hint; prev = NULL; while (l) { - int cmp = memcmp(l->sha1, sha1, 20); + int cmp = hashcmp(l->sha1, sha1); if (cmp > 0) /* not in list, since sorted */ return prev; if(!cmp) { /* found */ @@ -256,7 +256,7 @@ static void cmp_two_packs(struct pack_list *p1, struct pack_list *p2) while (p1_off <= p1->pack->index_size - 3 * 20 && p2_off <= p2->pack->index_size - 3 * 20) { - int cmp = memcmp(p1_base + p1_off, p2_base + p2_off, 20); + int cmp = hashcmp(p1_base + p1_off, p2_base + p2_off); /* cmp ~ p1 - p2 */ if (cmp == 0) { p1_hint = llist_sorted_remove(p1->unique_objects, @@ -351,16 +351,16 @@ static size_t sizeof_union(struct packed_git *p1, struct packed_git *p2) { size_t ret = 0; int p1_off, p2_off; - char *p1_base, *p2_base; + unsigned char *p1_base, *p2_base; p1_off = p2_off = 256 * 4 + 4; - p1_base = (char *)p1->index_base; - p2_base = (char *)p2->index_base; + p1_base = (unsigned char *)p1->index_base; + p2_base = (unsigned char *)p2->index_base; while (p1_off <= p1->index_size - 3 * 20 && p2_off <= p2->index_size - 3 * 20) { - int cmp = memcmp(p1_base + p1_off, p2_base + p2_off, 20); + int cmp = hashcmp(p1_base + p1_off, p2_base + p2_off); /* cmp ~ p1 - p2 */ if (cmp == 0) { ret++; @@ -15,11 +15,13 @@ void setup_pager(void) { pid_t pid; int fd[2]; - const char *pager = getenv("PAGER"); + const char *pager = getenv("GIT_PAGER"); if (!isatty(1)) return; if (!pager) + pager = getenv("PAGER"); + if (!pager) pager = "less"; else if (!*pager || !strcmp(pager, "cat")) return; diff --git a/patch-id.c b/patch-id.c index 3b4c80f76..086d2d9c6 100644 --- a/patch-id.c +++ b/patch-id.c @@ -47,7 +47,7 @@ static void generate_id_list(void) if (!get_sha1_hex(p, n)) { flush_current_id(patchlen, sha1, &ctx); - memcpy(sha1, n, 20); + hashcpy(sha1, n); patchlen = 0; continue; } diff --git a/read-cache.c b/read-cache.c index c575edac4..20c9d494a 100644 --- a/read-cache.c +++ b/read-cache.c @@ -18,16 +18,16 @@ #define CACHE_EXT(s) ( (s[0]<<24)|(s[1]<<16)|(s[2]<<8)|(s[3]) ) #define CACHE_EXT_TREE 0x54524545 /* "TREE" */ -struct cache_entry **active_cache = NULL; +struct cache_entry **active_cache; static time_t index_file_timestamp; -unsigned int active_nr = 0, active_alloc = 0, active_cache_changed = 0; +unsigned int active_nr, active_alloc, active_cache_changed; -struct cache_tree *active_cache_tree = NULL; +struct cache_tree *active_cache_tree; -int cache_errno = 0; +int cache_errno; -static void *cache_mmap = NULL; -static size_t cache_mmap_size = 0; +static void *cache_mmap; +static size_t cache_mmap_size; /* * This only updates the "non-critical" parts of the directory @@ -60,7 +60,7 @@ static int ce_compare_data(struct cache_entry *ce, struct stat *st) if (fd >= 0) { unsigned char sha1[20]; if (!index_fd(sha1, fd, st, 0, NULL)) - match = memcmp(sha1, ce->sha1, 20); + match = hashcmp(sha1, ce->sha1); /* index_fd() closed the file descriptor already */ } return match; @@ -169,9 +169,11 @@ static int ce_match_stat_basic(struct cache_entry *ce, struct stat *st) return changed; } -int ce_match_stat(struct cache_entry *ce, struct stat *st, int ignore_valid) +int ce_match_stat(struct cache_entry *ce, struct stat *st, int options) { unsigned int changed; + int ignore_valid = options & 01; + int assume_racy_is_modified = options & 02; /* * If it's marked as always valid in the index, it's @@ -200,8 +202,12 @@ int ce_match_stat(struct cache_entry *ce, struct stat *st, int ignore_valid) */ if (!changed && index_file_timestamp && - index_file_timestamp <= ntohl(ce->ce_mtime.sec)) - changed |= ce_modified_check_fs(ce, st); + index_file_timestamp <= ntohl(ce->ce_mtime.sec)) { + if (assume_racy_is_modified) + changed |= DATA_CHANGED; + else + changed |= ce_modified_check_fs(ce, st); + } return changed; } @@ -738,7 +744,7 @@ static int verify_hdr(struct cache_header *hdr, unsigned long size) SHA1_Init(&c); SHA1_Update(&c, hdr, size - 20); SHA1_Final(sha1, &c); - if (memcmp(sha1, (char *) hdr + size - 20, 20)) + if (hashcmp(sha1, (unsigned char *)hdr + size - 20)) return error("bad index file sha1 signature"); return 0; } @@ -857,6 +863,18 @@ int discard_cache() static unsigned char write_buffer[WRITE_BUFFER_SIZE]; static unsigned long write_buffer_len; +static int ce_write_flush(SHA_CTX *context, int fd) +{ + unsigned int buffered = write_buffer_len; + if (buffered) { + SHA1_Update(context, write_buffer, buffered); + if (write(fd, write_buffer, buffered) != buffered) + return -1; + write_buffer_len = 0; + } + return 0; +} + static int ce_write(SHA_CTX *context, int fd, void *data, unsigned int len) { while (len) { @@ -867,8 +885,8 @@ static int ce_write(SHA_CTX *context, int fd, void *data, unsigned int len) memcpy(write_buffer + buffered, data, partial); buffered += partial; if (buffered == WRITE_BUFFER_SIZE) { - SHA1_Update(context, write_buffer, WRITE_BUFFER_SIZE); - if (write(fd, write_buffer, WRITE_BUFFER_SIZE) != WRITE_BUFFER_SIZE) + write_buffer_len = buffered; + if (ce_write_flush(context, fd)) return -1; buffered = 0; } @@ -884,10 +902,8 @@ static int write_index_ext_header(SHA_CTX *context, int fd, { ext = htonl(ext); sz = htonl(sz); - if ((ce_write(context, fd, &ext, 4) < 0) || - (ce_write(context, fd, &sz, 4) < 0)) - return -1; - return 0; + return ((ce_write(context, fd, &ext, 4) < 0) || + (ce_write(context, fd, &sz, 4) < 0)) ? -1 : 0; } static int ce_flush(SHA_CTX *context, int fd) @@ -909,9 +925,7 @@ static int ce_flush(SHA_CTX *context, int fd) /* Append the SHA1 signature at the end */ SHA1_Final(write_buffer + left, context); left += 20; - if (write(fd, write_buffer, left) != left) - return -1; - return 0; + return (write(fd, write_buffer, left) != left) ? -1 : 0; } static void ce_smudge_racily_clean_entry(struct cache_entry *ce) @@ -940,7 +954,7 @@ static void ce_smudge_racily_clean_entry(struct cache_entry *ce) * $ echo filfre >nitfol * $ git-update-index --add nitfol * - * but it does not. Whe the second update-index runs, + * but it does not. When the second update-index runs, * it notices that the entry "frotz" has the same timestamp * as index, and if we were to smudge it by resetting its * size to zero here, then the object name recorded diff --git a/receive-pack.c b/receive-pack.c index 93929b537..201531626 100644 --- a/receive-pack.c +++ b/receive-pack.c @@ -8,10 +8,10 @@ static const char receive_pack_usage[] = "git-receive-pack <git-dir>"; static const char *unpacker[] = { "unpack-objects", NULL }; -static int report_status = 0; +static int report_status; static char capabilities[] = "report-status"; -static int capabilities_sent = 0; +static int capabilities_sent; static int show_ref(const char *path, const unsigned char *sha1) { @@ -40,7 +40,7 @@ struct command { char ref_name[FLEX_ARRAY]; /* more */ }; -static struct command *commands = NULL; +static struct command *commands; static int is_all_zeroes(const char *hex) { @@ -247,8 +247,8 @@ static void read_head_info(void) report_status = 1; } cmd = xmalloc(sizeof(struct command) + len - 80); - memcpy(cmd->old_sha1, old_sha1, 20); - memcpy(cmd->new_sha1, new_sha1, 20); + hashcpy(cmd->old_sha1, old_sha1); + hashcpy(cmd->new_sha1, new_sha1); memcpy(cmd->ref_name, line + 82, len - 81); cmd->error_string = "n/a (unpacker error)"; cmd->next = NULL; @@ -29,7 +29,7 @@ const char *resolve_ref(const char *path, unsigned char *sha1, int reading) if (lstat(path, &st) < 0) { if (reading || errno != ENOENT) return NULL; - memset(sha1, 0, 20); + hashclr(sha1); return path; } @@ -281,7 +281,7 @@ static struct ref_lock *verify_lock(struct ref_lock *lock, unlock_ref(lock); return NULL; } - if (memcmp(lock->old_sha1, old_sha1, 20)) { + if (hashcmp(lock->old_sha1, old_sha1)) { error("Ref %s is at %s but expected %s", lock->ref_file, sha1_to_hex(lock->old_sha1), sha1_to_hex(old_sha1)); unlock_ref(lock); @@ -411,7 +411,7 @@ int write_ref_sha1(struct ref_lock *lock, if (!lock) return -1; - if (!lock->force_write && !memcmp(lock->old_sha1, sha1, 20)) { + if (!lock->force_write && !hashcmp(lock->old_sha1, sha1)) { unlock_ref(lock); return 0; } @@ -475,7 +475,7 @@ int read_ref_at(const char *ref, unsigned long at_time, unsigned char *sha1) die("Log %s is corrupt.", logfile); if (get_sha1_hex(rec + 41, sha1)) die("Log %s is corrupt.", logfile); - if (memcmp(logged_sha1, sha1, 20)) { + if (hashcmp(logged_sha1, sha1)) { tz = strtoul(tz_c, NULL, 10); fprintf(stderr, "warning: Log %s has gap after %s.\n", @@ -489,7 +489,7 @@ int read_ref_at(const char *ref, unsigned long at_time, unsigned char *sha1) else { if (get_sha1_hex(rec + 41, logged_sha1)) die("Log %s is corrupt.", logfile); - if (memcmp(logged_sha1, sha1, 20)) { + if (hashcmp(logged_sha1, sha1)) { tz = strtoul(tz_c, NULL, 10); fprintf(stderr, "warning: Log %s unexpectedly ended on %s.\n", diff --git a/revision.c b/revision.c index 5a91d06b9..1d89d7273 100644 --- a/revision.c +++ b/revision.c @@ -496,7 +496,7 @@ static int add_parents_only(struct rev_info *revs, const char *arg, int flags) it = get_reference(revs, arg, sha1, 0); if (it->type != OBJ_TAG) break; - memcpy(sha1, ((struct tag*)it)->tagged->sha1, 20); + hashcpy(sha1, ((struct tag*)it)->tagged->sha1); } if (it->type != OBJ_COMMIT) return 0; diff --git a/run-command.c b/run-command.c index ca67ee933..61908682b 100644 --- a/run-command.c +++ b/run-command.c @@ -25,15 +25,15 @@ int run_command_v_opt(int argc, const char **argv, int flags) } for (;;) { int status, code; - int retval = waitpid(pid, &status, 0); + pid_t waiting = waitpid(pid, &status, 0); - if (retval < 0) { + if (waiting < 0) { if (errno == EINTR) continue; - error("waitpid failed (%s)", strerror(retval)); + error("waitpid failed (%s)", strerror(errno)); return -ERR_RUN_COMMAND_WAITPID; } - if (retval != pid) + if (waiting != pid) return -ERR_RUN_COMMAND_WAITPID_WRONG_PID; if (WIFSIGNALED(status)) return -ERR_RUN_COMMAND_WAITPID_SIGNAL; diff --git a/send-pack.c b/send-pack.c index 10bc8bc35..fd79a6192 100644 --- a/send-pack.c +++ b/send-pack.c @@ -9,10 +9,10 @@ static const char send_pack_usage[] = "git-send-pack [--all] [--exec=git-receive-pack] <remote> [<head>...]\n" " --all and explicit <head> specification are mutually exclusive."; static const char *exec = "git-receive-pack"; -static int verbose = 0; -static int send_all = 0; -static int force_update = 0; -static int use_thin_pack = 0; +static int verbose; +static int send_all; +static int force_update; +static int use_thin_pack; static int is_zero_sha1(const unsigned char *sha1) { @@ -111,7 +111,7 @@ static void rev_list(int fd, struct ref *refs) exec_rev_list(refs); } -static int pack_objects(int fd, struct ref *refs) +static void pack_objects(int fd, struct ref *refs) { pid_t rev_list_pid; @@ -126,7 +126,6 @@ static int pack_objects(int fd, struct ref *refs) * We don't wait for the rev-list pipeline in the parent: * we end up waiting for the other end instead */ - return 0; } static void unmark_and_free(struct commit_list *list, unsigned int mark) @@ -186,7 +185,7 @@ static int one_local_ref(const char *refname, const unsigned char *sha1) struct ref *ref; int len = strlen(refname) + 1; ref = xcalloc(1, sizeof(*ref) + len); - memcpy(ref->new_sha1, sha1, 20); + hashcpy(ref->new_sha1, sha1); memcpy(ref->name, refname, len); *local_tail = ref; local_tail = &ref->next; @@ -266,7 +265,7 @@ static int send_pack(int in, int out, int nr_refspec, char **refspec) char old_hex[60], *new_hex; if (!ref->peer_ref) continue; - if (!memcmp(ref->old_sha1, ref->peer_ref->new_sha1, 20)) { + if (!hashcmp(ref->old_sha1, ref->peer_ref->new_sha1)) { if (verbose) fprintf(stderr, "'%s': up-to-date\n", ref->name); continue; @@ -311,7 +310,7 @@ static int send_pack(int in, int out, int nr_refspec, char **refspec) continue; } } - memcpy(ref->new_sha1, ref->peer_ref->new_sha1, 20); + hashcpy(ref->new_sha1, ref->peer_ref->new_sha1); if (is_zero_sha1(ref->new_sha1)) { error("cannot happen anymore"); ret = -3; diff --git a/sha1_file.c b/sha1_file.c index 3db956dd5..46272b591 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -22,7 +22,7 @@ #endif #endif -const unsigned char null_sha1[20] = { 0, }; +const unsigned char null_sha1[20]; static unsigned int sha1_file_open_flag = O_NOATIME; @@ -463,6 +463,7 @@ int use_packed_git(struct packed_git *p) int fd; struct stat st; void *map; + struct pack_header *hdr; pack_mapped += p->pack_size; while (PACK_MAX_SZ < pack_mapped && unuse_one_packed_git()) @@ -482,13 +483,24 @@ int use_packed_git(struct packed_git *p) die("packfile %s cannot be mapped.", p->pack_name); p->pack_base = map; + /* Check if we understand this pack file. If we don't we're + * likely too old to handle it. + */ + hdr = map; + if (hdr->hdr_signature != htonl(PACK_SIGNATURE)) + die("packfile %s isn't actually a pack.", p->pack_name); + if (!pack_version_ok(hdr->hdr_version)) + die("packfile %s is version %i and not supported" + " (try upgrading GIT to a newer version)", + p->pack_name, ntohl(hdr->hdr_version)); + /* Check if the pack file matches with the index file. * this is cheap. */ - if (memcmp((char*)(p->index_base) + p->index_size - 40, - (char *) p->pack_base + p->pack_size - 20, - 20)) { - + if (hashcmp((unsigned char *)(p->index_base) + + p->index_size - 40, + (unsigned char *)p->pack_base + + p->pack_size - 20)) { die("packfile %s does not match index.", p->pack_name); } } @@ -528,7 +540,7 @@ struct packed_git *add_packed_git(char *path, int path_len, int local) p->pack_use_cnt = 0; p->pack_local = local; if ((path_len > 44) && !get_sha1_hex(path + path_len - 44, sha1)) - memcpy(p->sha1, sha1, 20); + hashcpy(p->sha1, sha1); return p; } @@ -559,7 +571,7 @@ struct packed_git *parse_pack_index_file(const unsigned char *sha1, char *idx_pa p->pack_base = NULL; p->pack_last_used = 0; p->pack_use_cnt = 0; - memcpy(p->sha1, sha1, 20); + hashcpy(p->sha1, sha1); return p; } @@ -643,11 +655,10 @@ int check_sha1_signature(const unsigned char *sha1, void *map, unsigned long siz SHA1_Update(&c, header, 1+sprintf(header, "%s %lu", type, size)); SHA1_Update(&c, map, size); SHA1_Final(real_sha1, &c); - return memcmp(sha1, real_sha1, 20) ? -1 : 0; + return hashcmp(sha1, real_sha1) ? -1 : 0; } -static void *map_sha1_file_internal(const unsigned char *sha1, - unsigned long *size) +void *map_sha1_file(const unsigned char *sha1, unsigned long *size) { struct stat st; void *map; @@ -684,10 +695,26 @@ static void *map_sha1_file_internal(const unsigned char *sha1, return map; } +int legacy_loose_object(unsigned char *map) +{ + unsigned int word; + + /* + * Is it a zlib-compressed buffer? If so, the first byte + * must be 0x78 (15-bit window size, deflated), and the + * first 16-bit word is evenly divisible by 31 + */ + word = (map[0] << 8) + map[1]; + if (map[0] == 0x78 && !(word % 31)) + return 1; + else + return 0; +} + static int unpack_sha1_header(z_stream *stream, unsigned char *map, unsigned long mapsize, void *buffer, unsigned long bufsiz) { unsigned char c; - unsigned int word, bits; + unsigned int bits; unsigned long size; static const char *typename[8] = { NULL, /* OBJ_EXT */ @@ -703,13 +730,7 @@ static int unpack_sha1_header(z_stream *stream, unsigned char *map, unsigned lon stream->next_out = buffer; stream->avail_out = bufsiz; - /* - * Is it a zlib-compressed buffer? If so, the first byte - * must be 0x78 (15-bit window size, deflated), and the - * first 16-bit word is evenly divisible by 31 - */ - word = (map[0] << 8) + map[1]; - if (map[0] == 0x78 && !(word % 31)) { + if (legacy_loose_object(map)) { inflateInit(stream); return inflate(stream, 0); } @@ -896,23 +917,19 @@ static unsigned long unpack_object_header(struct packed_git *p, unsigned long of enum object_type *type, unsigned long *sizep) { unsigned shift; - unsigned char *pack, c; + unsigned char c; unsigned long size; if (offset >= p->pack_size) die("object offset outside of pack file"); - - pack = (unsigned char *) p->pack_base + offset; - c = *pack++; - offset++; + c = *((unsigned char *)p->pack_base + offset++); *type = (c >> 4) & 7; size = c & 15; shift = 4; while (c & 0x80) { if (offset >= p->pack_size) die("object offset outside of pack file"); - c = *pack++; - offset++; + c = *((unsigned char *)p->pack_base + offset++); size += (c & 0x7f) << shift; shift += 7; } @@ -932,7 +949,7 @@ int check_reuse_pack_delta(struct packed_git *p, unsigned long offset, ptr = unpack_object_header(p, ptr, kindp, sizep); if (*kindp != OBJ_DELTA) goto done; - memcpy(base, (char *) p->pack_base + ptr, 20); + hashcpy(base, (unsigned char *) p->pack_base + ptr); status = 0; done: unuse_packed_git(p); @@ -960,7 +977,7 @@ void packed_object_info_detail(struct pack_entry *e, if (p->pack_size <= offset + 20) die("pack file %s records an incomplete delta base", p->pack_name); - memcpy(base_sha1, pack, 20); + hashcpy(base_sha1, pack); do { struct pack_entry base_ent; unsigned long junk; @@ -975,16 +992,10 @@ void packed_object_info_detail(struct pack_entry *e, } switch (kind) { case OBJ_COMMIT: - strcpy(type, commit_type); - break; case OBJ_TREE: - strcpy(type, tree_type); - break; case OBJ_BLOB: - strcpy(type, blob_type); - break; case OBJ_TAG: - strcpy(type, tag_type); + strcpy(type, type_names[kind]); break; default: die("corrupted pack file %s containing object of kind %d", @@ -1015,16 +1026,10 @@ static int packed_object_info(struct pack_entry *entry, unuse_packed_git(p); return retval; case OBJ_COMMIT: - strcpy(type, commit_type); - break; case OBJ_TREE: - strcpy(type, tree_type); - break; case OBJ_BLOB: - strcpy(type, blob_type); - break; case OBJ_TAG: - strcpy(type, tag_type); + strcpy(type, type_names[kind]); break; default: die("corrupted pack file %s containing object of kind %d", @@ -1036,26 +1041,49 @@ static int packed_object_info(struct pack_entry *entry, return 0; } -/* forward declaration for a mutually recursive function */ -static void *unpack_entry(struct pack_entry *, char *, unsigned long *); +static void *unpack_compressed_entry(struct packed_git *p, + unsigned long offset, + unsigned long size) +{ + int st; + z_stream stream; + unsigned char *buffer; + + buffer = xmalloc(size + 1); + buffer[size] = 0; + memset(&stream, 0, sizeof(stream)); + stream.next_in = (unsigned char*)p->pack_base + offset; + stream.avail_in = p->pack_size - offset; + stream.next_out = buffer; + stream.avail_out = size; + + inflateInit(&stream); + st = inflate(&stream, Z_FINISH); + inflateEnd(&stream); + if ((st != Z_STREAM_END) || stream.total_out != size) { + free(buffer); + return NULL; + } -static void *unpack_delta_entry(unsigned char *base_sha1, + return buffer; +} + +static void *unpack_delta_entry(struct packed_git *p, + unsigned long offset, unsigned long delta_size, - unsigned long left, char *type, - unsigned long *sizep, - struct packed_git *p) + unsigned long *sizep) { struct pack_entry base_ent; - void *data, *delta_data, *result, *base; - unsigned long data_size, result_size, base_size; - z_stream stream; - int st; + void *delta_data, *result, *base; + unsigned long result_size, base_size; + unsigned char* base_sha1; - if (left < 20) + if ((offset + 20) >= p->pack_size) die("truncated pack file"); /* The base entry _must_ be in the same pack */ + base_sha1 = (unsigned char*)p->pack_base + offset; if (!find_pack_entry_one(base_sha1, &base_ent, p)) die("failed to find delta-pack base object %s", sha1_to_hex(base_sha1)); @@ -1064,23 +1092,7 @@ static void *unpack_delta_entry(unsigned char *base_sha1, die("failed to read delta-pack base object %s", sha1_to_hex(base_sha1)); - data = base_sha1 + 20; - data_size = left - 20; - delta_data = xmalloc(delta_size); - - memset(&stream, 0, sizeof(stream)); - - stream.next_in = data; - stream.avail_in = data_size; - stream.next_out = delta_data; - stream.avail_out = delta_size; - - inflateInit(&stream); - st = inflate(&stream, Z_FINISH); - inflateEnd(&stream); - if ((st != Z_STREAM_END) || stream.total_out != delta_size) - die("delta data unpack failed"); - + delta_data = unpack_compressed_entry(p, offset + 20, delta_size); result = patch_delta(base, base_size, delta_data, delta_size, &result_size); @@ -1092,33 +1104,6 @@ static void *unpack_delta_entry(unsigned char *base_sha1, return result; } -static void *unpack_non_delta_entry(unsigned char *data, - unsigned long size, - unsigned long left) -{ - int st; - z_stream stream; - unsigned char *buffer; - - buffer = xmalloc(size + 1); - buffer[size] = 0; - memset(&stream, 0, sizeof(stream)); - stream.next_in = data; - stream.avail_in = left; - stream.next_out = buffer; - stream.avail_out = size; - - inflateInit(&stream); - st = inflate(&stream, Z_FINISH); - inflateEnd(&stream); - if ((st != Z_STREAM_END) || stream.total_out != size) { - free(buffer); - return NULL; - } - - return buffer; -} - static void *unpack_entry(struct pack_entry *entry, char *type, unsigned long *sizep) { @@ -1139,36 +1124,23 @@ void *unpack_entry_gently(struct pack_entry *entry, char *type, unsigned long *sizep) { struct packed_git *p = entry->p; - unsigned long offset, size, left; - unsigned char *pack; + unsigned long offset, size; enum object_type kind; - void *retval; offset = unpack_object_header(p, entry->offset, &kind, &size); - pack = (unsigned char *) p->pack_base + offset; - left = p->pack_size - offset; switch (kind) { case OBJ_DELTA: - retval = unpack_delta_entry(pack, size, left, type, sizep, p); - return retval; + return unpack_delta_entry(p, offset, size, type, sizep); case OBJ_COMMIT: - strcpy(type, commit_type); - break; case OBJ_TREE: - strcpy(type, tree_type); - break; case OBJ_BLOB: - strcpy(type, blob_type); - break; case OBJ_TAG: - strcpy(type, tag_type); - break; + strcpy(type, type_names[kind]); + *sizep = size; + return unpack_compressed_entry(p, offset, size); default: return NULL; } - *sizep = size; - retval = unpack_non_delta_entry(pack, size, left); - return retval; } int num_packed_objects(const struct packed_git *p) @@ -1183,7 +1155,7 @@ int nth_packed_object_sha1(const struct packed_git *p, int n, void *index = p->index_base + 256; if (n < 0 || num_packed_objects(p) <= n) return -1; - memcpy(sha1, (char *) index + (24 * n) + 4, 20); + hashcpy(sha1, (unsigned char *) index + (24 * n) + 4); return 0; } @@ -1197,10 +1169,10 @@ int find_pack_entry_one(const unsigned char *sha1, do { int mi = (lo + hi) / 2; - int cmp = memcmp((char *) index + (24 * mi) + 4, sha1, 20); + int cmp = hashcmp((unsigned char *)index + (24 * mi) + 4, sha1); if (!cmp) { e->offset = ntohl(*((unsigned int *) ((char *) index + (24 * mi)))); - memcpy(e->sha1, sha1, 20); + hashcpy(e->sha1, sha1); e->p = p; return 1; } @@ -1246,7 +1218,7 @@ int sha1_object_info(const unsigned char *sha1, char *type, unsigned long *sizep z_stream stream; char hdr[128]; - map = map_sha1_file_internal(sha1, &mapsize); + map = map_sha1_file(sha1, &mapsize); if (!map) { struct pack_entry e; @@ -1291,7 +1263,7 @@ void * read_sha1_file(const unsigned char *sha1, char *type, unsigned long *size if (find_pack_entry(sha1, &e)) return read_packed_sha1(sha1, type, size); - map = map_sha1_file_internal(sha1, &mapsize); + map = map_sha1_file(sha1, &mapsize); if (map) { buf = unpack_sha1_file(map, mapsize, type, size); munmap(map, mapsize); @@ -1313,7 +1285,7 @@ void *read_object_with_reference(const unsigned char *sha1, unsigned long isize; unsigned char actual_sha1[20]; - memcpy(actual_sha1, sha1, 20); + hashcpy(actual_sha1, sha1); while (1) { int ref_length = -1; const char *ref_type = NULL; @@ -1324,7 +1296,7 @@ void *read_object_with_reference(const unsigned char *sha1, if (!strcmp(type, required_type)) { *size = isize; if (actual_sha1_return) - memcpy(actual_sha1_return, actual_sha1, 20); + hashcpy(actual_sha1_return, actual_sha1); return buffer; } /* Handle references */ @@ -1519,7 +1491,7 @@ int write_sha1_file(void *buf, unsigned long len, const char *type, unsigned cha */ filename = write_sha1_file_prepare(buf, len, type, sha1, hdr, &hdrlen); if (returnsha1) - memcpy(returnsha1, sha1, 20); + hashcpy(returnsha1, sha1); if (has_sha1_file(sha1)) return 0; fd = open(filename, O_RDONLY); @@ -1629,7 +1601,7 @@ int write_sha1_to_fd(int fd, const unsigned char *sha1) { int retval; unsigned long objsize; - void *buf = map_sha1_file_internal(sha1, &objsize); + void *buf = map_sha1_file(sha1, &objsize); if (buf) { retval = write_buffer(fd, buf, objsize); @@ -1706,7 +1678,7 @@ int write_sha1_from_fd(const unsigned char *sha1, int fd, char *buffer, unlink(tmpfile); return error("File %s corrupted", sha1_to_hex(sha1)); } - if (memcmp(sha1, real_sha1, 20)) { + if (hashcmp(sha1, real_sha1)) { unlink(tmpfile); return error("File %s has bad hash", sha1_to_hex(sha1)); } @@ -1768,7 +1740,7 @@ int read_pipe(int fd, char** return_buf, unsigned long* return_size) off += iret; if (off == size) { size *= 2; - buf = realloc(buf, size); + buf = xrealloc(buf, size); } } } while (iret > 0); diff --git a/sha1_name.c b/sha1_name.c index c5a05faeb..3f6b77ccf 100644 --- a/sha1_name.c +++ b/sha1_name.c @@ -84,7 +84,7 @@ static int find_short_packed_object(int len, const unsigned char *match, unsigne int cmp; nth_packed_object_sha1(p, mid, now); - cmp = memcmp(match, now, 20); + cmp = hashcmp(match, now); if (!cmp) { first = mid; break; @@ -103,10 +103,10 @@ static int find_short_packed_object(int len, const unsigned char *match, unsigne !match_sha(len, match, next)) { /* unique within this pack */ if (!found) { - memcpy(found_sha1, now, 20); + hashcpy(found_sha1, now); found++; } - else if (memcmp(found_sha1, now, 20)) { + else if (hashcmp(found_sha1, now)) { found = 2; break; } @@ -120,7 +120,7 @@ static int find_short_packed_object(int len, const unsigned char *match, unsigne } } if (found == 1) - memcpy(sha1, found_sha1, 20); + hashcpy(sha1, found_sha1); return found; } @@ -140,13 +140,13 @@ static int find_unique_short_object(int len, char *canonical, if (1 < has_unpacked || 1 < has_packed) return SHORT_NAME_AMBIGUOUS; if (has_unpacked != has_packed) { - memcpy(sha1, (has_packed ? packed_sha1 : unpacked_sha1), 20); + hashcpy(sha1, (has_packed ? packed_sha1 : unpacked_sha1)); return 0; } /* Both have unique ones -- do they match? */ - if (memcmp(packed_sha1, unpacked_sha1, 20)) + if (hashcmp(packed_sha1, unpacked_sha1)) return SHORT_NAME_AMBIGUOUS; - memcpy(sha1, packed_sha1, 20); + hashcpy(sha1, packed_sha1); return 0; } @@ -159,7 +159,7 @@ static int get_short_sha1(const char *name, int len, unsigned char *sha1, if (len < MINIMUM_ABBREV) return -1; - memset(res, 0, 20); + hashclr(res); memset(canonical, 'x', 40); for (i = 0; i < len ;i++) { unsigned char c = name[i]; @@ -191,7 +191,7 @@ const char *find_unique_abbrev(const unsigned char *sha1, int len) int status, is_null; static char hex[41]; - is_null = !memcmp(sha1, null_sha1, 20); + is_null = is_null_sha1(sha1); memcpy(hex, sha1_to_hex(sha1), 40); if (len == 40 || !len) return hex; @@ -320,13 +320,13 @@ static int get_parent(const char *name, int len, if (parse_commit(commit)) return -1; if (!idx) { - memcpy(result, commit->object.sha1, 20); + hashcpy(result, commit->object.sha1); return 0; } p = commit->parents; while (p) { if (!--idx) { - memcpy(result, p->item->object.sha1, 20); + hashcpy(result, p->item->object.sha1); return 0; } p = p->next; @@ -347,9 +347,9 @@ static int get_nth_ancestor(const char *name, int len, if (!commit || parse_commit(commit) || !commit->parents) return -1; - memcpy(sha1, commit->parents->item->object.sha1, 20); + hashcpy(sha1, commit->parents->item->object.sha1); } - memcpy(result, sha1, 20); + hashcpy(result, sha1); return 0; } @@ -401,7 +401,7 @@ static int peel_onion(const char *name, int len, unsigned char *sha1) o = deref_tag(o, name, sp - name - 2); if (!o || (!o->parsed && !parse_object(o->sha1))) return -1; - memcpy(sha1, o->sha1, 20); + hashcpy(sha1, o->sha1); } else { /* At this point, the syntax look correct, so @@ -413,7 +413,7 @@ static int peel_onion(const char *name, int len, unsigned char *sha1) if (!o || (!o->parsed && !parse_object(o->sha1))) return -1; if (o->type == expected_type) { - memcpy(sha1, o->sha1, 20); + hashcpy(sha1, o->sha1); return 0; } if (o->type == OBJ_TAG) @@ -520,7 +520,7 @@ int get_sha1(const char *name, unsigned char *sha1) memcmp(ce->name, cp, namelen)) break; if (ce_stage(ce) == stage) { - memcpy(sha1, ce->sha1, 20); + hashcpy(sha1, ce->sha1); return 0; } pos++; diff --git a/ssh-fetch.c b/ssh-fetch.c index c7d8fa80e..b006c5c98 100644 --- a/ssh-fetch.c +++ b/ssh-fetch.c @@ -17,7 +17,7 @@ static int fd_in; static int fd_out; -static unsigned char remote_version = 0; +static unsigned char remote_version; static unsigned char local_version = 1; static ssize_t force_write(int fd, void *buffer, size_t length) @@ -36,9 +36,9 @@ static ssize_t force_write(int fd, void *buffer, size_t length) return ret; } -static int prefetches = 0; +static int prefetches; -static struct object_list *in_transit = NULL; +static struct object_list *in_transit; static struct object_list **end_of_transit = &in_transit; void prefetch(unsigned char *sha1) @@ -59,7 +59,7 @@ void prefetch(unsigned char *sha1) } static char conn_buf[4096]; -static size_t conn_buf_posn = 0; +static size_t conn_buf_posn; int fetch(unsigned char *sha1) { @@ -67,7 +67,7 @@ int fetch(unsigned char *sha1) signed char remote; struct object_list *temp; - if (memcmp(sha1, in_transit->item->sha1, 20)) { + if (hashcmp(sha1, in_transit->item->sha1)) { /* we must have already fetched it to clean the queue */ return has_sha1_file(sha1) ? 0 : -1; } diff --git a/ssh-upload.c b/ssh-upload.c index 2da66618f..20b15eab5 100644 --- a/ssh-upload.c +++ b/ssh-upload.c @@ -15,9 +15,9 @@ #include <string.h> static unsigned char local_version = 1; -static unsigned char remote_version = 0; +static unsigned char remote_version; -static int verbose = 0; +static int verbose; static int serve_object(int fd_in, int fd_out) { ssize_t size; diff --git a/t/t4116-apply-reverse.sh b/t/t4116-apply-reverse.sh new file mode 100755 index 000000000..74f5c2a57 --- /dev/null +++ b/t/t4116-apply-reverse.sh @@ -0,0 +1,85 @@ +#!/bin/sh +# +# Copyright (c) 2005 Junio C Hamano +# + +test_description='git-apply in reverse + +' + +. ./test-lib.sh + +test_expect_success setup ' + + for i in a b c d e f g h i j k l m n; do echo $i; done >file1 && + tr "[ijk]" '\''[\0\1\2]'\'' <file1 >file2 && + + git add file1 file2 && + git commit -m initial && + git tag initial && + + for i in a b c g h i J K L m o n p q; do echo $i; done >file1 && + tr "[mon]" '\''[\0\1\2]'\'' <file1 >file2 && + + git commit -a -m second && + git tag second && + + git diff --binary initial second >patch + +' + +test_expect_success 'apply in forward' ' + + T0=`git rev-parse "second^{tree}"` && + git reset --hard initial && + git apply --index --binary patch && + T1=`git write-tree` && + test "$T0" = "$T1" +' + +test_expect_success 'apply in reverse' ' + + git reset --hard second && + git apply --reverse --binary --index patch && + git diff >diff && + diff -u /dev/null diff + +' + +test_expect_success 'setup separate repository lacking postimage' ' + + git tar-tree initial initial | tar xf - && + ( + cd initial && git init-db && git add . + ) && + + git tar-tree second second | tar xf - && + ( + cd second && git init-db && git add . + ) + +' + +test_expect_success 'apply in forward without postimage' ' + + T0=`git rev-parse "second^{tree}"` && + ( + cd initial && + git apply --index --binary ../patch && + T1=`git write-tree` && + test "$T0" = "$T1" + ) +' + +test_expect_success 'apply in reverse without postimage' ' + + T0=`git rev-parse "initial^{tree}"` && + ( + cd second && + git apply --index --binary --reverse ../patch && + T1=`git write-tree` && + test "$T0" = "$T1" + ) +' + +test_done diff --git a/t/t4117-apply-reject.sh b/t/t4117-apply-reject.sh new file mode 100755 index 000000000..b4de075a3 --- /dev/null +++ b/t/t4117-apply-reject.sh @@ -0,0 +1,157 @@ +#!/bin/sh +# +# Copyright (c) 2005 Junio C Hamano +# + +test_description='git-apply with rejects + +' + +. ./test-lib.sh + +test_expect_success setup ' + for i in 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 + do + echo $i + done >file1 && + cat file1 >saved.file1 && + git update-index --add file1 && + git commit -m initial && + + for i in 1 2 A B 4 5 6 7 8 9 10 11 12 C 13 14 15 16 17 18 19 20 D 21 + do + echo $i + done >file1 && + git diff >patch.1 && + cat file1 >clean && + + for i in 1 E 2 3 4 5 6 7 8 9 10 11 12 C 13 14 15 16 17 18 19 20 F 21 + do + echo $i + done >expected && + + mv file1 file2 && + git update-index --add --remove file1 file2 && + git diff -M HEAD >patch.2 && + + rm -f file1 file2 && + mv saved.file1 file1 && + git update-index --add --remove file1 file2 && + + for i in 1 E 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 F 21 + do + echo $i + done >file1 && + + cat file1 >saved.file1 +' + +test_expect_success 'apply without --reject should fail' ' + + if git apply patch.1 + then + echo "Eh? Why?" + exit 1 + fi + + diff -u file1 saved.file1 +' + +test_expect_success 'apply without --reject should fail' ' + + if git apply --verbose patch.1 + then + echo "Eh? Why?" + exit 1 + fi + + diff -u file1 saved.file1 +' + +test_expect_success 'apply with --reject should fail but update the file' ' + + cat saved.file1 >file1 && + rm -f file1.rej file2.rej && + + if git apply --reject patch.1 + then + echo "succeeds with --reject?" + exit 1 + fi + + diff -u file1 expected && + + cat file1.rej && + + if test -f file2.rej + then + echo "file2 should not have been touched" + exit 1 + fi +' + +test_expect_success 'apply with --reject should fail but update the file' ' + + cat saved.file1 >file1 && + rm -f file1.rej file2.rej file2 && + + if git apply --reject patch.2 >rejects + then + echo "succeeds with --reject?" + exit 1 + fi + + test -f file1 && { + echo "file1 still exists?" + exit 1 + } + diff -u file2 expected && + + cat file2.rej && + + if test -f file1.rej + then + echo "file2 should not have been touched" + exit 1 + fi + +' + +test_expect_success 'the same test with --verbose' ' + + cat saved.file1 >file1 && + rm -f file1.rej file2.rej file2 && + + if git apply --reject --verbose patch.2 >rejects + then + echo "succeeds with --reject?" + exit 1 + fi + + test -f file1 && { + echo "file1 still exists?" + exit 1 + } + diff -u file2 expected && + + cat file2.rej && + + if test -f file1.rej + then + echo "file2 should not have been touched" + exit 1 + fi + +' + +test_expect_success 'apply cleanly with --verbose' ' + + git cat-file -p HEAD:file1 >file1 && + rm -f file?.rej file2 && + + git apply --verbose patch.1 && + + diff -u file1 clean +' + +test_done diff --git a/t/t7001-mv.sh b/t/t7001-mv.sh index 900ca93cd..b7fcdb390 100755 --- a/t/t7001-mv.sh +++ b/t/t7001-mv.sh @@ -60,6 +60,10 @@ test_expect_success \ grep -E "^R100.+path0/README.+path2/README"' test_expect_success \ + 'succeed when source is a prefix of destination' \ + 'git-mv path2/COPYING path2/COPYING-renamed' + +test_expect_success \ 'moving whole subdirectory into subdirectory' \ 'git-mv path2 path1' @@ -78,4 +82,8 @@ test_expect_failure \ 'do not move directory over existing directory' \ 'mkdir path0 && mkdir path0/path2 && git-mv path2 path0' +test_expect_success \ + 'move into "."' \ + 'git-mv path1/path2/ .' + test_done diff --git a/t/t7002-grep.sh b/t/t7002-grep.sh index 00a7d762c..6bfb899ed 100755 --- a/t/t7002-grep.sh +++ b/t/t7002-grep.sh @@ -3,7 +3,7 @@ # Copyright (c) 2006 Junio C Hamano # -test_description='git grep -w +test_description='git grep various. ' . ./test-lib.sh @@ -19,7 +19,9 @@ test_expect_success setup ' echo x x xx x >x && echo y yy >y && echo zzz > z && - git add file x y z && + mkdir t && + echo test >t/t && + git add file x y z t/t && git commit -m initial ' @@ -80,6 +82,31 @@ do diff expected actual fi ' + + test_expect_success "grep $L (t-1)" ' + echo "${HC}t/t:1:test" >expected && + git grep -n -e test $H >actual && + diff expected actual + ' + + test_expect_success "grep $L (t-2)" ' + echo "${HC}t:1:test" >expected && + ( + cd t && + git grep -n -e test $H + ) >actual && + diff expected actual + ' + + test_expect_success "grep $L (t-3)" ' + echo "${HC}t/t:1:test" >expected && + ( + cd t && + git grep --full-name -n -e test $H + ) >actual && + diff expected actual + ' + done test_done diff --git a/templates/hooks--commit-msg b/templates/hooks--commit-msg index 643822d23..0b906caa9 100644 --- a/templates/hooks--commit-msg +++ b/templates/hooks--commit-msg @@ -11,4 +11,8 @@ # This example catches duplicate Signed-off-by lines. test "" = "$(grep '^Signed-off-by: ' "$1" | - sort | uniq -c | sed -e '/^[ ]*1 /d')" + sort | uniq -c | sed -e '/^[ ]*1[ ]/d')" || { + echo >&2 Duplicate Signed-off-by lines. + exit 1 +} + diff --git a/tree-diff.c b/tree-diff.c index 1cdf8aa90..7e2f4f088 100644 --- a/tree-diff.c +++ b/tree-diff.c @@ -15,7 +15,8 @@ static char *malloc_base(const char *base, const char *path, int pathlen) return newbase; } -static int show_entry(struct diff_options *opt, const char *prefix, struct tree_desc *desc, const char *base); +static void show_entry(struct diff_options *opt, const char *prefix, struct tree_desc *desc, + const char *base); static int compare_tree_entry(struct tree_desc *t1, struct tree_desc *t2, const char *base, struct diff_options *opt) { @@ -38,8 +39,7 @@ static int compare_tree_entry(struct tree_desc *t1, struct tree_desc *t2, const show_entry(opt, "+", t2, base); return 1; } - if (!opt->find_copies_harder && - !memcmp(sha1, sha2, 20) && mode1 == mode2) + if (!opt->find_copies_harder && !hashcmp(sha1, sha2) && mode1 == mode2) return 0; /* @@ -131,7 +131,8 @@ static void show_tree(struct diff_options *opt, const char *prefix, struct tree_ } /* A file entry went away or appeared */ -static int show_entry(struct diff_options *opt, const char *prefix, struct tree_desc *desc, const char *base) +static void show_entry(struct diff_options *opt, const char *prefix, struct tree_desc *desc, + const char *base) { unsigned mode; const char *path; @@ -152,11 +153,9 @@ static int show_entry(struct diff_options *opt, const char *prefix, struct tree_ free(tree); free(newbase); - return 0; + } else { + opt->add_remove(opt, prefix[0], mode, sha1, base, path); } - - opt->add_remove(opt, prefix[0], mode, sha1, base, path); - return 0; } int diff_tree(struct tree_desc *t1, struct tree_desc *t2, const char *base, struct diff_options *opt) diff --git a/tree-walk.c b/tree-walk.c index 3f83e98f3..14cc5aea6 100644 --- a/tree-walk.c +++ b/tree-walk.c @@ -179,7 +179,7 @@ static int find_tree_entry(struct tree_desc *t, const char *name, unsigned char if (cmp < 0) break; if (entrylen == namelen) { - memcpy(result, sha1, 20); + hashcpy(result, sha1); return 0; } if (name[entrylen] != '/') @@ -187,7 +187,7 @@ static int find_tree_entry(struct tree_desc *t, const char *name, unsigned char if (!S_ISDIR(*mode)) break; if (++entrylen == namelen) { - memcpy(result, sha1, 20); + hashcpy(result, sha1); return 0; } return get_tree_entry(sha1, name + entrylen, result, mode); @@ -25,7 +25,7 @@ static int read_one_entry(const unsigned char *sha1, const char *base, int basel ce->ce_flags = create_ce_flags(baselen + len, stage); memcpy(ce->name, base, baselen); memcpy(ce->name + baselen, pathname, len+1); - memcpy(ce->sha1, sha1, 20); + hashcpy(ce->sha1, sha1); return add_cache_entry(ce, ADD_CACHE_OK_TO_ADD|ADD_CACHE_SKIP_DFCHECK); } @@ -144,7 +144,7 @@ struct tree *lookup_tree(const unsigned char *sha1) return (struct tree *) obj; } -static int track_tree_refs(struct tree *item) +static void track_tree_refs(struct tree *item) { int n_refs = 0, i; struct object_refs *refs; @@ -174,7 +174,6 @@ static int track_tree_refs(struct tree *item) refs->ref[i++] = obj; } set_object_refs(&item->object, refs); - return 0; } int parse_tree_buffer(struct tree *item, void *buffer, unsigned long size) diff --git a/unpack-trees.c b/unpack-trees.c index a20639be7..3ac0289b3 100644 --- a/unpack-trees.c +++ b/unpack-trees.c @@ -200,7 +200,7 @@ static int unpack_trees_rec(struct tree_entry_list **posns, int len, any_files = 1; - memcpy(ce->sha1, posns[i]->sha1, 20); + hashcpy(ce->sha1, posns[i]->sha1); src[i + o->merge] = ce; subposns[i] = df_conflict_list; posns[i] = posns[i]->next; @@ -278,7 +278,7 @@ static void unlink_entry(char *name) } } -static volatile int progress_update = 0; +static volatile sig_atomic_t progress_update; static void progress_interval(int signum) { @@ -417,7 +417,7 @@ static int same(struct cache_entry *a, struct cache_entry *b) if (!a && !b) return 1; return a->ce_mode == b->ce_mode && - !memcmp(a->sha1, b->sha1, 20); + !hashcmp(a->sha1, b->sha1); } diff --git a/upload-pack.c b/upload-pack.c index 07ecdb428..51ce936b0 100644 --- a/upload-pack.c +++ b/upload-pack.c @@ -14,14 +14,12 @@ static const char upload_pack_usage[] = "git-upload-pack [--strict] [--timeout=n #define THEY_HAVE (1U << 0) #define OUR_REF (1U << 1) #define WANTED (1U << 2) -#define MAX_HAS 256 -#define MAX_NEEDS 256 -static int nr_has = 0, nr_needs = 0, multi_ack = 0, nr_our_refs = 0; -static int use_thin_pack = 0; -static unsigned char has_sha1[MAX_HAS][20]; -static unsigned char needs_sha1[MAX_NEEDS][20]; -static unsigned int timeout = 0; -static int use_sideband = 0; +static int multi_ack, nr_our_refs; +static int use_thin_pack; +static struct object_array have_obj; +static struct object_array want_obj; +static unsigned int timeout; +static int use_sideband; static void reset_timeout(void) { @@ -83,7 +81,7 @@ static void create_pack_file(void) */ int lp_pipe[2], pu_pipe[2], pe_pipe[2]; pid_t pid_rev_list, pid_pack_objects; - int create_full_pack = (nr_our_refs == nr_needs && !nr_has); + int create_full_pack = (nr_our_refs == want_obj.nr && !have_obj.nr); char data[8193], progress[128]; char abort_msg[] = "aborting due to possible repository " "corruption on the remote side."; @@ -107,7 +105,7 @@ static void create_pack_file(void) use_thin_pack = 0; /* no point doing it */ } else - args = nr_has + nr_needs + 5; + args = have_obj.nr + want_obj.nr + 5; p = xmalloc(args * sizeof(char *)); argv = (const char **) p; buf = xmalloc(args * 45); @@ -118,20 +116,22 @@ static void create_pack_file(void) close(lp_pipe[1]); *p++ = "rev-list"; *p++ = use_thin_pack ? "--objects-edge" : "--objects"; - if (create_full_pack || MAX_NEEDS <= nr_needs) + if (create_full_pack) *p++ = "--all"; else { - for (i = 0; i < nr_needs; i++) { + for (i = 0; i < want_obj.nr; i++) { + struct object *o = want_obj.objects[i].item; *p++ = buf; - memcpy(buf, sha1_to_hex(needs_sha1[i]), 41); + memcpy(buf, sha1_to_hex(o->sha1), 41); buf += 41; } } if (!create_full_pack) - for (i = 0; i < nr_has; i++) { + for (i = 0; i < have_obj.nr; i++) { + struct object *o = have_obj.objects[i].item; *p++ = buf; *buf++ = '^'; - memcpy(buf, sha1_to_hex(has_sha1[i]), 41); + memcpy(buf, sha1_to_hex(o->sha1), 41); buf += 41; } *p++ = NULL; @@ -322,28 +322,29 @@ static void create_pack_file(void) static int got_sha1(char *hex, unsigned char *sha1) { + struct object *o; + if (get_sha1_hex(hex, sha1)) die("git-upload-pack: expected SHA1 object, got '%s'", hex); if (!has_sha1_file(sha1)) return 0; - if (nr_has < MAX_HAS) { - struct object *o = lookup_object(sha1); - if (!(o && o->parsed)) - o = parse_object(sha1); - if (!o) - die("oops (%s)", sha1_to_hex(sha1)); - if (o->type == OBJ_COMMIT) { - struct commit_list *parents; - if (o->flags & THEY_HAVE) - return 0; - o->flags |= THEY_HAVE; - for (parents = ((struct commit*)o)->parents; - parents; - parents = parents->next) - parents->item->object.flags |= THEY_HAVE; - } - memcpy(has_sha1[nr_has++], sha1, 20); + + o = lookup_object(sha1); + if (!(o && o->parsed)) + o = parse_object(sha1); + if (!o) + die("oops (%s)", sha1_to_hex(sha1)); + if (o->type == OBJ_COMMIT) { + struct commit_list *parents; + if (o->flags & THEY_HAVE) + return 0; + o->flags |= THEY_HAVE; + for (parents = ((struct commit*)o)->parents; + parents; + parents = parents->next) + parents->item->object.flags |= THEY_HAVE; } + add_object_array(o, NULL, &have_obj); return 1; } @@ -361,26 +362,24 @@ static int get_common_commits(void) reset_timeout(); if (!len) { - if (nr_has == 0 || multi_ack) + if (have_obj.nr == 0 || multi_ack) packet_write(1, "NAK\n"); continue; } len = strip(line, len); if (!strncmp(line, "have ", 5)) { if (got_sha1(line+5, sha1) && - (multi_ack || nr_has == 1)) { - if (nr_has >= MAX_HAS) - multi_ack = 0; + (multi_ack || have_obj.nr == 1)) { packet_write(1, "ACK %s%s\n", - sha1_to_hex(sha1), - multi_ack ? " continue" : ""); + sha1_to_hex(sha1), + multi_ack ? " continue" : ""); if (multi_ack) - memcpy(last_sha1, sha1, 20); + hashcpy(last_sha1, sha1); } continue; } if (!strcmp(line, "done")) { - if (nr_has > 0) { + if (have_obj.nr > 0) { if (multi_ack) packet_write(1, "ACK %s\n", sha1_to_hex(last_sha1)); @@ -393,31 +392,21 @@ static int get_common_commits(void) } } -static int receive_needs(void) +static void receive_needs(void) { static char line[1000]; - int len, needs; + int len; - needs = 0; for (;;) { struct object *o; - unsigned char dummy[20], *sha1_buf; + unsigned char sha1_buf[20]; len = packet_read_line(0, line, sizeof(line)); reset_timeout(); if (!len) - return needs; - - sha1_buf = dummy; - if (needs == MAX_NEEDS) { - fprintf(stderr, - "warning: supporting only a max of %d requests. " - "sending everything instead.\n", - MAX_NEEDS); - } - else if (needs < MAX_NEEDS) - sha1_buf = needs_sha1[needs]; + return; - if (strncmp("want ", line, 5) || get_sha1_hex(line+5, sha1_buf)) + if (strncmp("want ", line, 5) || + get_sha1_hex(line+5, sha1_buf)) die("git-upload-pack: protocol error, " "expected to get sha, not '%s'", line); if (strstr(line+45, "multi_ack")) @@ -440,7 +429,7 @@ static int receive_needs(void) die("git-upload-pack: not our ref %s", line+5); if (!(o->flags & WANTED)) { o->flags |= WANTED; - needs++; + add_object_array(o, NULL, &want_obj); } } } @@ -470,18 +459,17 @@ static int send_ref(const char *refname, const unsigned char *sha1) return 0; } -static int upload_pack(void) +static void upload_pack(void) { reset_timeout(); head_ref(send_ref); for_each_ref(send_ref); packet_flush(1); - nr_needs = receive_needs(); - if (!nr_needs) - return 0; - get_common_commits(); - create_pack_file(); - return 0; + receive_needs(); + if (want_obj.nr) { + get_common_commits(); + create_pack_file(); + } } int main(int argc, char **argv) diff --git a/write_or_die.c b/write_or_die.c new file mode 100644 index 000000000..ab4cb8a69 --- /dev/null +++ b/write_or_die.c @@ -0,0 +1,20 @@ +#include "cache.h" + +void write_or_die(int fd, const void *buf, size_t count) +{ + const char *p = buf; + ssize_t written; + + while (count > 0) { + written = xwrite(fd, p, count); + if (written == 0) + die("disk full?"); + else if (written < 0) { + if (errno == EPIPE) + exit(0); + die("write error (%s)", strerror(errno)); + } + count -= written; + p += written; + } +} diff --git a/xdiff-interface.c b/xdiff-interface.c index 6a82da73b..08602f522 100644 --- a/xdiff-interface.c +++ b/xdiff-interface.c @@ -69,9 +69,9 @@ int xdiff_outf(void *priv_, mmbuffer_t *mb, int nbuf) for (i = 0; i < nbuf; i++) { if (mb[i].ptr[mb[i].size-1] != '\n') { /* Incomplete line */ - priv->remainder = realloc(priv->remainder, - priv->remainder_size + - mb[i].size); + priv->remainder = xrealloc(priv->remainder, + priv->remainder_size + + mb[i].size); memcpy(priv->remainder + priv->remainder_size, mb[i].ptr, mb[i].size); priv->remainder_size += mb[i].size; @@ -83,9 +83,9 @@ int xdiff_outf(void *priv_, mmbuffer_t *mb, int nbuf) consume_one(priv, mb[i].ptr, mb[i].size); continue; } - priv->remainder = realloc(priv->remainder, - priv->remainder_size + - mb[i].size); + priv->remainder = xrealloc(priv->remainder, + priv->remainder_size + + mb[i].size); memcpy(priv->remainder + priv->remainder_size, mb[i].ptr, mb[i].size); consume_one(priv, priv->remainder, |