diff options
182 files changed, 9825 insertions, 3302 deletions
diff --git a/.gitignore b/.gitignore index 55cd9844d6..a3d9c7a11d 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ git-apply git-applymbox git-applypatch git-archimport +git-archive git-bisect git-branch git-cat-file @@ -62,6 +63,7 @@ git-merge-tree git-merge-octopus git-merge-one-file git-merge-ours +git-merge-recur git-merge-recursive git-merge-resolve git-merge-stupid @@ -93,6 +95,7 @@ git-rev-list git-rev-parse git-revert git-rm +git-runstatus git-send-email git-send-pack git-sh-setup @@ -117,6 +120,7 @@ git-unpack-objects git-update-index git-update-ref git-update-server-info +git-upload-archive git-upload-pack git-upload-tar git-var @@ -124,6 +128,7 @@ git-verify-pack git-verify-tag git-whatchanged git-write-tree +git-zip-tree git-core-*/?* gitweb/gitweb.cgi test-date @@ -139,6 +144,7 @@ git-core.spec *.py[co] config.mak autom4te.cache +config.cache config.log config.status config.mak.autogen diff --git a/Documentation/Makefile b/Documentation/Makefile index 0d9ffb4ad9..c00f5f62b7 100644 --- a/Documentation/Makefile +++ b/Documentation/Makefile @@ -33,6 +33,8 @@ man7dir=$(mandir)/man7 INSTALL?=install +-include ../config.mak.autogen + # # Please note that there is a minor bug in asciidoc. # The version after 6.0.3 _will_ include the patch found here: @@ -105,7 +107,7 @@ WEBDOC_DEST = /pub/software/scm/git/docs $(patsubst %.txt,%.html,$(wildcard howto/*.txt)): %.html : %.txt rm -f $@+ $@ - sed -e '1,/^$$/d' $? | asciidoc -b xhtml11 - >$@+ + sed -e '1,/^$$/d' $< | asciidoc -b xhtml11 - >$@+ mv $@+ $@ install-webdoc : html diff --git a/Documentation/config.txt b/Documentation/config.txt index ce722a2db0..844cae4cf0 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -225,6 +225,20 @@ showbranch.default:: The default set of branches for gitlink:git-show-branch[1]. See gitlink:git-show-branch[1]. +status.color:: + A boolean to enable/disable color in the output of + gitlink:git-status[1]. May be set to `true` (or `always`), + `false` (or `never`) or `auto`, in which case colors are used + only when the output is to a terminal. Defaults to false. + +status.color.<slot>:: + Use customized color for status colorization. `<slot>` is + one of `header` (the header text of the status message), + `updated` (files which are updated but not committed), + `changed` (files which are changed but not updated in the index), + or `untracked` (files which are not tracked by git). The values of + these variables may be specified as in diff.color.<slot>. + tar.umask:: By default, gitlink:git-tar-tree[1] sets file and directories modes to 0666 or 0777. While this is both useful and acceptable for projects diff --git a/Documentation/core-tutorial.txt b/Documentation/core-tutorial.txt index 1185897f70..47505aa20a 100644 --- a/Documentation/core-tutorial.txt +++ b/Documentation/core-tutorial.txt @@ -1620,7 +1620,7 @@ suggested in the previous section may be new to you. You do not have to worry. git supports "shared public repository" style of cooperation you are probably more familiar with as well. -See link:cvs-migration.txt[git for CVS users] for the details. +See link:cvs-migration.html[git for CVS users] for the details. Bundling your work together --------------------------- diff --git a/Documentation/cvs-migration.txt b/Documentation/cvs-migration.txt index d2b0bd38de..6812683a16 100644 --- a/Documentation/cvs-migration.txt +++ b/Documentation/cvs-migration.txt @@ -172,7 +172,7 @@ Advanced Shared Repository Management Git allows you to specify scripts called "hooks" to be run at certain points. You can use these, for example, to send all commits to the shared -repository to a mailing list. See link:hooks.txt[Hooks used by git]. +repository to a mailing list. See link:hooks.html[Hooks used by git]. You can enforce finer grained permissions using update hooks. See link:howto/update-hook-example.txt[Controlling access to branches using diff --git a/Documentation/git-apply.txt b/Documentation/git-apply.txt index 2ff74949a7..d9137c7489 100644 --- a/Documentation/git-apply.txt +++ b/Documentation/git-apply.txt @@ -10,9 +10,10 @@ SYNOPSIS -------- [verse] 'git-apply' [--stat] [--numstat] [--summary] [--check] [--index] [--apply] - [--no-add] [--index-info] [--allow-binary-replacement] [-z] [-pNUM] - [-CNUM] [--whitespace=<nowarn|warn|error|error-all|strip>] - [<patch>...] + [--no-add] [--index-info] [--allow-binary-replacement | --binary] + [-R | --reverse] [--reject] [-z] [-pNUM] [-CNUM] [--inaccurate-eof] + [--whitespace=<nowarn|warn|error|error-all|strip>] [--exclude=PATH] + [--cached] [--verbose] [<patch>...] DESCRIPTION ----------- @@ -55,6 +56,11 @@ OPTIONS up-to-date, it is flagged as an error. This flag also causes the index file to be updated. +--cached:: + Apply a patch without touching the working tree. Instead, take the + cached data, apply the patch, and store the result in the index, + without using the working tree. This implies '--index'. + --index-info:: Newer git-diff output has embedded 'index information' for each blob to help identify the original version that @@ -62,6 +68,16 @@ OPTIONS the original version of the blob is available locally, outputs information about them to the standard output. +-R, --reverse:: + Apply the patch in reverse. + +--reject:: + For atomicity, gitlink:git-apply[1] by default fails the whole patch and + does not touch the working tree when some of the hunks + do not apply. This option makes it apply + the parts of the patch that are applicable, and leave the + rejected hunks in corresponding *.rej files. + -z:: When showing the index information, do not munge paths, but use NUL terminated machine readable format. Without @@ -79,9 +95,19 @@ OPTIONS context exist they all must match. By default no context is ever ignored. +--unidiff-zero:: + By default, gitlink:git-apply[1] expects that the patch being + applied is a unified diff with at least one line of context. + This provides good safety measures, but breaks down when + applying a diff generated with --unified=0. To bypass these + checks use '--unidiff-zero'. ++ +Note, for the reasons stated above usage of context-free patches are +discouraged. + --apply:: - If you use any of the options marked ``Turns off - "apply"'' above, git-apply reads and outputs the + If you use any of the options marked "Turns off + 'apply'" above, gitlink:git-apply[1] reads and outputs the information you asked without actually applying the patch. Give this flag after those flags to also apply the patch. @@ -93,16 +119,16 @@ OPTIONS the result with this option, which would apply the deletion part but not addition part. ---allow-binary-replacement:: - When applying a patch, which is a git-enhanced patch - that was prepared to record the pre- and post-image object - name in full, and the path being patched exactly matches - the object the patch applies to (i.e. "index" line's - pre-image object name is what is in the working tree), - and the post-image object is available in the object - database, use the post-image object as the patch - result. This allows binary files to be patched in a - very limited way. +--allow-binary-replacement, --binary:: + Historically we did not allow binary patch applied + without an explicit permission from the user, and this + flag was the way to do so. Currently we always allow binary + patch application, so this is a no-op. + +--exclude=<path-pattern>:: + Don't apply changes to files matching the given path pattern. This can + be useful when importing patchsets, where you want to exclude certain + files or directories. --whitespace=<option>:: When applying a patch, detect a new or modified line @@ -110,7 +136,7 @@ OPTIONS line that solely consists of whitespaces). By default, the command outputs warning messages and applies the patch. - When `git-apply` is used for statistics and not applying a + When gitlink:git-apply[1] is used for statistics and not applying a patch, it defaults to `nowarn`. You can use different `<option>` to control this behavior: @@ -124,6 +150,17 @@ OPTIONS * `strip` outputs warnings for a few such errors, strips out the trailing whitespaces and applies the patch. +--inacurate-eof:: + Under certain circumstances, some versions of diff do not correctly + detect a missing new-line at the end of the file. As a result, patches + created by such diff programs do not record incomplete lines + correctly. This option adds support for applying such patches by + working around this bug. + +--verbose:: + Report progress to stderr. By default, only a message about the + current patch being applied will be printed. This option will cause + additional information to be reported. Configuration ------------- diff --git a/Documentation/git-archive.txt b/Documentation/git-archive.txt new file mode 100644 index 0000000000..913528d373 --- /dev/null +++ b/Documentation/git-archive.txt @@ -0,0 +1,100 @@ +git-archive(1) +============== + +NAME +---- +git-archive - Creates a archive of the files in the named tree + + +SYNOPSIS +-------- +'git-archive' --format=<fmt> [--list] [--prefix=<prefix>/] [<extra>] + [--remote=<repo>] <tree-ish> [path...] + +DESCRIPTION +----------- +Creates an archive of the specified format containing the tree +structure for the named tree. If <prefix> is specified it is +prepended to the filenames in the archive. + +'git-archive' behaves differently when given a tree ID versus when +given a commit ID or tag ID. In the first case the current time is +used as modification time of each file in the archive. In the latter +case the commit time as recorded in the referenced commit object is +used instead. Additionally the commit ID is stored in a global +extended pax header if the tar format is used; it can be extracted +using 'git-get-tar-commit-id'. In ZIP files it is stored as a file +comment. + +OPTIONS +------- + +--format=<fmt>:: + Format of the resulting archive: 'tar', 'zip'... + +--list:: + Show all available formats. + +--prefix=<prefix>/:: + Prepend <prefix>/ to each filename in the archive. + +<extra>:: + This can be any options that the archiver backend understand. + +--remote=<repo>:: + Instead of making a tar archive from local repository, + retrieve a tar archive from a remote repository. + +<tree-ish>:: + The tree or commit to produce an archive for. + +path:: + If one or more paths are specified, include only these in the + archive, otherwise include all files and subdirectories. + +CONFIGURATION +------------- +By default, file and directories modes are set to 0666 or 0777 in tar +archives. It is possible to change this by setting the "umask" variable +in the repository configuration as follows : + +[tar] + umask = 002 ;# group friendly + +The special umask value "user" indicates that the user's current umask +will be used instead. The default value remains 0, which means world +readable/writable files and directories. + +EXAMPLES +-------- +git archive --format=tar --prefix=junk/ HEAD | (cd /var/tmp/ && tar xf -):: + + Create a tar archive that contains the contents of the + latest commit on the current branch, and extracts it in + `/var/tmp/junk` directory. + +git archive --format=tar --prefix=git-1.4.0/ v1.4.0 | gzip >git-1.4.0.tar.gz:: + + Create a compressed tarball for v1.4.0 release. + +git archive --format=tar --prefix=git-1.4.0/ v1.4.0{caret}\{tree\} | gzip >git-1.4.0.tar.gz:: + + Create a compressed tarball for v1.4.0 release, but without a + global extended pax header. + +git archive --format=zip --prefix=git-docs/ HEAD:Documentation/ > git-1.4.0-docs.zip:: + + Put everything in the current head's Documentation/ directory + into 'git-1.4.0-docs.zip', with the prefix 'git-docs/'. + +Author +------ +Written by Franck Bui-Huu and Rene Scharfe. + +Documentation +-------------- +Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>. + +GIT +--- +Part of the gitlink:git[7] suite diff --git a/Documentation/git-blame.txt b/Documentation/git-blame.txt index bfed945914..e1f89444a9 100644 --- a/Documentation/git-blame.txt +++ b/Documentation/git-blame.txt @@ -3,21 +3,38 @@ git-blame(1) NAME ---- -git-blame - Blame file lines on commits +git-blame - Show what revision and author last modified each line of a file SYNOPSIS -------- -git-blame file [options] file [revision] +'git-blame' [-c] [-l] [-t] [-S <revs-file>] [--] <file> [<rev>] DESCRIPTION ----------- -Annotates each line in the given file with information from the commit -which introduced the line. Start annotation from the given revision. + +Annotates each line in the given file with information from the revision which +last modified the line. Optionally, start annotating from the given revision. + +This report doesn't tell you anything about lines which have been deleted or +replaced; you need to use a tool such as gitlink:git-diff[1] or the "pickaxe" +interface briefly mentioned in the following paragraph. + +Apart from supporting file annotation, git also supports searching the +development history for when a code snippet occured in a change. This makes it +possible to track when a code snippet was added to a file, moved or copied +between files, and eventually deleted or replaced. It works by searching for +a text string in the diff. A small example: + +----------------------------------------------------------------------------- +$ git log --pretty=oneline -S'blame_usage' +5040f17eba15504bad66b14a645bddd9b015ebb7 blame -S <ancestry-file> +ea4c7f9bf69e781dd0cd88d2bccb2bf5cc15c9a7 git-blame: Make the output +----------------------------------------------------------------------------- OPTIONS ------- -c, --compatibility:: - Use the same output mode as git-annotate (Default: off). + Use the same output mode as gitlink:git-annotate[1] (Default: off). -l, --long:: Show long rev (Default: off). @@ -26,7 +43,7 @@ OPTIONS Show raw timestamp (Default: off). -S, --rev-file <revs-file>:: - Use revs from revs-file instead of calling git-rev-list. + Use revs from revs-file instead of calling gitlink:git-rev-list[1]. -h, --help:: Show help message. diff --git a/Documentation/git-daemon.txt b/Documentation/git-daemon.txt index 0f7d274eab..741f2c69bd 100644 --- a/Documentation/git-daemon.txt +++ b/Documentation/git-daemon.txt @@ -11,16 +11,16 @@ 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...] + [--enable=service] [--disable=service] + [--allow-override=service] [--forbid-override=service] + [--reuseaddr] [--detach] [--pid-file=file] + [--user=user [--group=group]] [directory...] DESCRIPTION ----------- A really simple TCP git daemon that normally listens on port "DEFAULT_GIT_PORT" -aka 9418. It waits for a connection, and will just execute "git-upload-pack" -when it gets one. - -It's careful in that there's a magic request-line that gives the command and -what directory to upload, and it verifies that the directory is OK. +aka 9418. It waits for a connection asking for a service, and will serve +that service if it is enabled. It verifies that the directory has the magic file "git-daemon-export-ok", and it will refuse to export any git directory that hasn't explicitly been marked @@ -28,7 +28,12 @@ for export this way (unless the '--export-all' parameter is specified). If you pass some directory paths as 'git-daemon' arguments, you can further restrict the offers to a whitelist comprising of those. -This is ideally suited for read-only updates, i.e., pulling from git repositories. +By default, only `upload-pack` service is enabled, which serves +`git-fetch-pack` and `git-peek-remote` clients that are invoked +from `git-fetch`, `git-ls-remote`, and `git-clone`. + +This is ideally suited for read-only updates, i.e., pulling from +git repositories. OPTIONS ------- @@ -93,11 +98,43 @@ OPTIONS --pid-file=file:: Save the process id in 'file'. +--user=user, --group=group:: + Change daemon's uid and gid before entering the service loop. + When only `--user` is given without `--group`, the + primary group ID for the user is used. The values of + the option are given to `getpwnam(3)` and `getgrnam(3)` + and numeric IDs are not supported. ++ +Giving these options is an error when used with `--inetd`; use +the facility of inet daemon to achieve the same before spawning +`git-daemon` if needed. + +--enable-service, --disable-service:: + Enable/disable the service site-wide per default. Note + that a service disabled site-wide can still be enabled + per repository if it is marked overridable and the + repository enables the service with an configuration + item. + +--allow-override, --forbid-override:: + Allow/forbid overriding the site-wide default with per + repository configuration. By default, all the services + are overridable. + <directory>:: A directory to add to the whitelist of allowed directories. Unless --strict-paths is specified this will also include subdirectories of each named directory. +SERVICES +-------- + +upload-pack:: + This serves `git-fetch-pack` and `git-peek-remote` + clients. It is enabled by default, but a repository can + disable it by setting `daemon.uploadpack` configuration + item to `false`. + Author ------ Written by Linus Torvalds <torvalds@osdl.org>, YOSHIFUJI Hideaki diff --git a/Documentation/git-grep.txt b/Documentation/git-grep.txt index 7545dd9a3e..d8af4d961b 100644 --- a/Documentation/git-grep.txt +++ b/Documentation/git-grep.txt @@ -11,7 +11,7 @@ SYNOPSIS [verse] 'git-grep' [--cached] [-a | --text] [-I] [-i | --ignore-case] [-w | --word-regexp] - [-v | --invert-match] [--full-name] + [-v | --invert-match] [-h|-H] [--full-name] [-E | --extended-regexp] [-G | --basic-regexp] [-F | --fixed-strings] [-n] [-l | --files-with-matches] [-L | --files-without-match] [-c | --count] @@ -47,6 +47,13 @@ OPTIONS -v | --invert-match:: Select non-matching lines. +-h | -H:: + By default, the command shows the filename for each + match. `-h` option is used to suppress this output. + `-H` is there for completeness and does not do anything + except it overrides `-h` given earlier on the command + line. + --full-name:: When run from a subdirectory, the command usually outputs paths relative to the current directory. This diff --git a/Documentation/git-ls-remote.txt b/Documentation/git-ls-remote.txt index ae4c1a250e..c8a4c5a4d9 100644 --- a/Documentation/git-ls-remote.txt +++ b/Documentation/git-ls-remote.txt @@ -3,16 +3,19 @@ git-ls-remote(1) NAME ---- -git-ls-remote - Look at references other repository has +git-ls-remote - List references in a remote repository SYNOPSIS -------- -'git-ls-remote' [--heads] [--tags] <repository> <refs>... +[verse] +'git-ls-remote' [--heads] [--tags] [-u <exec> | --upload-pack <exec>] + <repository> <refs>... DESCRIPTION ----------- -Displays the references other repository has. +Displays references available in a remote repository along with the associated +commit IDs. OPTIONS @@ -23,9 +26,16 @@ OPTIONS both, references stored in refs/heads and refs/tags are displayed. +-u <exec>, --upload-pack=<exec>:: + Specify the full path of gitlink:git-upload-pack[1] on the remote + host. This allows listing references from repositories accessed via + SSH and where the SSH deamon does not use the PATH configured by the + user. Also see the '--exec' option for gitlink:git-peek-remote[1]. + <repository>:: Location of the repository. The shorthand defined in - $GIT_DIR/branches/ can be used. + $GIT_DIR/branches/ can be used. Use "." (dot) to list references in + the local repository. <refs>...:: When unspecified, all references, after filtering done diff --git a/Documentation/git-pack-objects.txt b/Documentation/git-pack-objects.txt index 4991f88c92..d4661ddc2f 100644 --- a/Documentation/git-pack-objects.txt +++ b/Documentation/git-pack-objects.txt @@ -11,7 +11,7 @@ SYNOPSIS [verse] 'git-pack-objects' [-q] [--no-reuse-delta] [--non-empty] [--local] [--incremental] [--window=N] [--depth=N] - {--stdout | base-name} < object-list + [--revs [--unpacked | --all]*] [--stdout | base-name] < object-list DESCRIPTION @@ -56,6 +56,24 @@ base-name:: Write the pack contents (what would have been written to .pack file) out to the standard output. +--revs:: + Read the revision arguments from the standard input, instead of + individual object names. The revision arguments are processed + the same way as gitlink:git-rev-list[1] with `--objects` flag + uses its `commit` arguments to build the list of objects it + outputs. The objects on the resulting list are packed. + +--unpacked:: + This implies `--revs`. When processing the list of + revision arguments read from the standard input, limit + the objects packed to those that are not already packed. + +--all:: + This implies `--revs`. In addition to the list of + revision arguments read from the standard input, pretend + as if all refs under `$GIT_DIR/refs` are specifed to be + included. + --window and --depth:: These two options affects how the objects contained in the pack are stored using delta compression. The @@ -103,6 +121,7 @@ Documentation by Junio C Hamano See Also -------- +gitlink:git-rev-list[1] gitlink:git-repack[1] gitlink:git-prune-packed[1] diff --git a/Documentation/git-repack.txt b/Documentation/git-repack.txt index 951622774a..49f7e0a4a4 100644 --- a/Documentation/git-repack.txt +++ b/Documentation/git-repack.txt @@ -9,7 +9,7 @@ objects into pack files. SYNOPSIS -------- -'git-repack' [-a] [-d] [-f] [-l] [-n] [-q] +'git-repack' [-a] [-d] [-f] [-l] [-n] [-q] [--window=N] [--depth=N] DESCRIPTION ----------- @@ -56,6 +56,16 @@ OPTIONS Do not update the server information with `git update-server-info`. +--window=[N], --depth=[N]:: + These two options affects how the objects contained in the pack are + stored using delta compression. The objects are first internally + sorted by type, size and optionally names and compared against the + other objects within `--window` to see if using delta compression saves + space. `--depth` limits the maximum delta depth; making it too deep + affects the performance on the unpacker side, because delta data needs + to be applied that many times to get to the necessary object. + + Author ------ Written by Linus Torvalds <torvalds@osdl.org> diff --git a/Documentation/git-rev-list.txt b/Documentation/git-rev-list.txt index dd9fff16d3..28966adbbc 100644 --- a/Documentation/git-rev-list.txt +++ b/Documentation/git-rev-list.txt @@ -17,6 +17,7 @@ SYNOPSIS [ \--remove-empty ] [ \--not ] [ \--all ] + [ \--stdin ] [ \--topo-order ] [ \--parents ] [ [\--objects | \--objects-edge] [ \--unpacked ] ] @@ -27,116 +28,248 @@ SYNOPSIS DESCRIPTION ----------- + Lists commit objects in reverse chronological order starting at the given commit(s), taking ancestry relationship into account. This is useful to produce human-readable log output. -Commits which are stated with a preceding '{caret}' cause listing to stop at -that point. Their parents are implied. "git-rev-list foo bar {caret}baz" thus +Commits which are stated with a preceding '{caret}' cause listing to +stop at that point. Their parents are implied. Thus the following +command: + +----------------------------------------------------------------------- + $ git-rev-list foo bar ^baz +----------------------------------------------------------------------- + means "list all the commits which are included in 'foo' and 'bar', but not in 'baz'". -A special notation <commit1>..<commit2> can be used as a -short-hand for {caret}<commit1> <commit2>. +A special notation "'<commit1>'..'<commit2>'" can be used as a +short-hand for "{caret}'<commit1>' '<commit2>'". For example, either of +the following may be used interchangeably: -Another special notation is <commit1>...<commit2> which is useful for -merges. The resulting set of commits is the symmetric difference +----------------------------------------------------------------------- + $ git-rev-list origin..HEAD + $ git-rev-list HEAD ^origin +----------------------------------------------------------------------- + +Another special notation is "'<commit1>'...'<commit2>'" which is useful +for merges. The resulting set of commits is the symmetric difference between the two operands. The following two commands are equivalent: ------------- -$ git-rev-list A B --not $(git-merge-base --all A B) -$ git-rev-list A...B ------------- +----------------------------------------------------------------------- + $ git-rev-list A B --not $(git-merge-base --all A B) + $ git-rev-list A...B +----------------------------------------------------------------------- + +gitlink:git-rev-list[1] is a very essential git program, since it +provides the ability to build and traverse commit ancestry graphs. For +this reason, it has a lot of different options that enables it to be +used by commands as different as gitlink:git-bisect[1] and +gitlink:git-repack[1]. OPTIONS ------- ---pretty:: - Print the contents of the commit changesets in human-readable form. + +Commit Formatting +~~~~~~~~~~~~~~~~~ + +Using these options, gitlink:git-rev-list[1] will act similar to the +more specialized family of commit log tools: gitlink:git-log[1], +gitlink:git-show[1], and gitlink:git-whatchanged[1] + +--pretty[='<format>']:: + + Pretty print the contents of the commit logs in a given format, + where '<format>' can be one of 'raw', 'medium', 'short', 'full', + and 'oneline'. When left out the format default to 'medium'. + +--relative-date:: + + Show dates relative to the current time, e.g. "2 hours ago". + Only takes effect for dates shown in human-readable format, such + as when using "--pretty". --header:: - Print the contents of the commit in raw-format; each - record is separated with a NUL character. + + Print the contents of the commit in raw-format; each record is + separated with a NUL character. --parents:: + Print the parents of the commit. ---objects:: - Print the object IDs of any object referenced by the listed commits. - 'git-rev-list --objects foo ^bar' thus means "send me all object IDs - which I need to download if I have the commit object 'bar', but - not 'foo'". +Diff Formatting +~~~~~~~~~~~~~~~ ---objects-edge:: - Similar to `--objects`, but also print the IDs of - excluded commits prefixed with a `-` character. This is - used by `git-pack-objects` to build 'thin' pack, which - records objects in deltified form based on objects - contained in these excluded commits to reduce network - traffic. +Below are listed options that control the formatting of diff output. +Some of them are specific to gitlink:git-rev-list[1], however other diff +options may be given. See gitlink:git-diff-files[1] for more options. ---unpacked:: - Only useful with `--objects`; print the object IDs that - are not in packs. +-c:: + + This flag changes the way a merge commit is displayed. It shows + the differences from each of the parents to the merge result + simultaneously instead of showing pairwise diff between a parent + and the result one at a time. Furthermore, it lists only files + which were modified from all parents. + +--cc:: + + This flag implies the '-c' options and further compresses the + patch output by omitting hunks that show differences from only + one parent, or show the same change from all but one parent for + an Octopus merge. + +-r:: + + Show recursive diffs. + +-t:: + + Show the tree objects in the diff output. This implies '-r'. + +Commit Limiting +~~~~~~~~~~~~~~~ + +Besides specifying a range of commits that should be listed using the +special notations explained in the description, additional commit +limiting may be applied. + +-- + +-n 'number', --max-count='number':: ---bisect:: - Limit output to the one commit object which is roughly halfway - between the included and excluded commits. Thus, if 'git-rev-list - --bisect foo {caret}bar {caret}baz' outputs 'midpoint', the output - of 'git-rev-list foo {caret}midpoint' and 'git-rev-list midpoint - {caret}bar {caret}baz' would be of roughly the same length. - Finding the change - which introduces a regression is thus reduced to a binary search: - repeatedly generate and test new 'midpoint's until the commit chain - is of length one. - ---max-count:: Limit the number of commits output. ---max-age=timestamp, --min-age=timestamp:: - Limit the commits output to specified time range. +--since='date', --after='date':: + + Show commits more recent than a specific date. + +--until='date', --before='date':: ---sparse:: - When optional paths are given, the command outputs only - the commits that changes at least one of them, and also - ignores merges that do not touch the given paths. This - flag makes the command output all eligible commits - (still subject to count and age limitation), but apply - merge simplification nevertheless. + Show commits older than a specific date. + +--max-age='timestamp', --min-age='timestamp':: + + Limit the commits output to specified time range. --remove-empty:: + Stop when a given path disappears from the tree. --no-merges:: + Do not print commits with more than one parent. --not:: - Reverses the meaning of the '{caret}' prefix (or lack - thereof) for all following revision specifiers, up to - the next `--not`. + + Reverses the meaning of the '{caret}' prefix (or lack thereof) + for all following revision specifiers, up to the next '--not'. --all:: - Pretend as if all the refs in `$GIT_DIR/refs/` are - listed on the command line as <commit>. ---topo-order:: - By default, the commits are shown in reverse - chronological order. This option makes them appear in - topological order (i.e. descendant commits are shown - before their parents). + Pretend as if all the refs in `$GIT_DIR/refs/` are listed on the + command line as '<commit>'. + +--stdin:: + + In addition to the '<commit>' listed on the command + line, read them from the standard input. --merge:: + After a failed merge, show refs that touch files having a conflict and don't exist on all heads to merge. +--boundary:: + + Output uninteresting commits at the boundary, which are usually + not shown. + +--dense, --sparse:: + +When optional paths are given, the default behaviour ('--dense') is to +only output commits that changes at least one of them, and also ignore +merges that do not touch the given paths. + +Use the '--sparse' flag to makes the command output all eligible commits +(still subject to count and age limitation), but apply merge +simplification nevertheless. + +--bisect:: + +Limit output to the one commit object which is roughly halfway between +the included and excluded commits. Thus, if + +----------------------------------------------------------------------- + $ git-rev-list --bisect foo ^bar ^baz +----------------------------------------------------------------------- + +outputs 'midpoint', the output of the two commands + +----------------------------------------------------------------------- + $ git-rev-list foo ^midpoint + $ git-rev-list midpoint ^bar ^baz +----------------------------------------------------------------------- + +would be of roughly the same length. Finding the change which +introduces a regression is thus reduced to a binary search: repeatedly +generate and test new 'midpoint's until the commit chain is of length +one. + +-- + +Commit Ordering +~~~~~~~~~~~~~~~ + +By default, the commits are shown in reverse chronological order. + +--topo-order:: + + This option makes them appear in topological order (i.e. + descendant commits are shown before their parents). + +--date-order:: + + This option is similar to '--topo-order' in the sense that no + parent comes before all of its children, but otherwise things + are still ordered in the commit timestamp order. + +Object Traversal +~~~~~~~~~~~~~~~~ + +These options are mostly targeted for packing of git repositories. + +--objects:: + + Print the object IDs of any object referenced by the listed + commits. 'git-rev-list --objects foo ^bar' thus means "send me + all object IDs which I need to download if I have the commit + object 'bar', but not 'foo'". + +--objects-edge:: + + Similar to '--objects', but also print the IDs of excluded + commits prefixed with a "-" character. This is used by + gitlink:git-pack-objects[1] to build "thin" pack, which records + objects in deltified form based on objects contained in these + excluded commits to reduce network traffic. + +--unpacked:: + + Only useful with '--objects'; print the object IDs that are not + in packs. + Author ------ Written by Linus Torvalds <torvalds@osdl.org> Documentation -------------- -Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>. +Documentation by David Greaves, Junio C Hamano, Jonas Fonseca +and the git-list <git@vger.kernel.org>. GIT --- Part of the gitlink:git[7] suite - diff --git a/Documentation/git-svn.txt b/Documentation/git-svn.txt index 7d86809844..b7b63f7136 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-unpack-objects.txt b/Documentation/git-unpack-objects.txt index c20b38b08a..ff6184b0f7 100644 --- a/Documentation/git-unpack-objects.txt +++ b/Documentation/git-unpack-objects.txt @@ -8,7 +8,7 @@ git-unpack-objects - Unpack objects from a packed archive SYNOPSIS -------- -'git-unpack-objects' [-n] [-q] <pack-file +'git-unpack-objects' [-n] [-q] [-r] <pack-file DESCRIPTION @@ -34,6 +34,12 @@ OPTIONS The command usually shows percentage progress. This flag suppresses it. +-r:: + When unpacking a corrupt packfile, the command dies at + the first corruption. This flag tells it to keep going + and make the best effort to recover as many objects as + possible. + Author ------ diff --git a/Documentation/git-update-index.txt b/Documentation/git-update-index.txt index 3ae6e74573..41bb7e12e3 100644 --- a/Documentation/git-update-index.txt +++ b/Documentation/git-update-index.txt @@ -15,7 +15,7 @@ SYNOPSIS [--cacheinfo <mode> <object> <file>]\* [--chmod=(+|-)x] [--assume-unchanged | --no-assume-unchanged] - [--really-refresh] [--unresolve] [--again] + [--really-refresh] [--unresolve] [--again | -g] [--info-only] [--index-info] [-z] [--stdin] [--verbose] @@ -80,7 +80,7 @@ OPTIONS filesystem that has very slow lstat(2) system call (e.g. cifs). ---again:: +--again, -g:: Runs `git-update-index` itself on the paths whose index entries are different from those from the `HEAD` commit. diff --git a/Documentation/git-upload-archive.txt b/Documentation/git-upload-archive.txt new file mode 100644 index 0000000000..388bb53d29 --- /dev/null +++ b/Documentation/git-upload-archive.txt @@ -0,0 +1,37 @@ +git-upload-archive(1) +==================== + +NAME +---- +git-upload-archive - Send archive + + +SYNOPSIS +-------- +'git-upload-archive' <directory> + +DESCRIPTION +----------- +Invoked by 'git-archive --remote' and sends a generated archive to the +other end over the git protocol. + +This command is usually not invoked directly by the end user. The UI +for the protocol is on the 'git-archive' side, and the program pair +is meant to be used to get an archive from a remote repository. + +OPTIONS +------- +<directory>:: + The repository to get a tar archive from. + +Author +------ +Written by Franck Bui-Huu. + +Documentation +-------------- +Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>. + +GIT +--- +Part of the gitlink:git[7] suite diff --git a/Documentation/git-zip-tree.txt b/Documentation/git-zip-tree.txt new file mode 100644 index 0000000000..2e9d981247 --- /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 3de5fa9c82..744c38dee3 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 ----------- @@ -302,6 +303,9 @@ gitlink:git-format-patch[1]:: gitlink:git-grep[1]:: Print lines matching a pattern. +gitlink:gitk[1]:: + The git repository browser. + gitlink:git-log[1]:: Shows commit logs. @@ -482,13 +486,6 @@ gitlink:git-stripspace[1]:: Filter out empty lines. -Commands not yet documented ---------------------------- - -gitlink:gitk[1]:: - The gitk repository browser. - - Configuration Mechanism ----------------------- @@ -637,9 +634,18 @@ other This environment variable overrides `$PAGER`. 'GIT_TRACE':: - If this variable is set git will print `trace:` messages on + If this variable is set to "1", "2" or "true" (comparison + is case insensitive), git will print `trace:` messages on stderr telling about alias expansion, built-in command execution and external command execution. + If this variable is set to an integer value greater than 1 + and lower than 10 (strictly) then git will interpret this + value as an open file descriptor and will try to write the + trace messages into this file descriptor. + Alternatively, if this variable is set to an absolute path + (starting with a '/' character), git will interpret this + as a file path and will try to write the trace messages + into it. Discussion[[Discussion]] ------------------------ diff --git a/Documentation/gitk.txt b/Documentation/gitk.txt index cb482bf98e..23be0050b0 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 0000000000..7597d04142 --- /dev/null +++ b/Documentation/technical/racy-git.txt @@ -0,0 +1,193 @@ +Use of index and Racy git problem +================================= + +Background +---------- + +The index is one of the most important data structure in git. +It represents a virtual working tree state by recording list of +paths and their object names and serves as a staging area to +write out the next tree object to be committed. The state is +"virtual" in the sense that it does not necessarily have to, and +often does not, match the files in the working tree. + +There are cases git needs to examine the differences between the +virtual working tree state in the index and the files in the +working tree. The most obvious case is when the user asks `git +diff` (or its low level implementation, `git diff-files`) or +`git-ls-files --modified`. In addition, git internally checks +if the files in the working tree is different from what are +recorded in the index to avoid stomping on local changes in them +during patch application, switching branches, and merging. + +In order to speed up this comparison between the files in the +working tree and the index entries, the index entries record the +information obtained from the filesystem via `lstat(2)` system +call when they were last updated. When checking if they differ, +git first runs `lstat(2)` on the files and compare the result +with this information (this is what was originally done by the +`ce_match_stat()` function, which the current code does in +`ce_match_stat_basic()` function). If some of these "cached +stat information" fields do not match, git can tell that the +files are modified without even looking at their contents. + +Note: not all members in `struct stat` obtained via `lstat(2)` +are used for this comparison. For example, `st_atime` obviously +is not useful. Currently, git compares the file type (regular +files vs symbolic links) and executable bits (only for regular +files) from `st_mode` member, `st_mtime` and `st_ctime` +timestamps, `st_uid`, `st_gid`, `st_ino`, and `st_size` members. +With a `USE_STDEV` compile-time option, `st_dev` is also +compared, but this is not enabled by default because this member +is not stable on network filesystems. With `USE_NSEC` +compile-time option, `st_mtim.tv_nsec` and `st_ctim.tv_nsec` +members are also compared, but this is not enabled by default +because the value of this member becomes meaningless once the +inode is evicted from the inode cache on filesystems that do not +store it on disk. + + +Racy git +-------- + +There is one slight problem with the optimization based on the +cached stat information. Consider this sequence: + + $ git update-index 'foo' + : modify 'foo' in-place without changing its size + +The first `update-index` computes the object name of the +contents of file `foo` and updates the index entry for `foo` +along with the `struct stat` information. If the modification +that follows it happens very fast so that the file's `st_mtime` +timestamp does not change, after this sequence, the cached stat +information the index entry records still exactly match what you +can obtain from the filesystem, but the file `foo` is modified. +This way, git can incorrectly think files in the working tree +are unmodified even though they actually are. This is called +the "racy git" problem (discovered by Pasky), and the entries +that appear clean when they may not be because of this problem +are called "racily clean". + +To avoid this problem, git does two things: + +. When the cached stat information says the file has not been + modified, and the `st_mtime` is the same as (or newer than) + the timestamp of the index file itself (which is the time `git + update-index foo` finished running in the above example), it + also compares the contents with the object registered in the + index entry to make sure they match. + +. When the index file is updated that contains racily clean + entries, cached `st_size` information is truncated to zero + before writing a new version of the index file. + +Because the index file itself is written after collecting all +the stat information from updated paths, `st_mtime` timestamp of +it is usually the same as or newer than any of the paths the +index contains. And no matter how quick the modification that +follows `git update-index foo` finishes, the resulting +`st_mtime` timestamp on `foo` cannot get the timestamp earlier +than the index file. Therefore, index entries that can be +racily clean are limited to the ones that have the same +timestamp as the index file itself. + +The callers that want to check if an index entry matches the +corresponding file in the working tree continue to call +`ce_match_stat()`, but with this change, `ce_match_stat()` uses +`ce_modified_check_fs()` to see if racily clean ones are +actually clean after comparing the cached stat information using +`ce_match_stat_basic()`. + +The problem the latter solves is this sequence: + + $ git update-index 'foo' + : modify 'foo' in-place without changing its size + : wait for enough time + $ git update-index 'bar' + +Without the latter, the timestamp of the index file gets a newer +value, and falsely clean entry `foo` would not be caught by the +timestamp comparison check done with the former logic anymore. +The latter makes sure that the cached stat information for `foo` +would never match with the file in the working tree, so later +checks by `ce_match_stat_basic()` would report the index entry +does not match the file and git does not have to fall back on more +expensive `ce_modified_check_fs()`. + + +Runtime penalty +--------------- + +The runtime penalty of falling back to `ce_modified_check_fs()` +from `ce_match_stat()` can be very expensive when there are many +racily clean entries. An obvious way to artificially create +this situation is to give the same timestamp to all the files in +the working tree in a large project, run `git update-index` on +them, and give the same timestamp to the index file: + + $ date >.datestamp + $ git ls-files | xargs touch -r .datestamp + $ git ls-files | git update-index --stdin + $ touch -r .datestamp .git/index + +This will make all index entries racily clean. The linux-2.6 +project, for example, there are over 20,000 files in the working +tree. On my Athron 64X2 3800+, after the above: + + $ /usr/bin/time git diff-files + 1.68user 0.54system 0:02.22elapsed 100%CPU (0avgtext+0avgdata 0maxresident)k + 0inputs+0outputs (0major+67111minor)pagefaults 0swaps + $ git update-index MAINTAINERS + $ /usr/bin/time git diff-files + 0.02user 0.12system 0:00.14elapsed 100%CPU (0avgtext+0avgdata 0maxresident)k + 0inputs+0outputs (0major+935minor)pagefaults 0swaps + +Running `git update-index` in the middle checked the racily +clean entries, and left the cached `st_mtime` for all the paths +intact because they were actually clean (so this step took about +the same amount of time as the first `git diff-files`). After +that, they are not racily clean anymore but are truly clean, so +the second invocation of `git diff-files` fully took advantage +of the cached stat information. + + +Avoiding runtime penalty +------------------------ + +In order to avoid the above runtime penalty, the recent "master" +branch (post 1.4.2) has a code that makes sure the index file +gets timestamp newer than the youngest files in the index when +there are many young files with the same timestamp as the +resulting index file would otherwise would have by waiting +before finishing writing the index file out. + +I suspect that in practice the situation where many paths in the +index are all racily clean is quite rare. The only code paths +that can record recent timestamp for large number of paths I +know of are: + +. Initial `git add .` of a large project. + +. `git checkout` of a large project from an empty index into an + unpopulated working tree. + +Note: switching branches with `git checkout` keeps the cached +stat information of existing working tree files that are the +same between the current branch and the new branch, which are +all older than the resulting index file, and they will not +become racily clean. Only the files that are actually checked +out can become racily clean. + +In a large project where raciness avoidance cost really matters, +however, the initial computation of all object names in the +index takes more than one second, and the index file is written +out after all that happens. Therefore the timestamp of the +index file will be more than one seconds later than the the +youngest file in the working tree. This means that in these +cases there actually will not be any racily clean entry in +the resulting index. + +So in summary I think we should not worry about avoiding the +runtime penalty and get rid of the "wait before finishing +writing" code out. diff --git a/Documentation/tutorial-2.txt b/Documentation/tutorial-2.txt index 2f4fe1217a..42b6e7d7d2 100644 --- a/Documentation/tutorial-2.txt +++ b/Documentation/tutorial-2.txt @@ -368,7 +368,7 @@ in the index file is identical to the one in the working directory. In addition to being the staging area for new commits, the index file is also populated from the object database when checking out a branch, and is used to hold the trees involved in a merge operation. -See the link:core-tutorial.txt[core tutorial] and the relevant man +See the link:core-tutorial.html[core tutorial] and the relevant man pages for details. What next? @@ -123,12 +123,17 @@ GIT_PYTHON_DIR = $(prefix)/share/git-core/python # default configuration for gitweb GITWEB_CONFIG = gitweb_config.perl +GITWEB_HOME_LINK_STR = projects GITWEB_SITENAME = GITWEB_PROJECTROOT = /pub/git +GITWEB_EXPORT_OK = +GITWEB_STRICT_EXPORT = +GITWEB_BASE_URL = GITWEB_LIST = GITWEB_HOMETEXT = indextext.html GITWEB_CSS = gitweb.css GITWEB_LOGO = git-logo.png +GITWEB_FAVICON = git-favicon.png export prefix bindir gitexecdir template_dir GIT_PYTHON_DIR @@ -193,7 +198,12 @@ PROGRAMS = \ git-update-server-info$X \ git-upload-pack$X git-verify-pack$X \ git-pack-redundant$X git-var$X \ - git-describe$X git-merge-tree$X git-blame$X git-imap-send$X + git-describe$X git-merge-tree$X git-blame$X git-imap-send$X \ + git-merge-recur$X \ + $(EXTRA_PROGRAMS) + +# Empty... +EXTRA_PROGRAMS = BUILT_INS = \ git-format-patch$X git-show$X git-whatchanged$X \ @@ -224,8 +234,8 @@ LIB_FILE=libgit.a XDIFF_LIB=xdiff/lib.a LIB_H = \ - blob.h cache.h commit.h csum-file.h delta.h \ - diff.h object.h pack.h pkt-line.h quote.h refs.h \ + archive.h blob.h cache.h commit.h csum-file.h delta.h \ + diff.h object.h pack.h pkt-line.h quote.h refs.h list-objects.h sideband.h \ run-command.h strbuf.h tag.h tree.h git-compat-util.h revision.h \ tree-walk.h log-tree.h dir.h path-list.h unpack-trees.h builtin.h @@ -237,16 +247,19 @@ DIFF_OBJS = \ LIB_OBJS = \ blob.o commit.o connect.o csum-file.o cache-tree.o base85.o \ date.o diff-delta.o entry.o exec_cmd.o ident.o lockfile.o \ - object.o pack-check.o patch-delta.o path.o pkt-line.o \ + object.o pack-check.o patch-delta.o path.o pkt-line.o sideband.o \ quote.o read-cache.o refs.o run-command.o dir.o object-refs.o \ server-info.o setup.o sha1_file.o sha1_name.o strbuf.o \ tag.o tree.o usage.o config.o environment.o ctype.o copy.o \ fetch-clone.o revision.o pager.o tree-walk.o xdiff-interface.o \ - alloc.o merge-file.o path-list.o help.o unpack-trees.o $(DIFF_OBJS) + write_or_die.o trace.o list-objects.o \ + alloc.o merge-file.o path-list.o help.o unpack-trees.o $(DIFF_OBJS) \ + color.o wt-status.o BUILTIN_OBJS = \ builtin-add.o \ builtin-apply.o \ + builtin-archive.o \ builtin-cat-file.o \ builtin-checkout-index.o \ builtin-check-ref-format.o \ @@ -276,6 +289,7 @@ BUILTIN_OBJS = \ builtin-rev-list.o \ builtin-rev-parse.o \ builtin-rm.o \ + builtin-runstatus.o \ builtin-show-branch.o \ builtin-stripspace.o \ builtin-symbolic-ref.o \ @@ -283,9 +297,11 @@ BUILTIN_OBJS = \ builtin-unpack-objects.o \ builtin-update-index.o \ builtin-update-ref.o \ + builtin-upload-archive.o \ builtin-upload-tar.o \ builtin-verify-pack.o \ - builtin-write-tree.o + builtin-write-tree.o \ + builtin-zip-tree.o GITLIBS = $(LIB_FILE) $(XDIFF_LIB) LIBS = $(GITLIBS) -lz @@ -326,15 +342,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 @@ -486,7 +503,7 @@ ifdef NO_SETENV COMPAT_CFLAGS += -DNO_SETENV COMPAT_OBJS += compat/setenv.o endif -ifdef NO_SETENV +ifdef NO_UNSETENV COMPAT_CFLAGS += -DNO_UNSETENV COMPAT_OBJS += compat/unsetenv.o endif @@ -617,12 +634,17 @@ gitweb/gitweb.cgi: gitweb/gitweb.perl -e 's|++GIT_VERSION++|$(GIT_VERSION)|g' \ -e 's|++GIT_BINDIR++|$(bindir)|g' \ -e 's|++GITWEB_CONFIG++|$(GITWEB_CONFIG)|g' \ + -e 's|++GITWEB_HOME_LINK_STR++|$(GITWEB_HOME_LINK_STR)|g' \ -e 's|++GITWEB_SITENAME++|$(GITWEB_SITENAME)|g' \ -e 's|++GITWEB_PROJECTROOT++|$(GITWEB_PROJECTROOT)|g' \ + -e 's|++GITWEB_EXPORT_OK++|$(GITWEB_EXPORT_OK)|g' \ + -e 's|++GITWEB_STRICT_EXPORT++|$(GITWEB_STRICT_EXPORT)|g' \ + -e 's|++GITWEB_BASE_URL++|$(GITWEB_BASE_URL)|g' \ -e 's|++GITWEB_LIST++|$(GITWEB_LIST)|g' \ -e 's|++GITWEB_HOMETEXT++|$(GITWEB_HOMETEXT)|g' \ -e 's|++GITWEB_CSS++|$(GITWEB_CSS)|g' \ -e 's|++GITWEB_LOGO++|$(GITWEB_LOGO)|g' \ + -e 's|++GITWEB_FAVICON++|$(GITWEB_FAVICON)|g' \ $< >$@+ chmod +x $@+ mv $@+ $@ @@ -700,6 +722,11 @@ git-http-push$X: revision.o http.o http-push.o $(GITLIBS) $(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \ $(LIBS) $(CURL_LIBCURL) $(EXPAT_LIBEXPAT) +merge-recursive.o path-list.o: path-list.h +git-merge-recur$X: merge-recursive.o path-list.o $(GITLIBS) + $(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \ + $(LIBS) + $(LIB_OBJS) $(BUILTIN_OBJS): $(LIB_H) $(patsubst git-%$X,%.o,$(PROGRAMS)): $(LIB_H) $(wildcard */*.h) $(DIFF_OBJS): diffcore.h @@ -859,7 +886,7 @@ check-docs:: do \ case "$$v" in \ git-merge-octopus | git-merge-ours | git-merge-recursive | \ - git-merge-resolve | git-merge-stupid | \ + git-merge-resolve | git-merge-stupid | git-merge-recur | \ git-ssh-pull | git-ssh-push ) continue ;; \ esac ; \ test -f "Documentation/$$v.txt" || \ diff --git a/archive.h b/archive.h new file mode 100644 index 0000000000..16dcdb875c --- /dev/null +++ b/archive.h @@ -0,0 +1,47 @@ +#ifndef ARCHIVE_H +#define ARCHIVE_H + +#define MAX_EXTRA_ARGS 32 +#define MAX_ARGS (MAX_EXTRA_ARGS + 32) + +struct archiver_args { + const char *base; + struct tree *tree; + const unsigned char *commit_sha1; + time_t time; + const char **pathspec; + unsigned int verbose : 1; + void *extra; +}; + +typedef int (*write_archive_fn_t)(struct archiver_args *); + +typedef void *(*parse_extra_args_fn_t)(int argc, const char **argv); + +struct archiver { + const char *name; + struct archiver_args args; + write_archive_fn_t write_archive; + parse_extra_args_fn_t parse_extra; +}; + +extern struct archiver archivers[]; + +extern int parse_archive_args(int argc, + const char **argv, + struct archiver *ar); + +extern void parse_treeish_arg(const char **treeish, + struct archiver_args *ar_args, + const char *prefix); + +extern void parse_pathspec_arg(const char **pathspec, + struct archiver_args *args); +/* + * Archive-format specific backends. + */ +extern int write_tar_archive(struct archiver_args *); +extern int write_zip_archive(struct archiver_args *); +extern void *parse_extra_zip_args(int argc, const char **argv); + +#endif /* ARCHIVE_H */ @@ -56,9 +56,9 @@ struct patch { static void get_blob(struct commit *commit); /* Only used for statistics */ -static int num_get_patch = 0; -static int num_commits = 0; -static int patch_time = 0; +static int num_get_patch; +static int num_commits; +static int patch_time; struct blame_diff_state { struct xdiff_emit_state xm; @@ -165,7 +165,7 @@ static int get_blob_sha1(struct tree *t, const char *pathname, blame_file = pathname; pathspec[0] = pathname; pathspec[1] = NULL; - memset(blob_sha1, 0, sizeof(blob_sha1)); + hashclr(blob_sha1); read_tree_recursive(t, "", 0, 0, pathspec, get_blob_sha1_internal); for (i = 0; i < 20; i++) { @@ -176,7 +176,7 @@ static int get_blob_sha1(struct tree *t, const char *pathname, if (i == 20) return -1; - memcpy(sha1, blob_sha1, 20); + hashcpy(sha1, blob_sha1); return 0; } @@ -191,7 +191,7 @@ static int get_blob_sha1_internal(const unsigned char *sha1, const char *base, strcmp(blame_file + baselen, pathname)) return -1; - memcpy(blob_sha1, sha1, 20); + hashcpy(blob_sha1, sha1); return -1; } @@ -351,10 +351,7 @@ static int fill_util_info(struct commit *commit) assert(util); assert(util->pathname); - if (get_blob_sha1(commit->tree, util->pathname, util->sha1)) - return 1; - else - return 0; + return !!get_blob_sha1(commit->tree, util->pathname, util->sha1); } static void alloc_line_map(struct commit *commit) @@ -620,7 +617,7 @@ static void simplify_commit(struct rev_info *revs, struct commit *commit) if (new_name) { struct util_info* putil = get_util(p); if (!putil->pathname) - putil->pathname = strdup(new_name); + putil->pathname = xstrdup(new_name); } else { *pp = parent->next; continue; diff --git a/builtin-add.c b/builtin-add.c index 0cb9c81200..febb75ed99 100644 --- a/builtin-add.c +++ b/builtin-add.c @@ -70,7 +70,6 @@ static void fill_directory(struct dir_struct *dir, const char **pathspec) base = ""; if (baselen) { char *common = xmalloc(baselen + 1); - common = xmalloc(baselen + 1); memcpy(common, *pathspec, baselen); common[baselen] = 0; path = base = common; diff --git a/builtin-apply.c b/builtin-apply.c index 9cf477c701..25e90d8d29 100644 --- a/builtin-apply.c +++ b/builtin-apply.c @@ -27,22 +27,25 @@ static const char *prefix; static int prefix_length = -1; static int newfd = -1; +static int unidiff_zero; static int p_value = 1; -static int allow_binary_replacement = 0; -static int check_index = 0; -static int write_index = 0; -static int cached = 0; -static int diffstat = 0; -static int numstat = 0; -static int summary = 0; -static int check = 0; +static int check_index; +static int write_index; +static int cached; +static int diffstat; +static int numstat; +static int summary; +static int check; static int apply = 1; -static int no_add = 0; -static int show_index_info = 0; +static int apply_in_reverse; +static int apply_with_reject; +static int apply_verbosely; +static int no_add; +static int show_index_info; static int line_termination = '\n'; static unsigned long p_context = -1; static const char apply_usage[] = -"git-apply [--stat] [--numstat] [--summary] [--check] [--index] [--cached] [--apply] [--no-add] [--index-info] [--allow-binary-replacement] [-z] [-pNUM] [-CNUM] [--whitespace=<nowarn|warn|error|error-all|strip>] <patch>..."; +"git-apply [--stat] [--numstat] [--summary] [--check] [--index] [--cached] [--apply] [--no-add] [--index-info] [--allow-binary-replacement] [--reverse] [--reject] [--verbose] [-z] [-pNUM] [-CNUM] [--whitespace=<nowarn|warn|error|error-all|strip>] <patch>..."; static enum whitespace_eol { nowarn_whitespace, @@ -50,10 +53,10 @@ static enum whitespace_eol { error_on_whitespace, strip_whitespace, } new_whitespace = warn_on_whitespace; -static int whitespace_error = 0; +static int whitespace_error; static int squelch_whitespace_errors = 5; -static int applied_after_stripping = 0; -static const char *patch_input_file = NULL; +static int applied_after_stripping; +static const char *patch_input_file; static void parse_whitespace_option(const char *option) { @@ -108,21 +111,37 @@ static int max_change, max_len; */ static int linenr = 1; +/* + * This represents one "hunk" from a patch, starting with + * "@@ -oldpos,oldlines +newpos,newlines @@" marker. The + * patch text is pointed at by patch, and its byte length + * is stored in size. leading and trailing are the number + * of context lines. + */ struct fragment { unsigned long leading, trailing; unsigned long oldpos, oldlines; unsigned long newpos, newlines; const char *patch; int size; + int rejected; struct fragment *next; }; +/* + * When dealing with a binary patch, we reuse "leading" field + * to store the type of the binary hunk, either deflated "delta" + * or deflated "literal". + */ +#define binary_patch_method leading +#define BINARY_DELTA_DEFLATED 1 +#define BINARY_LITERAL_DEFLATED 2 + struct patch { char *new_name, *old_name, *def_name; unsigned int old_mode, new_mode; - int is_rename, is_copy, is_new, is_delete, is_binary, is_reverse; -#define BINARY_DELTA_DEFLATED 1 -#define BINARY_LITERAL_DEFLATED 2 + int is_rename, is_copy, is_new, is_delete, is_binary; + int rejected; unsigned long deflate_origlen; int lines_added, lines_deleted; int score; @@ -135,6 +154,24 @@ struct patch { struct patch *next; }; +static void say_patch_name(FILE *output, const char *pre, struct patch *patch, const char *post) +{ + fputs(pre, output); + if (patch->old_name && patch->new_name && + strcmp(patch->old_name, patch->new_name)) { + write_name_quoted(NULL, 0, patch->old_name, 1, output); + fputs(" => ", output); + write_name_quoted(NULL, 0, patch->new_name, 1, output); + } + else { + const char *n = patch->new_name; + if (!n) + n = patch->old_name; + write_name_quoted(NULL, 0, n, 1, output); + } + fputs(post, output); +} + #define CHUNKSIZE (8192) #define SLOP (16) @@ -591,9 +628,7 @@ static char *git_header_name(char *line, int llen) * form. */ for (len = 0 ; ; len++) { - char c = name[len]; - - switch (c) { + switch (name[len]) { default: continue; case '\n': @@ -820,11 +855,10 @@ static int find_header(char *line, unsigned long size, int *hdrsize, struct patc } /* - * Parse a unified diff. Note that this really needs - * to parse each fragment separately, since the only - * way to know the difference between a "---" that is - * part of a patch, and a "---" that starts the next - * patch is to look at the line counts.. + * Parse a unified diff. Note that this really needs to parse each + * fragment separately, since the only way to know the difference + * between a "---" that is part of a patch, and a "---" that starts + * the next patch is to look at the line counts.. */ static int parse_fragment(char *line, unsigned long size, struct patch *patch, struct fragment *fragment) { @@ -841,31 +875,14 @@ static int parse_fragment(char *line, unsigned long size, struct patch *patch, s leading = 0; trailing = 0; - if (patch->is_new < 0) { - patch->is_new = !oldlines; - if (!oldlines) - patch->old_name = NULL; - } - if (patch->is_delete < 0) { - patch->is_delete = !newlines; - if (!newlines) - patch->new_name = NULL; - } - - if (patch->is_new && oldlines) - return error("new file depends on old contents"); - if (patch->is_delete != !newlines) { - if (newlines) - return error("deleted file still has contents"); - fprintf(stderr, "** warning: file %s becomes empty but is not deleted\n", patch->new_name); - } - /* Parse the thing.. */ line += len; size -= len; linenr++; added = deleted = 0; - for (offset = len; size > 0; offset += len, size -= len, line += len, linenr++) { + for (offset = len; + 0 < size; + offset += len, size -= len, line += len, linenr++) { if (!oldlines && !newlines) break; len = linelen(line, size); @@ -938,12 +955,18 @@ static int parse_fragment(char *line, unsigned long size, struct patch *patch, s patch->lines_added += added; patch->lines_deleted += deleted; + + if (0 < patch->is_new && oldlines) + return error("new file depends on old contents"); + if (0 < patch->is_delete && newlines) + return error("deleted file still has contents"); return offset; } static int parse_single_patch(char *line, unsigned long size, struct patch *patch) { unsigned long offset = 0; + unsigned long oldlines = 0, newlines = 0, context = 0; struct fragment **fragp = &patch->fragments; while (size > 4 && !memcmp(line, "@@ -", 4)) { @@ -954,9 +977,11 @@ static int parse_single_patch(char *line, unsigned long size, struct patch *patc len = parse_fragment(line, size, patch, fragment); if (len <= 0) die("corrupt patch at line %d", linenr); - fragment->patch = line; fragment->size = len; + oldlines += fragment->oldlines; + newlines += fragment->newlines; + context += fragment->leading + fragment->trailing; *fragp = fragment; fragp = &fragment->next; @@ -965,6 +990,46 @@ static int parse_single_patch(char *line, unsigned long size, struct patch *patc line += len; size -= len; } + + /* + * If something was removed (i.e. we have old-lines) it cannot + * be creation, and if something was added it cannot be + * deletion. However, the reverse is not true; --unified=0 + * patches that only add are not necessarily creation even + * though they do not have any old lines, and ones that only + * delete are not necessarily deletion. + * + * Unfortunately, a real creation/deletion patch do _not_ have + * any context line by definition, so we cannot safely tell it + * apart with --unified=0 insanity. At least if the patch has + * more than one hunk it is not creation or deletion. + */ + if (patch->is_new < 0 && + (oldlines || (patch->fragments && patch->fragments->next))) + patch->is_new = 0; + if (patch->is_delete < 0 && + (newlines || (patch->fragments && patch->fragments->next))) + patch->is_delete = 0; + if (!unidiff_zero || context) { + /* If the user says the patch is not generated with + * --unified=0, or if we have seen context lines, + * then not having oldlines means the patch is creation, + * and not having newlines means the patch is deletion. + */ + if (patch->is_new < 0 && !oldlines) + patch->is_new = 1; + if (patch->is_delete < 0 && !newlines) + patch->is_delete = 1; + } + + if (0 < patch->is_new && oldlines) + die("new file %s depends on old contents", patch->new_name); + if (0 < patch->is_delete && newlines) + die("deleted file %s still has contents", patch->old_name); + if (!patch->is_delete && !newlines && context) + fprintf(stderr, "** warning: file %s becomes empty but " + "is not deleted\n", patch->new_name); + return offset; } @@ -978,51 +1043,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 +1139,78 @@ static int parse_binary(char *buffer, unsigned long size, struct patch *patch) if (max_byte_length < byte_length || byte_length <= max_byte_length - 4) goto corrupt; - newsize = fragment->size + byte_length; + newsize = hunk_size + byte_length; data = xrealloc(data, newsize); - if (decode_85(data + fragment->size, - buffer + 1, - byte_length)) + if (decode_85(data + hunk_size, buffer + 1, byte_length)) goto corrupt; - fragment->size = newsize; + hunk_size = newsize; buffer += llen; size -= llen; } - fragment->patch = data; - return used; + + frag = xcalloc(1, sizeof(*frag)); + frag->patch = inflate_it(data, hunk_size, origlen); + if (!frag->patch) + goto corrupt; + free(data); + frag->size = origlen; + *buf_p = buffer; + *sz_p = size; + *used_p = used; + frag->binary_patch_method = patch_method; + return frag; + corrupt: - return error("corrupt binary patch at line %d: %.*s", - linenr-1, llen-1, buffer); + free(data); + *status_p = -1; + error("corrupt binary patch at line %d: %.*s", + linenr-1, llen-1, buffer); + return NULL; +} + +static int parse_binary(char *buffer, unsigned long size, struct patch *patch) +{ + /* We have read "GIT binary patch\n"; what follows is a line + * that says the patch method (currently, either "literal" or + * "delta") and the length of data before deflating; a + * sequence of 'length-byte' followed by base-85 encoded data + * follows. + * + * When a binary patch is reversible, there is another binary + * hunk in the same format, starting with patch method (either + * "literal" or "delta") with the length of data, and a sequence + * of length-byte + base-85 encoded data, terminated with another + * empty line. This data, when applied to the postimage, produces + * the preimage. + */ + struct fragment *forward; + struct fragment *reverse; + int status; + int used, used_1; + + forward = parse_binary_hunk(&buffer, &size, &status, &used); + if (!forward && !status) + /* there has to be one hunk (forward hunk) */ + return error("unrecognized binary patch at line %d", linenr-1); + if (status) + /* otherwise we already gave an error message */ + return status; + + reverse = parse_binary_hunk(&buffer, &size, &status, &used_1); + if (reverse) + used += used_1; + else if (status) { + /* not having reverse hunk is not an error, but having + * a corrupt reverse hunk is. + */ + free((void*) forward->patch); + free(forward); + return status; + } + forward->next = reverse; + patch->fragments = forward; + patch->is_binary = 1; + return used; } static int parse_chunk(char *buffer, unsigned long size, struct patch *patch) @@ -1105,14 +1258,12 @@ static int parse_chunk(char *buffer, unsigned long size, struct patch *patch) } } - /* Empty patch cannot be applied if: - * - it is a binary patch and we do not do binary_replace, or - * - text patch without metadata change + /* Empty patch cannot be applied if it is a text patch + * without metadata change. A binary patch appears + * empty to us here. */ if ((apply || check) && - (patch->is_binary - ? !allow_binary_replacement - : !metadata_changes(patch))) + (!patch->is_binary && !metadata_changes(patch))) die("patch with only garbage at line %d", linenr); } @@ -1143,7 +1294,6 @@ static void reverse_patches(struct patch *p) swap(frag->newpos, frag->oldpos); swap(frag->newlines, frag->oldlines); } - p->is_reverse = !p->is_reverse; } } @@ -1206,8 +1356,7 @@ static void show_stats(struct patch *patch) printf(" %s%-*s |%5d %.*s%.*s\n", prefix, len, name, patch->lines_added + patch->lines_deleted, add, pluses, del, minuses); - if (qname) - free(qname); + free(qname); } static int read_old_data(struct stat *st, const char *path, void *buf, unsigned long size) @@ -1363,8 +1512,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 +1544,7 @@ static int apply_one_fragment(struct buffer_desc *desc, struct fragment *frag, if (len < size && patch[len] == '\\') plen--; first = *patch; - if (reverse) { + if (apply_in_reverse) { if (first == '-') first = '+'; else if (first == '+') @@ -1439,14 +1587,25 @@ static int apply_one_fragment(struct buffer_desc *desc, struct fragment *frag, /* * If we don't have any leading/trailing data in the patch, * we want it to match at the beginning/end of the file. + * + * But that would break if the patch is generated with + * --unified=0; sane people wouldn't do that to cause us + * trouble, but we try to please not so sane ones as well. */ - match_beginning = !leading && (frag->oldpos == 1); - match_end = !trailing; + if (unidiff_zero) { + match_beginning = (!leading && !frag->oldpos); + match_end = 0; + } + else { + match_beginning = !leading && (frag->oldpos == 1); + match_end = !trailing; + } lines = 0; pos = frag->newpos; for (;;) { - offset = find_offset(buf, desc->size, oldlines, oldsize, pos, &lines); + offset = find_offset(buf, desc->size, + oldlines, oldsize, pos, &lines); if (match_end && offset + oldsize != desc->size) offset = -1; if (match_beginning && offset) @@ -1459,8 +1618,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 +1631,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 +1669,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 +1676,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) @@ -1574,11 +1714,6 @@ static int apply_binary(struct buffer_desc *desc, struct patch *patch) unsigned char hdr[50]; int hdrlen; - if (!allow_binary_replacement) - return error("cannot apply binary patch to '%s' " - "without --allow-binary-replacement", - name); - /* For safety, we require patch index line to contain * full 40-byte textual SHA1 for old and new, at least for now. */ @@ -1609,7 +1744,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 +1777,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 +1792,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 +1833,9 @@ static int apply_data(struct patch *patch, struct stat *st, struct cache_entry * desc.size = size; desc.alloc = alloc; desc.buffer = buf; + if (apply_fragments(&desc, patch) < 0) - return -1; + return -1; /* note with --reject this succeeds. */ /* NUL terminate the result */ if (desc.alloc <= desc.size) @@ -1707,7 +1845,7 @@ static int apply_data(struct patch *patch, struct stat *st, struct cache_entry * patch->result = desc.buffer; patch->resultsize = desc.size; - if (patch->is_delete && patch->resultsize) + if (0 < patch->is_delete && patch->resultsize) return error("removal patch leaves file contents"); return 0; @@ -1722,6 +1860,7 @@ static int check_patch(struct patch *patch, struct patch *prev_patch) struct cache_entry *ce = NULL; int ok_if_exists; + patch->rejected = 1; /* we will drop this after we succeed */ if (old_name) { int changed = 0; int stat_ret = 0; @@ -1778,7 +1917,7 @@ static int check_patch(struct patch *patch, struct patch *prev_patch) old_name, st_mode, patch->old_mode); } - if (new_name && prev_patch && prev_patch->is_delete && + if (new_name && prev_patch && 0 < prev_patch->is_delete && !strcmp(prev_patch->old_name, new_name)) /* A type-change diff is always split into a patch to * delete old, immediately followed by a patch to @@ -1791,7 +1930,8 @@ static int check_patch(struct patch *patch, struct patch *prev_patch) else ok_if_exists = 0; - if (new_name && (patch->is_new | patch->is_rename | patch->is_copy)) { + if (new_name && + ((0 < patch->is_new) | (0 < patch->is_rename) | patch->is_copy)) { if (check_index && cache_name_pos(new_name, strlen(new_name)) >= 0 && !ok_if_exists) @@ -1808,7 +1948,7 @@ static int check_patch(struct patch *patch, struct patch *prev_patch) return error("%s: %s", new_name, strerror(errno)); } if (!patch->new_mode) { - if (patch->is_new) + if (0 < patch->is_new) patch->new_mode = S_IFREG | 0644; else patch->new_mode = patch->old_mode; @@ -1827,24 +1967,23 @@ static int check_patch(struct patch *patch, struct patch *prev_patch) if (apply_data(patch, &st, ce) < 0) return error("%s: patch does not apply", name); + patch->rejected = 0; return 0; } static int check_patch_list(struct patch *patch) { struct patch *prev_patch = NULL; - int error = 0; + int err = 0; for (prev_patch = NULL; patch ; patch = patch->next) { - error |= check_patch(patch, prev_patch); + if (apply_verbosely) + say_patch_name(stderr, + "Checking patch ", patch, "...\n"); + err |= check_patch(patch, prev_patch); prev_patch = patch; } - return error; -} - -static inline int is_null_sha1(const unsigned char *sha1) -{ - return !memcmp(sha1, null_sha1, 20); + return err; } static void show_index_list(struct patch *list) @@ -1860,7 +1999,7 @@ static void show_index_list(struct patch *list) const char *name; name = patch->old_name ? patch->old_name : patch->new_name; - if (patch->is_new) + if (0 < patch->is_new) sha1_ptr = null_sha1; else if (get_sha1(patch->old_sha1_prefix, sha1)) die("sha1 information is lacking or useless (%s).", @@ -2150,23 +2289,99 @@ static void write_out_one_result(struct patch *patch, int phase) if (phase == 0) remove_file(patch); if (phase == 1) - create_file(patch); + create_file(patch); } -static void write_out_results(struct patch *list, int skipped_patch) +static int write_out_one_reject(struct patch *patch) +{ + FILE *rej; + char namebuf[PATH_MAX]; + struct fragment *frag; + int cnt = 0; + + for (cnt = 0, frag = patch->fragments; frag; frag = frag->next) { + if (!frag->rejected) + continue; + cnt++; + } + + if (!cnt) { + if (apply_verbosely) + say_patch_name(stderr, + "Applied patch ", patch, " cleanly.\n"); + return 0; + } + + /* This should not happen, because a removal patch that leaves + * contents are marked "rejected" at the patch level. + */ + if (!patch->new_name) + die("internal error"); + + /* Say this even without --verbose */ + say_patch_name(stderr, "Applying patch ", patch, " with"); + fprintf(stderr, " %d rejects...\n", cnt); + + cnt = strlen(patch->new_name); + if (ARRAY_SIZE(namebuf) <= cnt + 5) { + cnt = ARRAY_SIZE(namebuf) - 5; + fprintf(stderr, + "warning: truncating .rej filename to %.*s.rej", + cnt - 1, patch->new_name); + } + memcpy(namebuf, patch->new_name, cnt); + memcpy(namebuf + cnt, ".rej", 5); + + rej = fopen(namebuf, "w"); + if (!rej) + return error("cannot open %s: %s", namebuf, strerror(errno)); + + /* Normal git tools never deal with .rej, so do not pretend + * this is a git patch by saying --git nor give extended + * headers. While at it, maybe please "kompare" that wants + * the trailing TAB and some garbage at the end of line ;-). + */ + fprintf(rej, "diff a/%s b/%s\t(rejected hunks)\n", + patch->new_name, patch->new_name); + for (cnt = 1, frag = patch->fragments; + frag; + cnt++, frag = frag->next) { + if (!frag->rejected) { + fprintf(stderr, "Hunk #%d applied cleanly.\n", cnt); + continue; + } + fprintf(stderr, "Rejected hunk #%d.\n", cnt); + fprintf(rej, "%.*s", frag->size, frag->patch); + if (frag->patch[frag->size-1] != '\n') + fputc('\n', rej); + } + fclose(rej); + return -1; +} + +static int write_out_results(struct patch *list, int skipped_patch) { int phase; + int errs = 0; + struct patch *l; if (!list && !skipped_patch) - die("No changes"); + return error("No changes"); for (phase = 0; phase < 2; phase++) { - struct patch *l = list; + l = list; while (l) { - write_out_one_result(l, phase); + if (l->rejected) + errs = 1; + else { + write_out_one_result(l, phase); + if (phase == 1 && write_out_one_reject(l)) + errs = 1; + } l = l->next; } } + return errs; } static struct lock_file lock_file; @@ -2194,8 +2409,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 +2429,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 +2456,13 @@ static int apply_patch(int fd, const char *filename, die("unable to read index file"); } - if ((check || apply) && check_patch_list(list) < 0) + if ((check || apply) && + check_patch_list(list) < 0 && + !apply_with_reject) exit(1); - if (apply) - write_out_results(list, skipped_patch); + if (apply && write_out_results(list, skipped_patch)) + exit(1); if (show_index_info) show_index_list(list); @@ -2267,7 +2483,7 @@ static int apply_patch(int fd, const char *filename, static int git_apply_config(const char *var, const char *value) { if (!strcmp(var, "apply.whitespace")) { - apply_default_whitespace = strdup(value); + apply_default_whitespace = xstrdup(value); return 0; } return git_default_config(var, value); @@ -2278,8 +2494,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 +2505,7 @@ int cmd_apply(int argc, const char **argv, const char *prefix) int fd; if (!strcmp(arg, "-")) { - apply_patch(0, "<stdin>", reverse, inaccurate_eof); + errs |= apply_patch(0, "<stdin>", inaccurate_eof); read_stdin = 0; continue; } @@ -2315,8 +2531,7 @@ int cmd_apply(int argc, const char **argv, const char *prefix) } if (!strcmp(arg, "--allow-binary-replacement") || !strcmp(arg, "--binary")) { - allow_binary_replacement = 1; - continue; + continue; /* now no-op */ } if (!strcmp(arg, "--numstat")) { apply = 0; @@ -2367,7 +2582,19 @@ int cmd_apply(int argc, const char **argv, const char *prefix) continue; } if (!strcmp(arg, "-R") || !strcmp(arg, "--reverse")) { - reverse = 1; + apply_in_reverse = 1; + continue; + } + if (!strcmp(arg, "--unidiff-zero")) { + unidiff_zero = 1; + continue; + } + if (!strcmp(arg, "--reject")) { + apply = apply_with_reject = apply_verbosely = 1; + continue; + } + if (!strcmp(arg, "--verbose")) { + apply_verbosely = 1; continue; } if (!strcmp(arg, "--inaccurate-eof")) { @@ -2390,18 +2617,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 +2657,5 @@ int cmd_apply(int argc, const char **argv, const char *prefix) die("Unable to write new index file"); } - return 0; + return !!errs; } diff --git a/builtin-archive.c b/builtin-archive.c new file mode 100644 index 0000000000..6dabdee201 --- /dev/null +++ b/builtin-archive.c @@ -0,0 +1,263 @@ +/* + * Copyright (c) 2006 Franck Bui-Huu + * Copyright (c) 2006 Rene Scharfe + */ +#include <time.h> +#include "cache.h" +#include "builtin.h" +#include "archive.h" +#include "commit.h" +#include "tree-walk.h" +#include "exec_cmd.h" +#include "pkt-line.h" +#include "sideband.h" + +static const char archive_usage[] = \ +"git-archive --format=<fmt> [--prefix=<prefix>/] [--verbose] [<extra>] <tree-ish> [path...]"; + +struct archiver archivers[] = { + { + .name = "tar", + .write_archive = write_tar_archive, + }, + { + .name = "zip", + .write_archive = write_zip_archive, + .parse_extra = parse_extra_zip_args, + }, +}; + +static int run_remote_archiver(const char *remote, int argc, + const char **argv) +{ + char *url, buf[LARGE_PACKET_MAX]; + int fd[2], i, len, rv; + pid_t pid; + const char *exec = "git-upload-archive"; + int exec_at = 0; + + for (i = 1; i < argc; i++) { + const char *arg = argv[i]; + if (!strncmp("--exec=", arg, 7)) { + if (exec_at) + die("multiple --exec specified"); + exec = arg + 7; + exec_at = i; + break; + } + } + + url = xstrdup(remote); + pid = git_connect(fd, url, exec); + if (pid < 0) + return pid; + + for (i = 1; i < argc; i++) { + if (i == exec_at) + continue; + packet_write(fd[1], "argument %s\n", argv[i]); + } + packet_flush(fd[1]); + + len = packet_read_line(fd[0], buf, sizeof(buf)); + if (!len) + die("git-archive: expected ACK/NAK, got EOF"); + if (buf[len-1] == '\n') + buf[--len] = 0; + if (strcmp(buf, "ACK")) { + if (len > 5 && !strncmp(buf, "NACK ", 5)) + die("git-archive: NACK %s", buf + 5); + die("git-archive: protocol error"); + } + + len = packet_read_line(fd[0], buf, sizeof(buf)); + if (len) + die("git-archive: expected a flush"); + + /* Now, start reading from fd[0] and spit it out to stdout */ + rv = recv_sideband("archive", fd[0], 1, 2, buf, sizeof(buf)); + close(fd[0]); + rv |= finish_connect(pid); + + return !!rv; +} + +static int init_archiver(const char *name, struct archiver *ar) +{ + int rv = -1, i; + + for (i = 0; i < ARRAY_SIZE(archivers); i++) { + if (!strcmp(name, archivers[i].name)) { + memcpy(ar, &archivers[i], sizeof(struct archiver)); + rv = 0; + break; + } + } + return rv; +} + +void parse_pathspec_arg(const char **pathspec, struct archiver_args *ar_args) +{ + ar_args->pathspec = get_pathspec(ar_args->base, pathspec); +} + +void parse_treeish_arg(const char **argv, struct archiver_args *ar_args, + const char *prefix) +{ + const char *name = argv[0]; + const unsigned char *commit_sha1; + time_t archive_time; + struct tree *tree; + struct commit *commit; + unsigned char sha1[20]; + + if (get_sha1(name, sha1)) + die("Not a valid object name"); + + commit = lookup_commit_reference_gently(sha1, 1); + if (commit) { + commit_sha1 = commit->object.sha1; + archive_time = commit->date; + } else { + commit_sha1 = NULL; + archive_time = time(NULL); + } + + tree = parse_tree_indirect(sha1); + if (tree == NULL) + die("not a tree object"); + + if (prefix) { + unsigned char tree_sha1[20]; + unsigned int mode; + int err; + + err = get_tree_entry(tree->object.sha1, prefix, + tree_sha1, &mode); + if (err || !S_ISDIR(mode)) + die("current working directory is untracked"); + + free(tree); + tree = parse_tree_indirect(tree_sha1); + } + ar_args->tree = tree; + ar_args->commit_sha1 = commit_sha1; + ar_args->time = archive_time; +} + +int parse_archive_args(int argc, const char **argv, struct archiver *ar) +{ + const char *extra_argv[MAX_EXTRA_ARGS]; + int extra_argc = 0; + const char *format = NULL; /* might want to default to "tar" */ + const char *base = ""; + int verbose = 0; + int i; + + for (i = 1; i < argc; i++) { + const char *arg = argv[i]; + + if (!strcmp(arg, "--list") || !strcmp(arg, "-l")) { + for (i = 0; i < ARRAY_SIZE(archivers); i++) + printf("%s\n", archivers[i].name); + exit(0); + } + if (!strcmp(arg, "--verbose") || !strcmp(arg, "-v")) { + verbose = 1; + continue; + } + if (!strncmp(arg, "--format=", 9)) { + format = arg + 9; + continue; + } + if (!strncmp(arg, "--prefix=", 9)) { + base = arg + 9; + continue; + } + if (!strcmp(arg, "--")) { + i++; + break; + } + if (arg[0] == '-') { + if (extra_argc > MAX_EXTRA_ARGS - 1) + die("Too many extra options"); + extra_argv[extra_argc++] = arg; + continue; + } + break; + } + + /* We need at least one parameter -- tree-ish */ + if (argc - 1 < i) + usage(archive_usage); + if (!format) + die("You must specify an archive format"); + if (init_archiver(format, ar) < 0) + die("Unknown archive format '%s'", format); + + if (extra_argc) { + if (!ar->parse_extra) + die("'%s' format does not handle %s", + ar->name, extra_argv[0]); + ar->args.extra = ar->parse_extra(extra_argc, extra_argv); + } + ar->args.verbose = verbose; + ar->args.base = base; + + return i; +} + +static const char *extract_remote_arg(int *ac, const char **av) +{ + int ix, iy, cnt = *ac; + int no_more_options = 0; + const char *remote = NULL; + + for (ix = iy = 1; ix < cnt; ix++) { + const char *arg = av[ix]; + if (!strcmp(arg, "--")) + no_more_options = 1; + if (!no_more_options) { + if (!strncmp(arg, "--remote=", 9)) { + if (remote) + die("Multiple --remote specified"); + remote = arg + 9; + continue; + } + if (arg[0] != '-') + no_more_options = 1; + } + if (ix != iy) + av[iy] = arg; + iy++; + } + if (remote) { + av[--cnt] = NULL; + *ac = cnt; + } + return remote; +} + +int cmd_archive(int argc, const char **argv, const char *prefix) +{ + struct archiver ar; + int tree_idx; + const char *remote = NULL; + + remote = extract_remote_arg(&argc, argv); + if (remote) + return run_remote_archiver(remote, argc, argv); + + setlinebuf(stderr); + + memset(&ar, 0, sizeof(ar)); + tree_idx = parse_archive_args(argc, argv, &ar); + if (prefix == NULL) + prefix = setup_git_directory(); + + argv += tree_idx; + parse_treeish_arg(argv, &ar.args, prefix); + parse_pathspec_arg(argv + 1, &ar.args); + + return ar.write_archive(&ar.args); +} diff --git a/builtin-cat-file.c b/builtin-cat-file.c index 814fb0743f..6c16bfa1ae 100644 --- a/builtin-cat-file.c +++ b/builtin-cat-file.c @@ -9,24 +9,7 @@ #include "tree.h" #include "builtin.h" -static void flush_buffer(const char *buf, unsigned long size) -{ - while (size > 0) { - long ret = xwrite(1, buf, size); - if (ret < 0) { - /* Ignore epipe */ - if (errno == EPIPE) - break; - die("git-cat-file: %s", strerror(errno)); - } else if (!ret) { - die("git-cat-file: disk full?"); - } - size -= ret; - buf += ret; - } -} - -static int pprint_tag(const unsigned char *sha1, const char *buf, unsigned long size) +static void pprint_tag(const unsigned char *sha1, const char *buf, unsigned long size) { /* the parser in tag.c is useless here. */ const char *endp = buf + size; @@ -42,7 +25,7 @@ static int pprint_tag(const unsigned char *sha1, const char *buf, unsigned long /* Found the tagger line. Copy out the contents * of the buffer so far. */ - flush_buffer(buf, cp - buf); + write_or_die(1, buf, cp - buf); /* * Do something intelligent, like pretty-printing @@ -61,18 +44,18 @@ static int pprint_tag(const unsigned char *sha1, const char *buf, unsigned long sp++; if (sp == cp) { /* give up */ - flush_buffer(tagger, + write_or_die(1, tagger, cp - tagger); break; } while (sp < cp && !('0' <= *sp && *sp <= '9')) sp++; - flush_buffer(tagger, sp - tagger); + write_or_die(1, tagger, sp - tagger); date = strtoul(sp, &ep, 10); tz = strtol(ep, NULL, 10); - sp = show_date(date, tz); - flush_buffer(sp, strlen(sp)); + sp = show_date(date, tz, 0); + write_or_die(1, sp, strlen(sp)); xwrite(1, "\n", 1); break; } @@ -90,8 +73,7 @@ static int pprint_tag(const unsigned char *sha1, const char *buf, unsigned long * remainder as is. */ if (cp < endp) - flush_buffer(cp, endp - cp); - return 0; + write_or_die(1, cp, endp - cp); } int cmd_cat_file(int argc, const char **argv, const char *prefix) @@ -145,8 +127,10 @@ int cmd_cat_file(int argc, const char **argv, const char *prefix) buf = read_sha1_file(sha1, type, &size); if (!buf) die("Cannot read object %s", argv[2]); - if (!strcmp(type, tag_type)) - return pprint_tag(sha1, buf, size); + if (!strcmp(type, tag_type)) { + pprint_tag(sha1, buf, size); + return 0; + } /* otherwise just spit out the data */ break; @@ -161,6 +145,6 @@ int cmd_cat_file(int argc, const char **argv, const char *prefix) if (!buf) die("git-cat-file %s: bad file", argv[2]); - flush_buffer(buf, size); + write_or_die(1, buf, size); return 0; } diff --git a/builtin-checkout-index.c b/builtin-checkout-index.c index 8d0dbad49e..b097c888a0 100644 --- a/builtin-checkout-index.c +++ b/builtin-checkout-index.c @@ -45,7 +45,7 @@ static int line_termination = '\n'; static int checkout_stage; /* default to checkout stage0 */ static int to_tempfile; -static char topath[4][MAXPATHLEN+1]; +static char topath[4][PATH_MAX + 1]; static struct checkout state; @@ -122,7 +122,7 @@ static int checkout_file(const char *name, int prefix_length) return -1; } -static int checkout_all(const char *prefix, int prefix_length) +static void checkout_all(const char *prefix, int prefix_length) { int i, errs = 0; struct cache_entry* last_ce = NULL; @@ -153,7 +153,6 @@ static int checkout_all(const char *prefix, int prefix_length) * exit with the same code as die(). */ exit(128); - return 0; } static const char checkout_cache_usage[] = diff --git a/builtin-commit-tree.c b/builtin-commit-tree.c index 9c98796671..e2e690a1ae 100644 --- a/builtin-commit-tree.c +++ b/builtin-commit-tree.c @@ -69,7 +69,7 @@ static int new_parent(int idx) int i; unsigned char *sha1 = parent_sha1[idx]; for (i = 0; i < idx; i++) { - if (!memcmp(parent_sha1[i], sha1, 20)) { + if (!hashcmp(parent_sha1[i], sha1)) { error("duplicate parent %s ignored", sha1_to_hex(sha1)); return 0; } diff --git a/builtin-count-objects.c b/builtin-count-objects.c index 1d3729aa99..73c5982423 100644 --- a/builtin-count-objects.c +++ b/builtin-count-objects.c @@ -62,7 +62,7 @@ static void count_objects(DIR *d, char *path, int len, int verbose, hex[40] = 0; if (get_sha1_hex(hex, sha1)) die("internal error"); - if (has_sha1_pack(sha1)) + if (has_sha1_pack(sha1, NULL)) (*packed_loose)++; } } diff --git a/builtin-diff-stages.c b/builtin-diff-stages.c index 5960e08997..70bb89808d 100644 --- a/builtin-diff-stages.c +++ b/builtin-diff-stages.c @@ -46,7 +46,7 @@ static void diff_stages(int stage1, int stage2, const char **pathspec) else if (!two) diff_addremove(&diff_options, '-', ntohl(one->ce_mode), one->sha1, name, NULL); - else if (memcmp(one->sha1, two->sha1, 20) || + else if (hashcmp(one->sha1, two->sha1) || (one->ce_mode != two->ce_mode) || diff_options.find_copies_harder) diff_change(&diff_options, diff --git a/builtin-diff.c b/builtin-diff.c index 82afce782d..a6590205e8 100644 --- a/builtin-diff.c +++ b/builtin-diff.c @@ -68,9 +68,8 @@ static void stuff_change(struct diff_options *opt, { struct diff_filespec *one, *two; - if (memcmp(null_sha1, old_sha1, 20) && - memcmp(null_sha1, new_sha1, 20) && - !memcmp(old_sha1, new_sha1, 20)) + if (!is_null_sha1(old_sha1) && !is_null_sha1(new_sha1) && + !hashcmp(old_sha1, new_sha1)) return; if (opt->reverse_diff) { @@ -193,7 +192,7 @@ static int builtin_diff_combined(struct rev_info *revs, parent = xmalloc(ents * sizeof(*parent)); /* Again, the revs are all reverse */ for (i = 0; i < ents; i++) - memcpy(parent + i, ent[ents - 1 - i].item->sha1, 20); + hashcpy((unsigned char*)parent + i, ent[ents - 1 - i].item->sha1); diff_tree_combined(parent[0], parent + 1, ents - 1, revs->dense_combined_merges, revs); return 0; @@ -291,7 +290,7 @@ int cmd_diff(int argc, const char **argv, const char *prefix) if (obj->type == OBJ_BLOB) { if (2 <= blobs) die("more than two blobs given: '%s'", name); - memcpy(blob[blobs].sha1, obj->sha1, 20); + hashcpy(blob[blobs].sha1, obj->sha1); blob[blobs].name = name; blobs++; continue; diff --git a/builtin-fmt-merge-msg.c b/builtin-fmt-merge-msg.c index 485ede7cad..c407c033e7 100644 --- a/builtin-fmt-merge-msg.c +++ b/builtin-fmt-merge-msg.c @@ -8,7 +8,7 @@ static const char *fmt_merge_msg_usage = "git-fmt-merge-msg [--summary] [--no-summary] [--file <file>]"; -static int merge_summary = 0; +static int merge_summary; static int fmt_merge_msg_config(const char *key, const char *value) { @@ -27,8 +27,8 @@ static void append_to_list(struct list *list, char *value, void *payload) { if (list->nr == list->alloc) { list->alloc += 32; - list->list = realloc(list->list, sizeof(char *) * list->alloc); - list->payload = realloc(list->payload, + list->list = xrealloc(list->list, sizeof(char *) * list->alloc); + list->payload = xrealloc(list->payload, sizeof(char *) * list->alloc); } list->payload[list->nr] = payload; @@ -55,8 +55,7 @@ static void free_list(struct list *list) for (i = 0; i < list->nr; i++) { free(list->list[i]); - if (list->payload[i]) - free(list->payload[i]); + free(list->payload[i]); } free(list->list); free(list->payload); @@ -112,43 +111,43 @@ static int handle_line(char *line) i = find_in_list(&srcs, src); if (i < 0) { i = srcs.nr; - append_to_list(&srcs, strdup(src), + append_to_list(&srcs, xstrdup(src), xcalloc(1, sizeof(struct src_data))); } src_data = srcs.payload[i]; if (pulling_head) { - origin = strdup(src); + origin = xstrdup(src); src_data->head_status |= 1; } else if (!strncmp(line, "branch ", 7)) { - origin = strdup(line + 7); + origin = xstrdup(line + 7); append_to_list(&src_data->branch, origin, NULL); src_data->head_status |= 2; } else if (!strncmp(line, "tag ", 4)) { origin = line; - append_to_list(&src_data->tag, strdup(origin + 4), NULL); + append_to_list(&src_data->tag, xstrdup(origin + 4), NULL); src_data->head_status |= 2; } else if (!strncmp(line, "remote branch ", 14)) { - origin = strdup(line + 14); + origin = xstrdup(line + 14); append_to_list(&src_data->r_branch, origin, NULL); src_data->head_status |= 2; } else { - origin = strdup(src); - append_to_list(&src_data->generic, strdup(line), NULL); + origin = xstrdup(src); + append_to_list(&src_data->generic, xstrdup(line), NULL); src_data->head_status |= 2; } if (!strcmp(".", src) || !strcmp(src, origin)) { int len = strlen(origin); if (origin[0] == '\'' && origin[len - 1] == '\'') { - char *new_origin = malloc(len - 1); + char *new_origin = xmalloc(len - 1); memcpy(new_origin, origin + 1, len - 2); - new_origin[len - 1] = 0; + new_origin[len - 2] = 0; origin = new_origin; } else - origin = strdup(origin); + origin = xstrdup(origin); } else { - char *new_origin = malloc(strlen(origin) + strlen(src) + 5); + char *new_origin = xmalloc(strlen(origin) + strlen(src) + 5); sprintf(new_origin, "%s of %s", origin, src); origin = new_origin; } @@ -204,7 +203,7 @@ static void shortlog(const char *name, unsigned char *sha1, bol = strstr(commit->buffer, "\n\n"); if (!bol) { - append_to_list(&subjects, strdup(sha1_to_hex( + append_to_list(&subjects, xstrdup(sha1_to_hex( commit->object.sha1)), NULL); continue; @@ -215,11 +214,11 @@ static void shortlog(const char *name, unsigned char *sha1, if (eol) { int len = eol - bol; - oneline = malloc(len + 1); + oneline = xmalloc(len + 1); memcpy(oneline, bol, len); oneline[len] = 0; } else - oneline = strdup(bol); + oneline = xstrdup(bol); append_to_list(&subjects, oneline, NULL); } @@ -278,7 +277,7 @@ int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix) usage(fmt_merge_msg_usage); /* get current branch */ - head = strdup(git_path("HEAD")); + head = xstrdup(git_path("HEAD")); current_branch = resolve_ref(head, head_sha1, 1); current_branch += strlen(head) - 4; free((char *)head); diff --git a/builtin-grep.c b/builtin-grep.c index a561612e7e..ed87a5550c 100644 --- a/builtin-grep.c +++ b/builtin-grep.c @@ -138,6 +138,7 @@ struct grep_opt { unsigned binary:2; unsigned extended:1; unsigned relative:1; + unsigned pathname:1; int regflags; unsigned pre_context; unsigned post_context; @@ -175,61 +176,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 */ @@ -257,8 +209,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: @@ -281,8 +231,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) { @@ -306,8 +254,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) { @@ -325,8 +271,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); } @@ -350,9 +294,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); } @@ -376,7 +317,8 @@ static int word_char(char ch) static void show_line(struct grep_opt *opt, const char *bol, const char *eol, const char *name, unsigned lno, char sign) { - printf("%s%c", name, sign); + if (opt->pathname) + printf("%s%c", name, sign); if (opt->linenum) printf("%d%c", lno, sign); printf("%.*s\n", (int)(eol-bol), bol); @@ -390,9 +332,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) @@ -753,6 +693,8 @@ static int external_grep(struct grep_opt *opt, const char **paths, int cached) push_arg("-F"); if (opt->linenum) push_arg("-n"); + if (!opt->pathname) + push_arg("-h"); if (opt->regflags & REG_EXTENDED) push_arg("-E"); if (opt->regflags & REG_ICASE) @@ -973,6 +915,7 @@ int cmd_grep(int argc, const char **argv, const char *prefix) memset(&opt, 0, sizeof(opt)); opt.prefix_length = (prefix && *prefix) ? strlen(prefix) : 0; opt.relative = 1; + opt.pathname = 1; opt.pattern_tail = &opt.pattern_list; opt.regflags = REG_NEWLINE; @@ -1032,10 +975,12 @@ int cmd_grep(int argc, const char **argv, const char *prefix) opt.linenum = 1; continue; } + if (!strcmp("-h", arg)) { + opt.pathname = 0; + continue; + } if (!strcmp("-H", arg)) { - /* We always show the pathname, so this - * is a noop. - */ + opt.pathname = 1; continue; } if (!strcmp("-l", arg) || @@ -1110,7 +1055,7 @@ int cmd_grep(int argc, const char **argv, const char *prefix) /* ignore empty line like grep does */ if (!buf[0]) continue; - add_pattern(&opt, strdup(buf), argv[1], ++lno, + add_pattern(&opt, xstrdup(buf), argv[1], ++lno, GREP_PATTERN); } fclose(patterns); diff --git a/builtin-log.c b/builtin-log.c index 691cf3aef7..fbc58bbcab 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 11386c432b..ad8c41e731 100644 --- a/builtin-ls-files.c +++ b/builtin-ls-files.c @@ -12,21 +12,22 @@ #include "dir.h" #include "builtin.h" -static int abbrev = 0; -static int show_deleted = 0; -static int show_cached = 0; -static int show_others = 0; -static int show_stage = 0; -static int show_unmerged = 0; -static int show_modified = 0; -static int show_killed = 0; -static int show_valid_bit = 0; +static int abbrev; +static int show_deleted; +static int show_cached; +static int show_others; +static int show_stage; +static int show_unmerged; +static int show_modified; +static int show_killed; +static int show_valid_bit; static int line_terminator = '\n'; -static int prefix_len = 0, prefix_offset = 0; -static const char **pathspec = NULL; -static int error_unmatch = 0; -static char *ps_matched = NULL; +static int prefix_len; +static int prefix_offset; +static const char **pathspec; +static int error_unmatch; +static char *ps_matched; static const char *tag_cached = ""; static const char *tag_unmerged = ""; diff --git a/builtin-ls-tree.c b/builtin-ls-tree.c index 261147fdbe..201defd934 100644 --- a/builtin-ls-tree.c +++ b/builtin-ls-tree.c @@ -14,10 +14,10 @@ static int line_termination = '\n'; #define LS_TREE_ONLY 2 #define LS_SHOW_TREES 4 #define LS_NAME_ONLY 8 -static int abbrev = 0; -static int ls_options = 0; +static int abbrev; +static int ls_options; static const char **pathspec; -static int chomp_prefix = 0; +static int chomp_prefix; static const char *ls_tree_prefix; static const char ls_tree_usage[] = diff --git a/builtin-mailinfo.c b/builtin-mailinfo.c index 24a4fc63b3..0c65f93145 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 a731f8d9cf..4d21d88412 100644 --- a/builtin-mv.c +++ b/builtin-mv.c @@ -17,12 +17,19 @@ static const char builtin_mv_usage[] = static const char **copy_pathspec(const char *prefix, const char **pathspec, int count, int base_name) { + int i; const char **result = xmalloc((count + 1) * sizeof(const char *)); memcpy(result, pathspec, count * sizeof(const char *)); result[count] = NULL; - if (base_name) { - int i; - for (i = 0; i < count; i++) { + for (i = 0; i < count; i++) { + int length = strlen(result[i]); + if (length > 0 && result[i][length - 1] == '/') { + char *without_slash = xmalloc(length); + memcpy(without_slash, result[i], length - 1); + without_slash[length - 1] = '\0'; + result[i] = without_slash; + } + if (base_name) { const char *last_slash = strrchr(result[i], '/'); if (last_slash) result[i] = last_slash + 1; @@ -107,7 +114,10 @@ int cmd_mv(int argc, const char **argv, const char *prefix) modes = xcalloc(count, sizeof(enum update_mode)); dest_path = copy_pathspec(prefix, argv + argc - 1, 1, 0); - if (!lstat(dest_path[0], &st) && + if (dest_path[0][0] == '\0') + /* special case: "." was normalized to "" */ + destination = copy_pathspec(dest_path[0], argv + i, count, 1); + else if (!lstat(dest_path[0], &st) && S_ISDIR(st.st_mode)) { dest_path[0] = add_slash(dest_path[0]); destination = copy_pathspec(dest_path[0], argv + i, count, 1); @@ -119,72 +129,71 @@ int cmd_mv(int argc, const char **argv, const char *prefix) /* Checking */ for (i = 0; i < count; i++) { + const char *src = source[i], *dst = destination[i]; + int length, src_is_dir; const char *bad = NULL; if (show_only) - printf("Checking rename of '%s' to '%s'\n", - source[i], destination[i]); + printf("Checking rename of '%s' to '%s'\n", src, dst); - if (lstat(source[i], &st) < 0) + length = strlen(src); + if (lstat(src, &st) < 0) bad = "bad source"; - - if (S_ISDIR(st.st_mode)) { - const char *dir = source[i], *dest_dir = destination[i]; - int first, last, len = strlen(dir); - - if (lstat(dest_dir, &st) == 0) { - bad = "cannot move directory over file"; - goto next; - } + else if (!strncmp(src, dst, length) && + (dst[length] == 0 || dst[length] == '/')) { + bad = "can not move directory into itself"; + } else if ((src_is_dir = S_ISDIR(st.st_mode)) + && lstat(dst, &st) == 0) + bad = "cannot move directory over file"; + else if (src_is_dir) { + int first, last; modes[i] = WORKING_DIRECTORY; - first = cache_name_pos(source[i], len); + first = cache_name_pos(src, length); if (first >= 0) - die ("Huh? %s/ is in index?", dir); + die ("Huh? %s/ is in index?", src); first = -1 - first; for (last = first; last < active_nr; last++) { const char *path = active_cache[last]->name; - if (strncmp(path, dir, len) || path[len] != '/') + if (strncmp(path, src, length) + || path[length] != '/') break; } if (last - first < 1) bad = "source directory is empty"; - else if (!bad) { - int j, dst_len = strlen(dest_dir); + else { + int j, dst_len; if (last - first > 0) { - source = realloc(source, + source = xrealloc(source, (count + last - first) * sizeof(char *)); - destination = realloc(destination, + destination = xrealloc(destination, (count + last - first) * sizeof(char *)); - modes = realloc(modes, + modes = xrealloc(modes, (count + last - first) * sizeof(enum update_mode)); } - dest_dir = add_slash(dest_dir); + dst = add_slash(dst); + dst_len = strlen(dst) - 1; for (j = 0; j < last - first; j++) { const char *path = active_cache[first + j]->name; source[count + j] = path; destination[count + j] = - prefix_path(dest_dir, dst_len, - path + len); + prefix_path(dst, dst_len, + path + length); modes[count + j] = INDEX; } count += last - first; } - - goto next; - } - - if (!bad && lstat(destination[i], &st) == 0) { + } else if (lstat(dst, &st) == 0) { bad = "destination exists"; if (force) { /* @@ -196,28 +205,17 @@ int cmd_mv(int argc, const char **argv, const char *prefix) " will overwrite!\n", bad); bad = NULL; - path_list_insert(destination[i], - &overwritten); + path_list_insert(dst, &overwritten); } else bad = "Cannot overwrite"; } - } - - if (!bad && - !strncmp(destination[i], source[i], strlen(source[i]))) - bad = "can not move directory into itself"; - - if (!bad && cache_name_pos(source[i], strlen(source[i])) < 0) + } else if (cache_name_pos(src, length) < 0) bad = "not under version control"; + else if (path_list_has_path(&src_for_dst, dst)) + bad = "multiple sources for the same target"; + else + path_list_insert(dst, &src_for_dst); - if (!bad) { - if (path_list_has_path(&src_for_dst, destination[i])) - bad = "multiple sources for the same target"; - else - path_list_insert(destination[i], &src_for_dst); - } - -next: if (bad) { if (ignore_errors) { if (--count > 0) { @@ -229,33 +227,32 @@ next: } } else die ("%s, source=%s, destination=%s", - bad, source[i], destination[i]); + bad, src, dst); } } for (i = 0; i < count; i++) { + const char *src = source[i], *dst = destination[i]; + enum update_mode mode = modes[i]; if (show_only || verbose) - printf("Renaming %s to %s\n", - source[i], destination[i]); - if (!show_only && modes[i] != INDEX && - rename(source[i], destination[i]) < 0 && - !ignore_errors) - die ("renaming %s failed: %s", - source[i], strerror(errno)); - - if (modes[i] == WORKING_DIRECTORY) + printf("Renaming %s to %s\n", src, dst); + if (!show_only && mode != INDEX && + rename(src, dst) < 0 && !ignore_errors) + die ("renaming %s failed: %s", src, strerror(errno)); + + if (mode == WORKING_DIRECTORY) continue; - if (cache_name_pos(source[i], strlen(source[i])) >= 0) { - path_list_insert(source[i], &deleted); + if (cache_name_pos(src, strlen(src)) >= 0) { + path_list_insert(src, &deleted); /* destination can be a directory with 1 file inside */ - if (path_list_has_path(&overwritten, destination[i])) - path_list_insert(destination[i], &changed); + if (path_list_has_path(&overwritten, dst)) + path_list_insert(dst, &changed); else - path_list_insert(destination[i], &added); + path_list_insert(dst, &added); } else - path_list_insert(destination[i], &added); + path_list_insert(dst, &added); } if (show_only) { @@ -265,10 +262,10 @@ next: } else { for (i = 0; i < changed.nr; i++) { const char *path = changed.items[i].path; - int i = cache_name_pos(path, strlen(path)); - struct cache_entry *ce = active_cache[i]; + int j = cache_name_pos(path, strlen(path)); + struct cache_entry *ce = active_cache[j]; - if (i < 0) + if (j < 0) die ("Huh? Cache entry for %s unknown?", path); refresh_cache_entry(ce, 0); } diff --git a/builtin-name-rev.c b/builtin-name-rev.c index 571bba4817..52886b69b0 100644 --- a/builtin-name-rev.c +++ b/builtin-name-rev.c @@ -75,7 +75,7 @@ copy_data: } } -static int tags_only = 0; +static int tags_only; static int name_ref(const char *path, const unsigned char *sha1) { @@ -100,7 +100,7 @@ static int name_ref(const char *path, const unsigned char *sha1) else if (!strncmp(path, "refs/", 5)) path = path + 5; - name_rev(commit, strdup(path), 0, 0, deref); + name_rev(commit, xstrdup(path), 0, 0, deref); } return 0; } diff --git a/builtin-pack-objects.c b/builtin-pack-objects.c index 2f9921224d..8d7a1209d5 100644 --- a/builtin-pack-objects.c +++ b/builtin-pack-objects.c @@ -9,10 +9,13 @@ #include "pack.h" #include "csum-file.h" #include "tree-walk.h" +#include "diff.h" +#include "revision.h" +#include "list-objects.h" #include <sys/time.h> #include <signal.h> -static const char pack_usage[] = "git-pack-objects [-q] [--no-reuse-delta] [--non-empty] [--local] [--incremental] [--window=N] [--depth=N] {--stdout | base-name} < object-list"; +static const char pack_usage[] = "git-pack-objects [-q] [--no-reuse-delta] [--non-empty] [--local] [--incremental] [--window=N] [--depth=N] [--revs [--unpacked | --all]*] [--stdout | base-name] <ref-list | <object-list]"; struct object_entry { unsigned char sha1[20]; @@ -53,18 +56,20 @@ struct object_entry { */ static unsigned char object_list_sha1[20]; -static int non_empty = 0; -static int no_reuse_delta = 0; -static int local = 0; -static int incremental = 0; +static int non_empty; +static int no_reuse_delta; +static int local; +static int incremental; static struct object_entry **sorted_by_sha, **sorted_by_type; -static struct object_entry *objects = NULL; -static int nr_objects = 0, nr_alloc = 0, nr_result = 0; +static struct object_entry *objects; +static int nr_objects, nr_alloc, nr_result; static const char *base_name; static unsigned char pack_file_sha1[20]; static int progress = 1; -static volatile sig_atomic_t progress_update = 0; +static volatile sig_atomic_t progress_update; static int window = 10; +static int pack_to_stdout; +static int num_preferred_base; /* * The object names in objects array are hashed with this hashtable, @@ -72,8 +77,8 @@ static int window = 10; * sorted_by_sha is also possible but this was easier to code and faster. * This hashtable is built after all the objects are seen. */ -static int *object_ix = NULL; -static int object_ix_hashsz = 0; +static int *object_ix; +static int object_ix_hashsz; /* * Pack index for existing packs give us easy access to the offsets into @@ -90,15 +95,15 @@ struct pack_revindex { struct packed_git *p; unsigned long *revindex; } *pack_revindex = NULL; -static int pack_revindex_hashsz = 0; +static int pack_revindex_hashsz; /* * stats */ -static int written = 0; -static int written_delta = 0; -static int reused = 0; -static int reused_delta = 0; +static int written; +static int written_delta; +static int reused; +static int reused_delta; static int pack_revindex_ix(struct packed_git *p) { @@ -242,6 +247,82 @@ static int encode_header(enum object_type type, unsigned long size, unsigned cha return n; } +static int check_inflate(unsigned char *data, unsigned long len, unsigned long expect) +{ + z_stream stream; + unsigned char fakebuf[4096]; + int st; + + memset(&stream, 0, sizeof(stream)); + stream.next_in = data; + stream.avail_in = len; + stream.next_out = fakebuf; + stream.avail_out = sizeof(fakebuf); + inflateInit(&stream); + + while (1) { + st = inflate(&stream, Z_FINISH); + if (st == Z_STREAM_END || st == Z_OK) { + st = (stream.total_out == expect && + stream.total_in == len) ? 0 : -1; + break; + } + if (st != Z_BUF_ERROR) { + st = -1; + break; + } + stream.next_out = fakebuf; + stream.avail_out = sizeof(fakebuf); + } + inflateEnd(&stream); + return st; +} + +/* + * we are going to reuse the existing pack entry data. make + * sure it is not corrupt. + */ +static int revalidate_pack_entry(struct object_entry *entry, unsigned char *data, unsigned long len) +{ + enum object_type type; + unsigned long size, used; + + if (pack_to_stdout) + return 0; + + /* the caller has already called use_packed_git() for us, + * so it is safe to access the pack data from mmapped location. + * make sure the entry inflates correctly. + */ + used = unpack_object_header_gently(data, len, &type, &size); + if (!used) + return -1; + if (type == OBJ_DELTA) + used += 20; /* skip base object name */ + data += used; + len -= used; + return check_inflate(data, len, entry->size); +} + +static int revalidate_loose_object(struct object_entry *entry, + unsigned char *map, + unsigned long mapsize) +{ + /* we already know this is a loose object with new type header. */ + enum object_type type; + unsigned long size, used; + + if (pack_to_stdout) + return 0; + + used = unpack_object_header_gently(map, mapsize, &type, &size); + if (!used) + return -1; + map += used; + mapsize -= used; + return check_inflate(map, mapsize, size); +} + static unsigned long write_object(struct sha1file *f, struct object_entry *entry) { @@ -276,6 +357,9 @@ static unsigned long write_object(struct sha1file *f, map = map_sha1_file(entry->sha1, &mapsize); if (map && !legacy_loose_object(map)) { /* We can copy straight into the pack file */ + if (revalidate_loose_object(entry, map, mapsize)) + die("corrupt loose object %s", + sha1_to_hex(entry->sha1)); sha1write(f, map, mapsize); munmap(map, mapsize); written++; @@ -286,7 +370,7 @@ static unsigned long write_object(struct sha1file *f, munmap(map, mapsize); } - if (! to_reuse) { + if (!to_reuse) { buf = read_sha1_file(entry->sha1, type, &size); if (!buf) die("unable to read %s", sha1_to_hex(entry->sha1)); @@ -319,6 +403,9 @@ static unsigned long write_object(struct sha1file *f, datalen = find_packed_object_size(p, entry->in_pack_offset); buf = (char *) p->pack_base + entry->in_pack_offset; + + if (revalidate_pack_entry(entry, buf, datalen)) + die("corrupt delta in pack %s", sha1_to_hex(entry->sha1)); sha1write(f, buf, datalen); unuse_packed_git(p); hdrlen = 0; /* not really */ @@ -441,7 +528,7 @@ static int locate_object_entry_hash(const unsigned char *sha1) memcpy(&ui, sha1, sizeof(unsigned int)); i = ui % object_ix_hashsz; while (0 < object_ix[i]) { - if (!memcmp(sha1, objects[object_ix[i]-1].sha1, 20)) + if (!hashcmp(sha1, objects[object_ix[i] - 1].sha1)) return i; if (++i == object_ix_hashsz) i = 0; @@ -534,7 +621,7 @@ static int add_object_entry(const unsigned char *sha1, unsigned hash, int exclud entry = objects + idx; nr_objects = idx + 1; memset(entry, 0, sizeof(*entry)); - memcpy(entry->sha1, sha1, 20); + hashcpy(entry->sha1, sha1); entry->hash = hash; if (object_ix_hashsz * 3 <= nr_objects * 4) @@ -607,7 +694,7 @@ static struct pbase_tree_cache *pbase_tree_get(const unsigned char *sha1) */ for (neigh = 0; neigh < 8; neigh++) { ent = pbase_tree_cache[my_ix]; - if (ent && !memcmp(ent->sha1, sha1, 20)) { + if (ent && !hashcmp(ent->sha1, sha1)) { ent->ref++; return ent; } @@ -649,7 +736,7 @@ static struct pbase_tree_cache *pbase_tree_get(const unsigned char *sha1) free(ent->tree_data); nent = ent; } - memcpy(nent->sha1, sha1, 20); + hashcpy(nent->sha1, sha1); nent->tree_data = data; nent->tree_size = size; nent->ref = 1; @@ -755,7 +842,7 @@ static int check_pbase_path(unsigned hash) return 0; } -static void add_preferred_base_object(char *name, unsigned hash) +static void add_preferred_base_object(const char *name, unsigned hash) { struct pbase_tree *it; int cmplen = name_cmp_len(name); @@ -784,12 +871,15 @@ static void add_preferred_base(unsigned char *sha1) unsigned long size; unsigned char tree_sha1[20]; + if (window <= num_preferred_base++) + return; + data = read_object_with_reference(sha1, tree_type, &size, tree_sha1); if (!data) return; for (it = pbase_tree; it; it = it->next) { - if (!memcmp(it->pcache.sha1, tree_sha1, 20)) { + if (!hashcmp(it->pcache.sha1, tree_sha1)) { free(data); return; } @@ -799,7 +889,7 @@ static void add_preferred_base(unsigned char *sha1) it->next = pbase_tree; pbase_tree = it; - memcpy(it->pcache.sha1, tree_sha1, 20); + hashcpy(it->pcache.sha1, tree_sha1); it->pcache.tree_data = data; it->pcache.tree_size = size; } @@ -931,7 +1021,7 @@ static struct object_entry **create_sorted_list(entry_sort_t sort) static int sha1_sort(const struct object_entry *a, const struct object_entry *b) { - return memcmp(a->sha1, b->sha1, 20); + return hashcmp(a->sha1, b->sha1); } static struct object_entry **create_final_object_list(void) @@ -1163,7 +1253,7 @@ static void prepare_pack(int window, int depth) find_deltas(sorted_by_type, window+1, depth); } -static int reuse_cached_pack(unsigned char *sha1, int pack_to_stdout) +static int reuse_cached_pack(unsigned char *sha1) { static const char cache[] = "pack-cache/pack-%s.%s"; char *cached_pack, *cached_idx; @@ -1243,14 +1333,105 @@ static int git_pack_config(const char *k, const char *v) return git_default_config(k, v); } +static void read_object_list_from_stdin(void) +{ + char line[40 + 1 + PATH_MAX + 2]; + unsigned char sha1[20]; + unsigned hash; + + for (;;) { + if (!fgets(line, sizeof(line), stdin)) { + if (feof(stdin)) + break; + if (!ferror(stdin)) + die("fgets returned NULL, not EOF, not error!"); + if (errno != EINTR) + die("fgets: %s", strerror(errno)); + clearerr(stdin); + continue; + } + if (line[0] == '-') { + if (get_sha1_hex(line+1, sha1)) + die("expected edge sha1, got garbage:\n %s", + line); + add_preferred_base(sha1); + continue; + } + if (get_sha1_hex(line, sha1)) + die("expected sha1, got garbage:\n %s", line); + + hash = name_hash(line+41); + add_preferred_base_object(line+41, hash); + add_object_entry(sha1, hash, 0); + } +} + +static void show_commit(struct commit *commit) +{ + unsigned hash = name_hash(""); + add_preferred_base_object("", hash); + add_object_entry(commit->object.sha1, hash, 0); +} + +static void show_object(struct object_array_entry *p) +{ + unsigned hash = name_hash(p->name); + add_preferred_base_object(p->name, hash); + add_object_entry(p->item->sha1, hash, 0); +} + +static void show_edge(struct commit *commit) +{ + add_preferred_base(commit->object.sha1); +} + +static void get_object_list(int ac, const char **av) +{ + struct rev_info revs; + char line[1000]; + int flags = 0; + + init_revisions(&revs, NULL); + save_commit_buffer = 0; + track_object_refs = 0; + setup_revisions(ac, av, &revs, NULL); + + while (fgets(line, sizeof(line), stdin) != NULL) { + int len = strlen(line); + if (line[len - 1] == '\n') + line[--len] = 0; + if (!len) + break; + if (*line == '-') { + if (!strcmp(line, "--not")) { + flags ^= UNINTERESTING; + continue; + } + die("not a rev '%s'", line); + } + if (handle_revision_arg(line, &revs, flags, 1)) + die("bad revision '%s'", line); + } + + prepare_revision_walk(&revs); + mark_edges_uninteresting(revs.commits, &revs, show_edge); + traverse_commit_list(&revs, show_commit, show_object); +} + int cmd_pack_objects(int argc, const char **argv, const char *prefix) { SHA_CTX ctx; - char line[40 + 1 + PATH_MAX + 2]; - int depth = 10, pack_to_stdout = 0; + int depth = 10; struct object_entry **list; - int num_preferred_base = 0; + int use_internal_rev_list = 0; + int thin = 0; int i; + const char *rp_av[64]; + int rp_ac; + + rp_av[0] = "pack-objects"; + rp_av[1] = "--objects"; /* --thin will make it --objects-edge */ + rp_ac = 2; git_config(git_pack_config); @@ -1258,63 +1439,99 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) for (i = 1; i < argc; i++) { const char *arg = argv[i]; - if (*arg == '-') { - if (!strcmp("--non-empty", arg)) { - non_empty = 1; - continue; - } - if (!strcmp("--local", arg)) { - local = 1; - continue; - } - if (!strcmp("--progress", arg)) { - progress = 1; - continue; - } - if (!strcmp("--incremental", arg)) { - incremental = 1; - continue; - } - if (!strncmp("--window=", arg, 9)) { - char *end; - window = strtoul(arg+9, &end, 0); - if (!arg[9] || *end) - usage(pack_usage); - continue; - } - if (!strncmp("--depth=", arg, 8)) { - char *end; - depth = strtoul(arg+8, &end, 0); - if (!arg[8] || *end) - usage(pack_usage); - continue; - } - if (!strcmp("--progress", arg)) { - progress = 1; - continue; - } - if (!strcmp("-q", arg)) { - progress = 0; - continue; - } - if (!strcmp("--no-reuse-delta", arg)) { - no_reuse_delta = 1; - continue; - } - if (!strcmp("--stdout", arg)) { - pack_to_stdout = 1; - continue; - } - usage(pack_usage); + if (*arg != '-') + break; + + if (!strcmp("--non-empty", arg)) { + non_empty = 1; + continue; + } + if (!strcmp("--local", arg)) { + local = 1; + continue; + } + if (!strcmp("--progress", arg)) { + progress = 1; + continue; + } + if (!strcmp("--incremental", arg)) { + incremental = 1; + continue; + } + if (!strncmp("--window=", arg, 9)) { + char *end; + window = strtoul(arg+9, &end, 0); + if (!arg[9] || *end) + usage(pack_usage); + continue; + } + if (!strncmp("--depth=", arg, 8)) { + char *end; + depth = strtoul(arg+8, &end, 0); + if (!arg[8] || *end) + usage(pack_usage); + continue; + } + if (!strcmp("--progress", arg)) { + progress = 1; + continue; + } + if (!strcmp("-q", arg)) { + progress = 0; + continue; + } + if (!strcmp("--no-reuse-delta", arg)) { + no_reuse_delta = 1; + continue; + } + if (!strcmp("--stdout", arg)) { + pack_to_stdout = 1; + continue; + } + if (!strcmp("--revs", arg)) { + use_internal_rev_list = 1; + continue; } - if (base_name) - usage(pack_usage); - base_name = arg; + if (!strcmp("--unpacked", arg) || + !strncmp("--unpacked=", arg, 11) || + !strcmp("--all", arg)) { + use_internal_rev_list = 1; + if (ARRAY_SIZE(rp_av) - 1 <= rp_ac) + die("too many internal rev-list options"); + rp_av[rp_ac++] = arg; + continue; + } + if (!strcmp("--thin", arg)) { + use_internal_rev_list = 1; + thin = 1; + rp_av[1] = "--objects-edge"; + continue; + } + usage(pack_usage); } + /* Traditionally "pack-objects [options] base extra" failed; + * we would however want to take refs parameter that would + * have been given to upstream rev-list ourselves, which means + * we somehow want to say what the base name is. So the + * syntax would be: + * + * pack-objects [options] base <refs...> + * + * in other words, we would treat the first non-option as the + * base_name and send everything else to the internal revision + * walker. + */ + + if (!pack_to_stdout) + base_name = argv[i++]; + if (pack_to_stdout != !base_name) usage(pack_usage); + if (!pack_to_stdout && thin) + die("--thin cannot be used to build an indexable pack."); + prepare_packed_git(); if (progress) { @@ -1322,35 +1539,13 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) setup_progress_signal(); } - for (;;) { - unsigned char sha1[20]; - unsigned hash; - - if (!fgets(line, sizeof(line), stdin)) { - if (feof(stdin)) - break; - if (!ferror(stdin)) - die("fgets returned NULL, not EOF, not error!"); - if (errno != EINTR) - die("fgets: %s", strerror(errno)); - clearerr(stdin); - continue; - } - - if (line[0] == '-') { - if (get_sha1_hex(line+1, sha1)) - die("expected edge sha1, got garbage:\n %s", - line+1); - if (num_preferred_base++ < window) - add_preferred_base(sha1); - continue; - } - if (get_sha1_hex(line, sha1)) - die("expected sha1, got garbage:\n %s", line); - hash = name_hash(line+41); - add_preferred_base_object(line+41, hash); - add_object_entry(sha1, hash, 0); + if (!use_internal_rev_list) + read_object_list_from_stdin(); + else { + rp_av[rp_ac] = NULL; + get_object_list(rp_ac, rp_av); } + if (progress) fprintf(stderr, "Done counting %d objects.\n", nr_objects); sorted_by_sha = create_final_object_list(); @@ -1367,7 +1562,7 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) if (progress && (nr_objects != nr_result)) fprintf(stderr, "Result has %d objects.\n", nr_result); - if (reuse_cached_pack(object_list_sha1, pack_to_stdout)) + if (reuse_cached_pack(object_list_sha1)) ; else { if (nr_result) diff --git a/builtin-prune-packed.c b/builtin-prune-packed.c index d3dd94d9ef..960db49859 100644 --- a/builtin-prune-packed.c +++ b/builtin-prune-packed.c @@ -19,7 +19,7 @@ static void prune_dir(int i, DIR *dir, char *pathname, int len) memcpy(hex+2, de->d_name, 38); if (get_sha1_hex(hex, sha1)) continue; - if (!has_sha1_pack(sha1)) + if (!has_sha1_pack(sha1, NULL)) continue; memcpy(pathname + len, de->d_name, 38); if (dryrun) diff --git a/builtin-prune.c b/builtin-prune.c index 89ec7f1426..6228c7907b 100644 --- a/builtin-prune.c +++ b/builtin-prune.c @@ -11,7 +11,7 @@ #include "cache-tree.h" static const char prune_usage[] = "git-prune [-n]"; -static int show_only = 0; +static int show_only; static struct rev_info revs; static int prune_object(char *path, const char *filename, const unsigned char *sha1) @@ -106,7 +106,7 @@ static void process_tree(struct tree *tree, obj->flags |= SEEN; if (parse_tree(tree) < 0) die("bad tree object %s", sha1_to_hex(obj->sha1)); - name = strdup(name); + name = xstrdup(name); add_object(obj, p, path, name); me.up = path; me.elem = name; diff --git a/builtin-push.c b/builtin-push.c index 53bc378f73..c43f2566d5 100644 --- a/builtin-push.c +++ b/builtin-push.c @@ -10,14 +10,14 @@ static const char push_usage[] = "git-push [--all] [--tags] [-f | --force] <repository> [<refspec>...]"; -static int all = 0, tags = 0, force = 0, thin = 1; -static const char *execute = NULL; +static int all, tags, force, thin = 1; +static const char *execute; #define BUF_SIZE (2084) static char buffer[BUF_SIZE]; -static const char **refspec = NULL; -static int refspec_nr = 0; +static const char **refspec; +static int refspec_nr; static void add_refspec(const char *ref) { @@ -32,10 +32,8 @@ static int expand_one_ref(const char *ref, const unsigned char *sha1) /* Ignore the "refs/" at the beginning of the refname */ ref += 5; - if (strncmp(ref, "tags/", 5)) - return 0; - - add_refspec(strdup(ref)); + if (!strncmp(ref, "tags/", 5)) + add_refspec(xstrdup(ref)); return 0; } @@ -102,12 +100,12 @@ static int get_remotes_uri(const char *repo, const char *uri[MAX_URI]) if (!is_refspec) { if (n < MAX_URI) - uri[n++] = strdup(s); + uri[n++] = xstrdup(s); else error("more than %d URL's specified, ignoring the rest", MAX_URI); } else if (is_refspec && !has_explicit_refspec) - add_refspec(strdup(s)); + add_refspec(xstrdup(s)); } fclose(f); if (!n) @@ -127,13 +125,13 @@ static int get_remote_config(const char* key, const char* value) !strncmp(key + 7, config_repo, config_repo_len)) { if (!strcmp(key + 7 + config_repo_len, ".url")) { if (config_current_uri < MAX_URI) - config_uri[config_current_uri++] = strdup(value); + config_uri[config_current_uri++] = xstrdup(value); else error("more than %d URL's specified, ignoring the rest", MAX_URI); } else if (config_get_refspecs && !strcmp(key + 7 + config_repo_len, ".push")) - add_refspec(strdup(value)); + add_refspec(xstrdup(value)); } return 0; } @@ -234,7 +232,7 @@ static int do_push(const char *repo) common_argc = argc; for (i = 0; i < n; i++) { - int error; + int err; int dest_argc = common_argc; int dest_refspec_nr = refspec_nr; const char **dest_refspec = refspec; @@ -250,10 +248,10 @@ static int do_push(const char *repo) while (dest_refspec_nr--) argv[dest_argc++] = *dest_refspec++; argv[dest_argc] = NULL; - error = run_command_v(argc, argv); - if (!error) + err = run_command_v(argc, argv); + if (!err) continue; - switch (error) { + switch (err) { case -ERR_RUN_COMMAND_FORK: die("unable to fork for %s", sender); case -ERR_RUN_COMMAND_EXEC: @@ -264,7 +262,7 @@ static int do_push(const char *repo) case -ERR_RUN_COMMAND_WAITPID_NOEXIT: die("%s died with strange error", sender); default: - return -error; + return -err; } } return 0; diff --git a/builtin-read-tree.c b/builtin-read-tree.c index 8da8acbb0a..c1867d2a00 100644 --- a/builtin-read-tree.c +++ b/builtin-read-tree.c @@ -12,7 +12,7 @@ #include "unpack-trees.h" #include "builtin.h" -static struct object_list *trees = NULL; +static struct object_list *trees; static int list_tree(unsigned char *sha1) { @@ -53,7 +53,7 @@ static void prime_cache_tree_rec(struct cache_tree *it, struct tree *tree) struct name_entry entry; int cnt; - memcpy(it->sha1, tree->object.sha1, 20); + hashcpy(it->sha1, tree->object.sha1); desc.buf = tree->buffer; desc.size = tree->size; cnt = 0; diff --git a/builtin-repo-config.c b/builtin-repo-config.c index c821e22717..9cf12d32e5 100644 --- a/builtin-repo-config.c +++ b/builtin-repo-config.c @@ -5,14 +5,14 @@ static const char git_config_set_usage[] = "git-repo-config [ --bool | --int ] [--get | --get-all | --get-regexp | --replace-all | --unset | --unset-all] name [value [value_regex]] | --list"; -static char* key = NULL; -static regex_t* key_regexp = NULL; -static regex_t* regexp = NULL; -static int show_keys = 0; -static int use_key_regexp = 0; -static int do_all = 0; -static int do_not_match = 0; -static int seen = 0; +static char *key; +static regex_t *key_regexp; +static regex_t *regexp; +static int show_keys; +static int use_key_regexp; +static int do_all; +static int do_not_match; +static int seen; static enum { T_RAW, T_INT, T_BOOL } type = T_RAW; static int show_all_config(const char *key_, const char *value_) @@ -72,19 +72,19 @@ static int get_value(const char* key_, const char* regex_) const char *home = getenv("HOME"); local = getenv("GIT_CONFIG_LOCAL"); if (!local) - local = repo_config = strdup(git_path("config")); + local = repo_config = xstrdup(git_path("config")); if (home) - global = strdup(mkpath("%s/.gitconfig", home)); + global = xstrdup(mkpath("%s/.gitconfig", home)); } - key = strdup(key_); + key = xstrdup(key_); for (tl=key+strlen(key)-1; tl >= key && *tl != '.'; --tl) *tl = tolower(*tl); for (tl=key; *tl && *tl != '.'; ++tl) *tl = tolower(*tl); if (use_key_regexp) { - key_regexp = (regex_t*)malloc(sizeof(regex_t)); + key_regexp = (regex_t*)xmalloc(sizeof(regex_t)); if (regcomp(key_regexp, key, REG_EXTENDED)) { fprintf(stderr, "Invalid key pattern: %s\n", key_); goto free_strings; @@ -97,7 +97,7 @@ static int get_value(const char* key_, const char* regex_) regex_++; } - regexp = (regex_t*)malloc(sizeof(regex_t)); + regexp = (regex_t*)xmalloc(sizeof(regex_t)); if (regcomp(regexp, regex_, REG_EXTENDED)) { fprintf(stderr, "Invalid pattern: %s\n", regex_); goto free_strings; @@ -122,10 +122,8 @@ static int get_value(const char* key_, const char* regex_) ret = (seen == 1) ? 0 : 1; free_strings: - if (repo_config) - free(repo_config); - if (global) - free(global); + free(repo_config); + free(global); return ret; } diff --git a/builtin-rev-list.c b/builtin-rev-list.c index 0dee1734a3..1f3333da38 100644 --- a/builtin-rev-list.c +++ b/builtin-rev-list.c @@ -7,6 +7,7 @@ #include "tree-walk.h" #include "diff.h" #include "revision.h" +#include "list-objects.h" #include "builtin.h" /* bits #0-15 in revision.h */ @@ -23,6 +24,7 @@ static const char rev_list_usage[] = " --no-merges\n" " --remove-empty\n" " --all\n" +" --stdin\n" " ordering output:\n" " --topo-order\n" " --date-order\n" @@ -39,9 +41,9 @@ static const char rev_list_usage[] = static struct rev_info revs; -static int bisect_list = 0; -static int show_timestamp = 0; -static int hdr_termination = 0; +static int bisect_list; +static int show_timestamp; +static int hdr_termination; static const char *header_prefix; static void show_commit(struct commit *commit) @@ -85,7 +87,7 @@ static void show_commit(struct commit *commit) static char pretty_header[16384]; pretty_print_commit(revs.commit_format, commit, ~0, pretty_header, sizeof(pretty_header), - revs.abbrev, NULL, NULL); + revs.abbrev, NULL, NULL, revs.relative_date); printf("%s%c", pretty_header, hdr_termination); } fflush(stdout); @@ -93,110 +95,28 @@ static void show_commit(struct commit *commit) free_commit_list(commit->parents); commit->parents = NULL; } - if (commit->buffer) { - free(commit->buffer); - commit->buffer = NULL; - } + free(commit->buffer); + commit->buffer = NULL; } -static void process_blob(struct blob *blob, - struct object_array *p, - struct name_path *path, - const char *name) +static void show_object(struct object_array_entry *p) { - struct object *obj = &blob->object; - - if (!revs.blob_objects) - return; - if (obj->flags & (UNINTERESTING | SEEN)) - return; - obj->flags |= SEEN; - name = strdup(name); - add_object(obj, p, path, name); -} - -static void process_tree(struct tree *tree, - struct object_array *p, - struct name_path *path, - const char *name) -{ - struct object *obj = &tree->object; - struct tree_desc desc; - struct name_entry entry; - struct name_path me; - - if (!revs.tree_objects) - return; - if (obj->flags & (UNINTERESTING | SEEN)) - return; - if (parse_tree(tree) < 0) - die("bad tree object %s", sha1_to_hex(obj->sha1)); - obj->flags |= SEEN; - name = strdup(name); - add_object(obj, p, path, name); - me.up = path; - me.elem = name; - me.elem_len = strlen(name); - - desc.buf = tree->buffer; - desc.size = tree->size; - - while (tree_entry(&desc, &entry)) { - if (S_ISDIR(entry.mode)) - process_tree(lookup_tree(entry.sha1), p, &me, entry.path); - else - process_blob(lookup_blob(entry.sha1), p, &me, entry.path); + /* An object with name "foo\n0000000..." can be used to + * confuse downstream git-pack-objects very badly. + */ + const char *ep = strchr(p->name, '\n'); + if (ep) { + printf("%s %.*s\n", sha1_to_hex(p->item->sha1), + (int) (ep - p->name), + p->name); } - free(tree->buffer); - tree->buffer = NULL; + else + printf("%s %s\n", sha1_to_hex(p->item->sha1), p->name); } -static void show_commit_list(struct rev_info *revs) +static void show_edge(struct commit *commit) { - int i; - struct commit *commit; - struct object_array objects = { 0, 0, NULL }; - - while ((commit = get_revision(revs)) != NULL) { - process_tree(commit->tree, &objects, NULL, ""); - show_commit(commit); - } - for (i = 0; i < revs->pending.nr; i++) { - struct object_array_entry *pending = revs->pending.objects + i; - struct object *obj = pending->item; - const char *name = pending->name; - if (obj->flags & (UNINTERESTING | SEEN)) - continue; - if (obj->type == OBJ_TAG) { - obj->flags |= SEEN; - add_object_array(obj, name, &objects); - continue; - } - if (obj->type == OBJ_TREE) { - process_tree((struct tree *)obj, &objects, NULL, name); - continue; - } - if (obj->type == OBJ_BLOB) { - process_blob((struct blob *)obj, &objects, NULL, name); - continue; - } - die("unknown pending object %s (%s)", sha1_to_hex(obj->sha1), name); - } - for (i = 0; i < objects.nr; i++) { - struct object_array_entry *p = objects.objects + i; - - /* An object with name "foo\n0000000..." can be used to - * confuse downstream git-pack-objects very badly. - */ - const char *ep = strchr(p->name, '\n'); - if (ep) { - printf("%s %.*s\n", sha1_to_hex(p->item->sha1), - (int) (ep - p->name), - p->name); - } - else - printf("%s %s\n", sha1_to_hex(p->item->sha1), p->name); - } + printf("-%s\n", sha1_to_hex(commit->object.sha1)); } /* @@ -277,32 +197,20 @@ static struct commit_list *find_bisection(struct commit_list *list) return best; } -static void mark_edge_parents_uninteresting(struct commit *commit) +static void read_revisions_from_stdin(struct rev_info *revs) { - struct commit_list *parents; - - for (parents = commit->parents; parents; parents = parents->next) { - struct commit *parent = parents->item; - if (!(parent->object.flags & UNINTERESTING)) - continue; - mark_tree_uninteresting(parent->tree); - if (revs.edge_hint && !(parent->object.flags & SHOWN)) { - parent->object.flags |= SHOWN; - printf("-%s\n", sha1_to_hex(parent->object.sha1)); - } - } -} + char line[1000]; -static void mark_edges_uninteresting(struct commit_list *list) -{ - for ( ; list; list = list->next) { - struct commit *commit = list->item; - - if (commit->object.flags & UNINTERESTING) { - mark_tree_uninteresting(commit->tree); - continue; - } - mark_edge_parents_uninteresting(commit); + while (fgets(line, sizeof(line), stdin) != NULL) { + int len = strlen(line); + if (line[len - 1] == '\n') + line[--len] = 0; + if (!len) + break; + if (line[0] == '-') + die("options not supported in --stdin mode"); + if (handle_revision_arg(line, revs, 0, 1)) + die("bad revision '%s'", line); } } @@ -310,6 +218,7 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) { struct commit_list *list; int i; + int read_from_stdin = 0; init_revisions(&revs, prefix); revs.abbrev = 0; @@ -331,6 +240,12 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) bisect_list = 1; continue; } + if (!strcmp(arg, "--stdin")) { + if (read_from_stdin++) + die("--stdin given twice?"); + read_revisions_from_stdin(&revs); + continue; + } usage(rev_list_usage); } @@ -361,12 +276,12 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) prepare_revision_walk(&revs); if (revs.tree_objects) - mark_edges_uninteresting(revs.commits); + mark_edges_uninteresting(revs.commits, &revs, show_edge); if (bisect_list) revs.commits = find_bisection(revs.commits); - show_commit_list(&revs); + traverse_commit_list(&revs, show_commit, show_object); return 0; } diff --git a/builtin-rev-parse.c b/builtin-rev-parse.c index aca4a36032..fd3ccc8546 100644 --- a/builtin-rev-parse.c +++ b/builtin-rev-parse.c @@ -15,16 +15,16 @@ #define DO_NONFLAGS 8 static int filter = ~0; -static const char *def = NULL; +static const char *def; #define NORMAL 0 #define REVERSED 1 static int show_type = NORMAL; -static int symbolic = 0; -static int abbrev = 0; -static int output_sq = 0; +static int symbolic; +static int abbrev; +static int output_sq; -static int revs_count = 0; +static int revs_count; /* * Some arguments are relevant "revision" arguments, diff --git a/builtin-rm.c b/builtin-rm.c index 593d86744c..33d04bd015 100644 --- a/builtin-rm.c +++ b/builtin-rm.c @@ -32,7 +32,7 @@ static int remove_file(const char *name) ret = unlink(name); if (!ret && (slash = strrchr(name, '/'))) { - char *n = strdup(name); + char *n = xstrdup(name); do { n[slash - name] = 0; name = n; diff --git a/builtin-runstatus.c b/builtin-runstatus.c new file mode 100644 index 0000000000..303c556da0 --- /dev/null +++ b/builtin-runstatus.c @@ -0,0 +1,36 @@ +#include "wt-status.h" +#include "cache.h" + +extern int wt_status_use_color; + +static const char runstatus_usage[] = +"git-runstatus [--color|--nocolor] [--amend] [--verbose]"; + +int cmd_runstatus(int argc, const char **argv, const char *prefix) +{ + struct wt_status s; + int i; + + git_config(git_status_config); + wt_status_prepare(&s); + + for (i = 1; i < argc; i++) { + if (!strcmp(argv[i], "--color")) + wt_status_use_color = 1; + else if (!strcmp(argv[i], "--nocolor")) + wt_status_use_color = 0; + else if (!strcmp(argv[i], "--amend")) { + s.amend = 1; + s.reference = "HEAD^1"; + } + else if (!strcmp(argv[i], "--verbose")) + s.verbose = 1; + else if (!strcmp(argv[i], "--untracked")) + s.untracked = 1; + else + usage(runstatus_usage); + } + + wt_status_print(&s); + return s.commitable ? 0 : 1; +} diff --git a/builtin-show-branch.c b/builtin-show-branch.c index 2a1b848f6c..578c9fafd0 100644 --- a/builtin-show-branch.c +++ b/builtin-show-branch.c @@ -8,9 +8,9 @@ static const char show_branch_usage[] = "git-show-branch [--sparse] [--current] [--all] [--heads] [--tags] [--topo-order] [--more=count | --list | --independent | --merge-base ] [--topics] [<refs>...]"; -static int default_num = 0; -static int default_alloc = 0; -static const char **default_arg = NULL; +static int default_num; +static int default_alloc; +static const char **default_arg; #define UNINTERESTING 01 @@ -163,7 +163,7 @@ static void name_commits(struct commit_list *list, en += sprintf(en, "^"); else en += sprintf(en, "^%d", nth); - name_commit(p, strdup(newname), 0); + name_commit(p, xstrdup(newname), 0); i++; name_first_parent_chain(p); } @@ -261,7 +261,7 @@ static void show_one_commit(struct commit *commit, int no_name) struct commit_name *name = commit->util; if (commit->object.parsed) pretty_print_commit(CMIT_FMT_ONELINE, commit, ~0, - pretty, sizeof(pretty), 0, NULL, NULL); + pretty, sizeof(pretty), 0, NULL, NULL, 0); else strcpy(pretty, "(unavailable)"); if (!strncmp(pretty, "[PATCH] ", 8)) @@ -364,7 +364,7 @@ static int append_ref(const char *refname, const unsigned char *sha1) refname, MAX_REVS); return 0; } - ref_name[ref_name_cnt++] = strdup(refname); + ref_name[ref_name_cnt++] = xstrdup(refname); ref_name[ref_name_cnt] = NULL; return 0; } @@ -378,7 +378,7 @@ static int append_head_ref(const char *refname, const unsigned char *sha1) /* If both heads/foo and tags/foo exists, get_sha1 would * get confused. */ - if (get_sha1(refname + ofs, tmp) || memcmp(tmp, sha1, 20)) + if (get_sha1(refname + ofs, tmp) || hashcmp(tmp, sha1)) ofs = 5; return append_ref(refname + ofs, sha1); } @@ -442,7 +442,7 @@ static int rev_is_head(char *head_path, int headlen, char *name, { int namelen; if ((!head_path[0]) || - (head_sha1 && sha1 && memcmp(head_sha1, sha1, 20))) + (head_sha1 && sha1 && hashcmp(head_sha1, sha1))) return 0; namelen = strlen(name); if ((headlen < namelen) || @@ -521,7 +521,7 @@ static int git_show_branch_config(const char *var, const char *value) default_alloc = default_alloc * 3 / 2 + 20; default_arg = xrealloc(default_arg, sizeof *default_arg * default_alloc); } - default_arg[default_num++] = strdup(value); + default_arg[default_num++] = xstrdup(value); default_arg[default_num] = NULL; return 0; } diff --git a/builtin-symbolic-ref.c b/builtin-symbolic-ref.c index b4ec6f28ed..1d3a5e229a 100644 --- a/builtin-symbolic-ref.c +++ b/builtin-symbolic-ref.c @@ -7,7 +7,7 @@ static const char git_symbolic_ref_usage[] = static void check_symref(const char *HEAD) { unsigned char sha1[20]; - const char *git_HEAD = strdup(git_path("%s", HEAD)); + const char *git_HEAD = xstrdup(git_path("%s", HEAD)); const char *git_refs_heads_master = resolve_ref(git_HEAD, sha1, 0); if (git_refs_heads_master) { /* we want to strip the .git/ part */ @@ -26,7 +26,7 @@ int cmd_symbolic_ref(int argc, const char **argv, const char *prefix) check_symref(argv[1]); break; case 3: - create_symref(strdup(git_path("%s", argv[1])), argv[2]); + create_symref(xstrdup(git_path("%s", argv[1])), argv[2]); break; default: usage(git_symbolic_ref_usage); diff --git a/builtin-tar-tree.c b/builtin-tar-tree.c index 215892b696..437eb726a9 100644 --- a/builtin-tar-tree.c +++ b/builtin-tar-tree.c @@ -3,49 +3,31 @@ */ #include <time.h> #include "cache.h" -#include "tree-walk.h" #include "commit.h" #include "strbuf.h" #include "tar.h" #include "builtin.h" #include "pkt-line.h" +#include "archive.h" #define RECORDSIZE (512) #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; 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; - } -} +static int verbose; /* 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 +52,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 +76,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); } } @@ -187,6 +169,8 @@ static void write_entry(const unsigned char *sha1, struct strbuf *path, mode = 0100666; sprintf(header.name, "%s.paxheader", sha1_to_hex(sha1)); } else { + if (verbose) + fprintf(stderr, "%.*s\n", path->len, path->buf); if (S_ISDIR(mode)) { *header.typeflag = TYPEFLAG_DIR; mode = (mode | 0777) & ~tar_umask; @@ -263,38 +247,7 @@ static void write_global_extended_header(const unsigned char *sha1) free(ext_header.buf); } -static void traverse_tree(struct tree_desc *tree, struct strbuf *path) -{ - int pathlen = path->len; - struct name_entry entry; - - while (tree_entry(tree, &entry)) { - void *eltbuf; - char elttype[20]; - unsigned long eltsize; - - eltbuf = read_sha1_file(entry.sha1, elttype, &eltsize); - if (!eltbuf) - die("cannot read %s", sha1_to_hex(entry.sha1)); - - path->len = pathlen; - strbuf_append_string(path, entry.path); - if (S_ISDIR(entry.mode)) - strbuf_append_string(path, "/"); - - write_entry(entry.sha1, path, entry.mode, eltbuf, eltsize); - - if (S_ISDIR(entry.mode)) { - struct tree_desc subtree; - subtree.buf = eltbuf; - subtree.size = eltsize; - traverse_tree(&subtree, path); - } - free(eltbuf); - } -} - -int git_tar_config(const char *var, const char *value) +static int git_tar_config(const char *var, const char *value) { if (!strcmp(var, "tar.umask")) { if (!strcmp(value, "user")) { @@ -310,50 +263,95 @@ int git_tar_config(const char *var, const char *value) static int generate_tar(int argc, const char **argv, const char *prefix) { - unsigned char sha1[20], tree_sha1[20]; - struct commit *commit; - struct tree_desc tree; - struct strbuf current_path; - void *buffer; - - current_path.buf = xmalloc(PATH_MAX); - current_path.alloc = PATH_MAX; - current_path.len = current_path.eof = 0; + struct archiver_args args; + int result; + char *base = NULL; git_config(git_tar_config); - switch (argc) { - case 3: - strbuf_append_string(¤t_path, argv[2]); - strbuf_append_string(¤t_path, "/"); - /* FALLTHROUGH */ - case 2: - if (get_sha1(argv[1], sha1)) - die("Not a valid object name %s", argv[1]); - break; - default: + memset(&args, 0, sizeof(args)); + if (argc != 2 && argc != 3) usage(tar_tree_usage); + if (argc == 3) { + int baselen = strlen(argv[2]); + base = xmalloc(baselen + 2); + memcpy(base, argv[2], baselen); + base[baselen] = '/'; + base[baselen + 1] = '\0'; } + args.base = base; + parse_treeish_arg(argv + 1, &args, NULL); - commit = lookup_commit_reference_gently(sha1, 1); - if (commit) { - write_global_extended_header(commit->object.sha1); - archive_time = commit->date; - } else - archive_time = time(NULL); - - tree.buf = buffer = read_object_with_reference(sha1, tree_type, - &tree.size, tree_sha1); - if (!tree.buf) - die("not a reference to a tag, commit or tree object: %s", - sha1_to_hex(sha1)); - - if (current_path.len > 0) - write_entry(tree_sha1, ¤t_path, 040777, NULL, 0); - traverse_tree(&tree, ¤t_path); - write_trailer(); + result = write_tar_archive(&args); + free(base); + + return result; +} + +static int write_tar_entry(const unsigned char *sha1, + const char *base, int baselen, + const char *filename, unsigned mode, int stage) +{ + static struct strbuf path; + int filenamelen = strlen(filename); + void *buffer; + char type[20]; + unsigned long size; + + if (!path.alloc) { + path.buf = xmalloc(PATH_MAX); + path.alloc = PATH_MAX; + path.len = path.eof = 0; + } + if (path.alloc < baselen + filenamelen) { + free(path.buf); + path.buf = xmalloc(baselen + filenamelen); + path.alloc = baselen + filenamelen; + } + memcpy(path.buf, base, baselen); + memcpy(path.buf + baselen, filename, filenamelen); + path.len = baselen + filenamelen; + if (S_ISDIR(mode)) { + strbuf_append_string(&path, "/"); + buffer = NULL; + size = 0; + } else { + buffer = read_sha1_file(sha1, type, &size); + if (!buffer) + die("cannot read %s", sha1_to_hex(sha1)); + } + + write_entry(sha1, &path, mode, buffer, size); free(buffer); - free(current_path.buf); + + return READ_TREE_RECURSIVE; +} + +int write_tar_archive(struct archiver_args *args) +{ + int plen = args->base ? strlen(args->base) : 0; + + git_config(git_tar_config); + + archive_time = args->time; + verbose = args->verbose; + + if (args->commit_sha1) + write_global_extended_header(args->commit_sha1); + + if (args->base && plen > 0 && args->base[plen - 1] == '/') { + char *base = xstrdup(args->base); + int baselen = strlen(base); + + while (baselen > 0 && base[baselen - 1] == '/') + base[--baselen] = '\0'; + write_tar_entry(args->tree->object.sha1, "", 0, base, 040777, 0); + free(base); + } + read_tree_recursive(args->tree, args->base, plen, 0, + args->pathspec, write_tar_entry); + write_trailer(); + return 0; } @@ -370,7 +368,7 @@ static int remote_tar(int argc, const char **argv) usage(tar_tree_usage); /* --remote=<repo> */ - url = strdup(argv[1]+9); + url = xstrdup(argv[1]+9); pid = git_connect(fd, url, exec); if (pid < 0) return 1; diff --git a/builtin-unpack-objects.c b/builtin-unpack-objects.c index 63f4b8e45d..4f96bcae32 100644 --- a/builtin-unpack-objects.c +++ b/builtin-unpack-objects.c @@ -10,12 +10,12 @@ #include <sys/time.h> -static int dry_run, quiet; -static const char unpack_usage[] = "git-unpack-objects [-n] [-q] < pack-file"; +static int dry_run, quiet, recover, has_errors; +static const char unpack_usage[] = "git-unpack-objects [-n] [-q] [-r] < pack-file"; /* We always read in 4kB chunks. */ static unsigned char buffer[4096]; -static unsigned long offset, len, eof; +static unsigned long offset, len; static SHA_CTX ctx; /* @@ -26,8 +26,6 @@ static void * fill(int min) { if (min <= len) return buffer + offset; - if (eof) - die("unable to fill input"); if (min > sizeof(buffer)) die("cannot fill %d bytes", min); if (offset) { @@ -73,8 +71,15 @@ static void *get_data(unsigned long size) use(len - stream.avail_in); if (stream.total_out == size && ret == Z_STREAM_END) break; - if (ret != Z_OK) - die("inflate returned %d\n", ret); + if (ret != Z_OK) { + error("inflate returned %d\n", ret); + free(buf); + buf = NULL; + if (!recover) + exit(1); + has_errors = 1; + break; + } stream.next_in = fill(1); stream.avail_in = len; } @@ -95,7 +100,7 @@ static void add_delta_to_list(unsigned char *base_sha1, void *delta, unsigned lo { struct delta_info *info = xmalloc(sizeof(*info)); - memcpy(info->base_sha1, base_sha1, 20); + hashcpy(info->base_sha1, base_sha1); info->size = size; info->delta = delta; info->next = delta_list; @@ -112,9 +117,9 @@ static void write_object(void *buf, unsigned long size, const char *type) added_object(sha1, type, buf, size); } -static int resolve_delta(const char *type, - void *base, unsigned long base_size, - void *delta, unsigned long delta_size) +static void resolve_delta(const char *type, + void *base, unsigned long base_size, + void *delta, unsigned long delta_size) { void *result; unsigned long result_size; @@ -127,7 +132,6 @@ static int resolve_delta(const char *type, free(delta); write_object(result, result_size, type); free(result); - return 0; } static void added_object(unsigned char *sha1, const char *type, void *data, unsigned long size) @@ -136,7 +140,7 @@ static void added_object(unsigned char *sha1, const char *type, void *data, unsi struct delta_info *info; while ((info = *p) != NULL) { - if (!memcmp(info->base_sha1, sha1, 20)) { + if (!hashcmp(info->base_sha1, sha1)) { *p = info->next; p = &delta_list; resolve_delta(type, data, size, info->delta, info->size); @@ -147,7 +151,7 @@ static void added_object(unsigned char *sha1, const char *type, void *data, unsi } } -static int unpack_non_delta_entry(enum object_type kind, unsigned long size) +static void unpack_non_delta_entry(enum object_type kind, unsigned long size) { void *buf = get_data(size); const char *type; @@ -159,39 +163,42 @@ static int unpack_non_delta_entry(enum object_type kind, unsigned long size) case OBJ_TAG: type = tag_type; break; default: die("bad type %d", kind); } - if (!dry_run) + if (!dry_run && buf) write_object(buf, size, type); free(buf); - return 0; } -static int unpack_delta_entry(unsigned long delta_size) +static void unpack_delta_entry(unsigned long delta_size) { void *delta_data, *base; unsigned long base_size; char type[20]; unsigned char base_sha1[20]; - int result; - memcpy(base_sha1, fill(20), 20); + hashcpy(base_sha1, fill(20)); use(20); delta_data = get_data(delta_size); - if (dry_run) { + if (dry_run || !delta_data) { free(delta_data); - return 0; + return; } if (!has_sha1_file(base_sha1)) { add_delta_to_list(base_sha1, delta_data, delta_size); - return 0; + return; } base = read_sha1_file(base_sha1, type, &base_size); - if (!base) - die("failed to read delta-pack base object %s", sha1_to_hex(base_sha1)); - result = resolve_delta(type, base, base_size, delta_data, delta_size); + if (!base) { + error("failed to read delta-pack base object %s", + sha1_to_hex(base_sha1)); + if (!recover) + exit(1); + has_errors = 1; + return; + } + resolve_delta(type, base, base_size, delta_data, delta_size); free(base); - return result; } static void unpack_one(unsigned nr, unsigned total) @@ -238,7 +245,11 @@ static void unpack_one(unsigned nr, unsigned total) unpack_delta_entry(size); return; default: - die("bad object type %d", type); + error("bad object type %d", type); + has_errors = 1; + if (recover) + return; + exit(1); } } @@ -282,6 +293,10 @@ int cmd_unpack_objects(int argc, const char **argv, const char *prefix) quiet = 1; continue; } + if (!strcmp(arg, "-r")) { + recover = 1; + continue; + } usage(unpack_usage); } @@ -292,7 +307,7 @@ int cmd_unpack_objects(int argc, const char **argv, const char *prefix) unpack_all(); SHA1_Update(&ctx, buffer, offset); SHA1_Final(sha1, &ctx); - if (memcmp(fill(20), sha1, 20)) + if (hashcmp(fill(20), sha1)) die("final sha1 did not match"); use(20); @@ -308,5 +323,5 @@ int cmd_unpack_objects(int argc, const char **argv, const char *prefix) /* All done */ if (!quiet) fprintf(stderr, "\n"); - return 0; + return has_errors; } diff --git a/builtin-update-index.c b/builtin-update-index.c index d2556f376b..0620e779b0 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 5bd71825fd..90a3da53ad 100644 --- a/builtin-update-ref.c +++ b/builtin-update-ref.c @@ -44,7 +44,7 @@ int cmd_update_ref(int argc, const char **argv, const char *prefix) if (get_sha1(value, sha1)) die("%s: not a valid SHA1", value); - memset(oldsha1, 0, 20); + hashclr(oldsha1); if (oldval && get_sha1(oldval, oldsha1)) die("%s: not a valid old SHA1", oldval); diff --git a/builtin-upload-archive.c b/builtin-upload-archive.c new file mode 100644 index 0000000000..0596865679 --- /dev/null +++ b/builtin-upload-archive.c @@ -0,0 +1,175 @@ +/* + * Copyright (c) 2006 Franck Bui-Huu + */ +#include <time.h> +#include "cache.h" +#include "builtin.h" +#include "archive.h" +#include "pkt-line.h" +#include "sideband.h" +#include <sys/wait.h> +#include <sys/poll.h> + +static const char upload_archive_usage[] = + "git-upload-archive <repo>"; + +static const char deadchild[] = +"git-upload-archive: archiver died with error"; + +static const char lostchild[] = +"git-upload-archive: archiver process was lost"; + + +static int run_upload_archive(int argc, const char **argv, const char *prefix) +{ + struct archiver ar; + const char *sent_argv[MAX_ARGS]; + const char *arg_cmd = "argument "; + char *p, buf[4096]; + int treeish_idx; + int sent_argc; + int len; + + if (argc != 2) + usage(upload_archive_usage); + + if (strlen(argv[1]) > sizeof(buf)) + die("insanely long repository name"); + + strcpy(buf, argv[1]); /* enter-repo smudges its argument */ + + if (!enter_repo(buf, 0)) + die("not a git archive"); + + /* put received options in sent_argv[] */ + sent_argc = 1; + sent_argv[0] = "git-upload-archive"; + for (p = buf;;) { + /* This will die if not enough free space in buf */ + len = packet_read_line(0, p, (buf + sizeof buf) - p); + if (len == 0) + break; /* got a flush */ + if (sent_argc > MAX_ARGS - 2) + die("Too many options (>29)"); + + if (p[len-1] == '\n') { + p[--len] = 0; + } + if (len < strlen(arg_cmd) || + strncmp(arg_cmd, p, strlen(arg_cmd))) + die("'argument' token or flush expected"); + + len -= strlen(arg_cmd); + memmove(p, p + strlen(arg_cmd), len); + sent_argv[sent_argc++] = p; + p += len; + *p++ = 0; + } + sent_argv[sent_argc] = NULL; + + /* parse all options sent by the client */ + treeish_idx = parse_archive_args(sent_argc, sent_argv, &ar); + + parse_treeish_arg(sent_argv + treeish_idx, &ar.args, prefix); + parse_pathspec_arg(sent_argv + treeish_idx + 1, &ar.args); + + return ar.write_archive(&ar.args); +} + +static void error_clnt(const char *fmt, ...) +{ + char buf[1024]; + va_list params; + int len; + + va_start(params, fmt); + len = vsprintf(buf, fmt, params); + va_end(params); + send_sideband(1, 3, buf, len, LARGE_PACKET_MAX); + die("sent error to the client: %s", buf); +} + +static void process_input(int child_fd, int band) +{ + char buf[16384]; + ssize_t sz = read(child_fd, buf, sizeof(buf)); + if (sz < 0) { + if (errno != EINTR) + error_clnt("read error: %s\n", strerror(errno)); + return; + } + send_sideband(1, band, buf, sz, LARGE_PACKET_MAX); +} + +int cmd_upload_archive(int argc, const char **argv, const char *prefix) +{ + pid_t writer; + int fd1[2], fd2[2]; + /* + * Set up sideband subprocess. + * + * We (parent) monitor and read from child, sending its fd#1 and fd#2 + * multiplexed out to our fd#1. If the child dies, we tell the other + * end over channel #3. + */ + if (pipe(fd1) < 0 || pipe(fd2) < 0) { + int err = errno; + packet_write(1, "NACK pipe failed on the remote side\n"); + die("upload-archive: %s", strerror(err)); + } + writer = fork(); + if (writer < 0) { + int err = errno; + packet_write(1, "NACK fork failed on the remote side\n"); + die("upload-archive: %s", strerror(err)); + } + if (!writer) { + /* child - connect fd#1 and fd#2 to the pipe */ + dup2(fd1[1], 1); + dup2(fd2[1], 2); + close(fd1[1]); close(fd2[1]); + close(fd1[0]); close(fd2[0]); /* we do not read from pipe */ + + exit(run_upload_archive(argc, argv, prefix)); + } + + /* parent - read from child, multiplex and send out to fd#1 */ + close(fd1[1]); close(fd2[1]); /* we do not write to pipe */ + packet_write(1, "ACK\n"); + packet_flush(1); + + while (1) { + struct pollfd pfd[2]; + int status; + + pfd[0].fd = fd1[0]; + pfd[0].events = POLLIN; + pfd[1].fd = fd2[0]; + pfd[1].events = POLLIN; + if (poll(pfd, 2, -1) < 0) { + if (errno != EINTR) { + error("poll failed resuming: %s", + strerror(errno)); + sleep(1); + } + continue; + } + if (pfd[0].revents & POLLIN) + /* Data stream ready */ + process_input(pfd[0].fd, 1); + if (pfd[1].revents & POLLIN) + /* Status stream ready */ + process_input(pfd[1].fd, 2); + /* Always finish to read data when available */ + if ((pfd[0].revents | pfd[1].revents) & POLLIN) + continue; + + if (waitpid(writer, &status, 0) < 0) + error_clnt("%s", lostchild); + else if (!WIFEXITED(status) || WEXITSTATUS(status) > 0) + error_clnt("%s", deadchild); + packet_flush(1); + break; + } + return 0; +} diff --git a/builtin-upload-tar.c b/builtin-upload-tar.c index 7b401bbb77..06a945a4b1 100644 --- a/builtin-upload-tar.c +++ b/builtin-upload-tar.c @@ -53,7 +53,7 @@ int cmd_upload_tar(int argc, const char **argv, const char *prefix) return nak("expected (optional) base"); if (buf[len-1] == '\n') buf[--len] = 0; - base = strdup(buf + 5); + base = xstrdup(buf + 5); len = packet_read_line(0, buf, sizeof(buf)); } if (len) diff --git a/builtin-write-tree.c b/builtin-write-tree.c index ca06149f18..50670dc7bf 100644 --- a/builtin-write-tree.c +++ b/builtin-write-tree.c @@ -50,10 +50,10 @@ int write_tree(unsigned char *sha1, int missing_ok, const char *prefix) if (prefix) { struct cache_tree *subtree = cache_tree_find(active_cache_tree, prefix); - memcpy(sha1, subtree->sha1, 20); + hashcpy(sha1, subtree->sha1); } else - memcpy(sha1, active_cache_tree->sha1, 20); + hashcpy(sha1, active_cache_tree->sha1); rollback_lock_file(lock_file); diff --git a/builtin-zip-tree.c b/builtin-zip-tree.c new file mode 100644 index 0000000000..52d4b7a17e --- /dev/null +++ b/builtin-zip-tree.c @@ -0,0 +1,398 @@ +/* + * Copyright (c) 2006 Rene Scharfe + */ +#include <time.h> +#include "cache.h" +#include "commit.h" +#include "blob.h" +#include "tree.h" +#include "quote.h" +#include "builtin.h" +#include "archive.h" + +static const char zip_tree_usage[] = +"git-zip-tree [-0|...|-9] <tree-ish> [ <base> ]"; + +static int verbose; +static int zip_date; +static int zip_time; + +static unsigned char *zip_dir; +static unsigned int zip_dir_size; + +static unsigned int zip_offset; +static unsigned int zip_dir_offset; +static unsigned int zip_dir_entries; + +#define ZIP_DIRECTORY_MIN_SIZE (1024 * 1024) + +struct zip_local_header { + unsigned char magic[4]; + unsigned char version[2]; + unsigned char flags[2]; + unsigned char compression_method[2]; + unsigned char mtime[2]; + unsigned char mdate[2]; + unsigned char crc32[4]; + unsigned char compressed_size[4]; + unsigned char size[4]; + unsigned char filename_length[2]; + unsigned char extra_length[2]; +}; + +struct zip_dir_header { + unsigned char magic[4]; + unsigned char creator_version[2]; + unsigned char version[2]; + unsigned char flags[2]; + unsigned char compression_method[2]; + unsigned char mtime[2]; + unsigned char mdate[2]; + unsigned char crc32[4]; + unsigned char compressed_size[4]; + unsigned char size[4]; + unsigned char filename_length[2]; + unsigned char extra_length[2]; + unsigned char comment_length[2]; + unsigned char disk[2]; + unsigned char attr1[2]; + unsigned char attr2[4]; + unsigned char offset[4]; +}; + +struct zip_dir_trailer { + unsigned char magic[4]; + unsigned char disk[2]; + unsigned char directory_start_disk[2]; + unsigned char entries_on_this_disk[2]; + unsigned char entries[2]; + unsigned char size[4]; + unsigned char offset[4]; + unsigned char comment_length[2]; +}; + +static void copy_le16(unsigned char *dest, unsigned int n) +{ + dest[0] = 0xff & n; + dest[1] = 0xff & (n >> 010); +} + +static void copy_le32(unsigned char *dest, unsigned int n) +{ + dest[0] = 0xff & n; + dest[1] = 0xff & (n >> 010); + dest[2] = 0xff & (n >> 020); + dest[3] = 0xff & (n >> 030); +} + +static void *zlib_deflate(void *data, unsigned long size, + unsigned long *compressed_size) +{ + z_stream stream; + unsigned long maxsize; + void *buffer; + int result; + + memset(&stream, 0, sizeof(stream)); + deflateInit(&stream, zlib_compression_level); + maxsize = deflateBound(&stream, size); + buffer = xmalloc(maxsize); + + stream.next_in = data; + stream.avail_in = size; + stream.next_out = buffer; + stream.avail_out = maxsize; + + do { + result = deflate(&stream, Z_FINISH); + } while (result == Z_OK); + + if (result != Z_STREAM_END) { + free(buffer); + return NULL; + } + + deflateEnd(&stream); + *compressed_size = stream.total_out; + + return buffer; +} + +static char *construct_path(const char *base, int baselen, + const char *filename, int isdir, int *pathlen) +{ + int filenamelen = strlen(filename); + int len = baselen + filenamelen; + char *path, *p; + + if (isdir) + len++; + p = path = xmalloc(len + 1); + + memcpy(p, base, baselen); + p += baselen; + memcpy(p, filename, filenamelen); + p += filenamelen; + if (isdir) + *p++ = '/'; + *p = '\0'; + + *pathlen = len; + + return path; +} + +static int write_zip_entry(const unsigned char *sha1, + const char *base, int baselen, + const char *filename, unsigned mode, int stage) +{ + struct zip_local_header header; + struct zip_dir_header dirent; + unsigned long compressed_size; + unsigned long uncompressed_size; + unsigned long crc; + unsigned long direntsize; + unsigned long size; + int method; + int result = -1; + int pathlen; + unsigned char *out; + char *path; + char type[20]; + void *buffer = NULL; + void *deflated = NULL; + + crc = crc32(0, Z_NULL, 0); + + path = construct_path(base, baselen, filename, S_ISDIR(mode), &pathlen); + if (verbose) + fprintf(stderr, "%s\n", path); + if (pathlen > 0xffff) { + error("path too long (%d chars, SHA1: %s): %s", pathlen, + sha1_to_hex(sha1), path); + goto out; + } + + if (S_ISDIR(mode)) { + method = 0; + result = READ_TREE_RECURSIVE; + out = NULL; + uncompressed_size = 0; + compressed_size = 0; + } else if (S_ISREG(mode)) { + method = zlib_compression_level == 0 ? 0 : 8; + result = 0; + buffer = read_sha1_file(sha1, type, &size); + if (!buffer) + die("cannot read %s", sha1_to_hex(sha1)); + crc = crc32(crc, buffer, size); + out = buffer; + uncompressed_size = size; + compressed_size = size; + } else { + error("unsupported file mode: 0%o (SHA1: %s)", mode, + sha1_to_hex(sha1)); + goto out; + } + + if (method == 8) { + deflated = zlib_deflate(buffer, size, &compressed_size); + if (deflated && compressed_size - 6 < size) { + /* ZLIB --> raw compressed data (see RFC 1950) */ + /* CMF and FLG ... */ + out = (unsigned char *)deflated + 2; + compressed_size -= 6; /* ... and ADLER32 */ + } else { + method = 0; + compressed_size = size; + } + } + + /* make sure we have enough free space in the dictionary */ + direntsize = sizeof(struct zip_dir_header) + pathlen; + while (zip_dir_size < zip_dir_offset + direntsize) { + zip_dir_size += ZIP_DIRECTORY_MIN_SIZE; + zip_dir = xrealloc(zip_dir, zip_dir_size); + } + + copy_le32(dirent.magic, 0x02014b50); + copy_le16(dirent.creator_version, 0); + copy_le16(dirent.version, 20); + copy_le16(dirent.flags, 0); + copy_le16(dirent.compression_method, method); + copy_le16(dirent.mtime, zip_time); + copy_le16(dirent.mdate, zip_date); + copy_le32(dirent.crc32, crc); + copy_le32(dirent.compressed_size, compressed_size); + copy_le32(dirent.size, uncompressed_size); + copy_le16(dirent.filename_length, pathlen); + copy_le16(dirent.extra_length, 0); + copy_le16(dirent.comment_length, 0); + copy_le16(dirent.disk, 0); + copy_le16(dirent.attr1, 0); + copy_le32(dirent.attr2, 0); + copy_le32(dirent.offset, zip_offset); + memcpy(zip_dir + zip_dir_offset, &dirent, sizeof(struct zip_dir_header)); + zip_dir_offset += sizeof(struct zip_dir_header); + memcpy(zip_dir + zip_dir_offset, path, pathlen); + zip_dir_offset += pathlen; + zip_dir_entries++; + + copy_le32(header.magic, 0x04034b50); + copy_le16(header.version, 20); + copy_le16(header.flags, 0); + copy_le16(header.compression_method, method); + copy_le16(header.mtime, zip_time); + copy_le16(header.mdate, zip_date); + copy_le32(header.crc32, crc); + copy_le32(header.compressed_size, compressed_size); + copy_le32(header.size, uncompressed_size); + copy_le16(header.filename_length, pathlen); + copy_le16(header.extra_length, 0); + write_or_die(1, &header, sizeof(struct zip_local_header)); + zip_offset += sizeof(struct zip_local_header); + write_or_die(1, path, pathlen); + zip_offset += pathlen; + if (compressed_size > 0) { + write_or_die(1, out, compressed_size); + zip_offset += compressed_size; + } + +out: + free(buffer); + free(deflated); + free(path); + + return result; +} + +static void write_zip_trailer(const unsigned char *sha1) +{ + struct zip_dir_trailer trailer; + + copy_le32(trailer.magic, 0x06054b50); + copy_le16(trailer.disk, 0); + copy_le16(trailer.directory_start_disk, 0); + copy_le16(trailer.entries_on_this_disk, zip_dir_entries); + copy_le16(trailer.entries, zip_dir_entries); + copy_le32(trailer.size, zip_dir_offset); + copy_le32(trailer.offset, zip_offset); + copy_le16(trailer.comment_length, sha1 ? 40 : 0); + + write_or_die(1, zip_dir, zip_dir_offset); + write_or_die(1, &trailer, sizeof(struct zip_dir_trailer)); + if (sha1) + write_or_die(1, sha1_to_hex(sha1), 40); +} + +static void dos_time(time_t *time, int *dos_date, int *dos_time) +{ + struct tm *t = localtime(time); + + *dos_date = t->tm_mday + (t->tm_mon + 1) * 32 + + (t->tm_year + 1900 - 1980) * 512; + *dos_time = t->tm_sec / 2 + t->tm_min * 32 + t->tm_hour * 2048; +} + +int 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 = xstrdup(argv[2]); + baselen = strlen(base); + break; + case 2: + base = xstrdup(""); + 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; +} + +int write_zip_archive(struct archiver_args *args) +{ + int plen = strlen(args->base); + + dos_time(&args->time, &zip_date, &zip_time); + + zip_dir = xmalloc(ZIP_DIRECTORY_MIN_SIZE); + zip_dir_size = ZIP_DIRECTORY_MIN_SIZE; + verbose = args->verbose; + + if (args->base && plen > 0 && args->base[plen - 1] == '/') { + char *base = xstrdup(args->base); + int baselen = strlen(base); + + while (baselen > 0 && base[baselen - 1] == '/') + base[--baselen] = '\0'; + write_zip_entry(args->tree->object.sha1, "", 0, base, 040777, 0); + free(base); + } + read_tree_recursive(args->tree, args->base, plen, 0, + args->pathspec, write_zip_entry); + write_zip_trailer(args->commit_sha1); + + free(zip_dir); + + return 0; +} + +void *parse_extra_zip_args(int argc, const char **argv) +{ + for (; argc > 0; argc--, argv++) { + const char *arg = argv[0]; + + if (arg[0] == '-' && isdigit(arg[1]) && arg[2] == '\0') + zlib_compression_level = arg[1] - '0'; + else + die("Unknown argument for zip format: %s", arg); + } + return NULL; +} @@ -1,8 +1,7 @@ #ifndef BUILTIN_H #define BUILTIN_H -#include <stdio.h> -#include <limits.h> +#include "git-compat-util.h" extern const char git_version_string[]; extern const char git_usage_string[]; @@ -15,6 +14,7 @@ extern int write_tree(unsigned char *sha1, int missing_ok, const char *prefix); extern int cmd_add(int argc, const char **argv, const char *prefix); extern int cmd_apply(int argc, const char **argv, const char *prefix); +extern int cmd_archive(int argc, const char **argv, const char *prefix); extern int cmd_cat_file(int argc, const char **argv, const char *prefix); extern int cmd_checkout_index(int argc, const char **argv, const char *prefix); extern int cmd_check_ref_format(int argc, const char **argv, const char *prefix); @@ -47,14 +47,17 @@ extern int cmd_repo_config(int argc, const char **argv, const char *prefix); extern int cmd_rev_list(int argc, const char **argv, const char *prefix); extern int cmd_rev_parse(int argc, const char **argv, const char *prefix); extern int cmd_rm(int argc, const char **argv, const char *prefix); +extern int cmd_runstatus(int argc, const char **argv, const char *prefix); extern int cmd_show_branch(int argc, const char **argv, const char *prefix); extern int cmd_show(int argc, const char **argv, const char *prefix); extern int cmd_stripspace(int argc, const char **argv, const char *prefix); extern int cmd_symbolic_ref(int argc, const char **argv, const char *prefix); extern int cmd_tar_tree(int argc, const char **argv, const char *prefix); +extern int cmd_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_archive(int argc, const char **argv, const char *prefix); extern int cmd_upload_tar(int argc, const char **argv, const char *prefix); extern int cmd_version(int argc, const char **argv, const char *prefix); extern int cmd_whatchanged(int argc, const char **argv, const char *prefix); diff --git a/cache-tree.c b/cache-tree.c index d9f7e1e3dd..323c68a670 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); @@ -145,6 +145,7 @@ extern void verify_non_filename(const char *prefix, const char *name); extern int read_cache(void); extern int read_cache_from(const char *path); extern int write_cache(int newfd, struct cache_entry **cache, int entries); +extern int discard_cache(void); extern int verify_path(const char *path); extern int cache_name_pos(const char *name, int namelen); #define ADD_CACHE_OK_TO_ADD 1 /* Ok to add */ @@ -210,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); @@ -240,9 +257,9 @@ extern int check_sha1_signature(const unsigned char *sha1, void *buf, unsigned l extern int write_sha1_from_fd(const unsigned char *sha1, int fd, char *buffer, size_t bufsize, size_t *bufposn); extern int write_sha1_to_fd(int fd, const unsigned char *sha1); -extern int move_temp_to_file(const char *tmpfile, char *filename); +extern int move_temp_to_file(const char *tmpfile, const char *filename); -extern int has_sha1_pack(const unsigned char *sha1); +extern int has_sha1_pack(const unsigned char *sha1, const char **ignore); extern int has_sha1_file(const unsigned char *sha1); extern void *map_sha1_file(const unsigned char *sha1, unsigned long *); extern int legacy_loose_object(unsigned char *); @@ -250,6 +267,17 @@ extern int legacy_loose_object(unsigned char *); extern int has_pack_file(const unsigned char *sha1); extern int has_pack_index(const unsigned char *sha1); +enum object_type { + OBJ_NONE = 0, + OBJ_COMMIT = 1, + OBJ_TREE = 2, + OBJ_BLOB = 3, + OBJ_TAG = 4, + /* 5/6 for future expansion */ + OBJ_DELTA = 7, + OBJ_BAD, +}; + /* Convert to/from hex/sha1 representation */ #define MINIMUM_ABBREV 4 #define DEFAULT_ABBREV 7 @@ -270,7 +298,7 @@ extern void *read_object_with_reference(const unsigned char *sha1, unsigned long *size, unsigned char *sha1_ret); -const char *show_date(unsigned long time, int timezone); +const char *show_date(unsigned long time, int timezone, int relative); const char *show_rfc2822_date(unsigned long time, int timezone); int parse_date(const char *date, char *buf, int bufsize); void datestamp(char *buf, int bufsize); @@ -331,7 +359,7 @@ struct ref { #define REF_HEADS (1u << 1) #define REF_TAGS (1u << 2) -extern int git_connect(int fd[2], char *url, const char *prog); +extern pid_t git_connect(int fd[2], char *url, const char *prog); extern int finish_connect(pid_t pid); extern int path_match(const char *path, int nr, char **match); extern int match_refs(struct ref *src, struct ref *dst, struct ref ***dst_tail, @@ -357,6 +385,7 @@ extern int num_packed_objects(const struct packed_git *p); extern int nth_packed_object_sha1(const struct packed_git *, int, unsigned char*); extern int find_pack_entry_one(const unsigned char *, struct pack_entry *, struct packed_git *); extern void *unpack_entry_gently(struct pack_entry *, char *, unsigned long *); +extern unsigned long unpack_object_header_gently(const unsigned char *buf, unsigned long len, enum object_type *type, unsigned long *sizep); extern void packed_object_info_detail(struct pack_entry *, char *, unsigned long *, unsigned long *, unsigned int *, unsigned char *); /* Dumb servers support */ @@ -380,6 +409,8 @@ extern char git_default_name[MAX_GITNAME]; extern char git_commit_encoding[MAX_ENCODING_LENGTH]; extern int copy_fd(int ifd, int ofd); +extern void write_or_die(int fd, const void *buf, size_t count); +extern int write_or_whine(int fd, const void *buf, size_t count, const char *msg); /* Finish off pack transfer receiving end */ extern int receive_unpack_pack(int fd[2], const char *me, int quiet, int); @@ -405,4 +436,9 @@ extern struct commit *alloc_commit_node(void); extern struct tag *alloc_tag_node(void); extern void alloc_report(void); +/* trace.c */ +extern int nfvasprintf(char **str, const char *fmt, va_list va); +extern void trace_printf(const char *format, ...); +extern void trace_argv_printf(const char **argv, int count, const char *format, ...); + #endif /* CACHE_H */ diff --git a/check-racy.c b/check-racy.c new file mode 100644 index 0000000000..d6a08b4a55 --- /dev/null +++ b/check-racy.c @@ -0,0 +1,28 @@ +#include "cache.h" + +int main(int ac, char **av) +{ + int i; + int dirty, clean, racy; + + dirty = clean = racy = 0; + read_cache(); + for (i = 0; i < active_nr; i++) { + struct cache_entry *ce = active_cache[i]; + struct stat st; + + if (lstat(ce->name, &st)) { + error("lstat(%s): %s", ce->name, strerror(errno)); + continue; + } + + if (ce_match_stat(ce, &st, 0)) + dirty++; + else if (ce_match_stat(ce, &st, 2)) + racy++; + else + clean++; + } + printf("dirty %d, clean %d, racy %d\n", dirty, clean, racy); + return 0; +} diff --git a/color.c b/color.c new file mode 100644 index 0000000000..d8c8399d59 --- /dev/null +++ b/color.c @@ -0,0 +1,176 @@ +#include "color.h" +#include "cache.h" +#include "git-compat-util.h" + +#include <stdarg.h> + +#define COLOR_RESET "\033[m" + +static int parse_color(const char *name, int len) +{ + static const char * const color_names[] = { + "normal", "black", "red", "green", "yellow", + "blue", "magenta", "cyan", "white" + }; + char *end; + int i; + for (i = 0; i < ARRAY_SIZE(color_names); i++) { + const char *str = color_names[i]; + if (!strncasecmp(name, str, len) && !str[len]) + return i - 1; + } + i = strtol(name, &end, 10); + if (*name && !*end && i >= -1 && i <= 255) + return i; + return -2; +} + +static int parse_attr(const char *name, int len) +{ + static const int attr_values[] = { 1, 2, 4, 5, 7 }; + static const char * const attr_names[] = { + "bold", "dim", "ul", "blink", "reverse" + }; + int i; + for (i = 0; i < ARRAY_SIZE(attr_names); i++) { + const char *str = attr_names[i]; + if (!strncasecmp(name, str, len) && !str[len]) + return attr_values[i]; + } + return -1; +} + +void color_parse(const char *value, const char *var, char *dst) +{ + const char *ptr = value; + int attr = -1; + int fg = -2; + int bg = -2; + + if (!strcasecmp(value, "reset")) { + strcpy(dst, "\033[m"); + return; + } + + /* [fg [bg]] [attr] */ + while (*ptr) { + const char *word = ptr; + int val, len = 0; + + while (word[len] && !isspace(word[len])) + len++; + + ptr = word + len; + while (*ptr && isspace(*ptr)) + ptr++; + + val = parse_color(word, len); + if (val >= -1) { + if (fg == -2) { + fg = val; + continue; + } + if (bg == -2) { + bg = val; + continue; + } + goto bad; + } + val = parse_attr(word, len); + if (val < 0 || attr != -1) + goto bad; + attr = val; + } + + if (attr >= 0 || fg >= 0 || bg >= 0) { + int sep = 0; + + *dst++ = '\033'; + *dst++ = '['; + if (attr >= 0) { + *dst++ = '0' + attr; + sep++; + } + if (fg >= 0) { + if (sep++) + *dst++ = ';'; + if (fg < 8) { + *dst++ = '3'; + *dst++ = '0' + fg; + } else { + dst += sprintf(dst, "38;5;%d", fg); + } + } + if (bg >= 0) { + if (sep++) + *dst++ = ';'; + if (bg < 8) { + *dst++ = '4'; + *dst++ = '0' + bg; + } else { + dst += sprintf(dst, "48;5;%d", bg); + } + } + *dst++ = 'm'; + } + *dst = 0; + return; +bad: + die("bad config value '%s' for variable '%s'", value, var); +} + +int git_config_colorbool(const char *var, const char *value) +{ + if (!value) + return 1; + if (!strcasecmp(value, "auto")) { + if (isatty(1) || (pager_in_use && pager_use_color)) { + char *term = getenv("TERM"); + if (term && strcmp(term, "dumb")) + return 1; + } + return 0; + } + if (!strcasecmp(value, "never")) + return 0; + if (!strcasecmp(value, "always")) + return 1; + return git_config_bool(var, value); +} + +static int color_vprintf(const char *color, const char *fmt, + va_list args, const char *trail) +{ + int r = 0; + + if (*color) + r += printf("%s", color); + r += vprintf(fmt, args); + if (*color) + r += printf("%s", COLOR_RESET); + if (trail) + r += printf("%s", trail); + return r; +} + + + +int color_printf(const char *color, const char *fmt, ...) +{ + va_list args; + int r; + va_start(args, fmt); + r = color_vprintf(color, fmt, args, NULL); + va_end(args); + return r; +} + +int color_printf_ln(const char *color, const char *fmt, ...) +{ + va_list args; + int r; + va_start(args, fmt); + r = color_vprintf(color, fmt, args, "\n"); + va_end(args); + return r; +} diff --git a/color.h b/color.h new file mode 100644 index 0000000000..88bb8ff1bd --- /dev/null +++ b/color.h @@ -0,0 +1,12 @@ +#ifndef COLOR_H +#define COLOR_H + +/* "\033[1;38;5;2xx;48;5;2xxm\0" is 23 bytes */ +#define COLOR_MAXLEN 24 + +int git_config_colorbool(const char *var, const char *value); +void color_parse(const char *var, const char *value, char *dst); +int color_printf(const char *color, const char *fmt, ...); +int color_printf_ln(const char *color, const char *fmt, ...); + +#endif /* COLOR_H */ diff --git a/combine-diff.c b/combine-diff.c index ba8baca0ab..46d9121baf 100644 --- a/combine-diff.c +++ b/combine-diff.c @@ -7,13 +7,6 @@ #include "xdiff-interface.h" #include "log-tree.h" -static int uninteresting(struct diff_filepair *p) -{ - if (diff_unmodified_pair(p)) - return 1; - return 0; -} - static struct combine_diff_path *intersect_paths(struct combine_diff_path *curr, int n, int num_parent) { struct diff_queue_struct *q = &diff_queued_diff; @@ -25,7 +18,7 @@ static struct combine_diff_path *intersect_paths(struct combine_diff_path *curr, for (i = 0; i < q->nr; i++) { int len; const char *path; - if (uninteresting(q->queue[i])) + if (diff_unmodified_pair(q->queue[i])) continue; path = q->queue[i]->two->path; len = strlen(path); @@ -38,9 +31,9 @@ static struct combine_diff_path *intersect_paths(struct combine_diff_path *curr, memset(p->parent, 0, sizeof(p->parent[0]) * num_parent); - memcpy(p->sha1, q->queue[i]->two->sha1, 20); + hashcpy(p->sha1, q->queue[i]->two->sha1); p->mode = q->queue[i]->two->mode; - memcpy(p->parent[n].sha1, q->queue[i]->one->sha1, 20); + hashcpy(p->parent[n].sha1, q->queue[i]->one->sha1); p->parent[n].mode = q->queue[i]->one->mode; p->parent[n].status = q->queue[i]->status; *tail = p; @@ -57,14 +50,13 @@ static struct combine_diff_path *intersect_paths(struct combine_diff_path *curr, const char *path; int len; - if (uninteresting(q->queue[i])) + if (diff_unmodified_pair(q->queue[i])) continue; path = q->queue[i]->two->path; len = strlen(path); if (len == p->len && !memcmp(path, p->path, len)) { found = 1; - memcpy(p->parent[n].sha1, - q->queue[i]->one->sha1, 20); + hashcpy(p->parent[n].sha1, q->queue[i]->one->sha1); p->parent[n].mode = q->queue[i]->one->mode; p->parent[n].status = q->queue[i]->status; break; @@ -101,7 +93,7 @@ static char *grab_blob(const unsigned char *sha1, unsigned long *size) { char *blob; char type[20]; - if (!memcmp(sha1, null_sha1, 20)) { + if (is_null_sha1(sha1)) { /* deleted blob */ *size = 0; return xcalloc(1, 1); @@ -609,16 +601,16 @@ static void dump_quoted_path(const char *prefix, const char *path, printf("%s\n", c_reset); } -static int show_patch_diff(struct combine_diff_path *elem, int num_parent, - int dense, struct rev_info *rev) +static void show_patch_diff(struct combine_diff_path *elem, int num_parent, + int dense, struct rev_info *rev) { struct diff_options *opt = &rev->diffopt; unsigned long result_size, cnt, lno; char *result, *cp; struct sline *sline; /* survived lines */ int mode_differs = 0; - int i, show_hunks, shown_header = 0; - int working_tree_file = !memcmp(elem->sha1, null_sha1, 20); + int i, show_hunks; + int working_tree_file = is_null_sha1(elem->sha1); int abbrev = opt->full_index ? 40 : DEFAULT_ABBREV; mmfile_t result_file; @@ -695,8 +687,8 @@ static int show_patch_diff(struct combine_diff_path *elem, int num_parent, for (i = 0; i < num_parent; i++) { int j; for (j = 0; j < i; j++) { - if (!memcmp(elem->parent[i].sha1, - elem->parent[j].sha1, 20)) { + if (!hashcmp(elem->parent[i].sha1, + elem->parent[j].sha1)) { reuse_combine_diff(sline, cnt, i, j); break; } @@ -769,7 +761,6 @@ static int show_patch_diff(struct combine_diff_path *elem, int num_parent, } free(sline[0].p_lno); free(sline); - return shown_header; } #define COLONS "::::::::::::::::::::::::::::::::" @@ -837,11 +828,10 @@ void show_combined_diff(struct combine_diff_path *p, return; if (opt->output_format & (DIFF_FORMAT_RAW | DIFF_FORMAT_NAME | - DIFF_FORMAT_NAME_STATUS)) { + DIFF_FORMAT_NAME_STATUS)) show_raw_diff(p, num_parent, rev); - } else if (opt->output_format & DIFF_FORMAT_PATCH) { + else if (opt->output_format & DIFF_FORMAT_PATCH) show_patch_diff(p, num_parent, dense, rev); - } } void diff_tree_combined(const unsigned char *sha1, @@ -936,6 +926,7 @@ void diff_tree_combined_merge(const unsigned char *sha1, for (parents = commit->parents, num_parent = 0; parents; parents = parents->next, num_parent++) - memcpy(parent + num_parent, parents->item->object.sha1, 20); + hashcpy((unsigned char*)(parent + num_parent), + parents->item->object.sha1); diff_tree_combined(sha1, parent, num_parent, dense, rev); } @@ -7,15 +7,15 @@ int save_commit_buffer = 1; struct sort_node { /* - * the number of children of the associated commit - * that also occur in the list being sorted. - */ + * the number of children of the associated commit + * that also occur in the list being sorted. + */ unsigned int indegree; /* - * reference to original list item that we will re-use - * on output. - */ + * reference to original list item that we will re-use + * on output. + */ struct commit_list * list_item; }; @@ -123,7 +123,7 @@ static int commit_graft_pos(const unsigned char *sha1) while (lo < hi) { int mi = (lo + hi) / 2; struct commit_graft *graft = commit_graft[mi]; - int cmp = memcmp(sha1, graft->sha1, 20); + int cmp = hashcmp(sha1, graft->sha1); if (!cmp) return mi; if (cmp < 0) @@ -467,7 +467,8 @@ static int add_rfc2047(char *buf, const char *line, int len) return bp - buf; } -static int add_user_info(const char *what, enum cmit_fmt fmt, char *buf, const char *line) +static int add_user_info(const char *what, enum cmit_fmt fmt, char *buf, + const char *line, int relative_date) { char *date; int namelen; @@ -507,14 +508,16 @@ static int add_user_info(const char *what, enum cmit_fmt fmt, char *buf, const c } switch (fmt) { case CMIT_FMT_MEDIUM: - ret += sprintf(buf + ret, "Date: %s\n", show_date(time, tz)); + ret += sprintf(buf + ret, "Date: %s\n", + show_date(time, tz, relative_date)); break; case CMIT_FMT_EMAIL: ret += sprintf(buf + ret, "Date: %s\n", show_rfc2822_date(time, tz)); break; case CMIT_FMT_FULLER: - ret += sprintf(buf + ret, "%sDate: %s\n", what, show_date(time, tz)); + ret += sprintf(buf + ret, "%sDate: %s\n", what, + show_date(time, tz, relative_date)); break; default: /* notin' */ @@ -557,7 +560,10 @@ static int add_merge_info(enum cmit_fmt fmt, char *buf, const struct commit *com return offset; } -unsigned long pretty_print_commit(enum cmit_fmt fmt, const struct commit *commit, unsigned long len, char *buf, unsigned long space, int abbrev, const char *subject, const char *after_subject) +unsigned long pretty_print_commit(enum cmit_fmt fmt, const struct commit *commit, + unsigned long len, char *buf, unsigned long space, + int abbrev, const char *subject, + const char *after_subject, int relative_date) { int hdr = 1, body = 0; unsigned long offset = 0; @@ -646,12 +652,14 @@ unsigned long pretty_print_commit(enum cmit_fmt fmt, const struct commit *commit if (!memcmp(line, "author ", 7)) offset += add_user_info("Author", fmt, buf + offset, - line + 7); + line + 7, + relative_date); if (!memcmp(line, "committer ", 10) && (fmt == CMIT_FMT_FULL || fmt == CMIT_FMT_FULLER)) offset += add_user_info("Commit", fmt, buf + offset, - line + 10); + line + 10, + relative_date); continue; } @@ -727,10 +735,10 @@ struct commit *pop_commit(struct commit_list **stack) int count_parents(struct commit * commit) { - int count = 0; + int count; struct commit_list * parents = commit->parents; - for (count=0;parents; parents=parents->next,count++) - ; + for (count = 0; parents; parents = parents->next,count++) + ; return count; } @@ -52,7 +52,7 @@ enum cmit_fmt { }; extern enum cmit_fmt get_commit_format(const char *arg); -extern unsigned long pretty_print_commit(enum cmit_fmt fmt, const struct commit *, unsigned long len, char *buf, unsigned long space, int abbrev, const char *subject, const char *after_subject); +extern unsigned long pretty_print_commit(enum cmit_fmt fmt, const struct commit *, unsigned long len, char *buf, unsigned long space, int abbrev, const char *subject, const char *after_subject, int relative_date); /** Removes the first commit from a list sorted by date, and adds all * of its parents. @@ -350,19 +350,18 @@ int git_config(config_fn_t fn) home = getenv("HOME"); filename = getenv("GIT_CONFIG_LOCAL"); if (!filename) - filename = repo_config = strdup(git_path("config")); + filename = repo_config = xstrdup(git_path("config")); } if (home) { - char *user_config = strdup(mkpath("%s/.gitconfig", home)); + char *user_config = xstrdup(mkpath("%s/.gitconfig", home)); if (!access(user_config, R_OK)) ret = git_config_from_file(fn, user_config); free(user_config); } ret += git_config_from_file(fn, filename); - if (repo_config) - free(repo_config); + free(repo_config); return ret; } @@ -546,8 +545,8 @@ int git_config_set_multivar(const char* key, const char* value, if (!config_filename) config_filename = git_path("config"); } - config_filename = strdup(config_filename); - lock_file = strdup(mkpath("%s.lock", config_filename)); + config_filename = xstrdup(config_filename); + lock_file = xstrdup(mkpath("%s.lock", config_filename)); /* * Since "key" actually contains the section name and the real @@ -566,7 +565,7 @@ int git_config_set_multivar(const char* key, const char* value, /* * Validate the key and while at it, lower case it for matching. */ - store.key = (char*)malloc(strlen(key)+1); + store.key = xmalloc(strlen(key) + 1); dot = 0; for (i = 0; key[i]; i++) { unsigned char c = key[i]; @@ -634,7 +633,7 @@ int git_config_set_multivar(const char* key, const char* value, } else store.do_not_match = 0; - store.value_regex = (regex_t*)malloc(sizeof(regex_t)); + store.value_regex = (regex_t*)xmalloc(sizeof(regex_t)); if (regcomp(store.value_regex, value_regex, REG_EXTENDED)) { fprintf(stderr, "Invalid pattern: %s\n", @@ -734,8 +733,7 @@ int git_config_set_multivar(const char* key, const char* value, out_free: if (0 <= fd) close(fd); - if (config_filename) - free(config_filename); + free(config_filename); if (lock_file) { unlink(lock_file); free(lock_file); diff --git a/config.mak.in b/config.mak.in index 369e6116e0..6d20673b24 100644 --- a/config.mak.in +++ b/config.mak.in @@ -37,4 +37,5 @@ NO_C99_FORMAT=@NO_C99_FORMAT@ NO_STRCASESTR=@NO_STRCASESTR@ NO_STRLCPY=@NO_STRLCPY@ NO_SETENV=@NO_SETENV@ +NO_ICONV=@NO_ICONV@ diff --git a/configure.ac b/configure.ac index e890131c46..511cac93d6 100644 --- a/configure.ac +++ b/configure.ac @@ -143,10 +143,15 @@ AC_CHECK_LIB([expat], [XML_ParserCreate], AC_SUBST(NO_EXPAT) # # Define NEEDS_LIBICONV if linking with libc is not enough (Darwin). +# Define NO_ICONV if neither libc nor libiconv support iconv. AC_CHECK_LIB([c], [iconv], -[NEEDS_LIBICONV=], -[NEEDS_LIBICONV=YesPlease]) + [NEEDS_LIBICONV=], + AC_CHECK_LIB([iconv], [iconv], + [NEEDS_LIBICONV=YesPlease], + [NO_ICONV=YesPlease])) AC_SUBST(NEEDS_LIBICONV) +AC_SUBST(NO_ICONV) +test -n "$NEEDS_LIBICONV" && LIBS="$LIBS -liconv" # # Define NEEDS_SOCKET if linking with libc is not enough (SunOS, # Patrick Mauritz). @@ -154,6 +159,7 @@ AC_CHECK_LIB([c], [socket], [NEEDS_SOCKET=], [NEEDS_SOCKET=YesPlease]) AC_SUBST(NEEDS_SOCKET) +test -n "$NEEDS_SOCKET" && LIBS="$LIBS -lsocket" ## Checks for header files. @@ -181,8 +187,10 @@ AC_SUBST(NO_D_TYPE_IN_DIRENT) # sockaddr_storage. AC_CHECK_TYPE(struct sockaddr_storage, [NO_SOCKADDR_STORAGE=], -[NO_SOCKADDR_STORAGE=YesPlease], -[#include <netinet/in.h>]) +[NO_SOCKADDR_STORAGE=YesPlease],[ +#include <sys/types.h> +#include <sys/socket.h> +]) AC_SUBST(NO_SOCKADDR_STORAGE) # # Define NO_IPV6 if you lack IPv6 support and getaddrinfo(). @@ -201,8 +209,8 @@ AC_SUBST(NO_IPV6) # do not support the 'size specifiers' introduced by C99, namely ll, hh, # j, z, t. (representing long long int, char, intmax_t, size_t, ptrdiff_t). # some C compilers supported these specifiers prior to C99 as an extension. -AC_CACHE_CHECK(whether formatted IO functions support C99 size specifiers, - ac_cv_c_c99_format, +AC_CACHE_CHECK([whether formatted IO functions support C99 size specifiers], + [ac_cv_c_c99_format], [# Actually git uses only %z (%zu) in alloc.c, and %t (%td) in mktag.c AC_RUN_IFELSE( [AC_LANG_PROGRAM([AC_INCLUDES_DEFAULT], @@ -257,6 +265,16 @@ AC_SUBST(NO_SETENV) # Enable it on Windows. By default, symrefs are still used. # # Define WITH_OWN_SUBPROCESS_PY if you want to use with python 2.3. +AC_CACHE_CHECK([for subprocess.py], + [ac_cv_python_has_subprocess_py], +[if $PYTHON_PATH -c 'import subprocess' 2>/dev/null; then + ac_cv_python_has_subprocess_py=yes +else + ac_cv_python_has_subprocess_py=no +fi]) +if test $ac_cv_python_has_subprocess_py != yes; then + GIT_CONF_APPEND_LINE([WITH_OWN_SUBPROCESS_PY=YesPlease]) +fi # # Define NO_ACCURATE_DIFF if your diff program at least sometimes misses # a missing newline at the end of the file. @@ -326,6 +344,16 @@ GIT_PARSE_WITH(expat)) # library directories by defining CFLAGS and LDFLAGS appropriately. # # Define NO_MMAP if you want to avoid mmap. +# +# Define NO_ICONV if your libc does not properly support iconv. +AC_ARG_WITH(iconv, +AS_HELP_STRING([--without-iconv], +[if your architecture doesn't properly support iconv]) +AS_HELP_STRING([--with-iconv=PATH], +[PATH is prefix for libiconv library and headers]) +AS_HELP_STRING([], +[used only if you need linking with libiconv]), +GIT_PARSE_WITH(iconv)) ## --enable-FEATURE[=ARG] and --disable-FEATURE # @@ -10,14 +10,14 @@ #include <netdb.h> #include <signal.h> -static char *server_capabilities = NULL; +static char *server_capabilities; static int check_ref(const char *name, int len, unsigned int flags) { if (!flags) return 1; - if (len > 45 || memcmp(name, "refs/", 5)) + if (len < 5 || memcmp(name, "refs/", 5)) return 0; /* Skip the "refs/" part */ @@ -69,7 +69,7 @@ struct ref **get_remote_heads(int in, struct ref **list, if (len != name_len + 41) { if (server_capabilities) free(server_capabilities); - server_capabilities = strdup(name + name_len + 1); + server_capabilities = xstrdup(name + name_len + 1); } if (!check_ref(name, name_len, flags)) @@ -77,7 +77,7 @@ struct ref **get_remote_heads(int in, struct ref **list, if (nr_match && !path_match(name, nr_match, match)) continue; ref = xcalloc(1, sizeof(*ref) + len - 40); - memcpy(ref->old_sha1, old_sha1, 20); + hashcpy(ref->old_sha1, old_sha1); memcpy(ref->name, buffer + 41, len - 40); *list = ref; list = &ref->next; @@ -208,7 +208,7 @@ static struct ref *try_explicit_object_name(const char *name) len = strlen(name) + 1; ref = xcalloc(1, sizeof(*ref) + len); memcpy(ref->name, name, len); - memcpy(ref->new_sha1, sha1, 20); + hashcpy(ref->new_sha1, sha1); return ref; } @@ -318,7 +318,7 @@ int match_refs(struct ref *src, struct ref *dst, struct ref ***dst_tail, int len = strlen(src->name) + 1; dst_peer = xcalloc(1, sizeof(*dst_peer) + len); memcpy(dst_peer->name, src->name, len); - memcpy(dst_peer->new_sha1, src->new_sha1, 20); + hashcpy(dst_peer->new_sha1, src->new_sha1); link_dst_tail(dst_peer, dst_tail); } dst_peer->peer_ref = src; @@ -493,8 +493,8 @@ static void git_tcp_connect(int fd[2], char *host) } -static char *git_proxy_command = NULL; -static const char *rhost_name = NULL; +static char *git_proxy_command; +static const char *rhost_name; static int rhost_len; static int git_proxy_command_options(const char *var, const char *value) @@ -599,12 +599,19 @@ static void git_proxy_connect(int fd[2], char *host) close(pipefd[1][0]); } +#define MAX_CMD_LEN 1024 + /* - * Yeah, yeah, fixme. Need to pass in the heads etc. + * This returns 0 if the transport protocol does not need fork(2), + * or a process id if it does. Once done, finish the connection + * with finish_connect() with the value returned from this function + * (it is safe to call finish_connect() with 0 to support the former + * case). + * + * Does not return a negative value on error; it just dies. */ -int git_connect(int fd[2], char *url, const char *prog) +pid_t git_connect(int fd[2], char *url, const char *prog) { - char command[1024]; char *host, *path = url; char *end; int c; @@ -661,7 +668,7 @@ int git_connect(int fd[2], char *url, const char *prog) if (path[1] == '~') path++; else { - path = strdup(ptr); + path = xstrdup(ptr); free_path = 1; } @@ -672,7 +679,7 @@ int git_connect(int fd[2], char *url, const char *prog) /* These underlying connection commands die() if they * cannot connect. */ - char *target_host = strdup(host); + char *target_host = xstrdup(host); if (git_use_proxy(host)) git_proxy_connect(fd, host); else @@ -697,8 +704,18 @@ int git_connect(int fd[2], char *url, const char *prog) if (pid < 0) die("unable to fork"); if (!pid) { - snprintf(command, sizeof(command), "%s %s", prog, - sq_quote(path)); + char command[MAX_CMD_LEN]; + char *posn = command; + int size = MAX_CMD_LEN; + int of = 0; + + of |= add_to_string(&posn, &size, prog, 0); + of |= add_to_string(&posn, &size, " ", 0); + of |= add_to_string(&posn, &size, path, 1); + + if (of) + die("command line too long"); + dup2(pipefd[1][0], 0); dup2(pipefd[0][1], 1); close(pipefd[0][0]); @@ -737,14 +754,12 @@ int git_connect(int fd[2], char *url, const char *prog) int finish_connect(pid_t pid) { - int ret; + if (pid == 0) + return 0; - for (;;) { - ret = waitpid(pid, NULL, 0); - if (!ret) - break; + while (waitpid(pid, NULL, 0) < 0) { if (errno != EINTR) - break; + return -1; } - return ret; + return 0; } diff --git a/contrib/emacs/vc-git.el b/contrib/emacs/vc-git.el index 3f6ed699f0..4a8f79092d 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 6924df286e..77c29de305 100644 --- a/contrib/gitview/gitview.txt +++ b/contrib/gitview/gitview.txt @@ -7,40 +7,50 @@ gitview - A GTK based repository browser for git SYNOPSIS -------- -'gitview' [options] [args] +'gitview' [options] [args] DESCRIPTION --------- -Dependencies +Dependencies: * Python 2.4 * PyGTK 2.8 or later * PyCairo 1.0 or later OPTIONS ------- - --without-diff - If the user doesn't want to list the commit diffs in the main window. This may speed up the repository browsing. - - <args> - All the valid option for git-rev-list(1) - Key Bindings: - F4: - To maximize the window - F5: - To reread references. - F11: - Full screen - F12: - Leave full screen +------- +--without-diff:: + + If the user doesn't want to list the commit diffs in the main window. + This may speed up the repository browsing. + +<args>:: + + All the valid option for gitlink:git-rev-list[1]. + +Key Bindings +------------ +F4:: + To maximize the window + +F5:: + To reread references. + +F11:: + Full screen + +F12:: + Leave full screen EXAMPLES ------- - gitview v2.6.12.. include/scsi drivers/scsi - Show as the changes since version v2.6.12 that changed any file in the include/scsi - or drivers/scsi subdirectories +-------- + +gitview v2.6.12.. include/scsi drivers/scsi:: + + Show as the changes since version v2.6.12 that changed any file in the + include/scsi or drivers/scsi subdirectories - gitview --since=2.weeks.ago - Show the changes during the last two weeks +gitview --since=2.weeks.ago:: + Show the changes during the last two weeks diff --git a/contrib/vim/README b/contrib/vim/README new file mode 100644 index 0000000000..9e7881fea9 --- /dev/null +++ b/contrib/vim/README @@ -0,0 +1,8 @@ +To syntax highlight git's commit messages, you need to: + 1. Copy syntax/gitcommit.vim to vim's syntax directory: + $ mkdir -p $HOME/.vim/syntax + $ cp syntax/gitcommit.vim $HOME/.vim/syntax + 2. Auto-detect the editing of git commit files: + $ cat >>$HOME/.vimrc <<'EOF' + autocmd BufNewFile,BufRead COMMIT_EDITMSG set filetype=gitcommit + EOF diff --git a/contrib/vim/syntax/gitcommit.vim b/contrib/vim/syntax/gitcommit.vim new file mode 100644 index 0000000000..a9de09fa2f --- /dev/null +++ b/contrib/vim/syntax/gitcommit.vim @@ -0,0 +1,18 @@ +syn region gitLine start=/^#/ end=/$/ +syn region gitCommit start=/^# Updated but not checked in:$/ end=/^#$/ contains=gitHead,gitCommitFile +syn region gitHead contained start=/^# (.*)/ end=/^#$/ +syn region gitChanged start=/^# Changed but not updated:/ end=/^#$/ contains=gitHead,gitChangedFile +syn region gitUntracked start=/^# Untracked files:/ end=/^#$/ contains=gitHead,gitUntrackedFile + +syn match gitCommitFile contained /^#\t.*/hs=s+2 +syn match gitChangedFile contained /^#\t.*/hs=s+2 +syn match gitUntrackedFile contained /^#\t.*/hs=s+2 + +hi def link gitLine Comment +hi def link gitCommit Comment +hi def link gitChanged Comment +hi def link gitHead Comment +hi def link gitUntracked Comment +hi def link gitCommitFile Type +hi def link gitChangedFile Constant +hi def link gitUntrackedFile Constant diff --git a/convert-objects.c b/convert-objects.c index 168771ed85..631678b08a 100644 --- a/convert-objects.c +++ b/convert-objects.c @@ -23,7 +23,7 @@ static struct entry * convert_entry(unsigned char *sha1); static struct entry *insert_new(unsigned char *sha1, int pos) { struct entry *new = xcalloc(1, sizeof(struct entry)); - memcpy(new->old_sha1, sha1, 20); + hashcpy(new->old_sha1, sha1); memmove(convert + pos + 1, convert + pos, (nr_convert - pos) * sizeof(struct entry *)); convert[pos] = new; nr_convert++; @@ -39,7 +39,7 @@ static struct entry *lookup_entry(unsigned char *sha1) while (low < high) { int next = (low + high) / 2; struct entry *n = convert[next]; - int cmp = memcmp(sha1, n->old_sha1, 20); + int cmp = hashcmp(sha1, n->old_sha1); if (!cmp) return n; if (cmp < 0) { @@ -54,7 +54,7 @@ static struct entry *lookup_entry(unsigned char *sha1) static void convert_binary_sha1(void *buffer) { struct entry *entry = convert_entry(buffer); - memcpy(buffer, entry->new_sha1, 20); + hashcpy(buffer, entry->new_sha1); } static void convert_ascii_sha1(void *buffer) @@ -104,7 +104,7 @@ static int write_subdirectory(void *buffer, unsigned long size, const char *base if (!slash) { newlen += sprintf(new + newlen, "%o %s", mode, path); new[newlen++] = '\0'; - memcpy(new + newlen, (char *) buffer + len - 20, 20); + hashcpy((unsigned char*)new + newlen, (unsigned char *) buffer + len - 20); newlen += 20; used += len; diff --git a/csum-file.c b/csum-file.c index 6a7b40fd09..b7174c6c05 100644 --- a/csum-file.c +++ b/csum-file.c @@ -10,7 +10,7 @@ #include "cache.h" #include "csum-file.h" -static int sha1flush(struct sha1file *f, unsigned int count) +static void sha1flush(struct sha1file *f, unsigned int count) { void *buf = f->buffer; @@ -21,7 +21,7 @@ static int sha1flush(struct sha1file *f, unsigned int count) count -= ret; if (count) continue; - return 0; + return; } if (!ret) die("sha1 file '%s' write error. Out of diskspace", f->name); @@ -38,7 +38,7 @@ int sha1close(struct sha1file *f, unsigned char *result, int update) } SHA1_Final(f->buffer, &f->ctx); if (result) - memcpy(result, f->buffer, 20); + hashcpy(result, f->buffer); if (update) sha1flush(f, 20); if (close(f->fd)) @@ -7,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,29 @@ 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" +" [--[enable|disable|allow-override|forbid-override]=service]\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) { @@ -229,13 +233,42 @@ static char *path_ok(char *dir) return NULL; /* Fallthrough. Deny by default */ } -static int upload(char *dir) +typedef int (*daemon_service_fn)(void); +struct daemon_service { + const char *name; + const char *config_name; + daemon_service_fn fn; + int enabled; + int overridable; +}; + +static struct daemon_service *service_looking_at; +static int service_enabled; + +static int git_daemon_config(const char *var, const char *value) +{ + if (!strncmp(var, "daemon.", 7) && + !strcmp(var + 7, service_looking_at->config_name)) { + service_enabled = git_config_bool(var, value); + return 0; + } + + /* we are not interested in parsing any other configuration here */ + return 0; +} + +static int run_service(char *dir, struct daemon_service *service) { - /* Timeout as string */ - char timeout_buf[64]; const char *path; + int enabled = service->enabled; + + loginfo("Request %s for '%s'", service->name, dir); - loginfo("Request for '%s'", dir); + if (!enabled && !service->overridable) { + logerror("'%s': service not enabled.", service->name); + errno = EACCES; + return -1; + } if (!(path = path_ok(dir))) return -1; @@ -257,12 +290,34 @@ static int upload(char *dir) return -1; } + if (service->overridable) { + service_looking_at = service; + service_enabled = -1; + git_config(git_daemon_config); + if (0 <= service_enabled) + enabled = service_enabled; + } + if (!enabled) { + logerror("'%s': service not enabled for '%s'", + service->name, path); + errno = EACCES; + return -1; + } + /* * We'll ignore SIGTERM from now on, we have a * good client. */ signal(SIGTERM, SIG_IGN); + return service->fn(); +} + +static int upload_pack(void) +{ + /* Timeout as string */ + char timeout_buf[64]; + snprintf(timeout_buf, sizeof timeout_buf, "--timeout=%u", timeout); /* git-upload-pack only ever reads stuff, so this is safe */ @@ -270,10 +325,43 @@ static int upload(char *dir) return -1; } +static int upload_archive(void) +{ + execl_git_cmd("upload-archive", ".", NULL); + return -1; +} + +static struct daemon_service daemon_service[] = { + { "upload-archive", "uploadarch", upload_archive, 0, 1 }, + { "upload-pack", "uploadpack", upload_pack, 1, 1 }, +}; + +static void enable_service(const char *name, int ena) { + int i; + for (i = 0; i < ARRAY_SIZE(daemon_service); i++) { + if (!strcmp(daemon_service[i].name, name)) { + daemon_service[i].enabled = ena; + return; + } + } + die("No such service %s", name); +} + +static void make_service_overridable(const char *name, int ena) { + int i; + for (i = 0; i < ARRAY_SIZE(daemon_service); i++) { + if (!strcmp(daemon_service[i].name, name)) { + daemon_service[i].overridable = ena; + return; + } + } + die("No such service %s", name); +} + static int execute(struct sockaddr *addr) { static char line[1000]; - int pktlen, len; + int pktlen, len, i; if (addr) { char addrbuf[256] = ""; @@ -310,8 +398,14 @@ static int execute(struct sockaddr *addr) if (len && line[len-1] == '\n') line[--len] = 0; - if (!strncmp("git-upload-pack ", line, 16)) - return upload(line+16); + for (i = 0; i < ARRAY_SIZE(daemon_service); i++) { + struct daemon_service *s = &(daemon_service[i]); + int namelen = strlen(s->name); + if (!strncmp("git-", line, 4) && + !strncmp(s->name, line + 4, namelen) && + line[namelen + 4] == ' ') + return run_service(line + namelen + 5, s); + } logerror("Protocol error: '%s'", line); return -1; @@ -333,12 +427,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 +620,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 +653,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 +790,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 +798,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 +810,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 +888,30 @@ int main(int argc, char **argv) log_syslog = 1; continue; } + if (!strncmp(arg, "--user=", 7)) { + user_name = arg + 7; + continue; + } + if (!strncmp(arg, "--group=", 8)) { + group_name = arg + 8; + continue; + } + if (!strncmp(arg, "--enable=", 9)) { + enable_service(arg + 9, 1); + continue; + } + if (!strncmp(arg, "--disable=", 10)) { + enable_service(arg + 10, 0); + continue; + } + if (!strncmp(arg, "--allow-override=", 17)) { + make_service_overridable(arg + 17, 1); + continue; + } + if (!strncmp(arg, "--forbid-override=", 18)) { + make_service_overridable(arg + 18, 0); + continue; + } if (!strcmp(arg, "--")) { ok_paths = &argv[i+1]; break; @@ -802,6 +923,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 +974,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 324ca8965b..ab192f83ae 100644 --- a/describe.c +++ b/describe.c @@ -8,12 +8,12 @@ static const char describe_usage[] = "git-describe [--all] [--tags] [--abbrev=<n>] <committish>*"; -static int all = 0; /* Default to annotated tags only */ -static int tags = 0; /* But allow any tags if --tags is specified */ +static int all; /* Default to annotated tags only */ +static int tags; /* But allow any tags if --tags is specified */ static int abbrev = DEFAULT_ABBREV; -static int names = 0, allocs = 0; +static int names, allocs; static struct commit_name { const struct commit *commit; int prio; /* annotated tag = 2, tag = 1, head = 0 */ @@ -42,7 +42,7 @@ static void add_to_known_names(const char *path, struct commit_name *name = xmalloc(sizeof(struct commit_name) + len); name->commit = commit; - name->prio = prio; + name->prio = prio; memcpy(name->path, path, len); idx = names; if (idx >= allocs) { @@ -154,14 +154,16 @@ int main(int argc, char **argv) tags = 1; else if (!strncmp(arg, "--abbrev=", 9)) { abbrev = strtoul(arg + 9, NULL, 10); - if (abbrev < MINIMUM_ABBREV || 40 <= abbrev) + if (abbrev < MINIMUM_ABBREV || 40 < abbrev) abbrev = DEFAULT_ABBREV; } else usage(describe_usage); } - if (i == argc) + setup_git_directory(); + + if (argc <= i) describe("HEAD", 1); else while (i < argc) { diff --git a/diff-delta.c b/diff-delta.c index 7da9205a5d..fa16d06c8d 100644 --- a/diff-delta.c +++ b/diff-delta.c @@ -152,7 +152,7 @@ struct delta_index * create_delta_index(const void *buf, unsigned long bufsize) initialization in create_delta(). */ entries = (bufsize - 1) / RABIN_WINDOW; hsize = entries / 4; - for (i = 4; (1 << i) < hsize && i < 31; i++); + for (i = 4; (1u << i) < hsize && i < 31; i++); hsize = 1 << i; hmask = hsize - 1; @@ -392,7 +392,7 @@ create_delta(const struct delta_index *index, outsize = max_size + MAX_OP_SIZE + 1; if (max_size && outpos > max_size) break; - out = realloc(out, outsize); + out = xrealloc(out, outsize); if (!out) { free(tmp); return NULL; diff --git a/diff-lib.c b/diff-lib.c index 116b5a9d68..fc69fb92a5 100644 --- a/diff-lib.c +++ b/diff-lib.c @@ -48,7 +48,7 @@ int run_diff_files(struct rev_info *revs, int silent_on_removed) memcpy(dpath->path, ce->name, path_len); dpath->path[path_len] = '\0'; dpath->mode = 0; - memset(dpath->sha1, 0, 20); + hashclr(dpath->sha1); memset(&(dpath->parent[0]), 0, sizeof(struct combine_diff_parent)*5); @@ -66,8 +66,7 @@ int run_diff_files(struct rev_info *revs, int silent_on_removed) if (2 <= stage) { int mode = ntohl(nce->ce_mode); num_compare_stages++; - memcpy(dpath->parent[stage-2].sha1, - nce->sha1, 20); + hashcpy(dpath->parent[stage-2].sha1, nce->sha1); dpath->parent[stage-2].mode = canon_mode(mode); dpath->parent[stage-2].status = @@ -214,8 +213,33 @@ static int show_modified(struct rev_info *revs, return -1; } + if (revs->combine_merges && !cached && + (hashcmp(sha1, old->sha1) || hashcmp(old->sha1, new->sha1))) { + struct combine_diff_path *p; + int pathlen = ce_namelen(new); + + p = xmalloc(combine_diff_path_size(2, pathlen)); + p->path = (char *) &p->parent[2]; + p->next = NULL; + p->len = pathlen; + memcpy(p->path, new->name, pathlen); + p->path[pathlen] = 0; + p->mode = ntohl(mode); + hashclr(p->sha1); + memset(p->parent, 0, 2 * sizeof(struct combine_diff_parent)); + p->parent[0].status = DIFF_STATUS_MODIFIED; + p->parent[0].mode = ntohl(new->ce_mode); + hashcpy(p->parent[0].sha1, new->sha1); + p->parent[1].status = DIFF_STATUS_MODIFIED; + p->parent[1].mode = ntohl(old->ce_mode); + hashcpy(p->parent[1].sha1, old->sha1); + show_combined_diff(p, 2, revs->dense_combined_merges, revs); + free(p); + return 0; + } + oldmode = old->ce_mode; - if (mode == oldmode && !memcmp(sha1, old->sha1, 20) && + if (mode == oldmode && !hashcmp(sha1, old->sha1) && !revs->diffopt.find_copies_harder) return 0; @@ -10,15 +10,15 @@ #include "diffcore.h" #include "delta.h" #include "xdiff-interface.h" +#include "color.h" static int use_size_cache; -static int diff_detect_rename_default = 0; +static int diff_detect_rename_default; static int diff_rename_limit_default = -1; -static int diff_use_color_default = 0; +static int diff_use_color_default; -/* "\033[1;38;5;2xx;48;5;2xxm\0" is 23 bytes */ -static char diff_colors[][24] = { +static char diff_colors[][COLOR_MAXLEN] = { "\033[m", /* reset */ "", /* normal */ "\033[1m", /* bold */ @@ -45,119 +45,6 @@ static int parse_diff_color_slot(const char *var, int ofs) die("bad config variable '%s'", var); } -static int parse_color(const char *name, int len) -{ - static const char * const color_names[] = { - "normal", "black", "red", "green", "yellow", - "blue", "magenta", "cyan", "white" - }; - char *end; - int i; - for (i = 0; i < ARRAY_SIZE(color_names); i++) { - const char *str = color_names[i]; - if (!strncasecmp(name, str, len) && !str[len]) - return i - 1; - } - i = strtol(name, &end, 10); - if (*name && !*end && i >= -1 && i <= 255) - return i; - return -2; -} - -static int parse_attr(const char *name, int len) -{ - static const int attr_values[] = { 1, 2, 4, 5, 7 }; - static const char * const attr_names[] = { - "bold", "dim", "ul", "blink", "reverse" - }; - int i; - for (i = 0; i < ARRAY_SIZE(attr_names); i++) { - const char *str = attr_names[i]; - if (!strncasecmp(name, str, len) && !str[len]) - return attr_values[i]; - } - return -1; -} - -static void parse_diff_color_value(const char *value, const char *var, char *dst) -{ - const char *ptr = value; - int attr = -1; - int fg = -2; - int bg = -2; - - if (!strcasecmp(value, "reset")) { - strcpy(dst, "\033[m"); - return; - } - - /* [fg [bg]] [attr] */ - while (*ptr) { - const char *word = ptr; - int val, len = 0; - - while (word[len] && !isspace(word[len])) - len++; - - ptr = word + len; - while (*ptr && isspace(*ptr)) - ptr++; - - val = parse_color(word, len); - if (val >= -1) { - if (fg == -2) { - fg = val; - continue; - } - if (bg == -2) { - bg = val; - continue; - } - goto bad; - } - val = parse_attr(word, len); - if (val < 0 || attr != -1) - goto bad; - attr = val; - } - - if (attr >= 0 || fg >= 0 || bg >= 0) { - int sep = 0; - - *dst++ = '\033'; - *dst++ = '['; - if (attr >= 0) { - *dst++ = '0' + attr; - sep++; - } - if (fg >= 0) { - if (sep++) - *dst++ = ';'; - if (fg < 8) { - *dst++ = '3'; - *dst++ = '0' + fg; - } else { - dst += sprintf(dst, "38;5;%d", fg); - } - } - if (bg >= 0) { - if (sep++) - *dst++ = ';'; - if (bg < 8) { - *dst++ = '4'; - *dst++ = '0' + bg; - } else { - dst += sprintf(dst, "48;5;%d", bg); - } - } - *dst++ = 'm'; - } - *dst = 0; - return; -bad: - die("bad config value '%s' for variable '%s'", value, var); -} - /* * These are to give UI layer defaults. * The core-level commands such as git-diff-files should @@ -171,22 +58,7 @@ int git_diff_ui_config(const char *var, const char *value) return 0; } if (!strcmp(var, "diff.color")) { - if (!value) - diff_use_color_default = 1; /* bool */ - else if (!strcasecmp(value, "auto")) { - diff_use_color_default = 0; - if (isatty(1) || (pager_in_use && pager_use_color)) { - char *term = getenv("TERM"); - if (term && strcmp(term, "dumb")) - diff_use_color_default = 1; - } - } - else if (!strcasecmp(value, "never")) - diff_use_color_default = 0; - else if (!strcasecmp(value, "always")) - diff_use_color_default = 1; - else - diff_use_color_default = git_config_bool(var, value); + diff_use_color_default = git_config_colorbool(var, value); return 0; } if (!strcmp(var, "diff.renames")) { @@ -201,7 +73,7 @@ int git_diff_ui_config(const char *var, const char *value) } if (!strncmp(var, "diff.color.", 11)) { int slot = parse_diff_color_slot(var, 11); - parse_diff_color_value(value, var, diff_colors[slot]); + color_parse(value, var, diff_colors[slot]); return 0; } return git_default_config(var, value); @@ -216,7 +88,7 @@ static char *quote_one(const char *str) return NULL; needlen = quote_c_style(str, NULL, NULL, 0); if (!needlen) - return strdup(str); + return xstrdup(str); xp = xmalloc(needlen + 1); quote_c_style(str, xp, NULL, 0); return xp; @@ -658,7 +530,7 @@ static struct diffstat_file *diffstat_add(struct diffstat_t *diffstat, x->is_renamed = 1; } else - x->name = strdup(name_a); + x->name = xstrdup(name_a); return x; } @@ -838,7 +710,7 @@ static unsigned char *deflate_it(char *data, return deflated; } -static void emit_binary_diff(mmfile_t *one, mmfile_t *two) +static void emit_binary_diff_body(mmfile_t *one, mmfile_t *two) { void *cp; void *delta; @@ -849,7 +721,6 @@ static void emit_binary_diff(mmfile_t *one, mmfile_t *two) unsigned long deflate_size; unsigned long data_size; - printf("GIT binary patch\n"); /* We could do deflated delta, or we could do just deflated two, * whichever is smaller. */ @@ -898,15 +769,20 @@ static void emit_binary_diff(mmfile_t *one, mmfile_t *two) free(data); } +static void emit_binary_diff(mmfile_t *one, mmfile_t *two) +{ + printf("GIT binary patch\n"); + emit_binary_diff_body(one, two); + emit_binary_diff_body(two, one); +} + #define FIRST_FEW_BYTES 8000 static int mmfile_is_binary(mmfile_t *mf) { long sz = mf->size; if (FIRST_FEW_BYTES < sz) sz = FIRST_FEW_BYTES; - if (memchr(mf->ptr, 0, sz)) - return 1; - return 0; + return !!memchr(mf->ptr, 0, sz); } static void builtin_diff(const char *name_a, @@ -1103,8 +979,8 @@ void fill_filespec(struct diff_filespec *spec, const unsigned char *sha1, { if (mode) { spec->mode = canon_mode(mode); - memcpy(spec->sha1, sha1, 20); - spec->sha1_valid = !!memcmp(sha1, null_sha1, 20); + hashcpy(spec->sha1, sha1); + spec->sha1_valid = !is_null_sha1(sha1); } } @@ -1142,7 +1018,7 @@ static int work_tree_matches(const char *name, const unsigned char *sha1) if ((lstat(name, &st) < 0) || !S_ISREG(st.st_mode) || /* careful! */ ce_match_stat(ce, &st, 0) || - memcmp(sha1, ce->sha1, 20)) + hashcmp(sha1, ce->sha1)) return 0; /* we return 1 only when we can stat, it is a regular file, * stat information matches, and sha1 recorded in the cache @@ -1170,7 +1046,7 @@ static struct sha1_size_cache *locate_size_cache(unsigned char *sha1, while (last > first) { int cmp, next = (last + first) >> 1; e = sha1_size_cache[next]; - cmp = memcmp(e->sha1, sha1, 20); + cmp = hashcmp(e->sha1, sha1); if (!cmp) return e; if (cmp < 0) { @@ -1196,7 +1072,7 @@ static struct sha1_size_cache *locate_size_cache(unsigned char *sha1, sizeof(*sha1_size_cache)); e = xmalloc(sizeof(struct sha1_size_cache)); sha1_size_cache[first] = e; - memcpy(e->sha1, sha1, 20); + hashcpy(e->sha1, sha1); e->size = size; return e; } @@ -1518,7 +1394,7 @@ static void diff_fill_sha1_info(struct diff_filespec *one) } } else - memset(one->sha1, 0, 20); + hashclr(one->sha1); } static void run_diff(struct diff_filepair *p, struct diff_options *o) @@ -1581,9 +1457,15 @@ static void run_diff(struct diff_filepair *p, struct diff_options *o) ; } - if (memcmp(one->sha1, two->sha1, 20)) { + if (hashcmp(one->sha1, two->sha1)) { int abbrev = o->full_index ? 40 : DEFAULT_ABBREV; + if (o->binary) { + mmfile_t mf; + if ((!fill_mmfile(&mf, one) && mmfile_is_binary(&mf)) || + (!fill_mmfile(&mf, two) && mmfile_is_binary(&mf))) + abbrev = 40; + } len += snprintf(msg + len, sizeof(msg) - len, "index %.*s..%.*s", abbrev, sha1_to_hex(one->sha1), @@ -1814,7 +1696,7 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac) options->full_index = 1; else if (!strcmp(arg, "--binary")) { options->output_format |= DIFF_FORMAT_PATCH; - options->full_index = options->binary = 1; + options->binary = 1; } else if (!strcmp(arg, "-a") || !strcmp(arg, "--text")) { options->text = 1; @@ -2100,7 +1982,7 @@ int diff_unmodified_pair(struct diff_filepair *p) * dealing with a change. */ if (one->sha1_valid && two->sha1_valid && - !memcmp(one->sha1, two->sha1, sizeof(one->sha1))) + !hashcmp(one->sha1, two->sha1)) return 1; /* no change */ if (!one->sha1_valid && !two->sha1_valid) return 1; /* both look at the same file on the filesystem. */ @@ -2239,7 +2121,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 { @@ -2583,6 +2465,9 @@ void diff_flush(struct diff_options *options) } } + if (output_format & DIFF_FORMAT_CALLBACK) + options->format_callback(q, options, options->format_callback_data); + for (i = 0; i < q->nr; i++) diff_free_filepair(q->queue[i]); free_queue: @@ -8,6 +8,7 @@ struct rev_info; struct diff_options; +struct diff_queue_struct; typedef void (*change_fn_t)(struct diff_options *options, unsigned old_mode, unsigned new_mode, @@ -20,6 +21,9 @@ typedef void (*add_remove_fn_t)(struct diff_options *options, const unsigned char *sha1, const char *base, const char *path); +typedef void (*diff_format_fn_t)(struct diff_queue_struct *q, + struct diff_options *options, void *data); + #define DIFF_FORMAT_RAW 0x0001 #define DIFF_FORMAT_DIFFSTAT 0x0002 #define DIFF_FORMAT_SUMMARY 0x0004 @@ -35,6 +39,8 @@ typedef void (*add_remove_fn_t)(struct diff_options *options, */ #define DIFF_FORMAT_NO_OUTPUT 0x0080 +#define DIFF_FORMAT_CALLBACK 0x0100 + struct diff_options { const char *filter; const char *orderfile; @@ -68,6 +74,8 @@ struct diff_options { int *pathlens; change_fn_t change; add_remove_fn_t add_remove; + diff_format_fn_t format_callback; + void *format_callback_data; }; enum color_diff { diff --git a/diffcore-break.c b/diffcore-break.c index ed0e14c6d8..acb18db1db 100644 --- a/diffcore-break.c +++ b/diffcore-break.c @@ -56,7 +56,7 @@ static int should_break(struct diff_filespec *src, return 0; /* leave symlink rename alone */ if (src->sha1_valid && dst->sha1_valid && - !memcmp(src->sha1, dst->sha1, 20)) + !hashcmp(src->sha1, dst->sha1)) return 0; /* they are the same */ if (diff_populate_filespec(src, 0) || diff_populate_filespec(dst, 0)) diff --git a/diffcore-rename.c b/diffcore-rename.c index 0ec488a903..ef239012b6 100644 --- a/diffcore-rename.c +++ b/diffcore-rename.c @@ -101,7 +101,7 @@ static int is_exact_match(struct diff_filespec *src, int contents_too) { if (src->sha1_valid && dst->sha1_valid && - !memcmp(src->sha1, dst->sha1, 20)) + !hashcmp(src->sha1, dst->sha1)) return 1; if (!contents_too) return 0; @@ -101,8 +101,8 @@ void add_exclude(const char *string, const char *base, x->baselen = baselen; if (which->nr == which->alloc) { which->alloc = alloc_nr(which->alloc); - which->excludes = realloc(which->excludes, - which->alloc * sizeof(x)); + which->excludes = xrealloc(which->excludes, + which->alloc * sizeof(x)); } which->excludes[which->nr++] = x; } @@ -112,17 +112,15 @@ static int add_excludes_from_file_1(const char *fname, int baselen, struct exclude_list *which) { + struct stat st; int fd, i; long size; char *buf, *entry; fd = open(fname, O_RDONLY); - if (fd < 0) + if (fd < 0 || fstat(fd, &st) < 0) goto err; - size = lseek(fd, 0, SEEK_END); - if (size < 0) - goto err; - lseek(fd, 0, SEEK_SET); + size = st.st_size; if (size == 0) { close(fd); return 0; @@ -293,7 +291,7 @@ static int read_directory_recursive(struct dir_struct *dir, const char *path, co if (fdir) { int exclude_stk; struct dirent *de; - char fullname[MAXPATHLEN + 1]; + char fullname[PATH_MAX + 1]; memcpy(fullname, base, baselen); exclude_stk = push_exclude_per_directory(dir, base, baselen); @@ -399,3 +397,10 @@ int read_directory(struct dir_struct *dir, const char *path, const char *base, i qsort(dir->entries, dir->nr, sizeof(struct dir_entry *), cmp_name); return dir->nr; } + +int +file_exists(const char *f) +{ + struct stat sb; + return stat(f, &sb) == 0; +} @@ -47,5 +47,6 @@ extern int excluded(struct dir_struct *, const char *); extern void add_excludes_from_file(struct dir_struct *, const char *fname); extern void add_exclude(const char *string, const char *base, int baselen, struct exclude_list *which); +extern int file_exists(const char *); #endif diff --git a/dump-cache-tree.c b/dump-cache-tree.c index 1ccaf51773..1f73f1ea7d 100644 --- a/dump-cache-tree.c +++ b/dump-cache-tree.c @@ -33,7 +33,7 @@ static int dump_cache_tree(struct cache_tree *it, } else { dump_one(it, pfx, ""); - if (memcmp(it->sha1, ref->sha1, 20) || + if (hashcmp(it->sha1, ref->sha1) || ref->entry_count != it->entry_count || ref->subtree_nr != it->subtree_nr) { dump_one(ref, pfx, "#(ref) "); @@ -135,7 +135,7 @@ static int write_entry(struct cache_entry *ce, char *path, struct checkout *stat int checkout_entry(struct cache_entry *ce, struct checkout *state, char *topath) { - static char path[MAXPATHLEN+1]; + static char path[PATH_MAX + 1]; struct stat st; int len = state->base_dir_len; @@ -172,5 +172,3 @@ int checkout_entry(struct cache_entry *ce, struct checkout *state, char *topath) create_directories(path, state); return write_entry(ce, path, state, 0); } - - diff --git a/environment.c b/environment.c index 87162b2572..84d870ca4e 100644 --- a/environment.c +++ b/environment.c @@ -13,20 +13,21 @@ char git_default_email[MAX_GITNAME]; char git_default_name[MAX_GITNAME]; int use_legacy_headers = 1; int trust_executable_bit = 1; -int assume_unchanged = 0; -int prefer_symlink_refs = 0; -int log_all_ref_updates = 0; +int assume_unchanged; +int prefer_symlink_refs; +int log_all_ref_updates; int warn_ambiguous_refs = 1; -int repository_format_version = 0; +int repository_format_version; char git_commit_encoding[MAX_ENCODING_LENGTH] = "utf-8"; int shared_repository = PERM_UMASK; -const char *apply_default_whitespace = NULL; +const char *apply_default_whitespace; int zlib_compression_level = Z_DEFAULT_COMPRESSION; int pager_in_use; int pager_use_color = 1; -static char *git_dir, *git_object_dir, *git_index_file, *git_refs_dir, - *git_graft_file; +static const char *git_dir; +static char *git_object_dir, *git_index_file, *git_refs_dir, *git_graft_file; + static void setup_git_env(void) { git_dir = getenv(GIT_DIR_ENVIRONMENT); @@ -46,10 +47,10 @@ static void setup_git_env(void) } git_graft_file = getenv(GRAFT_ENVIRONMENT); if (!git_graft_file) - git_graft_file = strdup(git_path("info/grafts")); + git_graft_file = xstrdup(git_path("info/grafts")); } -char *get_git_dir(void) +const char *get_git_dir(void) { if (!git_dir) setup_git_env(); diff --git a/exec_cmd.c b/exec_cmd.c index 62f51fcd6e..5d6a1247b4 100644 --- a/exec_cmd.c +++ b/exec_cmd.c @@ -5,7 +5,7 @@ extern char **environ; static const char *builtin_exec_path = GIT_EXEC_PATH; -static const char *current_exec_path = NULL; +static const char *current_exec_path; void git_set_exec_path(const char *exec_path) { @@ -97,26 +97,12 @@ int execv_git_cmd(const char **argv) tmp = argv[0]; argv[0] = git_command; - if (getenv("GIT_TRACE")) { - const char **p = argv; - fputs("trace: exec:", stderr); - while (*p) { - fputc(' ', stderr); - sq_quote_print(stderr, *p); - ++p; - } - putc('\n', stderr); - fflush(stderr); - } + trace_argv_printf(argv, -1, "trace: exec:"); /* execve() can only ever return if it fails */ execve(git_command, (char **)argv, environ); - if (getenv("GIT_TRACE")) { - fprintf(stderr, "trace: exec failed: %s\n", - strerror(errno)); - fflush(stderr); - } + trace_printf("trace: exec failed: %s\n", strerror(errno)); argv[0] = tmp; } diff --git a/fetch-clone.c b/fetch-clone.c index 5e84c4620f..b632ca0438 100644 --- a/fetch-clone.c +++ b/fetch-clone.c @@ -1,6 +1,7 @@ #include "cache.h" #include "exec_cmd.h" #include "pkt-line.h" +#include "sideband.h" #include <sys/wait.h> #include <sys/time.h> @@ -44,9 +45,8 @@ static int finish_pack(const char *pack_tmp_name, const char *me) for (;;) { int status, code; - int retval = waitpid(pid, &status, 0); - if (retval < 0) { + if (waitpid(pid, &status, 0) < 0) { if (errno == EINTR) continue; error("waitpid failed (%s)", strerror(errno)); @@ -115,36 +115,13 @@ static pid_t setup_sideband(int sideband, const char *me, int fd[2], int xd[2]) die("%s: unable to fork off sideband demultiplexer", me); if (!side_pid) { /* subprocess */ + char buf[LARGE_PACKET_MAX]; + close(fd[0]); if (xd[0] != xd[1]) close(xd[1]); - while (1) { - char buf[1024]; - int len = packet_read_line(xd[0], buf, sizeof(buf)); - if (len == 0) - break; - if (len < 1) - die("%s: protocol error: no band designator", - me); - len--; - switch (buf[0] & 0xFF) { - case 3: - safe_write(2, "remote: ", 8); - safe_write(2, buf+1, len); - safe_write(2, "\n", 1); - exit(1); - case 2: - safe_write(2, "remote: ", 8); - safe_write(2, buf+1, len); - continue; - case 1: - safe_write(fd[1], buf+1, len); - continue; - default: - die("%s: protocol error: bad band #%d", - me, (buf[0] & 0xFF)); - } - } + if (recv_sideband(me, xd[0], fd[1], 2, buf, sizeof(buf))) + exit(1); exit(0); } close(xd[0]); diff --git a/fetch-pack.c b/fetch-pack.c index b7824dbed4..e8708aa802 100644 --- a/fetch-pack.c +++ b/fetch-pack.c @@ -24,8 +24,8 @@ static const char *exec = "git-upload-pack"; */ #define MAX_IN_VAIN 256 -static struct commit_list *rev_list = NULL; -static int non_common_revs = 0, multi_ack = 0, use_thin_pack = 0, use_sideband; +static struct commit_list *rev_list; +static int non_common_revs, multi_ack, use_thin_pack, use_sideband; static void rev_list_push(struct commit *commit, int mark) { @@ -166,10 +166,11 @@ static int find_common(int fd[2], unsigned char *result_sha1, } if (!fetching) - packet_write(fd[1], "want %s%s%s%s\n", + packet_write(fd[1], "want %s%s%s%s%s\n", sha1_to_hex(remote), (multi_ack ? " multi_ack" : ""), - (use_sideband ? " side-band" : ""), + (use_sideband == 2 ? " side-band-64k" : ""), + (use_sideband == 1 ? " side-band" : ""), (use_thin_pack ? " thin-pack" : "")); else packet_write(fd[1], "want %s\n", sha1_to_hex(remote)); @@ -250,7 +251,7 @@ done: return retval; } -static struct commit_list *complete = NULL; +static struct commit_list *complete; static int mark_complete(const char *path, const unsigned char *sha1) { @@ -404,7 +405,7 @@ static int everything_local(struct ref **refs, int nr_match, char **match) continue; } - memcpy(ref->new_sha1, local, 20); + hashcpy(ref->new_sha1, local); if (!verbose) continue; fprintf(stderr, @@ -426,7 +427,12 @@ static int fetch_pack(int fd[2], int nr_match, char **match) fprintf(stderr, "Server supports multi_ack\n"); multi_ack = 1; } - if (server_supports("side-band")) { + if (server_supports("side-band-64k")) { + if (verbose) + fprintf(stderr, "Server supports side-band-64k\n"); + use_sideband = 2; + } + else if (server_supports("side-band")) { if (verbose) fprintf(stderr, "Server supports side-band\n"); use_sideband = 1; @@ -519,7 +525,7 @@ int main(int argc, char **argv) ret = fetch_pack(fd, nr_heads, heads); close(fd[0]); close(fd[1]); - finish_connect(pid); + ret |= finish_connect(pid); if (!ret && nr_heads) { /* If the heads to pull were given, we should have @@ -534,5 +540,5 @@ int main(int argc, char **argv) } } - return ret; + return !!ret; } @@ -84,7 +84,7 @@ static int process_commit(struct commit *commit) if (commit->object.flags & COMPLETE) return 0; - memcpy(current_commit_sha1, commit->object.sha1, 20); + hashcpy(current_commit_sha1, commit->object.sha1); pull_say("walk %s\n", sha1_to_hex(commit->object.sha1)); @@ -234,8 +234,8 @@ int pull_targets_stdin(char ***target, const char ***write_ref) *target = xrealloc(*target, targets_alloc * sizeof(**target)); *write_ref = xrealloc(*write_ref, targets_alloc * sizeof(**write_ref)); } - (*target)[targets] = strdup(tg_one); - (*write_ref)[targets] = rf_one ? strdup(rf_one) : NULL; + (*target)[targets] = xstrdup(tg_one); + (*write_ref)[targets] = rf_one ? xstrdup(rf_one) : NULL; targets++; } return targets; @@ -302,8 +302,7 @@ int pull(int targets, char **target, const char **write_ref, if (ret) goto unlock_and_fail; } - if (msg) - free(msg); + free(msg); return 0; diff --git a/fsck-objects.c b/fsck-objects.c index e167f4105f..4d994f3fc8 100644 --- a/fsck-objects.c +++ b/fsck-objects.c @@ -14,12 +14,12 @@ #define REACHABLE 0x0001 #define SEEN 0x0002 -static int show_root = 0; -static int show_tags = 0; -static int show_unreachable = 0; -static int check_full = 0; -static int check_strict = 0; -static int keep_cache_objects = 0; +static int show_root; +static int show_tags; +static int show_unreachable; +static int check_full; +static int check_strict; +static int keep_cache_objects; static unsigned char head_sha1[20]; #ifdef NO_D_INO_IN_DIRENT @@ -356,7 +356,7 @@ static void add_sha1_list(unsigned char *sha1, unsigned long ino) int nr; entry->ino = ino; - memcpy(entry->sha1, sha1, 20); + hashcpy(entry->sha1, sha1); nr = sha1_list.nr; if (nr == MAX_SHA1_ENTRIES) { fsck_sha1_list(); @@ -366,13 +366,13 @@ static void add_sha1_list(unsigned char *sha1, unsigned long ino) sha1_list.nr = ++nr; } -static int fsck_dir(int i, char *path) +static void fsck_dir(int i, char *path) { DIR *dir = opendir(path); struct dirent *de; if (!dir) - return 0; + return; while ((de = readdir(dir)) != NULL) { char name[100]; @@ -398,10 +398,9 @@ static int fsck_dir(int i, char *path) fprintf(stderr, "bad sha1 file: %s/%s\n", path, de->d_name); } closedir(dir); - return 0; } -static int default_refs = 0; +static int default_refs; static int fsck_handle_ref(const char *refname, const unsigned char *sha1) { @@ -426,8 +425,23 @@ static int fsck_handle_ref(const char *refname, const unsigned char *sha1) static void get_default_heads(void) { for_each_ref(fsck_handle_ref); - if (!default_refs) - die("No default references"); + + /* + * Not having any default heads isn't really fatal, but + * it does mean that "--unreachable" no longer makes any + * sense (since in this case everything will obviously + * be unreachable by definition. + * + * Showing dangling objects is valid, though (as those + * dangling objects are likely lost heads). + * + * So we just print a warning about it, and clear the + * "show_unreachable" flag. + */ + if (!default_refs) { + error("No default references"); + show_unreachable = 0; + } } static void fsck_object_dir(const char *path) @@ -444,7 +458,7 @@ static void fsck_object_dir(const char *path) static int fsck_head_link(void) { unsigned char sha1[20]; - const char *git_HEAD = strdup(git_path("HEAD")); + const char *git_HEAD = xstrdup(git_path("HEAD")); const char *git_refs_heads_master = resolve_ref(git_HEAD, sha1, 1); int pfxlen = strlen(git_HEAD) - 4; /* strip .../.git/ part */ @@ -453,7 +467,7 @@ static int fsck_head_link(void) if (strncmp(git_refs_heads_master + pfxlen, "refs/heads/", 11)) return error("HEAD points to something strange (%s)", git_refs_heads_master + pfxlen); - if (!memcmp(null_sha1, sha1, 20)) + if (is_null_sha1(sha1)) return error("HEAD: not a valid git pointer"); return 0; } diff --git a/generate-cmdlist.sh b/generate-cmdlist.sh index ec1eda20de..5450918be3 100755 --- a/generate-cmdlist.sh +++ b/generate-cmdlist.sh @@ -12,6 +12,7 @@ struct cmdname_help common_cmds[] = {" sort <<\EOF | add apply +archive bisect branch checkout @@ -166,10 +166,25 @@ fi if test -d "$dotest" then - if test ",$#," != ",0," || ! tty -s - then - die "previous dotest directory $dotest still exists but mbox given." - fi + case "$#,$skip$resolved" in + 0,*t*) + # Explicit resume command and we do not have file, so + # we are happy. + : ;; + 0,) + # No file input but without resume parameters; catch + # user error to feed us a patch from standard input + # when there is already .dotest. This is somewhat + # unreliable -- stdin could be /dev/null for example + # and the caller did not intend to feed us a patch but + # wanted to continue unattended. + tty -s + ;; + *) + false + ;; + esac || + die "previous dotest directory $dotest still exists but mbox given." resume=yes else # Make sure we are not given --skip nor --resolved diff --git a/git-cherry.sh b/git-cherry.sh index f0e8831fa4..8832573fee 100755 --- a/git-cherry.sh +++ b/git-cherry.sh @@ -51,9 +51,6 @@ patch=$tmp-patch mkdir $patch trap "rm -rf $tmp-*" 0 1 2 3 15 -_x40='[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]' -_x40="$_x40$_x40$_x40$_x40$_x40$_x40$_x40$_x40" - for c in $inup do git-diff-tree -p $c diff --git a/git-clone.sh b/git-clone.sh index 7060bdab01..e1b3bf382f 100755 --- a/git-clone.sh +++ b/git-clone.sh @@ -298,7 +298,7 @@ yes,yes) fi git-ls-remote "$repo" >"$GIT_DIR/CLONE_HEAD" || exit 1 ;; - https://*|http://*) + https://*|http://*|ftp://*) if test -z "@@NO_CURL@@" then clone_dumb_http "$repo" "$D" diff --git a/git-commit.sh b/git-commit.sh index 4cf3fab05c..5a4c659b6f 100755 --- a/git-commit.sh +++ b/git-commit.sh @@ -60,26 +60,6 @@ report () { } run_status () { - ( - # We always show status for the whole tree. - cd "$TOP" - - IS_INITIAL="$initial_commit" - REFERENCE=HEAD - case "$amend" in - t) - # If we are amending the initial commit, there - # is no HEAD^1. - if git-rev-parse --verify "HEAD^1" >/dev/null 2>&1 - then - REFERENCE="HEAD^1" - IS_INITIAL= - else - IS_INITIAL=t - fi - ;; - esac - # If TMP_INDEX is defined, that means we are doing # "--only" partial commit, and that index file is used # to build the tree for the commit. Otherwise, if @@ -96,85 +76,14 @@ run_status () { export GIT_INDEX_FILE fi - case "$branch" in - refs/heads/master) ;; - *) echo "# On branch $branch" ;; - esac - - if test -z "$IS_INITIAL" - then - git-diff-index -M --cached --name-status \ - --diff-filter=MDTCRA $REFERENCE | - sed -e ' - s/\\/\\\\/g - s/ /\\ /g - ' | - report "Updated but not checked in" "will commit" - committable="$?" - else - echo '# -# Initial commit -#' - git-ls-files | - sed -e ' - s/\\/\\\\/g - s/ /\\ /g - s/^/A / - ' | - report "Updated but not checked in" "will commit" - - committable="$?" - fi - - git-diff-files --name-status | - sed -e ' - s/\\/\\\\/g - s/ /\\ /g - ' | - report "Changed but not updated" \ - "use git-update-index to mark for commit" - - option="" - if test -z "$untracked_files"; then - option="--directory --no-empty-directory" - fi - hdr_shown= - if test -f "$GIT_DIR/info/exclude" - then - git-ls-files --others $option \ - --exclude-from="$GIT_DIR/info/exclude" \ - --exclude-per-directory=.gitignore - else - git-ls-files --others $option \ - --exclude-per-directory=.gitignore - fi | - while read line; do - if [ -z "$hdr_shown" ]; then - echo '#' - echo '# Untracked files:' - echo '# (use "git add" to add to commit)' - echo '#' - hdr_shown=1 - fi - echo "# $line" - done - - if test -n "$verbose" -a -z "$IS_INITIAL" - then - git-diff-index --cached -M -p --diff-filter=MDTCRA $REFERENCE - fi - case "$committable" in - 0) - case "$amend" in - t) - echo "# No changes" ;; - *) - echo "nothing to commit" ;; - esac - exit 1 ;; - esac - exit 0 - ) + case "$status_only" in + t) color= ;; + *) color=--nocolor ;; + esac + git-runstatus ${color} \ + ${verbose:+--verbose} \ + ${amend:+--amend} \ + ${untracked_files:+--untracked} } trap ' diff --git a/git-compat-util.h b/git-compat-util.h index b2e18954c0..0272d043d0 100644 --- a/git-compat-util.h +++ b/git-compat-util.h @@ -26,6 +26,13 @@ #include <sys/types.h> #include <dirent.h> +/* On most systems <limits.h> would have given us this, but + * not on some systems (e.g. GNU/Hurd). + */ +#ifndef PATH_MAX +#define PATH_MAX 4096 +#endif + #ifdef __GNUC__ #define NORETURN __attribute__((__noreturn__)) #else @@ -84,6 +91,14 @@ extern char *gitstrcasestr(const char *haystack, const char *needle); extern size_t gitstrlcpy(char *, const char *, size_t); #endif +static inline char* xstrdup(const char *str) +{ + char *ret = strdup(str); + if (!ret) + die("Out of memory, strdup failed"); + return ret; +} + static inline void *xmalloc(size_t size) { void *ret = malloc(size); @@ -172,7 +187,4 @@ static inline int sane_case(int x, int high) return x; } -#ifndef MAXPATHLEN -#define MAXPATHLEN 256 -#endif #endif diff --git a/git-fetch.sh b/git-fetch.sh index c2eebee798..09a5d6ceab 100755 --- a/git-fetch.sh +++ b/git-fetch.sh @@ -286,7 +286,7 @@ fetch_main () { # There are transports that can fetch only one head at a time... case "$remote" in - http://* | https://*) + http://* | https://* | ftp://*) if [ -n "$GIT_SSL_NO_VERIFY" ]; then curl_extra_args="-k" fi @@ -350,7 +350,7 @@ fetch_main () { done case "$remote" in - http://* | https://* | rsync://* ) + http://* | https://* | ftp://* | rsync://* ) ;; # we are already done. *) ( : subshell because we muck with IFS diff --git a/git-ls-remote.sh b/git-ls-remote.sh index 2fdcaf7886..2c0b52122f 100755 --- a/git-ls-remote.sh +++ b/git-ls-remote.sh @@ -49,7 +49,7 @@ trap "rm -fr $tmp-*" 0 1 2 3 15 tmpdir=$tmp-d case "$peek_repo" in -http://* | https://* ) +http://* | https://* | ftp://* ) if [ -n "$GIT_SSL_NO_VERIFY" ]; then curl_extra_args="-k" fi diff --git a/git-merge.sh b/git-merge.sh index a9cfafb1df..d049e16431 100755 --- a/git-merge.sh +++ b/git-merge.sh @@ -9,15 +9,20 @@ USAGE='[-n] [--no-commit] [--squash] [-s <strategy>]... <merge-message> <head> < LF=' ' -all_strategies='recursive octopus resolve stupid ours' -default_twohead_strategies='recursive' +all_strategies='recursive recur octopus resolve stupid ours' +case "${GIT_USE_RECUR_FOR_RECURSIVE}" in +'') + default_twohead_strategies=recursive ;; +?*) + default_twohead_strategies=recur ;; +esac default_octopus_strategies='octopus' no_trivial_merge_strategies='ours' use_strategies= index_merge=t if test "@@NO_PYTHON@@"; then - all_strategies='resolve octopus stupid ours' + all_strategies='recur resolve octopus stupid ours' default_twohead_strategies='resolve' fi @@ -117,6 +122,10 @@ do strategy="$2" shift ;; esac + case "$strategy,${GIT_USE_RECUR_FOR_RECURSIVE}" in + recursive,?*) + strategy=recur ;; + esac case " $all_strategies " in *" $strategy "*) use_strategies="$use_strategies$strategy " ;; diff --git a/git-rebase.sh b/git-rebase.sh index 7d3a5d0e71..20f74d4167 100755 --- a/git-rebase.sh +++ b/git-rebase.sh @@ -35,7 +35,13 @@ If you would prefer to skip this patch, instead run \"git rebase --skip\". To restore the original branch and stop rebasing run \"git rebase --abort\". " unset newbase -strategy=recursive +case "${GIT_USE_RECUR_FOR_RECURSIVE}" in +'') + strategy=recursive ;; +?*) + strategy=recur ;; +esac + do_merge= dotest=$GIT_DIR/.dotest-merge prec=4 @@ -200,6 +206,11 @@ do shift done +case "$strategy,${GIT_USE_RECUR_FOR_RECURSIVE}" in +recursive,?*) + strategy=recur ;; +esac + # Make sure we do not have .dotest if test -z "$do_merge" then diff --git a/git-repack.sh b/git-repack.sh index 9da92fb061..b525fc5dfd 100755 --- a/git-repack.sh +++ b/git-repack.sh @@ -24,8 +24,10 @@ do shift done -rm -f .tmp-pack-* PACKDIR="$GIT_OBJECT_DIRECTORY/pack" +PACKTMP="$GIT_DIR/.tmp-$$-pack" +rm -f "$PACKTMP"-* +trap 'rm -f "$PACKTMP"-*' 0 1 2 3 15 # There will be more repacking strategies to come... case ",$all_into_one," in @@ -38,15 +40,16 @@ case ",$all_into_one," in pack_objects= # Redundancy check in all-into-one case is trivial. - existing=`cd "$PACKDIR" && \ + existing=`test -d "$PACKDIR" && cd "$PACKDIR" && \ find . -type f \( -name '*.pack' -o -name '*.idx' \) -print` ;; esac + pack_objects="$pack_objects $local $quiet $no_reuse_delta$extra" name=$( { git-rev-list --objects --all $rev_list || echo "git-rev-list died with exit code $?" } | - git-pack-objects --non-empty $pack_objects .tmp-pack) || + git-pack-objects --non-empty $pack_objects "$PACKTMP") || exit 1 if [ -z "$name" ]; then echo Nothing new to pack. @@ -64,8 +67,8 @@ else "$PACKDIR/old-pack-$name.$sfx" fi done && - mv -f .tmp-pack-$name.pack "$PACKDIR/pack-$name.pack" && - mv -f .tmp-pack-$name.idx "$PACKDIR/pack-$name.idx" && + mv -f "$PACKTMP-$name.pack" "$PACKDIR/pack-$name.pack" && + mv -f "$PACKTMP-$name.idx" "$PACKDIR/pack-$name.idx" && test -f "$PACKDIR/pack-$name.pack" && test -f "$PACKDIR/pack-$name.idx" || { echo >&2 "Couldn't replace the existing pack with updated one." diff --git a/git-reset.sh b/git-reset.sh index 36fc8ce25b..3133b5bd25 100755 --- a/git-reset.sh +++ b/git-reset.sh @@ -3,9 +3,6 @@ USAGE='[--mixed | --soft | --hard] [<commit-ish>]' . git-sh-setup -tmp=${GIT_DIR}/reset.$$ -trap 'rm -f $tmp-*' 0 1 2 3 15 - update= reset_type=--mixed case "$1" in diff --git a/git-send-email.perl b/git-send-email.perl index a83c7e9094..746c525079 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 0d58bb9b37..0290850b66 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 ######################### @@ -29,13 +29,15 @@ static void prepend_to_path(const char *dir, int len) path_len = len + strlen(old_path) + 1; - path = malloc(path_len + 1); + path = xmalloc(path_len + 1); memcpy(path, dir, len); path[len] = ':'; memcpy(path + len + 1, old_path, path_len - len); setenv("PATH", path, 1); + + free(path); } static int handle_options(const char*** argv, int* argc) @@ -92,12 +94,12 @@ static int handle_options(const char*** argv, int* argc) } static const char *alias_command; -static char *alias_string = NULL; +static char *alias_string; static int git_alias_config(const char *var, const char *value) { if (!strncmp(var, "alias.", 6) && !strcmp(var + 6, alias_command)) { - alias_string = strdup(value); + alias_string = xstrdup(value); } return 0; } @@ -120,7 +122,7 @@ static int split_cmdline(char *cmdline, const char ***argv) ; /* skip */ if (count >= size) { size += 16; - *argv = realloc(*argv, sizeof(char*) * size); + *argv = xrealloc(*argv, sizeof(char*) * size); } (*argv)[count++] = cmdline + dst; } else if(!quoted && (c == '\'' || c == '"')) { @@ -179,20 +181,12 @@ static int handle_alias(int *argcp, const char ***argv) if (!strcmp(alias_command, new_argv[0])) die("recursive alias: %s", alias_command); - if (getenv("GIT_TRACE")) { - int i; - fprintf(stderr, "trace: alias expansion: %s =>", - alias_command); - for (i = 0; i < count; ++i) { - fputc(' ', stderr); - sq_quote_print(stderr, new_argv[i]); - } - fputc('\n', stderr); - fflush(stderr); - } + trace_argv_printf(new_argv, count, + "trace: alias expansion: %s =>", + alias_command); - new_argv = realloc(new_argv, sizeof(char*) * - (count + *argcp + 1)); + new_argv = xrealloc(new_argv, sizeof(char*) * + (count + *argcp + 1)); /* insert after command name */ memcpy(new_argv + count, *argv + 1, sizeof(char*) * *argcp); new_argv[count+*argcp] = NULL; @@ -226,11 +220,12 @@ static void handle_internal_command(int argc, const char **argv, char **envp) } commands[] = { { "add", cmd_add, RUN_SETUP }, { "apply", cmd_apply }, + { "archive", cmd_archive }, { "cat-file", cmd_cat_file, RUN_SETUP }, { "checkout-index", cmd_checkout_index, RUN_SETUP }, { "check-ref-format", cmd_check_ref_format }, { "commit-tree", cmd_commit_tree, RUN_SETUP }, - { "count-objects", cmd_count_objects }, + { "count-objects", cmd_count_objects, RUN_SETUP }, { "diff", cmd_diff, RUN_SETUP }, { "diff-files", cmd_diff_files, RUN_SETUP }, { "diff-index", cmd_diff_index, RUN_SETUP }, @@ -258,14 +253,17 @@ static void handle_internal_command(int argc, const char **argv, char **envp) { "rev-list", cmd_rev_list, RUN_SETUP }, { "rev-parse", cmd_rev_parse, RUN_SETUP }, { "rm", cmd_rm, RUN_SETUP }, + { "runstatus", cmd_runstatus, RUN_SETUP }, { "show-branch", cmd_show_branch, RUN_SETUP }, { "show", cmd_show, RUN_SETUP | USE_PAGER }, { "stripspace", cmd_stripspace }, { "symbolic-ref", cmd_symbolic_ref, RUN_SETUP }, { "tar-tree", cmd_tar_tree, RUN_SETUP }, + { "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-archive", cmd_upload_archive }, { "upload-tar", cmd_upload_tar }, { "version", cmd_version }, { "whatchanged", cmd_whatchanged, RUN_SETUP | USE_PAGER }, @@ -291,16 +289,7 @@ static void handle_internal_command(int argc, const char **argv, char **envp) prefix = setup_git_directory(); if (p->option & USE_PAGER) setup_pager(); - if (getenv("GIT_TRACE")) { - int i; - fprintf(stderr, "trace: built-in: git"); - for (i = 0; i < argc; ++i) { - fputc(' ', stderr); - sq_quote_print(stderr, argv[i]); - } - putc('\n', stderr); - fflush(stderr); - } + trace_argv_printf(argv, argc, "trace: built-in: git"); exit(p->fn(argc, argv, prefix)); } @@ -308,7 +297,7 @@ static void handle_internal_command(int argc, const char **argv, char **envp) int main(int argc, const char **argv, char **envp) { - const char *cmd = argv[0]; + const char *cmd = argv[0] ? argv[0] : "git-help"; char *slash = strrchr(cmd, '/'); const char *exec_path = NULL; int done_alias = 0; @@ -2,7 +2,7 @@ # Tcl ignores the next line -*- tcl -*- \ exec wish "$0" -- "$@" -# Copyright (C) 2005 Paul Mackerras. All rights reserved. +# Copyright (C) 2005-2006 Paul Mackerras. All rights reserved. # This program is free software; it may be used, copied, modified # and distributed under the terms of the GNU General Public Licence, # either version 2, or (at your option) any later version. @@ -17,13 +17,12 @@ proc gitdir {} { } proc start_rev_list {view} { - global startmsecs nextupdate ncmupdate + global startmsecs nextupdate global commfd leftover tclencoding datemode global viewargs viewfiles commitidx set startmsecs [clock clicks -milliseconds] set nextupdate [expr {$startmsecs + 100}] - set ncmupdate 1 set commitidx($view) 0 set args $viewargs($view) if {$viewfiles($view) ne {}} { @@ -79,7 +78,7 @@ proc getcommitlines {fd view} { global parentlist childlist children curview hlview global vparentlist vchildlist vdisporder vcmitlisted - set stuff [read $fd] + set stuff [read $fd 500000] if {$stuff == {}} { if {![eof $fd]} return global viewname @@ -185,7 +184,7 @@ proc getcommitlines {fd view} { } if {$gotsome} { if {$view == $curview} { - layoutmore + while {[layoutmore $nextupdate]} doupdate } elseif {[info exists hlview] && $view == $hlview} { vhighlightmore } @@ -196,20 +195,13 @@ proc getcommitlines {fd view} { } proc doupdate {} { - global commfd nextupdate numcommits ncmupdate + global commfd nextupdate numcommits foreach v [array names commfd] { fileevent $commfd($v) readable {} } update set nextupdate [expr {[clock clicks -milliseconds] + 100}] - if {$numcommits < 100} { - set ncmupdate [expr {$numcommits + 1}] - } elseif {$numcommits < 10000} { - set ncmupdate [expr {$numcommits + 10}] - } else { - set ncmupdate [expr {$numcommits + 100}] - } foreach v [array names commfd] { set fd $commfd($v) fileevent $fd readable [list getcommitlines $fd $v] @@ -341,13 +333,13 @@ proc readrefs {} { set tag {} catch { set commit [exec git rev-parse "$id^0"] - if {"$commit" != "$id"} { + if {$commit != $id} { set tagids($name) $commit lappend idtags($commit) $name } } catch { - set tagcontents($name) [exec git cat-file tag "$id"] + set tagcontents($name) [exec git cat-file tag $id] } } elseif { $type == "heads" } { set headids($name) $id @@ -384,6 +376,23 @@ proc error_popup msg { show_error $w $w $msg } +proc confirm_popup msg { + global confirm_ok + set confirm_ok 0 + set w .confirm + toplevel $w + wm transient $w . + message $w.m -text $msg -justify center -aspect 400 + pack $w.m -side top -fill x -padx 20 -pady 20 + button $w.ok -text OK -command "set confirm_ok 1; destroy $w" + pack $w.ok -side left -fill x + button $w.cancel -text Cancel -command "destroy $w" + pack $w.cancel -side right -fill x + bind $w <Visibility> "grab $w; focus $w" + tkwait window $w + return $confirm_ok +} + proc makewindow {} { global canv canv2 canv3 linespc charspc ctext cflist global textfont mainfont uifont @@ -394,6 +403,7 @@ proc makewindow {} { global highlight_files gdttype global searchstring sstring global bgcolor fgcolor bglist fglist diffcolors + global headctxmenu menu .bar .bar add cascade -label "File" -menu .bar.file @@ -711,6 +721,16 @@ proc makewindow {} { $rowctxmenu add command -label "Make patch" -command mkpatch $rowctxmenu add command -label "Create tag" -command mktag $rowctxmenu add command -label "Write commit to file" -command writecommit + $rowctxmenu add command -label "Create new branch" -command mkbranch + $rowctxmenu add command -label "Cherry-pick this commit" \ + -command cherrypick + + set headctxmenu .headctxmenu + menu $headctxmenu -tearoff 0 + $headctxmenu add command -label "Check out this branch" \ + -command cobranch + $headctxmenu add command -label "Remove this branch" \ + -command rmbranch } # mouse-2 makes all windows scan vertically, but only the one @@ -1669,7 +1689,7 @@ proc showview {n} { show_status "Reading commits..." } if {[info exists commfd($n)]} { - layoutmore + layoutmore {} } else { finishcommits } @@ -2350,20 +2370,38 @@ proc visiblerows {} { return [list $r0 $r1] } -proc layoutmore {} { +proc layoutmore {tmax} { global rowlaidout rowoptim commitidx numcommits optim_delay global uparrowlen curview - set row $rowlaidout - set rowlaidout [layoutrows $row $commitidx($curview) 0] - set orow [expr {$rowlaidout - $uparrowlen - 1}] - if {$orow > $rowoptim} { - optimize_rows $rowoptim 0 $orow - set rowoptim $orow - } - set canshow [expr {$rowoptim - $optim_delay}] - if {$canshow > $numcommits} { - showstuff $canshow + while {1} { + if {$rowoptim - $optim_delay > $numcommits} { + showstuff [expr {$rowoptim - $optim_delay}] + } elseif {$rowlaidout - $uparrowlen - 1 > $rowoptim} { + set nr [expr {$rowlaidout - $uparrowlen - 1 - $rowoptim}] + if {$nr > 100} { + set nr 100 + } + optimize_rows $rowoptim 0 [expr {$rowoptim + $nr}] + incr rowoptim $nr + } elseif {$commitidx($curview) > $rowlaidout} { + set nr [expr {$commitidx($curview) - $rowlaidout}] + # may need to increase this threshold if uparrowlen or + # mingaplen are increased... + if {$nr > 150} { + set nr 150 + } + set row $rowlaidout + set rowlaidout [layoutrows $row [expr {$row + $nr}] 0] + if {$rowlaidout == $row} { + return 0 + } + } else { + return 0 + } + if {$tmax ne {} && [clock clicks -milliseconds] >= $tmax} { + return 1 + } } } @@ -3236,6 +3274,8 @@ proc drawtags {id x xt y1} { -font $font -tags [list tag.$id text]] if {$ntags >= 0} { $canv bind $t <1> [list showtag $tag 1] + } elseif {$nheads >= 0} { + $canv bind $t <Button-3> [list headmenu %X %Y $id $tag] } } return $xt @@ -3263,8 +3303,7 @@ proc show_status {msg} { proc finishcommits {} { global commitidx phase curview - global canv mainfont ctext maincursor textcursor - global findinprogress pending_select + global pending_select if {$commitidx($curview) > 0} { drawrest @@ -3275,6 +3314,108 @@ proc finishcommits {} { catch {unset pending_select} } +# Insert a new commit as the child of the commit on row $row. +# The new commit will be displayed on row $row and the commits +# on that row and below will move down one row. +proc insertrow {row newcmit} { + global displayorder parentlist childlist commitlisted + global commitrow curview rowidlist rowoffsets numcommits + global rowrangelist idrowranges rowlaidout rowoptim numcommits + global linesegends selectedline + + if {$row >= $numcommits} { + puts "oops, inserting new row $row but only have $numcommits rows" + return + } + set p [lindex $displayorder $row] + set displayorder [linsert $displayorder $row $newcmit] + set parentlist [linsert $parentlist $row $p] + set kids [lindex $childlist $row] + lappend kids $newcmit + lset childlist $row $kids + set childlist [linsert $childlist $row {}] + set commitlisted [linsert $commitlisted $row 1] + set l [llength $displayorder] + for {set r $row} {$r < $l} {incr r} { + set id [lindex $displayorder $r] + set commitrow($curview,$id) $r + } + + set idlist [lindex $rowidlist $row] + set offs [lindex $rowoffsets $row] + set newoffs {} + foreach x $idlist { + if {$x eq {} || ($x eq $p && [llength $kids] == 1)} { + lappend newoffs {} + } else { + lappend newoffs 0 + } + } + if {[llength $kids] == 1} { + set col [lsearch -exact $idlist $p] + lset idlist $col $newcmit + } else { + set col [llength $idlist] + lappend idlist $newcmit + lappend offs {} + lset rowoffsets $row $offs + } + set rowidlist [linsert $rowidlist $row $idlist] + set rowoffsets [linsert $rowoffsets [expr {$row+1}] $newoffs] + + set rowrangelist [linsert $rowrangelist $row {}] + set l [llength $rowrangelist] + for {set r 0} {$r < $l} {incr r} { + set ranges [lindex $rowrangelist $r] + if {$ranges ne {} && [lindex $ranges end] >= $row} { + set newranges {} + foreach x $ranges { + if {$x >= $row} { + lappend newranges [expr {$x + 1}] + } else { + lappend newranges $x + } + } + lset rowrangelist $r $newranges + } + } + if {[llength $kids] > 1} { + set rp1 [expr {$row + 1}] + set ranges [lindex $rowrangelist $rp1] + if {$ranges eq {}} { + set ranges [list $row $rp1] + } elseif {[lindex $ranges end-1] == $rp1} { + lset ranges end-1 $row + } + lset rowrangelist $rp1 $ranges + } + foreach id [array names idrowranges] { + set ranges $idrowranges($id) + if {$ranges ne {} && [lindex $ranges end] >= $row} { + set newranges {} + foreach x $ranges { + if {$x >= $row} { + lappend newranges [expr {$x + 1}] + } else { + lappend newranges $x + } + } + set idrowranges($id) $newranges + } + } + + set linesegends [linsert $linesegends $row {}] + + incr rowlaidout + incr rowoptim + incr numcommits + + if {[info exists selectedline] && $selectedline >= $row} { + incr selectedline + } + redisplay +} + # Don't change the text pane cursor if it is currently the hand cursor, # showing that we are over a sha1 ID link. proc settextcursor {c} { @@ -3307,9 +3448,7 @@ proc notbusy {what} { } proc drawrest {} { - global numcommits global startmsecs - global canvy0 numcommits linespc global rowlaidout commitidx curview global pending_select @@ -3323,6 +3462,7 @@ proc drawrest {} { } set drawmsecs [expr {[clock clicks -milliseconds] - $startmsecs}] + #global numcommits #puts "overall $drawmsecs ms for $numcommits commits" } @@ -3603,27 +3743,20 @@ proc viewnextline {dir} { # add a list of tag or branch names at position pos # returns the number of names inserted -proc appendrefs {pos l var} { - global ctext commitrow linknum curview idtags $var +proc appendrefs {pos tags var} { + global ctext commitrow linknum curview $var if {[catch {$ctext index $pos}]} { return 0 } - set tags {} - foreach id $l { - foreach tag [set $var\($id\)] { - lappend tags [concat $tag $id] - } - } - set tags [lsort -index 1 $tags] + set tags [lsort $tags] set sep {} foreach tag $tags { - set name [lindex $tag 0] - set id [lindex $tag 1] + set id [set $var\($tag\)] set lk link$linknum incr linknum $ctext insert $pos $sep - $ctext insert $pos $name $lk + $ctext insert $pos $tag $lk $ctext tag conf $lk -foreground blue if {[info exists commitrow($curview,$id)]} { $ctext tag bind $lk <1> \ @@ -3637,6 +3770,18 @@ proc appendrefs {pos l var} { return [llength $tags] } +proc taglist {ids} { + global idtags + + set tags {} + foreach id $ids { + foreach tag $idtags($id) { + lappend tags $tag + } + } + return $tags +} + # called when we have finished computing the nearby tags proc dispneartags {} { global selectedline currentid ctext anc_tags desc_tags showneartags @@ -3646,15 +3791,15 @@ proc dispneartags {} { set id $currentid $ctext conf -state normal if {[info exists desc_heads($id)]} { - if {[appendrefs branch $desc_heads($id) idheads] > 1} { + if {[appendrefs branch $desc_heads($id) headids] > 1} { $ctext insert "branch -2c" "es" } } if {[info exists anc_tags($id)]} { - appendrefs follows $anc_tags($id) idtags + appendrefs follows [taglist $anc_tags($id)] tagids } if {[info exists desc_tags($id)]} { - appendrefs precedes $desc_tags($id) idtags + appendrefs precedes [taglist $desc_tags($id)] tagids } $ctext conf -state disabled } @@ -3787,7 +3932,7 @@ proc selectline {l isnew} { $ctext mark set branch "end -1c" $ctext mark gravity branch left if {[info exists desc_heads($id)]} { - if {[appendrefs branch $desc_heads($id) idheads] > 1} { + if {[appendrefs branch $desc_heads($id) headids] > 1} { # turn "Branch" into "Branches" $ctext insert "branch -2c" "es" } @@ -3796,13 +3941,13 @@ proc selectline {l isnew} { $ctext mark set follows "end -1c" $ctext mark gravity follows left if {[info exists anc_tags($id)]} { - appendrefs follows $anc_tags($id) idtags + appendrefs follows [taglist $anc_tags($id)] tagids } $ctext insert end "\nPrecedes: " $ctext mark set precedes "end -1c" $ctext mark gravity precedes left if {[info exists desc_tags($id)]} { - appendrefs precedes $desc_tags($id) idtags + appendrefs precedes [taglist $desc_tags($id)] tagids } $ctext insert end "\n" } @@ -4463,6 +4608,7 @@ proc redisplay {} { drawvisible if {[info exists selectedline]} { selectline $selectedline 0 + allcanvs yview moveto [lindex $span 0] } } @@ -4930,6 +5076,7 @@ proc domktag {} { set tagids($tag) $id lappend idtags($id) $tag redrawtags $id + addedtag $id } proc redrawtags {id} { @@ -5020,10 +5167,164 @@ proc wrcomcan {} { unset wrcomtop } +proc mkbranch {} { + global rowmenuid mkbrtop + + set top .makebranch + catch {destroy $top} + toplevel $top + label $top.title -text "Create new branch" + grid $top.title - -pady 10 + label $top.id -text "ID:" + entry $top.sha1 -width 40 -relief flat + $top.sha1 insert 0 $rowmenuid + $top.sha1 conf -state readonly + grid $top.id $top.sha1 -sticky w + label $top.nlab -text "Name:" + entry $top.name -width 40 + grid $top.nlab $top.name -sticky w + frame $top.buts + button $top.buts.go -text "Create" -command [list mkbrgo $top] + button $top.buts.can -text "Cancel" -command "catch {destroy $top}" + grid $top.buts.go $top.buts.can + grid columnconfigure $top.buts 0 -weight 1 -uniform a + grid columnconfigure $top.buts 1 -weight 1 -uniform a + grid $top.buts - -pady 10 -sticky ew + focus $top.name +} + +proc mkbrgo {top} { + global headids idheads + + set name [$top.name get] + set id [$top.sha1 get] + if {$name eq {}} { + error_popup "Please specify a name for the new branch" + return + } + catch {destroy $top} + nowbusy newbranch + update + if {[catch { + exec git branch $name $id + } err]} { + notbusy newbranch + error_popup $err + } else { + addedhead $id $name + # XXX should update list of heads displayed for selected commit + notbusy newbranch + redrawtags $id + } +} + +proc cherrypick {} { + global rowmenuid curview commitrow + global mainhead desc_heads anc_tags desc_tags allparents allchildren + + if {[info exists desc_heads($rowmenuid)] + && [lsearch -exact $desc_heads($rowmenuid) $mainhead] >= 0} { + set ok [confirm_popup "Commit [string range $rowmenuid 0 7] is already\ + included in branch $mainhead -- really re-apply it?"] + if {!$ok} return + } + nowbusy cherrypick + update + set oldhead [exec git rev-parse HEAD] + # Unfortunately git-cherry-pick writes stuff to stderr even when + # no error occurs, and exec takes that as an indication of error... + if {[catch {exec sh -c "git cherry-pick -r $rowmenuid 2>&1"} err]} { + notbusy cherrypick + error_popup $err + return + } + set newhead [exec git rev-parse HEAD] + if {$newhead eq $oldhead} { + notbusy cherrypick + error_popup "No changes committed" + return + } + set allparents($newhead) $oldhead + lappend allchildren($oldhead) $newhead + set desc_heads($newhead) $mainhead + if {[info exists anc_tags($oldhead)]} { + set anc_tags($newhead) $anc_tags($oldhead) + } + set desc_tags($newhead) {} + if {[info exists commitrow($curview,$oldhead)]} { + insertrow $commitrow($curview,$oldhead) $newhead + if {$mainhead ne {}} { + movedhead $newhead $mainhead + } + redrawtags $oldhead + redrawtags $newhead + } + notbusy cherrypick +} + +# context menu for a head +proc headmenu {x y id head} { + global headmenuid headmenuhead headctxmenu + + set headmenuid $id + set headmenuhead $head + tk_popup $headctxmenu $x $y +} + +proc cobranch {} { + global headmenuid headmenuhead mainhead headids + + # check the tree is clean first?? + set oldmainhead $mainhead + nowbusy checkout + update + if {[catch { + exec git checkout $headmenuhead + } err]} { + notbusy checkout + error_popup $err + } else { + notbusy checkout + set mainhead $headmenuhead + if {[info exists headids($oldmainhead)]} { + redrawtags $headids($oldmainhead) + } + redrawtags $headmenuid + } +} + +proc rmbranch {} { + global desc_heads headmenuid headmenuhead mainhead + global headids idheads + + set head $headmenuhead + set id $headmenuid + if {$head eq $mainhead} { + error_popup "Cannot delete the currently checked-out branch" + return + } + if {$desc_heads($id) eq $head} { + # the stuff on this branch isn't on any other branch + if {![confirm_popup "The commits on branch $head aren't on any other\ + branch.\nReally delete branch $head?"]} return + } + nowbusy rmbranch + update + if {[catch {exec git branch -D $head} err]} { + notbusy rmbranch + error_popup $err + return + } + removedhead $id $head + redrawtags $id + notbusy rmbranch +} + # Stuff for finding nearby tags proc getallcommits {} { - global allcstart allcommits allcfd + global allcstart allcommits allcfd allids + set allids {} set fd [open [concat | git rev-list --all --topo-order --parents] r] set allcfd $fd fconfigure $fd -blocking 0 @@ -5107,10 +5408,52 @@ proc combine_atags {l1 l2} { return $res } +proc forward_pass {id children} { + global idtags desc_tags idheads desc_heads alldtags tagisdesc + + set dtags {} + set dheads {} + foreach child $children { + if {[info exists idtags($child)]} { + set ctags [list $child] + } else { + set ctags $desc_tags($child) + } + if {$dtags eq {}} { + set dtags $ctags + } elseif {$ctags ne $dtags} { + set dtags [combine_dtags $dtags $ctags] + } + set cheads $desc_heads($child) + if {$dheads eq {}} { + set dheads $cheads + } elseif {$cheads ne $dheads} { + set dheads [lsort -unique [concat $dheads $cheads]] + } + } + set desc_tags($id) $dtags + if {[info exists idtags($id)]} { + set adt $dtags + foreach tag $dtags { + set adt [concat $adt $alldtags($tag)] + } + set adt [lsort -unique $adt] + set alldtags($id) $adt + foreach tag $adt { + set tagisdesc($id,$tag) -1 + set tagisdesc($tag,$id) 1 + } + } + if {[info exists idheads($id)]} { + set dheads [concat $dheads $idheads($id)] + } + set desc_heads($id) $dheads +} + proc getallclines {fd} { global allparents allchildren allcommits allcstart - global desc_tags anc_tags idtags alldtags tagisdesc allids - global desc_heads idheads + global desc_tags anc_tags idtags tagisdesc allids + global idheads travindex while {[gets $fd line] >= 0} { set id [lindex $line 0] @@ -5125,43 +5468,7 @@ proc getallclines {fd} { } # compute nearest tagged descendents as we go # also compute descendent heads - set dtags {} - set dheads {} - foreach child $allchildren($id) { - if {[info exists idtags($child)]} { - set ctags [list $child] - } else { - set ctags $desc_tags($child) - } - if {$dtags eq {}} { - set dtags $ctags - } elseif {$ctags ne $dtags} { - set dtags [combine_dtags $dtags $ctags] - } - set cheads $desc_heads($child) - if {$dheads eq {}} { - set dheads $cheads - } elseif {$cheads ne $dheads} { - set dheads [lsort -unique [concat $dheads $cheads]] - } - } - set desc_tags($id) $dtags - if {[info exists idtags($id)]} { - set adt $dtags - foreach tag $dtags { - set adt [concat $adt $alldtags($tag)] - } - set adt [lsort -unique $adt] - set alldtags($id) $adt - foreach tag $adt { - set tagisdesc($id,$tag) -1 - set tagisdesc($tag,$id) 1 - } - } - if {[info exists idheads($id)]} { - lappend dheads $id - } - set desc_heads($id) $dheads + forward_pass $id $allchildren($id) if {[clock clicks -milliseconds] - $allcstart >= 50} { fileevent $fd readable {} after idle restartgetall $fd @@ -5169,7 +5476,9 @@ proc getallclines {fd} { } } if {[eof $fd]} { - after idle restartatags [llength $allids] + set travindex [llength $allids] + set allcommits "traversing" + after idle restartatags if {[catch {close $fd} err]} { error_popup "Error reading full commit graph: $err.\n\ Results may be incomplete." @@ -5178,10 +5487,11 @@ proc getallclines {fd} { } # walk backward through the tree and compute nearest tagged ancestors -proc restartatags {i} { - global allids allparents idtags anc_tags t0 +proc restartatags {} { + global allids allparents idtags anc_tags travindex set t0 [clock clicks -milliseconds] + set i $travindex while {[incr i -1] >= 0} { set id [lindex $allids $i] set atags {} @@ -5199,17 +5509,195 @@ proc restartatags {i} { } set anc_tags($id) $atags if {[clock clicks -milliseconds] - $t0 >= 50} { - after idle restartatags $i + set travindex $i + after idle restartatags return } } set allcommits "done" + set travindex 0 notbusy allcommits dispneartags } +# update the desc_tags and anc_tags arrays for a new tag just added +proc addedtag {id} { + global desc_tags anc_tags allparents allchildren allcommits + global idtags tagisdesc alldtags + + if {![info exists desc_tags($id)]} return + set adt $desc_tags($id) + foreach t $desc_tags($id) { + set adt [concat $adt $alldtags($t)] + } + set adt [lsort -unique $adt] + set alldtags($id) $adt + foreach t $adt { + set tagisdesc($id,$t) -1 + set tagisdesc($t,$id) 1 + } + if {[info exists anc_tags($id)]} { + set todo $anc_tags($id) + while {$todo ne {}} { + set do [lindex $todo 0] + set todo [lrange $todo 1 end] + if {[info exists tagisdesc($id,$do)]} continue + set tagisdesc($do,$id) -1 + set tagisdesc($id,$do) 1 + if {[info exists anc_tags($do)]} { + set todo [concat $todo $anc_tags($do)] + } + } + } + + set lastold $desc_tags($id) + set lastnew [list $id] + set nup 0 + set nch 0 + set todo $allparents($id) + while {$todo ne {}} { + set do [lindex $todo 0] + set todo [lrange $todo 1 end] + if {![info exists desc_tags($do)]} continue + if {$desc_tags($do) ne $lastold} { + set lastold $desc_tags($do) + set lastnew [combine_dtags $lastold [list $id]] + incr nch + } + if {$lastold eq $lastnew} continue + set desc_tags($do) $lastnew + incr nup + if {![info exists idtags($do)]} { + set todo [concat $todo $allparents($do)] + } + } + + if {![info exists anc_tags($id)]} return + set lastold $anc_tags($id) + set lastnew [list $id] + set nup 0 + set nch 0 + set todo $allchildren($id) + while {$todo ne {}} { + set do [lindex $todo 0] + set todo [lrange $todo 1 end] + if {![info exists anc_tags($do)]} continue + if {$anc_tags($do) ne $lastold} { + set lastold $anc_tags($do) + set lastnew [combine_atags $lastold [list $id]] + incr nch + } + if {$lastold eq $lastnew} continue + set anc_tags($do) $lastnew + incr nup + if {![info exists idtags($do)]} { + set todo [concat $todo $allchildren($do)] + } + } +} + +# update the desc_heads array for a new head just added +proc addedhead {hid head} { + global desc_heads allparents headids idheads + + set headids($head) $hid + lappend idheads($hid) $head + + set todo [list $hid] + while {$todo ne {}} { + set do [lindex $todo 0] + set todo [lrange $todo 1 end] + if {![info exists desc_heads($do)] || + [lsearch -exact $desc_heads($do) $head] >= 0} continue + set oldheads $desc_heads($do) + lappend desc_heads($do) $head + set heads $desc_heads($do) + while {1} { + set p $allparents($do) + if {[llength $p] != 1 || ![info exists desc_heads($p)] || + $desc_heads($p) ne $oldheads} break + set do $p + set desc_heads($do) $heads + } + set todo [concat $todo $p] + } +} + +# update the desc_heads array for a head just removed +proc removedhead {hid head} { + global desc_heads allparents headids idheads + + unset headids($head) + if {$idheads($hid) eq $head} { + unset idheads($hid) + } else { + set i [lsearch -exact $idheads($hid) $head] + if {$i >= 0} { + set idheads($hid) [lreplace $idheads($hid) $i $i] + } + } + + set todo [list $hid] + while {$todo ne {}} { + set do [lindex $todo 0] + set todo [lrange $todo 1 end] + if {![info exists desc_heads($do)]} continue + set i [lsearch -exact $desc_heads($do) $head] + if {$i < 0} continue + set oldheads $desc_heads($do) + set heads [lreplace $desc_heads($do) $i $i] + while {1} { + set desc_heads($do) $heads + set p $allparents($do) + if {[llength $p] != 1 || ![info exists desc_heads($p)] || + $desc_heads($p) ne $oldheads} break + set do $p + } + set todo [concat $todo $p] + } +} + +# update things for a head moved to a child of its previous location +proc movedhead {id name} { + global headids idheads + + set oldid $headids($name) + set headids($name) $id + if {$idheads($oldid) eq $name} { + unset idheads($oldid) + } else { + set i [lsearch -exact $idheads($oldid) $name] + if {$i >= 0} { + set idheads($oldid) [lreplace $idheads($oldid) $i $i] + } + } + lappend idheads($id) $name +} + +proc changedrefs {} { + global desc_heads desc_tags anc_tags allcommits allids + global allchildren allparents idtags travindex + + if {![info exists allcommits]} return + catch {unset desc_heads} + catch {unset desc_tags} + catch {unset anc_tags} + catch {unset alldtags} + catch {unset tagisdesc} + foreach id $allids { + forward_pass $id $allchildren($id) + } + if {$allcommits ne "reading"} { + set travindex [llength $allids] + if {$allcommits ne "traversing"} { + set allcommits "traversing" + after idle restartatags + } + } +} + proc rereadrefs {} { - global idtags idheads idotherrefs + global idtags idheads idotherrefs mainhead set refids [concat [array names idtags] \ [array names idheads] [array names idotherrefs]] @@ -5218,12 +5706,16 @@ proc rereadrefs {} { set ref($id) [listrefs $id] } } + set oldmainhead $mainhead readrefs + changedrefs set refids [lsort -unique [concat $refids [array names idtags] \ [array names idheads] [array names idotherrefs]]] foreach id $refids { set v [listrefs $id] - if {![info exists ref($id)] || $ref($id) != $v} { + if {![info exists ref($id)] || $ref($id) != $v || + ($id eq $oldmainhead && $id ne $mainhead) || + ($id eq $mainhead && $id ne $oldmainhead)} { redrawtags $id } } diff --git a/gitweb/git-favicon.png b/gitweb/git-favicon.png Binary files differnew file mode 100644 index 0000000000..de637c0608 --- /dev/null +++ b/gitweb/git-favicon.png diff --git a/gitweb/gitweb.css b/gitweb/gitweb.css index 47c1ade87e..eb9fc3804b 100644 --- a/gitweb/gitweb.css +++ b/gitweb/gitweb.css @@ -42,6 +42,7 @@ div.page_nav a:visited { div.page_path { padding: 8px; + font-weight: bold; border: solid #d9d8d1; border-width: 0px 0px 1px; } @@ -115,11 +116,26 @@ div.list_head { font-style: italic; } +div.author_date { + padding: 8px; + border: solid #d9d8d1; + border-width: 0px 0px 1px 0px; + font-style: italic; +} + a.list { text-decoration: none; color: #000000; } +a.subject, a.name { + font-weight: bold; +} + +table.tags a.subject { + font-weight: normal; +} + a.list:hover { text-decoration: underline; color: #880000; @@ -221,6 +237,10 @@ table.diff_tree span.file_status.mode_chnge { color: #777777; } +table.diff_tree span.file_status.copied { + color: #70a070; +} + /* age2: 60*60*24*2 <= age */ table.project_list td.age2, table.blame td.age2 { font-style: italic; @@ -260,10 +280,22 @@ td.mode { font-family: monospace; } +div.diff a.list { + text-decoration: none; +} + +div.diff a.list:hover { + text-decoration: underline; +} + +div.diff.to_file a.list, +div.diff.to_file, div.diff.add { color: #008800; } +div.diff.from_file a.list, +div.diff.from_file, div.diff.rem { color: #cc0000; } @@ -272,6 +304,10 @@ div.diff.chunk_header { color: #990099; } +div.diff.incomplete { + color: #cccccc; +} + div.diff_info { font-family: monospace; color: #000099; @@ -321,15 +357,30 @@ a.rss_logo:hover { background-color: #ee5500; } -span.tag { +span.refs span { padding: 0px 4px; font-size: 10px; font-weight: normal; - background-color: #ffffaa; border: 1px solid; + background-color: #ffaaff; + border-color: #ffccff #ff00ee #ff00ee #ffccff; +} + +span.refs span.ref { + background-color: #aaaaff; + border-color: #ccccff #0033cc #0033cc #ccccff; +} + +span.refs span.tag { + background-color: #ffffaa; border-color: #ffffcc #ffee00 #ffee00 #ffffcc; } +span.refs span.head { + background-color: #aaffaa; + border-color: #ccffcc #00cc33 #00cc33 #ccffcc; +} + span.atnight { color: #cc0000; } diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 626fcc9201..c77270c7cd 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -15,6 +15,7 @@ use CGI::Carp qw(fatalsToBrowser); use Encode; use Fcntl ':mode'; use File::Find qw(); +use File::Basename qw(basename); binmode STDOUT, ':utf8'; our $cgi = new CGI; @@ -30,11 +31,11 @@ our $GIT = "++GIT_BINDIR++/git"; #our $projectroot = "/pub/scm"; our $projectroot = "++GITWEB_PROJECTROOT++"; -# location for temporary files needed for diffs -our $git_temp = "/tmp/gitweb"; - # target of the home link on top of all pages -our $home_link = $my_uri; +our $home_link = $my_uri || "/"; + +# string of the home link on top of all pages +our $home_link_str = "++GITWEB_HOME_LINK_STR++"; # name of your site or organization to appear in page titles # replace this with something more descriptive for clearer bookmarks @@ -47,10 +48,23 @@ our $home_text = "++GITWEB_HOMETEXT++"; our $stylesheet = "++GITWEB_CSS++"; # URI of GIT logo our $logo = "++GITWEB_LOGO++"; +# URI of GIT favicon, assumed to be image/png type +our $favicon = "++GITWEB_FAVICON++"; # source of projects list our $projects_list = "++GITWEB_LIST++"; +# show repository only if this file exists +# (only effective if this variable evaluates to true) +our $export_ok = "++GITWEB_EXPORT_OK++"; + +# only allow viewing of repositories also shown on the overview page +our $strict_export = "++GITWEB_STRICT_EXPORT++"; + +# list of git base URLs used for URL to where fetch project from, +# i.e. full URL is "$git_base_url/$project" +our @git_base_url_list = ("++GITWEB_BASE_URL++"); + # default blob_plain mimetype and default charset for text/plain blob our $default_blob_plain_mimetype = 'text/plain'; our $default_text_plain_charset = undef; @@ -59,16 +73,123 @@ our $default_text_plain_charset = undef; # (relative to the current git repository) our $mimetypes_file = undef; +# You define site-wide feature defaults here; override them with +# $GITWEB_CONFIG as necessary. +our %feature = ( + # feature => { + # 'sub' => feature-sub (subroutine), + # 'override' => allow-override (boolean), + # 'default' => [ default options...] (array reference)} + # + # if feature is overridable (it means that allow-override has true value, + # then feature-sub will be called with default options as parameters; + # return value of feature-sub indicates if to enable specified feature + # + # use gitweb_check_feature(<feature>) to check if <feature> is enabled + + 'blame' => { + 'sub' => \&feature_blame, + 'override' => 0, + 'default' => [0]}, + + 'snapshot' => { + 'sub' => \&feature_snapshot, + 'override' => 0, + # => [content-encoding, suffix, program] + 'default' => ['x-gzip', 'gz', 'gzip']}, + + 'pickaxe' => { + 'sub' => \&feature_pickaxe, + 'override' => 0, + 'default' => [1]}, +); + +sub gitweb_check_feature { + my ($name) = @_; + return undef unless exists $feature{$name}; + my ($sub, $override, @defaults) = ( + $feature{$name}{'sub'}, + $feature{$name}{'override'}, + @{$feature{$name}{'default'}}); + if (!$override) { return @defaults; } + return $sub->(@defaults); +} + +# To enable system wide have in $GITWEB_CONFIG +# $feature{'blame'}{'default'} = [1]; +# To have project specific config enable override in $GITWEB_CONFIG +# $feature{'blame'}{'override'} = 1; +# and in project config gitweb.blame = 0|1; + +sub feature_blame { + my ($val) = git_get_project_config('blame', '--bool'); + + if ($val eq 'true') { + return 1; + } elsif ($val eq 'false') { + return 0; + } + + return $_[0]; +} + +# To disable system wide have in $GITWEB_CONFIG +# $feature{'snapshot'}{'default'} = [undef]; +# To have project specific config enable override in $GITWEB_CONFIG +# $feature{'blame'}{'override'} = 1; +# and in project config gitweb.snapshot = none|gzip|bzip2 + +sub feature_snapshot { + my ($ctype, $suffix, $command) = @_; + + my ($val) = git_get_project_config('snapshot'); + + if ($val eq 'gzip') { + return ('x-gzip', 'gz', 'gzip'); + } elsif ($val eq 'bzip2') { + return ('x-bzip2', 'bz2', 'bzip2'); + } elsif ($val eq 'none') { + return (); + } + + return ($ctype, $suffix, $command); +} + +# To enable system wide have in $GITWEB_CONFIG +# $feature{'pickaxe'}{'default'} = [1]; +# To have project specific config enable override in $GITWEB_CONFIG +# $feature{'pickaxe'}{'override'} = 1; +# and in project config gitweb.pickaxe = 0|1; + +sub feature_pickaxe { + my ($val) = git_get_project_config('pickaxe', '--bool'); + + if ($val eq 'true') { + return (1); + } elsif ($val eq 'false') { + return (0); + } + + return ($_[0]); +} + +# rename detection options for git-diff and git-diff-tree +# - default is '-M', with the cost proportional to +# (number of removed files) * (number of new files). +# - more costly is '-C' (or '-C', '-M'), with the cost proportional to +# (number of changed files + number of removed files) * (number of new files) +# - even more costly is '-C', '--find-copies-harder' with cost +# (number of files in the original tree) * (number of new files) +# - one might want to include '-B' option, e.g. '-B', '-M' +our @diff_opts = ('-M'); # taken from git_commit + our $GITWEB_CONFIG = $ENV{'GITWEB_CONFIG'} || "++GITWEB_CONFIG++"; -require $GITWEB_CONFIG if -e $GITWEB_CONFIG; +do $GITWEB_CONFIG if -e $GITWEB_CONFIG; # version of the core git binary our $git_version = qx($GIT --version) =~ m/git version (.*)$/ ? $1 : "unknown"; $projects_list ||= $projectroot; -if (! -d $git_temp) { - mkdir($git_temp, 0700) || die_error(undef, "Couldn't mkdir $git_temp"); -} # ====================================================================== # input validation and dispatch @@ -77,32 +198,18 @@ if (defined $action) { if ($action =~ m/[^0-9a-zA-Z\.\-_]/) { die_error(undef, "Invalid action parameter"); } - # action which does not check rest of parameters - if ($action eq "opml") { - git_opml(); - exit; - } } -our $project = ($cgi->param('p') || $ENV{'PATH_INFO'}); +our $project = $cgi->param('p'); if (defined $project) { - $project =~ s|^/||; - $project =~ s|/$||; -} -if (defined $project && $project) { - if (!validate_input($project)) { - die_error(undef, "Invalid project parameter"); - } - if (!(-d "$projectroot/$project")) { - die_error(undef, "No such directory"); - } - if (!(-e "$projectroot/$project/HEAD")) { + if (!validate_input($project) || + !(-d "$projectroot/$project") || + !(-e "$projectroot/$project/HEAD") || + ($export_ok && !(-e "$projectroot/$project/$export_ok")) || + ($strict_export && !project_in_list($project))) { + undef $project; die_error(undef, "No such project"); } - $ENV{'GIT_DIR'} = "$projectroot/$project"; -} else { - git_project_list(); - exit; } our $file_name = $cgi->param('f'); @@ -112,6 +219,13 @@ if (defined $file_name) { } } +our $file_parent = $cgi->param('fp'); +if (defined $file_parent) { + if (!validate_input($file_parent)) { + die_error(undef, "Invalid file parent parameter"); + } +} + our $hash = $cgi->param('h'); if (defined $hash) { if (!validate_input($hash)) { @@ -133,9 +247,16 @@ if (defined $hash_base) { } } +our $hash_parent_base = $cgi->param('hpb'); +if (defined $hash_parent_base) { + if (!validate_input($hash_parent_base)) { + die_error(undef, "Invalid hash parent base parameter"); + } +} + our $page = $cgi->param('pg'); if (defined $page) { - if ($page =~ m/[^0-9]$/) { + if ($page =~ m/[^0-9]/) { die_error(undef, "Invalid page parameter"); } } @@ -148,6 +269,43 @@ if (defined $searchtext) { $searchtext = quotemeta $searchtext; } +# now read PATH_INFO and use it as alternative to parameters +sub evaluate_path_info { + return if defined $project; + my $path_info = $ENV{"PATH_INFO"}; + return if !$path_info; + $path_info =~ s,(^/|/$),,gs; + $path_info = validate_input($path_info); + return if !$path_info; + $project = $path_info; + while ($project && !-e "$projectroot/$project/HEAD") { + $project =~ s,/*[^/]*$,,; + } + if (!$project || + ($export_ok && !-e "$projectroot/$project/$export_ok") || + ($strict_export && !project_in_list($project))) { + undef $project; + return; + } + # do not change any parameters if an action is given using the query string + return if $action; + if ($path_info =~ m,^$project/([^/]+)/(.+)$,) { + # we got "project.git/branch/filename" + $action ||= "blob_plain"; + $hash_base ||= validate_input($1); + $file_name ||= validate_input($2); + } elsif ($path_info =~ m,^$project/([^/]+)$,) { + # we got "project.git/branch" + $action ||= "shortlog"; + $hash ||= validate_input($1); + } +} +evaluate_path_info(); + +# path to the current git repository +our $git_dir; +$git_dir = "$projectroot/$project" if $project; + # dispatch my %actions = ( "blame" => \&git_blame2, @@ -168,9 +326,18 @@ my %actions = ( "tag" => \&git_tag, "tags" => \&git_tags, "tree" => \&git_tree, + "snapshot" => \&git_snapshot, + # those below don't need $project + "opml" => \&git_opml, + "project_list" => \&git_project_list, + "project_index" => \&git_project_index, ); -$action = 'summary' if (!defined($action)); +if (defined $project) { + $action ||= 'summary'; +} else { + $action ||= 'project_list'; +} if (!defined($actions{$action})) { die_error(undef, "Unknown action"); } @@ -178,6 +345,40 @@ $actions{$action}->(); exit; ## ====================================================================== +## action links + +sub href(%) { + my %params = @_; + + my @mapping = ( + project => "p", + action => "a", + file_name => "f", + file_parent => "fp", + hash => "h", + hash_parent => "hp", + hash_base => "hb", + hash_parent_base => "hpb", + page => "pg", + order => "o", + searchtext => "s", + ); + my %mapping = @mapping; + + $params{'project'} = $project unless exists $params{'project'}; + + my @result = (); + for (my $i = 0; $i < @mapping; $i += 2) { + my ($name, $symbol) = ($mapping[$i], $mapping[$i+1]); + if (defined $params{$name}) { + push @result, $symbol . "=" . esc_param($params{$name}); + } + } + return "$my_uri?" . join(';', @result); +} + + +## ====================================================================== ## validation, quoting/unquoting and escaping sub validate_input { @@ -238,6 +439,12 @@ sub untabify { return $line; } +sub project_in_list { + my $project = shift; + my @list = git_get_projects_list(); + return @list && scalar(grep { $_->{'path'} eq $project } @list); +} + ## ---------------------------------------------------------------------- ## HTML aware string manipulation @@ -328,7 +535,13 @@ sub mode_str { # convert file mode in octal to file type string sub file_type { - my $mode = oct shift; + my $mode = shift; + + if ($mode !~ m/^[0-7]+$/) { + return $mode; + } else { + $mode = oct $mode; + } if (S_ISDIR($mode & S_IFMT)) { return "directory"; @@ -354,7 +567,9 @@ sub format_log_line_html { if ($line =~ m/([0-9a-fA-F]{40})/) { my $hash_text = $1; if (git_get_type($hash_text) eq "commit") { - my $link = $cgi->a({-class => "text", -href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash_text")}, $hash_text); + my $link = + $cgi->a({-href => href(action=>"commit", hash=>$hash_text), + -class => "text"}, $hash_text); $line =~ s/$hash_text/$link/; } } @@ -362,34 +577,96 @@ sub format_log_line_html { } # format marker of refs pointing to given object -sub git_get_referencing { +sub format_ref_marker { my ($refs, $id) = @_; + my $markers = ''; if (defined $refs->{$id}) { - return ' <span class="tag">' . esc_html($refs->{$id}) . '</span>'; + foreach my $ref (@{$refs->{$id}}) { + my ($type, $name) = qw(); + # e.g. tags/v2.6.11 or heads/next + if ($ref =~ m!^(.*?)s?/(.*)$!) { + $type = $1; + $name = $2; + } else { + $type = "ref"; + $name = $ref; + } + + $markers .= " <span class=\"$type\">" . esc_html($name) . "</span>"; + } + } + + if ($markers) { + return ' <span class="refs">'. $markers . '</span>'; } else { return ""; } } +# format, perhaps shortened and with markers, title line +sub format_subject_html { + my ($long, $short, $href, $extra) = @_; + $extra = '' unless defined($extra); + + if (length($short) < length($long)) { + return $cgi->a({-href => $href, -class => "list subject", + -title => $long}, + esc_html($short) . $extra); + } else { + return $cgi->a({-href => $href, -class => "list subject"}, + esc_html($long) . $extra); + } +} + +sub format_diff_line { + my $line = shift; + my $char = substr($line, 0, 1); + my $diff_class = ""; + + chomp $line; + + if ($char eq '+') { + $diff_class = " add"; + } elsif ($char eq "-") { + $diff_class = " rem"; + } elsif ($char eq "@") { + $diff_class = " chunk_header"; + } elsif ($char eq "\\") { + $diff_class = " incomplete"; + } + $line = untabify($line); + return "<div class=\"diff$diff_class\">" . esc_html($line) . "</div>\n"; +} + ## ---------------------------------------------------------------------- ## git utility subroutines, invoking git commands +# returns path to the core git executable and the --git-dir parameter as list +sub git_cmd { + return $GIT, '--git-dir='.$git_dir; +} + +# returns path to the core git executable and the --git-dir parameter as string +sub git_cmd_str { + return join(' ', git_cmd()); +} + # get HEAD ref of given project as hash -sub git_read_head { +sub git_get_head_hash { my $project = shift; - my $oENV = $ENV{'GIT_DIR'}; + my $o_git_dir = $git_dir; my $retval = undef; - $ENV{'GIT_DIR'} = "$projectroot/$project"; - if (open my $fd, "-|", $GIT, "rev-parse", "--verify", "HEAD") { + $git_dir = "$projectroot/$project"; + if (open my $fd, "-|", git_cmd(), "rev-parse", "--verify", "HEAD") { my $head = <$fd>; close $fd; if (defined $head && $head =~ /^([0-9a-fA-F]{40})$/) { $retval = $1; } } - if (defined $oENV) { - $ENV{'GIT_DIR'} = $oENV; + if (defined $o_git_dir) { + $git_dir = $o_git_dir; } return $retval; } @@ -398,7 +675,7 @@ sub git_read_head { sub git_get_type { my $hash = shift; - open my $fd, "-|", $GIT, "cat-file", '-t', $hash or return; + open my $fd, "-|", git_cmd(), "cat-file", '-t', $hash or return; my $type = <$fd>; close $fd or return; chomp $type; @@ -406,24 +683,21 @@ sub git_get_type { } sub git_get_project_config { - my $key = shift; + my ($key, $type) = @_; return unless ($key); $key =~ s/^gitweb\.//; return if ($key =~ m/\W/); - my $val = qx($GIT repo-config --get gitweb.$key); + my @x = (git_cmd(), 'repo-config'); + if (defined $type) { push @x, $type; } + push @x, "--get"; + push @x, "gitweb.$key"; + my $val = qx(@x); + chomp $val; return ($val); } -sub git_get_project_config_bool { - my $val = git_get_project_config (@_); - if ($val and $val =~ m/true|yes|on/) { - return (1); - } - return; # implicit false -} - # get hash of given path at given ref sub git_get_hash_by_path { my $base = shift; @@ -431,7 +705,7 @@ sub git_get_hash_by_path { my $tree = $base; - open my $fd, "-|", $GIT, "ls-tree", $base, "--", $path + open my $fd, "-|", git_cmd(), "ls-tree", $base, "--", $path or die_error(undef, "Open git-ls-tree failed"); my $line = <$fd>; close $fd or return undef; @@ -444,45 +718,53 @@ sub git_get_hash_by_path { ## ...................................................................... ## git utility functions, directly accessing git repository -# assumes that PATH is not symref -sub git_read_hash { +sub git_get_project_description { my $path = shift; - open my $fd, "$projectroot/$path" or return undef; - my $head = <$fd>; + open my $fd, "$projectroot/$path/description" or return undef; + my $descr = <$fd>; close $fd; - chomp $head; - if ($head =~ m/^[0-9a-fA-F]{40}$/) { - return $head; - } + chomp $descr; + return $descr; } -sub git_read_description { +sub git_get_project_url_list { my $path = shift; - open my $fd, "$projectroot/$path/description" or return undef; - my $descr = <$fd>; + open my $fd, "$projectroot/$path/cloneurl" or return undef; + my @git_project_url_list = map { chomp; $_ } <$fd>; close $fd; - chomp $descr; - return $descr; + + return wantarray ? @git_project_url_list : \@git_project_url_list; } -sub git_read_projects { +sub git_get_projects_list { my @list; if (-d $projects_list) { # search in directory my $dir = $projects_list; - opendir my ($dh), $dir or return undef; - while (my $dir = readdir($dh)) { - if (-e "$projectroot/$dir/HEAD") { - my $pr = { - path => $dir, - }; - push @list, $pr - } - } - closedir($dh); + my $pfxlen = length("$dir"); + + File::Find::find({ + follow_fast => 1, # follow symbolic links + dangling_symlinks => 0, # ignore dangling symlinks, silently + wanted => sub { + # skip project-list toplevel, if we get it. + return if (m!^[/.]$!); + # only directories can be git repositories + return unless (-d $_); + + my $subdir = substr($File::Find::name, $pfxlen + 1); + # we check related file in $projectroot + if (-e "$projectroot/$subdir/HEAD" && (!$export_ok || + -e "$projectroot/$subdir/$export_ok")) { + push @list, { path => $subdir }; + $File::Find::prune = 1; + } + }, + }, "$dir"); + } elsif (-f $projects_list) { # read from file(url-encoded): # 'git%2Fgit.git Linus+Torvalds' @@ -497,7 +779,8 @@ sub git_read_projects { if (!defined $path) { next; } - if (-e "$projectroot/$path/HEAD") { + if (-e "$projectroot/$path/HEAD" && (!$export_ok || + -e "$projectroot/$path/$export_ok")) { my $pr = { path => $path, owner => decode("utf8", $owner, Encode::FB_DEFAULT), @@ -511,21 +794,58 @@ sub git_read_projects { return @list; } -sub read_info_ref { +sub git_get_project_owner { + my $project = shift; + my $owner; + + return undef unless $project; + + # read from file (url-encoded): + # 'git%2Fgit.git Linus+Torvalds' + # 'libs%2Fklibc%2Fklibc.git H.+Peter+Anvin' + # 'linux%2Fhotplug%2Fudev.git Greg+Kroah-Hartman' + if (-f $projects_list) { + open (my $fd , $projects_list); + while (my $line = <$fd>) { + chomp $line; + my ($pr, $ow) = split ' ', $line; + $pr = unescape($pr); + $ow = unescape($ow); + if ($pr eq $project) { + $owner = 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^{} - open my $fd, "$projectroot/$project/info/refs" or return; + if (-f "$projectroot/$project/info/refs") { + open $fd, "$projectroot/$project/info/refs" + or return; + } else { + open $fd, "-|", git_cmd(), "ls-remote", "." + or return; + } + while (my $line = <$fd>) { chomp $line; - # attention: for $type == "" it saves only last path part of ref name - # e.g. from 'refs/heads/jn/gitweb' it would leave only 'gitweb' - if ($line =~ m/^([0-9a-fA-F]{40})\t.*$type\/([^\^]+)/) { + if ($line =~ m/^([0-9a-fA-F]{40})\trefs\/($type\/?[^\^]+)/) { if (defined $refs{$1}) { - $refs{$1} .= " / $2"; + push @{$refs{$1}}, $2; } else { - $refs{$1} = $2; + $refs{$1} = [ $2 ]; } } } @@ -533,10 +853,26 @@ sub read_info_ref { return \%refs; } +sub git_get_rev_name_tags { + my $hash = shift || return undef; + + open my $fd, "-|", git_cmd(), "name-rev", "--tags", $hash + or return; + my $name_rev = <$fd>; + close $fd; + + if ($name_rev =~ m|^$hash tags/(.*)$|) { + return $1; + } else { + # catches also '$hash undefined' output + return undef; + } +} + ## ---------------------------------------------------------------------- ## parse to hash functions -sub date_str { +sub parse_date { my $epoch = shift; my $tz = shift || "-0000"; @@ -549,8 +885,10 @@ sub date_str { $date{'mday'} = $mday; $date{'day'} = $days[$wday]; $date{'month'} = $months[$mon]; - $date{'rfc2822'} = sprintf "%s, %d %s %4d %02d:%02d:%02d +0000", $days[$wday], $mday, $months[$mon], 1900+$year, $hour ,$min, $sec; - $date{'mday-time'} = sprintf "%d %s %02d:%02d", $mday, $months[$mon], $hour ,$min; + $date{'rfc2822'} = sprintf "%s, %d %s %4d %02d:%02d:%02d +0000", + $days[$wday], $mday, $months[$mon], 1900+$year, $hour ,$min, $sec; + $date{'mday-time'} = sprintf "%d %s %02d:%02d", + $mday, $months[$mon], $hour ,$min; $tz =~ m/^([+\-][0-9][0-9])([0-9][0-9])$/; my $local = $epoch + ((int $1 + ($2/60)) * 3600); @@ -561,12 +899,12 @@ sub date_str { return %date; } -sub git_read_tag { +sub parse_tag { my $tag_id = shift; my %tag; my @comment; - open my $fd, "-|", $GIT, "cat-file", "tag", $tag_id or return; + open my $fd, "-|", git_cmd(), "cat-file", "tag", $tag_id or return; $tag{'id'} = $tag_id; while (my $line = <$fd>) { chomp $line; @@ -596,7 +934,7 @@ sub git_read_tag { return %tag } -sub git_read_commit { +sub parse_commit { my $commit_id = shift; my $commit_text = shift; @@ -607,7 +945,8 @@ 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_cmd(), "rev-list", "--header", "--parents", "--max-count=1", $commit_id + or return; @commit_lines = split '\n', <$fd>; close $fd or return; $/ = "\n"; @@ -690,63 +1029,132 @@ sub git_read_commit { return %co; } +# parse ref from ref_file, given by ref_id, with given type +sub parse_ref { + my $ref_file = shift; + my $ref_id = shift; + my $type = shift || git_get_type($ref_id); + my %ref_item; + + $ref_item{'type'} = $type; + $ref_item{'id'} = $ref_id; + $ref_item{'epoch'} = 0; + $ref_item{'age'} = "unknown"; + if ($type eq "tag") { + my %tag = parse_tag($ref_id); + $ref_item{'comment'} = $tag{'comment'}; + if ($tag{'type'} eq "commit") { + my %co = parse_commit($tag{'object'}); + $ref_item{'epoch'} = $co{'committer_epoch'}; + $ref_item{'age'} = $co{'age_string'}; + } elsif (defined($tag{'epoch'})) { + my $age = time - $tag{'epoch'}; + $ref_item{'epoch'} = $tag{'epoch'}; + $ref_item{'age'} = age_string($age); + } + $ref_item{'reftype'} = $tag{'type'}; + $ref_item{'name'} = $tag{'name'}; + $ref_item{'refid'} = $tag{'object'}; + } elsif ($type eq "commit"){ + my %co = parse_commit($ref_id); + $ref_item{'reftype'} = "commit"; + $ref_item{'name'} = $ref_file; + $ref_item{'title'} = $co{'title'}; + $ref_item{'refid'} = $ref_id; + $ref_item{'epoch'} = $co{'committer_epoch'}; + $ref_item{'age'} = $co{'age_string'}; + } else { + $ref_item{'reftype'} = $type; + $ref_item{'name'} = $ref_file; + $ref_item{'refid'} = $ref_id; + } + + return %ref_item; +} + +# parse line of git-diff-tree "raw" output +sub parse_difftree_raw_line { + my $line = shift; + my %res; + + # ':100644 100644 03b218260e99b78c6df0ed378e59ed9205ccc96d 3b93d5e7cc7f7dd4ebed13a5cc1a4ad976fc94d8 M ls-files.c' + # ':100644 100644 7f9281985086971d3877aca27704f2aaf9c448ce bc190ebc71bbd923f2b728e505408f5e54bd073a M rev-tree.c' + if ($line =~ m/^:([0-7]{6}) ([0-7]{6}) ([0-9a-fA-F]{40}) ([0-9a-fA-F]{40}) (.)([0-9]{0,3})\t(.*)$/) { + $res{'from_mode'} = $1; + $res{'to_mode'} = $2; + $res{'from_id'} = $3; + $res{'to_id'} = $4; + $res{'status'} = $5; + $res{'similarity'} = $6; + if ($res{'status'} eq 'R' || $res{'status'} eq 'C') { # renamed or copied + ($res{'from_file'}, $res{'to_file'}) = map { unquote($_) } split("\t", $7); + } else { + $res{'file'} = unquote($7); + } + } + # 'c512b523472485aef4fff9e57b229d9d243c967f' + elsif ($line =~ m/^([0-9a-fA-F]{40})$/) { + $res{'commit'} = $1; + } + + return wantarray ? %res : \%res; +} + +# parse line of git-ls-tree output +sub parse_ls_tree_line ($;%) { + my $line = shift; + my %opts = @_; + my %res; + + #'100644 blob 0fa3f3a66fb6a137f6ec2c19351ed4d807070ffa panic.c' + $line =~ m/^([0-9]+) (.+) ([0-9a-fA-F]{40})\t(.+)$/; + + $res{'mode'} = $1; + $res{'type'} = $2; + $res{'hash'} = $3; + if ($opts{'-z'}) { + $res{'name'} = $4; + } else { + $res{'name'} = unquote($4); + } + + return wantarray ? %res : \%res; +} + ## ...................................................................... ## parse to array of hashes functions -sub git_read_refs { +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_read_hash("$project/$ref_dir/$ref_file"); - my $type = git_get_type($ref_id) || next; - my %ref_item; - my %co; - $ref_item{'type'} = $type; - $ref_item{'id'} = $ref_id; - $ref_item{'epoch'} = 0; - $ref_item{'age'} = "unknown"; - if ($type eq "tag") { - my %tag = git_read_tag($ref_id); - $ref_item{'comment'} = $tag{'comment'}; - if ($tag{'type'} eq "commit") { - %co = git_read_commit($tag{'object'}); - $ref_item{'epoch'} = $co{'committer_epoch'}; - $ref_item{'age'} = $co{'age_string'}; - } elsif (defined($tag{'epoch'})) { - my $age = time - $tag{'epoch'}; - $ref_item{'epoch'} = $tag{'epoch'}; - $ref_item{'age'} = age_string($age); - } - $ref_item{'reftype'} = $tag{'type'}; - $ref_item{'name'} = $tag{'name'}; - $ref_item{'refid'} = $tag{'object'}; - } elsif ($type eq "commit"){ - %co = git_read_commit($ref_id); - $ref_item{'reftype'} = "commit"; - $ref_item{'name'} = $ref_file; - $ref_item{'title'} = $co{'title'}; - $ref_item{'refid'} = $ref_id; - $ref_item{'epoch'} = $co{'committer_epoch'}; - $ref_item{'age'} = $co{'age_string'}; - } else { - $ref_item{'reftype'} = $type; - $ref_item{'name'} = $ref_file; - $ref_item{'refid'} = $ref_id; + open my $fd, "-|", $GIT, "peek-remote", "$projectroot/$project/" + or return; + while (my $line = <$fd>) { + chomp $line; + if ($line =~ m/^([0-9a-fA-F]{40})\t$ref_dir\/?([^\^]+)$/) { + push @refs, { hash => $1, name => $2 }; + } elsif ($line =~ m/^[0-9a-fA-F]{40}\t$ref_dir\/?(.*)\^\{\}$/ && + $1 eq $refs[-1]{'name'}) { + # most likely a tag is followed by its peeled + # (deref) one, and when that happens we know the + # previous one was of type 'tag'. + $refs[-1]{'type'} = "tag"; } + } + close $fd; + + foreach my $ref (@refs) { + my $ref_file = $ref->{'name'}; + my $ref_id = $ref->{'hash'}; + + my $type = $ref->{'type'} || git_get_type($ref_id) || next; + my %ref_item = parse_ref($ref_file, $ref_id, $type); push @reflist, \%ref_item; } - # sort tags by age + # sort refs by age @reflist = sort {$b->{'epoch'} <=> $a->{'epoch'}} @reflist; return \@reflist; } @@ -778,6 +1186,7 @@ sub mimetype_guess_file { my %mimemap; open(MIME, $mimemap) or return undef; while (<MIME>) { + next if m/^#/; # skip comments my ($mime, $exts) = split(/\t+/); if (defined $exts) { my @exts = split(/\s+/, $exts); @@ -799,14 +1208,17 @@ sub mimetype_guess { if ($mimetypes_file) { my $file = $mimetypes_file; - #$file =~ m#^/# or $file = "$projectroot/$path/$file"; + if ($file !~ m!^/!) { # if it is relative path + # it is relative to project + $file = "$projectroot/$project/$file"; + } $mime = mimetype_guess_file($filename, $file); } $mime ||= mimetype_guess_file($filename, '/etc/mime.types'); return $mime; } -sub git_blob_plain_mimetype { +sub blob_mimetype { my $fd = shift; my $filename = shift; @@ -859,20 +1271,24 @@ sub git_header_html { # 'application/xhtml+xml', otherwise send it as plain old 'text/html'. # we have to do this because MSIE sometimes globs '*/*', pretending to # support xhtml+xml but choking when it gets what it asked for. - if (defined $cgi->http('HTTP_ACCEPT') && $cgi->http('HTTP_ACCEPT') =~ m/(,|;|\s|^)application\/xhtml\+xml(,|;|\s|$)/ && $cgi->Accept('application/xhtml+xml') != 0) { + if (defined $cgi->http('HTTP_ACCEPT') && + $cgi->http('HTTP_ACCEPT') =~ m/(,|;|\s|^)application\/xhtml\+xml(,|;|\s|$)/ && + $cgi->Accept('application/xhtml+xml') != 0) { $content_type = 'application/xhtml+xml'; } else { $content_type = 'text/html'; } - print $cgi->header(-type=>$content_type, -charset => 'utf-8', -status=> $status, -expires => $expires); + print $cgi->header(-type=>$content_type, -charset => 'utf-8', + -status=> $status, -expires => $expires); print <<EOF; <?xml version="1.0" encoding="utf-8"?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US" lang="en-US"> -<!-- git web interface v$version, (C) 2005-2006, Kay Sievers <kay.sievers\@vrfy.org>, Christian Gierke --> +<!-- git web interface version $version, (C) 2005-2006, Kay Sievers <kay.sievers\@vrfy.org>, Christian Gierke --> <!-- git core binaries version $git_version --> <head> <meta http-equiv="content-type" content="$content_type; charset=utf-8"/> +<meta name="generator" content="gitweb/$version git/$git_version"/> <meta name="robots" content="index, nofollow"/> <title>$title</title> <link rel="stylesheet" type="text/css" href="$stylesheet"/> @@ -880,8 +1296,17 @@ EOF if (defined $project) { printf('<link rel="alternate" title="%s log" '. 'href="%s" type="application/rss+xml"/>'."\n", - esc_param($project), - esc_param("$my_uri?p=$project;a=rss")); + esc_param($project), href(action=>"rss")); + } else { + printf('<link rel="alternate" title="%s projects list" '. + 'href="%s" type="text/plain; charset=utf-8"/>'."\n", + $site_name, href(project=>undef, action=>"project_index")); + printf('<link rel="alternate" title="%s projects logs" '. + 'href="%s" type="text/x-opml"/>'."\n", + $site_name, href(project=>undef, action=>"opml")); + } + if (defined $favicon) { + print qq(<link rel="shortcut icon" href="$favicon" type="image/png"/>\n); } print "</head>\n" . @@ -890,9 +1315,9 @@ EOF "<a href=\"http://www.kernel.org/pub/software/scm/git/docs/\" title=\"git documentation\">" . "<img src=\"$logo\" width=\"72\" height=\"27\" alt=\"git\" style=\"float:right; border-width:0px;\"/>" . "</a>\n"; - print $cgi->a({-href => esc_param($home_link)}, "projects") . " / "; + print $cgi->a({-href => esc_param($home_link)}, $home_link_str) . " / "; if (defined $project) { - print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary")}, esc_html($project)); + print $cgi->a({-href => href(action=>"summary")}, esc_html($project)); if (defined $action) { print " / $action"; } @@ -925,13 +1350,17 @@ EOF sub git_footer_html { print "<div class=\"page_footer\">\n"; if (defined $project) { - my $descr = git_read_description($project); + my $descr = git_get_project_description($project); if (defined $descr) { print "<div class=\"page_footer_text\">" . esc_html($descr) . "</div>\n"; } - print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=rss"), -class => "rss_logo"}, "RSS") . "\n"; + print $cgi->a({-href => href(action=>"rss"), + -class => "rss_logo"}, "RSS") . "\n"; } else { - print $cgi->a({-href => "$my_uri?" . esc_param("a=opml"), -class => "rss_logo"}, "OPML") . "\n"; + print $cgi->a({-href => href(project=>undef, action=>"opml"), + -class => "rss_logo"}, "OPML") . " "; + print $cgi->a({-href => href(project=>undef, action=>"project_index"), + -class => "rss_logo"}, "TXT") . "\n"; } print "</div>\n" . "</body>\n" . @@ -943,11 +1372,13 @@ sub die_error { my $error = shift || "Malformed query, file missing or permission denied"; git_header_html($status); - print "<div class=\"page_body\">\n" . - "<br/><br/>\n" . - "$status - $error\n" . - "<br/>\n" . - "</div>\n"; + print <<EOF; +<div class="page_body"> +<br /><br /> +$status - $error +<br /> +</div> +EOF git_footer_html(); exit; } @@ -955,7 +1386,7 @@ sub die_error { ## ---------------------------------------------------------------------- ## functions printing or outputting HTML: navigation -sub git_page_nav { +sub git_print_page_nav { my ($current, $suppress, $head, $treehead, $treebase, $extra) = @_; $extra = '' if !defined $extra; # pager or formats @@ -964,45 +1395,43 @@ sub git_page_nav { @navs = grep { $_ ne $suppress } @navs; } - my %arg = map { $_, ''} @navs; + my %arg = map { $_ => {action=>$_} } @navs; if (defined $head) { for (qw(commit commitdiff)) { - $arg{$_} = ";h=$head"; + $arg{$_}{hash} = $head; } if ($current =~ m/^(tree | log | shortlog | commit | commitdiff | search)$/x) { for (qw(shortlog log)) { - $arg{$_} = ";h=$head"; + $arg{$_}{hash} = $head; } } } - $arg{tree} .= ";h=$treehead" if defined $treehead; - $arg{tree} .= ";hb=$treebase" if defined $treebase; + $arg{tree}{hash} = $treehead if defined $treehead; + $arg{tree}{hash_base} = $treebase if defined $treebase; print "<div class=\"page_nav\">\n" . (join " | ", - map { $_ eq $current - ? $_ - : $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$_$arg{$_}")}, "$_") - } - @navs); + map { $_ eq $current ? + $_ : $cgi->a({-href => href(%{$arg{$_}})}, "$_") + } @navs); print "<br/>\n$extra<br/>\n" . "</div>\n"; } -sub git_get_paging_nav { +sub format_paging_nav { my ($action, $hash, $head, $page, $nrevs) = @_; my $paging_nav; if ($hash ne $head || $page) { - $paging_nav .= $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$action")}, "HEAD"); + $paging_nav .= $cgi->a({-href => href(action=>$action)}, "HEAD"); } else { $paging_nav .= "HEAD"; } if ($page > 0) { $paging_nav .= " ⋅ " . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$action;h=$hash;pg=" . ($page-1)), + $cgi->a({-href => href(action=>$action, hash=>$hash, page=>$page-1), -accesskey => "p", -title => "Alt-p"}, "prev"); } else { $paging_nav .= " ⋅ prev"; @@ -1010,7 +1439,7 @@ sub git_get_paging_nav { if ($nrevs >= (100 * ($page+1)-1)) { $paging_nav .= " ⋅ " . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$action;h=$hash;pg=" . ($page+1)), + $cgi->a({-href => href(action=>$action, hash=>$hash, page=>$page+1), -accesskey => "n", -title => "Alt-n"}, "next"); } else { $paging_nav .= " ⋅ next"; @@ -1022,39 +1451,508 @@ sub git_get_paging_nav { ## ...................................................................... ## functions printing or outputting HTML: div -sub git_header_div { +sub git_print_header_div { my ($action, $title, $hash, $hash_base) = @_; - my $rest = ''; + my %args = (); - $rest .= ";h=$hash" if $hash; - $rest .= ";hb=$hash_base" if $hash_base; + $args{action} = $action; + $args{hash} = $hash if $hash; + $args{hash_base} = $hash_base if $hash_base; print "<div class=\"header\">\n" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$action$rest"), - -class => "title"}, $title ? $title : $action) . "\n" . - "</div>\n"; + $cgi->a({-href => href(%args), -class => "title"}, + $title ? $title : $action) . + "\n</div>\n"; +} + +#sub git_print_authorship (\%) { +sub git_print_authorship { + my $co = shift; + + my %ad = parse_date($co->{'author_epoch'}, $co->{'author_tz'}); + print "<div class=\"author_date\">" . + esc_html($co->{'author_name'}) . + " [$ad{'rfc2822'}"; + if ($ad{'hour_local'} < 6) { + printf(" (<span class=\"atnight\">%02d:%02d</span> %s)", + $ad{'hour_local'}, $ad{'minute_local'}, $ad{'tz_local'}); + } else { + printf(" (%02d:%02d %s)", + $ad{'hour_local'}, $ad{'minute_local'}, $ad{'tz_local'}); + } + print "]</div>\n"; } sub git_print_page_path { my $name = shift; my $type = shift; + my $hb = shift; if (!defined $name) { - print "<div class=\"page_path\"><b>/</b></div>\n"; - } elsif (defined $type && $type eq 'blob') { - print "<div class=\"page_path\"><b>" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob_plain;f=$file_name")}, esc_html($name)) . "</b><br/></div>\n"; + print "<div class=\"page_path\">/</div>\n"; } else { - print "<div class=\"page_path\"><b>" . esc_html($name) . "</b><br/></div>\n"; + my @dirname = split '/', $name; + my $basename = pop @dirname; + my $fullname = ''; + + print "<div class=\"page_path\">"; + foreach my $dir (@dirname) { + $fullname .= $dir . '/'; + print $cgi->a({-href => href(action=>"tree", file_name=>$fullname, + hash_base=>$hb), + -title => $fullname}, esc_html($dir)); + print "/"; + } + if (defined $type && $type eq 'blob') { + print $cgi->a({-href => href(action=>"blob_plain", file_name=>$file_name, + hash_base=>$hb), + -title => $name}, esc_html($basename)); + } elsif (defined $type && $type eq 'tree') { + print $cgi->a({-href => href(action=>"tree", file_name=>$file_name, + hash_base=>$hb), + -title => $name}, esc_html($basename)); + print "/"; + } else { + print esc_html($basename); + } + print "<br/></div>\n"; + } +} + +# sub git_print_log (\@;%) { +sub git_print_log ($;%) { + my $log = shift; + my %opts = @_; + + if ($opts{'-remove_title'}) { + # remove title, i.e. first line of log + shift @$log; + } + # remove leading empty lines + while (defined $log->[0] && $log->[0] eq "") { + shift @$log; + } + + # print log + my $signoff = 0; + my $empty = 0; + foreach my $line (@$log) { + if ($line =~ m/^ *(signed[ \-]off[ \-]by[ :]|acked[ \-]by[ :]|cc[ :])/i) { + $signoff = 1; + $empty = 0; + if (! $opts{'-remove_signoff'}) { + print "<span class=\"signoff\">" . esc_html($line) . "</span><br/>\n"; + next; + } else { + # remove signoff lines + next; + } + } else { + $signoff = 0; + } + + # print only one empty line + # do not print empty line after signoff + if ($line eq "") { + next if ($empty || $signoff); + $empty = 1; + } else { + $empty = 0; + } + + print format_log_line_html($line) . "<br/>\n"; + } + + if ($opts{'-final_empty_line'}) { + # end with single empty line + print "<br/>\n" unless $empty; + } +} + +sub git_print_simplified_log { + my $log = shift; + my $remove_title = shift; + + git_print_log($log, + -final_empty_line=> 1, + -remove_title => $remove_title); +} + +# print tree entry (row of git_tree), but without encompassing <tr> element +sub git_print_tree_entry { + my ($t, $basedir, $hash_base, $have_blame) = @_; + + my %base_key = (); + $base_key{hash_base} = $hash_base if defined $hash_base; + + print "<td class=\"mode\">" . mode_str($t->{'mode'}) . "</td>\n"; + if ($t->{'type'} eq "blob") { + print "<td class=\"list\">" . + $cgi->a({-href => href(action=>"blob", hash=>$t->{'hash'}, + file_name=>"$basedir$t->{'name'}", %base_key), + -class => "list"}, esc_html($t->{'name'})) . + "</td>\n" . + "<td class=\"link\">" . + $cgi->a({-href => href(action=>"blob", hash=>$t->{'hash'}, + file_name=>"$basedir$t->{'name'}", %base_key)}, + "blob"); + if ($have_blame) { + print " | " . + $cgi->a({-href => href(action=>"blame", hash=>$t->{'hash'}, + file_name=>"$basedir$t->{'name'}", %base_key)}, + "blame"); + } + if (defined $hash_base) { + print " | " . + $cgi->a({-href => href(action=>"history", hash_base=>$hash_base, + hash=>$t->{'hash'}, file_name=>"$basedir$t->{'name'}")}, + "history"); + } + print " | " . + $cgi->a({-href => href(action=>"blob_plain", + hash=>$t->{'hash'}, file_name=>"$basedir$t->{'name'}")}, + "raw") . + "</td>\n"; + + } elsif ($t->{'type'} eq "tree") { + print "<td class=\"list\">" . + $cgi->a({-href => href(action=>"tree", hash=>$t->{'hash'}, + file_name=>"$basedir$t->{'name'}", %base_key)}, + esc_html($t->{'name'})) . + "</td>\n" . + "<td class=\"link\">" . + $cgi->a({-href => href(action=>"tree", hash=>$t->{'hash'}, + file_name=>"$basedir$t->{'name'}", %base_key)}, + "tree"); + if (defined $hash_base) { + print " | " . + $cgi->a({-href => href(action=>"history", hash_base=>$hash_base, + file_name=>"$basedir$t->{'name'}")}, + "history"); + } + print "</td>\n"; } } ## ...................................................................... ## functions printing large fragments of HTML +sub git_difftree_body { + my ($difftree, $hash, $parent) = @_; + + 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; + my $patchno = 0; + foreach my $line (@{$difftree}) { + my %diff = parse_difftree_raw_line($line); + + if ($alternate) { + print "<tr class=\"dark\">\n"; + } else { + print "<tr class=\"light\">\n"; + } + $alternate ^= 1; + + my ($to_mode_oct, $to_mode_str, $to_file_type); + my ($from_mode_oct, $from_mode_str, $from_file_type); + if ($diff{'to_mode'} ne ('0' x 6)) { + $to_mode_oct = oct $diff{'to_mode'}; + if (S_ISREG($to_mode_oct)) { # only for regular file + $to_mode_str = sprintf("%04o", $to_mode_oct & 0777); # permission bits + } + $to_file_type = file_type($diff{'to_mode'}); + } + if ($diff{'from_mode'} ne ('0' x 6)) { + $from_mode_oct = oct $diff{'from_mode'}; + if (S_ISREG($to_mode_oct)) { # only for regular file + $from_mode_str = sprintf("%04o", $from_mode_oct & 0777); # permission bits + } + $from_file_type = file_type($diff{'from_mode'}); + } + + if ($diff{'status'} eq "A") { # created + my $mode_chng = "<span class=\"file_status new\">[new $to_file_type"; + $mode_chng .= " with mode: $to_mode_str" if $to_mode_str; + $mode_chng .= "]</span>"; + print "<td>" . + $cgi->a({-href => href(action=>"blob", hash=>$diff{'to_id'}, + hash_base=>$hash, file_name=>$diff{'file'}), + -class => "list"}, esc_html($diff{'file'})) . + "</td>\n" . + "<td>$mode_chng</td>\n" . + "<td class=\"link\">" . + $cgi->a({-href => href(action=>"blob", hash=>$diff{'to_id'}, + hash_base=>$hash, file_name=>$diff{'file'})}, + "blob"); + if ($action eq 'commitdiff') { + # link to patch + $patchno++; + print " | " . + $cgi->a({-href => "#patch$patchno"}, "patch"); + } + print "</td>\n"; + + } elsif ($diff{'status'} eq "D") { # deleted + my $mode_chng = "<span class=\"file_status deleted\">[deleted $from_file_type]</span>"; + print "<td>" . + $cgi->a({-href => href(action=>"blob", hash=>$diff{'from_id'}, + hash_base=>$parent, file_name=>$diff{'file'}), + -class => "list"}, esc_html($diff{'file'})) . + "</td>\n" . + "<td>$mode_chng</td>\n" . + "<td class=\"link\">" . + $cgi->a({-href => href(action=>"blob", hash=>$diff{'from_id'}, + hash_base=>$parent, file_name=>$diff{'file'})}, + "blob") . + " | "; + if ($action eq 'commitdiff') { + # link to patch + $patchno++; + print " | " . + $cgi->a({-href => "#patch$patchno"}, "patch"); + } + print $cgi->a({-href => href(action=>"history", hash_base=>$parent, + file_name=>$diff{'file'})}, + "history") . + "</td>\n"; + + } elsif ($diff{'status'} eq "M" || $diff{'status'} eq "T") { # modified, or type changed + my $mode_chnge = ""; + if ($diff{'from_mode'} != $diff{'to_mode'}) { + $mode_chnge = "<span class=\"file_status mode_chnge\">[changed"; + if ($from_file_type != $to_file_type) { + $mode_chnge .= " from $from_file_type to $to_file_type"; + } + if (($from_mode_oct & 0777) != ($to_mode_oct & 0777)) { + if ($from_mode_str && $to_mode_str) { + $mode_chnge .= " mode: $from_mode_str->$to_mode_str"; + } elsif ($to_mode_str) { + $mode_chnge .= " mode: $to_mode_str"; + } + } + $mode_chnge .= "]</span>\n"; + } + print "<td>"; + if ($diff{'to_id'} ne $diff{'from_id'}) { # modified + print $cgi->a({-href => href(action=>"blobdiff", + hash=>$diff{'to_id'}, hash_parent=>$diff{'from_id'}, + hash_base=>$hash, hash_parent_base=>$parent, + file_name=>$diff{'file'}), + -class => "list"}, esc_html($diff{'file'})); + } else { # only mode changed + print $cgi->a({-href => href(action=>"blob", hash=>$diff{'to_id'}, + hash_base=>$hash, file_name=>$diff{'file'}), + -class => "list"}, esc_html($diff{'file'})); + } + print "</td>\n" . + "<td>$mode_chnge</td>\n" . + "<td class=\"link\">" . + $cgi->a({-href => href(action=>"blob", hash=>$diff{'to_id'}, + hash_base=>$hash, file_name=>$diff{'file'})}, + "blob"); + if ($diff{'to_id'} ne $diff{'from_id'}) { # modified + if ($action eq 'commitdiff') { + # link to patch + $patchno++; + print " | " . + $cgi->a({-href => "#patch$patchno"}, "patch"); + } else { + print " | " . + $cgi->a({-href => href(action=>"blobdiff", + hash=>$diff{'to_id'}, hash_parent=>$diff{'from_id'}, + hash_base=>$hash, hash_parent_base=>$parent, + file_name=>$diff{'file'})}, + "diff"); + } + } + print " | " . + $cgi->a({-href => href(action=>"history", + hash_base=>$hash, file_name=>$diff{'file'})}, + "history"); + print "</td>\n"; + + } elsif ($diff{'status'} eq "R" || $diff{'status'} eq "C") { # renamed or copied + my %status_name = ('R' => 'moved', 'C' => 'copied'); + my $nstatus = $status_name{$diff{'status'}}; + my $mode_chng = ""; + if ($diff{'from_mode'} != $diff{'to_mode'}) { + # mode also for directories, so we cannot use $to_mode_str + $mode_chng = sprintf(", mode: %04o", $to_mode_oct & 0777); + } + print "<td>" . + $cgi->a({-href => href(action=>"blob", hash_base=>$hash, + hash=>$diff{'to_id'}, file_name=>$diff{'to_file'}), + -class => "list"}, esc_html($diff{'to_file'})) . "</td>\n" . + "<td><span class=\"file_status $nstatus\">[$nstatus from " . + $cgi->a({-href => href(action=>"blob", hash_base=>$parent, + hash=>$diff{'from_id'}, file_name=>$diff{'from_file'}), + -class => "list"}, esc_html($diff{'from_file'})) . + " with " . (int $diff{'similarity'}) . "% similarity$mode_chng]</span></td>\n" . + "<td class=\"link\">" . + $cgi->a({-href => href(action=>"blob", hash_base=>$hash, + hash=>$diff{'to_id'}, file_name=>$diff{'to_file'})}, + "blob"); + if ($diff{'to_id'} ne $diff{'from_id'}) { + if ($action eq 'commitdiff') { + # link to patch + $patchno++; + print " | " . + $cgi->a({-href => "#patch$patchno"}, "patch"); + } else { + print " | " . + $cgi->a({-href => href(action=>"blobdiff", + hash=>$diff{'to_id'}, hash_parent=>$diff{'from_id'}, + hash_base=>$hash, hash_parent_base=>$parent, + file_name=>$diff{'to_file'}, file_parent=>$diff{'from_file'})}, + "diff"); + } + } + print "</td>\n"; + + } # we should not encounter Unmerged (U) or Unknown (X) status + print "</tr>\n"; + } + print "</table>\n"; +} + +sub git_patchset_body { + my ($fd, $difftree, $hash, $hash_parent) = @_; + + my $patch_idx = 0; + my $in_header = 0; + my $patch_found = 0; + my $diffinfo; + + print "<div class=\"patchset\">\n"; + + LINE: + while (my $patch_line = <$fd>) { + chomp $patch_line; + + if ($patch_line =~ m/^diff /) { # "git diff" header + # beginning of patch (in patchset) + if ($patch_found) { + # close previous patch + print "</div>\n"; # class="patch" + } else { + # first patch in patchset + $patch_found = 1; + } + print "<div class=\"patch\" id=\"patch". ($patch_idx+1) ."\">\n"; + + if (ref($difftree->[$patch_idx]) eq "HASH") { + $diffinfo = $difftree->[$patch_idx]; + } else { + $diffinfo = parse_difftree_raw_line($difftree->[$patch_idx]); + } + $patch_idx++; + + # for now, no extended header, hence we skip empty patches + # companion to next LINE if $in_header; + if ($diffinfo->{'from_id'} eq $diffinfo->{'to_id'}) { # no change + $in_header = 1; + next LINE; + } + + if ($diffinfo->{'status'} eq "A") { # added + print "<div class=\"diff_info\">" . file_type($diffinfo->{'to_mode'}) . ":" . + $cgi->a({-href => href(action=>"blob", hash_base=>$hash, + hash=>$diffinfo->{'to_id'}, file_name=>$diffinfo->{'file'})}, + $diffinfo->{'to_id'}) . "(new)" . + "</div>\n"; # class="diff_info" + + } elsif ($diffinfo->{'status'} eq "D") { # deleted + print "<div class=\"diff_info\">" . file_type($diffinfo->{'from_mode'}) . ":" . + $cgi->a({-href => href(action=>"blob", hash_base=>$hash_parent, + hash=>$diffinfo->{'from_id'}, file_name=>$diffinfo->{'file'})}, + $diffinfo->{'from_id'}) . "(deleted)" . + "</div>\n"; # class="diff_info" + + } elsif ($diffinfo->{'status'} eq "R" || # renamed + $diffinfo->{'status'} eq "C" || # copied + $diffinfo->{'status'} eq "2") { # with two filenames (from git_blobdiff) + print "<div class=\"diff_info\">" . + file_type($diffinfo->{'from_mode'}) . ":" . + $cgi->a({-href => href(action=>"blob", hash_base=>$hash_parent, + hash=>$diffinfo->{'from_id'}, file_name=>$diffinfo->{'from_file'})}, + $diffinfo->{'from_id'}) . + " -> " . + file_type($diffinfo->{'to_mode'}) . ":" . + $cgi->a({-href => href(action=>"blob", hash_base=>$hash, + hash=>$diffinfo->{'to_id'}, file_name=>$diffinfo->{'to_file'})}, + $diffinfo->{'to_id'}); + print "</div>\n"; # class="diff_info" + + } else { # modified, mode changed, ... + print "<div class=\"diff_info\">" . + file_type($diffinfo->{'from_mode'}) . ":" . + $cgi->a({-href => href(action=>"blob", hash_base=>$hash_parent, + hash=>$diffinfo->{'from_id'}, file_name=>$diffinfo->{'file'})}, + $diffinfo->{'from_id'}) . + " -> " . + file_type($diffinfo->{'to_mode'}) . ":" . + $cgi->a({-href => href(action=>"blob", hash_base=>$hash, + hash=>$diffinfo->{'to_id'}, file_name=>$diffinfo->{'file'})}, + $diffinfo->{'to_id'}); + print "</div>\n"; # class="diff_info" + } + + #print "<div class=\"diff extended_header\">\n"; + $in_header = 1; + next LINE; + } # start of patch in patchset + + + if ($in_header && $patch_line =~ m/^---/) { + #print "</div>\n"; # class="diff extended_header" + $in_header = 0; + + my $file = $diffinfo->{'from_file'}; + $file ||= $diffinfo->{'file'}; + $file = $cgi->a({-href => href(action=>"blob", hash_base=>$hash_parent, + hash=>$diffinfo->{'from_id'}, file_name=>$file), + -class => "list"}, esc_html($file)); + $patch_line =~ s|a/.*$|a/$file|g; + print "<div class=\"diff from_file\">$patch_line</div>\n"; + + $patch_line = <$fd>; + chomp $patch_line; + + #$patch_line =~ m/^+++/; + $file = $diffinfo->{'to_file'}; + $file ||= $diffinfo->{'file'}; + $file = $cgi->a({-href => href(action=>"blob", hash_base=>$hash, + hash=>$diffinfo->{'to_id'}, file_name=>$file), + -class => "list"}, esc_html($file)); + $patch_line =~ s|b/.*|b/$file|g; + print "<div class=\"diff to_file\">$patch_line</div>\n"; + + next LINE; + } + next LINE if $in_header; + + print format_diff_line($patch_line); + } + print "</div>\n" if $patch_found; # class="patch" + + print "</div>\n"; # class="patchset" +} + +# . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . + sub git_shortlog_body { # uses global variable $project my ($revlist, $from, $to, $refs, $extra) = @_; + + my ($ctype, $suffix, $command) = gitweb_check_feature('snapshot'); + my $have_snapshot = (defined $ctype && defined $suffix); + $from = 0 unless defined $from; $to = $#{$revlist} if (!defined $to || $#{$revlist} < $to); @@ -1062,9 +1960,9 @@ sub git_shortlog_body { my $alternate = 0; for (my $i = $from; $i <= $to; $i++) { my $commit = $revlist->[$i]; - #my $ref = defined $refs ? git_get_referencing($refs, $commit) : ''; - my $ref = git_get_referencing($refs, $commit); - my %co = git_read_commit($commit); + #my $ref = defined $refs ? format_ref_marker($refs, $commit) : ''; + my $ref = format_ref_marker($refs, $commit); + my %co = parse_commit($commit); if ($alternate) { print "<tr class=\"dark\">\n"; } else { @@ -1075,20 +1973,81 @@ sub git_shortlog_body { print "<td title=\"$co{'age_string_age'}\"><i>$co{'age_string_date'}</i></td>\n" . "<td><i>" . esc_html(chop_str($co{'author_name'}, 10)) . "</i></td>\n" . "<td>"; - if (length($co{'title_short'}) < length($co{'title'})) { - print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$commit"), - -class => "list", -title => "$co{'title'}"}, - "<b>" . esc_html($co{'title_short'}) . "$ref</b>"); + print format_subject_html($co{'title'}, $co{'title_short'}, + href(action=>"commit", hash=>$commit), $ref); + print "</td>\n" . + "<td class=\"link\">" . + $cgi->a({-href => href(action=>"commit", hash=>$commit)}, "commit") . " | " . + $cgi->a({-href => href(action=>"commitdiff", hash=>$commit)}, "commitdiff"); + if ($have_snapshot) { + print " | " . $cgi->a({-href => href(action=>"snapshot", hash=>$commit)}, "snapshot"); + } + print "</td>\n" . + "</tr>\n"; + } + if (defined $extra) { + print "<tr>\n" . + "<td colspan=\"4\">$extra</td>\n" . + "</tr>\n"; + } + print "</table>\n"; +} + +sub git_history_body { + # Warning: assumes constant type (blob or tree) during history + my ($revlist, $from, $to, $refs, $hash_base, $ftype, $extra) = @_; + + $from = 0 unless defined $from; + $to = $#{$revlist} unless (defined $to && $to <= $#{$revlist}); + + print "<table class=\"history\" cellspacing=\"0\">\n"; + my $alternate = 0; + for (my $i = $from; $i <= $to; $i++) { + if ($revlist->[$i] !~ m/^([0-9a-fA-F]{40})/) { + next; + } + + my $commit = $1; + my %co = parse_commit($commit); + if (!%co) { + next; + } + + my $ref = format_ref_marker($refs, $commit); + + if ($alternate) { + print "<tr class=\"dark\">\n"; } else { - print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$commit"), - -class => "list"}, - "<b>" . esc_html($co{'title'}) . "$ref</b>"); + print "<tr class=\"light\">\n"; } + $alternate ^= 1; + print "<td title=\"$co{'age_string_age'}\"><i>$co{'age_string_date'}</i></td>\n" . + # shortlog uses chop_str($co{'author_name'}, 10) + "<td><i>" . esc_html(chop_str($co{'author_name'}, 15, 3)) . "</i></td>\n" . + "<td>"; + # originally git_history used chop_str($co{'title'}, 50) + print format_subject_html($co{'title'}, $co{'title_short'}, + href(action=>"commit", hash=>$commit), $ref); print "</td>\n" . "<td class=\"link\">" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$commit")}, "commit") . " | " . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$commit")}, "commitdiff") . - "</td>\n" . + $cgi->a({-href => href(action=>"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=>$hash_base, hash_parent_base=>$commit, + file_name=>$file_name)}, + "diff to current"); + } + } + print "</td>\n" . "</tr>\n"; } if (defined $extra) { @@ -1124,34 +2083,29 @@ sub git_tags_body { $alternate ^= 1; print "<td><i>$tag{'age'}</i></td>\n" . "<td>" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$tag{'reftype'};h=$tag{'refid'}"), - -class => "list"}, "<b>" . esc_html($tag{'name'}) . "</b>") . + $cgi->a({-href => href(action=>$tag{'reftype'}, hash=>$tag{'refid'}), + -class => "list name"}, esc_html($tag{'name'})) . "</td>\n" . "<td>"; if (defined $comment) { - if (length($comment_short) < length($comment)) { - print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tag;h=$tag{'id'}"), - -class => "list", -title => $comment}, $comment_short); - } else { - print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tag;h=$tag{'id'}"), - -class => "list"}, $comment); - } + print format_subject_html($comment, $comment_short, + href(action=>"tag", hash=>$tag{'id'})); } print "</td>\n" . "<td class=\"selflink\">"; if ($tag{'type'} eq "tag") { - print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tag;h=$tag{'id'}")}, "tag"); + print $cgi->a({-href => href(action=>"tag", hash=>$tag{'id'})}, "tag"); } else { print " "; } print "</td>\n" . "<td class=\"link\">" . " | " . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$tag{'reftype'};h=$tag{'refid'}")}, $tag{'reftype'}); + $cgi->a({-href => href(action=>$tag{'reftype'}, hash=>$tag{'refid'})}, $tag{'reftype'}); if ($tag{'reftype'} eq "commit") { - print " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog;h=$tag{'name'}")}, "shortlog") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log;h=$tag{'refid'}")}, "log"); + print " | " . $cgi->a({-href => href(action=>"shortlog", hash=>$tag{'name'})}, "shortlog") . + " | " . $cgi->a({-href => href(action=>"log", hash=>$tag{'refid'})}, "log"); } elsif ($tag{'reftype'} eq "blob") { - print " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob_plain;h=$tag{'refid'}")}, "raw"); + print " | " . $cgi->a({-href => href(action=>"blob_plain", hash=>$tag{'refid'})}, "raw"); } print "</td>\n" . "</tr>"; @@ -1184,12 +2138,12 @@ sub git_heads_body { $alternate ^= 1; print "<td><i>$tag{'age'}</i></td>\n" . ($tag{'id'} eq $head ? "<td class=\"current_head\">" : "<td>") . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog;h=$tag{'name'}"), - -class => "list"}, "<b>" . esc_html($tag{'name'}) . "</b>") . + $cgi->a({-href => href(action=>"shortlog", hash=>$tag{'name'}), + -class => "list name"},esc_html($tag{'name'})) . "</td>\n" . "<td class=\"link\">" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog;h=$tag{'name'}")}, "shortlog") . " | " . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log;h=$tag{'name'}")}, "log") . + $cgi->a({-href => href(action=>"shortlog", hash=>$tag{'name'})}, "shortlog") . " | " . + $cgi->a({-href => href(action=>"log", hash=>$tag{'name'})}, "log") . "</td>\n" . "</tr>"; } @@ -1201,77 +2155,6 @@ sub git_heads_body { print "</table>\n"; } -## ---------------------------------------------------------------------- -## functions printing large fragments, format as one of arguments - -sub git_diff_print { - my $from = shift; - my $from_name = shift; - my $to = shift; - my $to_name = shift; - my $format = shift || "html"; - - my $from_tmp = "/dev/null"; - my $to_tmp = "/dev/null"; - my $pid = $$; - - # create tmp from-file - if (defined $from) { - $from_tmp = "$git_temp/gitweb_" . $$ . "_from"; - open my $fd2, "> $from_tmp"; - open my $fd, "-|", $GIT, "cat-file", "blob", $from; - my @file = <$fd>; - print $fd2 @file; - close $fd2; - close $fd; - } - - # create tmp to-file - if (defined $to) { - $to_tmp = "$git_temp/gitweb_" . $$ . "_to"; - open my $fd2, "> $to_tmp"; - open my $fd, "-|", $GIT, "cat-file", "blob", $to; - my @file = <$fd>; - print $fd2 @file; - close $fd2; - close $fd; - } - - open my $fd, "-|", "/usr/bin/diff -u -p -L \'$from_name\' -L \'$to_name\' $from_tmp $to_tmp"; - if ($format eq "plain") { - undef $/; - print <$fd>; - $/ = "\n"; - } else { - while (my $line = <$fd>) { - chomp $line; - my $char = substr($line, 0, 1); - my $diff_class = ""; - if ($char eq '+') { - $diff_class = " add"; - } elsif ($char eq "-") { - $diff_class = " rem"; - } elsif ($char eq "@") { - $diff_class = " chunk_header"; - } elsif ($char eq "\\") { - # skip errors - next; - } - $line = untabify($line); - print "<div class=\"diff$diff_class\">" . esc_html($line) . "</div>\n"; - } - } - close $fd; - - if (defined $from) { - unlink($from_tmp); - } - if (defined $to) { - unlink($to_tmp); - } -} - - ## ====================================================================== ## ====================================================================== ## actions @@ -1282,24 +2165,24 @@ sub git_project_list { die_error(undef, "Unknown order parameter"); } - my @list = git_read_projects(); + my @list = git_get_projects_list(); my @projects; if (!@list) { 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); + $git_dir = "$projectroot/$pr->{'path'}"; + 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'}) { @@ -1324,7 +2207,7 @@ sub git_project_list { print "<th>Project</th>\n"; } else { print "<th>" . - $cgi->a({-href => "$my_uri?" . esc_param("o=project"), + $cgi->a({-href => href(project=>undef, order=>'project'), -class => "header"}, "Project") . "</th>\n"; } @@ -1333,7 +2216,7 @@ sub git_project_list { print "<th>Description</th>\n"; } else { print "<th>" . - $cgi->a({-href => "$my_uri?" . esc_param("o=descr"), + $cgi->a({-href => href(project=>undef, order=>'descr'), -class => "header"}, "Description") . "</th>\n"; } @@ -1342,7 +2225,7 @@ sub git_project_list { print "<th>Owner</th>\n"; } else { print "<th>" . - $cgi->a({-href => "$my_uri?" . esc_param("o=owner"), + $cgi->a({-href => href(project=>undef, order=>'owner'), -class => "header"}, "Owner") . "</th>\n"; } @@ -1351,7 +2234,7 @@ sub git_project_list { print "<th>Last Change</th>\n"; } else { print "<th>" . - $cgi->a({-href => "$my_uri?" . esc_param("o=age"), + $cgi->a({-href => href(project=>undef, order=>'age'), -class => "header"}, "Last Change") . "</th>\n"; } @@ -1365,16 +2248,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"), + print "<td>" . $cgi->a({-href => href(project=>$pr->{'path'}, action=>"summary"), -class => "list"}, esc_html($pr->{'path'})) . "</td>\n" . "<td>" . esc_html($pr->{'descr'}) . "</td>\n" . "<td><i>" . chop_str($pr->{'owner'}, 15) . "</i></td>\n"; print "<td class=\"". age_class($pr->{'commit'}{'age'}) . "\">" . $pr->{'commit'}{'age_string'} . "</td>\n" . "<td class=\"link\">" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$pr->{'path'};a=summary")}, "summary") . " | " . - $cgi->a({-href => "$my_uri?" . esc_param("p=$pr->{'path'};a=shortlog")}, "shortlog") . " | " . - $cgi->a({-href => "$my_uri?" . esc_param("p=$pr->{'path'};a=log")}, "log") . + $cgi->a({-href => href(project=>$pr->{'path'}, action=>"summary")}, "summary") . " | " . + $cgi->a({-href => href(project=>$pr->{'path'}, action=>"shortlog")}, "shortlog") . " | " . + $cgi->a({-href => href(project=>$pr->{'path'}, action=>"log")}, "log") . "</td>\n" . "</tr>\n"; } @@ -1382,84 +2265,106 @@ sub git_project_list { git_footer_html(); } -sub git_summary { - my $descr = git_read_description($project) || "none"; - my $head = git_read_head($project); - my %co = git_read_commit($head); - my %cd = date_str($co{'committer_epoch'}, $co{'committer_tz'}); +sub git_project_index { + my @projects = git_get_projects_list(); - my $owner; - if (-f $projects_list) { - open (my $fd , $projects_list); - while (my $line = <$fd>) { - chomp $line; - my ($pr, $ow) = split ' ', $line; - $pr = unescape($pr); - $ow = unescape($ow); - if ($pr eq $project) { - $owner = decode("utf8", $ow, Encode::FB_DEFAULT); - last; - } + print $cgi->header( + -type => 'text/plain', + -charset => 'utf-8', + -content_disposition => qq(inline; filename="index.aux")); + + foreach my $pr (@projects) { + if (!exists $pr->{'owner'}) { + $pr->{'owner'} = get_file_owner("$projectroot/$project"); } - close $fd; - } - if (!defined $owner) { - $owner = get_file_owner("$projectroot/$project"); + + my ($path, $owner) = ($pr->{'path'}, $pr->{'owner'}); + # quote as in CGI::Util::encode, but keep the slash, and use '+' for ' ' + $path =~ s/([^a-zA-Z0-9_.\-\/ ])/sprintf("%%%02X", ord($1))/eg; + $owner =~ s/([^a-zA-Z0-9_.\-\/ ])/sprintf("%%%02X", ord($1))/eg; + $path =~ s/ /\+/g; + $owner =~ s/ /\+/g; + + print "$path $owner\n"; } +} + +sub git_summary { + my $descr = git_get_project_description($project) || "none"; + my $head = git_get_head_hash($project); + my %co = parse_commit($head); + my %cd = parse_date($co{'committer_epoch'}, $co{'committer_tz'}); - my $refs = read_info_ref(); + my $owner = git_get_project_owner($project); + + my $refs = git_get_references(); git_header_html(); - git_page_nav('summary','', $head); + 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"; + "<tr><td>last change</td><td>$cd{'rfc2822'}</td></tr>\n"; + # use per project git URL list in $projectroot/$project/cloneurl + # or make project git URL from git base URL and project name + my $url_tag = "URL"; + my @url_list = git_get_project_url_list($project); + @url_list = map { "$_/$project" } @git_base_url_list unless @url_list; + foreach my $git_url (@url_list) { + next unless $git_url; + print "<tr><td>$url_tag</td><td>$git_url</td></tr>\n"; + $url_tag = ""; + } + print "</table>\n"; - open my $fd, "-|", $GIT, "rev-list", "--max-count=17", git_read_head($project) + open my $fd, "-|", git_cmd(), "rev-list", "--max-count=17", + git_get_head_hash($project) or die_error(undef, "Open git-rev-list failed"); my @revlist = map { chomp; $_ } <$fd>; close $fd; - git_header_div('shortlog'); + git_print_header_div('shortlog'); git_shortlog_body(\@revlist, 0, 15, $refs, - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog")}, "...")); + $cgi->a({-href => href(action=>"shortlog")}, "...")); - my $taglist = git_read_refs("refs/tags"); + my $taglist = git_get_refs_list("refs/tags"); if (defined @$taglist) { - git_header_div('tags'); + git_print_header_div('tags'); git_tags_body($taglist, 0, 15, - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tags")}, "...")); + $cgi->a({-href => href(action=>"tags")}, "...")); } - my $headlist = git_read_refs("refs/heads"); + my $headlist = git_get_refs_list("refs/heads"); if (defined @$headlist) { - git_header_div('heads'); + git_print_header_div('heads'); git_heads_body($headlist, $head, 0, 15, - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=heads")}, "...")); + $cgi->a({-href => href(action=>"heads")}, "...")); } git_footer_html(); } sub git_tag { - my $head = git_read_head($project); + my $head = git_get_head_hash($project); git_header_html(); - git_page_nav('','', $head,undef,$head); - my %tag = git_read_tag($hash); - git_header_div('commit', esc_html($tag{'name'}), $hash); + git_print_page_nav('','', $head,undef,$head); + my %tag = parse_tag($hash); + git_print_header_div('commit', esc_html($tag{'name'}), $hash); print "<div class=\"title_text\">\n" . "<table cellspacing=\"0\">\n" . "<tr>\n" . "<td>object</td>\n" . - "<td>" . $cgi->a({-class => "list", -href => "$my_uri?" . esc_param("p=$project;a=$tag{'type'};h=$tag{'object'}")}, $tag{'object'}) . "</td>\n" . - "<td class=\"link\">" . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$tag{'type'};h=$tag{'object'}")}, $tag{'type'}) . "</td>\n" . + "<td>" . $cgi->a({-class => "list", -href => href(action=>$tag{'type'}, hash=>$tag{'object'})}, + $tag{'object'}) . "</td>\n" . + "<td class=\"link\">" . $cgi->a({-href => href(action=>$tag{'type'}, hash=>$tag{'object'})}, + $tag{'type'}) . "</td>\n" . "</tr>\n"; if (defined($tag{'author'})) { - my %ad = date_str($tag{'epoch'}, $tag{'tz'}); + my %ad = parse_date($tag{'epoch'}, $tag{'tz'}); print "<tr><td>author</td><td>" . esc_html($tag{'author'}) . "</td></tr>\n"; - print "<tr><td></td><td>" . $ad{'rfc2822'} . sprintf(" (%02d:%02d %s)", $ad{'hour_local'}, $ad{'minute_local'}, $ad{'tz_local'}) . "</td></tr>\n"; + print "<tr><td></td><td>" . $ad{'rfc2822'} . + sprintf(" (%02d:%02d %s)", $ad{'hour_local'}, $ad{'minute_local'}, $ad{'tz_local'}) . + "</td></tr>\n"; } print "</table>\n\n" . "</div>\n"; @@ -1475,11 +2380,15 @@ sub git_tag { sub git_blame2 { my $fd; my $ftype; - die_error(undef, "Permission denied") if (!git_get_project_config_bool ('blame')); + + my ($have_blame) = gitweb_check_feature('blame'); + if (!$have_blame) { + die_error('403 Permission denied', "Permission denied"); + } die_error('404 Not Found', "File name not defined") if (!$file_name); - $hash_base ||= git_read_head($project); + $hash_base ||= git_get_head_hash($project); die_error(undef, "Couldn't find base commit") unless ($hash_base); - my %co = git_read_commit($hash_base) + my %co = parse_commit($hash_base) or die_error(undef, "Reading commit failed"); if (!defined $hash) { $hash = git_get_hash_by_path($hash_base, $file_name, "blob") @@ -1489,22 +2398,27 @@ sub git_blame2 { if ($ftype !~ "blob") { die_error("400 Bad Request", "Object is not a blob"); } - open ($fd, "-|", $GIT, "blame", '-l', $file_name, $hash_base) + open ($fd, "-|", git_cmd(), "blame", '-l', $file_name, $hash_base) or die_error(undef, "Open git-blame failed"); git_header_html(); my $formats_nav = - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$hash;hb=$hash_base;f=$file_name")}, "blob") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blame;f=$file_name")}, "head"); - git_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav); - git_header_div('commit', esc_html($co{'title'}), $hash_base); - git_print_page_path($file_name, $ftype); + $cgi->a({-href => href(action=>"blob", hash=>$hash, hash_base=>$hash_base, file_name=>$file_name)}, + "blob") . + " | " . + $cgi->a({-href => href(action=>"blame", file_name=>$file_name)}, + "head"); + git_print_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav); + git_print_header_div('commit', esc_html($co{'title'}), $hash_base); + git_print_page_path($file_name, $ftype, $hash_base); my @rev_color = (qw(light2 dark2)); my $num_colors = scalar(@rev_color); my $current_color = 0; my $last_rev; - print "<div class=\"page_body\">\n"; - print "<table class=\"blame\">\n"; - print "<tr><th>Commit</th><th>Line</th><th>Data</th></tr>\n"; + print <<HTML; +<div class="page_body"> +<table class="blame"> +<tr><th>Commit</th><th>Line</th><th>Data</th></tr> +HTML while (<$fd>) { /^([0-9a-fA-F]{40}).*?(\d+)\)\s{1}(\s*.*)/; my $full_rev = $1; @@ -1520,38 +2434,48 @@ sub git_blame2 { } print "<tr class=\"$rev_color[$current_color]\">\n"; print "<td class=\"sha1\">" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$full_rev;f=$file_name")}, esc_html($rev)) . "</td>\n"; - print "<td class=\"linenr\"><a id=\"l$lineno\" href=\"#l$lineno\" class=\"linenr\">" . esc_html($lineno) . "</a></td>\n"; + $cgi->a({-href => href(action=>"commit", hash=>$full_rev, file_name=>$file_name)}, + esc_html($rev)) . "</td>\n"; + print "<td class=\"linenr\"><a id=\"l$lineno\" href=\"#l$lineno\" class=\"linenr\">" . + esc_html($lineno) . "</a></td>\n"; print "<td class=\"pre\">" . esc_html($data) . "</td>\n"; print "</tr>\n"; } print "</table>\n"; print "</div>"; - close $fd or print "Reading blob failed\n"; + close $fd + or print "Reading blob failed\n"; git_footer_html(); } sub git_blame { my $fd; - die_error('403 Permission denied', "Permission denied") if (!git_get_project_config_bool ('blame')); + + my ($have_blame) = gitweb_check_feature('blame'); + if (!$have_blame) { + die_error('403 Permission denied', "Permission denied"); + } die_error('404 Not Found', "File name not defined") if (!$file_name); - $hash_base ||= git_read_head($project); + $hash_base ||= git_get_head_hash($project); die_error(undef, "Couldn't find base commit") unless ($hash_base); - my %co = git_read_commit($hash_base) + my %co = parse_commit($hash_base) or die_error(undef, "Reading commit failed"); if (!defined $hash) { $hash = git_get_hash_by_path($hash_base, $file_name, "blob") or die_error(undef, "Error lookup file"); } - open ($fd, "-|", $GIT, "annotate", '-l', '-t', '-r', $file_name, $hash_base) + open ($fd, "-|", git_cmd(), "annotate", '-l', '-t', '-r', $file_name, $hash_base) or die_error(undef, "Open git-annotate failed"); git_header_html(); my $formats_nav = - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$hash;hb=$hash_base;f=$file_name")}, "blob") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blame;f=$file_name")}, "head"); - git_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav); - git_header_div('commit', esc_html($co{'title'}), $hash_base); - git_print_page_path($file_name, 'blob'); + $cgi->a({-href => href(action=>"blob", hash=>$hash, hash_base=>$hash_base, file_name=>$file_name)}, + "blob") . + " | " . + $cgi->a({-href => href(action=>"blame", file_name=>$file_name)}, + "head"); + git_print_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav); + git_print_header_div('commit', esc_html($co{'title'}), $hash_base); + git_print_page_path($file_name, 'blob', $hash_base); print "<div class=\"page_body\">\n"; print <<HTML; <table class="blame"> @@ -1580,7 +2504,7 @@ HTML chomp $line; $line_class_num = ($line_class_num + 1) % $line_class_len; - if ($line =~ m/^([0-9a-fA-F]{40})\t\(\s*([^\t]+)\t(\d+) \+\d\d\d\d\t(\d+)\)(.*)$/) { + if ($line =~ m/^([0-9a-fA-F]{40})\t\(\s*([^\t]+)\t(\d+) [+-]\d\d\d\d\t(\d+)\)(.*)$/) { $long_rev = $1; $author = $2; $time = $3; @@ -1603,7 +2527,7 @@ HTML print <<HTML; <tr class="$line_class[$line_class_num]"> - <td class="sha1"><a href="$my_uri?${\esc_param ("p=$project;a=commit;h=$long_rev")}" class="text">$short_rev..</a></td> + <td class="sha1"><a href="${\href (action=>"commit", hash=>$long_rev)}" class="text">$short_rev..</a></td> <td class="$age_class">$age_str</td> <td>$author</td> <td class="linenr"><a id="$lineno" href="#$lineno" class="linenr">$lineno</a></td> @@ -1612,18 +2536,19 @@ HTML HTML } # while (my $line = <$fd>) print "</table>\n\n"; - close $fd or print "Reading blob failed.\n"; + close $fd + or print "Reading blob failed.\n"; print "</div>"; git_footer_html(); } sub git_tags { - my $head = git_read_head($project); + my $head = git_get_head_hash($project); git_header_html(); - git_page_nav('','', $head,undef,$head); - git_header_div('summary', $project); + git_print_page_nav('','', $head,undef,$head); + git_print_header_div('summary', $project); - my $taglist = git_read_refs("refs/tags"); + my $taglist = git_get_refs_list("refs/tags"); if (defined @$taglist) { git_tags_body($taglist); } @@ -1631,12 +2556,12 @@ sub git_tags { } sub git_heads { - my $head = git_read_head($project); + my $head = git_get_head_hash($project); git_header_html(); - git_page_nav('','', $head,undef,$head); - git_header_div('summary', $project); + git_print_page_nav('','', $head,undef,$head); + git_print_header_div('summary', $project); - my $taglist = git_read_refs("refs/heads"); + my $taglist = git_get_refs_list("refs/heads"); if (defined @$taglist) { git_heads_body($taglist, $head); } @@ -1644,20 +2569,26 @@ sub git_heads { } sub git_blob_plain { + my $expires; + if (!defined $hash) { if (defined $file_name) { - my $base = $hash_base || git_read_head($project); + my $base = $hash_base || git_get_head_hash($project); $hash = git_get_hash_by_path($base, $file_name, "blob") or die_error(undef, "Error lookup file"); } else { die_error(undef, "No file name defined"); } + } elsif ($hash =~ m/^[0-9a-fA-F]{40}$/) { + # blobs defined by non-textual hash id's can be cached + $expires = "+1d"; } + my $type = shift; - open my $fd, "-|", $GIT, "cat-file", "blob", $hash + open my $fd, "-|", git_cmd(), "cat-file", "blob", $hash or die_error(undef, "Couldn't cat $file_name, $hash"); - $type ||= git_blob_plain_mimetype($fd, $file_name); + $type ||= blob_mimetype($fd, $file_name); # save as filename, even when no $file_name is given my $save_as = "$hash"; @@ -1667,7 +2598,10 @@ sub git_blob_plain { $save_as .= '.txt'; } - print $cgi->header(-type => "$type", '-content-disposition' => "inline; filename=\"$save_as\""); + print $cgi->header( + -type => "$type", + -expires=>$expires, + -content_disposition => "inline; filename=\"$save_as\""); undef $/; binmode STDOUT, ':raw'; print <$fd>; @@ -1677,60 +2611,78 @@ sub git_blob_plain { } sub git_blob { + my $expires; + if (!defined $hash) { if (defined $file_name) { - my $base = $hash_base || git_read_head($project); + my $base = $hash_base || git_get_head_hash($project); $hash = git_get_hash_by_path($base, $file_name, "blob") or die_error(undef, "Error lookup file"); } else { die_error(undef, "No file name defined"); } + } elsif ($hash =~ m/^[0-9a-fA-F]{40}$/) { + # blobs defined by non-textual hash id's can be cached + $expires = "+1d"; } - my $have_blame = git_get_project_config_bool ('blame'); - open my $fd, "-|", $GIT, "cat-file", "blob", $hash + + my ($have_blame) = gitweb_check_feature('blame'); + open my $fd, "-|", git_cmd(), "cat-file", "blob", $hash or die_error(undef, "Couldn't cat $file_name, $hash"); - my $mimetype = git_blob_plain_mimetype($fd, $file_name); + my $mimetype = blob_mimetype($fd, $file_name); if ($mimetype !~ m/^text\//) { close $fd; return git_blob_plain($mimetype); } - git_header_html(); + git_header_html(undef, $expires); my $formats_nav = ''; - if (defined $hash_base && (my %co = git_read_commit($hash_base))) { + if (defined $hash_base && (my %co = parse_commit($hash_base))) { if (defined $file_name) { if ($have_blame) { - $formats_nav .= $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blame;h=$hash;hb=$hash_base;f=$file_name")}, "blame") . " | "; + $formats_nav .= + $cgi->a({-href => href(action=>"blame", hash_base=>$hash_base, + hash=>$hash, file_name=>$file_name)}, + "blame") . + " | "; } $formats_nav .= - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob_plain;h=$hash;f=$file_name")}, "plain") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;hb=HEAD;f=$file_name")}, "head"); + $cgi->a({-href => href(action=>"blob_plain", + hash=>$hash, file_name=>$file_name)}, + "plain") . + " | " . + $cgi->a({-href => href(action=>"blob", + hash_base=>"HEAD", file_name=>$file_name)}, + "head"); } else { - $formats_nav .= $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob_plain;h=$hash")}, "plain"); + $formats_nav .= + $cgi->a({-href => href(action=>"blob_plain", hash=>$hash)}, "plain"); } - git_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav); - git_header_div('commit', esc_html($co{'title'}), $hash_base); + git_print_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav); + git_print_header_div('commit', esc_html($co{'title'}), $hash_base); } else { print "<div class=\"page_nav\">\n" . "<br/><br/></div>\n" . "<div class=\"title\">$hash</div>\n"; } - git_print_page_path($file_name, "blob"); + git_print_page_path($file_name, "blob", $hash_base); print "<div class=\"page_body\">\n"; my $nr; while (my $line = <$fd>) { chomp $line; $nr++; $line = untabify($line); - printf "<div class=\"pre\"><a id=\"l%i\" href=\"#l%i\" class=\"linenr\">%4i</a> %s</div>\n", $nr, $nr, $nr, esc_html($line); + printf "<div class=\"pre\"><a id=\"l%i\" href=\"#l%i\" class=\"linenr\">%4i</a> %s</div>\n", + $nr, $nr, $nr, esc_html($line); } - close $fd or print "Reading blob failed.\n"; + close $fd + or print "Reading blob failed.\n"; print "</div>"; git_footer_html(); } 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"); @@ -1740,23 +2692,22 @@ sub git_tree { } } $/ = "\0"; - open my $fd, "-|", $GIT, "ls-tree", '-z', $hash + open my $fd, "-|", git_cmd(), "ls-tree", '-z', $hash or die_error(undef, "Open git-ls-tree failed"); my @entries = map { chomp; $_ } <$fd>; close $fd or die_error(undef, "Reading tree failed"); $/ = "\n"; - my $refs = read_info_ref(); - my $ref = git_get_referencing($refs, $hash_base); + my $refs = git_get_references(); + my $ref = format_ref_marker($refs, $hash_base); git_header_html(); - my $base_key = ""; my $base = ""; - my $have_blame = git_get_project_config_bool ('blame'); - if (defined $hash_base && (my %co = git_read_commit($hash_base))) { - $base_key = ";hb=$hash_base"; - git_page_nav('tree','', $hash_base); - git_header_div('commit', esc_html($co{'title'}) . $ref, $hash_base); + my ($have_blame) = gitweb_check_feature('blame'); + if (defined $hash_base && (my %co = parse_commit($hash_base))) { + git_print_page_nav('tree','', $hash_base); + git_print_header_div('commit', esc_html($co{'title'}) . $ref, $hash_base); } else { + undef $hash_base; print "<div class=\"page_nav\">\n"; print "<br/><br/></div>\n"; print "<div class=\"title\">$hash</div>\n"; @@ -1764,45 +2715,22 @@ sub git_tree { if (defined $file_name) { $base = esc_html("$file_name/"); } - git_print_page_path($file_name, 'tree'); + git_print_page_path($file_name, 'tree', $hash_base); print "<div class=\"page_body\">\n"; print "<table cellspacing=\"0\">\n"; my $alternate = 0; foreach my $line (@entries) { - #'100644 blob 0fa3f3a66fb6a137f6ec2c19351ed4d807070ffa panic.c' - $line =~ m/^([0-9]+) (.+) ([0-9a-fA-F]{40})\t(.+)$/; - my $t_mode = $1; - my $t_type = $2; - my $t_hash = $3; - my $t_name = validate_input($4); + my %t = parse_ls_tree_line($line, -z => 1); + if ($alternate) { print "<tr class=\"dark\">\n"; } else { print "<tr class=\"light\">\n"; } $alternate ^= 1; - print "<td class=\"mode\">" . mode_str($t_mode) . "</td>\n"; - if ($t_type eq "blob") { - print "<td class=\"list\">" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$t_hash$base_key;f=$base$t_name"), -class => "list"}, esc_html($t_name)) . - "</td>\n" . - "<td class=\"link\">" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$t_hash$base_key;f=$base$t_name")}, "blob"); - if ($have_blame) { - print " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blame;h=$t_hash$base_key;f=$base$t_name")}, "blame"); - } - print " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=history;h=$t_hash;hb=$hash_base;f=$base$t_name")}, "history") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob_plain;h=$t_hash;f=$base$t_name")}, "raw") . - "</td>\n"; - } elsif ($t_type eq "tree") { - print "<td class=\"list\">" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;h=$t_hash$base_key;f=$base$t_name")}, esc_html($t_name)) . - "</td>\n" . - "<td class=\"link\">" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;h=$t_hash$base_key;f=$base$t_name")}, "tree") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=history;hb=$hash_base;f=$base$t_name")}, "history") . - "</td>\n"; - } + + git_print_tree_entry(\%t, $base, $hash_base, $have_blame); + print "</tr>\n"; } print "</table>\n" . @@ -1810,89 +2738,102 @@ sub git_tree { git_footer_html(); } +sub git_snapshot { + + my ($ctype, $suffix, $command) = gitweb_check_feature('snapshot'); + my $have_snapshot = (defined $ctype && defined $suffix); + if (!$have_snapshot) { + die_error('403 Permission denied', "Permission denied"); + } + + if (!defined $hash) { + $hash = git_get_head_hash($project); + } + + my $filename = basename($project) . "-$hash.tar.$suffix"; + + print $cgi->header(-type => 'application/x-tar', + -content_encoding => $ctype, + -content_disposition => "inline; filename=\"$filename\"", + -status => '200 OK'); + + my $git_command = git_cmd_str(); + open my $fd, "-|", "$git_command tar-tree $hash \'$project\' | $command" or + die_error(undef, "Execute git-tar-tree failed."); + binmode STDOUT, ':raw'; + print <$fd>; + binmode STDOUT, ':utf8'; # as set at the beginning of gitweb.cgi + close $fd; + +} + sub git_log { - my $head = git_read_head($project); + my $head = git_get_head_hash($project); if (!defined $hash) { $hash = $head; } if (!defined $page) { $page = 0; } - my $refs = read_info_ref(); + my $refs = git_get_references(); my $limit = sprintf("--max-count=%i", (100 * ($page+1))); - open my $fd, "-|", $GIT, "rev-list", $limit, $hash + open my $fd, "-|", git_cmd(), "rev-list", $limit, $hash or die_error(undef, "Open git-rev-list failed"); my @revlist = map { chomp; $_ } <$fd>; close $fd; - my $paging_nav = git_get_paging_nav('log', $hash, $head, $page, $#revlist); + my $paging_nav = format_paging_nav('log', $hash, $head, $page, $#revlist); git_header_html(); - git_page_nav('log','', $hash,undef,undef, $paging_nav); + git_print_page_nav('log','', $hash,undef,undef, $paging_nav); if (!@revlist) { - my %co = git_read_commit($hash); + my %co = parse_commit($hash); - git_header_div('summary', $project); + git_print_header_div('summary', $project); print "<div class=\"page_body\"> Last change $co{'age_string'}.<br/><br/></div>\n"; } for (my $i = ($page * 100); $i <= $#revlist; $i++) { my $commit = $revlist[$i]; - my $ref = git_get_referencing($refs, $commit); - my %co = git_read_commit($commit); + my $ref = format_ref_marker($refs, $commit); + my %co = parse_commit($commit); next if !%co; - my %ad = date_str($co{'author_epoch'}); - git_header_div('commit', + my %ad = parse_date($co{'author_epoch'}); + git_print_header_div('commit', "<span class=\"age\">$co{'age_string'}</span>" . esc_html($co{'title'}) . $ref, $commit); print "<div class=\"title_text\">\n" . "<div class=\"log_link\">\n" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$commit")}, "commit") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$commit")}, "commitdiff") . + $cgi->a({-href => href(action=>"commit", hash=>$commit)}, "commit") . + " | " . + $cgi->a({-href => href(action=>"commitdiff", hash=>$commit)}, "commitdiff") . "<br/>\n" . "</div>\n" . "<i>" . esc_html($co{'author_name'}) . " [$ad{'rfc2822'}]</i><br/>\n" . - "</div>\n" . - "<div class=\"log_body\">\n"; - my $comment = $co{'comment'}; - my $empty = 0; - foreach my $line (@$comment) { - if ($line =~ m/^ *(signed[ \-]off[ \-]by[ :]|acked[ \-]by[ :]|cc[ :])/i) { - next; - } - if ($line eq "") { - if ($empty) { - next; - } - $empty = 1; - } else { - $empty = 0; - } - print format_log_line_html($line) . "<br/>\n"; - } - if (!$empty) { - print "<br/>\n"; - } + "</div>\n"; + + print "<div class=\"log_body\">\n"; + git_print_simplified_log($co{'comment'}); print "</div>\n"; } git_footer_html(); } sub git_commit { - my %co = git_read_commit($hash); + my %co = parse_commit($hash); if (!%co) { die_error(undef, "Unknown commit object"); } - my %ad = date_str($co{'author_epoch'}, $co{'author_tz'}); - my %cd = date_str($co{'committer_epoch'}, $co{'committer_tz'}); + my %ad = parse_date($co{'author_epoch'}, $co{'author_tz'}); + my %cd = parse_date($co{'committer_epoch'}, $co{'committer_tz'}); my $parent = $co{'parent'}; if (!defined $parent) { $parent = "--root"; } - open my $fd, "-|", $GIT, "diff-tree", '-r', '-M', $parent, $hash + open my $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts, $parent, $hash or die_error(undef, "Open git-diff-tree failed"); my @difftree = map { chomp; $_ } <$fd>; close $fd or die_error(undef, "Reading git-diff-tree failed"); @@ -1902,22 +2843,28 @@ sub git_commit { if ($hash =~ m/^[0-9a-fA-F]{40}$/) { $expires = "+1d"; } - my $refs = read_info_ref(); - my $ref = git_get_referencing($refs, $co{'id'}); + my $refs = git_get_references(); + my $ref = format_ref_marker($refs, $co{'id'}); + + my ($ctype, $suffix, $command) = gitweb_check_feature('snapshot'); + my $have_snapshot = (defined $ctype && defined $suffix); + my $formats_nav = ''; if (defined $file_name && defined $co{'parent'}) { my $parent = $co{'parent'}; - $formats_nav .= $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blame;hb=$parent;f=$file_name")}, "blame"); + $formats_nav .= + $cgi->a({-href => href(action=>"blame", hash_parent=>$parent, file_name=>$file_name)}, + "blame"); } git_header_html(undef, $expires); - git_page_nav('commit', defined $co{'parent'} ? '' : 'commitdiff', - $hash, $co{'tree'}, $hash, - $formats_nav); + git_print_page_nav('commit', defined $co{'parent'} ? '' : 'commitdiff', + $hash, $co{'tree'}, $hash, + $formats_nav); if (defined $co{'parent'}) { - git_header_div('commitdiff', esc_html($co{'title'}) . $ref, $hash); + git_print_header_div('commitdiff', esc_html($co{'title'}) . $ref, $hash); } else { - git_header_div('tree', esc_html($co{'title'}) . $ref, $co{'tree'}, $hash); + git_print_header_div('tree', esc_html($co{'title'}) . $ref, $co{'tree'}, $hash); } print "<div class=\"title_text\">\n" . "<table cellspacing=\"0\">\n"; @@ -1925,403 +2872,401 @@ sub git_commit { "<tr>" . "<td></td><td> $ad{'rfc2822'}"; if ($ad{'hour_local'} < 6) { - printf(" (<span class=\"atnight\">%02d:%02d</span> %s)", $ad{'hour_local'}, $ad{'minute_local'}, $ad{'tz_local'}); + printf(" (<span class=\"atnight\">%02d:%02d</span> %s)", + $ad{'hour_local'}, $ad{'minute_local'}, $ad{'tz_local'}); } else { - printf(" (%02d:%02d %s)", $ad{'hour_local'}, $ad{'minute_local'}, $ad{'tz_local'}); + printf(" (%02d:%02d %s)", + $ad{'hour_local'}, $ad{'minute_local'}, $ad{'tz_local'}); } print "</td>" . "</tr>\n"; print "<tr><td>committer</td><td>" . esc_html($co{'committer'}) . "</td></tr>\n"; - print "<tr><td></td><td> $cd{'rfc2822'}" . sprintf(" (%02d:%02d %s)", $cd{'hour_local'}, $cd{'minute_local'}, $cd{'tz_local'}) . "</td></tr>\n"; + print "<tr><td></td><td> $cd{'rfc2822'}" . + sprintf(" (%02d:%02d %s)", $cd{'hour_local'}, $cd{'minute_local'}, $cd{'tz_local'}) . + "</td></tr>\n"; print "<tr><td>commit</td><td class=\"sha1\">$co{'id'}</td></tr>\n"; print "<tr>" . "<td>tree</td>" . "<td class=\"sha1\">" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;h=$co{'tree'};hb=$hash"), class => "list"}, $co{'tree'}) . - "</td>" . - "<td class=\"link\">" . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;h=$co{'tree'};hb=$hash")}, "tree") . + $cgi->a({-href => href(action=>"tree", hash=>$co{'tree'}, hash_base=>$hash), + class => "list"}, $co{'tree'}) . "</td>" . + "<td class=\"link\">" . + $cgi->a({-href => href(action=>"tree", hash=>$co{'tree'}, hash_base=>$hash)}, + "tree"); + if ($have_snapshot) { + print " | " . + $cgi->a({-href => href(action=>"snapshot", hash=>$hash)}, "snapshot"); + } + print "</td>" . "</tr>\n"; my $parents = $co{'parents'}; foreach my $par (@$parents) { print "<tr>" . "<td>parent</td>" . - "<td class=\"sha1\">" . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$par"), class => "list"}, $par) . "</td>" . + "<td class=\"sha1\">" . + $cgi->a({-href => href(action=>"commit", hash=>$par), + class => "list"}, $par) . + "</td>" . "<td class=\"link\">" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$par")}, "commit") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$hash;hp=$par")}, "commitdiff") . + $cgi->a({-href => href(action=>"commit", hash=>$par)}, "commit") . + " | " . + $cgi->a({-href => href(action=>"commitdiff", hash=>$hash, hash_parent=>$par)}, "diff") . "</td>" . "</tr>\n"; } print "</table>". "</div>\n"; + print "<div class=\"page_body\">\n"; - my $comment = $co{'comment'}; - my $empty = 0; - my $signed = 0; - foreach my $line (@$comment) { - # print only one empty line - if ($line eq "") { - if ($empty || $signed) { - next; - } - $empty = 1; + git_print_log($co{'comment'}); + print "</div>\n"; + + git_difftree_body(\@difftree, $hash, $parent); + + git_footer_html(); +} + +sub git_blobdiff { + my $format = shift || 'html'; + + my $fd; + my @difftree; + my %diffinfo; + my $expires; + + # preparing $fd and %diffinfo for git_patchset_body + # new style URI + if (defined $hash_base && defined $hash_parent_base) { + if (defined $file_name) { + # read raw output + open $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts, $hash_parent_base, $hash_base, + "--", $file_name + or die_error(undef, "Open git-diff-tree failed"); + @difftree = map { chomp; $_ } <$fd>; + close $fd + or die_error(undef, "Reading git-diff-tree failed"); + @difftree + or die_error('404 Not Found', "Blob diff not found"); + + } elsif (defined $hash && + $hash =~ /[0-9a-fA-F]{40}/) { + # try to find filename from $hash + + # read filtered raw output + open $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts, $hash_parent_base, $hash_base + or die_error(undef, "Open git-diff-tree failed"); + @difftree = + # ':100644 100644 03b21826... 3b93d5e7... M ls-files.c' + # $hash == to_id + grep { /^:[0-7]{6} [0-7]{6} [0-9a-fA-F]{40} $hash/ } + map { chomp; $_ } <$fd>; + close $fd + or die_error(undef, "Reading git-diff-tree failed"); + @difftree + or die_error('404 Not Found', "Blob diff not found"); + } else { - $empty = 0; + die_error('404 Not Found', "Missing one of the blob diff parameters"); } - if ($line =~ m/^ *(signed[ \-]off[ \-]by[ :]|acked[ \-]by[ :]|cc[ :])/i) { - $signed = 1; - print "<span class=\"signoff\">" . esc_html($line) . "</span><br/>\n"; - } else { - $signed = 0; - print format_log_line_html($line) . "<br/>\n"; + + if (@difftree > 1) { + die_error('404 Not Found', "Ambiguous blob diff specification"); } + + %diffinfo = parse_difftree_raw_line($difftree[0]); + $file_parent ||= $diffinfo{'from_file'} || $file_name || $diffinfo{'file'}; + $file_name ||= $diffinfo{'to_file'} || $diffinfo{'file'}; + + $hash_parent ||= $diffinfo{'from_id'}; + $hash ||= $diffinfo{'to_id'}; + + # non-textual hash id's can be cached + if ($hash_base =~ m/^[0-9a-fA-F]{40}$/ && + $hash_parent_base =~ m/^[0-9a-fA-F]{40}$/) { + $expires = '+1d'; + } + + # open patch output + open $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts, + '-p', $hash_parent_base, $hash_base, + "--", $file_name + or die_error(undef, "Open git-diff-tree failed"); } - print "</div>\n"; - print "<div class=\"list_head\">\n"; - if ($#difftree > 10) { - print(($#difftree + 1) . " files changed:\n"); - } - print "</div>\n"; - print "<table class=\"diff_tree\">\n"; - my $alternate = 0; - foreach my $line (@difftree) { - # ':100644 100644 03b218260e99b78c6df0ed378e59ed9205ccc96d 3b93d5e7cc7f7dd4ebed13a5cc1a4ad976fc94d8 M ls-files.c' - # ':100644 100644 7f9281985086971d3877aca27704f2aaf9c448ce bc190ebc71bbd923f2b728e505408f5e54bd073a M rev-tree.c' - if ($line !~ m/^:([0-7]{6}) ([0-7]{6}) ([0-9a-fA-F]{40}) ([0-9a-fA-F]{40}) (.)([0-9]{0,3})\t(.*)$/) { - next; + + # old/legacy style URI + if (!%diffinfo && # if new style URI failed + defined $hash && defined $hash_parent) { + # fake git-diff-tree raw output + $diffinfo{'from_mode'} = $diffinfo{'to_mode'} = "blob"; + $diffinfo{'from_id'} = $hash_parent; + $diffinfo{'to_id'} = $hash; + if (defined $file_name) { + if (defined $file_parent) { + $diffinfo{'status'} = '2'; + $diffinfo{'from_file'} = $file_parent; + $diffinfo{'to_file'} = $file_name; + } else { # assume not renamed + $diffinfo{'status'} = '1'; + $diffinfo{'from_file'} = $file_name; + $diffinfo{'to_file'} = $file_name; + } + } else { # no filename given + $diffinfo{'status'} = '2'; + $diffinfo{'from_file'} = $hash_parent; + $diffinfo{'to_file'} = $hash; } - my $from_mode = $1; - my $to_mode = $2; - my $from_id = $3; - my $to_id = $4; - my $status = $5; - my $similarity = $6; - my $file = validate_input(unquote($7)); - if ($alternate) { - print "<tr class=\"dark\">\n"; + + # non-textual hash id's can be cached + if ($hash =~ m/^[0-9a-fA-F]{40}$/ && + $hash_parent =~ m/^[0-9a-fA-F]{40}$/) { + $expires = '+1d'; + } + + # open patch output + open $fd, "-|", git_cmd(), "diff", '-p', @diff_opts, $hash_parent, $hash + or die_error(undef, "Open git-diff failed"); + } else { + die_error('404 Not Found', "Missing one of the blob diff parameters") + unless %diffinfo; + } + + # header + if ($format eq 'html') { + my $formats_nav = + $cgi->a({-href => href(action=>"blobdiff_plain", + hash=>$hash, hash_parent=>$hash_parent, + hash_base=>$hash_base, hash_parent_base=>$hash_parent_base, + file_name=>$file_name, file_parent=>$file_parent)}, + "plain"); + git_header_html(undef, $expires); + if (defined $hash_base && (my %co = parse_commit($hash_base))) { + git_print_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav); + git_print_header_div('commit', esc_html($co{'title'}), $hash_base); } else { - print "<tr class=\"light\">\n"; + print "<div class=\"page_nav\"><br/>$formats_nav<br/></div>\n"; + print "<div class=\"title\">$hash vs $hash_parent</div>\n"; } - $alternate ^= 1; - if ($status eq "A") { - my $mode_chng = ""; - if (S_ISREG(oct $to_mode)) { - $mode_chng = sprintf(" with mode: %04o", (oct $to_mode) & 0777); - } - print "<td>" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$to_id;hb=$hash;f=$file"), -class => "list"}, esc_html($file)) . "</td>\n" . - "<td><span class=\"file_status new\">[new " . file_type($to_mode) . "$mode_chng]</span></td>\n" . - "<td class=\"link\">" . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$to_id;hb=$hash;f=$file")}, "blob") . "</td>\n"; - } elsif ($status eq "D") { - print "<td>" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$from_id;hb=$parent;f=$file"), -class => "list"}, esc_html($file)) . "</td>\n" . - "<td><span class=\"file_status deleted\">[deleted " . file_type($from_mode). "]</span></td>\n" . - "<td class=\"link\">" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$from_id;hb=$parent;f=$file")}, "blob") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=history;hb=$parent;f=$file")}, "history") . - "</td>\n" - } elsif ($status eq "M" || $status eq "T") { - my $mode_chnge = ""; - if ($from_mode != $to_mode) { - $mode_chnge = " <span class=\"file_status mode_chnge\">[changed"; - if (((oct $from_mode) & S_IFMT) != ((oct $to_mode) & S_IFMT)) { - $mode_chnge .= " from " . file_type($from_mode) . " to " . file_type($to_mode); - } - if (((oct $from_mode) & 0777) != ((oct $to_mode) & 0777)) { - if (S_ISREG($from_mode) && S_ISREG($to_mode)) { - $mode_chnge .= sprintf(" mode: %04o->%04o", (oct $from_mode) & 0777, (oct $to_mode) & 0777); - } elsif (S_ISREG($to_mode)) { - $mode_chnge .= sprintf(" mode: %04o", (oct $to_mode) & 0777); - } - } - $mode_chnge .= "]</span>\n"; - } - print "<td>"; - if ($to_id ne $from_id) { - print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blobdiff;h=$to_id;hp=$from_id;hb=$hash;f=$file"), -class => "list"}, esc_html($file)); - } else { - print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$to_id;hb=$hash;f=$file"), -class => "list"}, esc_html($file)); - } - print "</td>\n" . - "<td>$mode_chnge</td>\n" . - "<td class=\"link\">"; - print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$to_id;hb=$hash;f=$file")}, "blob"); - if ($to_id ne $from_id) { - print " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blobdiff;h=$to_id;hp=$from_id;hb=$hash;f=$file")}, "diff"); - } - print " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=history;hb=$hash;f=$file")}, "history") . "\n"; - print "</td>\n"; - } elsif ($status eq "R") { - my ($from_file, $to_file) = split "\t", $file; - my $mode_chng = ""; - if ($from_mode != $to_mode) { - $mode_chng = sprintf(", mode: %04o", (oct $to_mode) & 0777); - } - print "<td>" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$to_id;hb=$hash;f=$to_file"), -class => "list"}, esc_html($to_file)) . "</td>\n" . - "<td><span class=\"file_status moved\">[moved from " . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$from_id;hb=$parent;f=$from_file"), -class => "list"}, esc_html($from_file)) . - " with " . (int $similarity) . "% similarity$mode_chng]</span></td>\n" . - "<td class=\"link\">" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$to_id;hb=$hash;f=$to_file")}, "blob"); - if ($to_id ne $from_id) { - print " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blobdiff;h=$to_id;hp=$from_id;hb=$hash;f=$to_file")}, "diff"); - } - print "</td>\n"; + if (defined $file_name) { + git_print_page_path($file_name, "blob", $hash_base); + } else { + print "<div class=\"page_path\"></div>\n"; } - print "</tr>\n"; + + } elsif ($format eq 'plain') { + print $cgi->header( + -type => 'text/plain', + -charset => 'utf-8', + -expires => $expires, + -content_disposition => qq(inline; filename="${file_name}.patch")); + + print "X-Git-Url: " . $cgi->self_url() . "\n\n"; + + } else { + die_error(undef, "Unknown blobdiff format"); } - print "</table>\n"; - git_footer_html(); -} -sub git_blobdiff { - mkdir($git_temp, 0700); - git_header_html(); - if (defined $hash_base && (my %co = git_read_commit($hash_base))) { - my $formats_nav = - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blobdiff_plain;h=$hash;hp=$hash_parent")}, "plain"); - git_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav); - git_header_div('commit', esc_html($co{'title'}), $hash_base); + # patch + if ($format eq 'html') { + print "<div class=\"page_body\">\n"; + + git_patchset_body($fd, [ \%diffinfo ], $hash_base, $hash_parent_base); + close $fd; + + print "</div>\n"; # class="page_body" + git_footer_html(); + } else { - print "<div class=\"page_nav\">\n" . - "<br/><br/></div>\n" . - "<div class=\"title\">$hash vs $hash_parent</div>\n"; - } - git_print_page_path($file_name, "blob"); - print "<div class=\"page_body\">\n" . - "<div class=\"diff_info\">blob:" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$hash_parent;hb=$hash_base;f=$file_name")}, $hash_parent) . - " -> blob:" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$hash;hb=$hash_base;f=$file_name")}, $hash) . - "</div>\n"; - git_diff_print($hash_parent, $file_name || $hash_parent, $hash, $file_name || $hash); - print "</div>"; - git_footer_html(); + while (my $line = <$fd>) { + $line =~ s!a/($hash|$hash_parent)!a/$diffinfo{'from_file'}!g; + $line =~ s!b/($hash|$hash_parent)!b/$diffinfo{'to_file'}!g; + + print $line; + + last if $line =~ m!^\+\+\+!; + } + local $/ = undef; + print <$fd>; + close $fd; + } } sub git_blobdiff_plain { - mkdir($git_temp, 0700); - print $cgi->header(-type => "text/plain", -charset => 'utf-8'); - git_diff_print($hash_parent, $file_name || $hash_parent, $hash, $file_name || $hash, "plain"); + git_blobdiff('plain'); } sub git_commitdiff { - mkdir($git_temp, 0700); - my %co = git_read_commit($hash); + my $format = shift || 'html'; + my %co = parse_commit($hash); if (!%co) { die_error(undef, "Unknown commit object"); } if (!defined $hash_parent) { $hash_parent = $co{'parent'} || '--root'; } - open my $fd, "-|", $GIT, "diff-tree", '-r', $hash_parent, $hash - or die_error(undef, "Open git-diff-tree failed"); - my @difftree = map { chomp; $_ } <$fd>; - close $fd or die_error(undef, "Reading git-diff-tree failed"); + + # read commitdiff + my $fd; + my @difftree; + if ($format eq 'html') { + open $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts, + "--patch-with-raw", "--full-index", $hash_parent, $hash + or die_error(undef, "Open git-diff-tree failed"); + + while (chomp(my $line = <$fd>)) { + # empty line ends raw part of diff-tree output + last unless $line; + push @difftree, $line; + } + + } elsif ($format eq 'plain') { + open $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts, + '-p', $hash_parent, $hash + or die_error(undef, "Open git-diff-tree failed"); + + } else { + die_error(undef, "Unknown commitdiff format"); + } # non-textual hash id's can be cached my $expires; if ($hash =~ m/^[0-9a-fA-F]{40}$/) { $expires = "+1d"; } - my $refs = read_info_ref(); - my $ref = git_get_referencing($refs, $co{'id'}); - my $formats_nav = - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff_plain;h=$hash;hp=$hash_parent")}, "plain"); - git_header_html(undef, $expires); - git_page_nav('commitdiff','', $hash,$co{'tree'},$hash, $formats_nav); - git_header_div('commit', esc_html($co{'title'}) . $ref, $hash); - print "<div class=\"page_body\">\n"; - my $comment = $co{'comment'}; - my $empty = 0; - my $signed = 0; - my @log = @$comment; - # remove first and empty lines after that - shift @log; - while (defined $log[0] && $log[0] eq "") { - shift @log; - } - foreach my $line (@log) { - if ($line =~ m/^ *(signed[ \-]off[ \-]by[ :]|acked[ \-]by[ :]|cc[ :])/i) { - next; - } - if ($line eq "") { - if ($empty) { - next; - } - $empty = 1; - } else { - $empty = 0; - } - print format_log_line_html($line) . "<br/>\n"; - } - print "<br/>\n"; - foreach my $line (@difftree) { - # ':100644 100644 03b218260e99b78c6df0ed378e59ed9205ccc96d 3b93d5e7cc7f7dd4ebed13a5cc1a4ad976fc94d8 M ls-files.c' - # ':100644 100644 7f9281985086971d3877aca27704f2aaf9c448ce bc190ebc71bbd923f2b728e505408f5e54bd073a M rev-tree.c' - if ($line !~ m/^:([0-7]{6}) ([0-7]{6}) ([0-9a-fA-F]{40}) ([0-9a-fA-F]{40}) (.)\t(.*)$/) { - next; - } - my $from_mode = $1; - my $to_mode = $2; - my $from_id = $3; - my $to_id = $4; - my $status = $5; - my $file = validate_input(unquote($6)); - if ($status eq "A") { - print "<div class=\"diff_info\">" . file_type($to_mode) . ":" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$to_id;hb=$hash;f=$file")}, $to_id) . "(new)" . - "</div>\n"; - git_diff_print(undef, "/dev/null", $to_id, "b/$file"); - } elsif ($status eq "D") { - print "<div class=\"diff_info\">" . file_type($from_mode) . ":" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$from_id;hb=$hash_parent;f=$file")}, $from_id) . "(deleted)" . - "</div>\n"; - git_diff_print($from_id, "a/$file", undef, "/dev/null"); - } elsif ($status eq "M") { - if ($from_id ne $to_id) { - print "<div class=\"diff_info\">" . - file_type($from_mode) . ":" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$from_id;hb=$hash_parent;f=$file")}, $from_id) . - " -> " . - file_type($to_mode) . ":" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$to_id;hb=$hash;f=$file")}, $to_id); - print "</div>\n"; - git_diff_print($from_id, "a/$file", $to_id, "b/$file"); - } - } - } - print "<br/>\n" . - "</div>"; - git_footer_html(); -} -sub git_commitdiff_plain { - mkdir($git_temp, 0700); - my %co = git_read_commit($hash); - if (!%co) { - die_error(undef, "Unknown commit object"); - } - if (!defined $hash_parent) { - $hash_parent = $co{'parent'} || '--root'; - } - open my $fd, "-|", $GIT, "diff-tree", '-r', $hash_parent, $hash - or die_error(undef, "Open git-diff-tree failed"); - my @difftree = map { chomp; $_ } <$fd>; - close $fd or die_error(undef, "Reading diff-tree failed"); - - # try to figure out the next tag after this commit - my $tagname; - my $refs = read_info_ref("tags"); - open $fd, "-|", $GIT, "rev-list", "HEAD"; - my @commits = map { chomp; $_ } <$fd>; - close $fd; - foreach my $commit (@commits) { - if (defined $refs->{$commit}) { - $tagname = $refs->{$commit} - } - if ($commit eq $hash) { - last; - } - } + # write commit message + if ($format eq 'html') { + my $refs = git_get_references(); + my $ref = format_ref_marker($refs, $co{'id'}); + my $formats_nav = + $cgi->a({-href => href(action=>"commitdiff_plain", + hash=>$hash, hash_parent=>$hash_parent)}, + "plain"); + + git_header_html(undef, $expires); + git_print_page_nav('commitdiff','', $hash,$co{'tree'},$hash, $formats_nav); + git_print_header_div('commit', esc_html($co{'title'}) . $ref, $hash); + git_print_authorship(\%co); + print "<div class=\"page_body\">\n"; + print "<div class=\"log\">\n"; + git_print_simplified_log($co{'comment'}, 1); # skip title + print "</div>\n"; # class="log" + + } elsif ($format eq 'plain') { + my $refs = git_get_references("tags"); + my $tagname = git_get_rev_name_tags($hash); + my $filename = basename($project) . "-$hash.patch"; + + print $cgi->header( + -type => 'text/plain', + -charset => 'utf-8', + -expires => $expires, + -content_disposition => qq(inline; filename="$filename")); + my %ad = parse_date($co{'author_epoch'}, $co{'author_tz'}); + print <<TEXT; +From: $co{'author'} +Date: $ad{'rfc2822'} ($ad{'tz_local'}) +Subject: $co{'title'} +TEXT + print "X-Git-Tag: $tagname\n" if $tagname; + print "X-Git-Url: " . $cgi->self_url() . "\n\n"; + + foreach my $line (@{$co{'comment'}}) { + print "$line\n"; + } + print "---\n\n"; + } + + # write patch + if ($format eq 'html') { + git_difftree_body(\@difftree, $hash, $hash_parent); + print "<br/>\n"; - print $cgi->header(-type => "text/plain", -charset => 'utf-8', '-content-disposition' => "inline; filename=\"git-$hash.patch\""); - my %ad = date_str($co{'author_epoch'}, $co{'author_tz'}); - my $comment = $co{'comment'}; - print "From: $co{'author'}\n" . - "Date: $ad{'rfc2822'} ($ad{'tz_local'})\n". - "Subject: $co{'title'}\n"; - if (defined $tagname) { - print "X-Git-Tag: $tagname\n"; - } - print "X-Git-Url: $my_url?p=$project;a=commitdiff;h=$hash\n" . - "\n"; + git_patchset_body($fd, \@difftree, $hash, $hash_parent); + close $fd; + print "</div>\n"; # class="page_body" + git_footer_html(); - foreach my $line (@$comment) {; - print "$line\n"; + } elsif ($format eq 'plain') { + local $/ = undef; + print <$fd>; + close $fd + or print "Reading git-diff-tree failed\n"; } - print "---\n\n"; +} - foreach my $line (@difftree) { - if ($line !~ m/^:([0-7]{6}) ([0-7]{6}) ([0-9a-fA-F]{40}) ([0-9a-fA-F]{40}) (.)\t(.*)$/) { - next; - } - my $from_id = $3; - my $to_id = $4; - my $status = $5; - my $file = $6; - if ($status eq "A") { - git_diff_print(undef, "/dev/null", $to_id, "b/$file", "plain"); - } elsif ($status eq "D") { - git_diff_print($from_id, "a/$file", undef, "/dev/null", "plain"); - } elsif ($status eq "M") { - git_diff_print($from_id, "a/$file", $to_id, "b/$file", "plain"); - } - } +sub git_commitdiff_plain { + git_commitdiff('plain'); } sub git_history { if (!defined $hash_base) { - $hash_base = git_read_head($project); + $hash_base = git_get_head_hash($project); + } + if (!defined $page) { + $page = 0; } my $ftype; - my %co = git_read_commit($hash_base); + my %co = parse_commit($hash_base); if (!%co) { die_error(undef, "Unknown commit object"); } - my $refs = read_info_ref(); - git_header_html(); - git_page_nav('','', $hash_base,$co{'tree'},$hash_base); - git_header_div('commit', esc_html($co{'title'}), $hash_base); + + my $refs = git_get_references(); + my $limit = sprintf("--max-count=%i", (100 * ($page+1))); + if (!defined $hash && defined $file_name) { $hash = git_get_hash_by_path($hash_base, $file_name); } if (defined $hash) { $ftype = git_get_type($hash); } - git_print_page_path($file_name, $ftype); open my $fd, "-|", - $GIT, "rev-list", "--full-history", $hash_base, "--", $file_name; - print "<table cellspacing=\"0\">\n"; - my $alternate = 0; - while (my $line = <$fd>) { - if ($line =~ m/^([0-9a-fA-F]{40})/){ - my $commit = $1; - my %co = git_read_commit($commit); - if (!%co) { - next; - } - my $ref = git_get_referencing($refs, $commit); - if ($alternate) { - print "<tr class=\"dark\">\n"; - } else { - print "<tr class=\"light\">\n"; - } - $alternate ^= 1; - print "<td title=\"$co{'age_string_age'}\"><i>$co{'age_string_date'}</i></td>\n" . - "<td><i>" . esc_html(chop_str($co{'author_name'}, 15, 3)) . "</i></td>\n" . - "<td>" . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$commit"), -class => "list"}, "<b>" . - esc_html(chop_str($co{'title'}, 50)) . "$ref</b>") . "</td>\n" . - "<td class=\"link\">" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$commit")}, "commit") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$commit")}, "commitdiff") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$ftype;hb=$commit;f=$file_name")}, $ftype); - my $blob = git_get_hash_by_path($hash_base, $file_name); - my $blob_parent = git_get_hash_by_path($commit, $file_name); - if (defined $blob && defined $blob_parent && $blob ne $blob_parent) { - print " | " . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blobdiff;h=$blob;hp=$blob_parent;hb=$commit;f=$file_name")}, - "diff to current"); - } - print "</td>\n" . - "</tr>\n"; - } + git_cmd(), "rev-list", $limit, "--full-history", $hash_base, "--", $file_name + or die_error(undef, "Open git-rev-list-failed"); + my @revlist = map { chomp; $_ } <$fd>; + close $fd + or die_error(undef, "Reading git-rev-list failed"); + + my $paging_nav = ''; + if ($page > 0) { + $paging_nav .= + $cgi->a({-href => href(action=>"history", hash=>$hash, hash_base=>$hash_base, + file_name=>$file_name)}, + "first"); + $paging_nav .= " ⋅ " . + $cgi->a({-href => href(action=>"history", hash=>$hash, hash_base=>$hash_base, + file_name=>$file_name, page=>$page-1), + -accesskey => "p", -title => "Alt-p"}, "prev"); + } else { + $paging_nav .= "first"; + $paging_nav .= " ⋅ prev"; } - print "</table>\n"; - close $fd; + if ($#revlist >= (100 * ($page+1)-1)) { + $paging_nav .= " ⋅ " . + $cgi->a({-href => href(action=>"history", hash=>$hash, hash_base=>$hash_base, + file_name=>$file_name, page=>$page+1), + -accesskey => "n", -title => "Alt-n"}, "next"); + } else { + $paging_nav .= " ⋅ next"; + } + my $next_link = ''; + if ($#revlist >= (100 * ($page+1)-1)) { + $next_link = + $cgi->a({-href => href(action=>"history", hash=>$hash, hash_base=>$hash_base, + file_name=>$file_name, page=>$page+1), + -title => "Alt-n"}, "next"); + } + + git_header_html(); + git_print_page_nav('history','', $hash_base,$co{'tree'},$hash_base, $paging_nav); + git_print_header_div('commit', esc_html($co{'title'}), $hash_base); + git_print_page_path($file_name, $ftype, $hash_base); + + git_history_body(\@revlist, ($page * 100), $#revlist, + $refs, $hash_base, $ftype, $next_link); + git_footer_html(); } @@ -2330,14 +3275,13 @@ sub git_search { die_error(undef, "Text field empty"); } if (!defined $hash) { - $hash = git_read_head($project); + $hash = git_get_head_hash($project); } - my %co = git_read_commit($hash); + my %co = parse_commit($hash); if (!%co) { die_error(undef, "Unknown commit object"); } - # pickaxe may take all resources of your box and run for several minutes - # with every query - so decide by yourself how public you make this feature :) + my $commit_search = 1; my $author_search = 0; my $committer_search = 0; @@ -2349,16 +3293,23 @@ sub git_search { } elsif ($searchtext =~ s/^pickaxe\\://i) { $commit_search = 0; $pickaxe_search = 1; + + # pickaxe may take all resources of your box and run for several minutes + # with every query - so decide by yourself how public you make this feature + my ($have_pickaxe) = gitweb_check_feature('pickaxe'); + if (!$have_pickaxe) { + die_error('403 Permission denied', "Permission denied"); + } } git_header_html(); - git_page_nav('','', $hash,$co{'tree'},$hash); - git_header_div('commit', esc_html($co{'title'}), $hash); + git_print_page_nav('','', $hash,$co{'tree'},$hash); + git_print_header_div('commit', esc_html($co{'title'}), $hash); print "<table cellspacing=\"0\">\n"; my $alternate = 0; if ($commit_search) { $/ = "\0"; - open my $fd, "-|", $GIT, "rev-list", "--header", "--parents", $hash or next; + open my $fd, "-|", git_cmd(), "rev-list", "--header", "--parents", $hash or next; while (my $commit_text = <$fd>) { if (!grep m/$searchtext/i, $commit_text) { next; @@ -2370,7 +3321,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; } @@ -2383,7 +3334,8 @@ sub git_search { print "<td title=\"$co{'age_string_age'}\"><i>$co{'age_string_date'}</i></td>\n" . "<td><i>" . esc_html(chop_str($co{'author_name'}, 15, 5)) . "</i></td>\n" . "<td>" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$co{'id'}"), -class => "list"}, "<b>" . esc_html(chop_str($co{'title'}, 50)) . "</b><br/>"); + $cgi->a({-href => href(action=>"commit", hash=>$co{'id'}), -class => "list subject"}, + esc_html(chop_str($co{'title'}, 50)) . "<br/>"); my $comment = $co{'comment'}; foreach my $line (@$comment) { if ($line =~ m/^(.*)($searchtext)(.*)$/i) { @@ -2398,8 +3350,9 @@ sub git_search { } print "</td>\n" . "<td class=\"link\">" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$co{'id'}")}, "commit") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;h=$co{'tree'};hb=$co{'id'}")}, "tree"); + $cgi->a({-href => href(action=>"commit", hash=>$co{'id'})}, "commit") . + " | " . + $cgi->a({-href => href(action=>"tree", hash=>$co{'tree'}, hash_base=>$co{'id'})}, "tree"); print "</td>\n" . "</tr>\n"; } @@ -2408,7 +3361,9 @@ sub git_search { if ($pickaxe_search) { $/ = "\n"; - open my $fd, "-|", "$GIT rev-list $hash | $GIT diff-tree -r --stdin -S\'$searchtext\'"; + my $git_command = git_cmd_str(); + open my $fd, "-|", "$git_command rev-list $hash | " . + "$git_command diff-tree -r --stdin -S\'$searchtext\'"; undef %co; my @files; while (my $line = <$fd>) { @@ -2436,22 +3391,26 @@ sub git_search { print "<td title=\"$co{'age_string_age'}\"><i>$co{'age_string_date'}</i></td>\n" . "<td><i>" . esc_html(chop_str($co{'author_name'}, 15, 5)) . "</i></td>\n" . "<td>" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$co{'id'}"), -class => "list"}, "<b>" . - esc_html(chop_str($co{'title'}, 50)) . "</b><br/>"); + $cgi->a({-href => href(action=>"commit", hash=>$co{'id'}), + -class => "list subject"}, + esc_html(chop_str($co{'title'}, 50)) . "<br/>"); while (my $setref = shift @files) { my %set = %$setref; - print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$set{'id'};hb=$co{'id'};f=$set{'file'}"), class => "list"}, - "<span class=\"match\">" . esc_html($set{'file'}) . "</span>") . + print $cgi->a({-href => href(action=>"blob", hash_base=>$co{'id'}, + hash=>$set{'id'}, file_name=>$set{'file'}), + -class => "list"}, + "<span class=\"match\">" . esc_html($set{'file'}) . "</span>") . "<br/>\n"; } print "</td>\n" . "<td class=\"link\">" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$co{'id'}")}, "commit") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;h=$co{'tree'};hb=$co{'id'}")}, "tree"); + $cgi->a({-href => href(action=>"commit", hash=>$co{'id'})}, "commit") . + " | " . + $cgi->a({-href => href(action=>"tree", hash=>$co{'tree'}, hash_base=>$co{'id'})}, "tree"); print "</td>\n" . "</tr>\n"; } - %co = git_read_commit($1); + %co = parse_commit($1); } } close $fd; @@ -2461,33 +3420,33 @@ 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(); + my $refs = git_get_references(); my $limit = sprintf("--max-count=%i", (100 * ($page+1))); - open my $fd, "-|", $GIT, "rev-list", $limit, $hash + open my $fd, "-|", git_cmd(), "rev-list", $limit, $hash or die_error(undef, "Open git-rev-list failed"); my @revlist = map { chomp; $_ } <$fd>; close $fd; - my $paging_nav = git_get_paging_nav('shortlog', $hash, $head, $page, $#revlist); + my $paging_nav = format_paging_nav('shortlog', $hash, $head, $page, $#revlist); my $next_link = ''; if ($#revlist >= (100 * ($page+1)-1)) { $next_link = - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog;h=$hash;pg=" . ($page+1)), + $cgi->a({-href => href(action=>"shortlog", hash=>$hash, page=>$page+1), -title => "Alt-n"}, "next"); } git_header_html(); - git_page_nav('shortlog','', $hash,$hash,$hash, $paging_nav); - git_header_div('summary', $project); + git_print_page_nav('shortlog','', $hash,$hash,$hash, $paging_nav); + git_print_header_div('summary', $project); git_shortlog_body(\@revlist, ($page * 100), $#revlist, $refs, $next_link); @@ -2499,30 +3458,35 @@ sub git_shortlog { sub git_rss { # http://www.notestips.com/80256B3A007F2692/1/NAMO5P9UPQ - open my $fd, "-|", $GIT, "rev-list", "--max-count=150", git_read_head($project) + open my $fd, "-|", git_cmd(), "rev-list", "--max-count=150", git_get_head_hash($project) or die_error(undef, "Open git-rev-list failed"); my @revlist = map { chomp; $_ } <$fd>; close $fd or die_error(undef, "Reading git-rev-list failed"); print $cgi->header(-type => 'text/xml', -charset => 'utf-8'); - print "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n". - "<rss version=\"2.0\" xmlns:content=\"http://purl.org/rss/1.0/modules/content/\">\n"; - print "<channel>\n"; - print "<title>$project</title>\n". - "<link>" . esc_html("$my_url?p=$project;a=summary") . "</link>\n". - "<description>$project log</description>\n". - "<language>en</language>\n"; + print <<XML; +<?xml version="1.0" encoding="utf-8"?> +<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"> +<channel> +<title>$project $my_uri $my_url</title> +<link>${\esc_html("$my_url?p=$project;a=summary")}</link> +<description>$project log</description> +<language>en</language> +XML for (my $i = 0; $i <= $#revlist; $i++) { my $commit = $revlist[$i]; - my %co = git_read_commit($commit); + my %co = parse_commit($commit); # we read 150, we always show 30 and the ones more recent than 48 hours if (($i >= 20) && ((time - $co{'committer_epoch'}) > 48*60*60)) { last; } - my %cd = date_str($co{'committer_epoch'}); - open $fd, "-|", $GIT, "diff-tree", '-r', $co{'parent'}, $co{'id'} or next; + my %cd = parse_date($co{'committer_epoch'}); + open $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts, + $co{'parent'}, $co{'id'} + or next; my @difftree = map { chomp; $_ } <$fd>; - close $fd or next; + close $fd + or next; print "<item>\n" . "<title>" . sprintf("%d %s %02d:%02d", $cd{'mday'}, $cd{'month'}, $cd{'hour'}, $cd{'minute'}) . " - " . esc_html($co{'title'}) . @@ -2556,25 +3520,27 @@ sub git_rss { } sub git_opml { - my @list = git_read_projects(); + my @list = git_get_projects_list(); print $cgi->header(-type => 'text/xml', -charset => 'utf-8'); - print "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n". - "<opml version=\"1.0\">\n". - "<head>". - " <title>$site_name Git OPML Export</title>\n". - "</head>\n". - "<body>\n". - "<outline text=\"git RSS feeds\">\n"; + print <<XML; +<?xml version="1.0" encoding="utf-8"?> +<opml version="1.0"> +<head> + <title>$site_name Git OPML Export</title> +</head> +<body> +<outline text="git RSS feeds"> +XML foreach my $pr (@list) { my %proj = %$pr; - my $head = git_read_head($proj{'path'}); + my $head = git_get_head_hash($proj{'path'}); if (!defined $head) { next; } - $ENV{'GIT_DIR'} = "$projectroot/$proj{'path'}"; - my %co = git_read_commit($head); + $git_dir = "$projectroot/$proj{'path'}"; + my %co = parse_commit($head); if (!%co) { next; } @@ -2584,7 +3550,9 @@ sub git_opml { my $html = "$my_url?p=$proj{'path'};a=summary"; print "<outline type=\"rss\" text=\"$path\" title=\"$path\" xmlUrl=\"$rss\" htmlUrl=\"$html\"/>\n"; } - print "</outline>\n". - "</body>\n". - "</opml>\n"; + print <<XML; +</outline> +</body> +</opml> +XML } @@ -14,7 +14,7 @@ static int term_columns(void) { char *col_string = getenv("COLUMNS"); - int n_cols = 0; + int n_cols; if (col_string && (n_cols = atoi(col_string)) > 0) return n_cols; @@ -184,7 +184,7 @@ static void show_man_page(const char *git_cmd) page = git_cmd; else { int page_len = strlen(git_cmd) + 4; - char *p = malloc(page_len + 1); + char *p = xmalloc(page_len + 1); strcpy(p, "git-"); strcpy(p + 4, git_cmd); p[page_len] = 0; diff --git a/http-fetch.c b/http-fetch.c index de5fc44e66..a113bb8c4b 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; @@ -559,9 +559,36 @@ static void process_alternates_response(void *callback_data) char *target = NULL; char *path; if (data[i] == '/') { - serverlen = strchr(base + 8, '/') - base; - okay = 1; + /* This counts + * http://git.host/pub/scm/linux.git/ + * -----------here^ + * so memcpy(dst, base, serverlen) will + * copy up to "...git.host". + */ + const char *colon_ss = strstr(base,"://"); + if (colon_ss) { + serverlen = (strchr(colon_ss + 3, '/') + - base); + okay = 1; + } } else if (!memcmp(data + i, "../", 3)) { + /* Relative URL; chop the corresponding + * number of subpath from base (and ../ + * from data), and concatenate the result. + * + * The code first drops ../ from data, and + * then drops one ../ from data and one path + * from base. IOW, one extra ../ is dropped + * from data than path is dropped from base. + * + * This is not wrong. The alternate in + * http://git.host/pub/scm/linux.git/ + * to borrow from + * http://git.host/pub/scm/linus.git/ + * is ../../linus.git/objects/. You need + * two ../../ to borrow from your direct + * neighbour. + */ i += 3; serverlen = strlen(base); while (i + 2 < posn && @@ -583,11 +610,13 @@ static void process_alternates_response(void *callback_data) okay = 1; } } - /* skip 'objects' at end */ + /* skip "objects\n" at end */ if (okay) { target = xmalloc(serverlen + posn - i - 6); - strlcpy(target, base, serverlen); - strlcpy(target + serverlen, data + i, posn - i - 6); + memcpy(target, base, serverlen); + memcpy(target + serverlen, data + i, + posn - i - 7); + target[serverlen + posn - i - 7] = 0; if (get_verbosely) fprintf(stderr, "Also look at %s\n", target); @@ -696,10 +725,8 @@ xml_start_tag(void *userData, const char *name, const char **atts) strcat(ctx->name, "."); strcat(ctx->name, c); - if (ctx->cdata) { - free(ctx->cdata); - ctx->cdata = NULL; - } + free(ctx->cdata); + ctx->cdata = NULL; ctx->userFunc(ctx, 0); } @@ -726,8 +753,7 @@ static void xml_cdata(void *userData, const XML_Char *s, int len) { struct xml_ctx *ctx = (struct xml_ctx *)userData; - if (ctx->cdata) - free(ctx->cdata); + free(ctx->cdata); ctx->cdata = xmalloc(len + 1); strlcpy(ctx->cdata, s, len + 1); } @@ -765,9 +791,7 @@ static void handle_remote_ls_ctx(struct xml_ctx *ctx, int tag_closed) ls->dentry_flags |= IS_DIR; } } else if (!strcmp(ctx->name, DAV_PROPFIND_RESP)) { - if (ls->dentry_name) { - free(ls->dentry_name); - } + free(ls->dentry_name); ls->dentry_name = NULL; ls->dentry_flags = 0; } @@ -792,7 +816,7 @@ static int remote_ls(struct alt_base *repo, const char *path, int flags, ls.flags = flags; ls.repo = repo; - ls.path = strdup(path); + ls.path = xstrdup(path); ls.dentry_name = NULL; ls.dentry_flags = 0; ls.userData = userData; @@ -1070,7 +1094,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 +1133,7 @@ static int fetch_object(struct alt_base *repo, unsigned char *sha1) } else if (obj_req->zret != Z_STREAM_END) { corrupt_object_found++; ret = error("File %s (%s) corrupt", hex, obj_req->url); - } else if (memcmp(obj_req->sha1, obj_req->real_sha1, 20)) { + } else if (hashcmp(obj_req->sha1, obj_req->real_sha1)) { ret = error("File %s has bad hash", hex); } else if (obj_req->rename < 0) { ret = error("unable to write sha1 filename %s", diff --git a/http-push.c b/http-push.c index 0359ae5b6f..670ff007be 100644 --- a/http-push.c +++ b/http-push.c @@ -70,18 +70,18 @@ enum XML_Status { /* We allow "recursive" symbolic refs. Only within reason, though */ #define MAXDEPTH 5 -static int pushing = 0; -static int aborted = 0; +static int pushing; +static int aborted; static signed char remote_dir_exists[256]; static struct curl_slist *no_pragma_header; static struct curl_slist *default_headers; -static int push_verbosely = 0; -static int push_all = 0; -static int force_all = 0; +static int push_verbosely; +static int push_all; +static int force_all; -static struct object_list *objects = NULL; +static struct object_list *objects; struct repo { @@ -94,7 +94,7 @@ struct repo struct remote_lock *locks; }; -static struct repo *remote = NULL; +static struct repo *remote; enum transfer_state { NEED_FETCH, @@ -134,7 +134,7 @@ struct transfer_request struct transfer_request *next; }; -static struct transfer_request *request_queue_head = NULL; +static struct transfer_request *request_queue_head; struct xml_ctx { @@ -745,7 +745,7 @@ static void finish_request(struct transfer_request *request) SHA1_Final(request->real_sha1, &request->c); if (request->zret != Z_STREAM_END) { unlink(request->tmpfile); - } else if (memcmp(request->obj->sha1, request->real_sha1, 20)) { + } else if (hashcmp(request->obj->sha1, request->real_sha1)) { unlink(request->tmpfile); } else { request->rename = @@ -1238,10 +1238,8 @@ xml_start_tag(void *userData, const char *name, const char **atts) strcat(ctx->name, "."); strcat(ctx->name, c); - if (ctx->cdata) { - free(ctx->cdata); - ctx->cdata = NULL; - } + free(ctx->cdata); + ctx->cdata = NULL; ctx->userFunc(ctx, 0); } @@ -1268,8 +1266,7 @@ static void xml_cdata(void *userData, const XML_Char *s, int len) { struct xml_ctx *ctx = (struct xml_ctx *)userData; - if (ctx->cdata) - free(ctx->cdata); + free(ctx->cdata); ctx->cdata = xmalloc(len + 1); strlcpy(ctx->cdata, s, len + 1); } @@ -1518,9 +1515,7 @@ static void handle_remote_ls_ctx(struct xml_ctx *ctx, int tag_closed) ls->dentry_flags |= IS_DIR; } } else if (!strcmp(ctx->name, DAV_PROPFIND_RESP)) { - if (ls->dentry_name) { - free(ls->dentry_name); - } + free(ls->dentry_name); ls->dentry_name = NULL; ls->dentry_flags = 0; } @@ -1544,7 +1539,7 @@ static void remote_ls(const char *path, int flags, struct remote_ls_ctx ls; ls.flags = flags; - ls.path = strdup(path); + ls.path = xstrdup(path); ls.dentry_name = NULL; ls.dentry_flags = 0; ls.userData = userData; @@ -1700,7 +1695,7 @@ static int locking_available(void) return lock_flags; } -struct object_list **add_one_object(struct object *obj, struct object_list **p) +static struct object_list **add_one_object(struct object *obj, struct object_list **p) { struct object_list *entry = xmalloc(sizeof(struct object_list)); entry->item = obj; @@ -1743,7 +1738,7 @@ static struct object_list **process_tree(struct tree *tree, die("bad tree object %s", sha1_to_hex(obj->sha1)); obj->flags |= SEEN; - name = strdup(name); + name = xstrdup(name); p = add_one_object(obj, p); me.up = path; me.elem = name; @@ -1874,7 +1869,7 @@ static int one_local_ref(const char *refname, const unsigned char *sha1) struct ref *ref; int len = strlen(refname) + 1; ref = xcalloc(1, sizeof(*ref) + len); - memcpy(ref->new_sha1, sha1, 20); + hashcpy(ref->new_sha1, sha1); memcpy(ref->name, refname, len); *local_tail = ref; local_tail = &ref->next; @@ -1909,7 +1904,7 @@ static void one_remote_ref(char *refname) } ref = xcalloc(1, sizeof(*ref) + len); - memcpy(ref->old_sha1, remote_sha1, 20); + hashcpy(ref->old_sha1, remote_sha1); memcpy(ref->name, refname, len); *remote_tail = ref; remote_tail = &ref->next; @@ -2164,7 +2159,7 @@ static void fetch_symref(const char *path, char **symref, unsigned char *sha1) if (*symref != NULL) free(*symref); *symref = NULL; - memset(sha1, 0, 20); + hashclr(sha1); if (buffer.posn == 0) return; @@ -2186,10 +2181,7 @@ static int verify_merge_base(unsigned char *head_sha1, unsigned char *branch_sha struct commit *branch = lookup_commit(branch_sha1); struct commit_list *merge_bases = get_merge_bases(head, branch, 1); - if (merge_bases && !merge_bases->next && merge_bases->item == branch) - return 1; - - return 0; + return (merge_bases && !merge_bases->next && merge_bases->item == branch); } static int delete_remote_branch(char *pattern, int force) @@ -2419,7 +2411,7 @@ int main(int argc, char **argv) if (!ref->peer_ref) continue; - if (!memcmp(ref->old_sha1, ref->peer_ref->new_sha1, 20)) { + if (!hashcmp(ref->old_sha1, ref->peer_ref->new_sha1)) { if (push_verbosely || 1) fprintf(stderr, "'%s': up-to-date\n", ref->name); continue; @@ -2448,7 +2440,7 @@ int main(int argc, char **argv) continue; } } - memcpy(ref->new_sha1, ref->peer_ref->new_sha1, 20); + hashcpy(ref->new_sha1, ref->peer_ref->new_sha1); if (is_zero_sha1(ref->new_sha1)) { error("cannot happen anymore"); rc = -3; @@ -2475,7 +2467,7 @@ int main(int argc, char **argv) /* Set up revision info for this refspec */ commit_argc = 3; - new_sha1_hex = strdup(sha1_to_hex(ref->new_sha1)); + new_sha1_hex = xstrdup(sha1_to_hex(ref->new_sha1)); old_sha1_hex = NULL; commit_argv[1] = "--objects"; commit_argv[2] = new_sha1_hex; diff --git a/imap-send.c b/imap-send.c index 65c71c602d..362e474374 100644 --- a/imap-send.c +++ b/imap-send.c @@ -110,7 +110,6 @@ static char *next_arg( char ** ); static void free_generic_messages( message_t * ); -static int nfvasprintf( char **str, const char *fmt, va_list va ); static int nfsnprintf( char *buf, int blen, const char *fmt, ... ); @@ -372,21 +371,6 @@ free_generic_messages( message_t *msgs ) } static int -git_vasprintf( char **strp, const char *fmt, va_list ap ) -{ - int len; - char tmp[1024]; - - if ((len = vsnprintf( tmp, sizeof(tmp), fmt, ap )) < 0 || !(*strp = xmalloc( len + 1 ))) - return -1; - if (len >= (int)sizeof(tmp)) - vsprintf( *strp, fmt, ap ); - else - memcpy( *strp, tmp, len + 1 ); - return len; -} - -static int nfsnprintf( char *buf, int blen, const char *fmt, ... ) { int ret; @@ -399,15 +383,6 @@ nfsnprintf( char *buf, int blen, const char *fmt, ... ) return ret; } -static int -nfvasprintf( char **str, const char *fmt, va_list va ) -{ - int ret = git_vasprintf( str, fmt, va ); - if (ret < 0) - die( "Fatal: Out of memory\n"); - return ret; -} - static struct { unsigned char i, j, s[256]; } rs; @@ -1032,7 +1007,7 @@ imap_open_store( imap_server_conf_t *srvc ) * getpass() returns a pointer to a static buffer. make a copy * for long term storage. */ - srvc->pass = strdup( arg ); + srvc->pass = xstrdup( arg ); } if (CAP(NOLOGIN)) { fprintf( stderr, "Skipping account %s@%s, server forbids LOGIN\n", srvc->user, srvc->host ); @@ -1288,7 +1263,7 @@ git_imap_config(const char *key, const char *val) key += sizeof imap_key - 1; if (!strcmp( "folder", key )) { - imap_folder = strdup( val ); + imap_folder = xstrdup( val ); } else if (!strcmp( "host", key )) { { if (!strncmp( "imap:", val, 5 )) @@ -1298,16 +1273,16 @@ git_imap_config(const char *key, const char *val) } if (!strncmp( "//", val, 2 )) val += 2; - server.host = strdup( val ); + server.host = xstrdup( val ); } else if (!strcmp( "user", key )) - server.user = strdup( val ); + server.user = xstrdup( val ); else if (!strcmp( "pass", key )) - server.pass = strdup( val ); + server.pass = xstrdup( val ); else if (!strcmp( "port", key )) server.port = git_config_int( key, val ); else if (!strcmp( "tunnel", key )) - server.tunnel = strdup( val ); + server.tunnel = xstrdup( val ); return 0; } diff --git a/index-pack.c b/index-pack.c index b20659c259..80bc6cb45b 100644 --- a/index-pack.c +++ b/index-pack.c @@ -82,7 +82,7 @@ static void parse_pack_header(void) SHA1_Init(&ctx); SHA1_Update(&ctx, pack_base, pack_size - 20); SHA1_Final(sha1, &ctx); - if (memcmp(sha1, pack_base + pack_size - 20, 20)) + if (hashcmp(sha1, pack_base + pack_size - 20)) die("packfile '%s' SHA1 mismatch", pack_name); } @@ -161,7 +161,7 @@ static void *unpack_raw_entry(unsigned long offset, case OBJ_DELTA: if (pos + 20 >= pack_limit) bad_object(offset, "object extends past end of pack"); - memcpy(delta_base, pack_base + pos, 20); + hashcpy(delta_base, pack_base + pos); pos += 20; /* fallthru */ case OBJ_COMMIT: @@ -189,7 +189,7 @@ static int find_delta(const unsigned char *base_sha1) struct delta_entry *delta = &deltas[next]; int cmp; - cmp = memcmp(base_sha1, delta->base_sha1, 20); + cmp = hashcmp(base_sha1, delta->base_sha1); if (!cmp) return next; if (cmp < 0) { @@ -210,9 +210,9 @@ static int find_deltas_based_on_sha1(const unsigned char *base_sha1, if (first < 0) return -1; - while (first > 0 && !memcmp(deltas[first-1].base_sha1, base_sha1, 20)) + while (first > 0 && !hashcmp(deltas[first - 1].base_sha1, base_sha1)) --first; - while (last < end && !memcmp(deltas[last+1].base_sha1, base_sha1, 20)) + while (last < end && !hashcmp(deltas[last + 1].base_sha1, base_sha1)) ++last; *first_index = first; *last_index = last; @@ -278,7 +278,7 @@ static int compare_delta_entry(const void *a, const void *b) { const struct delta_entry *delta_a = a; const struct delta_entry *delta_b = b; - return memcmp(delta_a->base_sha1, delta_b->base_sha1, 20); + return hashcmp(delta_a->base_sha1, delta_b->base_sha1); } static void parse_pack_objects(void) @@ -304,7 +304,7 @@ static void parse_pack_objects(void) if (obj->type == OBJ_DELTA) { struct delta_entry *delta = &deltas[nr_deltas++]; delta->obj = obj; - memcpy(delta->base_sha1, base_sha1, 20); + hashcpy(delta->base_sha1, base_sha1); } else sha1_object(data, data_size, obj->type, obj->sha1); free(data); @@ -350,7 +350,7 @@ static int sha1_compare(const void *_a, const void *_b) { struct object_entry *a = *(struct object_entry **)_a; struct object_entry *b = *(struct object_entry **)_b; - return memcmp(a->sha1, b->sha1, 20); + return hashcmp(a->sha1, b->sha1); } static void write_index_file(const char *index_name, unsigned char *sha1) diff --git a/list-objects.c b/list-objects.c new file mode 100644 index 0000000000..f1fa21c397 --- /dev/null +++ b/list-objects.c @@ -0,0 +1,140 @@ +#include "cache.h" +#include "tag.h" +#include "commit.h" +#include "tree.h" +#include "blob.h" +#include "diff.h" +#include "tree-walk.h" +#include "revision.h" +#include "list-objects.h" + +static void process_blob(struct rev_info *revs, + struct blob *blob, + struct object_array *p, + struct name_path *path, + const char *name) +{ + struct object *obj = &blob->object; + + if (!revs->blob_objects) + return; + if (obj->flags & (UNINTERESTING | SEEN)) + return; + obj->flags |= SEEN; + name = xstrdup(name); + add_object(obj, p, path, name); +} + +static void process_tree(struct rev_info *revs, + struct tree *tree, + struct object_array *p, + struct name_path *path, + const char *name) +{ + struct object *obj = &tree->object; + struct tree_desc desc; + struct name_entry entry; + struct name_path me; + + if (!revs->tree_objects) + return; + if (obj->flags & (UNINTERESTING | SEEN)) + return; + if (parse_tree(tree) < 0) + die("bad tree object %s", sha1_to_hex(obj->sha1)); + obj->flags |= SEEN; + name = xstrdup(name); + add_object(obj, p, path, name); + me.up = path; + me.elem = name; + me.elem_len = strlen(name); + + desc.buf = tree->buffer; + desc.size = tree->size; + + while (tree_entry(&desc, &entry)) { + if (S_ISDIR(entry.mode)) + process_tree(revs, + lookup_tree(entry.sha1), + p, &me, entry.path); + else + process_blob(revs, + lookup_blob(entry.sha1), + p, &me, entry.path); + } + free(tree->buffer); + tree->buffer = NULL; +} + +static void mark_edge_parents_uninteresting(struct commit *commit, + struct rev_info *revs, + show_edge_fn show_edge) +{ + struct commit_list *parents; + + for (parents = commit->parents; parents; parents = parents->next) { + struct commit *parent = parents->item; + if (!(parent->object.flags & UNINTERESTING)) + continue; + mark_tree_uninteresting(parent->tree); + if (revs->edge_hint && !(parent->object.flags & SHOWN)) { + parent->object.flags |= SHOWN; + show_edge(parent); + } + } +} + +void mark_edges_uninteresting(struct commit_list *list, + struct rev_info *revs, + show_edge_fn show_edge) +{ + for ( ; list; list = list->next) { + struct commit *commit = list->item; + + if (commit->object.flags & UNINTERESTING) { + mark_tree_uninteresting(commit->tree); + continue; + } + mark_edge_parents_uninteresting(commit, revs, show_edge); + } +} + +void traverse_commit_list(struct rev_info *revs, + void (*show_commit)(struct commit *), + void (*show_object)(struct object_array_entry *)) +{ + int i; + struct commit *commit; + struct object_array objects = { 0, 0, NULL }; + + while ((commit = get_revision(revs)) != NULL) { + process_tree(revs, commit->tree, &objects, NULL, ""); + show_commit(commit); + } + for (i = 0; i < revs->pending.nr; i++) { + struct object_array_entry *pending = revs->pending.objects + i; + struct object *obj = pending->item; + const char *name = pending->name; + if (obj->flags & (UNINTERESTING | SEEN)) + continue; + if (obj->type == OBJ_TAG) { + obj->flags |= SEEN; + add_object_array(obj, name, &objects); + continue; + } + if (obj->type == OBJ_TREE) { + process_tree(revs, (struct tree *)obj, &objects, + NULL, name); + continue; + } + if (obj->type == OBJ_BLOB) { + process_blob(revs, (struct blob *)obj, &objects, + NULL, name); + continue; + } + die("unknown pending object %s (%s)", + sha1_to_hex(obj->sha1), name); + } + for (i = 0; i < objects.nr; i++) + show_object(&objects.objects[i]); +} diff --git a/list-objects.h b/list-objects.h new file mode 100644 index 0000000000..0f41391ecc --- /dev/null +++ b/list-objects.h @@ -0,0 +1,12 @@ +#ifndef LIST_OBJECTS_H +#define LIST_OBJECTS_H + +typedef void (*show_commit_fn)(struct commit *); +typedef void (*show_object_fn)(struct object_array_entry *); +typedef void (*show_edge_fn)(struct commit *); + +void traverse_commit_list(struct rev_info *revs, show_commit_fn, show_object_fn); + +void mark_edges_uninteresting(struct commit_list *, struct rev_info *, show_edge_fn); + +#endif diff --git a/local-fetch.c b/local-fetch.c index 7d01845d39..7b6875cce6 100644 --- a/local-fetch.c +++ b/local-fetch.c @@ -5,10 +5,10 @@ #include "commit.h" #include "fetch.h" -static int use_link = 0; -static int use_symlink = 0; +static int use_link; +static int use_symlink; static int use_filecopy = 1; -static int commits_on_stdin = 0; +static int commits_on_stdin; static const char *path; /* "Remote" git repository */ @@ -16,7 +16,7 @@ void prefetch(unsigned char *sha1) { } -static struct packed_git *packs = NULL; +static struct packed_git *packs; static void setup_index(unsigned char *sha1) { diff --git a/log-tree.c b/log-tree.c index 05ede0c175..fbe139920a 100644 --- a/log-tree.c +++ b/log-tree.c @@ -12,28 +12,86 @@ static void show_parents(struct commit *commit, int abbrev) } } +/* + * Search for "^[-A-Za-z]+: [^@]+@" pattern. It usually matches + * Signed-off-by: and Acked-by: lines. + */ +static int detect_any_signoff(char *letter, int size) +{ + char ch, *cp; + int seen_colon = 0; + int seen_at = 0; + int seen_name = 0; + int seen_head = 0; + + cp = letter + size; + while (letter <= --cp && (ch = *cp) == '\n') + continue; + + while (letter <= cp) { + ch = *cp--; + if (ch == '\n') + break; + + if (!seen_at) { + if (ch == '@') + seen_at = 1; + continue; + } + if (!seen_colon) { + if (ch == '@') + return 0; + else if (ch == ':') + seen_colon = 1; + else + seen_name = 1; + continue; + } + if (('A' <= ch && ch <= 'Z') || + ('a' <= ch && ch <= 'z') || + ch == '-') { + seen_head = 1; + continue; + } + /* no empty last line doesn't match */ + return 0; + } + return seen_head && seen_name; +} + static int append_signoff(char *buf, int buf_sz, int at, const char *signoff) { - int signoff_len = strlen(signoff); static const char signed_off_by[] = "Signed-off-by: "; + int signoff_len = strlen(signoff); + int has_signoff = 0; char *cp = buf; /* Do we have enough space to add it? */ - if (buf_sz - at <= strlen(signed_off_by) + signoff_len + 2) + if (buf_sz - at <= strlen(signed_off_by) + signoff_len + 3) return at; /* First see if we already have the sign-off by the signer */ - while (1) { - cp = strstr(cp, signed_off_by); - if (!cp) - break; + while ((cp = strstr(cp, signed_off_by))) { + + has_signoff = 1; + cp += strlen(signed_off_by); - if ((cp + signoff_len < buf + at) && - !strncmp(cp, signoff, signoff_len) && - isspace(cp[signoff_len])) - return at; /* we already have him */ + if (cp + signoff_len >= buf + at) + break; + if (strncmp(cp, signoff, signoff_len)) + continue; + if (!isspace(cp[signoff_len])) + continue; + /* we already have him */ + return at; } + if (!has_signoff) + has_signoff = detect_any_signoff(buf, at); + + if (!has_signoff) + buf[at++] = '\n'; + strcpy(buf + at, signed_off_by); at += strlen(signed_off_by); strcpy(buf + at, signoff); @@ -152,7 +210,9 @@ void show_log(struct rev_info *opt, const char *sep) /* * And then the pretty-printed message itself */ - len = pretty_print_commit(opt->commit_format, commit, ~0u, this_header, sizeof(this_header), abbrev, subject, extra_headers); + len = pretty_print_commit(opt->commit_format, commit, ~0u, this_header, + sizeof(this_header), abbrev, subject, + extra_headers, opt->relative_date); if (opt->add_signoff) len = append_signoff(this_header, sizeof(this_header), len, diff --git a/merge-base.c b/merge-base.c index 59f723f404..009caf804b 100644 --- a/merge-base.c +++ b/merge-base.c @@ -2,7 +2,7 @@ #include "cache.h" #include "commit.h" -static int show_all = 0; +static int show_all; static int merge_base(struct commit *rev1, struct commit *rev2) { diff --git a/merge-file.c b/merge-file.c index f32c653825..fc9b148993 100644 --- a/merge-file.c +++ b/merge-file.c @@ -21,7 +21,7 @@ static const char *write_temp_file(mmfile_t *f) fd = mkstemp(filename); if (fd < 0) return NULL; - filename = strdup(filename); + filename = xstrdup(filename); if (f->size != xwrite(fd, f->ptr, f->size)) { rm_temp_file(filename); return NULL; diff --git a/merge-index.c b/merge-index.c index 0498a6f45e..646d090c58 100644 --- a/merge-index.c +++ b/merge-index.c @@ -4,14 +4,15 @@ #include "cache.h" -static const char *pgm = NULL; +static const char *pgm; static const char *arguments[8]; static int one_shot, quiet; static int err; static void run_program(void) { - int pid = fork(), status; + pid_t pid = fork(); + int status; if (pid < 0) die("unable to fork"); diff --git a/merge-recursive.c b/merge-recursive.c new file mode 100644 index 0000000000..611cd95cf5 --- /dev/null +++ b/merge-recursive.c @@ -0,0 +1,1354 @@ +/* + * Recursive Merge algorithm stolen from git-merge-recursive.py by + * Fredrik Kuivinen. + * The thieves were Alex Riesen and Johannes Schindelin, in June/July 2006 + */ +#include <stdarg.h> +#include <string.h> +#include <assert.h> +#include <sys/wait.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <time.h> +#include "cache.h" +#include "cache-tree.h" +#include "commit.h" +#include "blob.h" +#include "tree-walk.h" +#include "diff.h" +#include "diffcore.h" +#include "run-command.h" +#include "tag.h" +#include "unpack-trees.h" +#include "path-list.h" + +/* + * A virtual commit has + * - (const char *)commit->util set to the name, and + * - *(int *)commit->object.sha1 set to the virtual id. + */ + +static unsigned commit_list_count(const struct commit_list *l) +{ + unsigned c = 0; + for (; l; l = l->next ) + c++; + return c; +} + +static struct commit *make_virtual_commit(struct tree *tree, const char *comment) +{ + struct commit *commit = xcalloc(1, sizeof(struct commit)); + static unsigned virtual_id = 1; + commit->tree = tree; + commit->util = (void*)comment; + *(int*)commit->object.sha1 = virtual_id++; + /* avoid warnings */ + commit->object.parsed = 1; + return commit; +} + +/* + * Since we use get_tree_entry(), which does not put the read object into + * the object pool, we cannot rely on a == b. + */ +static int sha_eq(const unsigned char *a, const unsigned char *b) +{ + if (!a && !b) + return 2; + return a && b && hashcmp(a, b) == 0; +} + +/* + * Since we want to write the index eventually, we cannot reuse the index + * for these (temporary) data. + */ +struct stage_data +{ + struct + { + unsigned mode; + unsigned char sha[20]; + } stages[4]; + unsigned processed:1; +}; + +static struct path_list current_file_set = {NULL, 0, 0, 1}; +static struct path_list current_directory_set = {NULL, 0, 0, 1}; + +static int output_indent = 0; + +static void output(const char *fmt, ...) +{ + va_list args; + int i; + for (i = output_indent; i--;) + fputs(" ", stdout); + va_start(args, fmt); + vfprintf(stdout, fmt, args); + va_end(args); + fputc('\n', stdout); +} + +static void output_commit_title(struct commit *commit) +{ + int i; + for (i = output_indent; i--;) + fputs(" ", stdout); + if (commit->util) + printf("virtual %s\n", (char *)commit->util); + else { + printf("%s ", sha1_to_hex(commit->object.sha1)); + if (parse_commit(commit) != 0) + printf("(bad commit)\n"); + else { + const char *s; + int len; + for (s = commit->buffer; *s; s++) + if (*s == '\n' && s[1] == '\n') { + s += 2; + break; + } + for (len = 0; s[len] && '\n' != s[len]; len++) + ; /* do nothing */ + printf("%.*s\n", len, s); + } + } +} + +static const char *current_index_file = NULL; +static const char *original_index_file; +static const char *temporary_index_file; +static int cache_dirty = 0; + +static int flush_cache(void) +{ + /* flush temporary index */ + struct lock_file *lock = xcalloc(1, sizeof(struct lock_file)); + int fd = hold_lock_file_for_update(lock, current_index_file, 1); + if (write_cache(fd, active_cache, active_nr) || + close(fd) || commit_lock_file(lock)) + die ("unable to write %s", current_index_file); + discard_cache(); + cache_dirty = 0; + return 0; +} + +static void setup_index(int temp) +{ + current_index_file = temp ? temporary_index_file: original_index_file; + if (cache_dirty) { + discard_cache(); + cache_dirty = 0; + } + unlink(temporary_index_file); + discard_cache(); +} + +static struct cache_entry *make_cache_entry(unsigned int mode, + const unsigned char *sha1, const char *path, int stage, int refresh) +{ + int size, len; + struct cache_entry *ce; + + if (!verify_path(path)) + return NULL; + + len = strlen(path); + size = cache_entry_size(len); + ce = xcalloc(1, size); + + hashcpy(ce->sha1, sha1); + memcpy(ce->name, path, len); + ce->ce_flags = create_ce_flags(len, stage); + ce->ce_mode = create_ce_mode(mode); + + if (refresh) + return refresh_cache_entry(ce, 0); + + return ce; +} + +static int add_cacheinfo(unsigned int mode, const unsigned char *sha1, + const char *path, int stage, int refresh, int options) +{ + struct cache_entry *ce; + if (!cache_dirty) + read_cache_from(current_index_file); + cache_dirty++; + ce = make_cache_entry(mode, sha1 ? sha1 : null_sha1, path, stage, refresh); + if (!ce) + return error("cache_addinfo failed: %s", strerror(cache_errno)); + return add_cache_entry(ce, options); +} + +/* + * This is a global variable which is used in a number of places but + * only written to in the 'merge' function. + * + * index_only == 1 => Don't leave any non-stage 0 entries in the cache and + * don't update the working directory. + * 0 => Leave unmerged entries in the cache and update + * the working directory. + */ +static int index_only = 0; + +static int git_read_tree(struct tree *tree) +{ + int rc; + struct object_list *trees = NULL; + struct unpack_trees_options opts; + + if (cache_dirty) + die("read-tree with dirty cache"); + + memset(&opts, 0, sizeof(opts)); + object_list_append(&tree->object, &trees); + rc = unpack_trees(trees, &opts); + cache_tree_free(&active_cache_tree); + + if (rc == 0) + cache_dirty = 1; + + return rc; +} + +static int git_merge_trees(int index_only, + struct tree *common, + struct tree *head, + struct tree *merge) +{ + int rc; + struct object_list *trees = NULL; + struct unpack_trees_options opts; + + if (!cache_dirty) { + read_cache_from(current_index_file); + cache_dirty = 1; + } + + memset(&opts, 0, sizeof(opts)); + if (index_only) + opts.index_only = 1; + else + opts.update = 1; + opts.merge = 1; + opts.head_idx = 2; + opts.fn = threeway_merge; + + object_list_append(&common->object, &trees); + object_list_append(&head->object, &trees); + object_list_append(&merge->object, &trees); + + rc = unpack_trees(trees, &opts); + cache_tree_free(&active_cache_tree); + + cache_dirty = 1; + + return rc; +} + +static struct tree *git_write_tree(void) +{ + struct tree *result = NULL; + + if (cache_dirty) { + unsigned i; + for (i = 0; i < active_nr; i++) { + struct cache_entry *ce = active_cache[i]; + if (ce_stage(ce)) + return NULL; + } + } else + read_cache_from(current_index_file); + + if (!active_cache_tree) + active_cache_tree = cache_tree(); + + if (!cache_tree_fully_valid(active_cache_tree) && + cache_tree_update(active_cache_tree, + active_cache, active_nr, 0, 0) < 0) + die("error building trees"); + + result = lookup_tree(active_cache_tree->sha1); + + flush_cache(); + cache_dirty = 0; + + return result; +} + +static int save_files_dirs(const unsigned char *sha1, + const char *base, int baselen, const char *path, + unsigned int mode, int stage) +{ + int len = strlen(path); + char *newpath = xmalloc(baselen + len + 1); + memcpy(newpath, base, baselen); + memcpy(newpath + baselen, path, len); + newpath[baselen + len] = '\0'; + + if (S_ISDIR(mode)) + path_list_insert(newpath, ¤t_directory_set); + else + path_list_insert(newpath, ¤t_file_set); + free(newpath); + + return READ_TREE_RECURSIVE; +} + +static int get_files_dirs(struct tree *tree) +{ + int n; + if (read_tree_recursive(tree, "", 0, 0, NULL, save_files_dirs) != 0) + return 0; + n = current_file_set.nr + current_directory_set.nr; + return n; +} + +/* + * Returns a index_entry instance which doesn't have to correspond to + * a real cache entry in Git's index. + */ +static struct stage_data *insert_stage_data(const char *path, + struct tree *o, struct tree *a, struct tree *b, + struct path_list *entries) +{ + struct path_list_item *item; + struct stage_data *e = xcalloc(1, sizeof(struct stage_data)); + get_tree_entry(o->object.sha1, path, + e->stages[1].sha, &e->stages[1].mode); + get_tree_entry(a->object.sha1, path, + e->stages[2].sha, &e->stages[2].mode); + get_tree_entry(b->object.sha1, path, + e->stages[3].sha, &e->stages[3].mode); + item = path_list_insert(path, entries); + item->util = e; + return e; +} + +/* + * Create a dictionary mapping file names to stage_data objects. The + * dictionary contains one entry for every path with a non-zero stage entry. + */ +static struct path_list *get_unmerged(void) +{ + struct path_list *unmerged = xcalloc(1, sizeof(struct path_list)); + int i; + + unmerged->strdup_paths = 1; + if (!cache_dirty) { + read_cache_from(current_index_file); + cache_dirty++; + } + for (i = 0; i < active_nr; i++) { + struct path_list_item *item; + struct stage_data *e; + struct cache_entry *ce = active_cache[i]; + if (!ce_stage(ce)) + continue; + + item = path_list_lookup(ce->name, unmerged); + if (!item) { + item = path_list_insert(ce->name, unmerged); + item->util = xcalloc(1, sizeof(struct stage_data)); + } + e = item->util; + e->stages[ce_stage(ce)].mode = ntohl(ce->ce_mode); + hashcpy(e->stages[ce_stage(ce)].sha, ce->sha1); + } + + return unmerged; +} + +struct rename +{ + struct diff_filepair *pair; + struct stage_data *src_entry; + struct stage_data *dst_entry; + unsigned processed:1; +}; + +/* + * Get information of all renames which occured between 'o_tree' and + * 'tree'. We need the three trees in the merge ('o_tree', 'a_tree' and + * 'b_tree') to be able to associate the correct cache entries with + * the rename information. 'tree' is always equal to either a_tree or b_tree. + */ +static struct path_list *get_renames(struct tree *tree, + struct tree *o_tree, + struct tree *a_tree, + struct tree *b_tree, + struct path_list *entries) +{ + int i; + struct path_list *renames; + struct diff_options opts; + + renames = xcalloc(1, sizeof(struct path_list)); + diff_setup(&opts); + opts.recursive = 1; + opts.detect_rename = DIFF_DETECT_RENAME; + opts.output_format = DIFF_FORMAT_NO_OUTPUT; + if (diff_setup_done(&opts) < 0) + die("diff setup failed"); + diff_tree_sha1(o_tree->object.sha1, tree->object.sha1, "", &opts); + diffcore_std(&opts); + for (i = 0; i < diff_queued_diff.nr; ++i) { + struct path_list_item *item; + struct rename *re; + struct diff_filepair *pair = diff_queued_diff.queue[i]; + if (pair->status != 'R') { + diff_free_filepair(pair); + continue; + } + re = xmalloc(sizeof(*re)); + re->processed = 0; + re->pair = pair; + item = path_list_lookup(re->pair->one->path, entries); + if (!item) + re->src_entry = insert_stage_data(re->pair->one->path, + o_tree, a_tree, b_tree, entries); + else + re->src_entry = item->util; + + item = path_list_lookup(re->pair->two->path, entries); + if (!item) + re->dst_entry = insert_stage_data(re->pair->two->path, + o_tree, a_tree, b_tree, entries); + else + re->dst_entry = item->util; + item = path_list_insert(pair->one->path, renames); + item->util = re; + } + opts.output_format = DIFF_FORMAT_NO_OUTPUT; + diff_queued_diff.nr = 0; + diff_flush(&opts); + return renames; +} + +int update_stages(const char *path, struct diff_filespec *o, + struct diff_filespec *a, struct diff_filespec *b, int clear) +{ + int options = ADD_CACHE_OK_TO_ADD | ADD_CACHE_OK_TO_REPLACE; + if (clear) + if (remove_file_from_cache(path)) + return -1; + if (o) + if (add_cacheinfo(o->mode, o->sha1, path, 1, 0, options)) + return -1; + if (a) + if (add_cacheinfo(a->mode, a->sha1, path, 2, 0, options)) + return -1; + if (b) + if (add_cacheinfo(b->mode, b->sha1, path, 3, 0, options)) + return -1; + return 0; +} + +static int remove_path(const char *name) +{ + int ret, len; + char *slash, *dirs; + + ret = unlink(name); + if (ret) + return ret; + len = strlen(name); + dirs = xmalloc(len+1); + memcpy(dirs, name, len); + dirs[len] = '\0'; + while ((slash = strrchr(name, '/'))) { + *slash = '\0'; + len = slash - name; + if (rmdir(name) != 0) + break; + } + free(dirs); + return ret; +} + +int remove_file(int clean, const char *path) +{ + int update_cache = index_only || clean; + int update_working_directory = !index_only; + + if (update_cache) { + if (!cache_dirty) + read_cache_from(current_index_file); + cache_dirty++; + if (remove_file_from_cache(path)) + return -1; + } + if (update_working_directory) + { + unlink(path); + if (errno != ENOENT || errno != EISDIR) + return -1; + remove_path(path); + } + return 0; +} + +static char *unique_path(const char *path, const char *branch) +{ + char *newpath = xmalloc(strlen(path) + 1 + strlen(branch) + 8 + 1); + int suffix = 0; + struct stat st; + char *p = newpath + strlen(path); + strcpy(newpath, path); + *(p++) = '~'; + strcpy(p, branch); + for (; *p; ++p) + if ('/' == *p) + *p = '_'; + while (path_list_has_path(¤t_file_set, newpath) || + path_list_has_path(¤t_directory_set, newpath) || + lstat(newpath, &st) == 0) + sprintf(p, "_%d", suffix++); + + path_list_insert(newpath, ¤t_file_set); + return newpath; +} + +static int mkdir_p(const char *path, unsigned long mode) +{ + /* path points to cache entries, so xstrdup before messing with it */ + char *buf = xstrdup(path); + int result = safe_create_leading_directories(buf); + free(buf); + return result; +} + +static void flush_buffer(int fd, const char *buf, unsigned long size) +{ + while (size > 0) { + long ret = xwrite(fd, buf, size); + if (ret < 0) { + /* Ignore epipe */ + if (errno == EPIPE) + break; + die("merge-recursive: %s", strerror(errno)); + } else if (!ret) { + die("merge-recursive: disk full?"); + } + size -= ret; + buf += ret; + } +} + +void update_file_flags(const unsigned char *sha, + unsigned mode, + const char *path, + int update_cache, + int update_wd) +{ + if (index_only) + update_wd = 0; + + if (update_wd) { + char type[20]; + void *buf; + unsigned long size; + + buf = read_sha1_file(sha, type, &size); + if (!buf) + die("cannot read object %s '%s'", sha1_to_hex(sha), path); + if (strcmp(type, blob_type) != 0) + die("blob expected for %s '%s'", sha1_to_hex(sha), path); + + if (S_ISREG(mode)) { + int fd; + if (mkdir_p(path, 0777)) + die("failed to create path %s: %s", path, strerror(errno)); + unlink(path); + if (mode & 0100) + mode = 0777; + else + mode = 0666; + fd = open(path, O_WRONLY | O_TRUNC | O_CREAT, mode); + if (fd < 0) + die("failed to open %s: %s", path, strerror(errno)); + flush_buffer(fd, buf, size); + close(fd); + } else if (S_ISLNK(mode)) { + char *lnk = xmalloc(size + 1); + memcpy(lnk, buf, size); + lnk[size] = '\0'; + mkdir_p(path, 0777); + unlink(lnk); + symlink(lnk, path); + } else + die("do not know what to do with %06o %s '%s'", + mode, sha1_to_hex(sha), path); + } + if (update_cache) + add_cacheinfo(mode, sha, path, 0, update_wd, ADD_CACHE_OK_TO_ADD); +} + +void update_file(int clean, + const unsigned char *sha, + unsigned mode, + const char *path) +{ + update_file_flags(sha, mode, path, index_only || clean, !index_only); +} + +/* Low level file merging, update and removal */ + +struct merge_file_info +{ + unsigned char sha[20]; + unsigned mode; + unsigned clean:1, + merge:1; +}; + +static char *git_unpack_file(const unsigned char *sha1, char *path) +{ + void *buf; + char type[20]; + unsigned long size; + int fd; + + buf = read_sha1_file(sha1, type, &size); + if (!buf || strcmp(type, blob_type)) + die("unable to read blob object %s", sha1_to_hex(sha1)); + + strcpy(path, ".merge_file_XXXXXX"); + fd = mkstemp(path); + if (fd < 0) + die("unable to create temp-file"); + flush_buffer(fd, buf, size); + close(fd); + return path; +} + +static struct merge_file_info merge_file(struct diff_filespec *o, + struct diff_filespec *a, struct diff_filespec *b, + const char *branch1, const char *branch2) +{ + struct merge_file_info result; + result.merge = 0; + result.clean = 1; + + if ((S_IFMT & a->mode) != (S_IFMT & b->mode)) { + result.clean = 0; + if (S_ISREG(a->mode)) { + result.mode = a->mode; + hashcpy(result.sha, a->sha1); + } else { + result.mode = b->mode; + hashcpy(result.sha, b->sha1); + } + } else { + if (!sha_eq(a->sha1, o->sha1) && !sha_eq(b->sha1, o->sha1)) + result.merge = 1; + + result.mode = a->mode == o->mode ? b->mode: a->mode; + + if (sha_eq(a->sha1, o->sha1)) + hashcpy(result.sha, b->sha1); + else if (sha_eq(b->sha1, o->sha1)) + hashcpy(result.sha, a->sha1); + else if (S_ISREG(a->mode)) { + int code = 1, fd; + struct stat st; + char orig[PATH_MAX]; + char src1[PATH_MAX]; + char src2[PATH_MAX]; + const char *argv[] = { + "merge", "-L", NULL, "-L", NULL, "-L", NULL, + NULL, NULL, NULL, + NULL + }; + char *la, *lb, *lo; + + git_unpack_file(o->sha1, orig); + git_unpack_file(a->sha1, src1); + git_unpack_file(b->sha1, src2); + + argv[2] = la = xstrdup(mkpath("%s/%s", branch1, a->path)); + argv[6] = lb = xstrdup(mkpath("%s/%s", branch2, b->path)); + argv[4] = lo = xstrdup(mkpath("orig/%s", o->path)); + argv[7] = src1; + argv[8] = orig; + argv[9] = src2, + + code = run_command_v(10, argv); + + free(la); + free(lb); + free(lo); + if (code && code < -256) { + die("Failed to execute 'merge'. merge(1) is used as the " + "file-level merge tool. Is 'merge' in your path?"); + } + fd = open(src1, O_RDONLY); + if (fd < 0 || fstat(fd, &st) < 0 || + index_fd(result.sha, fd, &st, 1, + "blob")) + die("Unable to add %s to database", src1); + + unlink(orig); + unlink(src1); + unlink(src2); + + result.clean = WEXITSTATUS(code) == 0; + } else { + if (!(S_ISLNK(a->mode) || S_ISLNK(b->mode))) + die("cannot merge modes?"); + + hashcpy(result.sha, a->sha1); + + if (!sha_eq(a->sha1, b->sha1)) + result.clean = 0; + } + } + + return result; +} + +static void conflict_rename_rename(struct rename *ren1, + const char *branch1, + struct rename *ren2, + const char *branch2) +{ + char *del[2]; + int delp = 0; + const char *ren1_dst = ren1->pair->two->path; + const char *ren2_dst = ren2->pair->two->path; + const char *dst_name1 = ren1_dst; + const char *dst_name2 = ren2_dst; + if (path_list_has_path(¤t_directory_set, ren1_dst)) { + dst_name1 = del[delp++] = unique_path(ren1_dst, branch1); + output("%s is a directory in %s adding as %s instead", + ren1_dst, branch2, dst_name1); + remove_file(0, ren1_dst); + } + if (path_list_has_path(¤t_directory_set, ren2_dst)) { + dst_name2 = del[delp++] = unique_path(ren2_dst, branch2); + output("%s is a directory in %s adding as %s instead", + ren2_dst, branch1, dst_name2); + remove_file(0, ren2_dst); + } + update_stages(dst_name1, NULL, ren1->pair->two, NULL, 1); + update_stages(dst_name2, NULL, NULL, ren2->pair->two, 1); + while (delp--) + free(del[delp]); +} + +static void conflict_rename_dir(struct rename *ren1, + const char *branch1) +{ + char *new_path = unique_path(ren1->pair->two->path, branch1); + output("Renaming %s to %s instead", ren1->pair->one->path, new_path); + remove_file(0, ren1->pair->two->path); + update_file(0, ren1->pair->two->sha1, ren1->pair->two->mode, new_path); + free(new_path); +} + +static void conflict_rename_rename_2(struct rename *ren1, + const char *branch1, + struct rename *ren2, + const char *branch2) +{ + char *new_path1 = unique_path(ren1->pair->two->path, branch1); + char *new_path2 = unique_path(ren2->pair->two->path, branch2); + output("Renaming %s to %s and %s to %s instead", + ren1->pair->one->path, new_path1, + ren2->pair->one->path, new_path2); + remove_file(0, ren1->pair->two->path); + update_file(0, ren1->pair->two->sha1, ren1->pair->two->mode, new_path1); + update_file(0, ren2->pair->two->sha1, ren2->pair->two->mode, new_path2); + free(new_path2); + free(new_path1); +} + +static int process_renames(struct path_list *a_renames, + struct path_list *b_renames, + const char *a_branch, + const char *b_branch) +{ + int clean_merge = 1, i, j; + struct path_list a_by_dst = {NULL, 0, 0, 0}, b_by_dst = {NULL, 0, 0, 0}; + const struct rename *sre; + + for (i = 0; i < a_renames->nr; i++) { + sre = a_renames->items[i].util; + path_list_insert(sre->pair->two->path, &a_by_dst)->util + = sre->dst_entry; + } + for (i = 0; i < b_renames->nr; i++) { + sre = b_renames->items[i].util; + path_list_insert(sre->pair->two->path, &b_by_dst)->util + = sre->dst_entry; + } + + for (i = 0, j = 0; i < a_renames->nr || j < b_renames->nr;) { + int compare; + char *src; + struct path_list *renames1, *renames2, *renames2Dst; + struct rename *ren1 = NULL, *ren2 = NULL; + const char *branch1, *branch2; + const char *ren1_src, *ren1_dst; + + if (i >= a_renames->nr) { + compare = 1; + ren2 = b_renames->items[j++].util; + } else if (j >= b_renames->nr) { + compare = -1; + ren1 = a_renames->items[i++].util; + } else { + compare = strcmp(a_renames->items[i].path, + b_renames->items[j].path); + if (compare <= 0) + ren1 = a_renames->items[i++].util; + if (compare >= 0) + ren2 = b_renames->items[j++].util; + } + + /* TODO: refactor, so that 1/2 are not needed */ + if (ren1) { + renames1 = a_renames; + renames2 = b_renames; + renames2Dst = &b_by_dst; + branch1 = a_branch; + branch2 = b_branch; + } else { + struct rename *tmp; + renames1 = b_renames; + renames2 = a_renames; + renames2Dst = &a_by_dst; + branch1 = b_branch; + branch2 = a_branch; + tmp = ren2; + ren2 = ren1; + ren1 = tmp; + } + src = ren1->pair->one->path; + + ren1->dst_entry->processed = 1; + ren1->src_entry->processed = 1; + + if (ren1->processed) + continue; + ren1->processed = 1; + + ren1_src = ren1->pair->one->path; + ren1_dst = ren1->pair->two->path; + + if (ren2) { + const char *ren2_src = ren2->pair->one->path; + const char *ren2_dst = ren2->pair->two->path; + /* Renamed in 1 and renamed in 2 */ + if (strcmp(ren1_src, ren2_src) != 0) + die("ren1.src != ren2.src"); + ren2->dst_entry->processed = 1; + ren2->processed = 1; + if (strcmp(ren1_dst, ren2_dst) != 0) { + clean_merge = 0; + output("CONFLICT (rename/rename): " + "Rename %s->%s in branch %s " + "rename %s->%s in %s", + src, ren1_dst, branch1, + src, ren2_dst, branch2); + conflict_rename_rename(ren1, branch1, ren2, branch2); + } else { + struct merge_file_info mfi; + remove_file(1, ren1_src); + mfi = merge_file(ren1->pair->one, + ren1->pair->two, + ren2->pair->two, + branch1, + branch2); + if (mfi.merge || !mfi.clean) + output("Renaming %s->%s", src, ren1_dst); + + if (mfi.merge) + output("Auto-merging %s", ren1_dst); + + if (!mfi.clean) { + output("CONFLICT (content): merge conflict in %s", + ren1_dst); + clean_merge = 0; + + if (!index_only) + update_stages(ren1_dst, + ren1->pair->one, + ren1->pair->two, + ren2->pair->two, + 1 /* clear */); + } + update_file(mfi.clean, mfi.sha, mfi.mode, ren1_dst); + } + } else { + /* Renamed in 1, maybe changed in 2 */ + struct path_list_item *item; + /* we only use sha1 and mode of these */ + struct diff_filespec src_other, dst_other; + int try_merge, stage = a_renames == renames1 ? 3: 2; + + remove_file(1, ren1_src); + + hashcpy(src_other.sha1, ren1->src_entry->stages[stage].sha); + src_other.mode = ren1->src_entry->stages[stage].mode; + hashcpy(dst_other.sha1, ren1->dst_entry->stages[stage].sha); + dst_other.mode = ren1->dst_entry->stages[stage].mode; + + try_merge = 0; + + if (path_list_has_path(¤t_directory_set, ren1_dst)) { + clean_merge = 0; + output("CONFLICT (rename/directory): Rename %s->%s in %s " + " directory %s added in %s", + ren1_src, ren1_dst, branch1, + ren1_dst, branch2); + conflict_rename_dir(ren1, branch1); + } else if (sha_eq(src_other.sha1, null_sha1)) { + clean_merge = 0; + output("CONFLICT (rename/delete): Rename %s->%s in %s " + "and deleted in %s", + ren1_src, ren1_dst, branch1, + branch2); + update_file(0, ren1->pair->two->sha1, ren1->pair->two->mode, ren1_dst); + } else if (!sha_eq(dst_other.sha1, null_sha1)) { + const char *new_path; + clean_merge = 0; + try_merge = 1; + output("CONFLICT (rename/add): Rename %s->%s in %s. " + "%s added in %s", + ren1_src, ren1_dst, branch1, + ren1_dst, branch2); + new_path = unique_path(ren1_dst, branch2); + output("Adding as %s instead", new_path); + update_file(0, dst_other.sha1, dst_other.mode, new_path); + } else if ((item = path_list_lookup(ren1_dst, renames2Dst))) { + ren2 = item->util; + clean_merge = 0; + ren2->processed = 1; + output("CONFLICT (rename/rename): Rename %s->%s in %s. " + "Rename %s->%s in %s", + ren1_src, ren1_dst, branch1, + ren2->pair->one->path, ren2->pair->two->path, branch2); + conflict_rename_rename_2(ren1, branch1, ren2, branch2); + } else + try_merge = 1; + + if (try_merge) { + struct diff_filespec *o, *a, *b; + struct merge_file_info mfi; + src_other.path = (char *)ren1_src; + + o = ren1->pair->one; + if (a_renames == renames1) { + a = ren1->pair->two; + b = &src_other; + } else { + b = ren1->pair->two; + a = &src_other; + } + mfi = merge_file(o, a, b, + a_branch, b_branch); + + if (mfi.merge || !mfi.clean) + output("Renaming %s => %s", ren1_src, ren1_dst); + if (mfi.merge) + output("Auto-merging %s", ren1_dst); + if (!mfi.clean) { + output("CONFLICT (rename/modify): Merge conflict in %s", + ren1_dst); + clean_merge = 0; + + if (!index_only) + update_stages(ren1_dst, + o, a, b, 1); + } + update_file(mfi.clean, mfi.sha, mfi.mode, ren1_dst); + } + } + } + path_list_clear(&a_by_dst, 0); + path_list_clear(&b_by_dst, 0); + + if (cache_dirty) + flush_cache(); + return clean_merge; +} + +static unsigned char *has_sha(const unsigned char *sha) +{ + return is_null_sha1(sha) ? NULL: (unsigned char *)sha; +} + +/* Per entry merge function */ +static int process_entry(const char *path, struct stage_data *entry, + const char *branch1, + const char *branch2) +{ + /* + printf("processing entry, clean cache: %s\n", index_only ? "yes": "no"); + print_index_entry("\tpath: ", entry); + */ + int clean_merge = 1; + unsigned char *o_sha = has_sha(entry->stages[1].sha); + unsigned char *a_sha = has_sha(entry->stages[2].sha); + unsigned char *b_sha = has_sha(entry->stages[3].sha); + unsigned o_mode = entry->stages[1].mode; + unsigned a_mode = entry->stages[2].mode; + unsigned b_mode = entry->stages[3].mode; + + if (o_sha && (!a_sha || !b_sha)) { + /* Case A: Deleted in one */ + if ((!a_sha && !b_sha) || + (sha_eq(a_sha, o_sha) && !b_sha) || + (!a_sha && sha_eq(b_sha, o_sha))) { + /* Deleted in both or deleted in one and + * unchanged in the other */ + if (a_sha) + output("Removing %s", path); + remove_file(1, path); + } else { + /* Deleted in one and changed in the other */ + clean_merge = 0; + if (!a_sha) { + output("CONFLICT (delete/modify): %s deleted in %s " + "and modified in %s. Version %s of %s left in tree.", + path, branch1, + branch2, branch2, path); + update_file(0, b_sha, b_mode, path); + } else { + output("CONFLICT (delete/modify): %s deleted in %s " + "and modified in %s. Version %s of %s left in tree.", + path, branch2, + branch1, branch1, path); + update_file(0, a_sha, a_mode, path); + } + } + + } else if ((!o_sha && a_sha && !b_sha) || + (!o_sha && !a_sha && b_sha)) { + /* Case B: Added in one. */ + const char *add_branch; + const char *other_branch; + unsigned mode; + const unsigned char *sha; + const char *conf; + + if (a_sha) { + add_branch = branch1; + other_branch = branch2; + mode = a_mode; + sha = a_sha; + conf = "file/directory"; + } else { + add_branch = branch2; + other_branch = branch1; + mode = b_mode; + sha = b_sha; + conf = "directory/file"; + } + if (path_list_has_path(¤t_directory_set, path)) { + const char *new_path = unique_path(path, add_branch); + clean_merge = 0; + output("CONFLICT (%s): There is a directory with name %s in %s. " + "Adding %s as %s", + conf, path, other_branch, path, new_path); + remove_file(0, path); + update_file(0, sha, mode, new_path); + } else { + output("Adding %s", path); + update_file(1, sha, mode, path); + } + } else if (!o_sha && a_sha && b_sha) { + /* Case C: Added in both (check for same permissions). */ + if (sha_eq(a_sha, b_sha)) { + if (a_mode != b_mode) { + clean_merge = 0; + output("CONFLICT: File %s added identically in both branches, " + "but permissions conflict %06o->%06o", + path, a_mode, b_mode); + output("CONFLICT: adding with permission: %06o", a_mode); + update_file(0, a_sha, a_mode, path); + } else { + /* This case is handled by git-read-tree */ + assert(0 && "This case must be handled by git-read-tree"); + } + } else { + const char *new_path1, *new_path2; + clean_merge = 0; + new_path1 = unique_path(path, branch1); + new_path2 = unique_path(path, branch2); + output("CONFLICT (add/add): File %s added non-identically " + "in both branches. Adding as %s and %s instead.", + path, new_path1, new_path2); + remove_file(0, path); + update_file(0, a_sha, a_mode, new_path1); + update_file(0, b_sha, b_mode, new_path2); + } + + } else if (o_sha && a_sha && b_sha) { + /* case D: Modified in both, but differently. */ + struct merge_file_info mfi; + struct diff_filespec o, a, b; + + output("Auto-merging %s", path); + o.path = a.path = b.path = (char *)path; + hashcpy(o.sha1, o_sha); + o.mode = o_mode; + hashcpy(a.sha1, a_sha); + a.mode = a_mode; + hashcpy(b.sha1, b_sha); + b.mode = b_mode; + + mfi = merge_file(&o, &a, &b, + branch1, branch2); + + if (mfi.clean) + update_file(1, mfi.sha, mfi.mode, path); + else { + clean_merge = 0; + output("CONFLICT (content): Merge conflict in %s", path); + + if (index_only) + update_file(0, mfi.sha, mfi.mode, path); + else + update_file_flags(mfi.sha, mfi.mode, path, + 0 /* update_cache */, 1 /* update_working_directory */); + } + } else + die("Fatal merge failure, shouldn't happen."); + + if (cache_dirty) + flush_cache(); + + return clean_merge; +} + +static int merge_trees(struct tree *head, + struct tree *merge, + struct tree *common, + const char *branch1, + const char *branch2, + struct tree **result) +{ + int code, clean; + if (sha_eq(common->object.sha1, merge->object.sha1)) { + output("Already uptodate!"); + *result = head; + return 1; + } + + code = git_merge_trees(index_only, common, head, merge); + + if (code != 0) + die("merging of trees %s and %s failed", + sha1_to_hex(head->object.sha1), + sha1_to_hex(merge->object.sha1)); + + *result = git_write_tree(); + + if (!*result) { + struct path_list *entries, *re_head, *re_merge; + int i; + path_list_clear(¤t_file_set, 1); + path_list_clear(¤t_directory_set, 1); + get_files_dirs(head); + get_files_dirs(merge); + + entries = get_unmerged(); + re_head = get_renames(head, common, head, merge, entries); + re_merge = get_renames(merge, common, head, merge, entries); + clean = process_renames(re_head, re_merge, + branch1, branch2); + for (i = 0; i < entries->nr; i++) { + const char *path = entries->items[i].path; + struct stage_data *e = entries->items[i].util; + if (e->processed) + continue; + if (!process_entry(path, e, branch1, branch2)) + clean = 0; + } + + path_list_clear(re_merge, 0); + path_list_clear(re_head, 0); + path_list_clear(entries, 1); + + if (clean || index_only) + *result = git_write_tree(); + else + *result = NULL; + } else { + clean = 1; + printf("merging of trees %s and %s resulted in %s\n", + sha1_to_hex(head->object.sha1), + sha1_to_hex(merge->object.sha1), + sha1_to_hex((*result)->object.sha1)); + } + + return clean; +} + +static struct commit_list *reverse_commit_list(struct commit_list *list) +{ + struct commit_list *next = NULL, *current, *backup; + for (current = list; current; current = backup) { + backup = current->next; + current->next = next; + next = current; + } + return next; +} + +/* + * Merge the commits h1 and h2, return the resulting virtual + * commit object and a flag indicating the cleaness of the merge. + */ +static +int merge(struct commit *h1, + struct commit *h2, + const char *branch1, + const char *branch2, + int call_depth /* =0 */, + struct commit *ancestor /* =None */, + struct commit **result) +{ + struct commit_list *ca = NULL, *iter; + struct commit *merged_common_ancestors; + struct tree *mrtree; + int clean; + + output("Merging:"); + output_commit_title(h1); + output_commit_title(h2); + + if (ancestor) + commit_list_insert(ancestor, &ca); + else + ca = reverse_commit_list(get_merge_bases(h1, h2, 1)); + + output("found %u common ancestor(s):", commit_list_count(ca)); + for (iter = ca; iter; iter = iter->next) + output_commit_title(iter->item); + + merged_common_ancestors = pop_commit(&ca); + if (merged_common_ancestors == NULL) { + /* if there is no common ancestor, make an empty tree */ + struct tree *tree = xcalloc(1, sizeof(struct tree)); + unsigned char hdr[40]; + int hdrlen; + + tree->object.parsed = 1; + tree->object.type = OBJ_TREE; + write_sha1_file_prepare(NULL, 0, tree_type, tree->object.sha1, + hdr, &hdrlen); + merged_common_ancestors = make_virtual_commit(tree, "ancestor"); + } + + for (iter = ca; iter; iter = iter->next) { + output_indent = call_depth + 1; + /* + * When the merge fails, the result contains files + * with conflict markers. The cleanness flag is + * ignored, it was never acutally used, as result of + * merge_trees has always overwritten it: the commited + * "conflicts" were already resolved. + */ + merge(merged_common_ancestors, iter->item, + "Temporary merge branch 1", + "Temporary merge branch 2", + call_depth + 1, + NULL, + &merged_common_ancestors); + output_indent = call_depth; + + if (!merged_common_ancestors) + die("merge returned no commit"); + } + + if (call_depth == 0) { + setup_index(0 /* $GIT_DIR/index */); + index_only = 0; + } else { + setup_index(1 /* temporary index */); + git_read_tree(h1->tree); + index_only = 1; + } + + clean = merge_trees(h1->tree, h2->tree, merged_common_ancestors->tree, + branch1, branch2, &mrtree); + + if (!ancestor && (clean || index_only)) { + *result = make_virtual_commit(mrtree, "merged tree"); + commit_list_insert(h1, &(*result)->parents); + commit_list_insert(h2, &(*result)->parents->next); + } else + *result = NULL; + + return clean; +} + +static struct commit *get_ref(const char *ref) +{ + unsigned char sha1[20]; + struct object *object; + + if (get_sha1(ref, sha1)) + die("Could not resolve ref '%s'", ref); + object = deref_tag(parse_object(sha1), ref, strlen(ref)); + if (object->type != OBJ_COMMIT) + return NULL; + if (parse_commit((struct commit *)object)) + die("Could not parse commit '%s'", sha1_to_hex(object->sha1)); + return (struct commit *)object; +} + +int main(int argc, char *argv[]) +{ + static const char *bases[2]; + static unsigned bases_count = 0; + int i, clean; + const char *branch1, *branch2; + struct commit *result, *h1, *h2; + + original_index_file = getenv("GIT_INDEX_FILE"); + + if (!original_index_file) + original_index_file = xstrdup(git_path("index")); + + temporary_index_file = xstrdup(git_path("mrg-rcrsv-tmp-idx")); + + if (argc < 4) + die("Usage: %s <base>... -- <head> <remote> ...\n", argv[0]); + + for (i = 1; i < argc; ++i) { + if (!strcmp(argv[i], "--")) + break; + if (bases_count < sizeof(bases)/sizeof(*bases)) + bases[bases_count++] = argv[i]; + } + if (argc - i != 3) /* "--" "<head>" "<remote>" */ + die("Not handling anything other than two heads merge."); + + branch1 = argv[++i]; + branch2 = argv[++i]; + printf("Merging %s with %s\n", branch1, branch2); + + h1 = get_ref(branch1); + h2 = get_ref(branch2); + + if (bases_count == 1) { + struct commit *ancestor = get_ref(bases[0]); + clean = merge(h1, h2, branch1, branch2, 0, ancestor, &result); + } else + clean = merge(h1, h2, branch1, branch2, 0, NULL, &result); + + if (cache_dirty) + flush_cache(); + + return clean ? 0: 1; +} + +/* +vim: sw=8 noet +*/ diff --git a/merge-tree.c b/merge-tree.c index 7cf00be6d5..692ede0e3d 100644 --- a/merge-tree.c +++ b/merge-tree.c @@ -152,7 +152,7 @@ static int same_entry(struct name_entry *a, struct name_entry *b) { return a->sha1 && b->sha1 && - !memcmp(a->sha1, b->sha1, 20) && + !hashcmp(a->sha1, b->sha1) && a->mode == b->mode; } @@ -177,7 +177,7 @@ static void resolve(const char *base, struct name_entry *branch1, struct name_en if (!branch1) return; - path = strdup(mkpath("%s%s", base, result->path)); + path = xstrdup(mkpath("%s%s", base, result->path)); orig = create_entry(2, branch1->mode, branch1->sha1, path); final = create_entry(0, result->mode, result->sha1, path); @@ -233,7 +233,7 @@ static struct merge_list *link_entry(unsigned stage, const char *base, struct na if (entry) path = entry->path; else - path = strdup(mkpath("%s%s", base, n->path)); + path = xstrdup(mkpath("%s%s", base, n->path)); link = create_entry(stage, n->mode, n->sha1, path); link->link = entry; return link; @@ -337,9 +337,11 @@ int main(int argc, char **argv) struct tree_desc t[3]; void *buf1, *buf2, *buf3; - if (argc < 4) + if (argc != 4) usage(merge_tree_usage); + setup_git_directory(); + buf1 = get_tree_descriptor(t+0, argv[1]); buf2 = get_tree_descriptor(t+1, argv[2]); buf3 = get_tree_descriptor(t+2, argv[3]); @@ -119,7 +119,7 @@ static int verify_tag(char *buffer, unsigned long size) int main(int argc, char **argv) { unsigned long size = 4096; - char *buffer = malloc(size); + char *buffer = xmalloc(size); unsigned char result_sha1[20]; if (argc != 1) @@ -30,7 +30,7 @@ static void append_to_tree(unsigned mode, unsigned char *sha1, char *path) ent = entries[used++] = xmalloc(sizeof(**entries) + len + 1); ent->mode = mode; ent->len = len; - memcpy(ent->sha1, sha1, 20); + hashcpy(ent->sha1, sha1); memcpy(ent->name, path, len+1); } @@ -49,7 +49,6 @@ static void write_tree(unsigned char *sha1) int i; qsort(entries, used, sizeof(*entries), ent_compare); - size = 100; for (size = i = 0; i < used; i++) size += 32 + entries[i]->len; buffer = xmalloc(size); @@ -65,7 +64,7 @@ static void write_tree(unsigned char *sha1) offset += sprintf(buffer + offset, "%o ", ent->mode); offset += sprintf(buffer + offset, "%s", ent->name); buffer[offset++] = 0; - memcpy(buffer + offset, ent->sha1, 20); + hashcpy((unsigned char*)buffer + offset, ent->sha1); offset += 20; } write_sha1_file(buffer, offset, tree_type, sha1); diff --git a/object-refs.c b/object-refs.c index b1b8065851..98ea10005a 100644 --- a/object-refs.c +++ b/object-refs.c @@ -30,7 +30,7 @@ static void grow_refs_hash(void) int new_hash_size = (refs_hash_size + 1000) * 3 / 2; struct object_refs **new_hash; - new_hash = calloc(new_hash_size, sizeof(struct object_refs *)); + new_hash = xcalloc(new_hash_size, sizeof(struct object_refs *)); for (i = 0; i < refs_hash_size; i++) { struct object_refs *ref = refs_hash[i]; if (!ref) @@ -55,9 +55,13 @@ static void add_object_refs(struct object *obj, struct object_refs *ref) struct object_refs *lookup_object_refs(struct object *obj) { - int j = hash_obj(obj, refs_hash_size); struct object_refs *ref; + int j; + /* nothing to lookup */ + if (!refs_hash_size) + return NULL; + j = hash_obj(obj, refs_hash_size); while ((ref = refs_hash[j]) != NULL) { if (ref->base == obj) break; @@ -125,9 +129,6 @@ void mark_reachable(struct object *obj, unsigned int mask) if (!track_object_refs) die("cannot do reachability with object refs turned off"); - /* nothing to lookup */ - if (!refs_hash_size) - return; /* If we've been here already, don't bother */ if (obj->flags & mask) return; @@ -58,7 +58,7 @@ struct object *lookup_object(const unsigned char *sha1) i = hashtable_index(sha1); while ((obj = obj_hash[i]) != NULL) { - if (!memcmp(sha1, obj->sha1, 20)) + if (!hashcmp(sha1, obj->sha1)) break; i++; if (i == obj_hash_size) @@ -73,7 +73,7 @@ static void grow_object_hash(void) int new_hash_size = obj_hash_size < 32 ? 32 : 2 * obj_hash_size; struct object **new_hash; - new_hash = calloc(new_hash_size, sizeof(struct object *)); + new_hash = xcalloc(new_hash_size, sizeof(struct object *)); for (i = 0; i < obj_hash_size; i++) { struct object *obj = obj_hash[i]; if (!obj) @@ -91,7 +91,7 @@ void created_object(const unsigned char *sha1, struct object *obj) obj->used = 0; obj->type = OBJ_NONE; obj->flags = 0; - memcpy(obj->sha1, sha1, 20); + hashcpy(obj->sha1, sha1); if (obj_hash_size - 1 <= nr_objs * 2) grow_object_hash(); @@ -27,17 +27,6 @@ struct object_array { /* * The object type is stored in 3 bits. */ -enum object_type { - OBJ_NONE = 0, - OBJ_COMMIT = 1, - OBJ_TREE = 2, - OBJ_BLOB = 3, - OBJ_TAG = 4, - /* 5/6 for future expansion */ - OBJ_DELTA = 7, - OBJ_BAD, -}; - struct object { unsigned parsed : 1; unsigned used : 1; diff --git a/pack-check.c b/pack-check.c index 3a62e1b7e4..04c6c00821 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 41fb960569..edb5524fc4 100644 --- a/pack-redundant.c +++ b/pack-redundant.c @@ -13,7 +13,7 @@ static const char pack_redundant_usage[] = "git-pack-redundant [ --verbose ] [ --alt-odb ] < --all | <.pack filename> ...>"; -static int load_all_packs = 0, verbose = 0, alt_odb = 0; +static int load_all_packs, verbose, alt_odb; struct llist_item { struct llist_item *next; @@ -37,7 +37,7 @@ struct pll { struct pack_list *pl; }; -static struct llist_item *free_nodes = NULL; +static struct llist_item *free_nodes; static inline void llist_item_put(struct llist_item *item) { @@ -139,7 +139,7 @@ static inline struct llist_item *llist_insert_sorted_unique(struct llist *list, l = (hint == NULL) ? list->front : hint; while (l) { - int cmp = memcmp(l->sha1, sha1, 20); + int cmp = hashcmp(l->sha1, sha1); if (cmp > 0) { /* we insert before this entry */ return llist_insert(list, prev, sha1); } @@ -162,7 +162,7 @@ redo_from_start: l = (hint == NULL) ? list->front : hint; prev = NULL; while (l) { - int cmp = memcmp(l->sha1, sha1, 20); + int cmp = hashcmp(l->sha1, sha1); if (cmp > 0) /* not in list, since sorted */ return prev; if(!cmp) { /* found */ @@ -256,7 +256,7 @@ static void cmp_two_packs(struct pack_list *p1, struct pack_list *p2) while (p1_off <= p1->pack->index_size - 3 * 20 && p2_off <= p2->pack->index_size - 3 * 20) { - int cmp = memcmp(p1_base + p1_off, p2_base + p2_off, 20); + int cmp = hashcmp(p1_base + p1_off, p2_base + p2_off); /* cmp ~ p1 - p2 */ if (cmp == 0) { p1_hint = llist_sorted_remove(p1->unique_objects, @@ -351,16 +351,16 @@ static size_t sizeof_union(struct packed_git *p1, struct packed_git *p2) { size_t ret = 0; int p1_off, p2_off; - char *p1_base, *p2_base; + unsigned char *p1_base, *p2_base; p1_off = p2_off = 256 * 4 + 4; - p1_base = (char *)p1->index_base; - p2_base = (char *)p2->index_base; + p1_base = (unsigned char *)p1->index_base; + p2_base = (unsigned char *)p2->index_base; while (p1_off <= p1->index_size - 3 * 20 && p2_off <= p2->index_size - 3 * 20) { - int cmp = memcmp(p1_base + p1_off, p2_base + p2_off, 20); + int cmp = hashcmp(p1_base + p1_off, p2_base + p2_off); /* cmp ~ p1 - p2 */ if (cmp == 0) { ret++; diff --git a/patch-id.c b/patch-id.c index 3b4c80f764..086d2d9c68 100644 --- a/patch-id.c +++ b/patch-id.c @@ -47,7 +47,7 @@ static void generate_id_list(void) if (!get_sha1_hex(p, n)) { flush_current_id(patchlen, sha1, &ctx); - memcpy(sha1, n, 20); + hashcpy(sha1, n); patchlen = 0; continue; } diff --git a/path-list.c b/path-list.c index f15a10de37..0c332dc7b5 100644 --- a/path-list.c +++ b/path-list.c @@ -45,7 +45,7 @@ static int add_entry(struct path_list *list, const char *path) (list->nr - index) * sizeof(struct path_list_item)); list->items[index].path = list->strdup_paths ? - strdup(path) : (char *)path; + xstrdup(path) : (char *)path; list->items[index].util = NULL; list->nr++; @@ -85,8 +85,7 @@ void path_list_clear(struct path_list *list, int free_items) for (i = 0; i < list->nr; i++) { if (list->strdup_paths) free(list->items[i].path); - if (list->items[i].util) - free(list->items[i].util); + free(list->items[i].util); } free(list->items); } @@ -13,9 +13,15 @@ #include "cache.h" #include <pwd.h> -static char pathname[PATH_MAX]; static char bad_path[] = "/bad-path/"; +static char *get_pathname(void) +{ + static char pathname_array[4][PATH_MAX]; + static int index; + return pathname_array[3 & ++index]; +} + static char *cleanup_path(char *path) { /* Clean it up */ @@ -31,6 +37,7 @@ char *mkpath(const char *fmt, ...) { va_list args; unsigned len; + char *pathname = get_pathname(); va_start(args, fmt); len = vsnprintf(pathname, PATH_MAX, fmt, args); @@ -43,6 +50,7 @@ char *mkpath(const char *fmt, ...) char *git_path(const char *fmt, ...) { const char *git_dir = get_git_dir(); + char *pathname = get_pathname(); va_list args; unsigned len; diff --git a/peek-remote.c b/peek-remote.c index 2b30980b04..353da002b4 100644 --- a/peek-remote.c +++ b/peek-remote.c @@ -1,7 +1,6 @@ #include "cache.h" #include "refs.h" #include "pkt-line.h" -#include <sys/wait.h> static const char peek_remote_usage[] = "git-peek-remote [--exec=upload-pack] [host:]directory"; @@ -67,6 +66,6 @@ int main(int argc, char **argv) ret = peek_remote(fd, flags); close(fd[0]); close(fd[1]); - finish_connect(pid); - return ret; + ret |= finish_connect(pid); + return !!ret; } @@ -74,6 +74,67 @@ char *sq_quote(const char *src) return buf; } +char *sq_quote_argv(const char** argv, int count) +{ + char *buf, *to; + int i; + size_t len = 0; + + /* Count argv if needed. */ + if (count < 0) { + for (count = 0; argv[count]; count++) + ; /* just counting */ + } + + /* Special case: no argv. */ + if (!count) + return xcalloc(1,1); + + /* Get destination buffer length. */ + for (i = 0; i < count; i++) + len += sq_quote_buf(NULL, 0, argv[i]) + 1; + + /* Alloc destination buffer. */ + to = buf = xmalloc(len + 1); + + /* Copy into destination buffer. */ + for (i = 0; i < count; ++i) { + *to++ = ' '; + to += sq_quote_buf(to, len, argv[i]); + } + + return buf; +} + +/* + * Append a string to a string buffer, with or without shell quoting. + * Return true if the buffer overflowed. + */ +int add_to_string(char **ptrp, int *sizep, const char *str, int quote) +{ + char *p = *ptrp; + int size = *sizep; + int oc; + int err = 0; + + if (quote) + oc = sq_quote_buf(p, size, str); + else { + oc = strlen(str); + memcpy(p, str, (size <= oc) ? size - 1 : oc); + } + + if (size <= oc) { + err = 1; + oc = size - 1; + } + + *ptrp += oc; + **ptrp = '\0'; + *sizep -= oc; + return err; +} + char *sq_dequote(char *arg) { char *dst = arg; @@ -31,6 +31,13 @@ extern char *sq_quote(const char *src); extern void sq_quote_print(FILE *stream, const char *src); extern size_t sq_quote_buf(char *dst, size_t n, const char *src); +extern char *sq_quote_argv(const char** argv, int count); + +/* + * Append a string to a string buffer, with or without shell quoting. + * Return true if the buffer overflowed. + */ +extern int add_to_string(char **ptrp, int *sizep, const char *str, int quote); /* This unwraps what sq_quote() produces in place, but returns * NULL if the input does not look like what sq_quote would have diff --git a/read-cache.c b/read-cache.c index c923a32707..20c9d494ac 100644 --- a/read-cache.c +++ b/read-cache.c @@ -5,7 +5,6 @@ */ #include "cache.h" #include "cache-tree.h" -#include <time.h> /* Index extensions. * @@ -19,16 +18,16 @@ #define CACHE_EXT(s) ( (s[0]<<24)|(s[1]<<16)|(s[2]<<8)|(s[3]) ) #define CACHE_EXT_TREE 0x54524545 /* "TREE" */ -struct cache_entry **active_cache = NULL; +struct cache_entry **active_cache; static time_t index_file_timestamp; -unsigned int active_nr = 0, active_alloc = 0, active_cache_changed = 0; +unsigned int active_nr, active_alloc, active_cache_changed; -struct cache_tree *active_cache_tree = NULL; +struct cache_tree *active_cache_tree; -int cache_errno = 0; +int cache_errno; -static void *cache_mmap = NULL; -static size_t cache_mmap_size = 0; +static void *cache_mmap; +static size_t cache_mmap_size; /* * This only updates the "non-critical" parts of the directory @@ -61,7 +60,7 @@ static int ce_compare_data(struct cache_entry *ce, struct stat *st) if (fd >= 0) { unsigned char sha1[20]; if (!index_fd(sha1, fd, st, 0, NULL)) - match = memcmp(sha1, ce->sha1, 20); + match = hashcmp(sha1, ce->sha1); /* index_fd() closed the file descriptor already */ } return match; @@ -170,9 +169,11 @@ static int ce_match_stat_basic(struct cache_entry *ce, struct stat *st) return changed; } -int ce_match_stat(struct cache_entry *ce, struct stat *st, int ignore_valid) +int ce_match_stat(struct cache_entry *ce, struct stat *st, int options) { unsigned int changed; + int ignore_valid = options & 01; + int assume_racy_is_modified = options & 02; /* * If it's marked as always valid in the index, it's @@ -201,8 +202,12 @@ int ce_match_stat(struct cache_entry *ce, struct stat *st, int ignore_valid) */ if (!changed && index_file_timestamp && - index_file_timestamp <= ntohl(ce->ce_mtime.sec)) - changed |= ce_modified_check_fs(ce, st); + index_file_timestamp <= ntohl(ce->ce_mtime.sec)) { + if (assume_racy_is_modified) + changed |= DATA_CHANGED; + else + changed |= ce_modified_check_fs(ce, st); + } return changed; } @@ -739,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; } @@ -837,6 +842,23 @@ unmap: die("index file corrupt"); } +int discard_cache() +{ + int ret; + + active_nr = active_cache_changed = 0; + index_file_timestamp = 0; + cache_tree_free(&active_cache_tree); + if (cache_mmap == NULL) + return 0; + ret = munmap(cache_mmap, cache_mmap_size); + cache_mmap = NULL; + cache_mmap_size = 0; + + /* no need to throw away allocated active_cache */ + return ret; +} + #define WRITE_BUFFER_SIZE 8192 static unsigned char write_buffer[WRITE_BUFFER_SIZE]; static unsigned long write_buffer_len; @@ -880,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) @@ -905,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) @@ -958,9 +976,7 @@ int write_cache(int newfd, struct cache_entry **cache, int entries) { SHA_CTX c; struct cache_header hdr; - int i, removed, recent; - struct stat st; - time_t now; + int i, removed; for (i = removed = 0; i < entries; i++) if (!cache[i]->ce_mode) @@ -998,57 +1014,5 @@ int write_cache(int newfd, struct cache_entry **cache, int entries) return -1; } } - - /* - * To prevent later ce_match_stat() from always falling into - * check_fs(), if we have too many entries that can trigger - * racily clean check, we are better off delaying the return. - * We arbitrarily say if more than 20 paths or 25% of total - * paths are very new, we delay the return until the index - * file gets a new timestamp. - * - * NOTE! NOTE! NOTE! - * - * This assumes that nobody is touching the working tree while - * we are updating the index. - */ - - /* Make sure that the new index file has st_mtime - * that is current enough -- ce_write() batches the data - * so it might not have written anything yet. - */ - ce_write_flush(&c, newfd); - - now = fstat(newfd, &st) ? 0 : st.st_mtime; - if (now) { - recent = 0; - for (i = 0; i < entries; i++) { - struct cache_entry *ce = cache[i]; - time_t entry_time = (time_t) ntohl(ce->ce_mtime.sec); - if (!ce->ce_mode) - continue; - if (now && now <= entry_time) - recent++; - } - if (20 < recent && entries <= recent * 4) { -#if 0 - fprintf(stderr, "entries %d\n", entries); - fprintf(stderr, "recent %d\n", recent); - fprintf(stderr, "now %lu\n", now); -#endif - while (!fstat(newfd, &st) && st.st_mtime <= now) { - struct timespec rq, rm; - off_t where = lseek(newfd, 0, SEEK_CUR); - rq.tv_sec = 0; - rq.tv_nsec = 250000000; - nanosleep(&rq, &rm); - if ((where == (off_t) -1) || - (write(newfd, "", 1) != 1) || - (lseek(newfd, -1, SEEK_CUR) != where) || - ftruncate(newfd, where)) - break; - } - } - } return ce_flush(&c, newfd); } diff --git a/receive-pack.c b/receive-pack.c index 93929b5371..78f75da5ca 100644 --- a/receive-pack.c +++ b/receive-pack.c @@ -2,16 +2,15 @@ #include "refs.h" #include "pkt-line.h" #include "run-command.h" -#include <sys/wait.h> static const char receive_pack_usage[] = "git-receive-pack <git-dir>"; static const char *unpacker[] = { "unpack-objects", NULL }; -static int report_status = 0; +static int 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 +39,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 +246,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); @@ -313,8 +313,8 @@ static struct ref_lock *lock_ref_sha1_basic(const char *path, } lock->lk = xcalloc(1, sizeof(struct lock_file)); - lock->ref_file = strdup(path); - lock->log_file = strdup(git_path("logs/%s", lock->ref_file + plen)); + lock->ref_file = xstrdup(path); + lock->log_file = xstrdup(git_path("logs/%s", lock->ref_file + plen)); lock->force_write = lstat(lock->ref_file, &st) && errno == ENOENT; if (safe_create_leading_directories(lock->ref_file)) @@ -348,10 +348,8 @@ void unlock_ref(struct ref_lock *lock) if (lock->lk) rollback_lock_file(lock->lk); } - if (lock->ref_file) - free(lock->ref_file); - if (lock->log_file) - free(lock->log_file); + free(lock->ref_file); + free(lock->log_file); free(lock); } @@ -411,7 +409,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 +473,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 +487,7 @@ int read_ref_at(const char *ref, unsigned long at_time, unsigned char *sha1) else { if (get_sha1_hex(rec + 41, logged_sha1)) die("Log %s is corrupt.", logfile); - if (memcmp(logged_sha1, sha1, 20)) { + if (hashcmp(logged_sha1, sha1)) { tz = strtoul(tz_c, NULL, 10); fprintf(stderr, "warning: Log %s unexpectedly ended on %s.\n", diff --git a/revision.c b/revision.c index 5a91d06b98..6a2539b623 100644 --- a/revision.c +++ b/revision.c @@ -416,7 +416,8 @@ static void limit_list(struct rev_info *revs) if (revs->max_age != -1 && (commit->date < revs->max_age)) obj->flags |= UNINTERESTING; - if (revs->unpacked && has_sha1_pack(obj->sha1)) + if (revs->unpacked && + has_sha1_pack(obj->sha1, revs->ignore_packed)) obj->flags |= UNINTERESTING; add_parents_to_list(revs, commit, &list); if (obj->flags & UNINTERESTING) { @@ -496,7 +497,7 @@ static int add_parents_only(struct rev_info *revs, const char *arg, int flags) it = get_reference(revs, arg, sha1, 0); if (it->type != OBJ_TAG) break; - memcpy(sha1, ((struct tag*)it)->tagged->sha1, 20); + hashcpy(sha1, ((struct tag*)it)->tagged->sha1); } if (it->type != OBJ_COMMIT) return 0; @@ -592,6 +593,95 @@ static void prepare_show_merge(struct rev_info *revs) revs->prune_data = prune; } +int handle_revision_arg(const char *arg, struct rev_info *revs, + int flags, + int cant_be_filename) +{ + char *dotdot; + struct object *object; + unsigned char sha1[20]; + int local_flags; + + dotdot = strstr(arg, ".."); + if (dotdot) { + unsigned char from_sha1[20]; + const char *next = dotdot + 2; + const char *this = arg; + int symmetric = *next == '.'; + unsigned int flags_exclude = flags ^ UNINTERESTING; + + *dotdot = 0; + next += symmetric; + + if (!*next) + next = "HEAD"; + if (dotdot == arg) + this = "HEAD"; + if (!get_sha1(this, from_sha1) && + !get_sha1(next, sha1)) { + struct commit *a, *b; + struct commit_list *exclude; + + a = lookup_commit_reference(from_sha1); + b = lookup_commit_reference(sha1); + if (!a || !b) { + die(symmetric ? + "Invalid symmetric difference expression %s...%s" : + "Invalid revision range %s..%s", + arg, next); + } + + if (!cant_be_filename) { + *dotdot = '.'; + verify_non_filename(revs->prefix, arg); + } + + if (symmetric) { + exclude = get_merge_bases(a, b, 1); + add_pending_commit_list(revs, exclude, + flags_exclude); + free_commit_list(exclude); + a->object.flags |= flags; + } else + a->object.flags |= flags_exclude; + b->object.flags |= flags; + add_pending_object(revs, &a->object, this); + add_pending_object(revs, &b->object, next); + return 0; + } + *dotdot = '.'; + } + dotdot = strstr(arg, "^@"); + if (dotdot && !dotdot[2]) { + *dotdot = 0; + if (add_parents_only(revs, arg, flags)) + return 0; + *dotdot = '^'; + } + local_flags = 0; + if (*arg == '^') { + local_flags = UNINTERESTING; + arg++; + } + if (get_sha1(arg, sha1)) + return -1; + if (!cant_be_filename) + verify_non_filename(revs->prefix, arg); + object = get_reference(revs, arg, sha1, flags ^ local_flags); + add_pending_object(revs, object, arg); + return 0; +} + +static void add_ignore_packed(struct rev_info *revs, const char *name) +{ + int num = ++revs->num_ignore_packed; + + revs->ignore_packed = xrealloc(revs->ignore_packed, + sizeof(const char **) * (num + 1)); + revs->ignore_packed[num-1] = name; + revs->ignore_packed[num] = NULL; +} + /* * Parse revision information, filling in the "rev_info" structure, * and removing the used arguments from the argument list. @@ -620,12 +710,7 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch flags = show_merge = 0; for (i = 1; i < argc; i++) { - struct object *object; const char *arg = argv[i]; - unsigned char sha1[20]; - char *dotdot; - int local_flags; - if (*arg == '-') { int opts; if (!strncmp(arg, "--max-count=", 12)) { @@ -737,6 +822,14 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch } if (!strcmp(arg, "--unpacked")) { revs->unpacked = 1; + free(revs->ignore_packed); + revs->ignore_packed = NULL; + revs->num_ignore_packed = 0; + continue; + } + if (!strncmp(arg, "--unpacked=", 11)) { + revs->unpacked = 1; + add_ignore_packed(revs, arg+11); continue; } if (!strcmp(arg, "-r")) { @@ -816,6 +909,10 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch revs->simplify_history = 0; continue; } + if (!strcmp(arg, "--relative-date")) { + revs->relative_date = 1; + continue; + } opts = diff_opt_parse(&revs->diffopt, argv+i, argc-i); if (opts > 0) { revs->diff = 1; @@ -826,71 +923,10 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch left++; continue; } - dotdot = strstr(arg, ".."); - if (dotdot) { - unsigned char from_sha1[20]; - const char *next = dotdot + 2; - const char *this = arg; - int symmetric = *next == '.'; - unsigned int flags_exclude = flags ^ UNINTERESTING; - - *dotdot = 0; - next += symmetric; - - if (!*next) - next = "HEAD"; - if (dotdot == arg) - this = "HEAD"; - if (!get_sha1(this, from_sha1) && - !get_sha1(next, sha1)) { - struct commit *a, *b; - struct commit_list *exclude; - - a = lookup_commit_reference(from_sha1); - b = lookup_commit_reference(sha1); - if (!a || !b) { - die(symmetric ? - "Invalid symmetric difference expression %s...%s" : - "Invalid revision range %s..%s", - arg, next); - } - - if (!seen_dashdash) { - *dotdot = '.'; - verify_non_filename(revs->prefix, arg); - } - - if (symmetric) { - exclude = get_merge_bases(a, b, 1); - add_pending_commit_list(revs, exclude, - flags_exclude); - free_commit_list(exclude); - a->object.flags |= flags; - } else - a->object.flags |= flags_exclude; - b->object.flags |= flags; - add_pending_object(revs, &a->object, this); - add_pending_object(revs, &b->object, next); - continue; - } - *dotdot = '.'; - } - dotdot = strstr(arg, "^@"); - if (dotdot && !dotdot[2]) { - *dotdot = 0; - if (add_parents_only(revs, arg, flags)) - continue; - *dotdot = '^'; - } - local_flags = 0; - if (*arg == '^') { - local_flags = UNINTERESTING; - arg++; - } - if (get_sha1(arg, sha1)) { - int j; - if (seen_dashdash || local_flags) + if (handle_revision_arg(arg, revs, flags, seen_dashdash)) { + int j; + if (seen_dashdash || *arg == '^') die("bad revision '%s'", arg); /* If we didn't have a "--": @@ -902,14 +938,12 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch for (j = i; j < argc; j++) verify_filename(revs->prefix, argv[j]); - revs->prune_data = get_pathspec(revs->prefix, argv + i); + revs->prune_data = get_pathspec(revs->prefix, + argv + i); break; } - if (!seen_dashdash) - verify_non_filename(revs->prefix, arg); - object = get_reference(revs, arg, sha1, flags ^ local_flags); - add_pending_object(revs, object, arg); } + if (show_merge) prepare_show_merge(revs); if (def && !revs->pending.nr) { @@ -1042,7 +1076,8 @@ struct commit *get_revision(struct rev_info *revs) */ if (!revs->limited) { if ((revs->unpacked && - has_sha1_pack(commit->object.sha1)) || + has_sha1_pack(commit->object.sha1, + revs->ignore_packed)) || (revs->max_age != -1 && (commit->date < revs->max_age))) continue; diff --git a/revision.h b/revision.h index 0c3b8d9905..a5c35d05cb 100644 --- a/revision.h +++ b/revision.h @@ -38,7 +38,7 @@ struct rev_info { blob_objects:1, edge_hint:1, limited:1, - unpacked:1, + unpacked:1, /* see also ignore_packed below */ boundary:1, parents:1; @@ -55,7 +55,12 @@ struct rev_info { /* Format info */ unsigned int shown_one:1, - abbrev_commit:1; + abbrev_commit:1, + relative_date:1; + + const char **ignore_packed; /* pretend objects in these are unpacked */ + int num_ignore_packed; + unsigned int abbrev; enum cmit_fmt commit_format; struct log_info *loginfo; @@ -89,6 +94,8 @@ extern int rev_compare_tree(struct rev_info *, struct tree *t1, struct tree *t2) extern void init_revisions(struct rev_info *revs, const char *prefix); extern int setup_revisions(int argc, const char **argv, struct rev_info *revs, const char *def); +extern int handle_revision_arg(const char *arg, struct rev_info *revs,int flags,int cant_be_filename); + extern void prepare_revision_walk(struct rev_info *revs); extern struct commit *get_revision(struct rev_info *revs); @@ -8,36 +8,7 @@ #define COMMAND_SIZE 4096 -/* - * Append a string to a string buffer, with or without shell quoting. - * Return true if the buffer overflowed. - */ -static int add_to_string(char **ptrp, int *sizep, const char *str, int quote) -{ - char *p = *ptrp; - int size = *sizep; - int oc; - int err = 0; - - if ( quote ) { - oc = sq_quote_buf(p, size, str); - } else { - oc = strlen(str); - memcpy(p, str, (oc >= size) ? size-1 : oc); - } - - if ( oc >= size ) { - err = 1; - oc = size-1; - } - - *ptrp += oc; - **ptrp = '\0'; - *sizep -= oc; - return err; -} - -int setup_connection(int *fd_in, int *fd_out, const char *remote_prog, +int setup_connection(int *fd_in, int *fd_out, const char *remote_prog, char *url, int rmt_argc, char **rmt_argv) { char *host; diff --git a/run-command.c b/run-command.c index ca67ee9333..61908682b9 100644 --- a/run-command.c +++ b/run-command.c @@ -25,15 +25,15 @@ int run_command_v_opt(int argc, const char **argv, int flags) } for (;;) { int status, code; - int retval = waitpid(pid, &status, 0); + pid_t waiting = waitpid(pid, &status, 0); - if (retval < 0) { + if (waiting < 0) { if (errno == EINTR) continue; - error("waitpid failed (%s)", strerror(retval)); + error("waitpid failed (%s)", strerror(errno)); return -ERR_RUN_COMMAND_WAITPID; } - if (retval != pid) + if (waiting != pid) return -ERR_RUN_COMMAND_WAITPID_WRONG_PID; if (WIFSIGNALED(status)) return -ERR_RUN_COMMAND_WAITPID_SIGNAL; diff --git a/send-pack.c b/send-pack.c index 10bc8bc359..5bb123a376 100644 --- a/send-pack.c +++ b/send-pack.c @@ -9,10 +9,10 @@ static const char send_pack_usage[] = "git-send-pack [--all] [--exec=git-receive-pack] <remote> [<head>...]\n" " --all and explicit <head> specification are mutually exclusive."; static const char *exec = "git-receive-pack"; -static int verbose = 0; -static int send_all = 0; -static int force_update = 0; -static int use_thin_pack = 0; +static int verbose; +static int send_all; +static int force_update; +static int use_thin_pack; static int is_zero_sha1(const unsigned char *sha1) { @@ -38,9 +38,8 @@ static void exec_pack_objects(void) static void exec_rev_list(struct ref *refs) { - struct ref *ref; - static const char *args[1000]; - int i = 0, j; + static const char *args[4]; + int i = 0; args[i++] = "rev-list"; /* 0 */ if (use_thin_pack) /* 1 */ @@ -48,43 +47,16 @@ static void exec_rev_list(struct ref *refs) else args[i++] = "--objects"; - /* First send the ones we care about most */ - for (ref = refs; ref; ref = ref->next) { - if (900 < i) - die("git-rev-list environment overflow"); - if (!is_zero_sha1(ref->new_sha1)) { - char *buf = malloc(100); - args[i++] = buf; - snprintf(buf, 50, "%s", sha1_to_hex(ref->new_sha1)); - buf += 50; - if (!is_zero_sha1(ref->old_sha1) && - has_sha1_file(ref->old_sha1)) { - args[i++] = buf; - snprintf(buf, 50, "^%s", - sha1_to_hex(ref->old_sha1)); - } - } - } + args[i++] = "--stdin"; - /* Then a handful of the remainder - * NEEDSWORK: we would be better off if used the newer ones first. - */ - for (ref = refs, j = i + 16; - i < 900 && i < j && ref; - ref = ref->next) { - if (is_zero_sha1(ref->new_sha1) && - !is_zero_sha1(ref->old_sha1) && - has_sha1_file(ref->old_sha1)) { - char *buf = malloc(42); - args[i++] = buf; - snprintf(buf, 42, "^%s", sha1_to_hex(ref->old_sha1)); - } - } args[i] = NULL; execv_git_cmd(args); die("git-rev-list exec failed (%s)", strerror(errno)); } +/* + * Run "rev-list --stdin | pack-objects" pipe. + */ static void rev_list(int fd, struct ref *refs) { int pipe_fd[2]; @@ -94,6 +66,9 @@ static void rev_list(int fd, struct ref *refs) die("rev-list setup: pipe failed"); pack_objects_pid = fork(); if (!pack_objects_pid) { + /* The child becomes pack-objects; reads from pipe + * and writes to the original fd + */ dup2(pipe_fd[0], 0); dup2(fd, 1); close(pipe_fd[0]); @@ -104,6 +79,8 @@ static void rev_list(int fd, struct ref *refs) } if (pack_objects_pid < 0) die("pack-objects fork failed"); + + /* We become rev-list --stdin; output goes to pipe. */ dup2(pipe_fd[1], 1); close(pipe_fd[0]); close(pipe_fd[1]); @@ -111,13 +88,71 @@ static void rev_list(int fd, struct ref *refs) exec_rev_list(refs); } -static int pack_objects(int fd, struct ref *refs) +/* + * Create "rev-list --stdin | pack-objects" pipe and feed + * the refs into the pipeline. + */ +static void rev_list_generate(int fd, struct ref *refs) +{ + int pipe_fd[2]; + pid_t rev_list_generate_pid; + + if (pipe(pipe_fd) < 0) + die("rev-list-generate setup: pipe failed"); + rev_list_generate_pid = fork(); + if (!rev_list_generate_pid) { + /* The child becomes the "rev-list | pack-objects" + * pipeline. It takes input from us, and its output + * goes to fd. + */ + dup2(pipe_fd[0], 0); + dup2(fd, 1); + close(pipe_fd[0]); + close(pipe_fd[1]); + close(fd); + rev_list(fd, refs); + die("rev-list setup failed"); + } + if (rev_list_generate_pid < 0) + die("rev-list-generate fork failed"); + + /* We feed the rev parameters to them. We do not write into + * fd nor read from the pipe. + */ + close(pipe_fd[0]); + close(fd); + while (refs) { + char buf[42]; + + if (!is_null_sha1(refs->old_sha1) && + has_sha1_file(refs->old_sha1)) { + memcpy(buf + 1, sha1_to_hex(refs->old_sha1), 40); + buf[0] = '^'; + buf[41] = '\n'; + write(pipe_fd[1], buf, 42); + } + if (!is_null_sha1(refs->new_sha1)) { + memcpy(buf, sha1_to_hex(refs->new_sha1), 40); + buf[40] = '\n'; + write(pipe_fd[1], buf, 41); + } + refs = refs->next; + } + close(pipe_fd[1]); + // waitpid(rev_list_generate_pid); + exit(0); +} + +/* + * Make a pack stream and spit it out into file descriptor fd + */ +static void pack_objects(int fd, struct ref *refs) { pid_t rev_list_pid; rev_list_pid = fork(); if (!rev_list_pid) { - rev_list(fd, refs); + rev_list_generate(fd, refs); die("rev-list setup failed"); } if (rev_list_pid < 0) @@ -126,7 +161,6 @@ static int pack_objects(int fd, struct ref *refs) * We don't wait for the rev-list pipeline in the parent: * we end up waiting for the other end instead */ - return 0; } static void unmark_and_free(struct commit_list *list, unsigned int mark) @@ -186,7 +220,7 @@ static int one_local_ref(const char *refname, const unsigned char *sha1) struct ref *ref; int len = strlen(refname) + 1; ref = xcalloc(1, sizeof(*ref) + len); - memcpy(ref->new_sha1, sha1, 20); + hashcpy(ref->new_sha1, sha1); memcpy(ref->name, refname, len); *local_tail = ref; local_tail = &ref->next; @@ -266,7 +300,7 @@ static int send_pack(int in, int out, int nr_refspec, char **refspec) char old_hex[60], *new_hex; if (!ref->peer_ref) continue; - if (!memcmp(ref->old_sha1, ref->peer_ref->new_sha1, 20)) { + if (!hashcmp(ref->old_sha1, ref->peer_ref->new_sha1)) { if (verbose) fprintf(stderr, "'%s': up-to-date\n", ref->name); continue; @@ -311,7 +345,7 @@ static int send_pack(int in, int out, int nr_refspec, char **refspec) continue; } } - memcpy(ref->new_sha1, ref->peer_ref->new_sha1, 20); + hashcpy(ref->new_sha1, ref->peer_ref->new_sha1); if (is_zero_sha1(ref->new_sha1)) { error("cannot happen anymore"); ret = -3; @@ -409,6 +443,6 @@ int main(int argc, char **argv) ret = send_pack(fd[0], fd[1], nr_heads, heads); close(fd[0]); close(fd[1]); - finish_connect(pid); - return ret; + ret |= finish_connect(pid); + return !!ret; } diff --git a/server-info.c b/server-info.c index 7df628f2b2..2fb8f57103 100644 --- a/server-info.c +++ b/server-info.c @@ -23,7 +23,7 @@ static int add_info_ref(const char *path, const unsigned char *sha1) static int update_info_refs(int force) { - char *path0 = strdup(git_path("info/refs")); + char *path0 = xstrdup(git_path("info/refs")); int len = strlen(path0); char *path1 = xmalloc(len + 2); diff --git a/sha1_file.c b/sha1_file.c index 842a6f3ae8..b89edb9515 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -22,19 +22,47 @@ #endif #endif -const unsigned char null_sha1[20] = { 0, }; +const unsigned char null_sha1[20]; static unsigned int sha1_file_open_flag = O_NOATIME; -static unsigned hexval(char c) -{ - if (c >= '0' && c <= '9') - return c - '0'; - if (c >= 'a' && c <= 'f') - return c - 'a' + 10; - if (c >= 'A' && c <= 'F') - return c - 'A' + 10; - return ~0; +static inline unsigned int hexval(unsigned int c) +{ + static signed char val[256] = { + -1, -1, -1, -1, -1, -1, -1, -1, /* 00-07 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 08-0f */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 10-17 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 18-1f */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 20-27 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 28-2f */ + 0, 1, 2, 3, 4, 5, 6, 7, /* 30-37 */ + 8, 9, -1, -1, -1, -1, -1, -1, /* 38-3f */ + -1, 10, 11, 12, 13, 14, 15, -1, /* 40-47 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 48-4f */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 50-57 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 58-5f */ + -1, 10, 11, 12, 13, 14, 15, -1, /* 60-67 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 68-67 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 70-77 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 78-7f */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 80-87 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 88-8f */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 90-97 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 98-9f */ + -1, -1, -1, -1, -1, -1, -1, -1, /* a0-a7 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* a8-af */ + -1, -1, -1, -1, -1, -1, -1, -1, /* b0-b7 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* b8-bf */ + -1, -1, -1, -1, -1, -1, -1, -1, /* c0-c7 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* c8-cf */ + -1, -1, -1, -1, -1, -1, -1, -1, /* d0-d7 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* d8-df */ + -1, -1, -1, -1, -1, -1, -1, -1, /* e0-e7 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* e8-ef */ + -1, -1, -1, -1, -1, -1, -1, -1, /* f0-f7 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* f8-ff */ + }; + return val[c]; } int get_sha1_hex(const char *hex, unsigned char *sha1) @@ -115,7 +143,7 @@ static void fill_sha1_path(char *pathbuf, const unsigned char *sha1) /* * NOTE! This returns a statically allocated buffer, so you have to be - * careful about using it. Do a "strdup()" if you need to save the + * careful about using it. Do a "xstrdup()" if you need to save the * filename. * * Also note that this returns the location for creating. Reading @@ -463,6 +491,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 +511,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 +568,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 +599,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,7 +683,7 @@ 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; } void *map_sha1_file(const unsigned char *sha1, unsigned long *size) @@ -699,17 +739,39 @@ int legacy_loose_object(unsigned char *map) return 0; } -static int unpack_sha1_header(z_stream *stream, unsigned char *map, unsigned long mapsize, void *buffer, unsigned long bufsiz) +unsigned long unpack_object_header_gently(const unsigned char *buf, unsigned long len, enum object_type *type, unsigned long *sizep) { + unsigned shift; unsigned char c; - unsigned int bits; unsigned long size; - static const char *typename[8] = { - NULL, /* OBJ_EXT */ - "commit", "tree", "blob", "tag", - NULL, NULL, NULL + unsigned long used = 0; + + c = buf[used++]; + *type = (c >> 4) & 7; + size = c & 15; + shift = 4; + while (c & 0x80) { + if (len <= used) + return 0; + if (sizeof(long) * 8 <= shift) + return 0; + c = buf[used++]; + size += (c & 0x7f) << shift; + shift += 7; + } + *sizep = size; + return used; +} + +static int unpack_sha1_header(z_stream *stream, unsigned char *map, unsigned long mapsize, void *buffer, unsigned long bufsiz) +{ + unsigned long size, used; + static const char valid_loose_object_type[8] = { + 0, /* OBJ_EXT */ + 1, 1, 1, 1, /* "commit", "tree", "blob", "tag" */ + 0, /* "delta" and others are invalid in a loose object */ }; - const char *type; + enum object_type type; /* Get the data stream */ memset(stream, 0, sizeof(*stream)); @@ -723,22 +785,11 @@ static int unpack_sha1_header(z_stream *stream, unsigned char *map, unsigned lon return inflate(stream, 0); } - c = *map++; - mapsize--; - type = typename[(c >> 4) & 7]; - if (!type) + used = unpack_object_header_gently(map, mapsize, &type, &size); + if (!used || !valid_loose_object_type[type]) return -1; - - bits = 4; - size = c & 0xf; - while ((c & 0x80)) { - if (bits >= 8*sizeof(long)) - return -1; - c = *map++; - size += (c & 0x7f) << bits; - bits += 7; - mapsize--; - } + map += used; + mapsize -= used; /* Set up the stream for the rest.. */ stream->next_in = map; @@ -746,7 +797,8 @@ static int unpack_sha1_header(z_stream *stream, unsigned char *map, unsigned lon inflateInit(stream); /* And generate the fake traditional header */ - stream->total_out = 1 + snprintf(buffer, bufsiz, "%s %lu", type, size); + stream->total_out = 1 + snprintf(buffer, bufsiz, "%s %lu", + type_names[type], size); return 0; } @@ -904,29 +956,18 @@ static int packed_delta_info(unsigned char *base_sha1, static unsigned long unpack_object_header(struct packed_git *p, unsigned long offset, enum object_type *type, unsigned long *sizep) { - unsigned shift; - unsigned char *pack, c; - unsigned long size; + unsigned long used; - if (offset >= p->pack_size) + if (p->pack_size <= offset) die("object offset outside of pack file"); - pack = (unsigned char *) p->pack_base + offset; - c = *pack++; - offset++; - *type = (c >> 4) & 7; - size = c & 15; - shift = 4; - while (c & 0x80) { - if (offset >= p->pack_size) - die("object offset outside of pack file"); - c = *pack++; - offset++; - size += (c & 0x7f) << shift; - shift += 7; - } - *sizep = size; - return offset; + used = unpack_object_header_gently((unsigned char *)p->pack_base + + offset, + p->pack_size - offset, type, sizep); + if (!used) + die("object offset outside of pack file"); + + return offset + used; } int check_reuse_pack_delta(struct packed_git *p, unsigned long offset, @@ -941,7 +982,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); @@ -969,7 +1010,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; @@ -984,16 +1025,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", @@ -1024,16 +1059,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", @@ -1045,26 +1074,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)); @@ -1073,23 +1125,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); @@ -1101,33 +1137,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) { @@ -1148,36 +1157,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) @@ -1192,7 +1188,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; } @@ -1206,10 +1202,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; } @@ -1221,12 +1217,20 @@ int find_pack_entry_one(const unsigned char *sha1, return 0; } -static int find_pack_entry(const unsigned char *sha1, struct pack_entry *e) +static int find_pack_entry(const unsigned char *sha1, struct pack_entry *e, const char **ignore_packed) { struct packed_git *p; prepare_packed_git(); for (p = packed_git; p; p = p->next) { + if (ignore_packed) { + const char **ig; + for (ig = ignore_packed; *ig; ig++) + if (!strcmp(p->pack_name, *ig)) + break; + if (*ig) + continue; + } if (find_pack_entry_one(sha1, e, p)) return 1; } @@ -1259,10 +1263,10 @@ int sha1_object_info(const unsigned char *sha1, char *type, unsigned long *sizep if (!map) { struct pack_entry e; - if (find_pack_entry(sha1, &e)) + if (find_pack_entry(sha1, &e, NULL)) return packed_object_info(&e, type, sizep); reprepare_packed_git(); - if (find_pack_entry(sha1, &e)) + if (find_pack_entry(sha1, &e, NULL)) return packed_object_info(&e, type, sizep); return error("unable to find %s", sha1_to_hex(sha1)); } @@ -1285,7 +1289,7 @@ static void *read_packed_sha1(const unsigned char *sha1, char *type, unsigned lo { struct pack_entry e; - if (!find_pack_entry(sha1, &e)) { + if (!find_pack_entry(sha1, &e, NULL)) { error("cannot read sha1_file for %s", sha1_to_hex(sha1)); return NULL; } @@ -1298,7 +1302,7 @@ void * read_sha1_file(const unsigned char *sha1, char *type, unsigned long *size void *map, *buf; struct pack_entry e; - if (find_pack_entry(sha1, &e)) + if (find_pack_entry(sha1, &e, NULL)) return read_packed_sha1(sha1, type, size); map = map_sha1_file(sha1, &mapsize); if (map) { @@ -1307,7 +1311,7 @@ void * read_sha1_file(const unsigned char *sha1, char *type, unsigned long *size return buf; } reprepare_packed_git(); - if (find_pack_entry(sha1, &e)) + if (find_pack_entry(sha1, &e, NULL)) return read_packed_sha1(sha1, type, size); return NULL; } @@ -1322,7 +1326,7 @@ void *read_object_with_reference(const unsigned char *sha1, unsigned long isize; unsigned char actual_sha1[20]; - memcpy(actual_sha1, sha1, 20); + hashcpy(actual_sha1, sha1); while (1) { int ref_length = -1; const char *ref_type = NULL; @@ -1333,7 +1337,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 */ @@ -1385,7 +1389,7 @@ char *write_sha1_file_prepare(void *buf, * * Returns the errno on failure, 0 on success. */ -static int link_temp_to_file(const char *tmpfile, char *filename) +static int link_temp_to_file(const char *tmpfile, const char *filename) { int ret; char *dir; @@ -1418,7 +1422,7 @@ static int link_temp_to_file(const char *tmpfile, char *filename) /* * Move the just written object into its final resting place */ -int move_temp_to_file(const char *tmpfile, char *filename) +int move_temp_to_file(const char *tmpfile, const char *filename) { int ret = link_temp_to_file(tmpfile, filename); @@ -1528,7 +1532,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); @@ -1715,7 +1719,7 @@ int write_sha1_from_fd(const unsigned char *sha1, int fd, char *buffer, unlink(tmpfile); return error("File %s corrupted", sha1_to_hex(sha1)); } - if (memcmp(sha1, real_sha1, 20)) { + if (hashcmp(sha1, real_sha1)) { unlink(tmpfile); return error("File %s has bad hash", sha1_to_hex(sha1)); } @@ -1739,10 +1743,10 @@ int has_pack_file(const unsigned char *sha1) return 1; } -int has_sha1_pack(const unsigned char *sha1) +int has_sha1_pack(const unsigned char *sha1, const char **ignore_packed) { struct pack_entry e; - return find_pack_entry(sha1, &e); + return find_pack_entry(sha1, &e, ignore_packed); } int has_sha1_file(const unsigned char *sha1) @@ -1750,7 +1754,7 @@ int has_sha1_file(const unsigned char *sha1) struct stat st; struct pack_entry e; - if (find_pack_entry(sha1, &e)) + if (find_pack_entry(sha1, &e, NULL)) return 1; return find_sha1_file(sha1, &st) ? 1 : 0; } @@ -1777,7 +1781,7 @@ int read_pipe(int fd, char** return_buf, unsigned long* return_size) off += iret; if (off == size) { size *= 2; - buf = realloc(buf, size); + buf = xrealloc(buf, size); } } } while (iret > 0); @@ -1793,7 +1797,7 @@ int read_pipe(int fd, char** return_buf, unsigned long* return_size) int index_pipe(unsigned char *sha1, int fd, const char *type, int write_object) { unsigned long size = 4096; - char *buf = malloc(size); + char *buf = xmalloc(size); int ret; unsigned char hdr[50]; int hdrlen; diff --git a/sha1_name.c b/sha1_name.c index c5a05faeb6..1fbc443805 100644 --- a/sha1_name.c +++ b/sha1_name.c @@ -84,7 +84,7 @@ static int find_short_packed_object(int len, const unsigned char *match, unsigne int cmp; nth_packed_object_sha1(p, mid, now); - cmp = memcmp(match, now, 20); + cmp = hashcmp(match, now); if (!cmp) { first = mid; break; @@ -103,10 +103,10 @@ static int find_short_packed_object(int len, const unsigned char *match, unsigne !match_sha(len, match, next)) { /* unique within this pack */ if (!found) { - memcpy(found_sha1, now, 20); + hashcpy(found_sha1, now); found++; } - else if (memcmp(found_sha1, now, 20)) { + else if (hashcmp(found_sha1, now)) { found = 2; break; } @@ -120,7 +120,7 @@ static int find_short_packed_object(int len, const unsigned char *match, unsigne } } if (found == 1) - memcpy(sha1, found_sha1, 20); + hashcpy(sha1, found_sha1); return found; } @@ -140,13 +140,13 @@ static int find_unique_short_object(int len, char *canonical, if (1 < has_unpacked || 1 < has_packed) return SHORT_NAME_AMBIGUOUS; if (has_unpacked != has_packed) { - memcpy(sha1, (has_packed ? packed_sha1 : unpacked_sha1), 20); + hashcpy(sha1, (has_packed ? packed_sha1 : unpacked_sha1)); return 0; } /* Both have unique ones -- do they match? */ - if (memcmp(packed_sha1, unpacked_sha1, 20)) + if (hashcmp(packed_sha1, unpacked_sha1)) return SHORT_NAME_AMBIGUOUS; - memcpy(sha1, packed_sha1, 20); + hashcpy(sha1, packed_sha1); return 0; } @@ -159,7 +159,7 @@ static int get_short_sha1(const char *name, int len, unsigned char *sha1, if (len < MINIMUM_ABBREV) return -1; - memset(res, 0, 20); + hashclr(res); memset(canonical, 'x', 40); for (i = 0; i < len ;i++) { unsigned char c = name[i]; @@ -191,7 +191,7 @@ const char *find_unique_abbrev(const unsigned char *sha1, int len) int status, is_null; static char hex[41]; - is_null = !memcmp(sha1, null_sha1, 20); + is_null = is_null_sha1(sha1); memcpy(hex, sha1_to_hex(sha1), 40); if (len == 40 || !len) return hex; @@ -279,7 +279,7 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1) pathname = resolve_ref(git_path(*p, len, str), this_result, 1); if (pathname) { if (!refs_found++) - real_path = strdup(pathname); + real_path = xstrdup(pathname); if (!warn_ambiguous_refs) break; } @@ -320,13 +320,13 @@ static int get_parent(const char *name, int len, if (parse_commit(commit)) return -1; if (!idx) { - memcpy(result, commit->object.sha1, 20); + hashcpy(result, commit->object.sha1); return 0; } p = commit->parents; while (p) { if (!--idx) { - memcpy(result, p->item->object.sha1, 20); + hashcpy(result, p->item->object.sha1); return 0; } p = p->next; @@ -347,9 +347,9 @@ static int get_nth_ancestor(const char *name, int len, if (!commit || parse_commit(commit) || !commit->parents) return -1; - memcpy(sha1, commit->parents->item->object.sha1, 20); + hashcpy(sha1, commit->parents->item->object.sha1); } - memcpy(result, sha1, 20); + hashcpy(result, sha1); return 0; } @@ -401,7 +401,7 @@ static int peel_onion(const char *name, int len, unsigned char *sha1) o = deref_tag(o, name, sp - name - 2); if (!o || (!o->parsed && !parse_object(o->sha1))) return -1; - memcpy(sha1, o->sha1, 20); + hashcpy(sha1, o->sha1); } else { /* At this point, the syntax look correct, so @@ -413,7 +413,7 @@ static int peel_onion(const char *name, int len, unsigned char *sha1) if (!o || (!o->parsed && !parse_object(o->sha1))) return -1; if (o->type == expected_type) { - memcpy(sha1, o->sha1, 20); + hashcpy(sha1, o->sha1); return 0; } if (o->type == OBJ_TAG) @@ -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/sideband.c b/sideband.c new file mode 100644 index 0000000000..1b14ff8892 --- /dev/null +++ b/sideband.c @@ -0,0 +1,74 @@ +#include "pkt-line.h" +#include "sideband.h" + +/* + * Receive multiplexed output stream over git native protocol. + * in_stream is the input stream from the remote, which carries data + * in pkt_line format with band designator. Demultiplex it into out + * and err and return error appropriately. Band #1 carries the + * primary payload. Things coming over band #2 is not necessarily + * error; they are usually informative message on the standard error + * stream, aka "verbose"). A message over band #3 is a signal that + * the remote died unexpectedly. A flush() concludes the stream. + */ +int recv_sideband(const char *me, int in_stream, int out, int err, char *buf, int bufsz) +{ + while (1) { + int len = packet_read_line(in_stream, buf, bufsz); + if (len == 0) + break; + if (len < 1) { + len = sprintf(buf, "%s: protocol error: no band designator\n", me); + safe_write(err, buf, len); + return SIDEBAND_PROTOCOL_ERROR; + } + len--; + switch (buf[0] & 0xFF) { + case 3: + safe_write(err, "remote: ", 8); + safe_write(err, buf+1, len); + safe_write(err, "\n", 1); + return SIDEBAND_REMOTE_ERROR; + case 2: + safe_write(err, "remote: ", 8); + safe_write(err, buf+1, len); + continue; + case 1: + safe_write(out, buf+1, len); + continue; + default: + len = sprintf(buf + 1, + "%s: protocol error: bad band #%d\n", + me, buf[0] & 0xFF); + safe_write(err, buf+1, len); + return SIDEBAND_PROTOCOL_ERROR; + } + } + return 0; +} + +/* + * fd is connected to the remote side; send the sideband data + * over multiplexed packet stream. + */ +ssize_t send_sideband(int fd, int band, const char *data, ssize_t sz, int packet_max) +{ + ssize_t ssz = sz; + const char *p = data; + + while (sz) { + unsigned n; + char hdr[5]; + + n = sz; + if (packet_max - 5 < n) + n = packet_max - 5; + sprintf(hdr, "%04x", n + 5); + hdr[4] = band; + safe_write(fd, hdr, 5); + safe_write(fd, p, n); + p += n; + sz -= n; + } + return ssz; +} diff --git a/sideband.h b/sideband.h new file mode 100644 index 0000000000..4872106fa0 --- /dev/null +++ b/sideband.h @@ -0,0 +1,13 @@ +#ifndef SIDEBAND_H +#define SIDEBAND_H + +#define SIDEBAND_PROTOCOL_ERROR -2 +#define SIDEBAND_REMOTE_ERROR -1 + +#define DEFAULT_PACKET_MAX 1000 +#define LARGE_PACKET_MAX 65520 + +int recv_sideband(const char *me, int in_stream, int out, int err, char *, int); +ssize_t send_sideband(int fd, int band, const char *data, ssize_t sz, int packet_max); + +#endif diff --git a/ssh-fetch.c b/ssh-fetch.c index c7d8fa80e4..b006c5c980 100644 --- a/ssh-fetch.c +++ b/ssh-fetch.c @@ -17,7 +17,7 @@ static int fd_in; static int fd_out; -static unsigned char remote_version = 0; +static unsigned char remote_version; static unsigned char local_version = 1; static ssize_t force_write(int fd, void *buffer, size_t length) @@ -36,9 +36,9 @@ static ssize_t force_write(int fd, void *buffer, size_t length) return ret; } -static int prefetches = 0; +static int prefetches; -static struct object_list *in_transit = NULL; +static struct object_list *in_transit; static struct object_list **end_of_transit = &in_transit; void prefetch(unsigned char *sha1) @@ -59,7 +59,7 @@ void prefetch(unsigned char *sha1) } static char conn_buf[4096]; -static size_t conn_buf_posn = 0; +static size_t conn_buf_posn; int fetch(unsigned char *sha1) { @@ -67,7 +67,7 @@ int fetch(unsigned char *sha1) signed char remote; struct object_list *temp; - if (memcmp(sha1, in_transit->item->sha1, 20)) { + if (hashcmp(sha1, in_transit->item->sha1)) { /* we must have already fetched it to clean the queue */ return has_sha1_file(sha1) ? 0 : -1; } diff --git a/ssh-upload.c b/ssh-upload.c index 2da66618fc..20b15eab57 100644 --- a/ssh-upload.c +++ b/ssh-upload.c @@ -15,9 +15,9 @@ #include <string.h> static unsigned char local_version = 1; -static unsigned char remote_version = 0; +static unsigned char remote_version; -static int verbose = 0; +static int verbose; static int serve_object(int fd_in, int fd_out) { ssize_t size; diff --git a/t/t1400-update-ref.sh b/t/t1400-update-ref.sh index ddc80bbeae..b3b920edb1 100755 --- a/t/t1400-update-ref.sh +++ b/t/t1400-update-ref.sh @@ -19,51 +19,51 @@ n=$n_dir/fixes test_expect_success \ "create $m" \ - 'git-update-ref $m $A && - test $A = $(cat .git/$m)' + "git-update-ref $m $A && + test $A"' = $(cat .git/'"$m"')' test_expect_success \ "create $m" \ - 'git-update-ref $m $B $A && - test $B = $(cat .git/$m)' + "git-update-ref $m $B $A && + test $B"' = $(cat .git/'"$m"')' rm -f .git/$m test_expect_success \ "fail to create $n" \ - 'touch .git/$n_dir + "touch .git/$n_dir git-update-ref $n $A >out 2>err - test $? = 1 && + test "'$? = 1 && test "" = "$(cat out)" && grep "error: unable to resolve reference" err && - grep $n err' + grep '"$n err" rm -f .git/$n_dir out err test_expect_success \ "create $m (by HEAD)" \ - 'git-update-ref HEAD $A && - test $A = $(cat .git/$m)' + "git-update-ref HEAD $A && + test $A"' = $(cat .git/'"$m"')' test_expect_success \ "create $m (by HEAD)" \ - 'git-update-ref HEAD $B $A && - test $B = $(cat .git/$m)' + "git-update-ref HEAD $B $A && + test $B"' = $(cat .git/'"$m"')' rm -f .git/$m test_expect_failure \ '(not) create HEAD with old sha1' \ - 'git-update-ref HEAD $A $B' + "git-update-ref HEAD $A $B" test_expect_failure \ "(not) prior created .git/$m" \ - 'test -f .git/$m' + "test -f .git/$m" rm -f .git/$m test_expect_success \ "create HEAD" \ - 'git-update-ref HEAD $A' + "git-update-ref HEAD $A" test_expect_failure \ '(not) change HEAD with wrong SHA1' \ - 'git-update-ref HEAD $B $Z' + "git-update-ref HEAD $B $Z" test_expect_failure \ "(not) changed .git/$m" \ - 'test $B = $(cat .git/$m)' + "test $B"' = $(cat .git/'"$m"')' rm -f .git/$m mkdir -p .git/logs/refs/heads @@ -71,18 +71,18 @@ touch .git/logs/refs/heads/master test_expect_success \ "create $m (logged by touch)" \ 'GIT_COMMITTER_DATE="2005-05-26 23:30" \ - git-update-ref HEAD $A -m "Initial Creation" && - test $A = $(cat .git/$m)' + git-update-ref HEAD '"$A"' -m "Initial Creation" && + test '"$A"' = $(cat .git/'"$m"')' test_expect_success \ "update $m (logged by touch)" \ 'GIT_COMMITTER_DATE="2005-05-26 23:31" \ - git-update-ref HEAD $B $A -m "Switch" && - test $B = $(cat .git/$m)' + git-update-ref HEAD'" $B $A "'-m "Switch" && + test '"$B"' = $(cat .git/'"$m"')' test_expect_success \ "set $m (logged by touch)" \ 'GIT_COMMITTER_DATE="2005-05-26 23:41" \ - git-update-ref HEAD $A && - test $A = $(cat .git/$m)' + git-update-ref HEAD'" $A && + test $A"' = $(cat .git/'"$m"')' cat >expect <<EOF $Z $A $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150200 +0000 Initial Creation @@ -91,7 +91,7 @@ $B $A $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150860 +0000 EOF test_expect_success \ "verifying $m's log" \ - 'diff expect .git/logs/$m' + "diff expect .git/logs/$m" rm -rf .git/$m .git/logs expect test_expect_success \ @@ -102,18 +102,18 @@ test_expect_success \ test_expect_success \ "create $m (logged by config)" \ 'GIT_COMMITTER_DATE="2005-05-26 23:32" \ - git-update-ref HEAD $A -m "Initial Creation" && - test $A = $(cat .git/$m)' + git-update-ref HEAD'" $A "'-m "Initial Creation" && + test '"$A"' = $(cat .git/'"$m"')' test_expect_success \ "update $m (logged by config)" \ 'GIT_COMMITTER_DATE="2005-05-26 23:33" \ - git-update-ref HEAD $B $A -m "Switch" && - test $B = $(cat .git/$m)' + git-update-ref HEAD'" $B $A "'-m "Switch" && + test '"$B"' = $(cat .git/'"$m"')' test_expect_success \ "set $m (logged by config)" \ 'GIT_COMMITTER_DATE="2005-05-26 23:43" \ - git-update-ref HEAD $A && - test $A = $(cat .git/$m)' + git-update-ref HEAD '"$A && + test $A"' = $(cat .git/'"$m"')' cat >expect <<EOF $Z $A $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150320 +0000 Initial Creation @@ -140,50 +140,50 @@ test_expect_success \ 'Query "master@{May 25 2005}" (before history)' \ 'rm -f o e git-rev-parse --verify "master@{May 25 2005}" >o 2>e && - test $C = $(cat o) && - test "warning: Log .git/logs/$m only goes back to $ed." = "$(cat e)"' + test '"$C"' = $(cat o) && + test "warning: Log .git/logs/'"$m only goes back to $ed"'." = "$(cat e)"' test_expect_success \ "Query master@{2005-05-25} (before history)" \ 'rm -f o e git-rev-parse --verify master@{2005-05-25} >o 2>e && - test $C = $(cat o) && - echo test "warning: Log .git/logs/$m only goes back to $ed." = "$(cat e)"' + test '"$C"' = $(cat o) && + echo test "warning: Log .git/logs/'"$m only goes back to $ed"'." = "$(cat e)"' test_expect_success \ 'Query "master@{May 26 2005 23:31:59}" (1 second before history)' \ 'rm -f o e git-rev-parse --verify "master@{May 26 2005 23:31:59}" >o 2>e && - test $C = $(cat o) && - test "warning: Log .git/logs/$m only goes back to $ed." = "$(cat e)"' + test '"$C"' = $(cat o) && + test "warning: Log .git/logs/'"$m only goes back to $ed"'." = "$(cat e)"' test_expect_success \ 'Query "master@{May 26 2005 23:32:00}" (exactly history start)' \ 'rm -f o e git-rev-parse --verify "master@{May 26 2005 23:32:00}" >o 2>e && - test $A = $(cat o) && + test '"$A"' = $(cat o) && test "" = "$(cat e)"' test_expect_success \ 'Query "master@{2005-05-26 23:33:01}" (middle of history with gap)' \ 'rm -f o e git-rev-parse --verify "master@{2005-05-26 23:33:01}" >o 2>e && - test $B = $(cat o) && - test "warning: Log .git/logs/$m has gap after $gd." = "$(cat e)"' + test '"$B"' = $(cat o) && + test "warning: Log .git/logs/'"$m has gap after $gd"'." = "$(cat e)"' test_expect_success \ 'Query "master@{2005-05-26 23:38:00}" (middle of history)' \ 'rm -f o e git-rev-parse --verify "master@{2005-05-26 23:38:00}" >o 2>e && - test $Z = $(cat o) && + test '"$Z"' = $(cat o) && test "" = "$(cat e)"' test_expect_success \ 'Query "master@{2005-05-26 23:43:00}" (exact end of history)' \ 'rm -f o e git-rev-parse --verify "master@{2005-05-26 23:43:00}" >o 2>e && - test $E = $(cat o) && + test '"$E"' = $(cat o) && test "" = "$(cat e)"' test_expect_success \ 'Query "master@{2005-05-28}" (past end of history)' \ 'rm -f o e git-rev-parse --verify "master@{2005-05-28}" >o 2>e && - test $D = $(cat o) && - test "warning: Log .git/logs/$m unexpectedly ended on $ld." = "$(cat e)"' + test '"$D"' = $(cat o) && + test "warning: Log .git/logs/'"$m unexpectedly ended on $ld"'." = "$(cat e)"' rm -f .git/$m .git/logs/$m expect @@ -221,7 +221,7 @@ $h_FIXED $h_MERGED $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117151100 +0000 c EOF test_expect_success \ 'git-commit logged updates' \ - 'diff expect .git/logs/$m' + "diff expect .git/logs/$m" unset h_TEST h_OTHER h_FIXED h_MERGED test_expect_success \ diff --git a/t/t3403-rebase-skip.sh b/t/t3403-rebase-skip.sh index 8ab63c5276..bb25315361 100755 --- a/t/t3403-rebase-skip.sh +++ b/t/t3403-rebase-skip.sh @@ -37,7 +37,9 @@ test_expect_success setup ' git branch skip-merge skip-reference ' -test_expect_failure 'rebase with git am -3 (default)' 'git rebase master' +test_expect_failure 'rebase with git am -3 (default)' ' + git rebase master +' test_expect_success 'rebase --skip with am -3' ' git reset --hard HEAD && diff --git a/t/t4103-apply-binary.sh b/t/t4103-apply-binary.sh index ff052699a2..e2b1124c78 100755 --- a/t/t4103-apply-binary.sh +++ b/t/t4103-apply-binary.sh @@ -94,11 +94,11 @@ test_expect_failure 'apply binary diff (copy) -- should fail.' \ 'do_reset git-apply --index C.diff' -test_expect_failure 'apply binary diff without replacement -- should fail.' \ +test_expect_success 'apply binary diff without replacement.' \ 'do_reset git-apply BF.diff' -test_expect_failure 'apply binary diff without replacement (copy) -- should fail.' \ +test_expect_success 'apply binary diff without replacement (copy).' \ 'do_reset git-apply CF.diff' diff --git a/t/t4104-apply-boundary.sh b/t/t4104-apply-boundary.sh new file mode 100755 index 0000000000..2ff800c23f --- /dev/null +++ b/t/t4104-apply-boundary.sh @@ -0,0 +1,115 @@ +#!/bin/sh +# +# Copyright (c) 2005 Junio C Hamano +# + +test_description='git-apply boundary tests + +' +. ./test-lib.sh + +L="c d e f g h i j k l m n o p q r s t u v w x" + +test_expect_success setup ' + for i in b '"$L"' y + do + echo $i + done >victim && + cat victim >original && + git update-index --add victim && + + : add to the head + for i in a b '"$L"' y + do + echo $i + done >victim && + cat victim >add-a-expect && + git diff victim >add-a-patch.with && + git diff --unified=0 >add-a-patch.without && + + : modify at the head + for i in a '"$L"' y + do + echo $i + done >victim && + cat victim >mod-a-expect && + git diff victim >mod-a-patch.with && + git diff --unified=0 >mod-a-patch.without && + + : remove from the head + for i in '"$L"' y + do + echo $i + done >victim && + cat victim >del-a-expect && + git diff victim >del-a-patch.with + git diff --unified=0 >del-a-patch.without && + + : add to the tail + for i in b '"$L"' y z + do + echo $i + done >victim && + cat victim >add-z-expect && + git diff victim >add-z-patch.with && + git diff --unified=0 >add-z-patch.without && + + : modify at the tail + for i in a '"$L"' y + do + echo $i + done >victim && + cat victim >mod-z-expect && + git diff victim >mod-z-patch.with && + git diff --unified=0 >mod-z-patch.without && + + : remove from the tail + for i in b '"$L"' + do + echo $i + done >victim && + cat victim >del-z-expect && + git diff victim >del-z-patch.with + git diff --unified=0 >del-z-patch.without && + + : done +' + +for with in with without +do + case "$with" in + with) u= ;; + without) u='--unidiff-zero ' ;; + esac + for kind in add-a add-z mod-a mod-z del-a del-z + do + test_expect_success "apply $kind-patch $with context" ' + cat original >victim && + git update-index victim && + git apply --index '"$u$kind-patch.$with"' || { + cat '"$kind-patch.$with"' + (exit 1) + } && + diff -u '"$kind"'-expect victim + ' + done +done + +for kind in add-a add-z mod-a mod-z del-a del-z +do + rm -f $kind-ng.without + sed -e "s/^diff --git /diff /" \ + -e '/^index /d' \ + <$kind-patch.without >$kind-ng.without + test_expect_success "apply non-git $kind-patch without context" ' + cat original >victim && + git update-index victim && + git apply --unidiff-zero --index '"$kind-ng.without"' || { + cat '"$kind-ng.without"' + (exit 1) + } && + diff -u '"$kind"'-expect victim + ' +done + +test_done diff --git a/t/t4116-apply-reverse.sh b/t/t4116-apply-reverse.sh new file mode 100755 index 0000000000..74f5c2a575 --- /dev/null +++ b/t/t4116-apply-reverse.sh @@ -0,0 +1,85 @@ +#!/bin/sh +# +# Copyright (c) 2005 Junio C Hamano +# + +test_description='git-apply in reverse + +' + +. ./test-lib.sh + +test_expect_success setup ' + + for i in a b c d e f g h i j k l m n; do echo $i; done >file1 && + tr "[ijk]" '\''[\0\1\2]'\'' <file1 >file2 && + + git add file1 file2 && + git commit -m initial && + git tag initial && + + for i in a b c g h i J K L m o n p q; do echo $i; done >file1 && + tr "[mon]" '\''[\0\1\2]'\'' <file1 >file2 && + + git commit -a -m second && + git tag second && + + git diff --binary initial second >patch + +' + +test_expect_success 'apply in forward' ' + + T0=`git rev-parse "second^{tree}"` && + git reset --hard initial && + git apply --index --binary patch && + T1=`git write-tree` && + test "$T0" = "$T1" +' + +test_expect_success 'apply in reverse' ' + + git reset --hard second && + git apply --reverse --binary --index patch && + git diff >diff && + diff -u /dev/null diff + +' + +test_expect_success 'setup separate repository lacking postimage' ' + + git tar-tree initial initial | tar xf - && + ( + cd initial && git init-db && git add . + ) && + + git tar-tree second second | tar xf - && + ( + cd second && git init-db && git add . + ) + +' + +test_expect_success 'apply in forward without postimage' ' + + T0=`git rev-parse "second^{tree}"` && + ( + cd initial && + git apply --index --binary ../patch && + T1=`git write-tree` && + test "$T0" = "$T1" + ) +' + +test_expect_success 'apply in reverse without postimage' ' + + T0=`git rev-parse "initial^{tree}"` && + ( + cd second && + git apply --index --binary --reverse ../patch && + T1=`git write-tree` && + test "$T0" = "$T1" + ) +' + +test_done diff --git a/t/t4117-apply-reject.sh b/t/t4117-apply-reject.sh new file mode 100755 index 0000000000..b4de075a3e --- /dev/null +++ b/t/t4117-apply-reject.sh @@ -0,0 +1,157 @@ +#!/bin/sh +# +# Copyright (c) 2005 Junio C Hamano +# + +test_description='git-apply with rejects + +' + +. ./test-lib.sh + +test_expect_success setup ' + for i in 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 + do + echo $i + done >file1 && + cat file1 >saved.file1 && + git update-index --add file1 && + git commit -m initial && + + for i in 1 2 A B 4 5 6 7 8 9 10 11 12 C 13 14 15 16 17 18 19 20 D 21 + do + echo $i + done >file1 && + git diff >patch.1 && + cat file1 >clean && + + for i in 1 E 2 3 4 5 6 7 8 9 10 11 12 C 13 14 15 16 17 18 19 20 F 21 + do + echo $i + done >expected && + + mv file1 file2 && + git update-index --add --remove file1 file2 && + git diff -M HEAD >patch.2 && + + rm -f file1 file2 && + mv saved.file1 file1 && + git update-index --add --remove file1 file2 && + + for i in 1 E 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 F 21 + do + echo $i + done >file1 && + + cat file1 >saved.file1 +' + +test_expect_success 'apply without --reject should fail' ' + + if git apply patch.1 + then + echo "Eh? Why?" + exit 1 + fi + + diff -u file1 saved.file1 +' + +test_expect_success 'apply without --reject should fail' ' + + if git apply --verbose patch.1 + then + echo "Eh? Why?" + exit 1 + fi + + diff -u file1 saved.file1 +' + +test_expect_success 'apply with --reject should fail but update the file' ' + + cat saved.file1 >file1 && + rm -f file1.rej file2.rej && + + if git apply --reject patch.1 + then + echo "succeeds with --reject?" + exit 1 + fi + + diff -u file1 expected && + + cat file1.rej && + + if test -f file2.rej + then + echo "file2 should not have been touched" + exit 1 + fi +' + +test_expect_success 'apply with --reject should fail but update the file' ' + + cat saved.file1 >file1 && + rm -f file1.rej file2.rej file2 && + + if git apply --reject patch.2 >rejects + then + echo "succeeds with --reject?" + exit 1 + fi + + test -f file1 && { + echo "file1 still exists?" + exit 1 + } + diff -u file2 expected && + + cat file2.rej && + + if test -f file1.rej + then + echo "file2 should not have been touched" + exit 1 + fi + +' + +test_expect_success 'the same test with --verbose' ' + + cat saved.file1 >file1 && + rm -f file1.rej file2.rej file2 && + + if git apply --reject --verbose patch.2 >rejects + then + echo "succeeds with --reject?" + exit 1 + fi + + test -f file1 && { + echo "file1 still exists?" + exit 1 + } + diff -u file2 expected && + + cat file2.rej && + + if test -f file1.rej + then + echo "file2 should not have been touched" + exit 1 + fi + +' + +test_expect_success 'apply cleanly with --verbose' ' + + git cat-file -p HEAD:file1 >file1 && + rm -f file?.rej file2 && + + git apply --verbose patch.1 && + + diff -u file1 clean +' + +test_done diff --git a/t/t5710-info-alternate.sh b/t/t5710-info-alternate.sh index 2e1b48a89b..b9f6d96363 100755 --- a/t/t5710-info-alternate.sh +++ b/t/t5710-info-alternate.sh @@ -58,6 +58,8 @@ test_expect_failure 'creating too deep nesting' \ git clone -l -s D E && git clone -l -s E F && git clone -l -s F G && +git clone -l -s G H && +cd H && test_valid_repo' cd "$base_dir" diff --git a/t/t7001-mv.sh b/t/t7001-mv.sh index 900ca93cde..b7fcdb390c 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/test-lib.sh b/t/test-lib.sh index 470a909891..e2629339d4 100755 --- a/t/test-lib.sh +++ b/t/test-lib.sh @@ -28,13 +28,21 @@ unset GIT_DIR unset GIT_EXTERNAL_DIFF unset GIT_INDEX_FILE unset GIT_OBJECT_DIRECTORY -unset GIT_TRACE unset SHA1_FILE_DIRECTORIES unset SHA1_FILE_DIRECTORY export GIT_AUTHOR_EMAIL GIT_AUTHOR_NAME export GIT_COMMITTER_EMAIL GIT_COMMITTER_NAME export EDITOR VISUAL +case $(echo $GIT_TRACE |tr [A-Z] [a-z]) in + 1|2|true) + echo "* warning: Some tests will not work if GIT_TRACE" \ + "is set as to trace on STDERR ! *" + echo "* warning: Please set GIT_TRACE to something" \ + "other than 1, 2 or true ! *" + ;; +esac + # Each test should start with something like this, after copyright notices: # # test_description='Description of this test... diff --git a/templates/hooks--commit-msg b/templates/hooks--commit-msg index 643822d235..0b906caa98 100644 --- a/templates/hooks--commit-msg +++ b/templates/hooks--commit-msg @@ -11,4 +11,8 @@ # This example catches duplicate Signed-off-by lines. test "" = "$(grep '^Signed-off-by: ' "$1" | - sort | uniq -c | sed -e '/^[ ]*1 /d')" + sort | uniq -c | sed -e '/^[ ]*1[ ]/d')" || { + echo >&2 Duplicate Signed-off-by lines. + exit 1 +} + diff --git a/trace.c b/trace.c new file mode 100644 index 0000000000..f9efc918b8 --- /dev/null +++ b/trace.c @@ -0,0 +1,149 @@ +/* + * GIT - The information manager from hell + * + * Copyright (C) 2000-2002 Michael R. Elkins <me@mutt.org> + * Copyright (C) 2002-2004 Oswald Buddenhagen <ossi@users.sf.net> + * Copyright (C) 2004 Theodore Y. Ts'o <tytso@mit.edu> + * Copyright (C) 2006 Mike McCormack + * Copyright (C) 2006 Christian Couder + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "cache.h" +#include "quote.h" + +/* Stolen from "imap-send.c". */ +static int git_vasprintf(char **strp, const char *fmt, va_list ap) +{ + int len; + char tmp[1024]; + + if ((len = vsnprintf(tmp, sizeof(tmp), fmt, ap)) < 0 || + !(*strp = xmalloc(len + 1))) + return -1; + if (len >= (int)sizeof(tmp)) + vsprintf(*strp, fmt, ap); + else + memcpy(*strp, tmp, len + 1); + return len; +} + +/* Stolen from "imap-send.c". */ +int nfvasprintf(char **str, const char *fmt, va_list va) +{ + int ret = git_vasprintf(str, fmt, va); + if (ret < 0) + die("Fatal: Out of memory\n"); + return ret; +} + +/* Get a trace file descriptor from GIT_TRACE env variable. */ +static int get_trace_fd(int *need_close) +{ + char *trace = getenv("GIT_TRACE"); + + if (!trace || !strcmp(trace, "0") || !strcasecmp(trace, "false")) + return 0; + if (!strcmp(trace, "1") || !strcasecmp(trace, "true")) + return STDERR_FILENO; + if (strlen(trace) == 1 && isdigit(*trace)) + return atoi(trace); + if (*trace == '/') { + int fd = open(trace, O_WRONLY | O_APPEND | O_CREAT, 0666); + if (fd == -1) { + fprintf(stderr, + "Could not open '%s' for tracing: %s\n" + "Defaulting to tracing on stderr...\n", + trace, strerror(errno)); + return STDERR_FILENO; + } + *need_close = 1; + return fd; + } + + fprintf(stderr, "What does '%s' for GIT_TRACE means ?\n", trace); + fprintf(stderr, "If you want to trace into a file, " + "then please set GIT_TRACE to an absolute pathname " + "(starting with /).\n"); + fprintf(stderr, "Defaulting to tracing on stderr...\n"); + + return STDERR_FILENO; +} + +static const char err_msg[] = "Could not trace into fd given by " + "GIT_TRACE environment variable"; + +void trace_printf(const char *format, ...) +{ + char *trace_str; + va_list rest; + int need_close = 0; + int fd = get_trace_fd(&need_close); + + if (!fd) + return; + + va_start(rest, format); + nfvasprintf(&trace_str, format, rest); + va_end(rest); + + write_or_whine(fd, trace_str, strlen(trace_str), err_msg); + + free(trace_str); + + if (need_close) + close(fd); +} + +void trace_argv_printf(const char **argv, int count, const char *format, ...) +{ + char *argv_str, *format_str, *trace_str; + size_t argv_len, format_len, trace_len; + va_list rest; + int need_close = 0; + int fd = get_trace_fd(&need_close); + + if (!fd) + return; + + /* Get the argv string. */ + argv_str = sq_quote_argv(argv, count); + argv_len = strlen(argv_str); + + /* Get the formated string. */ + va_start(rest, format); + nfvasprintf(&format_str, format, rest); + va_end(rest); + + /* Allocate buffer for trace string. */ + format_len = strlen(format_str); + trace_len = argv_len + format_len + 1; /* + 1 for \n */ + trace_str = xmalloc(trace_len + 1); + + /* Copy everything into the trace string. */ + strncpy(trace_str, format_str, format_len); + strncpy(trace_str + format_len, argv_str, argv_len); + strcpy(trace_str + trace_len - 1, "\n"); + + write_or_whine(fd, trace_str, trace_len, err_msg); + + free(argv_str); + free(format_str); + free(trace_str); + + if (need_close) + close(fd); +} diff --git a/tree-diff.c b/tree-diff.c index 1cdf8aa908..7e2f4f088a 100644 --- a/tree-diff.c +++ b/tree-diff.c @@ -15,7 +15,8 @@ static char *malloc_base(const char *base, const char *path, int pathlen) return newbase; } -static int show_entry(struct diff_options *opt, const char *prefix, struct tree_desc *desc, const char *base); +static void show_entry(struct diff_options *opt, const char *prefix, struct tree_desc *desc, + const char *base); static int compare_tree_entry(struct tree_desc *t1, struct tree_desc *t2, const char *base, struct diff_options *opt) { @@ -38,8 +39,7 @@ static int compare_tree_entry(struct tree_desc *t1, struct tree_desc *t2, const show_entry(opt, "+", t2, base); return 1; } - if (!opt->find_copies_harder && - !memcmp(sha1, sha2, 20) && mode1 == mode2) + if (!opt->find_copies_harder && !hashcmp(sha1, sha2) && mode1 == mode2) return 0; /* @@ -131,7 +131,8 @@ static void show_tree(struct diff_options *opt, const char *prefix, struct tree_ } /* A file entry went away or appeared */ -static int show_entry(struct diff_options *opt, const char *prefix, struct tree_desc *desc, const char *base) +static void show_entry(struct diff_options *opt, const char *prefix, struct tree_desc *desc, + const char *base) { unsigned mode; const char *path; @@ -152,11 +153,9 @@ static int show_entry(struct diff_options *opt, const char *prefix, struct tree_ free(tree); free(newbase); - return 0; + } else { + opt->add_remove(opt, prefix[0], mode, sha1, base, path); } - - opt->add_remove(opt, prefix[0], mode, sha1, base, path); - return 0; } int diff_tree(struct tree_desc *t1, struct tree_desc *t2, const char *base, struct diff_options *opt) diff --git a/tree-walk.c b/tree-walk.c index 3f83e98f3a..14cc5aea6c 100644 --- a/tree-walk.c +++ b/tree-walk.c @@ -179,7 +179,7 @@ static int find_tree_entry(struct tree_desc *t, const char *name, unsigned char if (cmp < 0) break; if (entrylen == namelen) { - memcpy(result, sha1, 20); + hashcpy(result, sha1); return 0; } if (name[entrylen] != '/') @@ -187,7 +187,7 @@ static int find_tree_entry(struct tree_desc *t, const char *name, unsigned char if (!S_ISDIR(*mode)) break; if (++entrylen == namelen) { - memcpy(result, sha1, 20); + hashcpy(result, sha1); return 0; } return get_tree_entry(sha1, name + entrylen, result, mode); @@ -25,7 +25,7 @@ static int read_one_entry(const unsigned char *sha1, const char *base, int basel ce->ce_flags = create_ce_flags(baselen + len, stage); memcpy(ce->name, base, baselen); memcpy(ce->name + baselen, pathname, len+1); - memcpy(ce->sha1, sha1, 20); + hashcpy(ce->sha1, sha1); return add_cache_entry(ce, ADD_CACHE_OK_TO_ADD|ADD_CACHE_SKIP_DFCHECK); } @@ -144,7 +144,7 @@ struct tree *lookup_tree(const unsigned char *sha1) return (struct tree *) obj; } -static int track_tree_refs(struct tree *item) +static void track_tree_refs(struct tree *item) { int n_refs = 0, i; struct object_refs *refs; @@ -174,7 +174,6 @@ static int track_tree_refs(struct tree *item) refs->ref[i++] = obj; } set_object_refs(&item->object, refs); - return 0; } int parse_tree_buffer(struct tree *item, void *buffer, unsigned long size) diff --git a/unpack-trees.c b/unpack-trees.c index a20639be70..3ac0289b3a 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 35c7ecb7b4..189b239cc0 100644 --- a/upload-pack.c +++ b/upload-pack.c @@ -4,6 +4,7 @@ #include "cache.h" #include "refs.h" #include "pkt-line.h" +#include "sideband.h" #include "tag.h" #include "object.h" #include "commit.h" @@ -14,12 +15,15 @@ static const char upload_pack_usage[] = "git-upload-pack [--strict] [--timeout=n #define THEY_HAVE (1U << 0) #define OUR_REF (1U << 1) #define WANTED (1U << 2) -static int multi_ack = 0, nr_our_refs = 0; -static int use_thin_pack = 0; +static int multi_ack, nr_our_refs; +static int use_thin_pack; static struct object_array have_obj; static struct object_array want_obj; -static unsigned int timeout = 0; -static int use_sideband = 0; +static unsigned int timeout; +/* 0 for no sideband, + * otherwise maximum packet size (up to 65520 bytes). + */ +static int use_sideband; static void reset_timeout(void) { @@ -33,45 +37,18 @@ static int strip(char *line, int len) return len; } -#define PACKET_MAX 1000 static ssize_t send_client_data(int fd, const char *data, ssize_t sz) { - ssize_t ssz; - const char *p; - - if (!data) { - if (!use_sideband) - return 0; - packet_flush(1); - } - - if (!use_sideband) { - if (fd == 3) - /* emergency quit */ - fd = 2; - if (fd == 2) { - xwrite(fd, data, sz); - return sz; - } - return safe_write(fd, data, sz); + if (use_sideband) + return send_sideband(1, fd, data, sz, use_sideband); + if (fd == 3) + /* emergency quit */ + fd = 2; + if (fd == 2) { + xwrite(fd, data, sz); + return sz; } - p = data; - ssz = sz; - while (sz) { - unsigned n; - char hdr[5]; - - n = sz; - if (PACKET_MAX - 5 < n) - n = PACKET_MAX - 5; - sprintf(hdr, "%04x", n + 5); - hdr[4] = fd; - safe_write(1, hdr, 5); - safe_write(1, p, n); - p += n; - sz -= n; - } - return ssz; + return safe_write(fd, data, sz); } static void create_pack_file(void) @@ -308,7 +285,8 @@ static void create_pack_file(void) goto fail; fprintf(stderr, "flushed.\n"); } - send_client_data(1, NULL, 0); + if (use_sideband) + packet_flush(1); return; } fail: @@ -415,8 +393,10 @@ static void receive_needs(void) multi_ack = 1; if (strstr(line+45, "thin-pack")) use_thin_pack = 1; - if (strstr(line+45, "side-band")) - use_sideband = 1; + if (strstr(line+45, "side-band-64k")) + use_sideband = LARGE_PACKET_MAX; + else if (strstr(line+45, "side-band")) + use_sideband = DEFAULT_PACKET_MAX; /* We have sent all our refs already, and the other end * should have chosen out of them; otherwise they are @@ -438,7 +418,7 @@ static void receive_needs(void) static int send_ref(const char *refname, const unsigned char *sha1) { - static const char *capabilities = "multi_ack thin-pack side-band"; + static const char *capabilities = "multi_ack thin-pack side-band side-band-64k"; struct object *o = parse_object(sha1); if (!o) @@ -461,18 +441,17 @@ static int send_ref(const char *refname, const unsigned char *sha1) return 0; } -static int upload_pack(void) +static void upload_pack(void) { reset_timeout(); head_ref(send_ref); for_each_ref(send_ref); packet_flush(1); receive_needs(); - if (!want_obj.nr) - return 0; - get_common_commits(); - create_pack_file(); - return 0; + if (want_obj.nr) { + get_common_commits(); + create_pack_file(); + } } int main(int argc, char **argv) diff --git a/write_or_die.c b/write_or_die.c new file mode 100644 index 0000000000..bfe4eeb649 --- /dev/null +++ b/write_or_die.c @@ -0,0 +1,45 @@ +#include "cache.h" + +void write_or_die(int fd, const void *buf, size_t count) +{ + const char *p = buf; + ssize_t written; + + while (count > 0) { + written = xwrite(fd, p, count); + if (written == 0) + die("disk full?"); + else if (written < 0) { + if (errno == EPIPE) + exit(0); + die("write error (%s)", strerror(errno)); + } + count -= written; + p += written; + } +} + +int write_or_whine(int fd, const void *buf, size_t count, const char *msg) +{ + const char *p = buf; + ssize_t written; + + while (count > 0) { + written = xwrite(fd, p, count); + if (written == 0) { + fprintf(stderr, "%s: disk full?\n", msg); + return 0; + } + else if (written < 0) { + if (errno == EPIPE) + exit(0); + fprintf(stderr, "%s: write error (%s)\n", + msg, strerror(errno)); + return 0; + } + count -= written; + p += written; + } + + return 1; +} diff --git a/wt-status.c b/wt-status.c new file mode 100644 index 0000000000..4b74e68584 --- /dev/null +++ b/wt-status.c @@ -0,0 +1,276 @@ +#include "wt-status.h" +#include "color.h" +#include "cache.h" +#include "object.h" +#include "dir.h" +#include "commit.h" +#include "diff.h" +#include "revision.h" +#include "diffcore.h" + +int wt_status_use_color = 0; +static char wt_status_colors[][COLOR_MAXLEN] = { + "", /* WT_STATUS_HEADER: normal */ + "\033[32m", /* WT_STATUS_UPDATED: green */ + "\033[31m", /* WT_STATUS_CHANGED: red */ + "\033[31m", /* WT_STATUS_UNTRACKED: red */ +}; + +static int parse_status_slot(const char *var, int offset) +{ + if (!strcasecmp(var+offset, "header")) + return WT_STATUS_HEADER; + if (!strcasecmp(var+offset, "updated")) + return WT_STATUS_UPDATED; + if (!strcasecmp(var+offset, "changed")) + return WT_STATUS_CHANGED; + if (!strcasecmp(var+offset, "untracked")) + return WT_STATUS_UNTRACKED; + die("bad config variable '%s'", var); +} + +static const char* color(int slot) +{ + return wt_status_use_color ? wt_status_colors[slot] : ""; +} + +void wt_status_prepare(struct wt_status *s) +{ + unsigned char sha1[20]; + const char *head; + + s->is_initial = get_sha1("HEAD", sha1) ? 1 : 0; + + head = resolve_ref(git_path("HEAD"), sha1, 0); + s->branch = head ? + strdup(head + strlen(get_git_dir()) + 1) : + NULL; + + s->reference = "HEAD"; + s->amend = 0; + s->verbose = 0; + s->commitable = 0; + s->untracked = 0; +} + +static void wt_status_print_header(const char *main, const char *sub) +{ + const char *c = color(WT_STATUS_HEADER); + color_printf_ln(c, "# %s:", main); + color_printf_ln(c, "# (%s)", sub); + color_printf_ln(c, "#"); +} + +static void wt_status_print_trailer(void) +{ + color_printf_ln(color(WT_STATUS_HEADER), "#"); +} + +static void wt_status_print_filepair(int t, struct diff_filepair *p) +{ + const char *c = color(t); + color_printf(color(WT_STATUS_HEADER), "#\t"); + switch (p->status) { + case DIFF_STATUS_ADDED: + color_printf(c, "new file: %s", p->one->path); break; + case DIFF_STATUS_COPIED: + color_printf(c, "copied: %s -> %s", + p->one->path, p->two->path); + break; + case DIFF_STATUS_DELETED: + color_printf(c, "deleted: %s", p->one->path); break; + case DIFF_STATUS_MODIFIED: + color_printf(c, "modified: %s", p->one->path); break; + case DIFF_STATUS_RENAMED: + color_printf(c, "renamed: %s -> %s", + p->one->path, p->two->path); + break; + case DIFF_STATUS_TYPE_CHANGED: + color_printf(c, "typechange: %s", p->one->path); break; + case DIFF_STATUS_UNKNOWN: + color_printf(c, "unknown: %s", p->one->path); break; + case DIFF_STATUS_UNMERGED: + color_printf(c, "unmerged: %s", p->one->path); break; + default: + die("bug: unhandled diff status %c", p->status); + } + printf("\n"); +} + +static void wt_status_print_updated_cb(struct diff_queue_struct *q, + struct diff_options *options, + void *data) +{ + struct wt_status *s = data; + int shown_header = 0; + int i; + if (q->nr) { + } + for (i = 0; i < q->nr; i++) { + if (q->queue[i]->status == 'U') + continue; + if (!shown_header) { + wt_status_print_header("Updated but not checked in", + "will commit"); + s->commitable = 1; + shown_header = 1; + } + wt_status_print_filepair(WT_STATUS_UPDATED, q->queue[i]); + } + if (shown_header) + wt_status_print_trailer(); +} + +static void wt_status_print_changed_cb(struct diff_queue_struct *q, + struct diff_options *options, + void *data) +{ + int i; + if (q->nr) + wt_status_print_header("Changed but not updated", + "use git-update-index to mark for commit"); + for (i = 0; i < q->nr; i++) + wt_status_print_filepair(WT_STATUS_CHANGED, q->queue[i]); + if (q->nr) + wt_status_print_trailer(); +} + +void wt_status_print_initial(struct wt_status *s) +{ + int i; + read_cache(); + if (active_nr) { + s->commitable = 1; + wt_status_print_header("Updated but not checked in", + "will commit"); + } + for (i = 0; i < active_nr; i++) { + color_printf(color(WT_STATUS_HEADER), "#\t"); + color_printf_ln(color(WT_STATUS_UPDATED), "new file: %s", + active_cache[i]->name); + } + if (active_nr) + wt_status_print_trailer(); +} + +static void wt_status_print_updated(struct wt_status *s) +{ + struct rev_info rev; + const char *argv[] = { NULL, NULL, NULL }; + argv[1] = s->reference; + init_revisions(&rev, NULL); + setup_revisions(2, argv, &rev, NULL); + rev.diffopt.output_format |= DIFF_FORMAT_CALLBACK; + rev.diffopt.format_callback = wt_status_print_updated_cb; + rev.diffopt.format_callback_data = s; + rev.diffopt.detect_rename = 1; + run_diff_index(&rev, 1); +} + +static void wt_status_print_changed(struct wt_status *s) +{ + struct rev_info rev; + const char *argv[] = { NULL, NULL }; + init_revisions(&rev, ""); + setup_revisions(1, argv, &rev, NULL); + rev.diffopt.output_format |= DIFF_FORMAT_CALLBACK; + rev.diffopt.format_callback = wt_status_print_changed_cb; + rev.diffopt.format_callback_data = s; + run_diff_files(&rev, 0); +} + +static void wt_status_print_untracked(const struct wt_status *s) +{ + struct dir_struct dir; + const char *x; + int i; + int shown_header = 0; + + memset(&dir, 0, sizeof(dir)); + + dir.exclude_per_dir = ".gitignore"; + if (!s->untracked) { + dir.show_other_directories = 1; + dir.hide_empty_directories = 1; + } + x = git_path("info/exclude"); + if (file_exists(x)) + add_excludes_from_file(&dir, x); + + read_directory(&dir, ".", "", 0); + for(i = 0; i < dir.nr; i++) { + /* check for matching entry, which is unmerged; lifted from + * builtin-ls-files:show_other_files */ + struct dir_entry *ent = dir.entries[i]; + int pos = cache_name_pos(ent->name, ent->len); + struct cache_entry *ce; + if (0 <= pos) + die("bug in wt_status_print_untracked"); + pos = -pos - 1; + if (pos < active_nr) { + ce = active_cache[pos]; + if (ce_namelen(ce) == ent->len && + !memcmp(ce->name, ent->name, ent->len)) + continue; + } + if (!shown_header) { + wt_status_print_header("Untracked files", + "use \"git add\" to add to commit"); + shown_header = 1; + } + color_printf(color(WT_STATUS_HEADER), "#\t"); + color_printf_ln(color(WT_STATUS_UNTRACKED), "%.*s", + ent->len, ent->name); + } +} + +static void wt_status_print_verbose(struct wt_status *s) +{ + struct rev_info rev; + const char *argv[] = { NULL, NULL, NULL }; + argv[1] = s->reference; + init_revisions(&rev, NULL); + setup_revisions(2, argv, &rev, NULL); + rev.diffopt.output_format |= DIFF_FORMAT_PATCH; + rev.diffopt.detect_rename = 1; + run_diff_index(&rev, 1); +} + +void wt_status_print(struct wt_status *s) +{ + if (s->branch && strcmp(s->branch, "refs/heads/master")) + color_printf_ln(color(WT_STATUS_HEADER), + "# On branch %s", s->branch); + + if (s->is_initial) { + color_printf_ln(color(WT_STATUS_HEADER), "#"); + color_printf_ln(color(WT_STATUS_HEADER), "# Initial commit"); + color_printf_ln(color(WT_STATUS_HEADER), "#"); + wt_status_print_initial(s); + } + else { + wt_status_print_updated(s); + discard_cache(); + } + + wt_status_print_changed(s); + wt_status_print_untracked(s); + + if (s->verbose && !s->is_initial) + wt_status_print_verbose(s); + if (!s->commitable) + printf("%s\n", s->amend ? "# No changes" : "nothing to commit"); +} + +int git_status_config(const char *k, const char *v) +{ + if (!strcmp(k, "status.color")) { + wt_status_use_color = git_config_colorbool(k, v); + return 0; + } + if (!strncmp(k, "status.color.", 13)) { + int slot = parse_status_slot(k, 13); + color_parse(v, k, wt_status_colors[slot]); + } + return git_default_config(k, v); +} diff --git a/wt-status.h b/wt-status.h new file mode 100644 index 0000000000..0a5a5b7ba9 --- /dev/null +++ b/wt-status.h @@ -0,0 +1,25 @@ +#ifndef STATUS_H +#define STATUS_H + +enum color_wt_status { + WT_STATUS_HEADER, + WT_STATUS_UPDATED, + WT_STATUS_CHANGED, + WT_STATUS_UNTRACKED, +}; + +struct wt_status { + int is_initial; + char *branch; + const char *reference; + int commitable; + int verbose; + int amend; + int untracked; +}; + +int git_status_config(const char *var, const char *value); +void wt_status_prepare(struct wt_status *s); +void wt_status_print(struct wt_status *s); + +#endif /* STATUS_H */ diff --git a/xdiff-interface.c b/xdiff-interface.c index 6a82da73b6..08602f5221 100644 --- a/xdiff-interface.c +++ b/xdiff-interface.c @@ -69,9 +69,9 @@ int xdiff_outf(void *priv_, mmbuffer_t *mb, int nbuf) for (i = 0; i < nbuf; i++) { if (mb[i].ptr[mb[i].size-1] != '\n') { /* Incomplete line */ - priv->remainder = realloc(priv->remainder, - priv->remainder_size + - mb[i].size); + priv->remainder = xrealloc(priv->remainder, + priv->remainder_size + + mb[i].size); memcpy(priv->remainder + priv->remainder_size, mb[i].ptr, mb[i].size); priv->remainder_size += mb[i].size; @@ -83,9 +83,9 @@ int xdiff_outf(void *priv_, mmbuffer_t *mb, int nbuf) consume_one(priv, mb[i].ptr, mb[i].size); continue; } - priv->remainder = realloc(priv->remainder, - priv->remainder_size + - mb[i].size); + priv->remainder = xrealloc(priv->remainder, + priv->remainder_size + + mb[i].size); memcpy(priv->remainder + priv->remainder_size, mb[i].ptr, mb[i].size); consume_one(priv, priv->remainder, |