diff options
110 files changed, 5430 insertions, 2590 deletions
diff --git a/.gitignore b/.gitignore index 25eb4637a6..4c8c8e4115 100644 --- a/.gitignore +++ b/.gitignore @@ -37,6 +37,7 @@ git-fetch git-fetch-pack git-findtags git-fmt-merge-msg +git-for-each-ref git-format-patch git-fsck-objects git-get-tar-commit-id @@ -74,6 +75,7 @@ git-name-rev git-mv git-pack-redundant git-pack-objects +git-pack-refs git-parse-remote git-patch-id git-peek-remote @@ -105,6 +107,7 @@ git-shortlog git-show git-show-branch git-show-index +git-show-ref git-ssh-fetch git-ssh-pull git-ssh-push diff --git a/Documentation/config.txt b/Documentation/config.txt index ee51fe3b9f..d9e73da2a7 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -71,12 +71,16 @@ core.preferSymlinkRefs:: expect HEAD to be a symbolic link. core.logAllRefUpdates:: - If true, `git-update-ref` will append a line to - "$GIT_DIR/logs/<ref>" listing the new SHA1 and the date/time - of the update. If the file does not exist it will be - created automatically. This information can be used to - determine what commit was the tip of a branch "2 days ago". - This value is false by default (no logging). + Updates to a ref <ref> is logged to the file + "$GIT_DIR/logs/<ref>", by appending the new and old + SHA1, the date/time and the reason of the update, but + only when the file exists. If this configuration + variable is set to true, missing "$GIT_DIR/logs/<ref>" + file is automatically created for branch heads. + + This information can be used to determine what commit + was the tip of a branch "2 days ago". This value is + false by default (no automated creation of log files). core.repositoryFormatVersion:: Internal variable identifying the repository format and layout @@ -242,6 +246,10 @@ remote.<name>.push:: The default set of "refspec" for gitlink:git-push[1]. See gitlink:git-push[1]. +repack.usedeltabaseoffset:: + Allow gitlink:git-repack[1] to create packs that uses + delta-base offset. Defaults to false. + show.difftree:: The default gitlink:git-diff-tree[1] arguments to be used for gitlink:git-show[1]. diff --git a/Documentation/diff-options.txt b/Documentation/diff-options.txt index 7b7b9e8ce9..e112172ca5 100644 --- a/Documentation/diff-options.txt +++ b/Documentation/diff-options.txt @@ -16,6 +16,11 @@ The width of the filename part can be controlled by giving another width to it separated by a comma. +--numstat:: + Similar to \--stat, but shows number of added and + deleted lines in decimal notation and pathname without + abbreviation, to make it more machine friendly. + --summary:: Output a condensed summary of extended header information such as creations, renames and mode changes. diff --git a/Documentation/git-blame.txt b/Documentation/git-blame.txt index e1f89444a9..9891c1d377 100644 --- a/Documentation/git-blame.txt +++ b/Documentation/git-blame.txt @@ -7,7 +7,7 @@ git-blame - Show what revision and author last modified each line of a file SYNOPSIS -------- -'git-blame' [-c] [-l] [-t] [-S <revs-file>] [--] <file> [<rev>] +'git-blame' [-c] [-l] [-t] [-f] [-n] [-p] [-S <revs-file>] [--] <file> [<rev>] DESCRIPTION ----------- @@ -45,10 +45,47 @@ OPTIONS -S, --rev-file <revs-file>:: Use revs from revs-file instead of calling gitlink:git-rev-list[1]. +-f, --show-name:: + Show filename in the original commit. By default + filename is shown if there is any line that came from a + file with different name, due to rename detection. + +-n, --show-number:: + Show line number in the original commit (Default: off). + +-p, --porcelain:: + Show in a format designed for machine consumption. + -h, --help:: Show help message. +THE PORCELAIN FORMAT +-------------------- + +In this format, each line is output after a header; the +header at the minumum has the first line which has: + +- 40-byte SHA-1 of the commit the line is attributed to; +- the line number of the line in the original file; +- the line number of the line in the final file; +- on a line that starts a group of line from a different + commit than the previous one, the number of lines in this + group. On subsequent lines this field is absent. + +This header line is followed by the following information +at least once for each commit: + +- author name ("author"), email ("author-mail"), time + ("author-time"), and timezone ("author-tz"); similarly + for committer. +- filename in the commit the line is attributed to. +- the first line of the commit log message ("summary"). + +The contents of the actual line is output after the above +header, prefixed by a TAB. This is to allow adding more +header elements later. + SEE ALSO -------- gitlink:git-annotate[1] diff --git a/Documentation/git-for-each-ref.txt b/Documentation/git-for-each-ref.txt new file mode 100644 index 0000000000..2bf6aef735 --- /dev/null +++ b/Documentation/git-for-each-ref.txt @@ -0,0 +1,185 @@ +git-for-each-ref(1) +=================== + +NAME +---- +git-for-each-ref - Output information on each ref + +SYNOPSIS +-------- +'git-for-each-ref' [--count=<count>]\* [--shell|--perl|--python] [--sort=<key>]\* [--format=<format>] [<pattern>] + +DESCRIPTION +----------- + +Iterate over all refs that match `<pattern>` and show them +according to the given `<format>`, after sorting them according +to the given set of `<key>`. If `<max>` is given, stop after +showing that many refs. The interporated values in `<format>` +can optionally be quoted as string literals in the specified +host language allowing their direct evaluation in that language. + +OPTIONS +------- +<count>:: + By default the command shows all refs that match + `<pattern>`. This option makes it stop after showing + that many refs. + +<key>:: + A field name to sort on. Prefix `-` to sort in + descending order of the value. When unspecified, + `refname` is used. More than one sort keys can be + given. + +<format>:: + A string that interpolates `%(fieldname)` from the + object pointed at by a ref being shown. If `fieldname` + is prefixed with an asterisk (`*`) and the ref points + at a tag object, the value for the field in the object + tag refers is used. When unspecified, defaults to + `%(objectname) SPC %(objecttype) TAB %(refname)`. + It also interpolates `%%` to `%`, and `%xx` where `xx` + are hex digits interpolates to character with hex code + `xx`; for example `%00` interpolates to `\0` (NUL), + `%09` to `\t` (TAB) and `%0a` to `\n` (LF). + +<pattern>:: + If given, the name of the ref is matched against this + using fnmatch(3). Refs that do not match the pattern + are not shown. + +--shell, --perl, --python:: + If given, strings that substitute `%(fieldname)` + placeholders are quoted as string literals suitable for + the specified host language. This is meant to produce + a scriptlet that can directly be `eval`ed. + + +FIELD NAMES +----------- + +Various values from structured fields in referenced objects can +be used to interpolate into the resulting output, or as sort +keys. + +For all objects, the following names can be used: + +refname:: + The name of the ref (the part after $GIT_DIR/refs/). + +objecttype:: + The type of the object (`blob`, `tree`, `commit`, `tag`). + +objectsize:: + The size of the object (the same as `git-cat-file -s` reports). + +objectname:: + The object name (aka SHA-1). + +In addition to the above, for commit and tag objects, the header +field names (`tree`, `parent`, `object`, `type`, and `tag`) can +be used to specify the value in the header field. + +Fields that have name-email-date tuple as its value (`author`, +`committer`, and `tagger`) can be suffixed with `name`, `email`, +and `date` to extract the named component. + +The first line of the message in a commit and tag object is +`subject`, the remaining lines are `body`. The whole message +is `contents`. + +For sorting purposes, fields with numeric values sort in numeric +order (`objectsize`, `authordate`, `committerdate`, `taggerdate`). +All other fields are used to sort in their byte-value order. + +In any case, a field name that refers to a field inapplicable to +the object referred by the ref does not cause an error. It +returns an empty string instead. + + +EXAMPLES +-------- + +An example directly producing formatted text. Show the most recent +3 tagged commits:: + +------------ +#!/bin/sh + +git-for-each-ref --count=3 --sort='-*authordate' \ +--format='From: %(*authorname) %(*authoremail) +Subject: %(*subject) +Date: %(*authordate) +Ref: %(*refname) + +%(*body) +' 'refs/tags' +------------ + + +A simple example showing the use of shell eval on the output, +demonstrating the use of --shell. List the prefixes of all heads:: +------------ +#!/bin/sh + +git-for-each-ref --shell --format="ref=%(refname)" refs/heads | \ +while read entry +do + eval "$entry" + echo `dirname $ref` +done +------------ + + +A bit more elaborate report on tags, demonstrating that the format +may be an entire script:: +------------ +#!/bin/sh + +fmt=' + r=%(refname) + t=%(*objecttype) + T=${r#refs/tags/} + + o=%(*objectname) + n=%(*authorname) + e=%(*authoremail) + s=%(*subject) + d=%(*authordate) + b=%(*body) + + kind=Tag + if test "z$t" = z + then + # could be a lightweight tag + t=%(objecttype) + kind="Lightweight tag" + o=%(objectname) + n=%(authorname) + e=%(authoremail) + s=%(subject) + d=%(authordate) + b=%(body) + fi + echo "$kind $T points at a $t object $o" + if test "z$t" = zcommit + then + echo "The commit was authored by $n $e +at $d, and titled + + $s + +Its message reads as: +" + echo "$b" | sed -e "s/^/ /" + echo + fi +' + +eval=`git-for-each-ref --shell --format="$fmt" \ + --sort='*objecttype' \ + --sort=-taggerdate \ + refs/tags` +eval "$eval" +------------ diff --git a/Documentation/git-grep.txt b/Documentation/git-grep.txt index d8af4d961b..bfbece9864 100644 --- a/Documentation/git-grep.txt +++ b/Documentation/git-grep.txt @@ -14,7 +14,7 @@ SYNOPSIS [-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] + [-c | --count] [--all-match] [-A <post-context>] [-B <pre-context>] [-C <context>] [-f <file>] [-e] <pattern> [--and|--or|--not|(|)|-e <pattern>...] [<tree>...] @@ -96,6 +96,11 @@ OPTIONS higher precedence than `--or`. `-e` has to be used for all patterns. +--all-match:: + When giving multiple pattern expressions combined with `--or`, + this flag is specified to limit the match to files that + have lines to match all of them. + `<tree>...`:: Search blobs in the trees for specified patterns. @@ -111,6 +116,10 @@ git grep -e \'#define\' --and \( -e MAX_PATH -e PATH_MAX \):: Looks for a line that has `#define` and either `MAX_PATH` or `PATH_MAX`. +git grep --all-match -e NODE -e Unexpected:: + Looks for a line that has `NODE` or `Unexpected` in + files that have lines that match both. + Author ------ Originally written by Linus Torvalds <torvalds@osdl.org>, later diff --git a/Documentation/git-pack-objects.txt b/Documentation/git-pack-objects.txt index 944c9e1e8f..5ebe34eebf 100644 --- a/Documentation/git-pack-objects.txt +++ b/Documentation/git-pack-objects.txt @@ -9,8 +9,8 @@ git-pack-objects - Create a packed archive of objects SYNOPSIS -------- [verse] -'git-pack-objects' [-q] [--no-reuse-delta] [--non-empty] - [--local] [--incremental] [--window=N] [--depth=N] +'git-pack-objects' [-q] [--no-reuse-delta] [--delta-base-offset] [--non-empty] + [--local] [--incremental] [--window=N] [--depth=N] [--all-progress] [--revs [--unpacked | --all]*] [--stdout | base-name] < object-list @@ -110,6 +110,17 @@ base-name:: This flag tells the command not to reuse existing deltas but compute them from scratch. +--delta-base-offset:: + A packed archive can express base object of a delta as + either 20-byte object name or as an offset in the + stream, but older version of git does not understand the + latter. By default, git-pack-objects only uses the + former format for better compatibility. This option + allows the command to use the latter format for + compactness. Depending on the average delta chain + length, this option typically shrinks the resulting + packfile by 3-5 per-cent. + Author ------ diff --git a/Documentation/git-pack-refs.txt b/Documentation/git-pack-refs.txt new file mode 100644 index 0000000000..5da5105771 --- /dev/null +++ b/Documentation/git-pack-refs.txt @@ -0,0 +1,54 @@ +git-pack-refs(1) +================ + +NAME +---- +git-pack-refs - Pack heads and tags for efficient repository access + +SYNOPSIS +-------- +'git-pack-refs' [--all] [--prune] + +DESCRIPTION +----------- + +Traditionally, tips of branches and tags (collectively known as +'refs') were stored one file per ref under `$GIT_DIR/refs` +directory. While many branch tips tend to be updated often, +most tags and some branch tips are never updated. When a +repository has hundreds or thousands of tags, this +one-file-per-ref format both wastes storage and hurts +performance. + +This command is used to solve the storage and performance +problem by stashing the refs in a single file, +`$GIT_DIR/packed-refs`. When a ref is missing from the +traditional `$GIT_DIR/refs` hierarchy, it is looked up in this +file and used if found. + +Subsequent updates to branches always creates new file under +`$GIT_DIR/refs` hierarchy. + +OPTIONS +------- + +\--all:: + +The command by default packs all tags and leaves branch tips +alone. This is because branches are expected to be actively +developed and packing their tips does not help performance. +This option causes branch tips to be packed as well. Useful for +a repository with many branches of historical interests. + +\--prune:: + +After packing the refs, remove loose refs under `$GIT_DIR/refs` +hierarchy. This should probably become default. + +Author +------ +Written by Linus Torvalds <torvalds@osdl.org> + +GIT +--- +Part of the gitlink:git[7] suite diff --git a/Documentation/git-pull.txt b/Documentation/git-pull.txt index 51577fcbe6..2a5aea73ba 100644 --- a/Documentation/git-pull.txt +++ b/Documentation/git-pull.txt @@ -3,7 +3,7 @@ git-pull(1) NAME ---- -git-pull - Pull and merge from another repository +git-pull - Pull and merge from another repository or a local branch SYNOPSIS diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt index 878eb6fe88..03e867a403 100644 --- a/Documentation/git-rebase.txt +++ b/Documentation/git-rebase.txt @@ -7,7 +7,7 @@ git-rebase - Rebase local commits to a new head SYNOPSIS -------- -'git-rebase' [--merge] [--onto <newbase>] <upstream> [<branch>] +'git-rebase' [-v] [--merge] [--onto <newbase>] <upstream> [<branch>] 'git-rebase' --continue | --skip | --abort @@ -170,6 +170,9 @@ OPTIONS is used instead (`git-merge-recursive` when merging a single head, `git-merge-octopus` otherwise). This implies --merge. +-v, \--verbose:: + Display a diffstat of what changed upstream since the last rebase. + include::merge-strategies.txt[] NOTES diff --git a/Documentation/git-repack.txt b/Documentation/git-repack.txt index d2eaa0995d..0fa47e3b01 100644 --- a/Documentation/git-repack.txt +++ b/Documentation/git-repack.txt @@ -67,6 +67,20 @@ OPTIONS The default value for both --window and --depth is 10. +Configuration +------------- + +When configuration variable `repack.UseDeltaBaseOffset` is set +for the repository, the command passes `--delta-base-offset` +option to `git-pack-objects`; this typically results in slightly +smaller packs, but the generated packs are incompatible with +versions of git older than (and including) v1.4.3; do not set +the variable in a repository that older version of git needs to +be able to read (this includes repositories from which packs can +be copied out over http or rsync, and people who obtained packs +that way can try to use older git with it). + + Author ------ Written by Linus Torvalds <torvalds@osdl.org> diff --git a/Documentation/git-repo-config.txt b/Documentation/git-repo-config.txt index 8a1ab61e94..8199615dde 100644 --- a/Documentation/git-repo-config.txt +++ b/Documentation/git-repo-config.txt @@ -3,19 +3,19 @@ git-repo-config(1) NAME ---- -git-repo-config - Get and set options in .git/config +git-repo-config - Get and set repository or global options. SYNOPSIS -------- [verse] -'git-repo-config' [type] name [value [value_regex]] -'git-repo-config' [type] --replace-all name [value [value_regex]] -'git-repo-config' [type] --get name [value_regex] -'git-repo-config' [type] --get-all name [value_regex] -'git-repo-config' [type] --unset name [value_regex] -'git-repo-config' [type] --unset-all name [value_regex] -'git-repo-config' -l | --list +'git-repo-config' [--global] [type] name [value [value_regex]] +'git-repo-config' [--global] [type] --replace-all name [value [value_regex]] +'git-repo-config' [--global] [type] --get name [value_regex] +'git-repo-config' [--global] [type] --get-all name [value_regex] +'git-repo-config' [--global] [type] --unset name [value_regex] +'git-repo-config' [--global] [type] --unset-all name [value_regex] +'git-repo-config' [--global] -l | --list DESCRIPTION ----------- @@ -41,8 +41,9 @@ This command will fail if: . Can not write to .git/config, . no section was provided, . the section or key is invalid, -. you try to unset an option which does not exist, or -. you try to unset/set an option for which multiple lines match. +. you try to unset an option which does not exist, +. you try to unset/set an option for which multiple lines match, or +. you use --global option without $HOME being properly set. OPTIONS @@ -64,14 +65,17 @@ OPTIONS --get-regexp:: Like --get-all, but interprets the name as a regular expression. +--global:: + Use global ~/.gitconfig file rather than the repository .git/config. + --unset:: - Remove the line matching the key from .git/config. + Remove the line matching the key from config file. --unset-all:: - Remove all matching lines from .git/config. + Remove all matching lines from config file. -l, --list:: - List all variables set in .git/config. + List all variables set in config file. ENVIRONMENT @@ -79,6 +83,7 @@ ENVIRONMENT GIT_CONFIG:: Take the configuration from the given file instead of .git/config. + Using the "--global" option forces this to ~/.gitconfig. GIT_CONFIG_LOCAL:: Currently the same as $GIT_CONFIG; when Git will support global diff --git a/Documentation/git-send-email.txt b/Documentation/git-send-email.txt index 481b3f50e3..4c8d907bd5 100644 --- a/Documentation/git-send-email.txt +++ b/Documentation/git-send-email.txt @@ -66,8 +66,13 @@ The options available are: all that is output. --smtp-server:: - If set, specifies the outgoing SMTP server to use. Defaults to - localhost. + If set, specifies the outgoing SMTP server to use. A full + pathname of a sendmail-like program can be specified instead; + the program must support the `-i` option. Default value can + be specified by the 'sendemail.smtpserver' configuration + option; the built-in default is `/usr/sbin/sendmail` or + `/usr/lib/sendmail` if such program is available, or + `localhost` otherwise. --subject:: Specify the initial subject of the email thread. diff --git a/Documentation/git-show-ref.txt b/Documentation/git-show-ref.txt new file mode 100644 index 0000000000..5973a82517 --- /dev/null +++ b/Documentation/git-show-ref.txt @@ -0,0 +1,156 @@ +git-show-ref(1) +=============== + +NAME +---- +git-show-ref - List references in a local repository + +SYNOPSIS +-------- +[verse] +'git-show-ref' [-q|--quiet] [--verify] [-h|--head] [-d|--dereference] + [-s|--hash] [--abbrev] [--tags] [--heads] [--] <pattern>... + +DESCRIPTION +----------- + +Displays references available in a local repository along with the associated +commit IDs. Results can be filtered using a pattern and tags can be +dereferenced into object IDs. Additionally, it can be used to test whether a +particular ref exists. + +Use of this utility is encouraged in favor of directly accessing files under +in the `.git` directory. + +OPTIONS +------- + +-h, --head:: + + Show the HEAD reference. + +--tags, --heads:: + + Limit to only "refs/heads" and "refs/tags", respectively. These + options are not mutually exclusive; when given both, references stored + in "refs/heads" and "refs/tags" are displayed. + +-d, --dereference:: + + Dereference tags into object IDs as well. They will be shown with "^{}" + appended. + +-s, --hash:: + + Only show the SHA1 hash, not the reference name. When also using + --dereference the dereferenced tag will still be shown after the SHA1. + +--verify:: + + Enable stricter reference checking by requiring an exact ref path. + Aside from returning an error code of 1, it will also print an error + message if '--quiet' was not specified. + +--abbrev, --abbrev=len:: + + Abbreviate the object name. When using `--hash`, you do + not have to say `--hash --abbrev`; `--hash=len` would do. + +-q, --quiet:: + + Do not print any results to stdout. When combined with '--verify' this + can be used to silently check if a reference exists. + +<pattern>:: + + Show references matching one or more patterns. + +OUTPUT +------ + +The output is in the format: '<SHA-1 ID>' '<space>' '<reference name>'. + +----------------------------------------------------------------------------- +$ git show-ref --head --dereference +832e76a9899f560a90ffd62ae2ce83bbeff58f54 HEAD +832e76a9899f560a90ffd62ae2ce83bbeff58f54 refs/heads/master +832e76a9899f560a90ffd62ae2ce83bbeff58f54 refs/heads/origin +3521017556c5de4159da4615a39fa4d5d2c279b5 refs/tags/v0.99.9c +6ddc0964034342519a87fe013781abf31c6db6ad refs/tags/v0.99.9c^{} +055e4ae3ae6eb344cbabf2a5256a49ea66040131 refs/tags/v1.0rc4 +423325a2d24638ddcc82ce47be5e40be550f4507 refs/tags/v1.0rc4^{} +... +----------------------------------------------------------------------------- + +When using --hash (and not --dereference) the output format is: '<SHA-1 ID>' + +----------------------------------------------------------------------------- +$ git show-ref --heads --hash +2e3ba0114a1f52b47df29743d6915d056be13278 +185008ae97960c8d551adcd9e23565194651b5d1 +03adf42c988195b50e1a1935ba5fcbc39b2b029b +... +----------------------------------------------------------------------------- + +EXAMPLE +------- + +To show all references called "master", whether tags or heads or anything +else, and regardless of how deep in the reference naming hierarchy they are, +use: + +----------------------------------------------------------------------------- + git show-ref master +----------------------------------------------------------------------------- + +This will show "refs/heads/master" but also "refs/remote/other-repo/master", +if such references exists. + +When using the '--verify' flag, the command requires an exact path: + +----------------------------------------------------------------------------- + git show-ref --verify refs/heads/master +----------------------------------------------------------------------------- + +will only match the exact branch called "master". + +If nothing matches, gitlink:git-show-ref[1] will return an error code of 1, +and in the case of verification, it will show an error message. + +For scripting, you can ask it to be quiet with the "--quiet" flag, which +allows you to do things like + +----------------------------------------------------------------------------- + git-show-ref --quiet --verify -- "refs/heads/$headname" || + echo "$headname is not a valid branch" +----------------------------------------------------------------------------- + +to check whether a particular branch exists or not (notice how we don't +actually want to show any results, and we want to use the full refname for it +in order to not trigger the problem with ambiguous partial matches). + +To show only tags, or only proper branch heads, use "--tags" and/or "--heads" +respectively (using both means that it shows tags and heads, but not other +random references under the refs/ subdirectory). + +To do automatic tag object dereferencing, use the "-d" or "--dereference" +flag, so you can do + +----------------------------------------------------------------------------- + git show-ref --tags --dereference +----------------------------------------------------------------------------- + +to get a listing of all tags together with what they dereference. + +SEE ALSO +-------- +gitlink:git-ls-remote[1], gitlink:git-peek-remote[1] + +AUTHORS +------- +Written by Linus Torvalds <torvalds@osdl.org>. +Man page by Jonas Fonseca <fonseca@diku.dk>. + +GIT +--- +Part of the gitlink:git[7] suite diff --git a/Documentation/git-update-ref.txt b/Documentation/git-update-ref.txt index e062030e91..71bcb7954f 100644 --- a/Documentation/git-update-ref.txt +++ b/Documentation/git-update-ref.txt @@ -7,7 +7,7 @@ git-update-ref - update the object name stored in a ref safely SYNOPSIS -------- -'git-update-ref' [-m <reason>] <ref> <newvalue> [<oldvalue>] +'git-update-ref' [-m <reason>] (-d <ref> <oldvalue> | <ref> <newvalue> [<oldvalue>]) DESCRIPTION ----------- @@ -20,7 +20,9 @@ possibly dereferencing the symbolic refs, after verifying that the current value of the <ref> matches <oldvalue>. E.g. `git-update-ref refs/heads/master <newvalue> <oldvalue>` updates the master branch head to <newvalue> only if its current -value is <oldvalue>. +value is <oldvalue>. You can specify 40 "0" or an empty string +as <oldvalue> to make sure that the ref you are creating does +not exist. It also allows a "ref" file to be a symbolic pointer to another ref file by starting with the four-byte header sequence of @@ -49,6 +51,10 @@ for reading but not for writing (so we'll never write through a ref symlink to some other tree, if you have copied a whole archive by creating a symlink tree). +With `-d` flag, it deletes the named <ref> after verifying it +still contains <oldvalue>. + + Logging Updates --------------- If config parameter "core.logAllRefUpdates" is true or the file diff --git a/Documentation/git.txt b/Documentation/git.txt index b00607ee1b..4ce4f8d1c6 100644 --- a/Documentation/git.txt +++ b/Documentation/git.txt @@ -72,185 +72,6 @@ GIT COMMANDS We divide git into high level ("porcelain") commands and low level ("plumbing") commands. -Low-level commands (plumbing) ------------------------------ - -Although git includes its -own porcelain layer, its low-level commands are sufficient to support -development of alternative porcelains. Developers of such porcelains -might start by reading about gitlink:git-update-index[1] and -gitlink:git-read-tree[1]. - -We divide the low-level commands into commands that manipulate objects (in -the repository, index, and working tree), commands that interrogate and -compare objects, and commands that move objects and references between -repositories. - -Manipulation commands -~~~~~~~~~~~~~~~~~~~~~ -gitlink:git-apply[1]:: - Reads a "diff -up1" or git generated patch file and - applies it to the working tree. - -gitlink:git-checkout-index[1]:: - Copy files from the index to the working tree. - -gitlink:git-commit-tree[1]:: - Creates a new commit object. - -gitlink:git-hash-object[1]:: - Computes the object ID from a file. - -gitlink:git-index-pack[1]:: - Build pack idx file for an existing packed archive. - -gitlink:git-init-db[1]:: - Creates an empty git object database, or reinitialize an - existing one. - -gitlink:git-merge-index[1]:: - Runs a merge for files needing merging. - -gitlink:git-mktag[1]:: - Creates a tag object. - -gitlink:git-mktree[1]:: - Build a tree-object from ls-tree formatted text. - -gitlink:git-pack-objects[1]:: - Creates a packed archive of objects. - -gitlink:git-prune-packed[1]:: - Remove extra objects that are already in pack files. - -gitlink:git-read-tree[1]:: - Reads tree information into the index. - -gitlink:git-repo-config[1]:: - Get and set options in .git/config. - -gitlink:git-unpack-objects[1]:: - Unpacks objects out of a packed archive. - -gitlink:git-update-index[1]:: - Registers files in the working tree to the index. - -gitlink:git-write-tree[1]:: - Creates a tree from the index. - - -Interrogation commands -~~~~~~~~~~~~~~~~~~~~~~ - -gitlink:git-cat-file[1]:: - Provide content or type/size information for repository objects. - -gitlink:git-describe[1]:: - Show the most recent tag that is reachable from a commit. - -gitlink:git-diff-index[1]:: - Compares content and mode of blobs between the index and repository. - -gitlink:git-diff-files[1]:: - Compares files in the working tree and the index. - -gitlink:git-diff-stages[1]:: - Compares two "merge stages" in the index. - -gitlink:git-diff-tree[1]:: - Compares the content and mode of blobs found via two tree objects. - -gitlink:git-fsck-objects[1]:: - Verifies the connectivity and validity of the objects in the database. - -gitlink:git-ls-files[1]:: - Information about files in the index and the working tree. - -gitlink:git-ls-tree[1]:: - Displays a tree object in human readable form. - -gitlink:git-merge-base[1]:: - Finds as good common ancestors as possible for a merge. - -gitlink:git-name-rev[1]:: - Find symbolic names for given revs. - -gitlink:git-pack-redundant[1]:: - Find redundant pack files. - -gitlink:git-rev-list[1]:: - Lists commit objects in reverse chronological order. - -gitlink:git-show-index[1]:: - Displays contents of a pack idx file. - -gitlink:git-tar-tree[1]:: - Creates a tar archive of the files in the named tree object. - -gitlink:git-unpack-file[1]:: - Creates a temporary file with a blob's contents. - -gitlink:git-var[1]:: - Displays a git logical variable. - -gitlink:git-verify-pack[1]:: - Validates packed git archive files. - -In general, the interrogate commands do not touch the files in -the working tree. - - -Synching repositories -~~~~~~~~~~~~~~~~~~~~~ - -gitlink:git-fetch-pack[1]:: - Updates from a remote repository (engine for ssh and - local transport). - -gitlink:git-http-fetch[1]:: - Downloads a remote git repository via HTTP by walking - commit chain. - -gitlink:git-local-fetch[1]:: - Duplicates another git repository on a local system by - walking commit chain. - -gitlink:git-peek-remote[1]:: - Lists references on a remote repository using - upload-pack protocol (engine for ssh and local - transport). - -gitlink:git-receive-pack[1]:: - Invoked by 'git-send-pack' to receive what is pushed to it. - -gitlink:git-send-pack[1]:: - Pushes to a remote repository, intelligently. - -gitlink:git-http-push[1]:: - Push missing objects using HTTP/DAV. - -gitlink:git-shell[1]:: - Restricted shell for GIT-only SSH access. - -gitlink:git-ssh-fetch[1]:: - Pulls from a remote repository over ssh connection by - walking commit chain. - -gitlink:git-ssh-upload[1]:: - Helper "server-side" program used by git-ssh-fetch. - -gitlink:git-update-server-info[1]:: - Updates auxiliary information on a dumb server to help - clients discover references and packs on it. - -gitlink:git-upload-archive[1]:: - Invoked by 'git-archive' to send a generated archive. - -gitlink:git-upload-pack[1]:: - Invoked by 'git-fetch-pack' to push - what are asked for. - - High-level commands (porcelain) ------------------------------- @@ -320,8 +141,11 @@ gitlink:git-merge[1]:: gitlink:git-mv[1]:: Move or rename a file, a directory, or a symlink. +gitlink:git-pack-refs[1]:: + Pack heads and tags for efficient repository access. + gitlink:git-pull[1]:: - Fetch from and merge with a remote repository. + Fetch from and merge with a remote repository or a local branch. gitlink:git-push[1]:: Update remote refs along with associated objects. @@ -488,6 +312,191 @@ gitlink:git-stripspace[1]:: Filter out empty lines. +Low-level commands (plumbing) +----------------------------- + +Although git includes its +own porcelain layer, its low-level commands are sufficient to support +development of alternative porcelains. Developers of such porcelains +might start by reading about gitlink:git-update-index[1] and +gitlink:git-read-tree[1]. + +We divide the low-level commands into commands that manipulate objects (in +the repository, index, and working tree), commands that interrogate and +compare objects, and commands that move objects and references between +repositories. + +Manipulation commands +~~~~~~~~~~~~~~~~~~~~~ +gitlink:git-apply[1]:: + Reads a "diff -up1" or git generated patch file and + applies it to the working tree. + +gitlink:git-checkout-index[1]:: + Copy files from the index to the working tree. + +gitlink:git-commit-tree[1]:: + Creates a new commit object. + +gitlink:git-hash-object[1]:: + Computes the object ID from a file. + +gitlink:git-index-pack[1]:: + Build pack idx file for an existing packed archive. + +gitlink:git-init-db[1]:: + Creates an empty git object database, or reinitialize an + existing one. + +gitlink:git-merge-index[1]:: + Runs a merge for files needing merging. + +gitlink:git-mktag[1]:: + Creates a tag object. + +gitlink:git-mktree[1]:: + Build a tree-object from ls-tree formatted text. + +gitlink:git-pack-objects[1]:: + Creates a packed archive of objects. + +gitlink:git-prune-packed[1]:: + Remove extra objects that are already in pack files. + +gitlink:git-read-tree[1]:: + Reads tree information into the index. + +gitlink:git-repo-config[1]:: + Get and set options in .git/config. + +gitlink:git-unpack-objects[1]:: + Unpacks objects out of a packed archive. + +gitlink:git-update-index[1]:: + Registers files in the working tree to the index. + +gitlink:git-write-tree[1]:: + Creates a tree from the index. + + +Interrogation commands +~~~~~~~~~~~~~~~~~~~~~~ + +gitlink:git-cat-file[1]:: + Provide content or type/size information for repository objects. + +gitlink:git-describe[1]:: + Show the most recent tag that is reachable from a commit. + +gitlink:git-diff-index[1]:: + Compares content and mode of blobs between the index and repository. + +gitlink:git-diff-files[1]:: + Compares files in the working tree and the index. + +gitlink:git-diff-stages[1]:: + Compares two "merge stages" in the index. + +gitlink:git-diff-tree[1]:: + Compares the content and mode of blobs found via two tree objects. + +gitlink:git-for-each-ref[1]:: + Output information on each ref. + +gitlink:git-fsck-objects[1]:: + Verifies the connectivity and validity of the objects in the database. + +gitlink:git-ls-files[1]:: + Information about files in the index and the working tree. + +gitlink:git-ls-tree[1]:: + Displays a tree object in human readable form. + +gitlink:git-merge-base[1]:: + Finds as good common ancestors as possible for a merge. + +gitlink:git-name-rev[1]:: + Find symbolic names for given revs. + +gitlink:git-pack-redundant[1]:: + Find redundant pack files. + +gitlink:git-rev-list[1]:: + Lists commit objects in reverse chronological order. + +gitlink:git-show-index[1]:: + Displays contents of a pack idx file. + +gitlink:git-show-ref[1]:: + List references in a local repository. + +gitlink:git-tar-tree[1]:: + Creates a tar archive of the files in the named tree object. + +gitlink:git-unpack-file[1]:: + Creates a temporary file with a blob's contents. + +gitlink:git-var[1]:: + Displays a git logical variable. + +gitlink:git-verify-pack[1]:: + Validates packed git archive files. + +In general, the interrogate commands do not touch the files in +the working tree. + + +Synching repositories +~~~~~~~~~~~~~~~~~~~~~ + +gitlink:git-fetch-pack[1]:: + Updates from a remote repository (engine for ssh and + local transport). + +gitlink:git-http-fetch[1]:: + Downloads a remote git repository via HTTP by walking + commit chain. + +gitlink:git-local-fetch[1]:: + Duplicates another git repository on a local system by + walking commit chain. + +gitlink:git-peek-remote[1]:: + Lists references on a remote repository using + upload-pack protocol (engine for ssh and local + transport). + +gitlink:git-receive-pack[1]:: + Invoked by 'git-send-pack' to receive what is pushed to it. + +gitlink:git-send-pack[1]:: + Pushes to a remote repository, intelligently. + +gitlink:git-http-push[1]:: + Push missing objects using HTTP/DAV. + +gitlink:git-shell[1]:: + Restricted shell for GIT-only SSH access. + +gitlink:git-ssh-fetch[1]:: + Pulls from a remote repository over ssh connection by + walking commit chain. + +gitlink:git-ssh-upload[1]:: + Helper "server-side" program used by git-ssh-fetch. + +gitlink:git-update-server-info[1]:: + Updates auxiliary information on a dumb server to help + clients discover references and packs on it. + +gitlink:git-upload-archive[1]:: + Invoked by 'git-archive' to send a generated archive. + +gitlink:git-upload-pack[1]:: + Invoked by 'git-fetch-pack' to push + what are asked for. + + Configuration Mechanism ----------------------- @@ -132,6 +132,8 @@ GITWEB_HOMETEXT = indextext.html GITWEB_CSS = gitweb.css GITWEB_LOGO = git-logo.png GITWEB_FAVICON = git-favicon.png +GITWEB_SITE_HEADER = +GITWEB_SITE_FOOTER = export prefix bindir gitexecdir template_dir GIT_PYTHON_DIR @@ -156,8 +158,8 @@ BASIC_CFLAGS = BASIC_LDFLAGS = SCRIPT_SH = \ - git-bisect.sh git-branch.sh git-checkout.sh \ - git-cherry.sh git-clean.sh git-clone.sh git-commit.sh \ + git-bisect.sh git-checkout.sh \ + git-clean.sh git-clone.sh git-commit.sh \ git-fetch.sh \ git-ls-remote.sh \ git-merge-one-file.sh git-parse-remote.sh \ @@ -173,7 +175,7 @@ SCRIPT_SH = \ SCRIPT_PERL = \ git-archimport.perl git-cvsimport.perl git-relink.perl \ git-shortlog.perl git-rerere.perl \ - git-annotate.perl git-cvsserver.perl \ + git-cvsserver.perl \ git-svnimport.perl git-cvsexportcommit.perl \ git-send-email.perl git-svn.perl @@ -207,7 +209,7 @@ PROGRAMS = \ EXTRA_PROGRAMS = BUILT_INS = \ - git-format-patch$X git-show$X git-whatchanged$X \ + git-format-patch$X git-show$X git-whatchanged$X git-cherry$X \ git-get-tar-commit-id$X \ $(patsubst builtin-%.o,git-%$X,$(BUILTIN_OBJS)) @@ -262,8 +264,10 @@ LIB_OBJS = \ BUILTIN_OBJS = \ builtin-add.o \ + builtin-annotate.o \ builtin-apply.o \ builtin-archive.o \ + builtin-branch.o \ builtin-cat-file.o \ builtin-checkout-index.o \ builtin-check-ref-format.o \ @@ -275,6 +279,7 @@ BUILTIN_OBJS = \ builtin-diff-stages.o \ builtin-diff-tree.o \ builtin-fmt-merge-msg.o \ + builtin-for-each-ref.o \ builtin-grep.o \ builtin-init-db.o \ builtin-log.o \ @@ -303,7 +308,9 @@ BUILTIN_OBJS = \ builtin-update-ref.o \ builtin-upload-archive.o \ builtin-verify-pack.o \ - builtin-write-tree.o + builtin-write-tree.o \ + builtin-show-ref.o \ + builtin-pack-refs.o GITLIBS = $(LIB_FILE) $(XDIFF_LIB) EXTLIBS = -lz @@ -670,6 +677,8 @@ gitweb/gitweb.cgi: gitweb/gitweb.perl -e 's|++GITWEB_CSS++|$(GITWEB_CSS)|g' \ -e 's|++GITWEB_LOGO++|$(GITWEB_LOGO)|g' \ -e 's|++GITWEB_FAVICON++|$(GITWEB_FAVICON)|g' \ + -e 's|++GITWEB_SITE_HEADER++|$(GITWEB_SITE_HEADER)|g' \ + -e 's|++GITWEB_SITE_FOOTER++|$(GITWEB_SITE_FOOTER)|g' \ $< >$@+ chmod +x $@+ mv $@+ $@ @@ -923,3 +932,8 @@ check-docs:: *) echo "no link: $$v";; \ esac ; \ done | sort + +### Make sure built-ins do not have dups and listed in git.c +# +check-builtins:: + ./check-builtins.sh diff --git a/archive-zip.c b/archive-zip.c index 3ffdad68d1..28e7352e98 100644 --- a/archive-zip.c +++ b/archive-zip.c @@ -145,6 +145,7 @@ static int write_zip_entry(const unsigned char *sha1, { struct zip_local_header header; struct zip_dir_header dirent; + unsigned long attr2; unsigned long compressed_size; unsigned long uncompressed_size; unsigned long crc; @@ -172,12 +173,16 @@ static int write_zip_entry(const unsigned char *sha1, if (S_ISDIR(mode)) { method = 0; + attr2 = 16; result = READ_TREE_RECURSIVE; out = NULL; uncompressed_size = 0; compressed_size = 0; - } else if (S_ISREG(mode)) { - method = zlib_compression_level == 0 ? 0 : 8; + } else if (S_ISREG(mode) || S_ISLNK(mode)) { + method = 0; + attr2 = S_ISLNK(mode) ? ((mode | 0777) << 16) : 0; + if (S_ISREG(mode) && zlib_compression_level != 0) + method = 8; result = 0; buffer = read_sha1_file(sha1, type, &size); if (!buffer) @@ -213,8 +218,8 @@ static int write_zip_entry(const unsigned char *sha1, } copy_le32(dirent.magic, 0x02014b50); - copy_le16(dirent.creator_version, 0); - copy_le16(dirent.version, 20); + copy_le16(dirent.creator_version, S_ISLNK(mode) ? 0x0317 : 0); + copy_le16(dirent.version, 10); copy_le16(dirent.flags, 0); copy_le16(dirent.compression_method, method); copy_le16(dirent.mtime, zip_time); @@ -227,7 +232,7 @@ static int write_zip_entry(const unsigned char *sha1, copy_le16(dirent.comment_length, 0); copy_le16(dirent.disk, 0); copy_le16(dirent.attr1, 0); - copy_le32(dirent.attr2, 0); + copy_le32(dirent.attr2, attr2); 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); @@ -236,7 +241,7 @@ static int write_zip_entry(const unsigned char *sha1, zip_dir_entries++; copy_le32(header.magic, 0x04034b50); - copy_le16(header.version, 20); + copy_le16(header.version, 10); copy_le16(header.flags, 0); copy_le16(header.compression_method, method); copy_le16(header.mtime, zip_time); @@ -17,21 +17,26 @@ #include "diffcore.h" #include "revision.h" #include "xdiff-interface.h" +#include "quote.h" #ifndef DEBUG #define DEBUG 0 #endif -static const char blame_usage[] = "git-blame [-c] [-l] [-t] [-S <revs-file>] [--] file [commit]\n" - " -c, --compatibility Use the same output mode as git-annotate (Default: off)\n" - " -l, --long Show long commit SHA1 (Default: off)\n" - " -t, --time Show raw timestamp (Default: off)\n" - " -S, --revs-file Use revisions from revs-file instead of calling git-rev-list\n" - " -h, --help This message"; +static const char blame_usage[] = +"git-blame [-c] [-l] [-t] [-f] [-n] [-p] [-S <revs-file>] [--] file [commit]\n" +" -c, --compatibility Use the same output mode as git-annotate (Default: off)\n" +" -l, --long Show long commit SHA1 (Default: off)\n" +" -t, --time Show raw timestamp (Default: off)\n" +" -f, --show-name Show original filename (Default: auto)\n" +" -n, --show-number Show original linenumber (Default: off)\n" +" -p, --porcelain Show in a format designed for machine consumption\n" +" -S revs-file Use revisions from revs-file instead of calling git-rev-list\n" +" -h, --help This message"; static struct commit **blame_lines; static int num_blame_lines; -static char* blame_contents; +static char *blame_contents; static int blame_len; struct util_info { @@ -40,9 +45,10 @@ struct util_info { char *buf; unsigned long size; int num_lines; - const char* pathname; + const char *pathname; + unsigned meta_given:1; - void* topo_data; + void *topo_data; }; struct chunk { @@ -158,11 +164,10 @@ static int get_blob_sha1_internal(const unsigned char *sha1, const char *base, unsigned mode, int stage); static unsigned char blob_sha1[20]; -static const char* blame_file; +static const char *blame_file; static int get_blob_sha1(struct tree *t, const char *pathname, unsigned char *sha1) { - int i; const char *pathspec[2]; blame_file = pathname; pathspec[0] = pathname; @@ -170,12 +175,7 @@ static int get_blob_sha1(struct tree *t, const char *pathname, hashclr(blob_sha1); read_tree_recursive(t, "", 0, 0, pathspec, get_blob_sha1_internal); - for (i = 0; i < 20; i++) { - if (blob_sha1[i] != 0) - break; - } - - if (i == 20) + if (is_null_sha1(blob_sha1)) return -1; hashcpy(sha1, blob_sha1); @@ -244,7 +244,8 @@ static void print_map(struct commit *cmit, struct commit *other) if (i < util->num_lines) { num = util->line_map[i]; printf("%d\t", num); - } else + } + else printf("\t"); if (i < util2->num_lines) { @@ -252,7 +253,8 @@ static void print_map(struct commit *cmit, struct commit *other) printf("%d\t", num2); if (num != -1 && num2 != num) printf("---"); - } else + } + else printf("\t"); printf("\n"); @@ -271,12 +273,12 @@ static void fill_line_map(struct commit *commit, struct commit *other, int cur_chunk = 0; int i1, i2; - if (p->num && DEBUG) - print_patch(p); - - if (DEBUG) + if (DEBUG) { + if (p->num) + print_patch(p); printf("num lines 1: %d num lines 2: %d\n", util->num_lines, util2->num_lines); + } for (i1 = 0, i2 = 0; i1 < util->num_lines; i1++, i2++) { struct chunk *chunk = NULL; @@ -298,7 +300,8 @@ static void fill_line_map(struct commit *commit, struct commit *other, i2 += chunk->len2; cur_chunk++; - } else { + } + else { if (i2 >= util2->num_lines) break; @@ -332,19 +335,15 @@ static int map_line(struct commit *commit, int line) return info->line_map[line]; } -static struct util_info* get_util(struct commit *commit) +static struct util_info *get_util(struct commit *commit) { struct util_info *util = commit->util; if (util) return util; - util = xmalloc(sizeof(struct util_info)); - util->buf = NULL; - util->size = 0; - util->line_map = NULL; + util = xcalloc(1, sizeof(struct util_info)); util->num_lines = -1; - util->pathname = NULL; commit->util = util; return util; } @@ -374,7 +373,7 @@ static void alloc_line_map(struct commit *commit) if (util->buf[i] == '\n') util->num_lines++; } - if(util->buf[util->size - 1] != '\n') + if (util->buf[util->size - 1] != '\n') util->num_lines++; util->line_map = xmalloc(sizeof(int) * util->num_lines); @@ -383,9 +382,9 @@ static void alloc_line_map(struct commit *commit) util->line_map[i] = -1; } -static void init_first_commit(struct commit* commit, const char* filename) +static void init_first_commit(struct commit *commit, const char *filename) { - struct util_info* util = commit->util; + struct util_info *util = commit->util; int i; util->pathname = filename; @@ -400,18 +399,17 @@ static void init_first_commit(struct commit* commit, const char* filename) util->line_map[i] = i; } - static void process_commits(struct rev_info *rev, const char *path, - struct commit** initial) + struct commit **initial) { int i; - struct util_info* util; + struct util_info *util; int lines_left; int *blame_p; int *new_lines; int new_lines_len; - struct commit* commit = get_revision(rev); + struct commit *commit = get_revision(rev); assert(commit); init_first_commit(commit, path); @@ -447,7 +445,7 @@ static void process_commits(struct rev_info *rev, const char *path, parents != NULL; parents = parents->next) num_parents++; - if(num_parents == 0) + if (num_parents == 0) *initial = commit; if (fill_util_info(commit)) @@ -508,13 +506,12 @@ static void process_commits(struct rev_info *rev, const char *path, } while ((commit = get_revision(rev)) != NULL); } - -static int compare_tree_path(struct rev_info* revs, - struct commit* c1, struct commit* c2) +static int compare_tree_path(struct rev_info *revs, + struct commit *c1, struct commit *c2) { int ret; - const char* paths[2]; - struct util_info* util = c2->util; + const char *paths[2]; + struct util_info *util = c2->util; paths[0] = util->pathname; paths[1] = NULL; @@ -525,12 +522,11 @@ static int compare_tree_path(struct rev_info* revs, return ret; } - -static int same_tree_as_empty_path(struct rev_info *revs, struct tree* t1, - const char* path) +static int same_tree_as_empty_path(struct rev_info *revs, struct tree *t1, + const char *path) { int ret; - const char* paths[2]; + const char *paths[2]; paths[0] = path; paths[1] = NULL; @@ -541,9 +537,9 @@ static int same_tree_as_empty_path(struct rev_info *revs, struct tree* t1, return ret; } -static const char* find_rename(struct commit* commit, struct commit* parent) +static const char *find_rename(struct commit *commit, struct commit *parent) { - struct util_info* cutil = commit->util; + struct util_info *cutil = commit->util; struct diff_options diff_opts; const char *paths[1]; int i; @@ -569,9 +565,11 @@ static const char* find_rename(struct commit* commit, struct commit* parent) for (i = 0; i < diff_queued_diff.nr; i++) { struct diff_filepair *p = diff_queued_diff.queue[i]; - if (p->status == 'R' && !strcmp(p->one->path, cutil->pathname)) { + if (p->status == 'R' && + !strcmp(p->one->path, cutil->pathname)) { if (DEBUG) - printf("rename %s -> %s\n", p->one->path, p->two->path); + printf("rename %s -> %s\n", + p->one->path, p->two->path); return p->two->path; } } @@ -587,7 +585,7 @@ static void simplify_commit(struct rev_info *revs, struct commit *commit) return; if (!commit->parents) { - struct util_info* util = commit->util; + struct util_info *util = commit->util; if (!same_tree_as_empty_path(revs, commit->tree, util->pathname)) commit->object.flags |= TREECHANGE; @@ -613,17 +611,17 @@ static void simplify_commit(struct rev_info *revs, struct commit *commit) case REV_TREE_NEW: { - - struct util_info* util = commit->util; + struct util_info *util = commit->util; if (revs->remove_empty_trees && same_tree_as_empty_path(revs, p->tree, util->pathname)) { - const char* new_name = find_rename(commit, p); + const char *new_name = find_rename(commit, p); if (new_name) { - struct util_info* putil = get_util(p); + struct util_info *putil = get_util(p); if (!putil->pathname) putil->pathname = xstrdup(new_name); - } else { + } + else { *pp = parent->next; continue; } @@ -644,47 +642,106 @@ static void simplify_commit(struct rev_info *revs, struct commit *commit) commit->object.flags |= TREECHANGE; } - struct commit_info { - char* author; - char* author_mail; + char *author; + char *author_mail; unsigned long author_time; - char* author_tz; + char *author_tz; + + /* filled only when asked for details */ + char *committer; + char *committer_mail; + unsigned long committer_time; + char *committer_tz; + + char *summary; }; -static void get_commit_info(struct commit* commit, struct commit_info* ret) +static void get_ac_line(const char *inbuf, const char *what, + int bufsz, char *person, char **mail, + unsigned long *time, char **tz) { int len; - char* tmp; - static char author_buf[1024]; - - tmp = strstr(commit->buffer, "\nauthor ") + 8; - len = strchr(tmp, '\n') - tmp; - ret->author = author_buf; - memcpy(ret->author, tmp, len); + char *tmp, *endp; + + tmp = strstr(inbuf, what); + if (!tmp) + goto error_out; + tmp += strlen(what); + endp = strchr(tmp, '\n'); + if (!endp) + len = strlen(tmp); + else + len = endp - tmp; + if (bufsz <= len) { + error_out: + /* Ugh */ + person = *mail = *tz = "(unknown)"; + *time = 0; + return; + } + memcpy(person, tmp, len); - tmp = ret->author; + tmp = person; tmp += len; *tmp = 0; - while(*tmp != ' ') + while (*tmp != ' ') tmp--; - ret->author_tz = tmp+1; + *tz = tmp+1; *tmp = 0; - while(*tmp != ' ') + while (*tmp != ' ') tmp--; - ret->author_time = strtoul(tmp, NULL, 10); + *time = strtoul(tmp, NULL, 10); *tmp = 0; - while(*tmp != ' ') + while (*tmp != ' ') tmp--; - ret->author_mail = tmp + 1; - + *mail = tmp + 1; *tmp = 0; } -static const char* format_time(unsigned long time, const char* tz_str, +static void get_commit_info(struct commit *commit, struct commit_info *ret, int detailed) +{ + int len; + char *tmp, *endp; + static char author_buf[1024]; + static char committer_buf[1024]; + static char summary_buf[1024]; + + ret->author = author_buf; + get_ac_line(commit->buffer, "\nauthor ", + sizeof(author_buf), author_buf, &ret->author_mail, + &ret->author_time, &ret->author_tz); + + if (!detailed) + return; + + ret->committer = committer_buf; + get_ac_line(commit->buffer, "\ncommitter ", + sizeof(committer_buf), committer_buf, &ret->committer_mail, + &ret->committer_time, &ret->committer_tz); + + ret->summary = summary_buf; + tmp = strstr(commit->buffer, "\n\n"); + if (!tmp) { + error_out: + sprintf(summary_buf, "(%s)", sha1_to_hex(commit->object.sha1)); + return; + } + tmp += 2; + endp = strchr(tmp, '\n'); + if (!endp) + goto error_out; + len = endp - tmp; + if (len >= sizeof(summary_buf)) + goto error_out; + memcpy(summary_buf, tmp, len); + summary_buf[len] = 0; +} + +static const char *format_time(unsigned long time, const char *tz_str, int show_raw_time) { static char time_buf[128]; @@ -709,15 +766,15 @@ static const char* format_time(unsigned long time, const char* tz_str, return time_buf; } -static void topo_setter(struct commit* c, void* data) +static void topo_setter(struct commit *c, void *data) { - struct util_info* util = c->util; + struct util_info *util = c->util; util->topo_data = data; } -static void* topo_getter(struct commit* c) +static void *topo_getter(struct commit *c) { - struct util_info* util = c->util; + struct util_info *util = c->util; return util->topo_data; } @@ -740,6 +797,101 @@ static int read_ancestry(const char *graft_file, return 0; } +static int lineno_width(int lines) +{ + int i, width; + + for (width = 1, i = 10; i <= lines + 1; width++) + i *= 10; + return width; +} + +static int find_orig_linenum(struct util_info *u, int lineno) +{ + int i; + + for (i = 0; i < u->num_lines; i++) + if (lineno == u->line_map[i]) + return i + 1; + return 0; +} + +static void emit_meta(struct commit *c, int lno, + int sha1_len, int compatibility, int porcelain, + int show_name, int show_number, int show_raw_time, + int longest_file, int longest_author, + int max_digits, int max_orig_digits) +{ + struct util_info *u; + int lineno; + struct commit_info ci; + + u = c->util; + lineno = find_orig_linenum(u, lno); + + if (porcelain) { + int group_size = -1; + struct commit *cc = (lno == 0) ? NULL : blame_lines[lno-1]; + if (cc != c) { + /* This is the beginning of this group */ + int i; + for (i = lno + 1; i < num_blame_lines; i++) + if (blame_lines[i] != c) + break; + group_size = i - lno; + } + if (0 < group_size) + printf("%s %d %d %d\n", sha1_to_hex(c->object.sha1), + lineno, lno + 1, group_size); + else + printf("%s %d %d\n", sha1_to_hex(c->object.sha1), + lineno, lno + 1); + if (!u->meta_given) { + get_commit_info(c, &ci, 1); + printf("author %s\n", ci.author); + printf("author-mail %s\n", ci.author_mail); + printf("author-time %lu\n", ci.author_time); + printf("author-tz %s\n", ci.author_tz); + printf("committer %s\n", ci.committer); + printf("committer-mail %s\n", ci.committer_mail); + printf("committer-time %lu\n", ci.committer_time); + printf("committer-tz %s\n", ci.committer_tz); + printf("filename "); + if (quote_c_style(u->pathname, NULL, NULL, 0)) + quote_c_style(u->pathname, NULL, stdout, 0); + else + fputs(u->pathname, stdout); + printf("\nsummary %s\n", ci.summary); + + u->meta_given = 1; + } + putchar('\t'); + return; + } + + get_commit_info(c, &ci, 0); + fwrite(sha1_to_hex(c->object.sha1), sha1_len, 1, stdout); + if (compatibility) { + printf("\t(%10s\t%10s\t%d)", ci.author, + format_time(ci.author_time, ci.author_tz, + show_raw_time), + lno + 1); + } + else { + if (show_name) + printf(" %-*.*s", longest_file, longest_file, + u->pathname); + if (show_number) + printf(" %*d", max_orig_digits, + lineno); + printf(" (%-*.*s %10s %*d) ", + longest_author, longest_author, ci.author, + format_time(ci.author_time, ci.author_tz, + show_raw_time), + max_digits, lno + 1); + } +} + int main(int argc, const char **argv) { int i; @@ -752,38 +904,43 @@ int main(int argc, const char **argv) int compatibility = 0; int show_raw_time = 0; int options = 1; - struct commit* start_commit; + struct commit *start_commit; - const char* args[10]; + const char *args[10]; struct rev_info rev; struct commit_info ci; const char *buf; - int max_digits; - int longest_file, longest_author; - int found_rename; + int max_digits, max_orig_digits; + int longest_file, longest_author, longest_file_lines; + int show_name = 0; + int show_number = 0; + int porcelain = 0; - const char* prefix = setup_git_directory(); + const char *prefix = setup_git_directory(); git_config(git_default_config); - for(i = 1; i < argc; i++) { - if(options) { - if(!strcmp(argv[i], "-h") || + for (i = 1; i < argc; i++) { + if (options) { + if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "--help")) usage(blame_usage); - else if(!strcmp(argv[i], "-l") || - !strcmp(argv[i], "--long")) { + if (!strcmp(argv[i], "-l") || + !strcmp(argv[i], "--long")) { sha1_len = 40; continue; - } else if(!strcmp(argv[i], "-c") || - !strcmp(argv[i], "--compatibility")) { + } + if (!strcmp(argv[i], "-c") || + !strcmp(argv[i], "--compatibility")) { compatibility = 1; continue; - } else if(!strcmp(argv[i], "-t") || - !strcmp(argv[i], "--time")) { + } + if (!strcmp(argv[i], "-t") || + !strcmp(argv[i], "--time")) { show_raw_time = 1; continue; - } else if(!strcmp(argv[i], "-S")) { + } + if (!strcmp(argv[i], "-S")) { if (i + 1 < argc && !read_ancestry(argv[i + 1], &sha1_p)) { compatibility = 1; @@ -791,33 +948,51 @@ int main(int argc, const char **argv) continue; } usage(blame_usage); - } else if(!strcmp(argv[i], "--")) { + } + if (!strcmp(argv[i], "-f") || + !strcmp(argv[i], "--show-name")) { + show_name = 1; + continue; + } + if (!strcmp(argv[i], "-n") || + !strcmp(argv[i], "--show-number")) { + show_number = 1; + continue; + } + if (!strcmp(argv[i], "-p") || + !strcmp(argv[i], "--porcelain")) { + porcelain = 1; + sha1_len = 40; + show_raw_time = 1; + continue; + } + if (!strcmp(argv[i], "--")) { options = 0; continue; - } else if(argv[i][0] == '-') + } + if (argv[i][0] == '-') usage(blame_usage); - else - options = 0; + options = 0; } - if(!options) { - if(!filename) + if (!options) { + if (!filename) filename = argv[i]; - else if(!commit) + else if (!commit) commit = argv[i]; else usage(blame_usage); } } - if(!filename) + if (!filename) usage(blame_usage); if (commit && sha1_p) usage(blame_usage); - else if(!commit) + else if (!commit) commit = "HEAD"; - if(prefix) + if (prefix) sprintf(filename_buf, "%s%s", prefix, filename); else strcpy(filename_buf, filename); @@ -835,7 +1010,6 @@ int main(int argc, const char **argv) return 1; } - init_revisions(&rev, setup_git_directory()); rev.remove_empty_trees = 1; rev.topo_order = 1; @@ -853,62 +1027,49 @@ int main(int argc, const char **argv) prepare_revision_walk(&rev); process_commits(&rev, filename, &initial); + for (i = 0; i < num_blame_lines; i++) + if (!blame_lines[i]) + blame_lines[i] = initial; + buf = blame_contents; - for (max_digits = 1, i = 10; i <= num_blame_lines + 1; max_digits++) - i *= 10; + max_digits = lineno_width(num_blame_lines); longest_file = 0; longest_author = 0; - found_rename = 0; + longest_file_lines = 0; for (i = 0; i < num_blame_lines; i++) { struct commit *c = blame_lines[i]; - struct util_info* u; - if (!c) - c = initial; + struct util_info *u; u = c->util; - if (!found_rename && strcmp(filename, u->pathname)) - found_rename = 1; + if (!show_name && strcmp(filename, u->pathname)) + show_name = 1; if (longest_file < strlen(u->pathname)) longest_file = strlen(u->pathname); - get_commit_info(c, &ci); + if (longest_file_lines < u->num_lines) + longest_file_lines = u->num_lines; + get_commit_info(c, &ci, 0); if (longest_author < strlen(ci.author)) longest_author = strlen(ci.author); } - for (i = 0; i < num_blame_lines; i++) { - struct commit *c = blame_lines[i]; - struct util_info* u; + max_orig_digits = lineno_width(longest_file_lines); - if (!c) - c = initial; - - u = c->util; - get_commit_info(c, &ci); - fwrite(sha1_to_hex(c->object.sha1), sha1_len, 1, stdout); - if(compatibility) { - printf("\t(%10s\t%10s\t%d)", ci.author, - format_time(ci.author_time, ci.author_tz, - show_raw_time), - i+1); - } else { - if (found_rename) - printf(" %-*.*s", longest_file, longest_file, - u->pathname); - printf(" (%-*.*s %10s %*d) ", - longest_author, longest_author, ci.author, - format_time(ci.author_time, ci.author_tz, - show_raw_time), - max_digits, i+1); - } + for (i = 0; i < num_blame_lines; i++) { + emit_meta(blame_lines[i], i, + sha1_len, compatibility, porcelain, + show_name, show_number, show_raw_time, + longest_file, longest_author, + max_digits, max_orig_digits); - if(i == num_blame_lines - 1) { + if (i == num_blame_lines - 1) { fwrite(buf, blame_len - (buf - blame_contents), 1, stdout); - if(blame_contents[blame_len-1] != '\n') + if (blame_contents[blame_len-1] != '\n') putc('\n', stdout); - } else { - char* next_buf = strchr(buf, '\n') + 1; + } + else { + char *next_buf = strchr(buf, '\n') + 1; fwrite(buf, next_buf - buf, 1, stdout); buf = next_buf; } diff --git a/builtin-annotate.c b/builtin-annotate.c new file mode 100644 index 0000000000..25ad473711 --- /dev/null +++ b/builtin-annotate.c @@ -0,0 +1,25 @@ +/* + * "git annotate" builtin alias + * + * Copyright (C) 2006 Ryan Anderson + */ +#include "git-compat-util.h" +#include "exec_cmd.h" + +int cmd_annotate(int argc, const char **argv, const char *prefix) +{ + const char **nargv; + int i; + nargv = xmalloc(sizeof(char *) * (argc + 2)); + + nargv[0] = "blame"; + nargv[1] = "-c"; + + for (i = 1; i < argc; i++) { + nargv[i+1] = argv[i]; + } + nargv[argc + 1] = NULL; + + return execv_git_cmd(nargv); +} + diff --git a/builtin-apply.c b/builtin-apply.c index 6ec22b8daa..aad55261fa 100644 --- a/builtin-apply.c +++ b/builtin-apply.c @@ -360,7 +360,7 @@ static int gitdiff_hdrend(const char *line, struct patch *patch) static char *gitdiff_verify_name(const char *line, int isnull, char *orig_name, const char *oldnew) { if (!orig_name && !isnull) - return find_name(line, NULL, 1, 0); + return find_name(line, NULL, 1, TERM_TAB); if (orig_name) { int len; @@ -370,7 +370,7 @@ static char *gitdiff_verify_name(const char *line, int isnull, char *orig_name, len = strlen(name); if (isnull) die("git-apply: bad git-diff - expected /dev/null, got %s on line %d", name, linenr); - another = find_name(line, NULL, 1, 0); + another = find_name(line, NULL, 1, TERM_TAB); if (!another || memcmp(another, name, len)) die("git-apply: bad git-diff - inconsistent %s filename on line %d", oldnew, linenr); free(another); diff --git a/builtin-branch.c b/builtin-branch.c new file mode 100644 index 0000000000..368b68ec91 --- /dev/null +++ b/builtin-branch.c @@ -0,0 +1,221 @@ +/* + * Builtin "git branch" + * + * Copyright (c) 2006 Kristian Høgsberg <krh@redhat.com> + * Based on git-branch.sh by Junio C Hamano. + */ + +#include "cache.h" +#include "refs.h" +#include "commit.h" +#include "builtin.h" + +static const char builtin_branch_usage[] = +"git-branch (-d | -D) <branchname> | [-l] [-f] <branchname> [<start-point>] | [-r]"; + + +static const char *head; +static unsigned char head_sha1[20]; + +static int in_merge_bases(const unsigned char *sha1, + struct commit *rev1, + struct commit *rev2) +{ + struct commit_list *bases, *b; + int ret = 0; + + bases = get_merge_bases(rev1, rev2, 1); + for (b = bases; b; b = b->next) { + if (!hashcmp(sha1, b->item->object.sha1)) { + ret = 1; + break; + } + } + + free_commit_list(bases); + return ret; +} + +static void delete_branches(int argc, const char **argv, int force) +{ + struct commit *rev, *head_rev; + unsigned char sha1[20]; + char *name; + int i; + + head_rev = lookup_commit_reference(head_sha1); + for (i = 0; i < argc; i++) { + if (!strcmp(head, argv[i])) + die("Cannot delete the branch you are currently on."); + + name = xstrdup(mkpath("refs/heads/%s", argv[i])); + if (!resolve_ref(name, sha1, 1, NULL)) + die("Branch '%s' not found.", argv[i]); + + rev = lookup_commit_reference(sha1); + if (!rev || !head_rev) + die("Couldn't look up commit objects."); + + /* This checks whether the merge bases of branch and + * HEAD contains branch -- which means that the HEAD + * contains everything in both. + */ + + if (!force && + !in_merge_bases(sha1, rev, head_rev)) { + fprintf(stderr, + "The branch '%s' is not a strict subset of your current HEAD.\n" + "If you are sure you want to delete it, run 'git branch -D %s'.\n", + argv[i], argv[i]); + exit(1); + } + + if (delete_ref(name, sha1)) + printf("Error deleting branch '%s'\n", argv[i]); + else + printf("Deleted branch %s.\n", argv[i]); + + free(name); + } +} + +static int ref_index, ref_alloc; +static char **ref_list; + +static int append_ref(const char *refname, const unsigned char *sha1, int flags, + void *cb_data) +{ + if (ref_index >= ref_alloc) { + ref_alloc = alloc_nr(ref_alloc); + ref_list = xrealloc(ref_list, ref_alloc * sizeof(char *)); + } + + ref_list[ref_index++] = xstrdup(refname); + + return 0; +} + +static int ref_cmp(const void *r1, const void *r2) +{ + return strcmp(*(char **)r1, *(char **)r2); +} + +static void print_ref_list(int remote_only) +{ + int i; + char c; + + if (remote_only) + for_each_remote_ref(append_ref, NULL); + else + for_each_branch_ref(append_ref, NULL); + + qsort(ref_list, ref_index, sizeof(char *), ref_cmp); + + for (i = 0; i < ref_index; i++) { + c = ' '; + if (!strcmp(ref_list[i], head)) + c = '*'; + + printf("%c %s\n", c, ref_list[i]); + } +} + +static void create_branch(const char *name, const char *start, + int force, int reflog) +{ + struct ref_lock *lock; + struct commit *commit; + unsigned char sha1[20]; + char ref[PATH_MAX], msg[PATH_MAX + 20]; + + snprintf(ref, sizeof ref, "refs/heads/%s", name); + if (check_ref_format(ref)) + die("'%s' is not a valid branch name.", name); + + if (resolve_ref(ref, sha1, 1, NULL)) { + if (!force) + die("A branch named '%s' already exists.", name); + else if (!strcmp(head, name)) + die("Cannot force update the current branch."); + } + + if (get_sha1(start, sha1) || + (commit = lookup_commit_reference(sha1)) == NULL) + die("Not a valid branch point: '%s'.", start); + hashcpy(sha1, commit->object.sha1); + + lock = lock_any_ref_for_update(ref, NULL); + if (!lock) + die("Failed to lock ref for update: %s.", strerror(errno)); + + if (reflog) { + log_all_ref_updates = 1; + snprintf(msg, sizeof msg, "branch: Created from %s", start); + } + + if (write_ref_sha1(lock, sha1, msg) < 0) + die("Failed to write ref: %s.", strerror(errno)); +} + +int cmd_branch(int argc, const char **argv, const char *prefix) +{ + int delete = 0, force_delete = 0, force_create = 0, remote_only = 0; + int reflog = 0; + int i; + + git_config(git_default_config); + + for (i = 1; i < argc; i++) { + const char *arg = argv[i]; + + if (arg[0] != '-') + break; + if (!strcmp(arg, "--")) { + i++; + break; + } + if (!strcmp(arg, "-d")) { + delete = 1; + continue; + } + if (!strcmp(arg, "-D")) { + delete = 1; + force_delete = 1; + continue; + } + if (!strcmp(arg, "-f")) { + force_create = 1; + continue; + } + if (!strcmp(arg, "-r")) { + remote_only = 1; + continue; + } + if (!strcmp(arg, "-l")) { + reflog = 1; + continue; + } + usage(builtin_branch_usage); + } + + head = xstrdup(resolve_ref("HEAD", head_sha1, 0, NULL)); + if (!head) + die("Failed to resolve HEAD as a valid ref."); + if (strncmp(head, "refs/heads/", 11)) + die("HEAD not found below refs/heads!"); + head += 11; + + if (delete) + delete_branches(argc - i, argv + i, force_delete); + else if (i == argc) + print_ref_list(remote_only); + else if (i == argc - 1) + create_branch(argv[i], head, force_create, reflog); + else if (i == argc - 2) + create_branch(argv[i], argv[i + 1], force_create, reflog); + else + usage(builtin_branch_usage); + + return 0; +} diff --git a/builtin-fmt-merge-msg.c b/builtin-fmt-merge-msg.c index c407c033e7..3d3097d299 100644 --- a/builtin-fmt-merge-msg.c +++ b/builtin-fmt-merge-msg.c @@ -249,7 +249,7 @@ int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix) FILE *in = stdin; const char *sep = ""; unsigned char head_sha1[20]; - const char *head, *current_branch; + const char *current_branch; git_config(fmt_merge_msg_config); @@ -277,10 +277,7 @@ int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix) usage(fmt_merge_msg_usage); /* get current branch */ - head = xstrdup(git_path("HEAD")); - current_branch = resolve_ref(head, head_sha1, 1); - current_branch += strlen(head) - 4; - free((char *)head); + current_branch = resolve_ref("HEAD", head_sha1, 1, NULL); if (!strncmp(current_branch, "refs/heads/", 11)) current_branch += 11; diff --git a/builtin-for-each-ref.c b/builtin-for-each-ref.c new file mode 100644 index 0000000000..173bf38735 --- /dev/null +++ b/builtin-for-each-ref.c @@ -0,0 +1,896 @@ +#include "cache.h" +#include "refs.h" +#include "object.h" +#include "tag.h" +#include "commit.h" +#include "tree.h" +#include "blob.h" +#include "quote.h" +#include <fnmatch.h> + +/* Quoting styles */ +#define QUOTE_NONE 0 +#define QUOTE_SHELL 1 +#define QUOTE_PERL 2 +#define QUOTE_PYTHON 3 + +typedef enum { FIELD_STR, FIELD_ULONG, FIELD_TIME } cmp_type; + +struct atom_value { + const char *s; + unsigned long ul; /* used for sorting when not FIELD_STR */ +}; + +struct ref_sort { + struct ref_sort *next; + int atom; /* index into used_atom array */ + unsigned reverse : 1; +}; + +struct refinfo { + char *refname; + unsigned char objectname[20]; + struct atom_value *value; +}; + +static struct { + const char *name; + cmp_type cmp_type; +} valid_atom[] = { + { "refname" }, + { "objecttype" }, + { "objectsize", FIELD_ULONG }, + { "objectname" }, + { "tree" }, + { "parent" }, /* NEEDSWORK: how to address 2nd and later parents? */ + { "numparent", FIELD_ULONG }, + { "object" }, + { "type" }, + { "tag" }, + { "author" }, + { "authorname" }, + { "authoremail" }, + { "authordate", FIELD_TIME }, + { "committer" }, + { "committername" }, + { "committeremail" }, + { "committerdate", FIELD_TIME }, + { "tagger" }, + { "taggername" }, + { "taggeremail" }, + { "taggerdate", FIELD_TIME }, + { "creator" }, + { "creatordate", FIELD_TIME }, + { "subject" }, + { "body" }, + { "contents" }, +}; + +/* + * An atom is a valid field atom listed above, possibly prefixed with + * a "*" to denote deref_tag(). + * + * We parse given format string and sort specifiers, and make a list + * of properties that we need to extract out of objects. refinfo + * structure will hold an array of values extracted that can be + * indexed with the "atom number", which is an index into this + * array. + */ +static const char **used_atom; +static cmp_type *used_atom_type; +static int used_atom_cnt, sort_atom_limit, need_tagged; + +/* + * Used to parse format string and sort specifiers + */ +static int parse_atom(const char *atom, const char *ep) +{ + const char *sp; + char *n; + int i, at; + + sp = atom; + if (*sp == '*' && sp < ep) + sp++; /* deref */ + if (ep <= sp) + die("malformed field name: %.*s", (int)(ep-atom), atom); + + /* Do we have the atom already used elsewhere? */ + for (i = 0; i < used_atom_cnt; i++) { + int len = strlen(used_atom[i]); + if (len == ep - atom && !memcmp(used_atom[i], atom, len)) + return i; + } + + /* Is the atom a valid one? */ + for (i = 0; i < ARRAY_SIZE(valid_atom); i++) { + int len = strlen(valid_atom[i].name); + if (len == ep - sp && !memcmp(valid_atom[i].name, sp, len)) + break; + } + + if (ARRAY_SIZE(valid_atom) <= i) + die("unknown field name: %.*s", (int)(ep-atom), atom); + + /* Add it in, including the deref prefix */ + at = used_atom_cnt; + used_atom_cnt++; + used_atom = xrealloc(used_atom, + (sizeof *used_atom) * used_atom_cnt); + used_atom_type = xrealloc(used_atom_type, + (sizeof(*used_atom_type) * used_atom_cnt)); + n = xmalloc(ep - atom + 1); + memcpy(n, atom, ep - atom); + n[ep-atom] = 0; + used_atom[at] = n; + used_atom_type[at] = valid_atom[i].cmp_type; + return at; +} + +/* + * In a format string, find the next occurrence of %(atom). + */ +static const char *find_next(const char *cp) +{ + while (*cp) { + if (*cp == '%') { + /* %( is the start of an atom; + * %% is a quoteed per-cent. + */ + if (cp[1] == '(') + return cp; + else if (cp[1] == '%') + cp++; /* skip over two % */ + /* otherwise this is a singleton, literal % */ + } + cp++; + } + return NULL; +} + +/* + * Make sure the format string is well formed, and parse out + * the used atoms. + */ +static void verify_format(const char *format) +{ + const char *cp, *sp; + for (cp = format; *cp && (sp = find_next(cp)); ) { + const char *ep = strchr(sp, ')'); + if (!ep) + die("malformatted format string %s", sp); + /* sp points at "%(" and ep points at the closing ")" */ + parse_atom(sp + 2, ep); + cp = ep + 1; + } +} + +/* + * Given an object name, read the object data and size, and return a + * "struct object". If the object data we are returning is also borrowed + * by the "struct object" representation, set *eaten as well---it is a + * signal from parse_object_buffer to us not to free the buffer. + */ +static void *get_obj(const unsigned char *sha1, struct object **obj, unsigned long *sz, int *eaten) +{ + char type[20]; + void *buf = read_sha1_file(sha1, type, sz); + + if (buf) + *obj = parse_object_buffer(sha1, type, *sz, buf, eaten); + else + *obj = NULL; + return buf; +} + +/* See grab_values */ +static void grab_common_values(struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz) +{ + int i; + + for (i = 0; i < used_atom_cnt; i++) { + const char *name = used_atom[i]; + struct atom_value *v = &val[i]; + if (!!deref != (*name == '*')) + continue; + if (deref) + name++; + if (!strcmp(name, "objecttype")) + v->s = type_names[obj->type]; + else if (!strcmp(name, "objectsize")) { + char *s = xmalloc(40); + sprintf(s, "%lu", sz); + v->ul = sz; + v->s = s; + } + else if (!strcmp(name, "objectname")) { + char *s = xmalloc(41); + strcpy(s, sha1_to_hex(obj->sha1)); + v->s = s; + } + } +} + +/* See grab_values */ +static void grab_tag_values(struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz) +{ + int i; + struct tag *tag = (struct tag *) obj; + + for (i = 0; i < used_atom_cnt; i++) { + const char *name = used_atom[i]; + struct atom_value *v = &val[i]; + if (!!deref != (*name == '*')) + continue; + if (deref) + name++; + if (!strcmp(name, "tag")) + v->s = tag->tag; + } +} + +static int num_parents(struct commit *commit) +{ + struct commit_list *parents; + int i; + + for (i = 0, parents = commit->parents; + parents; + parents = parents->next) + i++; + return i; +} + +/* See grab_values */ +static void grab_commit_values(struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz) +{ + int i; + struct commit *commit = (struct commit *) obj; + + for (i = 0; i < used_atom_cnt; i++) { + const char *name = used_atom[i]; + struct atom_value *v = &val[i]; + if (!!deref != (*name == '*')) + continue; + if (deref) + name++; + if (!strcmp(name, "tree")) { + char *s = xmalloc(41); + strcpy(s, sha1_to_hex(commit->tree->object.sha1)); + v->s = s; + } + if (!strcmp(name, "numparent")) { + char *s = xmalloc(40); + sprintf(s, "%lu", v->ul); + v->s = s; + v->ul = num_parents(commit); + } + else if (!strcmp(name, "parent")) { + int num = num_parents(commit); + int i; + struct commit_list *parents; + char *s = xmalloc(42 * num); + v->s = s; + for (i = 0, parents = commit->parents; + parents; + parents = parents->next, i = i + 42) { + struct commit *parent = parents->item; + strcpy(s+i, sha1_to_hex(parent->object.sha1)); + if (parents->next) + s[i+40] = ' '; + } + } + } +} + +static const char *find_wholine(const char *who, int wholen, const char *buf, unsigned long sz) +{ + const char *eol; + while (*buf) { + if (!strncmp(buf, who, wholen) && + buf[wholen] == ' ') + return buf + wholen + 1; + eol = strchr(buf, '\n'); + if (!eol) + return ""; + eol++; + if (eol[1] == '\n') + return ""; /* end of header */ + buf = eol; + } + return ""; +} + +static char *copy_line(const char *buf) +{ + const char *eol = strchr(buf, '\n'); + char *line; + int len; + if (!eol) + return ""; + len = eol - buf; + line = xmalloc(len + 1); + memcpy(line, buf, len); + line[len] = 0; + return line; +} + +static char *copy_name(const char *buf) +{ + const char *eol = strchr(buf, '\n'); + const char *eoname = strstr(buf, " <"); + char *line; + int len; + if (!(eoname && eol && eoname < eol)) + return ""; + len = eoname - buf; + line = xmalloc(len + 1); + memcpy(line, buf, len); + line[len] = 0; + return line; +} + +static char *copy_email(const char *buf) +{ + const char *email = strchr(buf, '<'); + const char *eoemail = strchr(email, '>'); + char *line; + int len; + if (!email || !eoemail) + return ""; + eoemail++; + len = eoemail - email; + line = xmalloc(len + 1); + memcpy(line, email, len); + line[len] = 0; + return line; +} + +static void grab_date(const char *buf, struct atom_value *v) +{ + const char *eoemail = strstr(buf, "> "); + char *zone; + unsigned long timestamp; + long tz; + + if (!eoemail) + goto bad; + timestamp = strtoul(eoemail + 2, &zone, 10); + if (timestamp == ULONG_MAX) + goto bad; + tz = strtol(zone, NULL, 10); + if ((tz == LONG_MIN || tz == LONG_MAX) && errno == ERANGE) + goto bad; + v->s = xstrdup(show_date(timestamp, tz, 0)); + v->ul = timestamp; + return; + bad: + v->s = ""; + v->ul = 0; +} + +/* See grab_values */ +static void grab_person(const char *who, struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz) +{ + int i; + int wholen = strlen(who); + const char *wholine = NULL; + + for (i = 0; i < used_atom_cnt; i++) { + const char *name = used_atom[i]; + struct atom_value *v = &val[i]; + if (!!deref != (*name == '*')) + continue; + if (deref) + name++; + if (strncmp(who, name, wholen)) + continue; + if (name[wholen] != 0 && + strcmp(name + wholen, "name") && + strcmp(name + wholen, "email") && + strcmp(name + wholen, "date")) + continue; + if (!wholine) + wholine = find_wholine(who, wholen, buf, sz); + if (!wholine) + return; /* no point looking for it */ + if (name[wholen] == 0) + v->s = copy_line(wholine); + else if (!strcmp(name + wholen, "name")) + v->s = copy_name(wholine); + else if (!strcmp(name + wholen, "email")) + v->s = copy_email(wholine); + else if (!strcmp(name + wholen, "date")) + grab_date(wholine, v); + } + + /* For a tag or a commit object, if "creator" or "creatordate" is + * requested, do something special. + */ + if (strcmp(who, "tagger") && strcmp(who, "committer")) + return; /* "author" for commit object is not wanted */ + if (!wholine) + wholine = find_wholine(who, wholen, buf, sz); + if (!wholine) + return; + for (i = 0; i < used_atom_cnt; i++) { + const char *name = used_atom[i]; + struct atom_value *v = &val[i]; + if (!!deref != (*name == '*')) + continue; + if (deref) + name++; + + if (!strcmp(name, "creatordate")) + grab_date(wholine, v); + else if (!strcmp(name, "creator")) + v->s = copy_line(wholine); + } +} + +static void find_subpos(const char *buf, unsigned long sz, const char **sub, const char **body) +{ + while (*buf) { + const char *eol = strchr(buf, '\n'); + if (!eol) + return; + if (eol[1] == '\n') { + buf = eol + 1; + break; /* found end of header */ + } + buf = eol + 1; + } + while (*buf == '\n') + buf++; + if (!*buf) + return; + *sub = buf; /* first non-empty line */ + buf = strchr(buf, '\n'); + if (!buf) + return; /* no body */ + while (*buf == '\n') + buf++; /* skip blank between subject and body */ + *body = buf; +} + +/* See grab_values */ +static void grab_sub_body_contents(struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz) +{ + int i; + const char *subpos = NULL, *bodypos = NULL; + + for (i = 0; i < used_atom_cnt; i++) { + const char *name = used_atom[i]; + struct atom_value *v = &val[i]; + if (!!deref != (*name == '*')) + continue; + if (deref) + name++; + if (strcmp(name, "subject") && + strcmp(name, "body") && + strcmp(name, "contents")) + continue; + if (!subpos) + find_subpos(buf, sz, &subpos, &bodypos); + if (!subpos) + return; + + if (!strcmp(name, "subject")) + v->s = copy_line(subpos); + else if (!strcmp(name, "body")) + v->s = bodypos; + else if (!strcmp(name, "contents")) + v->s = subpos; + } +} + +/* We want to have empty print-string for field requests + * that do not apply (e.g. "authordate" for a tag object) + */ +static void fill_missing_values(struct atom_value *val) +{ + int i; + for (i = 0; i < used_atom_cnt; i++) { + struct atom_value *v = &val[i]; + if (v->s == NULL) + v->s = ""; + } +} + +/* + * val is a list of atom_value to hold returned values. Extract + * the values for atoms in used_atom array out of (obj, buf, sz). + * when deref is false, (obj, buf, sz) is the object that is + * pointed at by the ref itself; otherwise it is the object the + * ref (which is a tag) refers to. + */ +static void grab_values(struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz) +{ + grab_common_values(val, deref, obj, buf, sz); + switch (obj->type) { + case OBJ_TAG: + grab_tag_values(val, deref, obj, buf, sz); + grab_sub_body_contents(val, deref, obj, buf, sz); + grab_person("tagger", val, deref, obj, buf, sz); + break; + case OBJ_COMMIT: + grab_commit_values(val, deref, obj, buf, sz); + grab_sub_body_contents(val, deref, obj, buf, sz); + grab_person("author", val, deref, obj, buf, sz); + grab_person("committer", val, deref, obj, buf, sz); + break; + case OBJ_TREE: + // grab_tree_values(val, deref, obj, buf, sz); + break; + case OBJ_BLOB: + // grab_blob_values(val, deref, obj, buf, sz); + break; + default: + die("Eh? Object of type %d?", obj->type); + } +} + +/* + * Parse the object referred by ref, and grab needed value. + */ +static void populate_value(struct refinfo *ref) +{ + void *buf; + struct object *obj; + int eaten, i; + unsigned long size; + const unsigned char *tagged; + + ref->value = xcalloc(sizeof(struct atom_value), used_atom_cnt); + + buf = get_obj(ref->objectname, &obj, &size, &eaten); + if (!buf) + die("missing object %s for %s", + sha1_to_hex(ref->objectname), ref->refname); + if (!obj) + die("parse_object_buffer failed on %s for %s", + sha1_to_hex(ref->objectname), ref->refname); + + /* Fill in specials first */ + for (i = 0; i < used_atom_cnt; i++) { + const char *name = used_atom[i]; + struct atom_value *v = &ref->value[i]; + if (!strcmp(name, "refname")) + v->s = ref->refname; + else if (!strcmp(name, "*refname")) { + int len = strlen(ref->refname); + char *s = xmalloc(len + 4); + sprintf(s, "%s^{}", ref->refname); + v->s = s; + } + } + + grab_values(ref->value, 0, obj, buf, size); + if (!eaten) + free(buf); + + /* If there is no atom that wants to know about tagged + * object, we are done. + */ + if (!need_tagged || (obj->type != OBJ_TAG)) + return; + + /* If it is a tag object, see if we use a value that derefs + * the object, and if we do grab the object it refers to. + */ + tagged = ((struct tag *)obj)->tagged->sha1; + + /* NEEDSWORK: This derefs tag only once, which + * is good to deal with chains of trust, but + * is not consistent with what deref_tag() does + * which peels the onion to the core. + */ + buf = get_obj(tagged, &obj, &size, &eaten); + if (!buf) + die("missing object %s for %s", + sha1_to_hex(tagged), ref->refname); + if (!obj) + die("parse_object_buffer failed on %s for %s", + sha1_to_hex(tagged), ref->refname); + grab_values(ref->value, 1, obj, buf, size); + if (!eaten) + free(buf); +} + +/* + * Given a ref, return the value for the atom. This lazily gets value + * out of the object by calling populate value. + */ +static void get_value(struct refinfo *ref, int atom, struct atom_value **v) +{ + if (!ref->value) { + populate_value(ref); + fill_missing_values(ref->value); + } + *v = &ref->value[atom]; +} + +struct grab_ref_cbdata { + struct refinfo **grab_array; + const char **grab_pattern; + int grab_cnt; +}; + +/* + * A call-back given to for_each_ref(). It is unfortunate that we + * need to use global variables to pass extra information to this + * function. + */ +static int grab_single_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data) +{ + struct grab_ref_cbdata *cb = cb_data; + struct refinfo *ref; + int cnt; + + if (*cb->grab_pattern) { + const char **pattern; + int namelen = strlen(refname); + for (pattern = cb->grab_pattern; *pattern; pattern++) { + const char *p = *pattern; + int plen = strlen(p); + + if ((plen <= namelen) && + !strncmp(refname, p, plen) && + (refname[plen] == '\0' || + refname[plen] == '/')) + break; + if (!fnmatch(p, refname, FNM_PATHNAME)) + break; + } + if (!*pattern) + return 0; + } + + /* We do not open the object yet; sort may only need refname + * to do its job and the resulting list may yet to be pruned + * by maxcount logic. + */ + ref = xcalloc(1, sizeof(*ref)); + ref->refname = xstrdup(refname); + hashcpy(ref->objectname, sha1); + + cnt = cb->grab_cnt; + cb->grab_array = xrealloc(cb->grab_array, + sizeof(*cb->grab_array) * (cnt + 1)); + cb->grab_array[cnt++] = ref; + cb->grab_cnt = cnt; + return 0; +} + +static int cmp_ref_sort(struct ref_sort *s, struct refinfo *a, struct refinfo *b) +{ + struct atom_value *va, *vb; + int cmp; + cmp_type cmp_type = used_atom_type[s->atom]; + + get_value(a, s->atom, &va); + get_value(b, s->atom, &vb); + switch (cmp_type) { + case FIELD_STR: + cmp = strcmp(va->s, vb->s); + break; + default: + if (va->ul < vb->ul) + cmp = -1; + else if (va->ul == vb->ul) + cmp = 0; + else + cmp = 1; + break; + } + return (s->reverse) ? -cmp : cmp; +} + +static struct ref_sort *ref_sort; +static int compare_refs(const void *a_, const void *b_) +{ + struct refinfo *a = *((struct refinfo **)a_); + struct refinfo *b = *((struct refinfo **)b_); + struct ref_sort *s; + + for (s = ref_sort; s; s = s->next) { + int cmp = cmp_ref_sort(s, a, b); + if (cmp) + return cmp; + } + return 0; +} + +static void sort_refs(struct ref_sort *sort, struct refinfo **refs, int num_refs) +{ + ref_sort = sort; + qsort(refs, num_refs, sizeof(struct refinfo *), compare_refs); +} + +static void print_value(struct refinfo *ref, int atom, int quote_style) +{ + struct atom_value *v; + get_value(ref, atom, &v); + switch (quote_style) { + case QUOTE_NONE: + fputs(v->s, stdout); + break; + case QUOTE_SHELL: + sq_quote_print(stdout, v->s); + break; + case QUOTE_PERL: + perl_quote_print(stdout, v->s); + break; + case QUOTE_PYTHON: + python_quote_print(stdout, v->s); + break; + } +} + +static int hex1(char ch) +{ + if ('0' <= ch && ch <= '9') + return ch - '0'; + else if ('a' <= ch && ch <= 'f') + return ch - 'a' + 10; + else if ('A' <= ch && ch <= 'F') + return ch - 'A' + 10; + return -1; +} +static int hex2(const char *cp) +{ + if (cp[0] && cp[1]) + return (hex1(cp[0]) << 4) | hex1(cp[1]); + else + return -1; +} + +static void emit(const char *cp, const char *ep) +{ + while (*cp && (!ep || cp < ep)) { + if (*cp == '%') { + if (cp[1] == '%') + cp++; + else { + int ch = hex2(cp + 1); + if (0 <= ch) { + putchar(ch); + cp += 3; + continue; + } + } + } + putchar(*cp); + cp++; + } +} + +static void show_ref(struct refinfo *info, const char *format, int quote_style) +{ + const char *cp, *sp, *ep; + + for (cp = format; *cp && (sp = find_next(cp)); cp = ep + 1) { + ep = strchr(sp, ')'); + if (cp < sp) + emit(cp, sp); + print_value(info, parse_atom(sp + 2, ep), quote_style); + } + if (*cp) { + sp = cp + strlen(cp); + emit(cp, sp); + } + putchar('\n'); +} + +static struct ref_sort *default_sort(void) +{ + static const char cstr_name[] = "refname"; + + struct ref_sort *sort = xcalloc(1, sizeof(*sort)); + + sort->next = NULL; + sort->atom = parse_atom(cstr_name, cstr_name + strlen(cstr_name)); + return sort; +} + +int cmd_for_each_ref(int ac, const char **av, char *prefix) +{ + int i, num_refs; + const char *format = NULL; + struct ref_sort *sort = NULL, **sort_tail = &sort; + int maxcount = 0; + int quote_style = -1; /* unspecified yet */ + struct refinfo **refs; + struct grab_ref_cbdata cbdata; + + for (i = 1; i < ac; i++) { + const char *arg = av[i]; + if (arg[0] != '-') + break; + if (!strcmp(arg, "--")) { + i++; + break; + } + if (!strncmp(arg, "--format=", 9)) { + if (format) + die("more than one --format?"); + format = arg + 9; + continue; + } + if (!strcmp(arg, "-s") || !strcmp(arg, "--shell") ) { + if (0 <= quote_style) + die("more than one quoting style?"); + quote_style = QUOTE_SHELL; + continue; + } + if (!strcmp(arg, "-p") || !strcmp(arg, "--perl") ) { + if (0 <= quote_style) + die("more than one quoting style?"); + quote_style = QUOTE_PERL; + continue; + } + if (!strcmp(arg, "--python") ) { + if (0 <= quote_style) + die("more than one quoting style?"); + quote_style = QUOTE_PYTHON; + continue; + } + if (!strncmp(arg, "--count=", 8)) { + if (maxcount) + die("more than one --count?"); + maxcount = atoi(arg + 8); + if (maxcount <= 0) + die("The number %s did not parse", arg); + continue; + } + if (!strncmp(arg, "--sort=", 7)) { + struct ref_sort *s = xcalloc(1, sizeof(*s)); + int len; + + s->next = NULL; + *sort_tail = s; + sort_tail = &s->next; + + arg += 7; + if (*arg == '-') { + s->reverse = 1; + arg++; + } + len = strlen(arg); + sort->atom = parse_atom(arg, arg+len); + continue; + } + break; + } + if (quote_style < 0) + quote_style = QUOTE_NONE; + + if (!sort) + sort = default_sort(); + sort_atom_limit = used_atom_cnt; + if (!format) + format = "%(objectname) %(objecttype)\t%(refname)"; + + verify_format(format); + + memset(&cbdata, 0, sizeof(cbdata)); + cbdata.grab_pattern = av + i; + for_each_ref(grab_single_ref, &cbdata); + refs = cbdata.grab_array; + num_refs = cbdata.grab_cnt; + + for (i = 0; i < used_atom_cnt; i++) { + if (used_atom[i][0] == '*') { + need_tagged = 1; + break; + } + } + + sort_refs(sort, refs, num_refs); + + if (!maxcount || num_refs < maxcount) + maxcount = num_refs; + for (i = 0; i < maxcount; i++) + show_ref(refs[i], format, quote_style); + return 0; +} diff --git a/builtin-grep.c b/builtin-grep.c index 4205e5d38d..ad7dc00cde 100644 --- a/builtin-grep.c +++ b/builtin-grep.c @@ -596,6 +596,10 @@ int cmd_grep(int argc, const char **argv, const char *prefix) GREP_CLOSE_PAREN); continue; } + if (!strcmp("--all-match", arg)) { + opt.all_match = 1; + continue; + } if (!strcmp("-e", arg)) { if (1 < argc) { append_grep_pattern(&opt, argv[1], diff --git a/builtin-init-db.c b/builtin-init-db.c index c3ed1ce492..235a0ee48f 100644 --- a/builtin-init-db.c +++ b/builtin-init-db.c @@ -218,8 +218,8 @@ static void create_default_files(const char *git_dir, const char *template_path) * branch, if it does not exist yet. */ strcpy(path + len, "HEAD"); - if (read_ref(path, sha1) < 0) { - if (create_symref(path, "refs/heads/master") < 0) + if (read_ref("HEAD", sha1) < 0) { + if (create_symref("HEAD", "refs/heads/master") < 0) exit(1); } diff --git a/builtin-log.c b/builtin-log.c index 9d1ceae44c..fedb0137bc 100644 --- a/builtin-log.c +++ b/builtin-log.c @@ -171,8 +171,11 @@ static void reopen_stdout(struct commit *commit, int nr, int keep_subject) static int get_patch_id(struct commit *commit, struct diff_options *options, unsigned char *sha1) { - diff_tree_sha1(commit->parents->item->object.sha1, commit->object.sha1, - "", options); + if (commit->parents) + diff_tree_sha1(commit->parents->item->object.sha1, + commit->object.sha1, "", options); + else + diff_root_tree_sha1(commit->object.sha1, "", options); diffcore_std(options); return diff_flush_patch_id(options, sha1); } @@ -437,3 +440,109 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) return 0; } +static int add_pending_commit(const char *arg, struct rev_info *revs, int flags) +{ + unsigned char sha1[20]; + if (get_sha1(arg, sha1) == 0) { + struct commit *commit = lookup_commit_reference(sha1); + if (commit) { + commit->object.flags |= flags; + add_pending_object(revs, &commit->object, arg); + return 0; + } + } + return -1; +} + +static const char cherry_usage[] = +"git-cherry [-v] <upstream> [<head>] [<limit>]"; +int cmd_cherry(int argc, const char **argv, const char *prefix) +{ + struct rev_info revs; + struct diff_options patch_id_opts; + struct commit *commit; + struct commit_list *list = NULL; + const char *upstream; + const char *head = "HEAD"; + const char *limit = NULL; + int verbose = 0; + + if (argc > 1 && !strcmp(argv[1], "-v")) { + verbose = 1; + argc--; + argv++; + } + + switch (argc) { + case 4: + limit = argv[3]; + /* FALLTHROUGH */ + case 3: + head = argv[2]; + /* FALLTHROUGH */ + case 2: + upstream = argv[1]; + break; + default: + usage(cherry_usage); + } + + init_revisions(&revs, prefix); + revs.diff = 1; + revs.combine_merges = 0; + revs.ignore_merges = 1; + revs.diffopt.recursive = 1; + + if (add_pending_commit(head, &revs, 0)) + die("Unknown commit %s", head); + if (add_pending_commit(upstream, &revs, UNINTERESTING)) + die("Unknown commit %s", upstream); + + /* Don't say anything if head and upstream are the same. */ + if (revs.pending.nr == 2) { + struct object_array_entry *o = revs.pending.objects; + if (hashcmp(o[0].item->sha1, o[1].item->sha1) == 0) + return 0; + } + + get_patch_ids(&revs, &patch_id_opts, prefix); + + if (limit && add_pending_commit(limit, &revs, UNINTERESTING)) + die("Unknown commit %s", limit); + + /* reverse the list of commits */ + prepare_revision_walk(&revs); + while ((commit = get_revision(&revs)) != NULL) { + /* ignore merges */ + if (commit->parents && commit->parents->next) + continue; + + commit_list_insert(commit, &list); + } + + while (list) { + unsigned char sha1[20]; + char sign = '+'; + + commit = list->item; + if (!get_patch_id(commit, &patch_id_opts, sha1) && + lookup_object(sha1)) + sign = '-'; + + if (verbose) { + static char buf[16384]; + pretty_print_commit(CMIT_FMT_ONELINE, commit, ~0, + buf, sizeof(buf), 0, NULL, NULL, 0); + printf("%c %s %s\n", sign, + sha1_to_hex(commit->object.sha1), buf); + } + else { + printf("%c %s\n", sign, + sha1_to_hex(commit->object.sha1)); + } + + list = list->next; + } + + return 0; +} diff --git a/builtin-name-rev.c b/builtin-name-rev.c index 52886b69b0..618aa314d2 100644 --- a/builtin-name-rev.c +++ b/builtin-name-rev.c @@ -75,11 +75,10 @@ copy_data: } } -static int tags_only; - -static int name_ref(const char *path, const unsigned char *sha1) +static int name_ref(const char *path, const unsigned char *sha1, int flags, void *cb_data) { struct object *o = parse_object(sha1); + int tags_only = *(int*)cb_data; int deref = 0; if (tags_only && strncmp(path, "refs/tags/", 10)) @@ -131,6 +130,7 @@ int cmd_name_rev(int argc, const char **argv, const char *prefix) { struct object_array revs = { 0, 0, NULL }; int as_is = 0, all = 0, transform_stdin = 0; + int tags_only = 0; git_config(git_default_config); @@ -186,7 +186,7 @@ int cmd_name_rev(int argc, const char **argv, const char *prefix) add_object_array((struct object *)commit, *argv, &revs); } - for_each_ref(name_ref); + for_each_ref(name_ref, &tags_only); if (transform_stdin) { char buffer[2048]; diff --git a/builtin-pack-objects.c b/builtin-pack-objects.c index 96c069a81d..270bcbded6 100644 --- a/builtin-pack-objects.c +++ b/builtin-pack-objects.c @@ -15,7 +15,7 @@ #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] [--revs [--unpacked | --all]*] [--stdout | base-name] <ref-list | <object-list]"; +static const char pack_usage[] = "git-pack-objects [-q] [--no-reuse-delta] [--delta-base-offset] [--non-empty] [--local] [--incremental] [--window=N] [--depth=N] [--all-progress] [--revs [--unpacked | --all]*] [--stdout | base-name] <ref-list | <object-list]"; struct object_entry { unsigned char sha1[20]; @@ -29,6 +29,7 @@ struct object_entry { enum object_type type; enum object_type in_pack_type; /* could be delta */ unsigned long delta_size; /* delta data size (uncompressed) */ +#define in_pack_header_size delta_size /* only when reusing pack data */ struct object_entry *delta; /* delta base object */ struct packed_git *in_pack; /* already in pack */ unsigned int in_pack_offset; @@ -60,6 +61,8 @@ static int non_empty; static int no_reuse_delta; static int local; static int incremental; +static int allow_ofs_delta; + static struct object_entry **sorted_by_sha, **sorted_by_type; static struct object_entry *objects; static int nr_objects, nr_alloc, nr_result; @@ -84,17 +87,25 @@ static int object_ix_hashsz; * Pack index for existing packs give us easy access to the offsets into * corresponding pack file where each object's data starts, but the entries * do not store the size of the compressed representation (uncompressed - * size is easily available by examining the pack entry header). We build - * a hashtable of existing packs (pack_revindex), and keep reverse index - * here -- pack index file is sorted by object name mapping to offset; this - * pack_revindex[].revindex array is an ordered list of offsets, so if you - * know the offset of an object, next offset is where its packed - * representation ends. + * size is easily available by examining the pack entry header). It is + * also rather expensive to find the sha1 for an object given its offset. + * + * We build a hashtable of existing packs (pack_revindex), and keep reverse + * index here -- pack index file is sorted by object name mapping to offset; + * this pack_revindex[].revindex array is a list of offset/index_nr pairs + * ordered by offset, so if you know the offset of an object, next offset + * is where its packed representation ends and the index_nr can be used to + * get the object sha1 from the main index. */ +struct revindex_entry { + unsigned int offset; + unsigned int nr; +}; struct pack_revindex { struct packed_git *p; - unsigned long *revindex; -} *pack_revindex = NULL; + struct revindex_entry *revindex; +}; +static struct pack_revindex *pack_revindex; static int pack_revindex_hashsz; /* @@ -141,14 +152,9 @@ static void prepare_pack_ix(void) static int cmp_offset(const void *a_, const void *b_) { - unsigned long a = *(unsigned long *) a_; - unsigned long b = *(unsigned long *) b_; - if (a < b) - return -1; - else if (a == b) - return 0; - else - return 1; + const struct revindex_entry *a = a_; + const struct revindex_entry *b = b_; + return (a->offset < b->offset) ? -1 : (a->offset > b->offset) ? 1 : 0; } /* @@ -161,25 +167,27 @@ static void prepare_pack_revindex(struct pack_revindex *rix) int i; void *index = p->index_base + 256; - rix->revindex = xmalloc(sizeof(unsigned long) * (num_ent + 1)); + rix->revindex = xmalloc(sizeof(*rix->revindex) * (num_ent + 1)); for (i = 0; i < num_ent; i++) { unsigned int hl = *((unsigned int *)((char *) index + 24*i)); - rix->revindex[i] = ntohl(hl); + rix->revindex[i].offset = ntohl(hl); + rix->revindex[i].nr = i; } /* This knows the pack format -- the 20-byte trailer * follows immediately after the last object data. */ - rix->revindex[num_ent] = p->pack_size - 20; - qsort(rix->revindex, num_ent, sizeof(unsigned long), cmp_offset); + rix->revindex[num_ent].offset = p->pack_size - 20; + rix->revindex[num_ent].nr = -1; + qsort(rix->revindex, num_ent, sizeof(*rix->revindex), cmp_offset); } -static unsigned long find_packed_object_size(struct packed_git *p, - unsigned long ofs) +static struct revindex_entry * find_packed_object(struct packed_git *p, + unsigned int ofs) { int num; int lo, hi; struct pack_revindex *rix; - unsigned long *revindex; + struct revindex_entry *revindex; num = pack_revindex_ix(p); if (num < 0) die("internal error: pack revindex uninitialized"); @@ -191,10 +199,10 @@ static unsigned long find_packed_object_size(struct packed_git *p, hi = num_packed_objects(p) + 1; do { int mi = (lo + hi) / 2; - if (revindex[mi] == ofs) { - return revindex[mi+1] - ofs; + if (revindex[mi].offset == ofs) { + return revindex + mi; } - else if (ofs < revindex[mi]) + else if (ofs < revindex[mi].offset) hi = mi; else lo = mi + 1; @@ -202,6 +210,20 @@ static unsigned long find_packed_object_size(struct packed_git *p, die("internal error: pack revindex corrupt"); } +static unsigned long find_packed_object_size(struct packed_git *p, + unsigned long ofs) +{ + struct revindex_entry *entry = find_packed_object(p, ofs); + return entry[1].offset - ofs; +} + +static unsigned char *find_packed_object_name(struct packed_git *p, + unsigned long ofs) +{ + struct revindex_entry *entry = find_packed_object(p, ofs); + return (unsigned char *)(p->index_base + 256) + 24 * entry->nr + 4; +} + static void *delta_against(void *buf, unsigned long size, struct object_entry *entry) { unsigned long othersize, delta_size; @@ -232,7 +254,7 @@ static int encode_header(enum object_type type, unsigned long size, unsigned cha int n = 1; unsigned char c; - if (type < OBJ_COMMIT || type > OBJ_DELTA) + if (type < OBJ_COMMIT || type > OBJ_REF_DELTA) die("bad type %d", type); c = (type << 4) | (size & 15); @@ -247,6 +269,10 @@ static int encode_header(enum object_type type, unsigned long size, unsigned cha return n; } +/* + * we are going to reuse the existing object data as is. make + * sure it is not corrupt. + */ static int check_inflate(unsigned char *data, unsigned long len, unsigned long expect) { z_stream stream; @@ -278,32 +304,6 @@ static int check_inflate(unsigned char *data, unsigned long len, unsigned long e 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) @@ -334,13 +334,10 @@ static unsigned long write_object(struct sha1file *f, enum object_type obj_type; int to_reuse = 0; - if (entry->preferred_base) - return 0; - obj_type = entry->type; if (! entry->in_pack) to_reuse = 0; /* can't reuse what we don't have */ - else if (obj_type == OBJ_DELTA) + else if (obj_type == OBJ_REF_DELTA || obj_type == OBJ_OFS_DELTA) to_reuse = 1; /* check_object() decided it for us */ else if (obj_type != entry->in_pack_type) to_reuse = 0; /* pack has delta which is unusable */ @@ -380,18 +377,35 @@ static unsigned long write_object(struct sha1file *f, if (entry->delta) { buf = delta_against(buf, size, entry); size = entry->delta_size; - obj_type = OBJ_DELTA; + obj_type = (allow_ofs_delta && entry->delta->offset) ? + OBJ_OFS_DELTA : OBJ_REF_DELTA; } /* * The object header is a byte of 'type' followed by zero or - * more bytes of length. For deltas, the 20 bytes of delta - * sha1 follows that. + * more bytes of length. */ hdrlen = encode_header(obj_type, size, header); sha1write(f, header, hdrlen); - if (entry->delta) { - sha1write(f, entry->delta, 20); + if (obj_type == OBJ_OFS_DELTA) { + /* + * Deltas with relative base contain an additional + * encoding of the relative offset for the delta + * base from this object's position in the pack. + */ + unsigned long ofs = entry->offset - entry->delta->offset; + unsigned pos = sizeof(header) - 1; + header[pos] = ofs & 127; + while (ofs >>= 7) + header[--pos] = 128 | (--ofs & 127); + sha1write(f, header + pos, sizeof(header) - pos); + hdrlen += sizeof(header) - pos; + } else if (obj_type == OBJ_REF_DELTA) { + /* + * Deltas with a base reference contain + * an additional 20 bytes for the base sha1. + */ + sha1write(f, entry->delta->sha1, 20); hdrlen += 20; } datalen = sha1write_compressed(f, buf, size); @@ -399,21 +413,40 @@ static unsigned long write_object(struct sha1file *f, } else { struct packed_git *p = entry->in_pack; - use_packed_git(p); - datalen = find_packed_object_size(p, entry->in_pack_offset); - buf = (char *) p->pack_base + entry->in_pack_offset; + if (entry->delta) { + obj_type = (allow_ofs_delta && entry->delta->offset) ? + OBJ_OFS_DELTA : OBJ_REF_DELTA; + reused_delta++; + } + hdrlen = encode_header(obj_type, entry->size, header); + sha1write(f, header, hdrlen); + if (obj_type == OBJ_OFS_DELTA) { + unsigned long ofs = entry->offset - entry->delta->offset; + unsigned pos = sizeof(header) - 1; + header[pos] = ofs & 127; + while (ofs >>= 7) + header[--pos] = 128 | (--ofs & 127); + sha1write(f, header + pos, sizeof(header) - pos); + hdrlen += sizeof(header) - pos; + } else if (obj_type == OBJ_REF_DELTA) { + sha1write(f, entry->delta->sha1, 20); + hdrlen += 20; + } - if (revalidate_pack_entry(entry, buf, datalen)) + use_packed_git(p); + buf = (char *) p->pack_base + + entry->in_pack_offset + + entry->in_pack_header_size; + datalen = find_packed_object_size(p, entry->in_pack_offset) + - entry->in_pack_header_size; + if (!pack_to_stdout && check_inflate(buf, datalen, entry->size)) die("corrupt delta in pack %s", sha1_to_hex(entry->sha1)); sha1write(f, buf, datalen); unuse_packed_git(p); - hdrlen = 0; /* not really */ - if (obj_type == OBJ_DELTA) - reused_delta++; reused++; } - if (obj_type == OBJ_DELTA) + if (entry->delta) written_delta++; written++; return hdrlen + datalen; @@ -423,17 +456,16 @@ static unsigned long write_one(struct sha1file *f, struct object_entry *e, unsigned long offset) { - if (e->offset) + if (e->offset || e->preferred_base) /* offset starts from header size and cannot be zero * if it is written already. */ return offset; - e->offset = offset; - offset += write_object(f, e); - /* if we are deltified, write out its base object. */ + /* if we are deltified, write out its base object first. */ if (e->delta) offset = write_one(f, e->delta, offset); - return offset; + e->offset = offset; + return offset + write_object(f, e); } static void write_pack_file(void) @@ -443,15 +475,15 @@ static void write_pack_file(void) unsigned long offset; struct pack_header hdr; unsigned last_percent = 999; - int do_progress = 0; + int do_progress = progress; - if (!base_name) + if (!base_name) { f = sha1fd(1, "<stdout>"); - else { + do_progress >>= 1; + } + else f = sha1create("%s-%s.%s", base_name, sha1_to_hex(object_list_sha1), "pack"); - do_progress = progress; - } if (do_progress) fprintf(stderr, "Writing %d objects.\n", nr_result); @@ -899,26 +931,64 @@ static void check_object(struct object_entry *entry) char type[20]; if (entry->in_pack && !entry->preferred_base) { - unsigned char base[20]; - unsigned long size; - struct object_entry *base_entry; + struct packed_git *p = entry->in_pack; + unsigned long left = p->pack_size - entry->in_pack_offset; + unsigned long size, used; + unsigned char *buf; + struct object_entry *base_entry = NULL; + + use_packed_git(p); + buf = p->pack_base; + buf += entry->in_pack_offset; /* We want in_pack_type even if we do not reuse delta. * There is no point not reusing non-delta representations. */ - check_reuse_pack_delta(entry->in_pack, - entry->in_pack_offset, - base, &size, - &entry->in_pack_type); + used = unpack_object_header_gently(buf, left, + &entry->in_pack_type, &size); + if (!used || left - used <= 20) + die("corrupt pack for %s", sha1_to_hex(entry->sha1)); /* Check if it is delta, and the base is also an object * we are going to pack. If so we will reuse the existing * delta. */ - if (!no_reuse_delta && - entry->in_pack_type == OBJ_DELTA && - (base_entry = locate_object_entry(base)) && - (!base_entry->preferred_base)) { + if (!no_reuse_delta) { + unsigned char c, *base_name; + unsigned long ofs; + /* there is at least 20 bytes left in the pack */ + switch (entry->in_pack_type) { + case OBJ_REF_DELTA: + base_name = buf + used; + used += 20; + break; + case OBJ_OFS_DELTA: + c = buf[used++]; + ofs = c & 127; + while (c & 128) { + ofs += 1; + if (!ofs || ofs & ~(~0UL >> 7)) + die("delta base offset overflow in pack for %s", + sha1_to_hex(entry->sha1)); + c = buf[used++]; + ofs = (ofs << 7) + (c & 127); + } + if (ofs >= entry->in_pack_offset) + die("delta base offset out of bound for %s", + sha1_to_hex(entry->sha1)); + ofs = entry->in_pack_offset - ofs; + base_name = find_packed_object_name(p, ofs); + break; + default: + base_name = NULL; + } + if (base_name) + base_entry = locate_object_entry(base_name); + } + unuse_packed_git(p); + entry->in_pack_header_size = used; + + if (base_entry) { /* Depth value does not matter - find_deltas() * will never consider reused delta as the @@ -927,9 +997,9 @@ static void check_object(struct object_entry *entry) */ /* uncompressed size of the delta data */ - entry->size = entry->delta_size = size; + entry->size = size; entry->delta = base_entry; - entry->type = OBJ_DELTA; + entry->type = entry->in_pack_type; entry->delta_sibling = base_entry->delta_child; base_entry->delta_child = entry; @@ -1454,6 +1524,10 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) progress = 1; continue; } + if (!strcmp("--all-progress", arg)) { + progress = 2; + continue; + } if (!strcmp("--incremental", arg)) { incremental = 1; continue; @@ -1484,6 +1558,10 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) no_reuse_delta = 1; continue; } + if (!strcmp("--delta-base-offset", arg)) { + allow_ofs_delta = 1; + continue; + } if (!strcmp("--stdout", arg)) { pack_to_stdout = 1; continue; @@ -1567,7 +1645,7 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) else { if (nr_result) prepare_pack(window, depth); - if (progress && pack_to_stdout) { + if (progress == 1 && pack_to_stdout) { /* the other end usually displays progress itself */ struct itimerval v = {{0,},}; setitimer(ITIMER_REAL, &v, NULL); diff --git a/builtin-pack-refs.c b/builtin-pack-refs.c new file mode 100644 index 0000000000..042d2718f9 --- /dev/null +++ b/builtin-pack-refs.c @@ -0,0 +1,107 @@ +#include "cache.h" +#include "refs.h" + +static const char builtin_pack_refs_usage[] = +"git-pack-refs [--all] [--prune]"; + +struct ref_to_prune { + struct ref_to_prune *next; + unsigned char sha1[20]; + char name[FLEX_ARRAY]; +}; + +struct pack_refs_cb_data { + int prune; + int all; + struct ref_to_prune *ref_to_prune; + FILE *refs_file; +}; + +static int do_not_prune(int flags) +{ + /* If it is already packed or if it is a symref, + * do not prune it. + */ + return (flags & (REF_ISSYMREF|REF_ISPACKED)); +} + +static int handle_one_ref(const char *path, const unsigned char *sha1, + int flags, void *cb_data) +{ + struct pack_refs_cb_data *cb = cb_data; + + if (!cb->all && strncmp(path, "refs/tags/", 10)) + return 0; + /* Do not pack the symbolic refs */ + if (!(flags & REF_ISSYMREF)) + fprintf(cb->refs_file, "%s %s\n", sha1_to_hex(sha1), path); + if (cb->prune && !do_not_prune(flags)) { + int namelen = strlen(path) + 1; + struct ref_to_prune *n = xcalloc(1, sizeof(*n) + namelen); + hashcpy(n->sha1, sha1); + strcpy(n->name, path); + n->next = cb->ref_to_prune; + cb->ref_to_prune = n; + } + return 0; +} + +/* make sure nobody touched the ref, and unlink */ +static void prune_ref(struct ref_to_prune *r) +{ + struct ref_lock *lock = lock_ref_sha1(r->name + 5, r->sha1); + + if (lock) { + unlink(git_path("%s", r->name)); + unlock_ref(lock); + } +} + +static void prune_refs(struct ref_to_prune *r) +{ + while (r) { + prune_ref(r); + r = r->next; + } +} + +static struct lock_file packed; + +int cmd_pack_refs(int argc, const char **argv, const char *prefix) +{ + int fd, i; + struct pack_refs_cb_data cbdata; + + memset(&cbdata, 0, sizeof(cbdata)); + + for (i = 1; i < argc; i++) { + const char *arg = argv[i]; + if (!strcmp(arg, "--prune")) { + cbdata.prune = 1; + continue; + } + if (!strcmp(arg, "--all")) { + cbdata.all = 1; + continue; + } + /* perhaps other parameters later... */ + break; + } + if (i != argc) + usage(builtin_pack_refs_usage); + + fd = hold_lock_file_for_update(&packed, git_path("packed-refs"), 1); + cbdata.refs_file = fdopen(fd, "w"); + if (!cbdata.refs_file) + die("unable to create ref-pack file structure (%s)", + strerror(errno)); + for_each_ref(handle_one_ref, &cbdata); + fflush(cbdata.refs_file); + fsync(fd); + fclose(cbdata.refs_file); + if (commit_lock_file(&packed) < 0) + die("unable to overwrite old ref-pack file (%s)", strerror(errno)); + if (cbdata.prune) + prune_refs(cbdata.ref_to_prune); + return 0; +} diff --git a/builtin-prune.c b/builtin-prune.c index 7290e6d9aa..d853902c51 100644 --- a/builtin-prune.c +++ b/builtin-prune.c @@ -174,7 +174,7 @@ static void walk_commit_list(struct rev_info *revs) } } -static int add_one_ref(const char *path, const unsigned char *sha1) +static int add_one_ref(const char *path, const unsigned char *sha1, int flag, void *cb_data) { struct object *object = parse_object(sha1); if (!object) @@ -240,7 +240,7 @@ int cmd_prune(int argc, const char **argv, const char *prefix) revs.tree_objects = 1; /* Add all external refs */ - for_each_ref(add_one_ref); + for_each_ref(add_one_ref, NULL); /* Add all refs from the index file */ add_cache_refs(); diff --git a/builtin-push.c b/builtin-push.c index f5150ed82d..d23974e708 100644 --- a/builtin-push.c +++ b/builtin-push.c @@ -10,7 +10,7 @@ static const char push_usage[] = "git-push [--all] [--tags] [-f | --force] <repository> [<refspec>...]"; -static int all, tags, force, thin = 1; +static int all, tags, force, thin = 1, verbose; static const char *execute; #define BUF_SIZE (2084) @@ -27,7 +27,7 @@ static void add_refspec(const char *ref) refspec_nr = nr; } -static int expand_one_ref(const char *ref, const unsigned char *sha1) +static int expand_one_ref(const char *ref, const unsigned char *sha1, int flag, void *cb_data) { /* Ignore the "refs/" at the beginning of the refname */ ref += 5; @@ -51,7 +51,7 @@ static void expand_refspecs(void) } if (!tags) return; - for_each_ref(expand_one_ref); + for_each_ref(expand_one_ref, NULL); } static void set_refspecs(const char **refs, int nr) @@ -248,6 +248,8 @@ static int do_push(const char *repo) while (dest_refspec_nr--) argv[dest_argc++] = *dest_refspec++; argv[dest_argc] = NULL; + if (verbose) + fprintf(stderr, "Pushing to %s\n", dest); err = run_command_v(argc, argv); if (!err) continue; @@ -281,6 +283,14 @@ int cmd_push(int argc, const char **argv, const char *prefix) i++; break; } + if (!strcmp(arg, "-v")) { + verbose=1; + continue; + } + if (!strncmp(arg, "--repo=", 7)) { + repo = arg+7; + continue; + } if (!strcmp(arg, "--all")) { all = 1; continue; diff --git a/builtin-repo-config.c b/builtin-repo-config.c index f60cee1dc5..7b6e5725ae 100644 --- a/builtin-repo-config.c +++ b/builtin-repo-config.c @@ -3,7 +3,7 @@ #include <regex.h> 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"; +"git-repo-config [ --global ] [ --bool | --int ] [--get | --get-all | --get-regexp | --replace-all | --unset | --unset-all] name [value [value_regex]] | --list"; static char *key; static regex_t *key_regexp; @@ -139,7 +139,16 @@ int cmd_repo_config(int argc, const char **argv, const char *prefix) type = T_BOOL; else if (!strcmp(argv[1], "--list") || !strcmp(argv[1], "-l")) return git_config(show_all_config); - else + else if (!strcmp(argv[1], "--global")) { + char *home = getenv("HOME"); + if (home) { + char *user_config = xstrdup(mkpath("%s/.gitconfig", home)); + setenv("GIT_CONFIG", user_config, 1); + free(user_config); + } else { + die("$HOME not set"); + } + } else break; argc--; argv++; diff --git a/builtin-rev-parse.c b/builtin-rev-parse.c index fd3ccc8546..3b716fba13 100644 --- a/builtin-rev-parse.c +++ b/builtin-rev-parse.c @@ -137,7 +137,7 @@ static void show_default(void) } } -static int show_reference(const char *refname, const unsigned char *sha1) +static int show_reference(const char *refname, const unsigned char *sha1, int flag, void *cb_data) { show_rev(NORMAL, sha1, refname); return 0; @@ -299,19 +299,19 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) continue; } if (!strcmp(arg, "--all")) { - for_each_ref(show_reference); + for_each_ref(show_reference, NULL); continue; } if (!strcmp(arg, "--branches")) { - for_each_branch_ref(show_reference); + for_each_branch_ref(show_reference, NULL); continue; } if (!strcmp(arg, "--tags")) { - for_each_tag_ref(show_reference); + for_each_tag_ref(show_reference, NULL); continue; } if (!strcmp(arg, "--remotes")) { - for_each_remote_ref(show_reference); + for_each_remote_ref(show_reference, NULL); continue; } if (!strcmp(arg, "--show-prefix")) { diff --git a/builtin-show-branch.c b/builtin-show-branch.c index 578c9fafd0..fb1a4000d9 100644 --- a/builtin-show-branch.c +++ b/builtin-show-branch.c @@ -346,7 +346,7 @@ static void sort_ref_range(int bottom, int top) compare_ref_name); } -static int append_ref(const char *refname, const unsigned char *sha1) +static int append_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data) { struct commit *commit = lookup_commit_reference_gently(sha1, 1); int i; @@ -369,7 +369,7 @@ static int append_ref(const char *refname, const unsigned char *sha1) return 0; } -static int append_head_ref(const char *refname, const unsigned char *sha1) +static int append_head_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data) { unsigned char tmp[20]; int ofs = 11; @@ -380,14 +380,14 @@ static int append_head_ref(const char *refname, const unsigned char *sha1) */ if (get_sha1(refname + ofs, tmp) || hashcmp(tmp, sha1)) ofs = 5; - return append_ref(refname + ofs, sha1); + return append_ref(refname + ofs, sha1, flag, cb_data); } -static int append_tag_ref(const char *refname, const unsigned char *sha1) +static int append_tag_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data) { if (strncmp(refname, "refs/tags/", 10)) return 0; - return append_ref(refname + 5, sha1); + return append_ref(refname + 5, sha1, flag, cb_data); } static const char *match_ref_pattern = NULL; @@ -401,7 +401,7 @@ static int count_slash(const char *s) return cnt; } -static int append_matching_ref(const char *refname, const unsigned char *sha1) +static int append_matching_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data) { /* we want to allow pattern hold/<asterisk> to show all * branches under refs/heads/hold/, and v0.99.9? to show @@ -417,41 +417,39 @@ static int append_matching_ref(const char *refname, const unsigned char *sha1) if (fnmatch(match_ref_pattern, tail, 0)) return 0; if (!strncmp("refs/heads/", refname, 11)) - return append_head_ref(refname, sha1); + return append_head_ref(refname, sha1, flag, cb_data); if (!strncmp("refs/tags/", refname, 10)) - return append_tag_ref(refname, sha1); - return append_ref(refname, sha1); + return append_tag_ref(refname, sha1, flag, cb_data); + return append_ref(refname, sha1, flag, cb_data); } static void snarf_refs(int head, int tag) { if (head) { int orig_cnt = ref_name_cnt; - for_each_ref(append_head_ref); + for_each_ref(append_head_ref, NULL); sort_ref_range(orig_cnt, ref_name_cnt); } if (tag) { int orig_cnt = ref_name_cnt; - for_each_ref(append_tag_ref); + for_each_ref(append_tag_ref, NULL); sort_ref_range(orig_cnt, ref_name_cnt); } } -static int rev_is_head(char *head_path, int headlen, char *name, +static int rev_is_head(char *head, int headlen, char *name, unsigned char *head_sha1, unsigned char *sha1) { - int namelen; - if ((!head_path[0]) || + if ((!head[0]) || (head_sha1 && sha1 && hashcmp(head_sha1, sha1))) return 0; - namelen = strlen(name); - if ((headlen < namelen) || - memcmp(head_path + headlen - namelen, name, namelen)) - return 0; - if (headlen == namelen || - head_path[headlen - namelen - 1] == '/') - return 1; - return 0; + if (!strncmp(head, "refs/heads/", 11)) + head += 11; + if (!strncmp(name, "refs/heads/", 11)) + name += 11; + else if (!strncmp(name, "heads/", 6)) + name += 6; + return !strcmp(head, name); } static int show_merge_base(struct commit_list *seen, int num_rev) @@ -495,7 +493,7 @@ static void append_one_rev(const char *av) { unsigned char revkey[20]; if (!get_sha1(av, revkey)) { - append_ref(av, revkey); + append_ref(av, revkey, 0, NULL); return; } if (strchr(av, '*') || strchr(av, '?') || strchr(av, '[')) { @@ -503,7 +501,7 @@ static void append_one_rev(const char *av) int saved_matches = ref_name_cnt; match_ref_pattern = av; match_ref_slash = count_slash(av); - for_each_ref(append_matching_ref); + for_each_ref(append_matching_ref, NULL); if (saved_matches == ref_name_cnt && ref_name_cnt < MAX_REVS) error("no matching refs with %s", av); @@ -559,9 +557,9 @@ int cmd_show_branch(int ac, const char **av, const char *prefix) int all_heads = 0, all_tags = 0; int all_mask, all_revs; int lifo = 1; - char head_path[128]; - const char *head_path_p; - int head_path_len; + char head[128]; + const char *head_p; + int head_len; unsigned char head_sha1[20]; int merge_base = 0; int independent = 0; @@ -638,31 +636,31 @@ int cmd_show_branch(int ac, const char **av, const char *prefix) ac--; av++; } - head_path_p = resolve_ref(git_path("HEAD"), head_sha1, 1); - if (head_path_p) { - head_path_len = strlen(head_path_p); - memcpy(head_path, head_path_p, head_path_len + 1); + head_p = resolve_ref("HEAD", head_sha1, 1, NULL); + if (head_p) { + head_len = strlen(head_p); + memcpy(head, head_p, head_len + 1); } else { - head_path_len = 0; - head_path[0] = 0; + head_len = 0; + head[0] = 0; } - if (with_current_branch && head_path_p) { + if (with_current_branch && head_p) { int has_head = 0; for (i = 0; !has_head && i < ref_name_cnt; i++) { /* We are only interested in adding the branch * HEAD points at. */ - if (rev_is_head(head_path, - head_path_len, + if (rev_is_head(head, + head_len, ref_name[i], head_sha1, NULL)) has_head++; } if (!has_head) { - int pfxlen = strlen(git_path("refs/heads/")); - append_one_rev(head_path + pfxlen); + int pfxlen = strlen("refs/heads/"); + append_one_rev(head + pfxlen); } } @@ -713,8 +711,8 @@ int cmd_show_branch(int ac, const char **av, const char *prefix) if (1 < num_rev || extra < 0) { for (i = 0; i < num_rev; i++) { int j; - int is_head = rev_is_head(head_path, - head_path_len, + int is_head = rev_is_head(head, + head_len, ref_name[i], head_sha1, rev[i]->object.sha1); diff --git a/builtin-show-ref.c b/builtin-show-ref.c new file mode 100644 index 0000000000..06ec400d7f --- /dev/null +++ b/builtin-show-ref.c @@ -0,0 +1,147 @@ +#include "cache.h" +#include "refs.h" +#include "object.h" +#include "tag.h" + +static const char show_ref_usage[] = "git show-ref [-q|--quiet] [--verify] [-h|--head] [-d|--dereference] [-s|--hash[=<length>]] [--abbrev[=<length>]] [--tags] [--heads] [--] [pattern*]"; + +static int deref_tags = 0, show_head = 0, tags_only = 0, heads_only = 0, + found_match = 0, verify = 0, quiet = 0, hash_only = 0, abbrev = 0; +static const char **pattern; + +static int show_ref(const char *refname, const unsigned char *sha1, int flag, void *cbdata) +{ + struct object *obj; + const char *hex; + + if (tags_only || heads_only) { + int match; + + match = heads_only && !strncmp(refname, "refs/heads/", 11); + match |= tags_only && !strncmp(refname, "refs/tags/", 10); + if (!match) + return 0; + } + if (pattern) { + int reflen = strlen(refname); + const char **p = pattern, *m; + while ((m = *p++) != NULL) { + int len = strlen(m); + if (len > reflen) + continue; + if (memcmp(m, refname + reflen - len, len)) + continue; + if (len == reflen) + goto match; + /* "--verify" requires an exact match */ + if (verify) + continue; + if (refname[reflen - len - 1] == '/') + goto match; + } + return 0; + } + +match: + found_match++; + obj = parse_object(sha1); + if (!obj) { + if (quiet) + return 0; + die("git-show-ref: bad ref %s (%s)", refname, sha1_to_hex(sha1)); + } + if (quiet) + return 0; + + hex = find_unique_abbrev(sha1, abbrev); + if (hash_only) + printf("%s\n", hex); + else + printf("%s %s\n", hex, refname); + if (deref_tags && obj->type == OBJ_TAG) { + obj = deref_tag(obj, refname, 0); + hex = find_unique_abbrev(obj->sha1, abbrev); + printf("%s %s^{}\n", hex, refname); + } + return 0; +} + +int cmd_show_ref(int argc, const char **argv, const char *prefix) +{ + int i; + + for (i = 1; i < argc; i++) { + const char *arg = argv[i]; + if (*arg != '-') { + pattern = argv + i; + break; + } + if (!strcmp(arg, "--")) { + pattern = argv + i + 1; + if (!*pattern) + pattern = NULL; + break; + } + if (!strcmp(arg, "-q") || !strcmp(arg, "--quiet")) { + quiet = 1; + continue; + } + if (!strcmp(arg, "-h") || !strcmp(arg, "--head")) { + show_head = 1; + continue; + } + if (!strcmp(arg, "-d") || !strcmp(arg, "--dereference")) { + deref_tags = 1; + continue; + } + if (!strcmp(arg, "-s") || !strcmp(arg, "--hash")) { + hash_only = 1; + continue; + } + if (!strncmp(arg, "--hash=", 7) || + (!strncmp(arg, "--abbrev", 8) && + (arg[8] == '=' || arg[8] == '\0'))) { + if (arg[3] != 'h' && !arg[8]) + /* --abbrev only */ + abbrev = DEFAULT_ABBREV; + else { + /* --hash= or --abbrev= */ + char *end; + if (arg[3] == 'h') { + hash_only = 1; + arg += 7; + } + else + arg += 9; + abbrev = strtoul(arg, &end, 10); + if (*end || abbrev > 40) + usage(show_ref_usage); + if (abbrev < MINIMUM_ABBREV) + abbrev = MINIMUM_ABBREV; + } + continue; + } + if (!strcmp(arg, "--verify")) { + verify = 1; + continue; + } + if (!strcmp(arg, "--tags")) { + tags_only = 1; + continue; + } + if (!strcmp(arg, "--heads")) { + heads_only = 1; + continue; + } + usage(show_ref_usage); + } + if (show_head) + head_ref(show_ref, NULL); + for_each_ref(show_ref, NULL); + if (!found_match) { + if (verify && !quiet) + die("No match"); + return 1; + } + return 0; +} diff --git a/builtin-symbolic-ref.c b/builtin-symbolic-ref.c index 1d3a5e229a..d8be0527f4 100644 --- a/builtin-symbolic-ref.c +++ b/builtin-symbolic-ref.c @@ -1,5 +1,6 @@ #include "builtin.h" #include "cache.h" +#include "refs.h" static const char git_symbolic_ref_usage[] = "git-symbolic-ref name [ref]"; @@ -7,15 +8,14 @@ static const char git_symbolic_ref_usage[] = static void check_symref(const char *HEAD) { unsigned char sha1[20]; - 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 */ - int pfxlen = strlen(git_HEAD) - strlen(HEAD); - puts(git_refs_heads_master + pfxlen); - } - else + int flag; + const char *refs_heads_master = resolve_ref(HEAD, sha1, 0, &flag); + + if (!refs_heads_master) die("No such ref: %s", HEAD); + else if (!(flag & REF_ISSYMREF)) + die("ref %s is not a symbolic ref", HEAD); + puts(refs_heads_master); } int cmd_symbolic_ref(int argc, const char **argv, const char *prefix) @@ -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(xstrdup(git_path("%s", argv[1])), argv[2]); + create_symref(argv[1], argv[2]); break; default: usage(git_symbolic_ref_usage); diff --git a/builtin-unpack-objects.c b/builtin-unpack-objects.c index 4f96bcae32..74a90c1129 100644 --- a/builtin-unpack-objects.c +++ b/builtin-unpack-objects.c @@ -15,14 +15,14 @@ static const char unpack_usage[] = "git-unpack-objects [-n] [-q] [-r] < pack-fil /* We always read in 4kB chunks. */ static unsigned char buffer[4096]; -static unsigned long offset, len; +static unsigned long offset, len, consumed_bytes; static SHA_CTX ctx; /* * Make sure at least "min" bytes are available in the buffer, and * return the pointer to the buffer. */ -static void * fill(int min) +static void *fill(int min) { if (min <= len) return buffer + offset; @@ -30,7 +30,7 @@ static void * fill(int min) die("cannot fill %d bytes", min); if (offset) { SHA1_Update(&ctx, buffer, offset); - memcpy(buffer, buffer + offset, len); + memmove(buffer, buffer + offset, len); offset = 0; } do { @@ -51,6 +51,7 @@ static void use(int bytes) die("used more bytes than were available"); len -= bytes; offset += bytes; + consumed_bytes += bytes; } static void *get_data(unsigned long size) @@ -89,35 +90,49 @@ static void *get_data(unsigned long size) struct delta_info { unsigned char base_sha1[20]; + unsigned long base_offset; unsigned long size; void *delta; + unsigned nr; struct delta_info *next; }; static struct delta_info *delta_list; -static void add_delta_to_list(unsigned char *base_sha1, void *delta, unsigned long size) +static void add_delta_to_list(unsigned nr, unsigned const char *base_sha1, + unsigned long base_offset, + void *delta, unsigned long size) { struct delta_info *info = xmalloc(sizeof(*info)); hashcpy(info->base_sha1, base_sha1); + info->base_offset = base_offset; info->size = size; info->delta = delta; + info->nr = nr; info->next = delta_list; delta_list = info; } -static void added_object(unsigned char *sha1, const char *type, void *data, unsigned long size); +struct obj_info { + unsigned long offset; + unsigned char sha1[20]; +}; + +static struct obj_info *obj_list; -static void write_object(void *buf, unsigned long size, const char *type) +static void added_object(unsigned nr, const char *type, void *data, + unsigned long size); + +static void write_object(unsigned nr, void *buf, unsigned long size, + const char *type) { - unsigned char sha1[20]; - if (write_sha1_file(buf, size, type, sha1) < 0) + if (write_sha1_file(buf, size, type, obj_list[nr].sha1) < 0) die("failed to write object"); - added_object(sha1, type, buf, size); + added_object(nr, type, buf, size); } -static void resolve_delta(const char *type, +static void resolve_delta(unsigned nr, const char *type, void *base, unsigned long base_size, void *delta, unsigned long delta_size) { @@ -130,20 +145,23 @@ static void resolve_delta(const char *type, if (!result) die("failed to apply delta"); free(delta); - write_object(result, result_size, type); + write_object(nr, result, result_size, type); free(result); } -static void added_object(unsigned char *sha1, const char *type, void *data, unsigned long size) +static void added_object(unsigned nr, const char *type, void *data, + unsigned long size) { struct delta_info **p = &delta_list; struct delta_info *info; while ((info = *p) != NULL) { - if (!hashcmp(info->base_sha1, sha1)) { + if (!hashcmp(info->base_sha1, obj_list[nr].sha1) || + info->base_offset == obj_list[nr].offset) { *p = info->next; p = &delta_list; - resolve_delta(type, data, size, info->delta, info->size); + resolve_delta(info->nr, type, data, size, + info->delta, info->size); free(info); continue; } @@ -151,7 +169,8 @@ static void added_object(unsigned char *sha1, const char *type, void *data, unsi } } -static void unpack_non_delta_entry(enum object_type kind, unsigned long size) +static void unpack_non_delta_entry(enum object_type kind, unsigned long size, + unsigned nr) { void *buf = get_data(size); const char *type; @@ -164,30 +183,80 @@ static void unpack_non_delta_entry(enum object_type kind, unsigned long size) default: die("bad type %d", kind); } if (!dry_run && buf) - write_object(buf, size, type); + write_object(nr, buf, size, type); free(buf); } -static void unpack_delta_entry(unsigned long delta_size) +static void unpack_delta_entry(enum object_type kind, unsigned long delta_size, + unsigned nr) { void *delta_data, *base; unsigned long base_size; char type[20]; unsigned char base_sha1[20]; - hashcpy(base_sha1, fill(20)); - use(20); + if (kind == OBJ_REF_DELTA) { + hashcpy(base_sha1, fill(20)); + use(20); + delta_data = get_data(delta_size); + if (dry_run || !delta_data) { + free(delta_data); + return; + } + if (!has_sha1_file(base_sha1)) { + hashcpy(obj_list[nr].sha1, null_sha1); + add_delta_to_list(nr, base_sha1, 0, delta_data, delta_size); + return; + } + } else { + unsigned base_found = 0; + unsigned char *pack, c; + unsigned long base_offset; + unsigned lo, mid, hi; - delta_data = get_data(delta_size); - if (dry_run || !delta_data) { - free(delta_data); - return; - } + pack = fill(1); + c = *pack; + use(1); + base_offset = c & 127; + while (c & 128) { + base_offset += 1; + if (!base_offset || base_offset & ~(~0UL >> 7)) + die("offset value overflow for delta base object"); + pack = fill(1); + c = *pack; + use(1); + base_offset = (base_offset << 7) + (c & 127); + } + base_offset = obj_list[nr].offset - base_offset; - if (!has_sha1_file(base_sha1)) { - add_delta_to_list(base_sha1, delta_data, delta_size); - return; + delta_data = get_data(delta_size); + if (dry_run || !delta_data) { + free(delta_data); + return; + } + lo = 0; + hi = nr; + while (lo < hi) { + mid = (lo + hi)/2; + if (base_offset < obj_list[mid].offset) { + hi = mid; + } else if (base_offset > obj_list[mid].offset) { + lo = mid + 1; + } else { + hashcpy(base_sha1, obj_list[mid].sha1); + base_found = !is_null_sha1(base_sha1); + break; + } + } + if (!base_found) { + /* The delta base object is itself a delta that + has not been resolved yet. */ + hashcpy(obj_list[nr].sha1, null_sha1); + add_delta_to_list(nr, null_sha1, base_offset, delta_data, delta_size); + return; + } } + base = read_sha1_file(base_sha1, type, &base_size); if (!base) { error("failed to read delta-pack base object %s", @@ -197,7 +266,7 @@ static void unpack_delta_entry(unsigned long delta_size) has_errors = 1; return; } - resolve_delta(type, base, base_size, delta_data, delta_size); + resolve_delta(nr, type, base, base_size, delta_data, delta_size); free(base); } @@ -208,6 +277,8 @@ static void unpack_one(unsigned nr, unsigned total) unsigned long size; enum object_type type; + obj_list[nr].offset = consumed_bytes; + pack = fill(1); c = *pack; use(1); @@ -216,7 +287,7 @@ static void unpack_one(unsigned nr, unsigned total) shift = 4; while (c & 0x80) { pack = fill(1); - c = *pack++; + c = *pack; use(1); size += (c & 0x7f) << shift; shift += 7; @@ -225,13 +296,14 @@ static void unpack_one(unsigned nr, unsigned total) static unsigned long last_sec; static unsigned last_percent; struct timeval now; - unsigned percentage = (nr * 100) / total; + unsigned percentage = ((nr+1) * 100) / total; gettimeofday(&now, NULL); if (percentage != last_percent || now.tv_sec != last_sec) { last_sec = now.tv_sec; last_percent = percentage; - fprintf(stderr, "%4u%% (%u/%u) done\r", percentage, nr, total); + fprintf(stderr, "%4u%% (%u/%u) done\r", + percentage, (nr+1), total); } } switch (type) { @@ -239,10 +311,11 @@ static void unpack_one(unsigned nr, unsigned total) case OBJ_TREE: case OBJ_BLOB: case OBJ_TAG: - unpack_non_delta_entry(type, size); + unpack_non_delta_entry(type, size, nr); return; - case OBJ_DELTA: - unpack_delta_entry(size); + case OBJ_REF_DELTA: + case OBJ_OFS_DELTA: + unpack_delta_entry(type, size, nr); return; default: error("bad object type %d", type); @@ -265,9 +338,10 @@ static void unpack_all(void) die("unknown pack file version %d", ntohl(hdr->hdr_version)); fprintf(stderr, "Unpacking %d objects\n", nr_objects); + obj_list = xmalloc(nr_objects * sizeof(*obj_list)); use(sizeof(struct pack_header)); for (i = 0; i < nr_objects; i++) - unpack_one(i+1, nr_objects); + unpack_one(i, nr_objects); if (delta_list) die("unresolved deltas left after unpacking"); } diff --git a/builtin-update-index.c b/builtin-update-index.c index a3c0a455ae..7f9c638466 100644 --- a/builtin-update-index.c +++ b/builtin-update-index.c @@ -406,9 +406,9 @@ static int unresolve_one(const char *path) static void read_head_pointers(void) { - if (read_ref(git_path("HEAD"), head_sha1)) + if (read_ref("HEAD", head_sha1)) die("No HEAD -- no initial commit yet?\n"); - if (read_ref(git_path("MERGE_HEAD"), merge_head_sha1)) { + if (read_ref("MERGE_HEAD", merge_head_sha1)) { fprintf(stderr, "Not in the middle of a merge.\n"); exit(0); } @@ -445,7 +445,7 @@ static int do_reupdate(int ac, const char **av, int has_head = 1; const char **pathspec = get_pathspec(prefix, av + 1); - if (read_ref(git_path("HEAD"), head_sha1)) + if (read_ref("HEAD", head_sha1)) /* If there is no HEAD, that means it is an initial * commit. Update everything in the index. */ diff --git a/builtin-update-ref.c b/builtin-update-ref.c index 90a3da53ad..b34e5987dd 100644 --- a/builtin-update-ref.c +++ b/builtin-update-ref.c @@ -3,15 +3,16 @@ #include "builtin.h" static const char git_update_ref_usage[] = -"git-update-ref <refname> <value> [<oldval>] [-m <reason>]"; +"git-update-ref [-m <reason>] (-d <refname> <value> | <refname> <value> [<oldval>])"; int cmd_update_ref(int argc, const char **argv, const char *prefix) { const char *refname=NULL, *value=NULL, *oldval=NULL, *msg=NULL; struct ref_lock *lock; unsigned char sha1[20], oldsha1[20]; - int i; + int i, delete; + delete = 0; setup_ident(); git_config(git_default_config); @@ -26,6 +27,10 @@ int cmd_update_ref(int argc, const char **argv, const char *prefix) die("Refusing to perform update with \\n in message."); continue; } + if (!strcmp("-d", argv[i])) { + delete = 1; + continue; + } if (!refname) { refname = argv[i]; continue; @@ -44,11 +49,18 @@ int cmd_update_ref(int argc, const char **argv, const char *prefix) if (get_sha1(value, sha1)) die("%s: not a valid SHA1", value); + + if (delete) { + if (oldval) + usage(git_update_ref_usage); + return delete_ref(refname, sha1); + } + hashclr(oldsha1); - if (oldval && get_sha1(oldval, oldsha1)) + if (oldval && *oldval && get_sha1(oldval, oldsha1)) die("%s: not a valid old SHA1", oldval); - lock = lock_any_ref_for_update(refname, oldval ? oldsha1 : NULL, 0); + lock = lock_any_ref_for_update(refname, oldval ? oldsha1 : NULL); if (!lock) return 1; if (write_ref_sha1(lock, sha1, msg) < 0) @@ -14,11 +14,14 @@ extern int write_tree(unsigned char *sha1, int missing_ok, const char *prefix); extern void prune_packed_objects(int); extern int cmd_add(int argc, const char **argv, const char *prefix); +extern int cmd_annotate(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_branch(int argc, const char **argv, const char *prefix); extern int cmd_cat_file(int argc, const char **argv, const char *prefix); extern int cmd_checkout_index(int argc, const char **argv, const char *prefix); extern int cmd_check_ref_format(int argc, const char **argv, const char *prefix); +extern int cmd_cherry(int argc, const char **argv, const char *prefix); extern int cmd_commit_tree(int argc, const char **argv, const char *prefix); extern int cmd_count_objects(int argc, const char **argv, const char *prefix); extern int cmd_diff_files(int argc, const char **argv, const char *prefix); @@ -27,6 +30,7 @@ extern int cmd_diff(int argc, const char **argv, const char *prefix); extern int cmd_diff_stages(int argc, const char **argv, const char *prefix); extern int cmd_diff_tree(int argc, const char **argv, const char *prefix); extern int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix); +extern int cmd_for_each_ref(int argc, const char **argv, const char *prefix); extern int cmd_format_patch(int argc, const char **argv, const char *prefix); extern int cmd_get_tar_commit_id(int argc, const char **argv, const char *prefix); extern int cmd_grep(int argc, const char **argv, const char *prefix); @@ -49,8 +53,8 @@ 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_show_branch(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); @@ -63,5 +67,7 @@ extern int cmd_version(int argc, const char **argv, const char *prefix); extern int cmd_whatchanged(int argc, const char **argv, const char *prefix); extern int cmd_write_tree(int argc, const char **argv, const char *prefix); extern int cmd_verify_pack(int argc, const char **argv, const char *prefix); +extern int cmd_show_ref(int argc, const char **argv, const char *prefix); +extern int cmd_pack_refs(int argc, const char **argv, const char *prefix); #endif @@ -179,6 +179,7 @@ struct lock_file { extern int hold_lock_file_for_update(struct lock_file *, const char *path, int); extern int commit_lock_file(struct lock_file *); extern void rollback_lock_file(struct lock_file *); +extern int delete_ref(const char *, unsigned char *sha1); /* Environment bits from configuration mechanism */ extern int use_legacy_headers; @@ -188,7 +189,6 @@ extern int prefer_symlink_refs; extern int log_all_ref_updates; extern int warn_ambiguous_refs; extern int shared_repository; -extern int deny_non_fast_forwards; extern const char *apply_default_whitespace; extern int zlib_compression_level; @@ -269,8 +269,9 @@ enum object_type { OBJ_TREE = 2, OBJ_BLOB = 3, OBJ_TAG = 4, - /* 5/6 for future expansion */ - OBJ_DELTA = 7, + /* 5 for future expansion */ + OBJ_OFS_DELTA = 6, + OBJ_REF_DELTA = 7, OBJ_BAD, }; @@ -288,9 +289,9 @@ extern int get_sha1(const char *str, unsigned char *sha1); extern int get_sha1_hex(const char *hex, unsigned char *sha1); extern char *sha1_to_hex(const unsigned char *sha1); /* static buffer result! */ extern int read_ref(const char *filename, unsigned char *sha1); -extern const char *resolve_ref(const char *path, unsigned char *sha1, int); -extern int create_symref(const char *git_HEAD, const char *refs_heads_master); -extern int validate_symref(const char *git_HEAD); +extern const char *resolve_ref(const char *path, unsigned char *sha1, int, int *); +extern int create_symref(const char *ref, const char *refs_heads_master); +extern int validate_symref(const char *ref); extern int base_name_compare(const char *name1, int len1, int mode1, const char *name2, int len2, int mode2); extern int cache_name_compare(const char *name1, int len1, const char *name2, int len2); diff --git a/check-builtins.sh b/check-builtins.sh new file mode 100755 index 0000000000..d6fe6cf174 --- /dev/null +++ b/check-builtins.sh @@ -0,0 +1,34 @@ +#!/bin/sh + +{ + cat <<\EOF +sayIt: + $(foreach b,$(BUILT_INS),echo XXX $b YYY;) +EOF + cat Makefile +} | +make -f - sayIt 2>/dev/null | +sed -n -e 's/.*XXX \(.*\) YYY.*/\1/p' | +sort | +{ + bad=0 + while read builtin + do + base=`expr "$builtin" : 'git-\(.*\)'` + x=`sed -ne 's/.*{ "'$base'", \(cmd_[^, ]*\).*/'$base' \1/p' git.c` + if test -z "$x" + then + echo "$base is builtin but not listed in git.c command list" + bad=1 + fi + for sfx in sh perl py + do + if test -f "$builtin.$sfx" + then + echo "$base is builtin but $builtin.$sfx still exists" + bad=1 + fi + done + done + exit $bad +} diff --git a/combine-diff.c b/combine-diff.c index 8bf99f215c..29d0c9cf95 100644 --- a/combine-diff.c +++ b/combine-diff.c @@ -898,8 +898,10 @@ void diff_tree_combined(const unsigned char *sha1, /* show stat against the first parent even * when doing combined diff. */ - if (i == 0 && opt->output_format & DIFF_FORMAT_DIFFSTAT) - diffopts.output_format = DIFF_FORMAT_DIFFSTAT; + int stat_opt = (opt->output_format & + (DIFF_FORMAT_NUMSTAT|DIFF_FORMAT_DIFFSTAT)); + if (i == 0 && stat_opt) + diffopts.output_format = stat_opt; else diffopts.output_format = DIFF_FORMAT_NO_OUTPUT; diff_tree_sha1(parent[i], sha1, "", &diffopts); @@ -929,7 +931,8 @@ void diff_tree_combined(const unsigned char *sha1, } needsep = 1; } - else if (opt->output_format & DIFF_FORMAT_DIFFSTAT) + else if (opt->output_format & + (DIFF_FORMAT_NUMSTAT|DIFF_FORMAT_DIFFSTAT)) needsep = 1; if (opt->output_format & DIFF_FORMAT_PATCH) { if (needsep) @@ -103,6 +103,11 @@ static char *parse_value(void) } } +static inline int iskeychar(int c) +{ + return isalnum(c) || c == '-'; +} + static int get_value(config_fn_t fn, char *name, unsigned int len) { int c; @@ -113,7 +118,7 @@ static int get_value(config_fn_t fn, char *name, unsigned int len) c = get_next_char(); if (c == EOF) break; - if (!isalnum(c)) + if (!iskeychar(c)) break; name[len++] = tolower(c); if (len >= MAXNAME) @@ -181,7 +186,7 @@ static int get_base_var(char *name) return baselen; if (isspace(c)) return get_extended_base_var(name, baselen, c); - if (!isalnum(c) && c != '.') + if (!iskeychar(c) && c != '.') return -1; if (baselen > MAXNAME / 2) return -1; @@ -573,7 +578,7 @@ int git_config_set_multivar(const char* key, const char* value, dot = 1; /* Leave the extended basename untouched.. */ if (!dot || i > store.baselen) { - if (!isalnum(c) || (i == store.baselen+1 && !isalpha(c))) { + if (!iskeychar(c) || (i == store.baselen+1 && !isalpha(c))) { fprintf(stderr, "invalid key: %s\n", key); free(store.key); ret = 1; diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index d9cb17d0b2..a43a177160 100755 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -19,15 +19,20 @@ # source ~/.git-completion.sh # +__gitdir () +{ + echo "${__git_dir:-$(git rev-parse --git-dir 2>/dev/null)}" +} + __git_refs () { - local cmd i is_hash=y - if [ -d "$1" ]; then + local cmd i is_hash=y dir="${1:-$(__gitdir)}" + if [ -d "$dir" ]; then cmd=git-peek-remote else cmd=git-ls-remote fi - for i in $($cmd "$1" 2>/dev/null); do + for i in $($cmd "$dir" 2>/dev/null); do case "$is_hash,$i" in y,*) is_hash=n ;; n,*^{}) is_hash=y ;; @@ -40,13 +45,13 @@ __git_refs () __git_refs2 () { - local cmd i is_hash=y - if [ -d "$1" ]; then + local cmd i is_hash=y dir="${1:-$(__gitdir)}" + if [ -d "$dir" ]; then cmd=git-peek-remote else cmd=git-ls-remote fi - for i in $($cmd "$1" 2>/dev/null); do + for i in $($cmd "$dir" 2>/dev/null); do case "$is_hash,$i" in y,*) is_hash=n ;; n,*^{}) is_hash=y ;; @@ -59,25 +64,34 @@ __git_refs2 () __git_remotes () { - local i REVERTGLOB=$(shopt -p nullglob) + local i ngoff IFS=$'\n' d="$(__gitdir)" + shopt -q nullglob || ngoff=1 shopt -s nullglob - for i in .git/remotes/*; do - echo ${i#.git/remotes/} + for i in "$d/remotes"/*; do + echo ${i#$d/remotes/} + done + [ "$ngoff" ] && shopt -u nullglob + for i in $(git --git-dir="$d" repo-config --list); do + case "$i" in + remote.*.url=*) + i="${i#remote.}" + echo "${i/.url=*/}" + ;; + esac done - $REVERTGLOB } __git_complete_file () { - local cur="${COMP_WORDS[COMP_CWORD]}" + local pfx ls ref cur="${COMP_WORDS[COMP_CWORD]}" case "$cur" in ?*:*) - local pfx ls ref="$(echo "$cur" | sed 's,:.*$,,')" - cur="$(echo "$cur" | sed 's,^.*:,,')" + ref="${cur%%:*}" + cur="${cur#*:}" case "$cur" in ?*/*) - pfx="$(echo "$cur" | sed 's,/[^/]*$,,')" - cur="$(echo "$cur" | sed 's,^.*/,,')" + pfx="${cur%/*}" + cur="${cur##*/}" ls="$ref:$pfx" pfx="$pfx/" ;; @@ -86,7 +100,7 @@ __git_complete_file () ;; esac COMPREPLY=($(compgen -P "$pfx" \ - -W "$(git-ls-tree "$ls" \ + -W "$(git --git-dir="$(__gitdir)" ls-tree "$ls" \ | sed '/^100... blob /s,^.* ,, /^040000 tree /{ s,^.* ,, @@ -96,15 +110,40 @@ __git_complete_file () -- "$cur")) ;; *) - COMPREPLY=($(compgen -W "$(__git_refs .)" -- "$cur")) + COMPREPLY=($(compgen -W "$(__git_refs)" -- "$cur")) ;; esac } +__git_aliases () +{ + local i IFS=$'\n' + for i in $(git --git-dir="$(__gitdir)" repo-config --list); do + case "$i" in + alias.*) + i="${i#alias.}" + echo "${i/=*/}" + ;; + esac + done +} + +__git_aliased_command () +{ + local word cmdline=$(git --git-dir="$(__gitdir)" \ + repo-config --get "alias.$1") + for word in $cmdline; do + if [ "${word##-*}" ]; then + echo $word + return + fi + done +} + _git_branch () { local cur="${COMP_WORDS[COMP_CWORD]}" - COMPREPLY=($(compgen -W "-l -f -d -D $(__git_refs .)" -- "$cur")) + COMPREPLY=($(compgen -W "-l -f -d -D $(__git_refs)" -- "$cur")) } _git_cat_file () @@ -126,7 +165,7 @@ _git_cat_file () _git_checkout () { local cur="${COMP_WORDS[COMP_CWORD]}" - COMPREPLY=($(compgen -W "-l -b $(__git_refs .)" -- "$cur")) + COMPREPLY=($(compgen -W "-l -b $(__git_refs)" -- "$cur")) } _git_diff () @@ -137,7 +176,7 @@ _git_diff () _git_diff_tree () { local cur="${COMP_WORDS[COMP_CWORD]}" - COMPREPLY=($(compgen -W "-r -p -M $(__git_refs .)" -- "$cur")) + COMPREPLY=($(compgen -W "-r -p -M $(__git_refs)" -- "$cur")) } _git_fetch () @@ -154,8 +193,8 @@ _git_fetch () *) case "$cur" in *:*) - cur=$(echo "$cur" | sed 's/^.*://') - COMPREPLY=($(compgen -W "$(__git_refs .)" -- "$cur")) + cur="${cur#*:}" + COMPREPLY=($(compgen -W "$(__git_refs)" -- "$cur")) ;; *) local remote @@ -183,15 +222,20 @@ _git_ls_tree () _git_log () { - local cur="${COMP_WORDS[COMP_CWORD]}" + local pfx cur="${COMP_WORDS[COMP_CWORD]}" case "$cur" in + *...*) + pfx="${cur%...*}..." + cur="${cur#*...}" + COMPREPLY=($(compgen -P "$pfx" -W "$(__git_refs)" -- "$cur")) + ;; *..*) - local pfx=$(echo "$cur" | sed 's/\.\..*$/../') - cur=$(echo "$cur" | sed 's/^.*\.\.//') - COMPREPLY=($(compgen -P "$pfx" -W "$(__git_refs .)" -- "$cur")) + pfx="${cur%..*}.." + cur="${cur#*..}" + COMPREPLY=($(compgen -P "$pfx" -W "$(__git_refs)" -- "$cur")) ;; *) - COMPREPLY=($(compgen -W "$(__git_refs .)" -- "$cur")) + COMPREPLY=($(compgen -W "$(__git_refs)" -- "$cur")) ;; esac } @@ -199,7 +243,7 @@ _git_log () _git_merge_base () { local cur="${COMP_WORDS[COMP_CWORD]}" - COMPREPLY=($(compgen -W "$(__git_refs .)" -- "$cur")) + COMPREPLY=($(compgen -W "$(__git_refs)" -- "$cur")) } _git_pull () @@ -243,54 +287,82 @@ _git_push () git-push) remote="${COMP_WORDS[1]}" ;; git) remote="${COMP_WORDS[2]}" ;; esac - cur=$(echo "$cur" | sed 's/^.*://') + cur="${cur#*:}" COMPREPLY=($(compgen -W "$(__git_refs "$remote")" -- "$cur")) ;; *) - COMPREPLY=($(compgen -W "$(__git_refs2 .)" -- "$cur")) + COMPREPLY=($(compgen -W "$(__git_refs2)" -- "$cur")) ;; esac ;; esac } +_git_reset () +{ + local cur="${COMP_WORDS[COMP_CWORD]}" + local opt="--mixed --hard --soft" + COMPREPLY=($(compgen -W "$opt $(__git_refs)" -- "$cur")) +} + _git_show () { local cur="${COMP_WORDS[COMP_CWORD]}" - COMPREPLY=($(compgen -W "$(__git_refs .)" -- "$cur")) + COMPREPLY=($(compgen -W "$(__git_refs)" -- "$cur")) } _git () { - if [ $COMP_CWORD = 1 ]; then + local i c=1 command __git_dir + + while [ $c -lt $COMP_CWORD ]; do + i="${COMP_WORDS[c]}" + case "$i" in + --git-dir=*) __git_dir="${i#--git-dir=}" ;; + --bare) __git_dir="." ;; + --version|--help|-p|--paginate) ;; + *) command="$i"; break ;; + esac + c=$((++c)) + done + + if [ $c -eq $COMP_CWORD -a -z "$command" ]; then COMPREPLY=($(compgen \ - -W "--version $(git help -a|egrep '^ ')" \ + -W "--git-dir= --version \ + $(git help -a|egrep '^ ') \ + $(__git_aliases)" \ -- "${COMP_WORDS[COMP_CWORD]}")) - else - case "${COMP_WORDS[1]}" in - branch) _git_branch ;; - cat-file) _git_cat_file ;; - checkout) _git_checkout ;; - diff) _git_diff ;; - diff-tree) _git_diff_tree ;; - fetch) _git_fetch ;; - log) _git_log ;; - ls-remote) _git_ls_remote ;; - ls-tree) _git_ls_tree ;; - pull) _git_pull ;; - push) _git_push ;; - show) _git_show ;; - show-branch) _git_log ;; - whatchanged) _git_log ;; - *) COMPREPLY=() ;; - esac + return; fi + + local expansion=$(__git_aliased_command "$command") + [ "$expansion" ] && command="$expansion" + + case "$command" in + branch) _git_branch ;; + cat-file) _git_cat_file ;; + checkout) _git_checkout ;; + diff) _git_diff ;; + diff-tree) _git_diff_tree ;; + fetch) _git_fetch ;; + log) _git_log ;; + ls-remote) _git_ls_remote ;; + ls-tree) _git_ls_tree ;; + merge-base) _git_merge_base ;; + pull) _git_pull ;; + push) _git_push ;; + reset) _git_reset ;; + show) _git_show ;; + show-branch) _git_log ;; + whatchanged) _git_log ;; + *) COMPREPLY=() ;; + esac } _gitk () { local cur="${COMP_WORDS[COMP_CWORD]}" - COMPREPLY=($(compgen -W "--all $(__git_refs .)" -- "$cur")) + COMPREPLY=($(compgen -W "--all $(__git_refs)" -- "$cur")) } complete -o default -o nospace -F _git git @@ -307,13 +379,18 @@ complete -o default -o nospace -F _git_ls_tree git-ls-tree complete -o default -F _git_merge_base git-merge-base complete -o default -o nospace -F _git_pull git-pull complete -o default -o nospace -F _git_push git-push +complete -o default -F _git_reset git-reset complete -o default -F _git_show git-show +complete -o default -o nospace -F _git_log git-show-branch complete -o default -o nospace -F _git_log git-whatchanged # The following are necessary only for Cygwin, and only are needed # when the user has tab-completed the executable name and consequently # included the '.exe' suffix. # +if [ Cygwin = "$(uname -o 2>/dev/null)" ]; then +complete -o default -o nospace -F _git git.exe +complete -o default -F _git_branch git-branch.exe complete -o default -o nospace -F _git_cat_file git-cat-file.exe complete -o default -o nospace -F _git_diff git-diff.exe complete -o default -o nospace -F _git_diff_tree git-diff-tree.exe @@ -321,4 +398,6 @@ complete -o default -o nospace -F _git_log git-log.exe complete -o default -o nospace -F _git_ls_tree git-ls-tree.exe complete -o default -F _git_merge_base git-merge-base.exe complete -o default -o nospace -F _git_push git-push.exe +complete -o default -o nospace -F _git_log git-show-branch.exe complete -o default -o nospace -F _git_log git-whatchanged.exe +fi diff --git a/contrib/emacs/git.el b/contrib/emacs/git.el index 5354cd67b3..972c402ea0 100644 --- a/contrib/emacs/git.el +++ b/contrib/emacs/git.el @@ -589,6 +589,7 @@ and returns the process output as a string." (let ((commit (git-commit-tree buffer tree head))) (git-update-ref "HEAD" commit head) (condition-case nil (delete-file ".git/MERGE_HEAD") (error nil)) + (condition-case nil (delete-file ".git/MERGE_MSG") (error nil)) (with-current-buffer buffer (erase-buffer)) (git-set-files-state files 'uptodate) (when (file-directory-p ".git/rr-cache") @@ -670,6 +671,32 @@ and returns the process output as a string." (unless git-status (error "Not in git-status buffer.")) (ewoc-goto-prev git-status n)) +(defun git-next-unmerged-file (&optional n) + "Move the selection down N unmerged files." + (interactive "p") + (unless git-status (error "Not in git-status buffer.")) + (let* ((last (ewoc-locate git-status)) + (node (ewoc-next git-status last))) + (while (and node (> n 0)) + (when (eq 'unmerged (git-fileinfo->state (ewoc-data node))) + (setq n (1- n)) + (setq last node)) + (setq node (ewoc-next git-status node))) + (ewoc-goto-node git-status last))) + +(defun git-prev-unmerged-file (&optional n) + "Move the selection up N unmerged files." + (interactive "p") + (unless git-status (error "Not in git-status buffer.")) + (let* ((last (ewoc-locate git-status)) + (node (ewoc-prev git-status last))) + (while (and node (> n 0)) + (when (eq 'unmerged (git-fileinfo->state (ewoc-data node))) + (setq n (1- n)) + (setq last node)) + (setq node (ewoc-prev git-status node))) + (ewoc-goto-node git-status last))) + (defun git-add-file () "Add marked file(s) to the index cache." (interactive) @@ -862,7 +889,7 @@ and returns the process output as a string." 'face 'git-header-face) (propertize git-log-msg-separator 'face 'git-separator-face) "\n") - (cond ((and merge-heads (file-readable-p ".git/MERGE_MSG")) + (cond ((file-readable-p ".git/MERGE_MSG") (insert-file-contents ".git/MERGE_MSG")) (sign-off (insert (format "\n\nSigned-off-by: %s <%s>\n" @@ -873,7 +900,8 @@ and returns the process output as a string." (2 font-lock-function-name-face)) (,(concat "^\\(" (regexp-quote git-log-msg-separator) "\\)$") (1 font-lock-comment-face))))) - (log-edit #'git-do-commit nil #'git-log-edit-files buffer)))) + (log-edit #'git-do-commit nil #'git-log-edit-files buffer) + (re-search-forward (regexp-quote (concat git-log-msg-separator "\n")) nil t)))) (defun git-find-file () "Visit the current file in its own buffer." @@ -884,6 +912,15 @@ and returns the process output as a string." (when (eq 'unmerged (git-fileinfo->state info)) (smerge-mode)))) +(defun git-find-file-other-window () + "Visit the current file in its own buffer in another window." + (interactive) + (unless git-status (error "Not in git-status buffer.")) + (let ((info (ewoc-data (ewoc-locate git-status)))) + (find-file-other-window (git-fileinfo->name info)) + (when (eq 'unmerged (git-fileinfo->state info)) + (smerge-mode)))) + (defun git-find-file-imerge () "Visit the current file in interactive merge mode." (interactive) @@ -967,7 +1004,10 @@ and returns the process output as a string." (define-key map "m" 'git-mark-file) (define-key map "M" 'git-mark-all) (define-key map "n" 'git-next-file) + (define-key map "N" 'git-next-unmerged-file) + (define-key map "o" 'git-find-file-other-window) (define-key map "p" 'git-prev-file) + (define-key map "P" 'git-prev-unmerged-file) (define-key map "q" 'git-status-quit) (define-key map "r" 'git-remove-file) (define-key map "R" 'git-resolve-file) diff --git a/contrib/emacs/vc-git.el b/contrib/emacs/vc-git.el index 80e767533a..8b6361922f 100644 --- a/contrib/emacs/vc-git.el +++ b/contrib/emacs/vc-git.el @@ -33,6 +33,8 @@ ;; - working with revisions other than HEAD ;; +(eval-when-compile (require 'cl)) + (defvar git-commits-coding-system 'utf-8 "Default coding system for git commits.") diff --git a/describe.c b/describe.c index ab192f83ae..f4029ee74e 100644 --- a/describe.c +++ b/describe.c @@ -53,7 +53,7 @@ static void add_to_known_names(const char *path, names = ++idx; } -static int get_name(const char *path, const unsigned char *sha1) +static int get_name(const char *path, const unsigned char *sha1, int flag, void *cb_data) { struct commit *commit = lookup_commit_reference_gently(sha1, 1); struct object *object; @@ -113,7 +113,7 @@ static void describe(const char *arg, int last_one) if (!initialized) { initialized = 1; - for_each_ref(get_name); + for_each_ref(get_name, NULL); qsort(name_array, names, sizeof(*name_array), compare_names); } @@ -795,6 +795,23 @@ static void show_stats(struct diffstat_t* data, struct diff_options *options) set, total_files, adds, dels, reset); } +static void show_numstat(struct diffstat_t* data, struct diff_options *options) +{ + int i; + + for (i = 0; i < data->nr; i++) { + struct diffstat_file *file = data->files[i]; + + printf("%d\t%d\t", file->added, file->deleted); + if (options->line_termination && + quote_c_style(file->name, NULL, NULL, 0)) + quote_c_style(file->name, NULL, stdout, 0); + else + fputs(file->name, stdout); + putchar(options->line_termination); + } +} + struct checkdiff_t { struct xdiff_emit_state xm; const char *filename; @@ -1731,6 +1748,7 @@ int diff_setup_done(struct diff_options *options) DIFF_FORMAT_CHECKDIFF | DIFF_FORMAT_NO_OUTPUT)) options->output_format &= ~(DIFF_FORMAT_RAW | + DIFF_FORMAT_NUMSTAT | DIFF_FORMAT_DIFFSTAT | DIFF_FORMAT_SUMMARY | DIFF_FORMAT_PATCH); @@ -1740,7 +1758,9 @@ int diff_setup_done(struct diff_options *options) * recursive bits for other formats here. */ if (options->output_format & (DIFF_FORMAT_PATCH | + DIFF_FORMAT_NUMSTAT | DIFF_FORMAT_DIFFSTAT | + DIFF_FORMAT_SUMMARY | DIFF_FORMAT_CHECKDIFF)) options->recursive = 1; /* @@ -1828,6 +1848,9 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac) else if (!strcmp(arg, "--patch-with-raw")) { options->output_format |= DIFF_FORMAT_PATCH | DIFF_FORMAT_RAW; } + else if (!strcmp(arg, "--numstat")) { + options->output_format |= DIFF_FORMAT_NUMSTAT; + } else if (!strncmp(arg, "--stat", 6)) { char *end; int width = options->stat_width; @@ -2602,7 +2625,7 @@ void diff_flush(struct diff_options *options) separator++; } - if (output_format & DIFF_FORMAT_DIFFSTAT) { + if (output_format & (DIFF_FORMAT_DIFFSTAT|DIFF_FORMAT_NUMSTAT)) { struct diffstat_t diffstat; memset(&diffstat, 0, sizeof(struct diffstat_t)); @@ -2612,7 +2635,10 @@ void diff_flush(struct diff_options *options) if (check_pair_status(p)) diff_flush_stat(p, options, &diffstat); } - show_stats(&diffstat, options); + if (output_format & DIFF_FORMAT_NUMSTAT) + show_numstat(&diffstat, options); + if (output_format & DIFF_FORMAT_DIFFSTAT) + show_stats(&diffstat, options); separator++; } @@ -26,20 +26,21 @@ typedef void (*diff_format_fn_t)(struct diff_queue_struct *q, #define DIFF_FORMAT_RAW 0x0001 #define DIFF_FORMAT_DIFFSTAT 0x0002 -#define DIFF_FORMAT_SUMMARY 0x0004 -#define DIFF_FORMAT_PATCH 0x0008 +#define DIFF_FORMAT_NUMSTAT 0x0004 +#define DIFF_FORMAT_SUMMARY 0x0008 +#define DIFF_FORMAT_PATCH 0x0010 /* These override all above */ -#define DIFF_FORMAT_NAME 0x0010 -#define DIFF_FORMAT_NAME_STATUS 0x0020 -#define DIFF_FORMAT_CHECKDIFF 0x0040 +#define DIFF_FORMAT_NAME 0x0100 +#define DIFF_FORMAT_NAME_STATUS 0x0200 +#define DIFF_FORMAT_CHECKDIFF 0x0400 /* Same as output_format = 0 but we know that -s flag was given * and we should not give default value to output_format. */ -#define DIFF_FORMAT_NO_OUTPUT 0x0080 +#define DIFF_FORMAT_NO_OUTPUT 0x0800 -#define DIFF_FORMAT_CALLBACK 0x0100 +#define DIFF_FORMAT_CALLBACK 0x1000 struct diff_options { const char *filter; @@ -101,6 +102,8 @@ extern int diff_tree(struct tree_desc *t1, struct tree_desc *t2, const char *base, struct diff_options *opt); extern int diff_tree_sha1(const unsigned char *old, const unsigned char *new, const char *base, struct diff_options *opt); +extern int diff_root_tree_sha1(const unsigned char *new, const char *base, + struct diff_options *opt); struct combine_diff_path { struct combine_diff_path *next; @@ -170,6 +173,7 @@ extern void diffcore_std_no_resolve(struct diff_options *); " --patch-with-raw\n" \ " output both a patch and the diff-raw format.\n" \ " --stat show diffstat instead of patch.\n" \ +" --numstat show numeric diffstat instead of patch.\n" \ " --patch-with-stat\n" \ " output a patch and prepend its diffstat.\n" \ " --name-only show only names of changed files.\n" \ diff --git a/environment.c b/environment.c index 63b1d155be..84d870ca4e 100644 --- a/environment.c +++ b/environment.c @@ -20,7 +20,6 @@ int warn_ambiguous_refs = 1; int repository_format_version; char git_commit_encoding[MAX_ENCODING_LENGTH] = "utf-8"; int shared_repository = PERM_UMASK; -int deny_non_fast_forwards = 0; const char *apply_default_whitespace; int zlib_compression_level = Z_DEFAULT_COMPRESSION; int pager_in_use; diff --git a/fetch-pack.c b/fetch-pack.c index e8708aa802..90b79407c6 100644 --- a/fetch-pack.c +++ b/fetch-pack.c @@ -42,7 +42,7 @@ static void rev_list_push(struct commit *commit, int mark) } } -static int rev_list_insert_ref(const char *path, const unsigned char *sha1) +static int rev_list_insert_ref(const char *path, const unsigned char *sha1, int flag, void *cb_data) { struct object *o = deref_tag(parse_object(sha1), path, 0); @@ -143,7 +143,7 @@ static int find_common(int fd[2], unsigned char *result_sha1, unsigned in_vain = 0; int got_continue = 0; - for_each_ref(rev_list_insert_ref); + for_each_ref(rev_list_insert_ref, NULL); fetching = 0; for ( ; refs ; refs = refs->next) { @@ -166,12 +166,13 @@ static int find_common(int fd[2], unsigned char *result_sha1, } if (!fetching) - packet_write(fd[1], "want %s%s%s%s%s\n", + packet_write(fd[1], "want %s%s%s%s%s%s\n", sha1_to_hex(remote), (multi_ack ? " multi_ack" : ""), (use_sideband == 2 ? " side-band-64k" : ""), (use_sideband == 1 ? " side-band" : ""), - (use_thin_pack ? " thin-pack" : "")); + (use_thin_pack ? " thin-pack" : ""), + " ofs-delta"); else packet_write(fd[1], "want %s\n", sha1_to_hex(remote)); fetching++; @@ -253,7 +254,7 @@ done: static struct commit_list *complete; -static int mark_complete(const char *path, const unsigned char *sha1) +static int mark_complete(const char *path, const unsigned char *sha1, int flag, void *cb_data) { struct object *o = parse_object(sha1); @@ -365,7 +366,7 @@ static int everything_local(struct ref **refs, int nr_match, char **match) } } - for_each_ref(mark_complete); + for_each_ref(mark_complete, NULL); if (cutoff) mark_recent_complete_commits(cutoff); @@ -201,7 +201,7 @@ static int interpret_target(char *target, unsigned char *sha1) return -1; } -static int mark_complete(const char *path, const unsigned char *sha1) +static int mark_complete(const char *path, const unsigned char *sha1, int flag, void *cb_data) { struct commit *commit = lookup_commit_reference_gently(sha1, 1); if (commit) { @@ -266,7 +266,7 @@ int pull(int targets, char **target, const char **write_ref, if (!write_ref || !write_ref[i]) continue; - lock[i] = lock_ref_sha1(write_ref[i], NULL, 0); + lock[i] = lock_ref_sha1(write_ref[i], NULL); if (!lock[i]) { error("Can't lock ref %s", write_ref[i]); goto unlock_and_fail; @@ -274,7 +274,7 @@ int pull(int targets, char **target, const char **write_ref, } if (!get_recover) - for_each_ref(mark_complete); + for_each_ref(mark_complete, NULL); for (i = 0; i < targets; i++) { if (interpret_target(target[i], &sha1[20 * i])) { diff --git a/fsck-objects.c b/fsck-objects.c index 4d994f3fc8..46b628cb94 100644 --- a/fsck-objects.c +++ b/fsck-objects.c @@ -402,7 +402,7 @@ static void fsck_dir(int i, char *path) static int default_refs; -static int fsck_handle_ref(const char *refname, const unsigned char *sha1) +static int fsck_handle_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data) { struct object *obj; @@ -424,7 +424,7 @@ static int fsck_handle_ref(const char *refname, const unsigned char *sha1) static void get_default_heads(void) { - for_each_ref(fsck_handle_ref); + for_each_ref(fsck_handle_ref, NULL); /* * Not having any default heads isn't really fatal, but @@ -458,15 +458,14 @@ static void fsck_object_dir(const char *path) static int fsck_head_link(void) { unsigned char sha1[20]; - 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 */ + int flag; + const char *head_points_at = resolve_ref("HEAD", sha1, 1, &flag); - if (!git_refs_heads_master) + if (!head_points_at || !(flag & REF_ISSYMREF)) return error("HEAD is not a symbolic ref"); - if (strncmp(git_refs_heads_master + pfxlen, "refs/heads/", 11)) + if (strncmp(head_points_at, "refs/heads/", 11)) return error("HEAD points to something strange (%s)", - git_refs_heads_master + pfxlen); + head_points_at); if (is_null_sha1(sha1)) return error("HEAD: not a valid git pointer"); return 0; diff --git a/git-annotate.perl b/git-annotate.perl deleted file mode 100755 index 215ed26f3a..0000000000 --- a/git-annotate.perl +++ /dev/null @@ -1,708 +0,0 @@ -#!/usr/bin/perl -# Copyright 2006, Ryan Anderson <ryan@michonline.com> -# -# GPL v2 (See COPYING) -# -# This file is licensed under the GPL v2, or a later version -# at the discretion of Linus Torvalds. - -use warnings; -use strict; -use Getopt::Long; -use POSIX qw(strftime gmtime); -use File::Basename qw(basename dirname); - -sub usage() { - print STDERR "Usage: ${\basename $0} [-s] [-S revs-file] file [ revision ] - -l, --long - Show long rev (Defaults off) - -t, --time - Show raw timestamp (Defaults off) - -r, --rename - Follow renames (Defaults on). - -S, --rev-file revs-file - Use revs from revs-file instead of calling git-rev-list - -h, --help - This message. -"; - - exit(1); -} - -our ($help, $longrev, $rename, $rawtime, $starting_rev, $rev_file) = (0, 0, 1); - -my $rc = GetOptions( "long|l" => \$longrev, - "time|t" => \$rawtime, - "help|h" => \$help, - "rename|r" => \$rename, - "rev-file|S=s" => \$rev_file); -if (!$rc or $help or !@ARGV) { - usage(); -} - -my $filename = shift @ARGV; -if (@ARGV) { - $starting_rev = shift @ARGV; -} - -my @stack = ( - { - 'rev' => defined $starting_rev ? $starting_rev : "HEAD", - 'filename' => $filename, - }, -); - -our @filelines = (); - -if (defined $starting_rev) { - @filelines = git_cat_file($starting_rev, $filename); -} else { - open(F,"<",$filename) - or die "Failed to open filename: $!"; - - while(<F>) { - chomp; - push @filelines, $_; - } - close(F); - -} - -our %revs; -our @revqueue; -our $head; - -my $revsprocessed = 0; -while (my $bound = pop @stack) { - my @revisions = git_rev_list($bound->{'rev'}, $bound->{'filename'}); - foreach my $revinst (@revisions) { - my ($rev, @parents) = @$revinst; - $head ||= $rev; - - if (!defined($rev)) { - $rev = ""; - } - $revs{$rev}{'filename'} = $bound->{'filename'}; - if (scalar @parents > 0) { - $revs{$rev}{'parents'} = \@parents; - next; - } - - if (!$rename) { - next; - } - - my $newbound = find_parent_renames($rev, $bound->{'filename'}); - if ( exists $newbound->{'filename'} && $newbound->{'filename'} ne $bound->{'filename'}) { - push @stack, $newbound; - $revs{$rev}{'parents'} = [$newbound->{'rev'}]; - } - } -} -push @revqueue, $head; -init_claim( defined $starting_rev ? $head : 'dirty'); -unless (defined $starting_rev) { - my $diff = open_pipe("git","diff","HEAD", "--",$filename) - or die "Failed to call git diff to check for dirty state: $!"; - - _git_diff_parse($diff, [$head], "dirty", ( - 'author' => gitvar_name("GIT_AUTHOR_IDENT"), - 'author_date' => sprintf("%s +0000",time()), - ) - ); - close($diff); -} -handle_rev(); - - -my $i = 0; -foreach my $l (@filelines) { - my ($output, $rev, $committer, $date); - if (ref $l eq 'ARRAY') { - ($output, $rev, $committer, $date) = @$l; - if (!$longrev && length($rev) > 8) { - $rev = substr($rev,0,8); - } - } else { - $output = $l; - ($rev, $committer, $date) = ('unknown', 'unknown', 'unknown'); - } - - printf("%s\t(%10s\t%10s\t%d)%s\n", $rev, $committer, - format_date($date), ++$i, $output); -} - -sub init_claim { - my ($rev) = @_; - for (my $i = 0; $i < @filelines; $i++) { - $filelines[$i] = [ $filelines[$i], '', '', '', 1]; - # line, - # rev, - # author, - # date, - # 1 <-- belongs to the original file. - } - $revs{$rev}{'lines'} = \@filelines; -} - - -sub handle_rev { - my $revseen = 0; - my %seen; - while (my $rev = shift @revqueue) { - next if $seen{$rev}++; - - my %revinfo = git_commit_info($rev); - - if (exists $revs{$rev}{parents} && - scalar @{$revs{$rev}{parents}} != 0) { - - git_diff_parse($revs{$rev}{'parents'}, $rev, %revinfo); - push @revqueue, @{$revs{$rev}{'parents'}}; - - } else { - # We must be at the initial rev here, so claim everything that is left. - for (my $i = 0; $i < @{$revs{$rev}{lines}}; $i++) { - if (ref ${$revs{$rev}{lines}}[$i] eq '' || ${$revs{$rev}{lines}}[$i][1] eq '') { - claim_line($i, $rev, $revs{$rev}{lines}, %revinfo); - } - } - } - } -} - - -sub git_rev_list { - my ($rev, $file) = @_; - - my $revlist; - if ($rev_file) { - open($revlist, '<' . $rev_file) - or die "Failed to open $rev_file : $!"; - } else { - $revlist = open_pipe("git-rev-list","--parents","--remove-empty",$rev,"--",$file) - or die "Failed to exec git-rev-list: $!"; - } - - my @revs; - while(my $line = <$revlist>) { - chomp $line; - my ($rev, @parents) = split /\s+/, $line; - push @revs, [ $rev, @parents ]; - } - close($revlist); - - printf("0 revs found for rev %s (%s)\n", $rev, $file) if (@revs == 0); - return @revs; -} - -sub find_parent_renames { - my ($rev, $file) = @_; - - my $patch = open_pipe("git-diff-tree", "-M50", "-r","--name-status", "-z","$rev") - or die "Failed to exec git-diff: $!"; - - local $/ = "\0"; - my %bound; - my $junk = <$patch>; - while (my $change = <$patch>) { - chomp $change; - my $filename = <$patch>; - if (!defined $filename) { - next; - } - chomp $filename; - - if ($change =~ m/^[AMD]$/ ) { - next; - } elsif ($change =~ m/^R/ ) { - my $oldfilename = $filename; - $filename = <$patch>; - chomp $filename; - if ( $file eq $filename ) { - my $parent = git_find_parent($rev, $oldfilename); - @bound{'rev','filename'} = ($parent, $oldfilename); - last; - } - } - } - close($patch); - - return \%bound; -} - - -sub git_find_parent { - my ($rev, $filename) = @_; - - my $revparent = open_pipe("git-rev-list","--remove-empty", "--parents","--max-count=1","$rev","--",$filename) - or die "Failed to open git-rev-list to find a single parent: $!"; - - my $parentline = <$revparent>; - chomp $parentline; - my ($revfound,$parent) = split m/\s+/, $parentline; - - close($revparent); - - return $parent; -} - -sub git_find_all_parents { - my ($rev) = @_; - - my $revparent = open_pipe("git-rev-list","--remove-empty", "--parents","--max-count=1","$rev") - or die "Failed to open git-rev-list to find a single parent: $!"; - - my $parentline = <$revparent>; - chomp $parentline; - my ($origrev, @parents) = split m/\s+/, $parentline; - - close($revparent); - - return @parents; -} - -sub git_merge_base { - my ($rev1, $rev2) = @_; - - my $mb = open_pipe("git-merge-base", $rev1, $rev2) - or die "Failed to open git-merge-base: $!"; - - my $base = <$mb>; - chomp $base; - - close($mb); - - return $base; -} - -# Construct a set of pseudo parents that are in the same order, -# and the same quantity as the real parents, -# but whose SHA1s are as similar to the logical parents -# as possible. -sub get_pseudo_parents { - my ($all, $fake) = @_; - - my @all = @$all; - my @fake = @$fake; - - my @pseudo; - - my %fake = map {$_ => 1} @fake; - my %seenfake; - - my $fakeidx = 0; - foreach my $p (@all) { - if (exists $fake{$p}) { - if ($fake[$fakeidx] ne $p) { - die sprintf("parent mismatch: %s != %s\nall:%s\nfake:%s\n", - $fake[$fakeidx], $p, - join(", ", @all), - join(", ", @fake), - ); - } - - push @pseudo, $p; - $fakeidx++; - $seenfake{$p}++; - - } else { - my $base = git_merge_base($fake[$fakeidx], $p); - if ($base ne $fake[$fakeidx]) { - die sprintf("Result of merge-base doesn't match fake: %s,%s != %s\n", - $fake[$fakeidx], $p, $base); - } - - # The details of how we parse the diffs - # mean that we cannot have a duplicate - # revision in the list, so if we've already - # seen the revision we would normally add, just use - # the actual revision. - if ($seenfake{$base}) { - push @pseudo, $p; - } else { - push @pseudo, $base; - $seenfake{$base}++; - } - } - } - - return @pseudo; -} - - -# Get a diff between the current revision and a parent. -# Record the commit information that results. -sub git_diff_parse { - my ($parents, $rev, %revinfo) = @_; - - my @pseudo_parents; - my @command = ("git-diff-tree"); - my $revision_spec; - - if (scalar @$parents == 1) { - - $revision_spec = join("..", $parents->[0], $rev); - @pseudo_parents = @$parents; - } else { - my @all_parents = git_find_all_parents($rev); - - if (@all_parents != @$parents) { - @pseudo_parents = get_pseudo_parents(\@all_parents, $parents); - } else { - @pseudo_parents = @$parents; - } - - $revision_spec = $rev; - push @command, "-c"; - } - - my @filenames = ( $revs{$rev}{'filename'} ); - - foreach my $parent (@$parents) { - push @filenames, $revs{$parent}{'filename'}; - } - - push @command, "-p", "-M", $revision_spec, "--", @filenames; - - - my $diff = open_pipe( @command ) - or die "Failed to call git-diff for annotation: $!"; - - _git_diff_parse($diff, \@pseudo_parents, $rev, %revinfo); - - close($diff); -} - -sub _git_diff_parse { - my ($diff, $parents, $rev, %revinfo) = @_; - - my $ri = 0; - - my $slines = $revs{$rev}{'lines'}; - my (%plines, %pi); - - my $gotheader = 0; - my ($remstart); - my $parent_count = @$parents; - - my $diff_header_regexp = "^@"; - $diff_header_regexp .= "@" x @$parents; - $diff_header_regexp .= ' -\d+,\d+' x @$parents; - $diff_header_regexp .= ' \+(\d+),\d+'; - $diff_header_regexp .= " " . ("@" x @$parents); - - my %claim_regexps; - my $allparentplus = '^' . '\\+' x @$parents . '(.*)$'; - - { - my $i = 0; - foreach my $parent (@$parents) { - - $pi{$parent} = 0; - my $r = '^' . '.' x @$parents . '(.*)$'; - my $p = $r; - substr($p,$i+1, 1) = '\\+'; - - my $m = $r; - substr($m,$i+1, 1) = '-'; - - $claim_regexps{$parent}{plus} = $p; - $claim_regexps{$parent}{minus} = $m; - - $plines{$parent} = []; - - $i++; - } - } - - DIFF: - while(<$diff>) { - chomp; - #printf("%d:%s:\n", $gotheader, $_); - if (m/$diff_header_regexp/) { - $remstart = $1 - 1; - # (0-based arrays) - - $gotheader = 1; - - foreach my $parent (@$parents) { - for (my $i = $ri; $i < $remstart; $i++) { - $plines{$parent}[$pi{$parent}++] = $slines->[$i]; - } - } - $ri = $remstart; - - next DIFF; - - } elsif (!$gotheader) { - # Skip over the leadin. - next DIFF; - } - - if (m/^\\/) { - ; - # Skip \No newline at end of file. - # But this can be internationalized, so only look - # for an initial \ - - } else { - my %claims = (); - my $negclaim = 0; - my $allclaimed = 0; - my $line; - - if (m/$allparentplus/) { - claim_line($ri, $rev, $slines, %revinfo); - $allclaimed = 1; - - } - - PARENT: - foreach my $parent (keys %claim_regexps) { - my $m = $claim_regexps{$parent}{minus}; - my $p = $claim_regexps{$parent}{plus}; - - if (m/$m/) { - $line = $1; - $plines{$parent}[$pi{$parent}++] = [ $line, '', '', '', 0 ]; - $negclaim++; - - } elsif (m/$p/) { - $line = $1; - if (get_line($slines, $ri) eq $line) { - # Found a match, claim - $claims{$parent}++; - - } else { - die sprintf("Sync error: %d\n|%s\n|%s\n%s => %s\n", - $ri, $line, - get_line($slines, $ri), - $rev, $parent); - } - } - } - - if (%claims) { - foreach my $parent (@$parents) { - next if $claims{$parent} || $allclaimed; - $plines{$parent}[$pi{$parent}++] = $slines->[$ri]; - #[ $line, '', '', '', 0 ]; - } - $ri++; - - } elsif ($negclaim) { - next DIFF; - - } else { - if (substr($_,scalar @$parents) ne get_line($slines,$ri) ) { - foreach my $parent (@$parents) { - printf("parent %s is on line %d\n", $parent, $pi{$parent}); - } - - my @context; - for (my $i = -2; $i < 2; $i++) { - push @context, get_line($slines, $ri + $i); - } - my $context = join("\n", @context); - - my $justline = substr($_, scalar @$parents); - die sprintf("Line %d, does not match:\n|%s|\n|%s|\n%s\n", - $ri, - $justline, - $context); - } - foreach my $parent (@$parents) { - $plines{$parent}[$pi{$parent}++] = $slines->[$ri]; - } - $ri++; - } - } - } - - for (my $i = $ri; $i < @{$slines} ; $i++) { - foreach my $parent (@$parents) { - push @{$plines{$parent}}, $slines->[$ri]; - } - $ri++; - } - - foreach my $parent (@$parents) { - $revs{$parent}{lines} = $plines{$parent}; - } - - return; -} - -sub get_line { - my ($lines, $index) = @_; - - return ref $lines->[$index] ne '' ? $lines->[$index][0] : $lines->[$index]; -} - -sub git_cat_file { - my ($rev, $filename) = @_; - return () unless defined $rev && defined $filename; - - my $blob = git_ls_tree($rev, $filename); - die "Failed to find a blob for $filename in rev $rev\n" if !defined $blob; - - my $catfile = open_pipe("git","cat-file", "blob", $blob) - or die "Failed to git-cat-file blob $blob (rev $rev, file $filename): " . $!; - - my @lines; - while(<$catfile>) { - chomp; - push @lines, $_; - } - close($catfile); - - return @lines; -} - -sub git_ls_tree { - my ($rev, $filename) = @_; - - my $lstree = open_pipe("git","ls-tree",$rev,$filename) - or die "Failed to call git ls-tree: $!"; - - my ($mode, $type, $blob, $tfilename); - while(<$lstree>) { - chomp; - ($mode, $type, $blob, $tfilename) = split(/\s+/, $_, 4); - last if ($tfilename eq $filename); - } - close($lstree); - - return $blob if ($tfilename eq $filename); - die "git-ls-tree failed to find blob for $filename"; - -} - - - -sub claim_line { - my ($floffset, $rev, $lines, %revinfo) = @_; - my $oline = get_line($lines, $floffset); - @{$lines->[$floffset]} = ( $oline, $rev, - $revinfo{'author'}, $revinfo{'author_date'} ); - #printf("Claiming line %d with rev %s: '%s'\n", - # $floffset, $rev, $oline) if 1; -} - -sub git_commit_info { - my ($rev) = @_; - my $commit = open_pipe("git-cat-file", "commit", $rev) - or die "Failed to call git-cat-file: $!"; - - my %info; - while(<$commit>) { - chomp; - last if (length $_ == 0); - - if (m/^author (.*) <(.*)> (.*)$/) { - $info{'author'} = $1; - $info{'author_email'} = $2; - $info{'author_date'} = $3; - } elsif (m/^committer (.*) <(.*)> (.*)$/) { - $info{'committer'} = $1; - $info{'committer_email'} = $2; - $info{'committer_date'} = $3; - } - } - close($commit); - - return %info; -} - -sub format_date { - if ($rawtime) { - return $_[0]; - } - my ($timestamp, $timezone) = split(' ', $_[0]); - my $minutes = abs($timezone); - $minutes = int($minutes / 100) * 60 + ($minutes % 100); - if ($timezone < 0) { - $minutes = -$minutes; - } - my $t = $timestamp + $minutes * 60; - return strftime("%Y-%m-%d %H:%M:%S " . $timezone, gmtime($t)); -} - -# Copied from git-send-email.perl - We need a Git.pm module.. -sub gitvar { - my ($var) = @_; - my $fh; - my $pid = open($fh, '-|'); - die "$!" unless defined $pid; - if (!$pid) { - exec('git-var', $var) or die "$!"; - } - my ($val) = <$fh>; - close $fh or die "$!"; - chomp($val); - return $val; -} - -sub gitvar_name { - my ($name) = @_; - my $val = gitvar($name); - my @field = split(/\s+/, $val); - return join(' ', @field[0...(@field-4)]); -} - -sub open_pipe { - if ($^O eq '##INSERT_ACTIVESTATE_STRING_HERE##') { - return open_pipe_activestate(@_); - } else { - return open_pipe_normal(@_); - } -} - -sub open_pipe_activestate { - tie *fh, "Git::ActiveStatePipe", @_; - return *fh; -} - -sub open_pipe_normal { - my (@execlist) = @_; - - my $pid = open my $kid, "-|"; - defined $pid or die "Cannot fork: $!"; - - unless ($pid) { - exec @execlist; - die "Cannot exec @execlist: $!"; - } - - return $kid; -} - -package Git::ActiveStatePipe; -use strict; - -sub TIEHANDLE { - my ($class, @params) = @_; - my $cmdline = join " ", @params; - my @data = qx{$cmdline}; - bless { i => 0, data => \@data }, $class; -} - -sub READLINE { - my $self = shift; - if ($self->{i} >= scalar @{$self->{data}}) { - return undef; - } - return $self->{'data'}->[ $self->{i}++ ]; -} - -sub CLOSE { - my $self = shift; - delete $self->{data}; - delete $self->{i}; -} - -sub EOF { - my $self = shift; - return ($self->{i} >= scalar @{$self->{data}}); -} diff --git a/git-bisect.sh b/git-bisect.sh index 06a8d26945..6da31e87a0 100755 --- a/git-bisect.sh +++ b/git-bisect.sh @@ -179,11 +179,12 @@ bisect_reset() { *) usage ;; esac - git checkout "$branch" && - rm -fr "$GIT_DIR/refs/bisect" - rm -f "$GIT_DIR/refs/heads/bisect" "$GIT_DIR/head-name" - rm -f "$GIT_DIR/BISECT_LOG" - rm -f "$GIT_DIR/BISECT_NAMES" + if git checkout "$branch"; then + rm -fr "$GIT_DIR/refs/bisect" + rm -f "$GIT_DIR/refs/heads/bisect" "$GIT_DIR/head-name" + rm -f "$GIT_DIR/BISECT_LOG" + rm -f "$GIT_DIR/BISECT_NAMES" + fi } bisect_replay () { diff --git a/git-branch.sh b/git-branch.sh deleted file mode 100755 index f823c788fd..0000000000 --- a/git-branch.sh +++ /dev/null @@ -1,140 +0,0 @@ -#!/bin/sh - -USAGE='[-l] [-f] <branchname> [<start-point>] | (-d | -D) <branchname> | [-r]' -LONG_USAGE='If no arguments, show available branches and mark current branch with a star. -If one argument, create a new branch <branchname> based off of current HEAD. -If two arguments, create a new branch <branchname> based off of <start-point>.' - -SUBDIRECTORY_OK='Yes' -. git-sh-setup - -headref=$(git-symbolic-ref HEAD | sed -e 's|^refs/heads/||') - -delete_branch () { - option="$1" - shift - for branch_name - do - case ",$headref," in - ",$branch_name,") - die "Cannot delete the branch you are on." ;; - ,,) - die "What branch are you on anyway?" ;; - esac - branch=$(cat "$GIT_DIR/refs/heads/$branch_name") && - branch=$(git-rev-parse --verify "$branch^0") || - die "Seriously, what branch are you talking about?" - case "$option" in - -D) - ;; - *) - mbs=$(git-merge-base -a "$branch" HEAD | tr '\012' ' ') - case " $mbs " in - *' '$branch' '*) - # the merge base of branch and HEAD contains branch -- - # which means that the HEAD contains everything in both. - ;; - *) - echo >&2 "The branch '$branch_name' is not a strict subset of your current HEAD. -If you are sure you want to delete it, run 'git branch -D $branch_name'." - exit 1 - ;; - esac - ;; - esac - rm -f "$GIT_DIR/logs/refs/heads/$branch_name" - rm -f "$GIT_DIR/refs/heads/$branch_name" - echo "Deleted branch $branch_name." - done - exit 0 -} - -ls_remote_branches () { - git-rev-parse --symbolic --all | - sed -ne 's|^refs/\(remotes/\)|\1|p' | - sort -} - -force= -create_log= -while case "$#,$1" in 0,*) break ;; *,-*) ;; *) break ;; esac -do - case "$1" in - -d | -D) - delete_branch "$@" - exit - ;; - -r) - ls_remote_branches - exit - ;; - -f) - force="$1" - ;; - -l) - create_log="yes" - ;; - --) - shift - break - ;; - -*) - usage - ;; - esac - shift -done - -case "$#" in -0) - git-rev-parse --symbolic --branches | - sort | - while read ref - do - if test "$headref" = "$ref" - then - pfx='*' - else - pfx=' ' - fi - echo "$pfx $ref" - done - exit 0 ;; -1) - head=HEAD ;; -2) - head="$2^0" ;; -esac -branchname="$1" - -rev=$(git-rev-parse --verify "$head") || exit - -git-check-ref-format "heads/$branchname" || - die "we do not like '$branchname' as a branch name." - -if [ -d "$GIT_DIR/refs/heads/$branchname" ] -then - for refdir in `cd "$GIT_DIR" && \ - find "refs/heads/$branchname" -type d | sort -r` - do - rmdir "$GIT_DIR/$refdir" || \ - die "Could not delete '$refdir', there may still be a ref there." - done -fi - -if [ -e "$GIT_DIR/refs/heads/$branchname" ] -then - if test '' = "$force" - then - die "$branchname already exists." - elif test "$branchname" = "$headref" - then - die "cannot force-update the current branch." - fi -fi -if test "$create_log" = 'yes' -then - mkdir -p $(dirname "$GIT_DIR/logs/refs/heads/$branchname") - touch "$GIT_DIR/logs/refs/heads/$branchname" -fi -git update-ref -m "branch: Created from $head" "refs/heads/$branchname" $rev diff --git a/git-checkout.sh b/git-checkout.sh index dd477245fb..119bca1ffb 100755 --- a/git-checkout.sh +++ b/git-checkout.sh @@ -22,7 +22,7 @@ while [ "$#" != "0" ]; do shift [ -z "$newbranch" ] && die "git checkout: -b needs a branch name" - [ -e "$GIT_DIR/refs/heads/$newbranch" ] && + git-show-ref --verify --quiet -- "refs/heads/$newbranch" && die "git checkout: branch $newbranch already exists" git-check-ref-format "heads/$newbranch" || die "git checkout: we do not like '$newbranch' as a branch name." @@ -51,7 +51,8 @@ while [ "$#" != "0" ]; do fi new="$rev" new_name="$arg^0" - if [ -f "$GIT_DIR/refs/heads/$arg" ]; then + if git-show-ref --verify --quiet -- "refs/heads/$arg" + then branch="$arg" fi elif rev=$(git-rev-parse --verify "$arg^{tree}" 2>/dev/null) diff --git a/git-cherry.sh b/git-cherry.sh deleted file mode 100755 index cf7af5502c..0000000000 --- a/git-cherry.sh +++ /dev/null @@ -1,91 +0,0 @@ -#!/bin/sh -# -# Copyright (c) 2005 Junio C Hamano. -# - -USAGE='[-v] <upstream> [<head>] [<limit>]' -LONG_USAGE=' __*__*__*__*__> <upstream> - / - fork-point - \__+__+__+__+__+__+__+__> <head> - -Each commit between the fork-point (or <limit> if given) and <head> is -examined, and compared against the change each commit between the -fork-point and <upstream> introduces. If the change seems to be in -the upstream, it is shown on the standard output with prefix "-". -Otherwise it is shown with prefix "+".' -. git-sh-setup - -case "$1" in -v) verbose=t; shift ;; esac - -case "$#,$1" in -1,*..*) - upstream=$(expr "z$1" : 'z\(.*\)\.\.') ours=$(expr "z$1" : '.*\.\.\(.*\)$') - set x "$upstream" "$ours" - shift ;; -esac - -case "$#" in -1) upstream=`git-rev-parse --verify "$1"` && - ours=`git-rev-parse --verify HEAD` || exit - limit="$upstream" - ;; -2) upstream=`git-rev-parse --verify "$1"` && - ours=`git-rev-parse --verify "$2"` || exit - limit="$upstream" - ;; -3) upstream=`git-rev-parse --verify "$1"` && - ours=`git-rev-parse --verify "$2"` && - limit=`git-rev-parse --verify "$3"` || exit - ;; -*) usage ;; -esac - -# Note that these list commits in reverse order; -# not that the order in inup matters... -inup=`git-rev-list ^$ours $upstream` && -ours=`git-rev-list $ours ^$limit` || exit - -tmp=.cherry-tmp$$ -patch=$tmp-patch -mkdir $patch -trap "rm -rf $tmp-*" 0 1 2 3 15 - -for c in $inup -do - git-diff-tree -p $c -done | git-patch-id | -while read id name -do - echo $name >>$patch/$id -done - -LF=' -' - -O= -for c in $ours -do - set x `git-diff-tree -p $c | git-patch-id` - if test "$2" != "" - then - if test -f "$patch/$2" - then - sign=- - else - sign=+ - fi - case "$verbose" in - t) - c=$(git-rev-list --pretty=oneline --max-count=1 $c) - esac - case "$O" in - '') O="$sign $c" ;; - *) O="$sign $c$LF$O" ;; - esac - fi -done -case "$O" in -'') ;; -*) echo "$O" ;; -esac diff --git a/git-clone.sh b/git-clone.sh index 24b119537b..3f006d1a77 100755 --- a/git-clone.sh +++ b/git-clone.sh @@ -8,11 +8,15 @@ # See git-sh-setup why. unset CDPATH -usage() { - echo >&2 "Usage: $0 [--template=<template_directory>] [--use-separate-remote] [--reference <reference-repo>] [--bare] [-l [-s]] [-q] [-u <upload-pack>] [--origin <name>] [-n] <repo> [<dir>]" +die() { + echo >&2 "$@" exit 1 } +usage() { + die "Usage: $0 [--template=<template_directory>] [--use-separate-remote] [--reference <reference-repo>] [--bare] [-l [-s]] [-q] [-u <upload-pack>] [--origin <name>] [-n] <repo> [<dir>]" +} + get_repo_base() { (cd "$1" && (cd .git ; pwd)) 2> /dev/null } @@ -35,11 +39,9 @@ clone_dumb_http () { "`git-repo-config --bool http.noEPSV`" = true ]; then curl_extra_args="${curl_extra_args} --disable-epsv" fi - http_fetch "$1/info/refs" "$clone_tmp/refs" || { - echo >&2 "Cannot get remote repository information. + http_fetch "$1/info/refs" "$clone_tmp/refs" || + die "Cannot get remote repository information. Perhaps git-update-server-info needs to be run there?" - exit 1; - } while read sha1 refname do name=`expr "z$refname" : 'zrefs/\(.*\)'` && @@ -143,17 +145,12 @@ while '') usage ;; */*) - echo >&2 "'$2' is not suitable for an origin name" - exit 1 + die "'$2' is not suitable for an origin name" esac - git-check-ref-format "heads/$2" || { - echo >&2 "'$2' is not suitable for a branch name" - exit 1 - } - test -z "$origin_override" || { - echo >&2 "Do not give more than one --origin options." - exit 1 - } + git-check-ref-format "heads/$2" || + die "'$2' is not suitable for a branch name" + test -z "$origin_override" || + die "Do not give more than one --origin options." origin_override=yes origin="$2"; shift ;; @@ -169,24 +166,19 @@ do done repo="$1" -if test -z "$repo" -then - echo >&2 'you must specify a repository to clone.' - exit 1 -fi +test -n "$repo" || + die 'you must specify a repository to clone.' # --bare implies --no-checkout if test yes = "$bare" then if test yes = "$origin_override" then - echo >&2 '--bare and --origin $origin options are incompatible.' - exit 1 + die '--bare and --origin $origin options are incompatible.' fi if test t = "$use_separate_remote" then - echo >&2 '--bare and --use-separate-remote options are incompatible.' - exit 1 + die '--bare and --use-separate-remote options are incompatible.' fi no_checkout=yes fi @@ -206,7 +198,7 @@ fi dir="$2" # Try using "humanish" part of source repo if user didn't specify one [ -z "$dir" ] && dir=$(echo "$repo" | sed -e 's|/$||' -e 's|:*/*\.git$||' -e 's|.*[/:]||g') -[ -e "$dir" ] && echo "$dir already exists." && usage +[ -e "$dir" ] && die "destination directory '$dir' already exists." mkdir -p "$dir" && D=$(cd "$dir" && pwd) && trap 'err=$?; cd ..; rm -rf "$D"; exit $err' 0 @@ -233,7 +225,7 @@ then cd reference-tmp && tar xf -) else - echo >&2 "$reference: not a local directory." && usage + die "reference repository '$reference' is not a local directory." fi fi @@ -242,10 +234,8 @@ rm -f "$GIT_DIR/CLONE_HEAD" # We do local magic only when the user tells us to. case "$local,$use_local" in yes,yes) - ( cd "$repo/objects" ) || { - echo >&2 "-l flag seen but $repo is not local." - exit 1 - } + ( cd "$repo/objects" ) || + die "-l flag seen but repository '$repo' is not local." case "$local_shared" in no) @@ -307,18 +297,15 @@ yes,yes) then clone_dumb_http "$repo" "$D" else - echo >&2 "http transport not supported, rebuild Git with curl support" - exit 1 + die "http transport not supported, rebuild Git with curl support" fi ;; *) case "$upload_pack" in '') git-fetch-pack --all -k $quiet "$repo" ;; *) git-fetch-pack --all -k $quiet "$upload_pack" "$repo" ;; - esac >"$GIT_DIR/CLONE_HEAD" || { - echo >&2 "fetch-pack from '$repo' failed." - exit 1 - } + esac >"$GIT_DIR/CLONE_HEAD" || + die "fetch-pack from '$repo' failed." ;; esac ;; diff --git a/git-commit.sh b/git-commit.sh index 5b1cf85825..81c3a0cb61 100755 --- a/git-commit.sh +++ b/git-commit.sh @@ -441,7 +441,7 @@ then elif test "$use_commit" != "" then git-cat-file commit "$use_commit" | sed -e '1,/^$/d' -elif test -f "$GIT_DIR/MERGE_HEAD" && test -f "$GIT_DIR/MERGE_MSG" +elif test -f "$GIT_DIR/MERGE_MSG" then cat "$GIT_DIR/MERGE_MSG" elif test -f "$GIT_DIR/SQUASH_MSG" @@ -522,15 +522,15 @@ then PARENTS=$(git-cat-file commit HEAD | sed -n -e '/^$/q' -e 's/^parent /-p /p') fi - current=$(git-rev-parse --verify HEAD) + current="$(git-rev-parse --verify HEAD)" else if [ -z "$(git-ls-files)" ]; then echo >&2 Nothing to commit exit 1 fi PARENTS="" - current= rloga='commit (initial)' + current='' fi if test -z "$no_edit" @@ -606,8 +606,8 @@ then fi && commit=$(cat "$GIT_DIR"/COMMIT_MSG | git-commit-tree $tree $PARENTS) && rlogm=$(sed -e 1q "$GIT_DIR"/COMMIT_MSG) && - git-update-ref -m "$rloga: $rlogm" HEAD $commit $current && - rm -f -- "$GIT_DIR/MERGE_HEAD" && + git-update-ref -m "$rloga: $rlogm" HEAD $commit "$current" && + rm -f -- "$GIT_DIR/MERGE_HEAD" "$GIT_DIR/MERGE_MSG" && if test -f "$NEXT_INDEX" then mv "$NEXT_INDEX" "$THIS_INDEX" diff --git a/git-cvsimport.perl b/git-cvsimport.perl index e5a00a1285..14e2c6131b 100755 --- a/git-cvsimport.perl +++ b/git-cvsimport.perl @@ -495,22 +495,17 @@ unless(-d $git_dir) { $tip_at_start = `git-rev-parse --verify HEAD`; # Get the last import timestamps - opendir(D,"$git_dir/refs/heads"); - while(defined(my $head = readdir(D))) { - next if $head =~ /^\./; - open(F,"$git_dir/refs/heads/$head") - or die "Bad head branch: $head: $!\n"; - chomp(my $ftag = <F>); - close(F); - open(F,"git-cat-file commit $ftag |"); - while(<F>) { - next unless /^author\s.*\s(\d+)\s[-+]\d{4}$/; - $branch_date{$head} = $1; - last; - } - close(F); + my $fmt = '($ref, $author) = (%(refname), %(author));'; + open(H, "git-for-each-ref --perl --format='$fmt' refs/heads |") or + die "Cannot run git-for-each-ref: $!\n"; + while(defined(my $entry = <H>)) { + my ($ref, $author); + eval($entry) || die "cannot eval refs list: $@"; + my ($head) = ($ref =~ m|^refs/heads/(.*)|); + $author =~ /^.*\s(\d+)\s[-+]\d{4}$/; + $branch_date{$head} = $1; } - closedir(D); + close(H); } -d $git_dir diff --git a/git-cvsserver.perl b/git-cvsserver.perl index 08ad831a39..8817f8bb4f 100755 --- a/git-cvsserver.perl +++ b/git-cvsserver.perl @@ -2118,9 +2118,17 @@ sub new mode TEXT NOT NULL ) "); + $self->{dbh}->do(" + CREATE INDEX revision_ix1 + ON revision (name,revision) + "); + $self->{dbh}->do(" + CREATE INDEX revision_ix2 + ON revision (name,commithash) + "); } - # Construct the revision table if required + # Construct the head table if required unless ( $self->{tables}{head} ) { $self->{dbh}->do(" @@ -2134,6 +2142,10 @@ sub new mode TEXT NOT NULL ) "); + $self->{dbh}->do(" + CREATE INDEX head_ix1 + ON head (name) + "); } # Construct the properties table if required diff --git a/git-fetch.sh b/git-fetch.sh index a674c8c574..539dff6ee0 100755 --- a/git-fetch.sh +++ b/git-fetch.sh @@ -129,38 +129,44 @@ append_fetch_head () { then headc_=$(git-rev-parse --verify "$head_^0") || exit echo "$headc_ $not_for_merge_ $note_" >>"$GIT_DIR/FETCH_HEAD" - [ "$verbose" ] && echo >&2 "* committish: $head_" - [ "$verbose" ] && echo >&2 " $note_" else echo "$head_ not-for-merge $note_" >>"$GIT_DIR/FETCH_HEAD" - [ "$verbose" ] && echo >&2 "* non-commit: $head_" - [ "$verbose" ] && echo >&2 " $note_" - fi - if test "$local_name_" != "" - then - # We are storing the head locally. Make sure that it is - # a fast forward (aka "reverse push"). - fast_forward_local "$local_name_" "$head_" "$note_" fi + + update_local_ref "$local_name_" "$head_" "$note_" } -fast_forward_local () { - mkdir -p "$(dirname "$GIT_DIR/$1")" +update_local_ref () { + # If we are storing the head locally make sure that it is + # a fast forward (aka "reverse push"). + + label_=$(git-cat-file -t $2) + newshort_=$(git-rev-parse --short $2) + if test -z "$1" ; then + [ "$verbose" ] && echo >&2 "* fetched $3" + [ "$verbose" ] && echo >&2 " $label_: $newshort_" + return 0 + fi + oldshort_=$(git show-ref --hash --abbrev "$1" 2>/dev/null) + case "$1" in refs/tags/*) # Tags need not be pointing at commits so there # is no way to guarantee "fast-forward" anyway. - if test -f "$GIT_DIR/$1" + if test -n "$oldshort_" then - if now_=$(cat "$GIT_DIR/$1") && test "$now_" = "$2" + if now_=$(git show-ref --hash "$1") && test "$now_" = "$2" then - [ "$verbose" ] && echo >&2 "* $1: same as $3" ||: + [ "$verbose" ] && echo >&2 "* $1: same as $3" + [ "$verbose" ] && echo >&2 " $label_: $newshort_" ||: else echo >&2 "* $1: updating with $3" + echo >&2 " $label_: $newshort_" git-update-ref -m "$rloga: updating tag" "$1" "$2" fi else echo >&2 "* $1: storing $3" + echo >&2 " $label_: $newshort_" git-update-ref -m "$rloga: storing tag" "$1" "$2" fi ;; @@ -178,31 +184,34 @@ fast_forward_local () { if test -n "$verbose" then echo >&2 "* $1: same as $3" + echo >&2 " $label_: $newshort_" fi ;; *,$local) echo >&2 "* $1: fast forward to $3" - echo >&2 " from $local to $2" + echo >&2 " old..new: $oldshort_..$newshort_" git-update-ref -m "$rloga: fast-forward" "$1" "$2" "$local" ;; *) false ;; esac || { - echo >&2 "* $1: does not fast forward to $3;" case ",$force,$single_force," in *,t,*) - echo >&2 " forcing update." + echo >&2 "* $1: forcing update to non-fast forward $3" + echo >&2 " old...new: $oldshort_...$newshort_" git-update-ref -m "$rloga: forced-update" "$1" "$2" "$local" ;; *) - echo >&2 " not updating." + echo >&2 "* $1: not updating to non-fast forward $3" + echo >&2 " old...new: $oldshort_...$newshort_" exit 1 ;; esac } else echo >&2 "* $1: storing $3" + echo >&2 " $label_: $newshort_" git-update-ref -m "$rloga: storing head" "$1" "$2" fi ;; @@ -418,7 +427,7 @@ case "$no_tags$tags" in sed -ne 's|^\([0-9a-f]*\)[ ]\(refs/tags/.*\)^{}$|\1 \2|p' | while read sha1 name do - test -f "$GIT_DIR/$name" && continue + git-show-ref --verify --quiet -- $name && continue git-check-ref-format "$name" || { echo >&2 "warning: tag ${name} ignored" continue diff --git a/git-merge.sh b/git-merge.sh index 789f4de595..cb094388bb 100755 --- a/git-merge.sh +++ b/git-merge.sh @@ -199,7 +199,7 @@ f,*) ;; ?,1,"$head",*) # Again the most common case of merging one remote. - echo "Updating from $head to $1" + echo "Updating $(git-rev-parse --short $head)..$(git-rev-parse --short $1)" git-update-index --refresh 2>/dev/null new_head=$(git-rev-parse --verify "$1^0") && git-read-tree -u -v -m $head "$new_head" && diff --git a/git-rebase.sh b/git-rebase.sh index a7373c0532..546fa446fc 100755 --- a/git-rebase.sh +++ b/git-rebase.sh @@ -3,7 +3,7 @@ # Copyright (c) 2005 Junio C Hamano. # -USAGE='[--onto <newbase>] <upstream> [<branch>]' +USAGE='[-v] [--onto <newbase>] <upstream> [<branch>]' LONG_USAGE='git-rebase replaces <branch> with a new branch of the same name. When the --onto option is provided the new branch starts out with a HEAD equal to <newbase>, otherwise it is equal to <upstream> @@ -39,6 +39,7 @@ strategy=recursive do_merge= dotest=$GIT_DIR/.dotest-merge prec=4 +verbose= continue_merge () { test -n "$prev_head" || die "prev_head must be defined" @@ -190,6 +191,9 @@ do esac do_merge=t ;; + -v|--verbose) + verbose=t + ;; -*) usage ;; @@ -273,6 +277,12 @@ then exit 0 fi +if test -n "$verbose" +then + echo "Changes from $mb to $onto:" + git-diff-tree --stat --summary "$mb" "$onto" +fi + # Rewind the head to "$onto"; this saves our current head in ORIG_HEAD. git-reset --hard "$onto" @@ -286,7 +296,7 @@ fi if test -z "$do_merge" then - git-format-patch -k --stdout --full-index "$upstream"..ORIG_HEAD | + git-format-patch -k --stdout --full-index --ignore-if-in-upstream "$upstream"..ORIG_HEAD | git am --binary -3 -k --resolvemsg="$RESOLVEMSG" \ --reflog-action=rebase exit $? diff --git a/git-repack.sh b/git-repack.sh index f2c9071d11..17e24526c2 100755 --- a/git-repack.sh +++ b/git-repack.sh @@ -3,7 +3,7 @@ # Copyright (c) 2005 Linus Torvalds # -USAGE='[-a] [-d] [-f] [-l] [-n] [-q]' +USAGE='[-a] [-d] [-f] [-l] [-n] [-q] [--window=N] [--depth=N]' SUBDIRECTORY_OK='Yes' . git-sh-setup @@ -25,6 +25,15 @@ do shift done +# Later we will default repack.UseDeltaBaseOffset to true +default_dbo=false + +case "`git repo-config --bool repack.usedeltabaseoffset || + echo $default_dbo`" in +true) + extra="$extra --delta-base-offset" ;; +esac + PACKDIR="$GIT_OBJECT_DIRECTORY/pack" PACKTMP="$GIT_DIR/.tmp-$$-pack" rm -f "$PACKTMP"-* diff --git a/git-resolve.sh b/git-resolve.sh index 729ec65dc9..36b90e3849 100755 --- a/git-resolve.sh +++ b/git-resolve.sh @@ -46,7 +46,7 @@ case "$common" in exit 0 ;; "$head") - echo "Updating from $head to $merge" + echo "Updating $(git-rev-parse --short $head)..$(git-rev-parse --short $merge)" git-read-tree -u -m $head $merge || exit 1 git-update-ref -m "resolve $merge_name: Fast forward" \ HEAD "$merge" "$head" diff --git a/git-revert.sh b/git-revert.sh index 4fd81b6ed6..6eab3c72df 100755 --- a/git-revert.sh +++ b/git-revert.sh @@ -145,9 +145,18 @@ git-read-tree -m -u --aggressive $base $head $next && result=$(git-write-tree 2>/dev/null) || { echo >&2 "Simple $me fails; trying Automatic $me." git-merge-index -o git-merge-one-file -a || { + mv -f .msg "$GIT_DIR/MERGE_MSG" + { + echo ' +Conflicts: +' + git ls-files --unmerged | + sed -e 's/^[^ ]* / /' | + uniq + } >>"$GIT_DIR/MERGE_MSG" echo >&2 "Automatic $me failed. After resolving the conflicts," echo >&2 "mark the corrected paths with 'git-update-index <paths>'" - echo >&2 "and commit with 'git commit -F .msg'" + echo >&2 "and commit the result." case "$me" in cherry-pick) echo >&2 "You may choose to use the following when making" diff --git a/git-send-email.perl b/git-send-email.perl index 3f50abaeb6..4c87c20c15 100755 --- a/git-send-email.perl +++ b/git-send-email.perl @@ -83,11 +83,12 @@ sub cleanup_compose_files(); my $compose_filename = ".msg.$$"; # Variables we fill in automatically, or via prompting: -my (@to,@cc,@initial_cc,@bcclist, +my (@to,@cc,@initial_cc,@bcclist,@xh, $initial_reply_to,$initial_subject,@files,$from,$compose,$time); # Behavior modification variables -my ($chain_reply_to, $quiet, $suppress_from, $no_signed_off_cc) = (1, 0, 0, 0); +my ($chain_reply_to, $quiet, $suppress_from, $no_signed_off_cc, + $dry_run) = (1, 0, 0, 0, 0); my $smtp_server; # Example reply to: @@ -116,6 +117,7 @@ my $rc = GetOptions("from=s" => \$from, "quiet" => \$quiet, "suppress-from" => \$suppress_from, "no-signed-off-cc|no-signed-off-by-cc" => \$no_signed_off_cc, + "dry-run" => \$dry_run, ); # Verify the user input @@ -229,6 +231,9 @@ if (!defined $initial_reply_to && $prompting) { } if (!$smtp_server) { + $smtp_server = $repo->config('sendemail.smtpserver'); +} +if (!$smtp_server) { foreach (qw( /usr/sbin/sendmail /usr/lib/sendmail )) { if (-x $_) { $smtp_server = $_; @@ -409,6 +414,11 @@ sub send_message $gitversion = Git::version(); } + my ($author_name) = ($from =~ /^(.*?)\s+</); + if ($author_name && $author_name =~ /\./ && $author_name !~ /^".*"$/) { + my ($name, $addr) = ($from =~ /^(.*?)(\s+<.*)/); + $from = "\"$name\"$addr"; + } my $header = "From: $from To: $to Cc: $cc @@ -422,8 +432,13 @@ X-Mailer: git-send-email $gitversion $header .= "In-Reply-To: $reply_to\n"; $header .= "References: $references\n"; } + if (@xh) { + $header .= join("\n", @xh) . "\n"; + } - if ($smtp_server =~ m#^/#) { + if ($dry_run) { + # We don't want to send the email. + } elsif ($smtp_server =~ m#^/#) { my $pid = open my $sm, '|-'; defined $pid or die $!; if (!$pid) { @@ -472,15 +487,22 @@ foreach my $t (@files) { my $author_not_sender = undef; @cc = @initial_cc; - my $found_mbox = 0; + @xh = (); + my $input_format = undef; my $header_done = 0; $message = ""; while(<F>) { if (!$header_done) { - $found_mbox = 1, next if (/^From /); + if (/^From /) { + $input_format = 'mbox'; + next; + } chomp; + if (!defined $input_format && /^[-A-Za-z]+:\s/) { + $input_format = 'mbox'; + } - if ($found_mbox) { + if (defined $input_format && $input_format eq 'mbox') { if (/^Subject:\s+(.*)$/) { $subject = $1; @@ -495,6 +517,9 @@ foreach my $t (@files) { $2, $_) unless $quiet; push @cc, $2; } + elsif (!/^Date:\s/ && /^[-A-Za-z]+:\s+\S/) { + push @xh, $_; + } } else { # In the traditional @@ -502,6 +527,7 @@ foreach my $t (@files) { # line 1 = cc # line 2 = subject # So let's support that, too. + $input_format = 'lots'; if (@cc == 0) { printf("(non-mbox) Adding cc: %s from line '%s'\n", $_, $_) unless $quiet; diff --git a/git-svn.perl b/git-svn.perl index 37ecc51787..4a56f1871a 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -2662,11 +2662,12 @@ sub libsvn_connect { } sub libsvn_get_file { - my ($gui, $f, $rev) = @_; + my ($gui, $f, $rev, $chg) = @_; my $p = $f; if (length $SVN_PATH > 0) { return unless ($p =~ s#^\Q$SVN_PATH\E/##); } + print "\t$chg\t$f\n" unless $_q; my ($hash, $pid, $in, $out); my $pool = SVN::Pool->new; @@ -2769,8 +2770,7 @@ sub libsvn_fetch { $pool->clear; } foreach (@amr) { - print "\t$_->[0]\t$_->[1]\n" unless $_q; - libsvn_get_file($gui, $_->[1], $rev) + libsvn_get_file($gui, $_->[1], $rev, $_->[0]); } close $gui or croak $?; return libsvn_log_entry($rev, $author, $date, $msg, [$last_commit]); @@ -2848,8 +2848,7 @@ sub libsvn_traverse { if (defined $files) { push @$files, $file; } else { - print "\tA\t$file\n" unless $_q; - libsvn_get_file($gui, $file, $rev); + libsvn_get_file($gui, $file, $rev, 'A'); } } } @@ -3140,7 +3139,7 @@ sub copy_remote_ref { my $ref = "refs/remotes/$GIT_SVN"; if (safe_qx('git-ls-remote', $origin, $ref)) { sys(qw/git fetch/, $origin, "$ref:$ref"); - } else { + } elsif ($_cp_remote && !$_upgrade) { die "Unable to find remote reference: ", "refs/remotes/$GIT_SVN on $origin\n"; } diff --git a/git-svnimport.perl b/git-svnimport.perl index aca0e4f64e..cbaa8ab37c 100755 --- a/git-svnimport.perl +++ b/git-svnimport.perl @@ -31,7 +31,7 @@ $SIG{'PIPE'}="IGNORE"; $ENV{'TZ'}="UTC"; our($opt_h,$opt_o,$opt_v,$opt_u,$opt_C,$opt_i,$opt_m,$opt_M,$opt_t,$opt_T, - $opt_b,$opt_r,$opt_I,$opt_A,$opt_s,$opt_l,$opt_d,$opt_D,$opt_S,$opt_F); + $opt_b,$opt_r,$opt_I,$opt_A,$opt_s,$opt_l,$opt_d,$opt_D,$opt_S,$opt_F,$opt_P); sub usage() { print STDERR <<END; @@ -39,17 +39,19 @@ Usage: ${\basename $0} # fetch/update GIT from SVN [-o branch-for-HEAD] [-h] [-v] [-l max_rev] [-C GIT_repository] [-t tagname] [-T trunkname] [-b branchname] [-d|-D] [-i] [-u] [-r] [-I ignorefilename] [-s start_chg] - [-m] [-M regex] [-A author_file] [-S] [-F] [SVN_URL] + [-m] [-M regex] [-A author_file] [-S] [-F] [-P project_name] [SVN_URL] END exit(1); } -getopts("A:b:C:dDFhiI:l:mM:o:rs:t:T:Suv") or usage(); +getopts("A:b:C:dDFhiI:l:mM:o:rs:t:T:SP:uv") or usage(); usage if $opt_h; my $tag_name = $opt_t || "tags"; my $trunk_name = $opt_T || "trunk"; my $branch_name = $opt_b || "branches"; +my $project_name = $opt_P || ""; +$project_name = "/" . $project_name if ($project_name); @ARGV == 1 or @ARGV == 2 or usage(); @@ -193,6 +195,13 @@ sub ignore { } } +sub dir_list { + my($self,$path,$rev) = @_; + my ($dirents,undef,$properties) + = $self->{'svn'}->get_dir($path,$rev,undef); + return $dirents; +} + package main; use URI; @@ -342,35 +351,16 @@ if ($opt_A) { open BRANCHES,">>", "$git_dir/svn2git"; -sub node_kind($$$) { - my ($branch, $path, $revision) = @_; +sub node_kind($$) { + my ($svnpath, $revision) = @_; my $pool=SVN::Pool->new; - my $kind = $svn->{'svn'}->check_path(revert_split_path($branch,$path),$revision,$pool); + my $kind = $svn->{'svn'}->check_path($svnpath,$revision,$pool); $pool->clear; return $kind; } -sub revert_split_path($$) { - my($branch,$path) = @_; - - my $svnpath; - $path = "" if $path eq "/"; # this should not happen, but ... - if($branch eq "/") { - $svnpath = "$trunk_name/$path"; - } elsif($branch =~ m#^/#) { - $svnpath = "$tag_name$branch/$path"; - } else { - $svnpath = "$branch_name/$branch/$path"; - } - - $svnpath =~ s#/+$##; - return $svnpath; -} - sub get_file($$$) { - my($rev,$branch,$path) = @_; - - my $svnpath = revert_split_path($branch,$path); + my($svnpath,$rev,$path) = @_; # now get it my ($name,$mode); @@ -413,10 +403,9 @@ sub get_file($$$) { } sub get_ignore($$$$$) { - my($new,$old,$rev,$branch,$path) = @_; + my($new,$old,$rev,$path,$svnpath) = @_; return unless $opt_I; - my $svnpath = revert_split_path($branch,$path); my $name = $svn->ignore("$svnpath",$rev); if ($path eq '/') { $path = $opt_I; @@ -435,11 +424,25 @@ sub get_ignore($$$$$) { close $F; unlink $name; push(@$new,['0644',$sha,$path]); - } else { + } elsif (defined $old) { push(@$old,$path); } } +sub project_path($$) +{ + my ($path, $project) = @_; + + $path = "/".$path unless ($path =~ m#^\/#) ; + return $1 if ($path =~ m#^$project\/(.*)$#); + + $path =~ s#\.#\\\.#g; + $path =~ s#\+#\\\+#g; + return "/" if ($project =~ m#^$path.*$#); + + return undef; +} + sub split_path($$) { my($rev,$path) = @_; my $branch; @@ -459,7 +462,11 @@ sub split_path($$) { print STDERR "$rev: Unrecognized path: $path\n" unless (defined $no_error{$path}); return () } - $path = "/" if $path eq ""; + if ($path eq "") { + $path = "/"; + } elsif ($project_name) { + $path = project_path($path, $project_name); + } return ($branch,$path); } @@ -480,6 +487,27 @@ sub branch_rev($$) { return $therev; } +sub expand_svndir($$$); + +sub expand_svndir($$$) +{ + my ($svnpath, $rev, $path) = @_; + my @list; + get_ignore(\@list, undef, $rev, $path, $svnpath); + my $dirents = $svn->dir_list($svnpath, $rev); + foreach my $p(keys %$dirents) { + my $kind = node_kind($svnpath.'/'.$p, $rev); + if ($kind eq $SVN::Node::file) { + my $f = get_file($svnpath.'/'.$p, $rev, $path.'/'.$p); + push(@list, $f) if $f; + } elsif ($kind eq $SVN::Node::dir) { + push(@list, + expand_svndir($svnpath.'/'.$p, $rev, $path.'/'.$p)); + } + } + return @list; +} + sub copy_path($$$$$$$$) { # Somebody copied a whole subdirectory. # We need to find the index entries from the old version which the @@ -488,8 +516,11 @@ sub copy_path($$$$$$$$) { my($newrev,$newbranch,$path,$oldpath,$rev,$node_kind,$new,$parents) = @_; my($srcbranch,$srcpath) = split_path($rev,$oldpath); - unless(defined $srcbranch) { - print "Path not found when copying from $oldpath @ $rev\n"; + unless(defined $srcbranch && defined $srcpath) { + print "Path not found when copying from $oldpath @ $rev.\n". + "Will try to copy from original SVN location...\n" + if $opt_v; + push (@$new, expand_svndir($oldpath, $rev, $path)); return; } my $therev = branch_rev($srcbranch, $rev); @@ -503,7 +534,7 @@ sub copy_path($$$$$$$$) { } print "$newrev:$newbranch:$path: copying from $srcbranch:$srcpath @ $rev\n" if $opt_v; if ($node_kind eq $SVN::Node::dir) { - $srcpath =~ s#/*$#/#; + $srcpath =~ s#/*$#/#; } my $pid = open my $f,'-|'; @@ -582,10 +613,12 @@ sub commit { if(defined $oldpath) { my $p; ($parent,$p) = split_path($revision,$oldpath); - if($parent eq "/") { - $parent = $opt_o; - } else { - $parent =~ s#^/##; # if it's a tag + if(defined $parent) { + if($parent eq "/") { + $parent = $opt_o; + } else { + $parent =~ s#^/##; # if it's a tag + } } } else { $parent = undef; @@ -651,9 +684,10 @@ sub commit { push(@old,$path); # remove any old stuff } if(($action->[0] eq "A") || ($action->[0] eq "R")) { - my $node_kind = node_kind($branch,$path,$revision); + my $node_kind = node_kind($action->[3], $revision); if ($node_kind eq $SVN::Node::file) { - my $f = get_file($revision,$branch,$path); + my $f = get_file($action->[3], + $revision, $path); if ($f) { push(@new,$f) if $f; } else { @@ -668,19 +702,20 @@ sub commit { \@new, \@parents); } else { get_ignore(\@new, \@old, $revision, - $branch, $path); + $path, $action->[3]); } } } elsif ($action->[0] eq "D") { push(@old,$path); } elsif ($action->[0] eq "M") { - my $node_kind = node_kind($branch,$path,$revision); + my $node_kind = node_kind($action->[3], $revision); if ($node_kind eq $SVN::Node::file) { - my $f = get_file($revision,$branch,$path); + my $f = get_file($action->[3], + $revision, $path); push(@new,$f) if $f; } elsif ($node_kind eq $SVN::Node::dir) { get_ignore(\@new, \@old, $revision, - $branch,$path); + $path, $action->[3]); } } else { die "$revision: unknown action '".$action->[0]."' for $path\n"; @@ -883,6 +918,7 @@ sub commit_all { while(my($path,$action) = each %$changed_paths) { ($branch,$path) = split_path($revision,$path); next if not defined $branch; + next if not defined $path; $done{$branch}{$path} = $action; } while(($branch,$changed_paths) = each %done) { diff --git a/git-tag.sh b/git-tag.sh index a0afa25821..ac269e3277 100755 --- a/git-tag.sh +++ b/git-tag.sh @@ -47,8 +47,10 @@ do -d) shift tag_name="$1" - rm "$GIT_DIR/refs/tags/$tag_name" && \ - echo "Deleted tag $tag_name." + tag=$(git-show-ref --verify --hash -- "refs/tags/$tag_name") || + die "Seriously, what tag are you talking about?" + git-update-ref -m 'tag: delete' -d "refs/tags/$tag_name" "$tag" && + echo "Deleted tag $tag_name." exit $? ;; -*) @@ -63,8 +65,11 @@ done name="$1" [ "$name" ] || usage -if [ -e "$GIT_DIR/refs/tags/$name" -a -z "$force" ]; then - die "tag '$name' already exists" +prev=0000000000000000000000000000000000000000 +if git-show-ref --verify --quiet -- "refs/tags/$name" +then + test -n "$force" || die "tag '$name' already exists" + prev=`git rev-parse "refs/tags/$name"` fi shift git-check-ref-format "tags/$name" || @@ -107,6 +112,5 @@ if [ "$annotate" ]; then object=$(git-mktag < "$GIT_DIR"/TAG_TMP) fi -leading=`expr "refs/tags/$name" : '\(.*\)/'` && -mkdir -p "$GIT_DIR/$leading" && -echo $object > "$GIT_DIR/refs/tags/$name" +git update-ref "refs/tags/$name" "$object" "$prev" + @@ -219,11 +219,14 @@ static void handle_internal_command(int argc, const char **argv, char **envp) int option; } commands[] = { { "add", cmd_add, RUN_SETUP }, + { "annotate", cmd_annotate, }, { "apply", cmd_apply }, { "archive", cmd_archive }, + { "branch", cmd_branch, RUN_SETUP }, { "cat-file", cmd_cat_file, RUN_SETUP }, { "checkout-index", cmd_checkout_index, RUN_SETUP }, { "check-ref-format", cmd_check_ref_format }, + { "cherry", cmd_cherry, RUN_SETUP }, { "commit-tree", cmd_commit_tree, RUN_SETUP }, { "count-objects", cmd_count_objects, RUN_SETUP }, { "diff", cmd_diff, RUN_SETUP | USE_PAGER }, @@ -232,6 +235,7 @@ static void handle_internal_command(int argc, const char **argv, char **envp) { "diff-stages", cmd_diff_stages, RUN_SETUP }, { "diff-tree", cmd_diff_tree, RUN_SETUP }, { "fmt-merge-msg", cmd_fmt_merge_msg, RUN_SETUP }, + { "for-each-ref", cmd_for_each_ref, RUN_SETUP }, { "format-patch", cmd_format_patch, RUN_SETUP }, { "get-tar-commit-id", cmd_get_tar_commit_id }, { "grep", cmd_grep, RUN_SETUP }, @@ -267,6 +271,8 @@ static void handle_internal_command(int argc, const char **argv, char **envp) { "whatchanged", cmd_whatchanged, RUN_SETUP | USE_PAGER }, { "write-tree", cmd_write_tree, RUN_SETUP }, { "verify-pack", cmd_verify_pack }, + { "show-ref", cmd_show_ref, RUN_SETUP }, + { "pack-refs", cmd_pack_refs, RUN_SETUP }, }; int i; diff --git a/gitweb/README b/gitweb/README index 78e6fc05f3..e02e90f042 100644 --- a/gitweb/README +++ b/gitweb/README @@ -26,12 +26,26 @@ You can specify the following configuration variables when building GIT: * GITWEB_LOGO Points to the location where you put git-logo.png on your web server. * GITWEB_CONFIG - This file will be loaded using 'require'. If the environment + This file will be loaded using 'require' and can be used to override any + of the options above as well as some other options - see the top of + 'gitweb.cgi' for their full list and description. If the environment $GITWEB_CONFIG is set when gitweb.cgi is executed the file in the environment variable will be loaded instead of the file specified when gitweb.cgi was created. +Runtime gitweb configuration +---------------------------- + +You can adjust gitweb behaviour using the file specified in `GITWEB_CONFIG` +(defaults to 'gitweb_config.perl' in the same directory as the CGI). +See the top of 'gitweb.cgi' for the list of variables and some description. +The most notable thing that is not configurable at compile time are the +optional features, stored in the '%features' variable. You can find further +description on how to reconfigure the default features setting in your +`GITWEB_CONFIG` or per-project in `project.git/config` inside 'gitweb.cgi'. + + Webserver configuration ----------------------- diff --git a/gitweb/gitweb.css b/gitweb/gitweb.css index 3f62b6d752..0eda98237e 100644 --- a/gitweb/gitweb.css +++ b/gitweb/gitweb.css @@ -333,6 +333,8 @@ div.index_include { } div.search { + font-size: 12px; + font-weight: normal; margin: 4px 8px; position: absolute; top: 56px; diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 05e7b1253b..3c6fd7ca47 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -39,13 +39,20 @@ our $home_link_str = "++GITWEB_HOME_LINK_STR++"; # name of your site or organization to appear in page titles # replace this with something more descriptive for clearer bookmarks -our $site_name = "++GITWEB_SITENAME++" || $ENV{'SERVER_NAME'} || "Untitled"; +our $site_name = "++GITWEB_SITENAME++" + || ($ENV{'SERVER_NAME'} || "Untitled") . " Git"; +# filename of html text to include at top of each page +our $site_header = "++GITWEB_SITE_HEADER++"; # html text to include at home page our $home_text = "++GITWEB_HOMETEXT++"; +# filename of html text to include at bottom of each page +our $site_footer = "++GITWEB_SITE_FOOTER++"; -# URI of default stylesheet -our $stylesheet = "++GITWEB_CSS++"; +# URI of stylesheets +our @stylesheets = ("++GITWEB_CSS++"); +# URI of a single stylesheet, which can be overridden in GITWEB_CONFIG. +our $stylesheet = undef; # URI of GIT logo (72x27 size) our $logo = "++GITWEB_LOGO++"; # URI of GIT favicon, assumed to be image/png type @@ -93,21 +100,81 @@ our %feature = ( # # use gitweb_check_feature(<feature>) to check if <feature> is enabled + # Enable the 'blame' blob view, showing the last commit that modified + # each line in the file. This can be very CPU-intensive. + + # 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; 'blame' => { 'sub' => \&feature_blame, 'override' => 0, 'default' => [0]}, + # Enable the 'snapshot' link, providing a compressed tarball of any + # tree. This can potentially generate high traffic if you have large + # project. + + # 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; 'snapshot' => { 'sub' => \&feature_snapshot, 'override' => 0, # => [content-encoding, suffix, program] 'default' => ['x-gzip', 'gz', 'gzip']}, + # Enable the pickaxe search, which will list the commits that modified + # a given string in a file. This can be practical and quite faster + # alternative to 'blame', but still potentially CPU-intensive. + + # 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; 'pickaxe' => { 'sub' => \&feature_pickaxe, 'override' => 0, 'default' => [1]}, + + # Make gitweb use an alternative format of the URLs which can be + # more readable and natural-looking: project name is embedded + # directly in the path and the query string contains other + # auxiliary information. All gitweb installations recognize + # URL in either format; this configures in which formats gitweb + # generates links. + + # To enable system wide have in $GITWEB_CONFIG + # $feature{'pathinfo'}{'default'} = [1]; + # Project specific override is not supported. + + # Note that you will need to change the default location of CSS, + # favicon, logo and possibly other files to an absolute URL. Also, + # if gitweb.cgi serves as your indexfile, you will need to force + # $my_uri to contain the script name in your $GITWEB_CONFIG. + 'pathinfo' => { + 'override' => 0, + 'default' => [0]}, + + # Make gitweb consider projects in project root subdirectories + # to be forks of existing projects. Given project $projname.git, + # projects matching $projname/*.git will not be shown in the main + # projects list, instead a '+' mark will be added to $projname + # there and a 'forks' view will be enabled for the project, listing + # all the forks. This feature is supported only if project list + # is taken from a directory, not file. + + # To enable system wide have in $GITWEB_CONFIG + # $feature{'forks'}{'default'} = [1]; + # Project specific override is not supported. + 'forks' => { + 'override' => 0, + 'default' => [0]}, ); sub gitweb_check_feature { @@ -118,15 +185,13 @@ sub gitweb_check_feature { $feature{$name}{'override'}, @{$feature{$name}{'default'}}); if (!$override) { return @defaults; } + if (!defined $sub) { + warn "feature $name is not overrideable"; + 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'); @@ -139,12 +204,6 @@ sub feature_blame { 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) = @_; @@ -168,12 +227,6 @@ sub gitweb_have_snapshot { return $have_snapshot; } -# To enable system wide have in $GITWEB_CONFIG -# $feature{'pickaxe'}{'default'} = [1]; -# To have project specific config enable override in $GITWEB_CONFIG -# $feature{'pickaxe'}{'override'} = 1; -# and in project config gitweb.pickaxe = 0|1; - sub feature_pickaxe { my ($val) = git_get_project_config('pickaxe', '--bool'); @@ -186,6 +239,22 @@ sub feature_pickaxe { return ($_[0]); } +# checking HEAD file with -e is fragile if the repository was +# initialized long time ago (i.e. symlink HEAD) and was pack-ref'ed +# and then pruned. +sub check_head_link { + my ($dir) = @_; + my $headfile = "$dir/HEAD"; + return ((-e $headfile) || + (-l $headfile && readlink($headfile) =~ /^refs\/heads\//)); +} + +sub check_export_ok { + my ($dir) = @_; + return (check_head_link($dir) && + (!$export_ok || -e "$dir/$export_ok")); +} + # 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). @@ -218,7 +287,7 @@ our $project = $cgi->param('p'); if (defined $project) { if (!validate_pathname($project) || !(-d "$projectroot/$project") || - !(-e "$projectroot/$project/HEAD") || + !check_head_link("$projectroot/$project") || ($export_ok && !(-e "$projectroot/$project/$export_ok")) || ($strict_export && !project_in_list($project))) { undef $project; @@ -285,6 +354,13 @@ if (defined $searchtext) { $searchtext = quotemeta $searchtext; } +our $searchtype = $cgi->param('st'); +if (defined $searchtype) { + if ($searchtype =~ m/[^a-z]/) { + die_error(undef, "Invalid searchtype parameter"); + } +} + # now read PATH_INFO and use it as alternative to parameters sub evaluate_path_info { return if defined $project; @@ -295,7 +371,7 @@ sub evaluate_path_info { # find which part of PATH_INFO is project $project = $path_info; $project =~ s,/+$,,; - while ($project && !-e "$projectroot/$project/HEAD") { + while ($project && !check_head_link("$projectroot/$project")) { $project =~ s,/*[^/]*$,,; } # validate project @@ -344,11 +420,13 @@ my %actions = ( "commitdiff" => \&git_commitdiff, "commitdiff_plain" => \&git_commitdiff_plain, "commit" => \&git_commit, + "forks" => \&git_forks, "heads" => \&git_heads, "history" => \&git_history, "log" => \&git_log, "rss" => \&git_rss, "search" => \&git_search, + "search_help" => \&git_search_help, "shortlog" => \&git_shortlog, "summary" => \&git_summary, "tag" => \&git_tag, @@ -381,6 +459,10 @@ exit; sub href(%) { my %params = @_; + my $href = $my_uri; + + # XXX: Warning: If you touch this, check the search form for updating, + # too. my @mapping = ( project => "p", @@ -394,11 +476,25 @@ sub href(%) { page => "pg", order => "o", searchtext => "s", + searchtype => "st", ); my %mapping = @mapping; $params{'project'} = $project unless exists $params{'project'}; + my ($use_pathinfo) = gitweb_check_feature('pathinfo'); + if ($use_pathinfo) { + # use PATH_INFO for project name + $href .= "/$params{'project'}" if defined $params{'project'}; + delete $params{'project'}; + + # Summary just uses the project path URL + if (defined $params{'action'} && $params{'action'} eq 'summary') { + delete $params{'action'}; + } + } + + # now encode the parameters explicitly my @result = (); for (my $i = 0; $i < @mapping; $i += 2) { my ($name, $symbol) = ($mapping[$i], $mapping[$i+1]); @@ -406,7 +502,9 @@ sub href(%) { push @result, $symbol . "=" . esc_param($params{$name}); } } - return "$my_uri?" . join(';', @result); + $href .= "?" . join(';', @result) if scalar @result; + + return $href; } @@ -472,12 +570,17 @@ sub esc_url { } # replace invalid utf8 character with SUBSTITUTION sequence -sub esc_html { +sub esc_html ($;%) { my $str = shift; + my %opts = @_; + $str = to_utf8($str); $str = escapeHTML($str); $str =~ s/\014/^L/g; # escape FORM FEED (FF) character (e.g. in COPYING file) $str =~ s/\033/^[/g; # "escape" ESCAPE (\e) character (e.g. commit 20a3847d8a5032ce41f90dcc68abfb36e6fee9b1) + if ($opts{'-nbsp'}) { + $str =~ s/ / /g; + } return $str; } @@ -702,7 +805,7 @@ sub format_diff_line { $diff_class = " incomplete"; } $line = untabify($line); - return "<div class=\"diff$diff_class\">" . esc_html($line) . "</div>\n"; + return "<div class=\"diff$diff_class\">" . esc_html($line, -nbsp=>1) . "</div>\n"; } ## ---------------------------------------------------------------------- @@ -778,7 +881,7 @@ sub git_get_hash_by_path { close $fd or return undef; #'100644 blob 0fa3f3a66fb6a137f6ec2c19351ed4d807070ffa panic.c' - $line =~ m/^([0-9]+) (.+) ([0-9a-fA-F]{40})\t(.+)$/; + $line =~ m/^([0-9]+) (.+) ([0-9a-fA-F]{40})\t/; if (defined $type && $type ne $2) { # type doesn't match return undef; @@ -810,13 +913,21 @@ sub git_get_project_url_list { } sub git_get_projects_list { + my ($filter) = @_; my @list; + $filter ||= ''; + $filter =~ s/\.git$//; + if (-d $projects_list) { # search in directory - my $dir = $projects_list; + my $dir = $projects_list . ($filter ? "/$filter" : ''); + # remove the trailing "/" + $dir =~ s!/+$!!; my $pfxlen = length("$dir"); + my $check_forks = gitweb_check_feature('forks'); + File::Find::find({ follow_fast => 1, # follow symbolic links dangling_symlinks => 0, # ignore dangling symlinks, silently @@ -828,9 +939,10 @@ sub git_get_projects_list { 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 }; + if ($check_forks and $subdir =~ m#/.#) { + $File::Find::prune = 1; + } elsif (check_export_ok("$projectroot/$filter/$subdir")) { + push @list, { path => ($filter ? "$filter/" : '') . $subdir }; $File::Find::prune = 1; } }, @@ -850,8 +962,7 @@ sub git_get_projects_list { if (!defined $path) { next; } - if (-e "$projectroot/$path/HEAD" && (!$export_ok || - -e "$projectroot/$path/$export_ok")) { + if (check_export_ok("$projectroot/$path")) { my $pr = { path => $path, owner => to_utf8($owner), @@ -896,6 +1007,24 @@ sub git_get_project_owner { return $owner; } +sub git_get_last_activity { + my ($path) = @_; + my $fd; + + $git_dir = "$projectroot/$path"; + open($fd, "-|", git_cmd(), 'for-each-ref', + '--format=%(refname) %(committer)', + '--sort=-committerdate', + 'refs/heads') or return; + my $most_recent = <$fd>; + close $fd or return; + if ($most_recent =~ / (\d+) [-+][01]\d\d\d$/) { + my $timestamp = $1; + my $age = time - $timestamp; + return ($age, age_string($age)); + } +} + sub git_get_references { my $type = shift || ""; my %refs; @@ -961,6 +1090,9 @@ sub parse_date { $date{'hour_local'} = $hour; $date{'minute_local'} = $min; $date{'tz_local'} = $tz; + $date{'iso-tz'} = sprintf ("%04d-%02d-%02d %02d:%02d:%02d %s", + 1900+$year, $mon+1, $mday, + $hour, $min, $sec, $tz); return %date; } @@ -1010,7 +1142,9 @@ sub parse_commit { @commit_lines = @$commit_text; } else { local $/ = "\0"; - open my $fd, "-|", git_cmd(), "rev-list", "--header", "--parents", "--max-count=1", $commit_id + 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; @@ -1174,7 +1308,7 @@ sub parse_ls_tree_line ($;%) { my %res; #'100644 blob 0fa3f3a66fb6a137f6ec2c19351ed4d807070ffa panic.c' - $line =~ m/^([0-9]+) (.+) ([0-9a-fA-F]{40})\t(.+)$/; + $line =~ m/^([0-9]+) (.+) ([0-9a-fA-F]{40})\t(.+)$/s; $res{'mode'} = $1; $res{'type'} = $2; @@ -1191,47 +1325,88 @@ sub parse_ls_tree_line ($;%) { ## ...................................................................... ## parse to array of hashes functions -sub git_get_refs_list { - my $type = shift || ""; - my %refs; - my @reflist; +sub git_get_heads_list { + my $limit = shift; + my @headslist; - my @refs; - open my $fd, "-|", $GIT, "peek-remote", "$projectroot/$project/" + open my $fd, '-|', git_cmd(), 'for-each-ref', + ($limit ? '--count='.($limit+1) : ()), '--sort=-committerdate', + '--format=%(objectname) %(refname) %(subject)%00%(committer)', + 'refs/heads' or return; while (my $line = <$fd>) { - chomp $line; - if ($line =~ m/^([0-9a-fA-F]{40})\trefs\/($type\/?([^\^]+))(\^\{\})?$/) { - if (defined $refs{$1}) { - push @{$refs{$1}}, $2; - } else { - $refs{$1} = [ $2 ]; - } + my %ref_item; - if (! $4) { # unpeeled, direct reference - push @refs, { hash => $1, name => $3 }; # without type - } elsif ($3 eq $refs[-1]{'name'}) { - # most likely a tag is followed by its peeled - # (deref) one, and when that happens we know the - # previous one was of type 'tag'. - $refs[-1]{'type'} = "tag"; - } + chomp $line; + my ($refinfo, $committerinfo) = split(/\0/, $line); + my ($hash, $name, $title) = split(' ', $refinfo, 3); + my ($committer, $epoch, $tz) = + ($committerinfo =~ /^(.*) ([0-9]+) (.*)$/); + $name =~ s!^refs/heads/!!; + + $ref_item{'name'} = $name; + $ref_item{'id'} = $hash; + $ref_item{'title'} = $title || '(no commit message)'; + $ref_item{'epoch'} = $epoch; + if ($epoch) { + $ref_item{'age'} = age_string(time - $ref_item{'epoch'}); + } else { + $ref_item{'age'} = "unknown"; } + + push @headslist, \%ref_item; } close $fd; - foreach my $ref (@refs) { - my $ref_file = $ref->{'name'}; - my $ref_id = $ref->{'hash'}; + return wantarray ? @headslist : \@headslist; +} + +sub git_get_tags_list { + my $limit = shift; + my @tagslist; + + open my $fd, '-|', git_cmd(), 'for-each-ref', + ($limit ? '--count='.($limit+1) : ()), '--sort=-creatordate', + '--format=%(objectname) %(objecttype) %(refname) '. + '%(*objectname) %(*objecttype) %(subject)%00%(creator)', + 'refs/tags' + or return; + while (my $line = <$fd>) { + my %ref_item; + + chomp $line; + my ($refinfo, $creatorinfo) = split(/\0/, $line); + my ($id, $type, $name, $refid, $reftype, $title) = split(' ', $refinfo, 6); + my ($creator, $epoch, $tz) = + ($creatorinfo =~ /^(.*) ([0-9]+) (.*)$/); + $name =~ s!^refs/tags/!!; + + $ref_item{'type'} = $type; + $ref_item{'id'} = $id; + $ref_item{'name'} = $name; + if ($type eq "tag") { + $ref_item{'subject'} = $title; + $ref_item{'reftype'} = $reftype; + $ref_item{'refid'} = $refid; + } else { + $ref_item{'reftype'} = $type; + $ref_item{'refid'} = $id; + } - my $type = $ref->{'type'} || git_get_type($ref_id) || next; - my %ref_item = parse_ref($ref_file, $ref_id, $type); + if ($type eq "tag" || $type eq "commit") { + $ref_item{'epoch'} = $epoch; + if ($epoch) { + $ref_item{'age'} = age_string(time - $ref_item{'epoch'}); + } else { + $ref_item{'age'} = "unknown"; + } + } - push @reflist, \%ref_item; + push @tagslist, \%ref_item; } - # sort refs by age - @reflist = sort {$b->{'epoch'} <=> $a->{'epoch'}} @reflist; - return (\@reflist, \%refs); + close $fd; + + return wantarray ? @tagslist : \@tagslist; } ## ---------------------------------------------------------------------- @@ -1328,7 +1503,7 @@ sub git_header_html { my $status = shift || "200 OK"; my $expires = shift; - my $title = "$site_name git"; + my $title = "$site_name"; if (defined $project) { $title .= " - $project"; if (defined $action) { @@ -1366,8 +1541,17 @@ sub git_header_html { <meta name="generator" content="gitweb/$version git/$git_version"/> <meta name="robots" content="index, nofollow"/> <title>$title</title> -<link rel="stylesheet" type="text/css" href="$stylesheet"/> EOF +# print out each stylesheet that exist + if (defined $stylesheet) { +#provides backwards capability for those people who define style sheet in a config file + print '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'"/>'."\n"; + } else { + foreach my $stylesheet (@stylesheets) { + next unless $stylesheet; + print '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'"/>'."\n"; + } + } if (defined $project) { printf('<link rel="alternate" title="%s log" '. 'href="%s" type="application/rss+xml"/>'."\n", @@ -1385,8 +1569,15 @@ EOF } print "</head>\n" . - "<body>\n" . - "<div class=\"page_header\">\n" . + "<body>\n"; + + if (-f $site_header) { + open (my $fd, $site_header); + print <$fd>; + close $fd; + } + + print "<div class=\"page_header\">\n" . $cgi->a({-href => esc_url($logo_url), -title => $logo_label}, qq(<img src="$logo" width="72" height="27" alt="git" class="logo"/>)); @@ -1410,11 +1601,16 @@ EOF } $cgi->param("a", "search"); $cgi->param("h", $search_hash); + $cgi->param("p", $project); print $cgi->startform(-method => "get", -action => $my_uri) . "<div class=\"search\">\n" . $cgi->hidden(-name => "p") . "\n" . $cgi->hidden(-name => "a") . "\n" . $cgi->hidden(-name => "h") . "\n" . + $cgi->popup_menu(-name => 'st', -default => 'commit', + -values => ['commit', 'author', 'committer', 'pickaxe']) . + $cgi->sup($cgi->a({-href => href(action=>"search_help")}, "?")) . + " search:\n", $cgi->textfield(-name => "s", -value => $searchtext) . "\n" . "</div>" . $cgi->end_form() . "\n"; @@ -1437,8 +1633,15 @@ sub git_footer_html { print $cgi->a({-href => href(project=>undef, action=>"project_index"), -class => "rss_logo"}, "TXT") . "\n"; } - print "</div>\n" . - "</body>\n" . + print "</div>\n" ; + + if (-f $site_footer) { + open (my $fd, $site_footer); + print <$fd>; + close $fd; + } + + print "</body>\n" . "</html>"; } @@ -1563,17 +1766,16 @@ sub git_print_page_path { my $type = shift; my $hb = shift; - if (!defined $name) { - print "<div class=\"page_path\">/</div>\n"; - } else { + + print "<div class=\"page_path\">"; + print $cgi->a({-href => href(action=>"tree", hash_base=>$hb), + -title => 'tree root'}, "[$project]"); + print " / "; + if (defined $name) { my @dirname = split '/', $name; my $basename = pop @dirname; my $fullname = ''; - print "<div class=\"page_path\">"; - print $cgi->a({-href => href(action=>"tree", hash_base=>$hb), - -title => 'tree root'}, "[$project]"); - print " / "; foreach my $dir (@dirname) { $fullname .= ($fullname ? '/' : '') . $dir; print $cgi->a({-href => href(action=>"tree", file_name=>$fullname, @@ -1589,11 +1791,12 @@ sub git_print_page_path { 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"; } + print "<br/></div>\n"; } # sub git_print_log (\@;%) { @@ -1646,15 +1849,6 @@ sub git_print_log ($;%) { } } -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) = @_; @@ -1673,16 +1867,18 @@ sub git_print_tree_entry { file_name=>"$basedir$t->{'name'}", %base_key), -class => "list"}, esc_html($t->{'name'})) . "</td>\n"; print "<td class=\"link\">"; + print $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'}, + print " | " . + $cgi->a({-href => href(action=>"blame", hash=>$t->{'hash'}, file_name=>"$basedir$t->{'name'}", %base_key)}, "blame"); } if (defined $hash_base) { - if ($have_blame) { - print " | "; - } - print $cgi->a({-href => href(action=>"history", hash_base=>$hash_base, + print " | " . + $cgi->a({-href => href(action=>"history", hash_base=>$hash_base, hash=>$t->{'hash'}, file_name=>"$basedir$t->{'name'}")}, "history"); } @@ -1699,8 +1895,12 @@ sub git_print_tree_entry { esc_html($t->{'name'})); print "</td>\n"; print "<td class=\"link\">"; + print $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, + print " | " . + $cgi->a({-href => href(action=>"history", hash_base=>$hash_base, file_name=>"$basedir$t->{'name'}")}, "history"); } @@ -1783,6 +1983,9 @@ sub git_difftree_body { print $cgi->a({-href => "#patch$patchno"}, "patch"); print " | "; } + print $cgi->a({-href => href(action=>"blob", hash=>$diff{'from_id'}, + hash_base=>$parent, file_name=>$diff{'file'})}, + "blob") . " | "; print $cgi->a({-href => href(action=>"blame", hash_base=>$parent, file_name=>$diff{'file'})}, "blame") . " | "; @@ -1814,20 +2017,23 @@ sub git_difftree_body { print "</td>\n"; print "<td>$mode_chnge</td>\n"; print "<td class=\"link\">"; - if ($diff{'to_id'} ne $diff{'from_id'}) { # modified - if ($action eq 'commitdiff') { - # link to patch - $patchno++; - print $cgi->a({-href => "#patch$patchno"}, "patch"); - } else { - print $cgi->a({-href => href(action=>"blobdiff", - hash=>$diff{'to_id'}, hash_parent=>$diff{'from_id'}, - hash_base=>$hash, hash_parent_base=>$parent, - file_name=>$diff{'file'})}, - "diff"); - } - print " | "; + if ($action eq 'commitdiff') { + # link to patch + $patchno++; + print $cgi->a({-href => "#patch$patchno"}, "patch") . + " | "; + } elsif ($diff{'to_id'} ne $diff{'from_id'}) { + # "commit" view and modified file (not onlu mode changed) + 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=>"blob", hash=>$diff{'to_id'}, + hash_base=>$hash, file_name=>$diff{'file'})}, + "blob") . " | "; print $cgi->a({-href => href(action=>"blame", hash_base=>$hash, file_name=>$diff{'file'})}, "blame") . " | "; @@ -1854,20 +2060,23 @@ sub git_difftree_body { -class => "list"}, esc_html($diff{'from_file'})) . " with " . (int $diff{'similarity'}) . "% similarity$mode_chng]</span></td>\n" . "<td class=\"link\">"; - if ($diff{'to_id'} ne $diff{'from_id'}) { - if ($action eq 'commitdiff') { - # link to patch - $patchno++; - print $cgi->a({-href => "#patch$patchno"}, "patch"); - } else { - print $cgi->a({-href => href(action=>"blobdiff", - hash=>$diff{'to_id'}, hash_parent=>$diff{'from_id'}, - hash_base=>$hash, hash_parent_base=>$parent, - file_name=>$diff{'to_file'}, file_parent=>$diff{'from_file'})}, - "diff"); - } - print " | "; + if ($action eq 'commitdiff') { + # link to patch + $patchno++; + print $cgi->a({-href => "#patch$patchno"}, "patch") . + " | "; + } elsif ($diff{'to_id'} ne $diff{'from_id'}) { + # "commit" view and modified file (not only pure rename or copy) + 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 $cgi->a({-href => href(action=>"blob", hash=>$diff{'from_id'}, + hash_base=>$parent, file_name=>$diff{'from_file'})}, + "blob") . " | "; print $cgi->a({-href => href(action=>"blame", hash_base=>$parent, file_name=>$diff{'from_file'})}, "blame") . " | "; @@ -1914,13 +2123,6 @@ sub git_patchset_body { } $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, @@ -2007,6 +2209,124 @@ sub git_patchset_body { # . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . +sub git_project_list_body { + my ($projlist, $order, $from, $to, $extra, $no_header) = @_; + + my $check_forks = gitweb_check_feature('forks'); + + my @projects; + foreach my $pr (@$projlist) { + my (@aa) = git_get_last_activity($pr->{'path'}); + unless (@aa) { + next; + } + ($pr->{'age'}, $pr->{'age_string'}) = @aa; + if (!defined $pr->{'descr'}) { + my $descr = git_get_project_description($pr->{'path'}) || ""; + $pr->{'descr'} = chop_str($descr, 25, 5); + } + if (!defined $pr->{'owner'}) { + $pr->{'owner'} = get_file_owner("$projectroot/$pr->{'path'}") || ""; + } + if ($check_forks) { + my $pname = $pr->{'path'}; + $pname =~ s/\.git$//; + $pr->{'forks'} = -d "$projectroot/$pname"; + } + push @projects, $pr; + } + + $order ||= "project"; + $from = 0 unless defined $from; + $to = $#projects if (!defined $to || $#projects < $to); + + print "<table class=\"project_list\">\n"; + unless ($no_header) { + print "<tr>\n"; + if ($check_forks) { + print "<th></th>\n"; + } + if ($order eq "project") { + @projects = sort {$a->{'path'} cmp $b->{'path'}} @projects; + print "<th>Project</th>\n"; + } else { + print "<th>" . + $cgi->a({-href => href(project=>undef, order=>'project'), + -class => "header"}, "Project") . + "</th>\n"; + } + if ($order eq "descr") { + @projects = sort {$a->{'descr'} cmp $b->{'descr'}} @projects; + print "<th>Description</th>\n"; + } else { + print "<th>" . + $cgi->a({-href => href(project=>undef, order=>'descr'), + -class => "header"}, "Description") . + "</th>\n"; + } + if ($order eq "owner") { + @projects = sort {$a->{'owner'} cmp $b->{'owner'}} @projects; + print "<th>Owner</th>\n"; + } else { + print "<th>" . + $cgi->a({-href => href(project=>undef, order=>'owner'), + -class => "header"}, "Owner") . + "</th>\n"; + } + if ($order eq "age") { + @projects = sort {$a->{'age'} <=> $b->{'age'}} @projects; + print "<th>Last Change</th>\n"; + } else { + print "<th>" . + $cgi->a({-href => href(project=>undef, order=>'age'), + -class => "header"}, "Last Change") . + "</th>\n"; + } + print "<th></th>\n" . + "</tr>\n"; + } + my $alternate = 1; + for (my $i = $from; $i <= $to; $i++) { + my $pr = $projects[$i]; + if ($alternate) { + print "<tr class=\"dark\">\n"; + } else { + print "<tr class=\"light\">\n"; + } + $alternate ^= 1; + if ($check_forks) { + print "<td>"; + if ($pr->{'forks'}) { + print $cgi->a({-href => href(project=>$pr->{'path'}, action=>"forks")}, "+"); + } + print "</td>\n"; + } + 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->{'age'}) . "\">" . + $pr->{'age_string'} . "</td>\n" . + "<td class=\"link\">" . + $cgi->a({-href => href(project=>$pr->{'path'}, action=>"summary")}, "summary") . " | " . + $cgi->a({-href => '/git-browser/by-commit.html?r='.$pr->{'path'}}, "graphiclog") . " | " . + $cgi->a({-href => href(project=>$pr->{'path'}, action=>"log")}, "log") . " | " . + $cgi->a({-href => href(project=>$pr->{'path'}, action=>"tree")}, "tree") . + ($pr->{'forks'} ? " | " . $cgi->a({-href => href(project=>$pr->{'path'}, action=>"forks")}, "forks") : '') . + "</td>\n" . + "</tr>\n"; + } + if (defined $extra) { + print "<tr>\n"; + if ($check_forks) { + print "<td></td>\n"; + } + print "<td colspan=\"5\">$extra</td>\n" . + "</tr>\n"; + } + print "</table>\n"; +} + sub git_shortlog_body { # uses global variable $project my ($revlist, $from, $to, $refs, $extra) = @_; @@ -2035,6 +2355,7 @@ sub git_shortlog_body { href(action=>"commit", hash=>$commit), $ref); print "</td>\n" . "<td class=\"link\">" . + $cgi->a({-href => href(action=>"commit", hash=>$commit)}, "commit") . " | " . $cgi->a({-href => href(action=>"commitdiff", hash=>$commit)}, "commitdiff") . " | " . $cgi->a({-href => href(action=>"tree", hash=>$commit, hash_base=>$commit)}, "tree"); if (gitweb_have_snapshot()) { @@ -2126,8 +2447,7 @@ sub git_tags_body { for (my $i = $from; $i <= $to; $i++) { my $entry = $taglist->[$i]; my %tag = %$entry; - my $comment_lines = $tag{'comment'}; - my $comment = shift @$comment_lines; + my $comment = $tag{'subject'}; my $comment_short; if (defined $comment) { $comment_short = chop_str($comment, 30, 5); @@ -2160,7 +2480,7 @@ sub git_tags_body { $cgi->a({-href => href(action=>$tag{'reftype'}, hash=>$tag{'refid'})}, $tag{'reftype'}); if ($tag{'reftype'} eq "commit") { print " | " . $cgi->a({-href => href(action=>"shortlog", hash=>$tag{'name'})}, "shortlog") . - " | " . $cgi->a({-href => href(action=>"log", hash=>$tag{'refid'})}, "log"); + " | " . $cgi->a({-href => href(action=>"log", hash=>$tag{'name'})}, "log"); } elsif ($tag{'reftype'} eq "blob") { print " | " . $cgi->a({-href => href(action=>"blob_plain", hash=>$tag{'refid'})}, "raw"); } @@ -2185,23 +2505,23 @@ sub git_heads_body { my $alternate = 1; for (my $i = $from; $i <= $to; $i++) { my $entry = $headlist->[$i]; - my %tag = %$entry; - my $curr = $tag{'id'} eq $head; + my %ref = %$entry; + my $curr = $ref{'id'} eq $head; if ($alternate) { print "<tr class=\"dark\">\n"; } else { print "<tr class=\"light\">\n"; } $alternate ^= 1; - print "<td><i>$tag{'age'}</i></td>\n" . - ($tag{'id'} eq $head ? "<td class=\"current_head\">" : "<td>") . - $cgi->a({-href => href(action=>"shortlog", hash=>$tag{'name'}), - -class => "list name"},esc_html($tag{'name'})) . + print "<td><i>$ref{'age'}</i></td>\n" . + ($curr ? "<td class=\"current_head\">" : "<td>") . + $cgi->a({-href => href(action=>"shortlog", hash=>$ref{'name'}), + -class => "list name"},esc_html($ref{'name'})) . "</td>\n" . "<td class=\"link\">" . - $cgi->a({-href => href(action=>"shortlog", hash=>$tag{'name'})}, "shortlog") . " | " . - $cgi->a({-href => href(action=>"log", hash=>$tag{'name'})}, "log") . " | " . - $cgi->a({-href => href(action=>"tree", hash=>$tag{'name'}, hash_base=>$tag{'name'})}, "tree") . + $cgi->a({-href => href(action=>"shortlog", hash=>$ref{'name'})}, "shortlog") . " | " . + $cgi->a({-href => href(action=>"log", hash=>$ref{'name'})}, "log") . " | " . + $cgi->a({-href => href(action=>"tree", hash=>$ref{'name'}, hash_base=>$ref{'name'})}, "tree") . "</td>\n" . "</tr>"; } @@ -2224,30 +2544,9 @@ sub git_project_list { } my @list = git_get_projects_list(); - my @projects; if (!@list) { die_error(undef, "No projects found"); } - foreach my $pr (@list) { - my $head = git_get_head_hash($pr->{'path'}); - if (!defined $head) { - next; - } - $git_dir = "$projectroot/$pr->{'path'}"; - my %co = parse_commit($head); - if (!%co) { - next; - } - $pr->{'commit'} = \%co; - if (!defined $pr->{'descr'}) { - my $descr = git_get_project_description($pr->{'path'}) || ""; - $pr->{'descr'} = chop_str($descr, 25, 5); - } - if (!defined $pr->{'owner'}) { - $pr->{'owner'} = get_file_owner("$projectroot/$pr->{'path'}") || ""; - } - push @projects, $pr; - } git_header_html(); if (-f $home_text) { @@ -2257,75 +2556,30 @@ sub git_project_list { close $fd; print "</div>\n"; } - print "<table class=\"project_list\">\n" . - "<tr>\n"; - $order ||= "project"; - if ($order eq "project") { - @projects = sort {$a->{'path'} cmp $b->{'path'}} @projects; - print "<th>Project</th>\n"; - } else { - print "<th>" . - $cgi->a({-href => href(project=>undef, order=>'project'), - -class => "header"}, "Project") . - "</th>\n"; - } - if ($order eq "descr") { - @projects = sort {$a->{'descr'} cmp $b->{'descr'}} @projects; - print "<th>Description</th>\n"; - } else { - print "<th>" . - $cgi->a({-href => href(project=>undef, order=>'descr'), - -class => "header"}, "Description") . - "</th>\n"; - } - if ($order eq "owner") { - @projects = sort {$a->{'owner'} cmp $b->{'owner'}} @projects; - print "<th>Owner</th>\n"; - } else { - print "<th>" . - $cgi->a({-href => href(project=>undef, order=>'owner'), - -class => "header"}, "Owner") . - "</th>\n"; - } - if ($order eq "age") { - @projects = sort {$a->{'commit'}{'age'} <=> $b->{'commit'}{'age'}} @projects; - print "<th>Last Change</th>\n"; - } else { - print "<th>" . - $cgi->a({-href => href(project=>undef, order=>'age'), - -class => "header"}, "Last Change") . - "</th>\n"; + git_project_list_body(\@list, $order); + git_footer_html(); +} + +sub git_forks { + my $order = $cgi->param('o'); + if (defined $order && $order !~ m/project|descr|owner|age/) { + die_error(undef, "Unknown order parameter"); } - print "<th></th>\n" . - "</tr>\n"; - my $alternate = 1; - foreach my $pr (@projects) { - if ($alternate) { - print "<tr class=\"dark\">\n"; - } else { - print "<tr class=\"light\">\n"; - } - $alternate ^= 1; - print "<td>" . $cgi->a({-href => href(project=>$pr->{'path'}, action=>"summary"), - -class => "list"}, esc_html($pr->{'path'})) . "</td>\n" . - "<td>" . esc_html($pr->{'descr'}) . "</td>\n" . - "<td><i>" . chop_str($pr->{'owner'}, 15) . "</i></td>\n"; - print "<td class=\"". age_class($pr->{'commit'}{'age'}) . "\">" . - $pr->{'commit'}{'age_string'} . "</td>\n" . - "<td class=\"link\">" . - $cgi->a({-href => href(project=>$pr->{'path'}, action=>"summary")}, "summary") . " | " . - $cgi->a({-href => href(project=>$pr->{'path'}, action=>"shortlog")}, "shortlog") . " | " . - $cgi->a({-href => href(project=>$pr->{'path'}, action=>"log")}, "log") . " | " . - $cgi->a({-href => href(project=>$pr->{'path'}, action=>"tree")}, "tree") . - "</td>\n" . - "</tr>\n"; + + my @list = git_get_projects_list($project); + if (!@list) { + die_error(undef, "No forks found"); } - print "</table>\n"; + + git_header_html(); + git_print_page_nav('',''); + git_print_header_div('summary', "$project forks"); + git_project_list_body(\@list, $order); git_footer_html(); } sub git_project_index { - my @projects = git_get_projects_list(); + my @projects = git_get_projects_list($project); print $cgi->header( -type => 'text/plain', @@ -2356,17 +2610,12 @@ sub git_summary { my $owner = git_get_project_owner($project); - my ($reflist, $refs) = git_get_refs_list(); - - my @taglist; - my @headlist; - foreach my $ref (@$reflist) { - if ($ref->{'name'} =~ s!^heads/!!) { - push @headlist, $ref; - } else { - $ref->{'name'} =~ s!^tags/!!; - push @taglist, $ref; - } + my $refs = git_get_references(); + my @taglist = git_get_tags_list(15); + my @headlist = git_get_heads_list(15); + my @forklist; + if (gitweb_check_feature('forks')) { + @forklist = git_get_projects_list($project); } git_header_html(); @@ -2389,8 +2638,16 @@ sub git_summary { } print "</table>\n"; + if (-s "$projectroot/$project/README.html") { + if (open my $fd, "$projectroot/$project/README.html") { + print "<div class=\"title\">readme</div>\n"; + print $_ while (<$fd>); + close $fd; + } + } + open my $fd, "-|", git_cmd(), "rev-list", "--max-count=17", - git_get_head_hash($project) + git_get_head_hash($project), "--" or die_error(undef, "Open git-rev-list failed"); my @revlist = map { chomp; $_ } <$fd>; close $fd; @@ -2410,6 +2667,13 @@ sub git_summary { $cgi->a({-href => href(action=>"heads")}, "...")); } + if (@forklist) { + git_print_header_div('forks'); + git_project_list_body(\@forklist, undef, 0, 15, + $cgi->a({-href => href(action=>"forks")}, "..."), + 'noheader'); + } + git_footer_html(); } @@ -2467,7 +2731,8 @@ sub git_blame2 { if ($ftype !~ "blob") { die_error("400 Bad Request", "Object is not a blob"); } - open ($fd, "-|", git_cmd(), "blame", '-l', '--', $file_name, $hash_base) + open ($fd, "-|", git_cmd(), "blame", '-p', '--', + $file_name, $hash_base) or die_error(undef, "Open git-blame failed"); git_header_html(); my $formats_nav = @@ -2491,25 +2756,52 @@ sub git_blame2 { <table class="blame"> <tr><th>Commit</th><th>Line</th><th>Data</th></tr> HTML - while (<$fd>) { - /^([0-9a-fA-F]{40}).*?(\d+)\)\s{1}(\s*.*)/; - my $full_rev = $1; + my %metainfo = (); + while (1) { + $_ = <$fd>; + last unless defined $_; + my ($full_rev, $orig_lineno, $lineno, $group_size) = + /^([0-9a-f]{40}) (\d+) (\d+)(?: (\d+))?$/; + if (!exists $metainfo{$full_rev}) { + $metainfo{$full_rev} = {}; + } + my $meta = $metainfo{$full_rev}; + while (<$fd>) { + last if (s/^\t//); + if (/^(\S+) (.*)$/) { + $meta->{$1} = $2; + } + } + my $data = $_; my $rev = substr($full_rev, 0, 8); - my $lineno = $2; - my $data = $3; - - if (!defined $last_rev) { - $last_rev = $full_rev; - } elsif ($last_rev ne $full_rev) { - $last_rev = $full_rev; + my $author = $meta->{'author'}; + my %date = parse_date($meta->{'author-time'}, + $meta->{'author-tz'}); + my $date = $date{'iso-tz'}; + if ($group_size) { $current_color = ++$current_color % $num_colors; } print "<tr class=\"$rev_color[$current_color]\">\n"; - print "<td class=\"sha1\">" . - $cgi->a({-href => href(action=>"commit", hash=>$full_rev, file_name=>$file_name)}, - esc_html($rev)) . "</td>\n"; - print "<td class=\"linenr\"><a id=\"l$lineno\" href=\"#l$lineno\" class=\"linenr\">" . - esc_html($lineno) . "</a></td>\n"; + if ($group_size) { + print "<td class=\"sha1\""; + print " title=\"". esc_html($author) . ", $date\""; + print " rowspan=\"$group_size\"" if ($group_size > 1); + print ">"; + print $cgi->a({-href => href(action=>"commit", + hash=>$full_rev, + file_name=>$file_name)}, + esc_html($rev)); + print "</td>\n"; + } + my $blamed = href(action => 'blame', + file_name => $meta->{'filename'}, + hash_base => $full_rev); + print "<td class=\"linenr\">"; + print $cgi->a({ -href => "$blamed#l$orig_lineno", + -id => "l$lineno", + -class => "linenr" }, + esc_html($lineno)); + print "</td>"; print "<td class=\"pre\">" . esc_html($data) . "</td>\n"; print "</tr>\n"; } @@ -2623,9 +2915,9 @@ sub git_tags { git_print_page_nav('','', $head,undef,$head); git_print_header_div('summary', $project); - my ($taglist) = git_get_refs_list("tags"); - if (@$taglist) { - git_tags_body($taglist); + my @tagslist = git_get_tags_list(); + if (@tagslist) { + git_tags_body(\@tagslist); } git_footer_html(); } @@ -2636,9 +2928,9 @@ sub git_heads { git_print_page_nav('','', $head,undef,$head); git_print_header_div('summary', $project); - my ($headlist) = git_get_refs_list("heads"); - if (@$headlist) { - git_heads_body($headlist, $head); + my @headslist = git_get_heads_list(); + if (@headslist) { + git_heads_body(\@headslist, $head); } git_footer_html(); } @@ -2751,7 +3043,7 @@ sub git_blob { $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); + $nr, $nr, $nr, esc_html($line, -nbsp=>1); } close $fd or print "Reading blob failed.\n"; @@ -2819,6 +3111,30 @@ sub git_tree { print "<div class=\"page_body\">\n"; print "<table cellspacing=\"0\">\n"; my $alternate = 1; + # '..' (top directory) link if possible + if (defined $hash_base && + defined $file_name && $file_name =~ m![^/]+$!) { + if ($alternate) { + print "<tr class=\"dark\">\n"; + } else { + print "<tr class=\"light\">\n"; + } + $alternate ^= 1; + + my $up = $file_name; + $up =~ s!/?[^/]+$!!; + undef $up unless $up; + # based on git_print_tree_entry + print '<td class="mode">' . mode_str('040000') . "</td>\n"; + print '<td class="list">'; + print $cgi->a({-href => href(action=>"tree", hash_base=>$hash_base, + file_name=>$up)}, + ".."); + print "</td>\n"; + print "<td class=\"link\"></td>\n"; + + print "</tr>\n"; + } foreach my $line (@entries) { my %t = parse_ls_tree_line($line, -z => 1); @@ -2881,7 +3197,7 @@ sub git_log { my $refs = git_get_references(); my $limit = sprintf("--max-count=%i", (100 * ($page+1))); - open my $fd, "-|", git_cmd(), "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; @@ -2920,7 +3236,7 @@ sub git_log { "</div>\n"; print "<div class=\"log_body\">\n"; - git_print_simplified_log($co{'comment'}); + git_print_log($co{'comment'}, -final_empty_line=> 1); print "</div>\n"; } git_footer_html(); @@ -2938,7 +3254,8 @@ sub git_commit { if (!defined $parent) { $parent = "--root"; } - open my $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts, $parent, $hash + open my $fd, "-|", git_cmd(), "diff-tree", '-r', "--no-commit-id", + @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"); @@ -3043,7 +3360,8 @@ sub git_blobdiff { 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, + 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>; @@ -3057,7 +3375,8 @@ sub git_blobdiff { # try to find filename from $hash # read filtered raw output - open $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts, $hash_parent_base, $hash_base + 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' @@ -3127,7 +3446,8 @@ sub git_blobdiff { } # open patch output - open $fd, "-|", git_cmd(), "diff", '-p', @diff_opts, $hash_parent, $hash + 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") @@ -3204,6 +3524,51 @@ sub git_commitdiff { if (!%co) { die_error(undef, "Unknown commit object"); } + + # we need to prepare $formats_nav before any parameter munging + my $formats_nav; + if ($format eq 'html') { + $formats_nav = + $cgi->a({-href => href(action=>"commitdiff_plain", + hash=>$hash, hash_parent=>$hash_parent)}, + "raw"); + + if (defined $hash_parent) { + # commitdiff with two commits given + my $hash_parent_short = $hash_parent; + if ($hash_parent =~ m/^[0-9a-fA-F]{40}$/) { + $hash_parent_short = substr($hash_parent, 0, 7); + } + $formats_nav .= + ' (from: ' . + $cgi->a({-href => href(action=>"commitdiff", + hash=>$hash_parent)}, + esc_html($hash_parent_short)) . + ')'; + } elsif (!$co{'parent'}) { + # --root commitdiff + $formats_nav .= ' (initial)'; + } elsif (scalar @{$co{'parents'}} == 1) { + # single parent commit + $formats_nav .= + ' (parent: ' . + $cgi->a({-href => href(action=>"commitdiff", + hash=>$co{'parent'})}, + esc_html(substr($co{'parent'}, 0, 7))) . + ')'; + } else { + # merge commit + $formats_nav .= + ' (merge: ' . + join(' ', map { + $cgi->a({-href => href(action=>"commitdiff", + hash=>$_)}, + esc_html(substr($_, 0, 7))); + } @{$co{'parents'}} ) . + ')'; + } + } + if (!defined $hash_parent) { $hash_parent = $co{'parent'} || '--root'; } @@ -3213,7 +3578,8 @@ sub git_commitdiff { my @difftree; if ($format eq 'html') { open $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts, - "--patch-with-raw", "--full-index", $hash_parent, $hash + "--no-commit-id", "--patch-with-raw", "--full-index", + $hash_parent, $hash, "--" or die_error(undef, "Open git-diff-tree failed"); while (chomp(my $line = <$fd>)) { @@ -3224,7 +3590,7 @@ sub git_commitdiff { } elsif ($format eq 'plain') { open $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts, - '-p', $hash_parent, $hash + '-p', $hash_parent, $hash, "--" or die_error(undef, "Open git-diff-tree failed"); } else { @@ -3241,19 +3607,17 @@ sub git_commitdiff { if ($format eq 'html') { my $refs = git_get_references(); my $ref = format_ref_marker($refs, $co{'id'}); - my $formats_nav = - $cgi->a({-href => href(action=>"commitdiff_plain", - hash=>$hash, hash_parent=>$hash_parent)}, - "raw"); git_header_html(undef, $expires); git_print_page_nav('commitdiff','', $hash,$co{'tree'},$hash, $formats_nav); git_print_header_div('commit', esc_html($co{'title'}) . $ref, $hash); git_print_authorship(\%co); print "<div class=\"page_body\">\n"; - print "<div class=\"log\">\n"; - git_print_simplified_log($co{'comment'}, 1); # skip title - print "</div>\n"; # class="log" + if (@{$co{'comment'}} > 1) { + print "<div class=\"log\">\n"; + git_print_log($co{'comment'}, -final_empty_line=> 1, -remove_title => 1); + print "</div>\n"; # class="log" + } } elsif ($format eq 'plain') { my $refs = git_get_references("tags"); @@ -3385,18 +3749,8 @@ sub git_search { die_error(undef, "Unknown commit object"); } - my $commit_search = 1; - my $author_search = 0; - my $committer_search = 0; - my $pickaxe_search = 0; - if ($searchtext =~ s/^author\\://i) { - $author_search = 1; - } elsif ($searchtext =~ s/^committer\\://i) { - $committer_search = 1; - } elsif ($searchtext =~ s/^pickaxe\\://i) { - $commit_search = 0; - $pickaxe_search = 1; - + $searchtype ||= 'commit'; + if ($searchtype eq 'pickaxe') { # 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'); @@ -3404,23 +3758,26 @@ sub git_search { die_error('403 Permission denied', "Permission denied"); } } + git_header_html(); git_print_page_nav('','', $hash,$co{'tree'},$hash); git_print_header_div('commit', esc_html($co{'title'}), $hash); print "<table cellspacing=\"0\">\n"; my $alternate = 1; - if ($commit_search) { + if ($searchtype eq 'commit' or $searchtype eq 'author' or $searchtype eq 'committer') { $/ = "\0"; - open my $fd, "-|", git_cmd(), "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; } - if ($author_search && !grep m/\nauthor .*$searchtext/i, $commit_text) { + if ($searchtype eq 'author' && !grep m/\nauthor .*$searchtext/i, $commit_text) { next; } - if ($committer_search && !grep m/\ncommitter .*$searchtext/i, $commit_text) { + if ($searchtype eq 'committer' && !grep m/\ncommitter .*$searchtext/i, $commit_text) { next; } my @commit_lines = split "\n", $commit_text; @@ -3462,7 +3819,7 @@ sub git_search { close $fd; } - if ($pickaxe_search) { + if ($searchtype eq 'pickaxe') { $/ = "\n"; my $git_command = git_cmd_str(); open my $fd, "-|", "$git_command rev-list $hash | " . @@ -3522,6 +3879,31 @@ sub git_search { git_footer_html(); } +sub git_search_help { + git_header_html(); + git_print_page_nav('','', $hash,$hash,$hash); + print <<EOT; +<dl> +<dt><b>commit</b></dt> +<dd>The commit messages and authorship information will be scanned for the given string.</dd> +<dt><b>author</b></dt> +<dd>Name and e-mail of the change author and date of birth of the patch will be scanned for the given string.</dd> +<dt><b>committer</b></dt> +<dd>Name and e-mail of the committer and date of commit will be scanned for the given string.</dd> +EOT + my ($have_pickaxe) = gitweb_check_feature('pickaxe'); + if ($have_pickaxe) { + print <<EOT; +<dt><b>pickaxe</b></dt> +<dd>All commits that caused the string to appear or disappear from any file (changes that +added, removed or "modified" the string) will be listed. This search can take a while and +takes a lot of strain on the server, so please use it wisely.</dd> +EOT + } + print "</dl>\n"; + git_footer_html(); +} + sub git_shortlog { my $head = git_get_head_hash($project); if (!defined $hash) { @@ -3533,7 +3915,7 @@ sub git_shortlog { my $refs = git_get_references(); my $limit = sprintf("--max-count=%i", (100 * ($page+1))); - open my $fd, "-|", git_cmd(), "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; @@ -3561,7 +3943,8 @@ sub git_shortlog { sub git_rss { # http://www.notestips.com/80256B3A007F2692/1/NAMO5P9UPQ - open my $fd, "-|", git_cmd(), "rev-list", "--max-count=150", git_get_head_hash($project) + 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"); @@ -3585,7 +3968,7 @@ XML } my %cd = parse_date($co{'committer_epoch'}); open $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts, - $co{'parent'}, $co{'id'} + $co{'parent'}, $co{'id'}, "--" or next; my @difftree = map { chomp; $_ } <$fd>; close $fd @@ -3630,7 +4013,7 @@ sub git_opml { <?xml version="1.0" encoding="utf-8"?> <opml version="1.0"> <head> - <title>$site_name Git OPML Export</title> + <title>$site_name OPML Export</title> </head> <body> <outline text="git RSS feeds"> @@ -34,7 +34,7 @@ static void compile_regexp(struct grep_pat *p, struct grep_opt *opt) } } -static struct grep_expr *compile_pattern_expr(struct grep_pat **); +static struct grep_expr *compile_pattern_or(struct grep_pat **); static struct grep_expr *compile_pattern_atom(struct grep_pat **list) { struct grep_pat *p; @@ -52,7 +52,7 @@ static struct grep_expr *compile_pattern_atom(struct grep_pat **list) return x; case GREP_OPEN_PAREN: *list = p->next; - x = compile_pattern_expr(list); + x = compile_pattern_or(list); if (!x) return NULL; if (!*list || (*list)->token != GREP_CLOSE_PAREN) @@ -138,6 +138,9 @@ void compile_grep_patterns(struct grep_opt *opt) { struct grep_pat *p; + if (opt->all_match) + opt->extended = 1; + for (p = opt->pattern_list; p; p = p->next) { switch (p->token) { case GREP_PATTERN: /* atom */ @@ -309,40 +312,63 @@ static int match_one_pattern(struct grep_opt *opt, struct grep_pat *p, char *bol return hit; } -static int match_expr_eval(struct grep_opt *opt, +static int match_expr_eval(struct grep_opt *o, struct grep_expr *x, char *bol, char *eol, - enum grep_context ctx) + enum grep_context ctx, + int collect_hits) { + int h = 0; + switch (x->node) { case GREP_NODE_ATOM: - return match_one_pattern(opt, x->u.atom, bol, eol, ctx); + h = match_one_pattern(o, x->u.atom, bol, eol, ctx); break; case GREP_NODE_NOT: - return !match_expr_eval(opt, x->u.unary, bol, eol, ctx); + h = !match_expr_eval(o, x->u.unary, bol, eol, ctx, 0); + break; case GREP_NODE_AND: - return (match_expr_eval(opt, x->u.binary.left, bol, eol, ctx) && - match_expr_eval(opt, x->u.binary.right, bol, eol, ctx)); + if (!collect_hits) + return (match_expr_eval(o, x->u.binary.left, + bol, eol, ctx, 0) && + match_expr_eval(o, x->u.binary.right, + bol, eol, ctx, 0)); + h = match_expr_eval(o, x->u.binary.left, bol, eol, ctx, 0); + h &= match_expr_eval(o, x->u.binary.right, bol, eol, ctx, 0); + break; case GREP_NODE_OR: - return (match_expr_eval(opt, x->u.binary.left, bol, eol, ctx) || - match_expr_eval(opt, x->u.binary.right, bol, eol, ctx)); + if (!collect_hits) + return (match_expr_eval(o, x->u.binary.left, + bol, eol, ctx, 0) || + match_expr_eval(o, x->u.binary.right, + bol, eol, ctx, 0)); + h = match_expr_eval(o, x->u.binary.left, bol, eol, ctx, 0); + x->u.binary.left->hit |= h; + h |= match_expr_eval(o, x->u.binary.right, bol, eol, ctx, 1); + break; + default: + die("Unexpected node type (internal error) %d\n", x->node); } - die("Unexpected node type (internal error) %d\n", x->node); + if (collect_hits) + x->hit |= h; + return h; } static int match_expr(struct grep_opt *opt, char *bol, char *eol, - enum grep_context ctx) + enum grep_context ctx, int collect_hits) { struct grep_expr *x = opt->pattern_expression; - return match_expr_eval(opt, x, bol, eol, ctx); + return match_expr_eval(opt, x, bol, eol, ctx, collect_hits); } static int match_line(struct grep_opt *opt, char *bol, char *eol, - enum grep_context ctx) + enum grep_context ctx, int collect_hits) { struct grep_pat *p; if (opt->extended) - return match_expr(opt, bol, eol, ctx); + return match_expr(opt, bol, eol, ctx, collect_hits); + + /* we do not call with collect_hits without being extended */ for (p = opt->pattern_list; p; p = p->next) { if (match_one_pattern(opt, p, bol, eol, ctx)) return 1; @@ -350,7 +376,8 @@ static int match_line(struct grep_opt *opt, char *bol, char *eol, return 0; } -int grep_buffer(struct grep_opt *opt, const char *name, char *buf, unsigned long size) +static int grep_buffer_1(struct grep_opt *opt, const char *name, + char *buf, unsigned long size, int collect_hits) { char *bol = buf; unsigned long left = size; @@ -386,7 +413,7 @@ int grep_buffer(struct grep_opt *opt, const char *name, char *buf, unsigned long while (left) { char *eol, ch; - int hit = 0; + int hit; eol = end_of_line(bol, &left); ch = *eol; @@ -395,9 +422,12 @@ int grep_buffer(struct grep_opt *opt, const char *name, char *buf, unsigned long if ((ctx == GREP_CONTEXT_HEAD) && (eol == bol)) ctx = GREP_CONTEXT_BODY; - hit = match_line(opt, bol, eol, ctx); + hit = match_line(opt, bol, eol, ctx, collect_hits); *eol = ch; + if (collect_hits) + goto next_line; + /* "grep -v -e foo -e bla" should list lines * that do not have either, so inversion should * be done outside. @@ -477,6 +507,8 @@ int grep_buffer(struct grep_opt *opt, const char *name, char *buf, unsigned long } free(prev); + if (collect_hits) + return 0; if (opt->status_only) return 0; @@ -496,3 +528,49 @@ int grep_buffer(struct grep_opt *opt, const char *name, char *buf, unsigned long return !!last_hit; } +static void clr_hit_marker(struct grep_expr *x) +{ + /* All-hit markers are meaningful only at the very top level + * OR node. + */ + while (1) { + x->hit = 0; + if (x->node != GREP_NODE_OR) + return; + x->u.binary.left->hit = 0; + x = x->u.binary.right; + } +} + +static int chk_hit_marker(struct grep_expr *x) +{ + /* Top level nodes have hit markers. See if they all are hits */ + while (1) { + if (x->node != GREP_NODE_OR) + return x->hit; + if (!x->u.binary.left->hit) + return 0; + x = x->u.binary.right; + } +} + +int grep_buffer(struct grep_opt *opt, const char *name, char *buf, unsigned long size) +{ + /* + * we do not have to do the two-pass grep when we do not check + * buffer-wide "all-match". + */ + if (!opt->all_match) + return grep_buffer_1(opt, name, buf, size, 0); + + /* Otherwise the toplevel "or" terms hit a bit differently. + * We first clear hit markers from them. + */ + clr_hit_marker(opt->pattern_expression); + grep_buffer_1(opt, name, buf, size, 1); + + if (!chk_hit_marker(opt->pattern_expression)) + return 0; + + return grep_buffer_1(opt, name, buf, size, 0); +} @@ -35,6 +35,7 @@ enum grep_expr_node { struct grep_expr { enum grep_expr_node node; + unsigned hit; union { struct grep_pat *atom; struct grep_expr *unary; @@ -59,6 +60,7 @@ struct grep_opt { unsigned count:1; unsigned word_regexp:1; unsigned fixed:1; + unsigned all_match:1; #define GREP_BINARY_DEFAULT 0 #define GREP_BINARY_NOMATCH 1 #define GREP_BINARY_TEXT 2 diff --git a/http-push.c b/http-push.c index 670ff007be..ecefdfd4f8 100644 --- a/http-push.c +++ b/http-push.c @@ -1864,7 +1864,7 @@ static int update_remote(unsigned char *sha1, struct remote_lock *lock) static struct ref *local_refs, **local_tail; static struct ref *remote_refs, **remote_tail; -static int one_local_ref(const char *refname, const unsigned char *sha1) +static int one_local_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data) { struct ref *ref; int len = strlen(refname) + 1; @@ -1913,7 +1913,7 @@ static void one_remote_ref(char *refname) static void get_local_heads(void) { local_tail = &local_refs; - for_each_ref(one_local_ref); + for_each_ref(one_local_ref, NULL); } static void get_dav_remote_heads(void) diff --git a/index-pack.c b/index-pack.c index 80bc6cb45b..70640e14d8 100644 --- a/index-pack.c +++ b/index-pack.c @@ -13,63 +13,93 @@ static const char index_pack_usage[] = struct object_entry { unsigned long offset; + unsigned long size; + unsigned int hdr_size; enum object_type type; enum object_type real_type; unsigned char sha1[20]; }; +union delta_base { + unsigned char sha1[20]; + unsigned long offset; +}; + +/* + * Even if sizeof(union delta_base) == 24 on 64-bit archs, we really want + * to memcmp() only the first 20 bytes. + */ +#define UNION_BASE_SZ 20 + struct delta_entry { struct object_entry *obj; - unsigned char base_sha1[20]; + union delta_base base; }; static const char *pack_name; -static unsigned char *pack_base; -static unsigned long pack_size; static struct object_entry *objects; static struct delta_entry *deltas; static int nr_objects; static int nr_deltas; -static void open_pack_file(void) +/* We always read in 4kB chunks. */ +static unsigned char input_buffer[4096]; +static unsigned long input_offset, input_len, consumed_bytes; +static SHA_CTX input_ctx; +static int input_fd; + +/* + * Make sure at least "min" bytes are available in the buffer, and + * return the pointer to the buffer. + */ +static void *fill(int min) { - int fd; - struct stat st; + if (min <= input_len) + return input_buffer + input_offset; + if (min > sizeof(input_buffer)) + die("cannot fill %d bytes", min); + if (input_offset) { + SHA1_Update(&input_ctx, input_buffer, input_offset); + memmove(input_buffer, input_buffer + input_offset, input_len); + input_offset = 0; + } + do { + int ret = xread(input_fd, input_buffer + input_len, + sizeof(input_buffer) - input_len); + if (ret <= 0) { + if (!ret) + die("early EOF"); + die("read error on input: %s", strerror(errno)); + } + input_len += ret; + } while (input_len < min); + return input_buffer; +} + +static void use(int bytes) +{ + if (bytes > input_len) + die("used more bytes than were available"); + input_len -= bytes; + input_offset += bytes; + consumed_bytes += bytes; +} - fd = open(pack_name, O_RDONLY); - if (fd < 0) +static void open_pack_file(void) +{ + input_fd = open(pack_name, O_RDONLY); + if (input_fd < 0) die("cannot open packfile '%s': %s", pack_name, strerror(errno)); - if (fstat(fd, &st)) { - int err = errno; - close(fd); - die("cannot fstat packfile '%s': %s", pack_name, - strerror(err)); - } - pack_size = st.st_size; - pack_base = mmap(NULL, pack_size, PROT_READ, MAP_PRIVATE, fd, 0); - if (pack_base == MAP_FAILED) { - int err = errno; - close(fd); - die("cannot mmap packfile '%s': %s", pack_name, - strerror(err)); - } - close(fd); + SHA1_Init(&input_ctx); } static void parse_pack_header(void) { - const struct pack_header *hdr; - unsigned char sha1[20]; - SHA_CTX ctx; - - /* Ensure there are enough bytes for the header and final SHA1 */ - if (pack_size < sizeof(struct pack_header) + 20) - die("packfile '%s' is too small", pack_name); + struct pack_header *hdr = fill(sizeof(struct pack_header)); /* Header consistency check */ - hdr = (void *)pack_base; if (hdr->hdr_signature != htonl(PACK_SIGNATURE)) die("packfile '%s' signature mismatch", pack_name); if (!pack_version_ok(hdr->hdr_version)) @@ -77,13 +107,8 @@ static void parse_pack_header(void) pack_name, ntohl(hdr->hdr_version)); nr_objects = ntohl(hdr->hdr_entries); - - /* Check packfile integrity */ - SHA1_Init(&ctx); - SHA1_Update(&ctx, pack_base, pack_size - 20); - SHA1_Final(sha1, &ctx); - if (hashcmp(sha1, pack_base + pack_size - 20)) - die("packfile '%s' SHA1 mismatch", pack_name); + use(sizeof(struct pack_header)); + /*fprintf(stderr, "Indexing %d objects\n", nr_objects);*/ } static void bad_object(unsigned long offset, const char *format, @@ -101,86 +126,121 @@ static void bad_object(unsigned long offset, const char *format, ...) pack_name, offset, buf); } -static void *unpack_entry_data(unsigned long offset, - unsigned long *current_pos, unsigned long size) +static void *unpack_entry_data(unsigned long offset, unsigned long size) { - unsigned long pack_limit = pack_size - 20; - unsigned long pos = *current_pos; z_stream stream; void *buf = xmalloc(size); memset(&stream, 0, sizeof(stream)); stream.next_out = buf; stream.avail_out = size; - stream.next_in = pack_base + pos; - stream.avail_in = pack_limit - pos; + stream.next_in = fill(1); + stream.avail_in = input_len; inflateInit(&stream); for (;;) { int ret = inflate(&stream, 0); - if (ret == Z_STREAM_END) + use(input_len - stream.avail_in); + if (stream.total_out == size && ret == Z_STREAM_END) break; if (ret != Z_OK) bad_object(offset, "inflate returned %d", ret); + stream.next_in = fill(1); + stream.avail_in = input_len; } inflateEnd(&stream); - if (stream.total_out != size) - bad_object(offset, "size mismatch (expected %lu, got %lu)", - size, stream.total_out); - *current_pos = pack_limit - stream.avail_in; return buf; } -static void *unpack_raw_entry(unsigned long offset, - enum object_type *obj_type, - unsigned long *obj_size, - unsigned char *delta_base, - unsigned long *next_obj_offset) +static void *unpack_raw_entry(struct object_entry *obj, union delta_base *delta_base) { - unsigned long pack_limit = pack_size - 20; - unsigned long pos = offset; - unsigned char c; - unsigned long size; + unsigned char *p, c; + unsigned long size, base_offset; unsigned shift; - enum object_type type; - void *data; - c = pack_base[pos++]; - type = (c >> 4) & 7; + obj->offset = consumed_bytes; + + p = fill(1); + c = *p; + use(1); + obj->type = (c >> 4) & 7; size = (c & 15); shift = 4; while (c & 0x80) { - if (pos >= pack_limit) - bad_object(offset, "object extends past end of pack"); - c = pack_base[pos++]; + p = fill(1); + c = *p; + use(1); size += (c & 0x7fUL) << shift; shift += 7; } + obj->size = size; - switch (type) { - case OBJ_DELTA: - if (pos + 20 >= pack_limit) - bad_object(offset, "object extends past end of pack"); - hashcpy(delta_base, pack_base + pos); - pos += 20; - /* fallthru */ + switch (obj->type) { + case OBJ_REF_DELTA: + hashcpy(delta_base->sha1, fill(20)); + use(20); + break; + case OBJ_OFS_DELTA: + memset(delta_base, 0, sizeof(*delta_base)); + p = fill(1); + c = *p; + use(1); + base_offset = c & 127; + while (c & 128) { + base_offset += 1; + if (!base_offset || base_offset & ~(~0UL >> 7)) + bad_object(obj->offset, "offset value overflow for delta base object"); + p = fill(1); + c = *p; + use(1); + base_offset = (base_offset << 7) + (c & 127); + } + delta_base->offset = obj->offset - base_offset; + if (delta_base->offset >= obj->offset) + bad_object(obj->offset, "delta base offset is out of bound"); + break; case OBJ_COMMIT: case OBJ_TREE: case OBJ_BLOB: case OBJ_TAG: - data = unpack_entry_data(offset, &pos, size); break; default: - bad_object(offset, "bad object type %d", type); + bad_object(obj->offset, "bad object type %d", obj->type); } + obj->hdr_size = consumed_bytes - obj->offset; - *obj_type = type; - *obj_size = size; - *next_obj_offset = pos; + return unpack_entry_data(obj->offset, obj->size); +} + +static void * get_data_from_pack(struct object_entry *obj) +{ + unsigned long from = obj[0].offset + obj[0].hdr_size; + unsigned long len = obj[1].offset - from; + unsigned pg_offset = from % getpagesize(); + unsigned char *map, *data; + z_stream stream; + int st; + + map = mmap(NULL, len + pg_offset, PROT_READ, MAP_PRIVATE, + input_fd, from - pg_offset); + if (map == MAP_FAILED) + die("cannot mmap packfile '%s': %s", pack_name, strerror(errno)); + data = xmalloc(obj->size); + memset(&stream, 0, sizeof(stream)); + stream.next_out = data; + stream.avail_out = obj->size; + stream.next_in = map + pg_offset; + stream.avail_in = len; + inflateInit(&stream); + while ((st = inflate(&stream, Z_FINISH)) == Z_OK); + inflateEnd(&stream); + if (st != Z_STREAM_END || stream.total_out != obj->size) + die("serious inflate inconsistency"); + munmap(map, len + pg_offset); return data; } -static int find_delta(const unsigned char *base_sha1) +static int find_delta(const union delta_base *base) { int first = 0, last = nr_deltas; @@ -189,7 +249,7 @@ static int find_delta(const unsigned char *base_sha1) struct delta_entry *delta = &deltas[next]; int cmp; - cmp = hashcmp(base_sha1, delta->base_sha1); + cmp = memcmp(base, &delta->base, UNION_BASE_SZ); if (!cmp) return next; if (cmp < 0) { @@ -201,18 +261,18 @@ static int find_delta(const unsigned char *base_sha1) return -first-1; } -static int find_deltas_based_on_sha1(const unsigned char *base_sha1, - int *first_index, int *last_index) +static int find_delta_childs(const union delta_base *base, + int *first_index, int *last_index) { - int first = find_delta(base_sha1); + int first = find_delta(base); int last = first; int end = nr_deltas - 1; if (first < 0) return -1; - while (first > 0 && !hashcmp(deltas[first - 1].base_sha1, base_sha1)) + while (first > 0 && !memcmp(&deltas[first - 1].base, base, UNION_BASE_SZ)) --first; - while (last < end && !hashcmp(deltas[last + 1].base_sha1, base_sha1)) + while (last < end && !memcmp(&deltas[last + 1].base, base, UNION_BASE_SZ)) ++last; *first_index = first; *last_index = last; @@ -252,25 +312,34 @@ static void resolve_delta(struct delta_entry *delta, void *base_data, unsigned long delta_size; void *result; unsigned long result_size; - enum object_type delta_type; - unsigned char base_sha1[20]; - unsigned long next_obj_offset; + union delta_base delta_base; int j, first, last; obj->real_type = type; - delta_data = unpack_raw_entry(obj->offset, &delta_type, - &delta_size, base_sha1, - &next_obj_offset); + delta_data = get_data_from_pack(obj); + delta_size = obj->size; result = patch_delta(base_data, base_size, delta_data, delta_size, &result_size); free(delta_data); if (!result) bad_object(obj->offset, "failed to apply delta"); sha1_object(result, result_size, type, obj->sha1); - if (!find_deltas_based_on_sha1(obj->sha1, &first, &last)) { + + hashcpy(delta_base.sha1, obj->sha1); + if (!find_delta_childs(&delta_base, &first, &last)) { + for (j = first; j <= last; j++) + if (deltas[j].obj->type == OBJ_REF_DELTA) + resolve_delta(&deltas[j], result, result_size, type); + } + + memset(&delta_base, 0, sizeof(delta_base)); + delta_base.offset = obj->offset; + if (!find_delta_childs(&delta_base, &first, &last)) { for (j = first; j <= last; j++) - resolve_delta(&deltas[j], result, result_size, type); + if (deltas[j].obj->type == OBJ_OFS_DELTA) + resolve_delta(&deltas[j], result, result_size, type); } + free(result); } @@ -278,16 +347,16 @@ 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 hashcmp(delta_a->base_sha1, delta_b->base_sha1); + return memcmp(&delta_a->base, &delta_b->base, UNION_BASE_SZ); } -static void parse_pack_objects(void) +/* Parse all objects and return the pack content SHA1 hash */ +static void parse_pack_objects(unsigned char *sha1) { int i; - unsigned long offset = sizeof(struct pack_header); - unsigned char base_sha1[20]; + struct delta_entry *delta = deltas; void *data; - unsigned long data_size; + struct stat st; /* * First pass: @@ -297,22 +366,32 @@ static void parse_pack_objects(void) */ for (i = 0; i < nr_objects; i++) { struct object_entry *obj = &objects[i]; - obj->offset = offset; - data = unpack_raw_entry(offset, &obj->type, &data_size, - base_sha1, &offset); + data = unpack_raw_entry(obj, &delta->base); obj->real_type = obj->type; - if (obj->type == OBJ_DELTA) { - struct delta_entry *delta = &deltas[nr_deltas++]; + if (obj->type == OBJ_REF_DELTA || obj->type == OBJ_OFS_DELTA) { + nr_deltas++; delta->obj = obj; - hashcpy(delta->base_sha1, base_sha1); + delta++; } else - sha1_object(data, data_size, obj->type, obj->sha1); + sha1_object(data, obj->size, obj->type, obj->sha1); free(data); } - if (offset != pack_size - 20) + objects[i].offset = consumed_bytes; + + /* Check pack integrity */ + SHA1_Update(&input_ctx, input_buffer, input_offset); + SHA1_Final(sha1, &input_ctx); + if (hashcmp(fill(20), sha1)) + die("packfile '%s' SHA1 mismatch", pack_name); + use(20); + + /* If input_fd is a file, we should have reached its end now. */ + if (fstat(input_fd, &st)) + die("cannot fstat packfile '%s': %s", pack_name, strerror(errno)); + if (S_ISREG(st.st_mode) && st.st_size != consumed_bytes) die("packfile '%s' has junk at the end", pack_name); - /* Sort deltas by base SHA1 for fast searching */ + /* Sort deltas by base SHA1/offset for fast searching */ qsort(deltas, nr_deltas, sizeof(struct delta_entry), compare_delta_entry); @@ -326,22 +405,36 @@ static void parse_pack_objects(void) */ for (i = 0; i < nr_objects; i++) { struct object_entry *obj = &objects[i]; - int j, first, last; + union delta_base base; + int j, ref, ref_first, ref_last, ofs, ofs_first, ofs_last; - if (obj->type == OBJ_DELTA) + if (obj->type == OBJ_REF_DELTA || obj->type == OBJ_OFS_DELTA) continue; - if (find_deltas_based_on_sha1(obj->sha1, &first, &last)) + hashcpy(base.sha1, obj->sha1); + ref = !find_delta_childs(&base, &ref_first, &ref_last); + memset(&base, 0, sizeof(base)); + base.offset = obj->offset; + ofs = !find_delta_childs(&base, &ofs_first, &ofs_last); + if (!ref && !ofs) continue; - data = unpack_raw_entry(obj->offset, &obj->type, &data_size, - base_sha1, &offset); - for (j = first; j <= last; j++) - resolve_delta(&deltas[j], data, data_size, obj->type); + data = get_data_from_pack(obj); + if (ref) + for (j = ref_first; j <= ref_last; j++) + if (deltas[j].obj->type == OBJ_REF_DELTA) + resolve_delta(&deltas[j], data, + obj->size, obj->type); + if (ofs) + for (j = ofs_first; j <= ofs_last; j++) + if (deltas[j].obj->type == OBJ_OFS_DELTA) + resolve_delta(&deltas[j], data, + obj->size, obj->type); free(data); } /* Check for unresolved deltas */ for (i = 0; i < nr_deltas; i++) { - if (deltas[i].obj->real_type == OBJ_DELTA) + if (deltas[i].obj->real_type == OBJ_REF_DELTA || + deltas[i].obj->real_type == OBJ_OFS_DELTA) die("packfile '%s' has unresolved deltas", pack_name); } } @@ -353,6 +446,10 @@ static int sha1_compare(const void *_a, const void *_b) return hashcmp(a->sha1, b->sha1); } +/* + * On entry *sha1 contains the pack content SHA1 hash, on exit it is + * the SHA1 hash of sorted object names. + */ static void write_index_file(const char *index_name, unsigned char *sha1) { struct sha1file *f; @@ -412,7 +509,7 @@ static void write_index_file(const char *index_name, unsigned char *sha1) sha1write(f, obj->sha1, 20); SHA1_Update(&ctx, obj->sha1, 20); } - sha1write(f, pack_base + pack_size - 20, 20); + sha1write(f, sha1, 20); sha1close(f, NULL, 1); free(sorted_by_sha); SHA1_Final(sha1, &ctx); @@ -458,9 +555,9 @@ int main(int argc, char **argv) open_pack_file(); parse_pack_header(); - objects = xcalloc(nr_objects, sizeof(struct object_entry)); + objects = xcalloc(nr_objects + 1, sizeof(struct object_entry)); deltas = xcalloc(nr_objects, sizeof(struct delta_entry)); - parse_pack_objects(); + parse_pack_objects(sha1); free(deltas); write_index_file(index_name, sha1); free(objects); diff --git a/log-tree.c b/log-tree.c index fbe139920a..8787df5cc6 100644 --- a/log-tree.c +++ b/log-tree.c @@ -252,26 +252,6 @@ int log_tree_diff_flush(struct rev_info *opt) return 1; } -static int diff_root_tree(struct rev_info *opt, - const unsigned char *new, const char *base) -{ - int retval; - void *tree; - struct tree_desc empty, real; - - tree = read_object_with_reference(new, tree_type, &real.size, NULL); - if (!tree) - die("unable to read root tree (%s)", sha1_to_hex(new)); - real.buf = tree; - - empty.buf = ""; - empty.size = 0; - retval = diff_tree(&empty, &real, base, &opt->diffopt); - free(tree); - log_tree_diff_flush(opt); - return retval; -} - static int do_diff_combined(struct rev_info *opt, struct commit *commit) { unsigned const char *sha1 = commit->object.sha1; @@ -297,8 +277,10 @@ static int log_tree_diff(struct rev_info *opt, struct commit *commit, struct log /* Root commit? */ parents = commit->parents; if (!parents) { - if (opt->show_root_diff) - diff_root_tree(opt, sha1, ""); + if (opt->show_root_diff) { + diff_root_tree_sha1(sha1, "", &opt->diffopt); + log_tree_diff_flush(opt); + } return !opt->loginfo; } @@ -138,42 +138,56 @@ struct object *lookup_unknown_object(const unsigned char *sha1) return obj; } +struct object *parse_object_buffer(const unsigned char *sha1, const char *type, unsigned long size, void *buffer, int *eaten_p) +{ + struct object *obj; + int eaten = 0; + + if (!strcmp(type, blob_type)) { + struct blob *blob = lookup_blob(sha1); + parse_blob_buffer(blob, buffer, size); + obj = &blob->object; + } else if (!strcmp(type, tree_type)) { + struct tree *tree = lookup_tree(sha1); + obj = &tree->object; + if (!tree->object.parsed) { + parse_tree_buffer(tree, buffer, size); + eaten = 1; + } + } else if (!strcmp(type, commit_type)) { + struct commit *commit = lookup_commit(sha1); + parse_commit_buffer(commit, buffer, size); + if (!commit->buffer) { + commit->buffer = buffer; + eaten = 1; + } + obj = &commit->object; + } else if (!strcmp(type, tag_type)) { + struct tag *tag = lookup_tag(sha1); + parse_tag_buffer(tag, buffer, size); + obj = &tag->object; + } else { + obj = NULL; + } + *eaten_p = eaten; + return obj; +} + struct object *parse_object(const unsigned char *sha1) { unsigned long size; char type[20]; + int eaten; void *buffer = read_sha1_file(sha1, type, &size); + if (buffer) { struct object *obj; if (check_sha1_signature(sha1, buffer, size, type) < 0) printf("sha1 mismatch %s\n", sha1_to_hex(sha1)); - if (!strcmp(type, blob_type)) { - struct blob *blob = lookup_blob(sha1); - parse_blob_buffer(blob, buffer, size); - obj = &blob->object; - } else if (!strcmp(type, tree_type)) { - struct tree *tree = lookup_tree(sha1); - obj = &tree->object; - if (!tree->object.parsed) { - parse_tree_buffer(tree, buffer, size); - buffer = NULL; - } - } else if (!strcmp(type, commit_type)) { - struct commit *commit = lookup_commit(sha1); - parse_commit_buffer(commit, buffer, size); - if (!commit->buffer) { - commit->buffer = buffer; - buffer = NULL; - } - obj = &commit->object; - } else if (!strcmp(type, tag_type)) { - struct tag *tag = lookup_tag(sha1); - parse_tag_buffer(tag, buffer, size); - obj = &tag->object; - } else { - obj = NULL; - } - free(buffer); + + obj = parse_object_buffer(sha1, type, size, buffer, &eaten); + if (!eaten) + free(buffer); return obj; } return NULL; @@ -59,6 +59,12 @@ void created_object(const unsigned char *sha1, struct object *obj); /** Returns the object, having parsed it to find out what it is. **/ struct object *parse_object(const unsigned char *sha1); +/* Given the result of read_sha1_file(), returns the object after + * parsing it. eaten_p indicates if the object has a borrowed copy + * of buffer and the caller should not free() it. + */ +struct object *parse_object_buffer(const unsigned char *sha1, const char *type, unsigned long size, void *buffer, int *eaten_p); + /** Returns the object, with potentially excess memory allocated. **/ struct object *lookup_unknown_object(const unsigned char *sha1); @@ -16,7 +16,4 @@ struct pack_header { }; extern int verify_pack(struct packed_git *, int); -extern int check_reuse_pack_delta(struct packed_git *, unsigned long, - unsigned char *, unsigned long *, - enum object_type *); #endif @@ -349,3 +349,41 @@ void write_name_quoted(const char *prefix, int prefix_len, else goto no_quote; } + +/* quoting as a string literal for other languages */ + +void perl_quote_print(FILE *stream, const char *src) +{ + const char sq = '\''; + const char bq = '\\'; + char c; + + fputc(sq, stream); + while ((c = *src++)) { + if (c == sq || c == bq) + fputc(bq, stream); + fputc(c, stream); + } + fputc(sq, stream); +} + +void python_quote_print(FILE *stream, const char *src) +{ + const char sq = '\''; + const char bq = '\\'; + const char nl = '\n'; + char c; + + fputc(sq, stream); + while ((c = *src++)) { + if (c == nl) { + fputc(bq, stream); + fputc('n', stream); + continue; + } + if (c == sq || c == bq) + fputc(bq, stream); + fputc(c, stream); + } + fputc(sq, stream); +} @@ -52,4 +52,8 @@ extern char *unquote_c_style(const char *quoted, const char **endp); extern void write_name_quoted(const char *prefix, int prefix_len, const char *name, int quote, FILE *out); +/* quoting as a string literal for other languages */ +extern void perl_quote_print(FILE *stream, const char *src); +extern void python_quote_print(FILE *stream, const char *src); + #endif diff --git a/receive-pack.c b/receive-pack.c index ea2dbd4e33..de1d6a4b1c 100644 --- a/receive-pack.c +++ b/receive-pack.c @@ -9,12 +9,26 @@ static const char receive_pack_usage[] = "git-receive-pack <git-dir>"; static const char *unpacker[] = { "unpack-objects", NULL }; +static int deny_non_fast_forwards = 0; static int report_status; static char capabilities[] = "report-status"; static int capabilities_sent; -static int show_ref(const char *path, const unsigned char *sha1) +static int receive_pack_config(const char *var, const char *value) +{ + git_default_config(var, value); + + if (strcmp(var, "receive.denynonfastforwards") == 0) + { + deny_non_fast_forwards = git_config_bool(var, value); + return 0; + } + + return 0; +} + +static int show_ref(const char *path, const unsigned char *sha1, int flag, void *cb_data) { if (capabilities_sent) packet_write(1, "%s %s\n", sha1_to_hex(sha1), path); @@ -27,9 +41,9 @@ static int show_ref(const char *path, const unsigned char *sha1) static void write_head_info(void) { - for_each_ref(show_ref); + for_each_ref(show_ref, NULL); if (!capabilities_sent) - show_ref("capabilities^{}", null_sha1); + show_ref("capabilities^{}", null_sha1, 0, NULL); } @@ -43,34 +57,6 @@ struct command { static struct command *commands; -static int is_all_zeroes(const char *hex) -{ - int i; - for (i = 0; i < 40; i++) - if (*hex++ != '0') - return 0; - return 1; -} - -static int verify_old_ref(const char *name, char *hex_contents) -{ - int fd, ret; - char buffer[60]; - - if (is_all_zeroes(hex_contents)) - return 0; - fd = open(name, O_RDONLY); - if (fd < 0) - return -1; - ret = read(fd, buffer, 40); - close(fd); - if (ret != 40) - return -1; - if (memcmp(buffer, hex_contents, 40)) - return -1; - return 0; -} - static char update_hook[] = "hooks/update"; static int run_update_hook(const char *refname, @@ -107,8 +93,8 @@ static int update(struct command *cmd) const char *name = cmd->ref_name; unsigned char *old_sha1 = cmd->old_sha1; unsigned char *new_sha1 = cmd->new_sha1; - char new_hex[60], *old_hex, *lock_name; - int newfd, namelen, written; + char new_hex[41], old_hex[41]; + struct ref_lock *lock; cmd->error_string = NULL; if (!strncmp(name, "refs/", 5) && check_ref_format(name + 5)) { @@ -117,13 +103,8 @@ static int update(struct command *cmd) name); } - namelen = strlen(name); - lock_name = xmalloc(namelen + 10); - memcpy(lock_name, name, namelen); - memcpy(lock_name + namelen, ".lock", 6); - strcpy(new_hex, sha1_to_hex(new_sha1)); - old_hex = sha1_to_hex(old_sha1); + strcpy(old_hex, sha1_to_hex(old_sha1)); if (!has_sha1_file(new_sha1)) { cmd->error_string = "bad pack"; return error("unpack should have generated %s, " @@ -144,47 +125,20 @@ static int update(struct command *cmd) return error("denying non-fast forward;" " you should pull first"); } - safe_create_leading_directories(lock_name); - - newfd = open(lock_name, O_CREAT | O_EXCL | O_WRONLY, 0666); - if (newfd < 0) { - cmd->error_string = "can't lock"; - return error("unable to create %s (%s)", - lock_name, strerror(errno)); - } - - /* Write the ref with an ending '\n' */ - new_hex[40] = '\n'; - new_hex[41] = 0; - written = write(newfd, new_hex, 41); - /* Remove the '\n' again */ - new_hex[40] = 0; - - close(newfd); - if (written != 41) { - unlink(lock_name); - cmd->error_string = "can't write"; - return error("unable to write %s", lock_name); - } - if (verify_old_ref(name, old_hex) < 0) { - unlink(lock_name); - cmd->error_string = "raced"; - return error("%s changed during push", name); - } if (run_update_hook(name, old_hex, new_hex)) { - unlink(lock_name); cmd->error_string = "hook declined"; return error("hook declined to update %s", name); } - else if (rename(lock_name, name) < 0) { - unlink(lock_name); - cmd->error_string = "can't rename"; - return error("unable to replace %s", name); - } - else { - fprintf(stderr, "%s: %s -> %s\n", name, old_hex, new_hex); - return 0; + + lock = lock_any_ref_for_update(name, old_sha1); + if (!lock) { + cmd->error_string = "failed to lock"; + return error("failed to lock %s", name); } + write_ref_sha1(lock, new_sha1, "push"); + + fprintf(stderr, "%s: %s -> %s\n", name, old_hex, new_hex); + return 0; } static char update_post_hook[] = "hooks/post-update"; @@ -335,9 +289,12 @@ int main(int argc, char **argv) if (!dir) usage(receive_pack_usage); - if(!enter_repo(dir, 0)) + if (!enter_repo(dir, 0)) die("'%s': unable to chdir or not a git archive", dir); + setup_ident(); + git_config(receive_pack_config); + write_head_info(); /* EOF */ @@ -3,15 +3,193 @@ #include <errno.h> +struct ref_list { + struct ref_list *next; + unsigned char flag; /* ISSYMREF? ISPACKED? */ + unsigned char sha1[20]; + char name[FLEX_ARRAY]; +}; + +static const char *parse_ref_line(char *line, unsigned char *sha1) +{ + /* + * 42: the answer to everything. + * + * In this case, it happens to be the answer to + * 40 (length of sha1 hex representation) + * +1 (space in between hex and name) + * +1 (newline at the end of the line) + */ + int len = strlen(line) - 42; + + if (len <= 0) + return NULL; + if (get_sha1_hex(line, sha1) < 0) + return NULL; + if (!isspace(line[40])) + return NULL; + line += 41; + if (isspace(*line)) + return NULL; + if (line[len] != '\n') + return NULL; + line[len] = 0; + return line; +} + +static struct ref_list *add_ref(const char *name, const unsigned char *sha1, + int flag, struct ref_list *list) +{ + int len; + struct ref_list **p = &list, *entry; + + /* Find the place to insert the ref into.. */ + while ((entry = *p) != NULL) { + int cmp = strcmp(entry->name, name); + if (cmp > 0) + break; + + /* Same as existing entry? */ + if (!cmp) + return list; + p = &entry->next; + } + + /* Allocate it and add it in.. */ + len = strlen(name) + 1; + entry = xmalloc(sizeof(struct ref_list) + len); + hashcpy(entry->sha1, sha1); + memcpy(entry->name, name, len); + entry->flag = flag; + entry->next = *p; + *p = entry; + return list; +} + +/* + * Future: need to be in "struct repository" + * when doing a full libification. + */ +struct cached_refs { + char did_loose; + char did_packed; + struct ref_list *loose; + struct ref_list *packed; +} cached_refs; + +static void free_ref_list(struct ref_list *list) +{ + struct ref_list *next; + for ( ; list; list = next) { + next = list->next; + free(list); + } +} + +static void invalidate_cached_refs(void) +{ + struct cached_refs *ca = &cached_refs; + + if (ca->did_loose && ca->loose) + free_ref_list(ca->loose); + if (ca->did_packed && ca->packed) + free_ref_list(ca->packed); + ca->loose = ca->packed = NULL; + ca->did_loose = ca->did_packed = 0; +} + +static struct ref_list *get_packed_refs(void) +{ + if (!cached_refs.did_packed) { + struct ref_list *refs = NULL; + FILE *f = fopen(git_path("packed-refs"), "r"); + if (f) { + struct ref_list *list = NULL; + char refline[PATH_MAX]; + while (fgets(refline, sizeof(refline), f)) { + unsigned char sha1[20]; + const char *name = parse_ref_line(refline, sha1); + if (!name) + continue; + list = add_ref(name, sha1, REF_ISPACKED, list); + } + fclose(f); + refs = list; + } + cached_refs.packed = refs; + cached_refs.did_packed = 1; + } + return cached_refs.packed; +} + +static struct ref_list *get_ref_dir(const char *base, struct ref_list *list) +{ + DIR *dir = opendir(git_path("%s", base)); + + if (dir) { + struct dirent *de; + int baselen = strlen(base); + char *ref = xmalloc(baselen + 257); + + memcpy(ref, base, baselen); + if (baselen && base[baselen-1] != '/') + ref[baselen++] = '/'; + + while ((de = readdir(dir)) != NULL) { + unsigned char sha1[20]; + struct stat st; + int flag; + int namelen; + + if (de->d_name[0] == '.') + continue; + namelen = strlen(de->d_name); + if (namelen > 255) + continue; + if (has_extension(de->d_name, ".lock")) + continue; + memcpy(ref + baselen, de->d_name, namelen+1); + if (stat(git_path("%s", ref), &st) < 0) + continue; + if (S_ISDIR(st.st_mode)) { + list = get_ref_dir(ref, list); + continue; + } + if (!resolve_ref(ref, sha1, 1, &flag)) { + error("%s points nowhere!", ref); + continue; + } + list = add_ref(ref, sha1, flag, list); + } + free(ref); + closedir(dir); + } + return list; +} + +static struct ref_list *get_loose_refs(void) +{ + if (!cached_refs.did_loose) { + cached_refs.loose = get_ref_dir("refs", NULL); + cached_refs.did_loose = 1; + } + return cached_refs.loose; +} + /* We allow "recursive" symbolic refs. Only within reason, though */ #define MAXDEPTH 5 -const char *resolve_ref(const char *path, unsigned char *sha1, int reading) +const char *resolve_ref(const char *ref, unsigned char *sha1, int reading, int *flag) { int depth = MAXDEPTH, len; char buffer[256]; + static char ref_buffer[256]; + + if (flag) + *flag = 0; for (;;) { + const char *path = git_path("%s", ref); struct stat st; char *buf; int fd; @@ -27,17 +205,31 @@ const char *resolve_ref(const char *path, unsigned char *sha1, int reading) * reading. */ if (lstat(path, &st) < 0) { + struct ref_list *list = get_packed_refs(); + while (list) { + if (!strcmp(ref, list->name)) { + hashcpy(sha1, list->sha1); + if (flag) + *flag |= REF_ISPACKED; + return ref; + } + list = list->next; + } if (reading || errno != ENOENT) return NULL; hashclr(sha1); - return path; + return ref; } /* Follow "normalized" - ie "refs/.." symlinks by hand */ if (S_ISLNK(st.st_mode)) { len = readlink(path, buffer, sizeof(buffer)-1); if (len >= 5 && !memcmp("refs/", buffer, 5)) { - path = git_path("%.*s", len, buffer); + buffer[len] = 0; + strcpy(ref_buffer, buffer); + ref = ref_buffer; + if (flag) + *flag |= REF_ISSYMREF; continue; } } @@ -68,19 +260,24 @@ const char *resolve_ref(const char *path, unsigned char *sha1, int reading) while (len && isspace(*buf)) buf++, len--; while (len && isspace(buf[len-1])) - buf[--len] = 0; - path = git_path("%.*s", len, buf); + len--; + buf[len] = 0; + memcpy(ref_buffer, buf, len + 1); + ref = ref_buffer; + if (flag) + *flag |= REF_ISSYMREF; } if (len < 40 || get_sha1_hex(buffer, sha1)) return NULL; - return path; + return ref; } -int create_symref(const char *git_HEAD, const char *refs_heads_master) +int create_symref(const char *ref_target, const char *refs_heads_master) { const char *lockpath; char ref[1000]; int fd, len, written; + const char *git_HEAD = git_path("%s", ref_target); #ifndef NO_SYMLINK_HEAD if (prefer_symlink_refs) { @@ -118,104 +315,101 @@ int create_symref(const char *git_HEAD, const char *refs_heads_master) return 0; } -int read_ref(const char *filename, unsigned char *sha1) +int read_ref(const char *ref, unsigned char *sha1) { - if (resolve_ref(filename, sha1, 1)) + if (resolve_ref(ref, sha1, 1, NULL)) return 0; return -1; } -static int do_for_each_ref(const char *base, int (*fn)(const char *path, const unsigned char *sha1), int trim) +static int do_for_each_ref(const char *base, each_ref_fn fn, int trim, + void *cb_data) { - int retval = 0; - DIR *dir = opendir(git_path("%s", base)); - - if (dir) { - struct dirent *de; - int baselen = strlen(base); - char *path = xmalloc(baselen + 257); - - if (!strncmp(base, "./", 2)) { - base += 2; - baselen -= 2; + int retval; + struct ref_list *packed = get_packed_refs(); + struct ref_list *loose = get_loose_refs(); + + while (packed && loose) { + struct ref_list *entry; + int cmp = strcmp(packed->name, loose->name); + if (!cmp) { + packed = packed->next; + continue; } - memcpy(path, base, baselen); - if (baselen && base[baselen-1] != '/') - path[baselen++] = '/'; - - while ((de = readdir(dir)) != NULL) { - unsigned char sha1[20]; - struct stat st; - int namelen; + if (cmp > 0) { + entry = loose; + loose = loose->next; + } else { + entry = packed; + packed = packed->next; + } + if (strncmp(base, entry->name, trim)) + continue; + if (is_null_sha1(entry->sha1)) + continue; + if (!has_sha1_file(entry->sha1)) { + error("%s does not point to a valid object!", entry->name); + continue; + } + retval = fn(entry->name + trim, entry->sha1, + entry->flag, cb_data); + if (retval) + return retval; + } - if (de->d_name[0] == '.') - continue; - namelen = strlen(de->d_name); - if (namelen > 255) - continue; - if (has_extension(de->d_name, ".lock")) - continue; - memcpy(path + baselen, de->d_name, namelen+1); - if (stat(git_path("%s", path), &st) < 0) - continue; - if (S_ISDIR(st.st_mode)) { - retval = do_for_each_ref(path, fn, trim); - if (retval) - break; - continue; - } - if (read_ref(git_path("%s", path), sha1) < 0) { - error("%s points nowhere!", path); - continue; - } - if (!has_sha1_file(sha1)) { - error("%s does not point to a valid " - "commit object!", path); - continue; - } - retval = fn(path + trim, sha1); + packed = packed ? packed : loose; + while (packed) { + if (!strncmp(base, packed->name, trim)) { + retval = fn(packed->name + trim, packed->sha1, + packed->flag, cb_data); if (retval) - break; + return retval; } - free(path); - closedir(dir); + packed = packed->next; } - return retval; + return 0; } -int head_ref(int (*fn)(const char *path, const unsigned char *sha1)) +int head_ref(each_ref_fn fn, void *cb_data) { unsigned char sha1[20]; - if (!read_ref(git_path("HEAD"), sha1)) - return fn("HEAD", sha1); + int flag; + + if (resolve_ref("HEAD", sha1, 1, &flag)) + return fn("HEAD", sha1, flag, cb_data); return 0; } -int for_each_ref(int (*fn)(const char *path, const unsigned char *sha1)) +int for_each_ref(each_ref_fn fn, void *cb_data) { - return do_for_each_ref("refs", fn, 0); + return do_for_each_ref("refs/", fn, 0, cb_data); } -int for_each_tag_ref(int (*fn)(const char *path, const unsigned char *sha1)) +int for_each_tag_ref(each_ref_fn fn, void *cb_data) { - return do_for_each_ref("refs/tags", fn, 10); + return do_for_each_ref("refs/tags/", fn, 10, cb_data); } -int for_each_branch_ref(int (*fn)(const char *path, const unsigned char *sha1)) +int for_each_branch_ref(each_ref_fn fn, void *cb_data) { - return do_for_each_ref("refs/heads", fn, 11); + return do_for_each_ref("refs/heads/", fn, 11, cb_data); } -int for_each_remote_ref(int (*fn)(const char *path, const unsigned char *sha1)) +int for_each_remote_ref(each_ref_fn fn, void *cb_data) { - return do_for_each_ref("refs/remotes", fn, 13); + return do_for_each_ref("refs/remotes/", fn, 13, cb_data); } +/* NEEDSWORK: This is only used by ssh-upload and it should go; the + * caller should do resolve_ref or read_ref like everybody else. Or + * maybe everybody else should use get_ref_sha1() instead of doing + * read_ref(). + */ int get_ref_sha1(const char *ref, unsigned char *sha1) { if (check_ref_format(ref)) return -1; - return read_ref(git_path("refs/%s", ref), sha1); + return read_ref(mkpath("refs/%s", ref), sha1); } /* @@ -273,22 +467,13 @@ int check_ref_format(const char *ref) static struct ref_lock *verify_lock(struct ref_lock *lock, const unsigned char *old_sha1, int mustexist) { - char buf[40]; - int nr, fd = open(lock->ref_file, O_RDONLY); - if (fd < 0 && (mustexist || errno != ENOENT)) { - error("Can't verify ref %s", lock->ref_file); - unlock_ref(lock); - return NULL; - } - nr = read(fd, buf, 40); - close(fd); - if (nr != 40 || get_sha1_hex(buf, lock->old_sha1) < 0) { - error("Can't verify ref %s", lock->ref_file); + if (!resolve_ref(lock->ref_name, lock->old_sha1, mustexist, NULL)) { + error("Can't verify ref %s", lock->ref_name); unlock_ref(lock); return NULL; } if (hashcmp(lock->old_sha1, old_sha1)) { - error("Ref %s is at %s but expected %s", lock->ref_file, + error("Ref %s is at %s but expected %s", lock->ref_name, sha1_to_hex(lock->old_sha1), sha1_to_hex(old_sha1)); unlock_ref(lock); return NULL; @@ -296,54 +481,223 @@ static struct ref_lock *verify_lock(struct ref_lock *lock, return lock; } -static struct ref_lock *lock_ref_sha1_basic(const char *path, - int plen, - const unsigned char *old_sha1, int mustexist) +static int remove_empty_dir_recursive(char *path, int len) +{ + DIR *dir = opendir(path); + struct dirent *e; + int ret = 0; + + if (!dir) + return -1; + if (path[len-1] != '/') + path[len++] = '/'; + while ((e = readdir(dir)) != NULL) { + struct stat st; + int namlen; + if ((e->d_name[0] == '.') && + ((e->d_name[1] == 0) || + ((e->d_name[1] == '.') && e->d_name[2] == 0))) + continue; /* "." and ".." */ + + namlen = strlen(e->d_name); + if ((len + namlen < PATH_MAX) && + strcpy(path + len, e->d_name) && + !lstat(path, &st) && + S_ISDIR(st.st_mode) && + !remove_empty_dir_recursive(path, len + namlen)) + continue; /* happy */ + + /* path too long, stat fails, or non-directory still exists */ + ret = -1; + break; + } + closedir(dir); + if (!ret) { + path[len] = 0; + ret = rmdir(path); + } + return ret; +} + +static int remove_empty_directories(char *file) { - const char *orig_path = path; + /* we want to create a file but there is a directory there; + * if that is an empty directory (or a directory that contains + * only empty directories), remove them. + */ + char path[PATH_MAX]; + int len = strlen(file); + + if (len >= PATH_MAX) /* path too long ;-) */ + return -1; + strcpy(path, file); + return remove_empty_dir_recursive(path, len); +} + +static struct ref_lock *lock_ref_sha1_basic(const char *ref, const unsigned char *old_sha1, int *flag) +{ + char *ref_file; + const char *orig_ref = ref; struct ref_lock *lock; struct stat st; + int last_errno = 0; + int mustexist = (old_sha1 && !is_null_sha1(old_sha1)); lock = xcalloc(1, sizeof(struct ref_lock)); lock->lock_fd = -1; - plen = strlen(path) - plen; - path = resolve_ref(path, lock->old_sha1, mustexist); - if (!path) { - int last_errno = errno; + ref = resolve_ref(ref, lock->old_sha1, mustexist, flag); + if (!ref && errno == EISDIR) { + /* we are trying to lock foo but we used to + * have foo/bar which now does not exist; + * it is normal for the empty directory 'foo' + * to remain. + */ + ref_file = git_path("%s", orig_ref); + if (remove_empty_directories(ref_file)) { + last_errno = errno; + error("there are still refs under '%s'", orig_ref); + goto error_return; + } + ref = resolve_ref(orig_ref, lock->old_sha1, mustexist, flag); + } + if (!ref) { + last_errno = errno; error("unable to resolve reference %s: %s", - orig_path, strerror(errno)); - unlock_ref(lock); - errno = last_errno; - return NULL; + orig_ref, strerror(errno)); + goto error_return; + } + if (is_null_sha1(lock->old_sha1)) { + /* The ref did not exist and we are creating it. + * Make sure there is no existing ref that is packed + * whose name begins with our refname, nor a ref whose + * name is a proper prefix of our refname. + */ + int namlen = strlen(ref); /* e.g. 'foo/bar' */ + struct ref_list *list = get_packed_refs(); + while (list) { + /* list->name could be 'foo' or 'foo/bar/baz' */ + int len = strlen(list->name); + int cmplen = (namlen < len) ? namlen : len; + const char *lead = (namlen < len) ? list->name : ref; + + if (!strncmp(ref, list->name, cmplen) && + lead[cmplen] == '/') { + error("'%s' exists; cannot create '%s'", + list->name, ref); + goto error_return; + } + list = list->next; + } } + lock->lk = xcalloc(1, sizeof(struct lock_file)); - 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; + lock->ref_name = xstrdup(ref); + lock->log_file = xstrdup(git_path("logs/%s", ref)); + ref_file = git_path("%s", ref); + lock->force_write = lstat(ref_file, &st) && errno == ENOENT; - if (safe_create_leading_directories(lock->ref_file)) - die("unable to create directory for %s", lock->ref_file); - lock->lock_fd = hold_lock_file_for_update(lock->lk, lock->ref_file, 1); + if (safe_create_leading_directories(ref_file)) { + last_errno = errno; + error("unable to create directory for %s", ref_file); + goto error_return; + } + lock->lock_fd = hold_lock_file_for_update(lock->lk, ref_file, 1); return old_sha1 ? verify_lock(lock, old_sha1, mustexist) : lock; + + error_return: + unlock_ref(lock); + errno = last_errno; + return NULL; } -struct ref_lock *lock_ref_sha1(const char *ref, - const unsigned char *old_sha1, int mustexist) +struct ref_lock *lock_ref_sha1(const char *ref, const unsigned char *old_sha1) { + char refpath[PATH_MAX]; if (check_ref_format(ref)) return NULL; - return lock_ref_sha1_basic(git_path("refs/%s", ref), - 5 + strlen(ref), old_sha1, mustexist); + strcpy(refpath, mkpath("refs/%s", ref)); + return lock_ref_sha1_basic(refpath, old_sha1, NULL); } -struct ref_lock *lock_any_ref_for_update(const char *ref, - const unsigned char *old_sha1, int mustexist) +struct ref_lock *lock_any_ref_for_update(const char *ref, const unsigned char *old_sha1) +{ + return lock_ref_sha1_basic(ref, old_sha1, NULL); +} + +static struct lock_file packlock; + +static int repack_without_ref(const char *refname) { - return lock_ref_sha1_basic(git_path("%s", ref), - strlen(ref), old_sha1, mustexist); + struct ref_list *list, *packed_ref_list; + int fd; + int found = 0; + + packed_ref_list = get_packed_refs(); + for (list = packed_ref_list; list; list = list->next) { + if (!strcmp(refname, list->name)) { + found = 1; + break; + } + } + if (!found) + return 0; + memset(&packlock, 0, sizeof(packlock)); + fd = hold_lock_file_for_update(&packlock, git_path("packed-refs"), 0); + if (fd < 0) + return error("cannot delete '%s' from packed refs", refname); + + for (list = packed_ref_list; list; list = list->next) { + char line[PATH_MAX + 100]; + int len; + + if (!strcmp(refname, list->name)) + continue; + len = snprintf(line, sizeof(line), "%s %s\n", + sha1_to_hex(list->sha1), list->name); + /* this should not happen but just being defensive */ + if (len > sizeof(line)) + die("too long a refname '%s'", list->name); + write_or_die(fd, line, len); + } + return commit_lock_file(&packlock); +} + +int delete_ref(const char *refname, unsigned char *sha1) +{ + struct ref_lock *lock; + int err, i, ret = 0, flag = 0; + + lock = lock_ref_sha1_basic(refname, sha1, &flag); + if (!lock) + return 1; + if (!(flag & REF_ISPACKED)) { + /* loose */ + i = strlen(lock->lk->filename) - 5; /* .lock */ + lock->lk->filename[i] = 0; + err = unlink(lock->lk->filename); + if (err) { + ret = 1; + error("unlink(%s) failed: %s", + lock->lk->filename, strerror(errno)); + } + lock->lk->filename[i] = '.'; + } + /* removing the loose one could have resurrected an earlier + * packed one. Also, if it was not loose we need to repack + * without it. + */ + ret |= repack_without_ref(refname); + + err = unlink(lock->log_file); + if (err && errno != ENOENT) + fprintf(stderr, "warning: unlink(%s) failed: %s", + lock->log_file, strerror(errno)); + invalidate_cached_refs(); + unlock_ref(lock); + return ret; } void unlock_ref(struct ref_lock *lock) @@ -354,7 +708,7 @@ void unlock_ref(struct ref_lock *lock) if (lock->lk) rollback_lock_file(lock->lk); } - free(lock->ref_file); + free(lock->ref_name); free(lock->log_file); free(lock); } @@ -367,7 +721,8 @@ static int log_ref_write(struct ref_lock *lock, char *logrec; const char *committer; - if (log_all_ref_updates) { + if (log_all_ref_updates && + !strncmp(lock->ref_name, "refs/heads/", 11)) { if (safe_create_leading_directories(lock->log_file) < 0) return error("unable to create directory for %s", lock->log_file); @@ -376,10 +731,20 @@ static int log_ref_write(struct ref_lock *lock, logfd = open(lock->log_file, oflags, 0666); if (logfd < 0) { - if (!log_all_ref_updates && errno == ENOENT) + if (!(oflags & O_CREAT) && errno == ENOENT) return 0; - return error("Unable to append to %s: %s", - lock->log_file, strerror(errno)); + + if ((oflags & O_CREAT) && errno == EISDIR) { + if (remove_empty_directories(lock->log_file)) { + return error("There are still logs under '%s'", + lock->log_file); + } + logfd = open(lock->log_file, oflags, 0666); + } + + if (logfd < 0) + return error("Unable to append to %s: %s", + lock->log_file, strerror(errno)); } committer = git_committer_info(1); @@ -426,12 +791,13 @@ int write_ref_sha1(struct ref_lock *lock, unlock_ref(lock); return -1; } + invalidate_cached_refs(); if (log_ref_write(lock, sha1, logmsg) < 0) { unlock_ref(lock); return -1; } if (commit_lock_file(lock->lk)) { - error("Couldn't set %s", lock->ref_file); + error("Couldn't set %s", lock->ref_name); unlock_ref(lock); return -1; } @@ -440,7 +806,7 @@ int write_ref_sha1(struct ref_lock *lock, return 0; } -int read_ref_at(const char *ref, unsigned long at_time, unsigned char *sha1) +int read_ref_at(const char *ref, unsigned long at_time, int cnt, unsigned char *sha1) { const char *logfile, *logdata, *logend, *rec, *lastgt, *lastrec; char *tz_c; @@ -473,7 +839,7 @@ int read_ref_at(const char *ref, unsigned long at_time, unsigned char *sha1) if (!lastgt) die("Log %s is corrupt.", logfile); date = strtoul(lastgt + 1, &tz_c, 10); - if (date <= at_time) { + if (date <= at_time || cnt == 0) { if (lastrec) { if (get_sha1_hex(lastrec, logged_sha1)) die("Log %s is corrupt.", logfile); @@ -504,6 +870,8 @@ int read_ref_at(const char *ref, unsigned long at_time, unsigned char *sha1) return 0; } lastrec = rec; + if (cnt > 0) + cnt--; } rec = logdata; @@ -2,7 +2,7 @@ #define REFS_H struct ref_lock { - char *ref_file; + char *ref_name; char *log_file; struct lock_file *lk; unsigned char old_sha1[20]; @@ -14,20 +14,23 @@ struct ref_lock { * Calls the specified function for each ref file until it returns nonzero, * and returns the value */ -extern int head_ref(int (*fn)(const char *path, const unsigned char *sha1)); -extern int for_each_ref(int (*fn)(const char *path, const unsigned char *sha1)); -extern int for_each_tag_ref(int (*fn)(const char *path, const unsigned char *sha1)); -extern int for_each_branch_ref(int (*fn)(const char *path, const unsigned char *sha1)); -extern int for_each_remote_ref(int (*fn)(const char *path, const unsigned char *sha1)); +#define REF_ISSYMREF 01 +#define REF_ISPACKED 02 +typedef int each_ref_fn(const char *refname, const unsigned char *sha1, int flags, void *cb_data); +extern int head_ref(each_ref_fn, void *); +extern int for_each_ref(each_ref_fn, void *); +extern int for_each_tag_ref(each_ref_fn, void *); +extern int for_each_branch_ref(each_ref_fn, void *); +extern int for_each_remote_ref(each_ref_fn, void *); /** Reads the refs file specified into sha1 **/ extern int get_ref_sha1(const char *ref, unsigned char *sha1); /** Locks a "refs/" ref returning the lock on success and NULL on failure. **/ -extern struct ref_lock *lock_ref_sha1(const char *ref, const unsigned char *old_sha1, int mustexist); +extern struct ref_lock *lock_ref_sha1(const char *ref, const unsigned char *old_sha1); /** Locks any ref (for 'HEAD' type refs). */ -extern struct ref_lock *lock_any_ref_for_update(const char *ref, const unsigned char *old_sha1, int mustexist); +extern struct ref_lock *lock_any_ref_for_update(const char *ref, const unsigned char *old_sha1); /** Release any lock taken but not written. **/ extern void unlock_ref(struct ref_lock *lock); @@ -36,7 +39,7 @@ extern void unlock_ref(struct ref_lock *lock); extern int write_ref_sha1(struct ref_lock *lock, const unsigned char *sha1, const char *msg); /** Reads log for the value of ref during at_time. **/ -extern int read_ref_at(const char *ref, unsigned long at_time, unsigned char *sha1); +extern int read_ref_at(const char *ref, unsigned long at_time, int cnt, unsigned char *sha1); /** Returns 0 if target has the right format for a ref. **/ extern int check_ref_format(const char *target); diff --git a/revision.c b/revision.c index 36cdfcd1ad..3dbc26c49c 100644 --- a/revision.c +++ b/revision.c @@ -465,7 +465,7 @@ static void limit_list(struct rev_info *revs) static int all_flags; static struct rev_info *all_revs; -static int handle_one_ref(const char *path, const unsigned char *sha1) +static int handle_one_ref(const char *path, const unsigned char *sha1, int flag, void *cb_data) { struct object *object = get_reference(all_revs, path, sha1, all_flags); add_pending_object(all_revs, object, ""); @@ -476,7 +476,7 @@ static void handle_all(struct rev_info *revs, unsigned flags) { all_revs = revs; all_flags = flags; - for_each_ref(handle_one_ref); + for_each_ref(handle_one_ref, NULL); } static int add_parents_only(struct rev_info *revs, const char *arg, int flags) @@ -729,6 +729,7 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch int i, flags, seen_dashdash, show_merge; const char **unrecognized = argv + 1; int left = 1; + int all_match = 0; /* First, search for "--" */ seen_dashdash = 0; @@ -964,6 +965,10 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch add_message_grep(revs, arg+7); continue; } + if (!strcmp(arg, "--all-match")) { + all_match = 1; + continue; + } opts = diff_opt_parse(&revs->diffopt, argv+i, argc-i); if (opts > 0) { @@ -1025,8 +1030,10 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch if (diff_setup_done(&revs->diffopt) < 0) die("diff_setup_done failed"); - if (revs->grep_filter) + if (revs->grep_filter) { + revs->grep_filter->all_match = all_match; compile_grep_patterns(revs->grep_filter); + } return left; } diff --git a/send-pack.c b/send-pack.c index 5bb123a376..447666665b 100644 --- a/send-pack.c +++ b/send-pack.c @@ -29,6 +29,7 @@ static void exec_pack_objects(void) { static const char *args[] = { "pack-objects", + "--all-progress", "--stdout", NULL }; @@ -215,7 +216,7 @@ static int ref_newer(const unsigned char *new_sha1, static struct ref *local_refs, **local_tail; static struct ref *remote_refs, **remote_tail; -static int one_local_ref(const char *refname, const unsigned char *sha1) +static int one_local_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data) { struct ref *ref; int len = strlen(refname) + 1; @@ -230,7 +231,7 @@ static int one_local_ref(const char *refname, const unsigned char *sha1) static void get_local_heads(void) { local_tail = &local_refs; - for_each_ref(one_local_ref); + for_each_ref(one_local_ref, NULL); } static int receive_status(int in) diff --git a/server-info.c b/server-info.c index 2fb8f57103..6cd38be329 100644 --- a/server-info.c +++ b/server-info.c @@ -7,7 +7,7 @@ /* refs */ static FILE *info_ref_fp; -static int add_info_ref(const char *path, const unsigned char *sha1) +static int add_info_ref(const char *path, const unsigned char *sha1, int flag, void *cb_data) { struct object *o = parse_object(sha1); @@ -34,7 +34,7 @@ static int update_info_refs(int force) info_ref_fp = fopen(path1, "w"); if (!info_ref_fp) return error("unable to update %s", path0); - for_each_ref(add_info_ref); + for_each_ref(add_info_ref, NULL); fclose(info_ref_fp); rename(path1, path0); free(path0); @@ -244,8 +244,6 @@ int check_repository_format_version(const char *var, const char *value) repository_format_version = git_config_int(var, value); else if (strcmp(var, "core.sharedrepository") == 0) shared_repository = git_config_perm(var, value); - else if (strcmp(var, "receive.denynonfastforwards") == 0) - deny_non_fast_forwards = git_config_bool(var, value); return 0; } diff --git a/sha1_file.c b/sha1_file.c index 27eb14b922..4e5ee1054f 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -877,26 +877,61 @@ void * unpack_sha1_file(void *map, unsigned long mapsize, char *type, unsigned l return unpack_sha1_rest(&stream, hdr, *size); } +static unsigned long get_delta_base(struct packed_git *p, + unsigned long offset, + enum object_type kind, + unsigned long delta_obj_offset, + unsigned long *base_obj_offset) +{ + unsigned char *base_info = (unsigned char *) p->pack_base + offset; + unsigned long base_offset; + + /* there must be at least 20 bytes left regardless of delta type */ + if (p->pack_size <= offset + 20) + die("truncated pack file"); + + if (kind == OBJ_OFS_DELTA) { + unsigned used = 0; + unsigned char c = base_info[used++]; + base_offset = c & 127; + while (c & 128) { + base_offset += 1; + if (!base_offset || base_offset & ~(~0UL >> 7)) + die("offset value overflow for delta base object"); + c = base_info[used++]; + base_offset = (base_offset << 7) + (c & 127); + } + base_offset = delta_obj_offset - base_offset; + if (base_offset >= delta_obj_offset) + die("delta base offset out of bound"); + offset += used; + } else if (kind == OBJ_REF_DELTA) { + /* The base entry _must_ be in the same pack */ + base_offset = find_pack_entry_one(base_info, p); + if (!base_offset) + die("failed to find delta-pack base object %s", + sha1_to_hex(base_info)); + offset += 20; + } else + die("I am totally screwed"); + *base_obj_offset = base_offset; + return offset; +} + /* forward declaration for a mutually recursive function */ static int packed_object_info(struct packed_git *p, unsigned long offset, char *type, unsigned long *sizep); static int packed_delta_info(struct packed_git *p, unsigned long offset, + enum object_type kind, + unsigned long obj_offset, char *type, unsigned long *sizep) { unsigned long base_offset; - unsigned char *base_sha1 = (unsigned char *) p->pack_base + offset; - if (p->pack_size < offset + 20) - die("truncated pack file"); - /* The base entry _must_ be in the same pack */ - base_offset = find_pack_entry_one(base_sha1, p); - if (!base_offset) - die("failed to find delta-pack base object %s", - sha1_to_hex(base_sha1)); - offset += 20; + offset = get_delta_base(p, offset, kind, obj_offset, &base_offset); /* We choose to only get the type of the base object and * ignore potentially corrupt pack file that expects the delta @@ -959,25 +994,6 @@ static unsigned long unpack_object_header(struct packed_git *p, unsigned long of return offset + used; } -int check_reuse_pack_delta(struct packed_git *p, unsigned long offset, - unsigned char *base, unsigned long *sizep, - enum object_type *kindp) -{ - unsigned long ptr; - int status = -1; - - use_packed_git(p); - ptr = offset; - ptr = unpack_object_header(p, ptr, kindp, sizep); - if (*kindp != OBJ_DELTA) - goto done; - hashcpy(base, (unsigned char *) p->pack_base + ptr); - status = 0; - done: - unuse_packed_git(p); - return status; -} - void packed_object_info_detail(struct packed_git *p, unsigned long offset, char *type, @@ -986,11 +1002,12 @@ void packed_object_info_detail(struct packed_git *p, unsigned int *delta_chain_length, unsigned char *base_sha1) { - unsigned long val; + unsigned long obj_offset, val; unsigned char *next_sha1; enum object_type kind; *delta_chain_length = 0; + obj_offset = offset; offset = unpack_object_header(p, offset, &kind, size); for (;;) { @@ -1005,7 +1022,13 @@ void packed_object_info_detail(struct packed_git *p, strcpy(type, type_names[kind]); *store_size = 0; /* notyet */ return; - case OBJ_DELTA: + case OBJ_OFS_DELTA: + get_delta_base(p, offset, kind, obj_offset, &offset); + if (*delta_chain_length == 0) { + /* TODO: find base_sha1 as pointed by offset */ + } + break; + case OBJ_REF_DELTA: if (p->pack_size <= offset + 20) die("pack file %s records an incomplete delta base", p->pack_name); @@ -1015,6 +1038,7 @@ void packed_object_info_detail(struct packed_git *p, offset = find_pack_entry_one(next_sha1, p); break; } + obj_offset = offset; offset = unpack_object_header(p, offset, &kind, &val); (*delta_chain_length)++; } @@ -1023,15 +1047,15 @@ void packed_object_info_detail(struct packed_git *p, static int packed_object_info(struct packed_git *p, unsigned long offset, char *type, unsigned long *sizep) { - unsigned long size; + unsigned long size, obj_offset = offset; enum object_type kind; offset = unpack_object_header(p, offset, &kind, &size); - if (kind == OBJ_DELTA) - return packed_delta_info(p, offset, type, sizep); - switch (kind) { + case OBJ_OFS_DELTA: + case OBJ_REF_DELTA: + return packed_delta_info(p, offset, kind, obj_offset, type, sizep); case OBJ_COMMIT: case OBJ_TREE: case OBJ_BLOB: @@ -1077,23 +1101,15 @@ static void *unpack_compressed_entry(struct packed_git *p, static void *unpack_delta_entry(struct packed_git *p, unsigned long offset, unsigned long delta_size, + enum object_type kind, + unsigned long obj_offset, char *type, unsigned long *sizep) { void *delta_data, *result, *base; unsigned long result_size, base_size, base_offset; - unsigned char *base_sha1; - - if (p->pack_size < offset + 20) - die("truncated pack file"); - /* The base entry _must_ be in the same pack */ - base_sha1 = (unsigned char*)p->pack_base + offset; - base_offset = find_pack_entry_one(base_sha1, p); - if (!base_offset) - die("failed to find delta-pack base object %s", - sha1_to_hex(base_sha1)); - offset += 20; + offset = get_delta_base(p, offset, kind, obj_offset, &base_offset); base = unpack_entry_gently(p, base_offset, type, &base_size); if (!base) die("failed to read delta base object at %lu from %s", @@ -1130,13 +1146,14 @@ static void *unpack_entry(struct pack_entry *entry, void *unpack_entry_gently(struct packed_git *p, unsigned long offset, char *type, unsigned long *sizep) { - unsigned long size; + unsigned long size, obj_offset = offset; enum object_type kind; offset = unpack_object_header(p, offset, &kind, &size); switch (kind) { - case OBJ_DELTA: - return unpack_delta_entry(p, offset, size, type, sizep); + case OBJ_OFS_DELTA: + case OBJ_REF_DELTA: + return unpack_delta_entry(p, offset, size, kind, obj_offset, type, sizep); case OBJ_COMMIT: case OBJ_TREE: case OBJ_BLOB: diff --git a/sha1_name.c b/sha1_name.c index 6ffee22081..6d7cd78381 100644 --- a/sha1_name.c +++ b/sha1_name.c @@ -247,26 +247,25 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1) NULL }; static const char *warning = "warning: refname '%.*s' is ambiguous.\n"; - const char **p, *pathname; - char *real_path = NULL; - int refs_found = 0, am; - unsigned long at_time = (unsigned long)-1; + const char **p, *ref; + char *real_ref = NULL; + int refs_found = 0; + int at, reflog_len; unsigned char *this_result; unsigned char sha1_from_ref[20]; if (len == 40 && !get_sha1_hex(str, sha1)) return 0; - /* At a given period of time? "@{2 hours ago}" */ - for (am = 1; am < len - 1; am++) { - if (str[am] == '@' && str[am+1] == '{' && str[len-1] == '}') { - int date_len = len - am - 3; - char *date_spec = xmalloc(date_len + 1); - strlcpy(date_spec, str + am + 2, date_len + 1); - at_time = approxidate(date_spec); - free(date_spec); - len = am; - break; + /* basic@{time or number} format to query ref-log */ + reflog_len = at = 0; + if (str[len-1] == '}') { + for (at = 1; at < len - 1; at++) { + if (str[at] == '@' && str[at+1] == '{') { + reflog_len = (len-1) - (at+2); + len = at; + break; + } } } @@ -276,10 +275,10 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1) for (p = fmt; *p; p++) { this_result = refs_found ? sha1_from_ref : sha1; - pathname = resolve_ref(git_path(*p, len, str), this_result, 1); - if (pathname) { + ref = resolve_ref(mkpath(*p, len, str), this_result, 1, NULL); + if (ref) { if (!refs_found++) - real_path = xstrdup(pathname); + real_ref = xstrdup(ref); if (!warn_ambiguous_refs) break; } @@ -291,14 +290,25 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1) if (warn_ambiguous_refs && refs_found > 1) fprintf(stderr, warning, len, str); - if (at_time != (unsigned long)-1) { - read_ref_at( - real_path + strlen(git_path(".")) - 1, - at_time, - sha1); + if (reflog_len) { + /* Is it asking for N-th entry, or approxidate? */ + int nth, i; + unsigned long at_time; + for (i = nth = 0; 0 <= nth && i < reflog_len; i++) { + char ch = str[at+2+i]; + if ('0' <= ch && ch <= '9') + nth = nth * 10 + ch - '0'; + else + nth = -1; + } + if (0 <= nth) + at_time = 0; + else + at_time = approxidate(str + at + 2); + read_ref_at(real_ref, at_time, nth, sha1); } - free(real_path); + free(real_ref); return 0; } diff --git a/t/t1400-update-ref.sh b/t/t1400-update-ref.sh index b3b920edb1..6a917f2ff4 100755 --- a/t/t1400-update-ref.sh +++ b/t/t1400-update-ref.sh @@ -30,11 +30,8 @@ rm -f .git/$m test_expect_success \ "fail to create $n" \ "touch .git/$n_dir - git-update-ref $n $A >out 2>err - test "'$? = 1 && - test "" = "$(cat out)" && - grep "error: unable to resolve reference" err && - grep '"$n err" + git-update-ref $n $A >out 2>err"' + test $? != 0' rm -f .git/$n_dir out err test_expect_success \ diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh index 6907cbcd29..acb54b6a07 100755 --- a/t/t3200-branch.sh +++ b/t/t3200-branch.sh @@ -17,13 +17,10 @@ test_expect_success \ git-commit -m "Initial commit." && HEAD=$(git-rev-parse --verify HEAD)' -test_expect_success \ - 'git branch --help should return success now.' \ - 'git-branch --help' - test_expect_failure \ 'git branch --help should not have created a bogus branch' \ - 'test -f .git/refs/heads/--help' + 'git-branch --help </dev/null >/dev/null 2>/dev/null || : + test -f .git/refs/heads/--help' test_expect_success \ 'git branch abc should create a branch' \ @@ -34,7 +31,7 @@ test_expect_success \ 'git-branch a/b/c && test -f .git/refs/heads/a/b/c' cat >expect <<EOF -0000000000000000000000000000000000000000 $HEAD $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150200 +0000 branch: Created from HEAD +0000000000000000000000000000000000000000 $HEAD $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150200 +0000 branch: Created from master EOF test_expect_success \ 'git branch -l d/e/f should create a branch and a log' \ diff --git a/t/t3210-pack-refs.sh b/t/t3210-pack-refs.sh new file mode 100755 index 0000000000..b1e9f2eed2 --- /dev/null +++ b/t/t3210-pack-refs.sh @@ -0,0 +1,99 @@ +#!/bin/sh +# +# Copyright (c) 2005 Amos Waterland +# Copyright (c) 2006 Christian Couder +# + +test_description='git pack-refs should not change the branch semantic + +This test runs git pack-refs and git show-ref and checks that the branch +semantic is still the same. +' +. ./test-lib.sh + +echo '[core] logallrefupdates = true' >>.git/config + +test_expect_success \ + 'prepare a trivial repository' \ + 'echo Hello > A && + git-update-index --add A && + git-commit -m "Initial commit." && + HEAD=$(git-rev-parse --verify HEAD)' + +SHA1= + +test_expect_success \ + 'see if git show-ref works as expected' \ + 'git-branch a && + SHA1=`cat .git/refs/heads/a` && + echo "$SHA1 refs/heads/a" >expect && + git-show-ref a >result && + diff expect result' + +test_expect_success \ + 'see if a branch still exists when packed' \ + 'git-branch b && + git-pack-refs --all && + rm .git/refs/heads/b && + echo "$SHA1 refs/heads/b" >expect && + git-show-ref b >result && + diff expect result' + +test_expect_failure \ + 'git branch c/d should barf if branch c exists' \ + 'git-branch c && + git-pack-refs --all && + rm .git/refs/heads/c && + git-branch c/d' + +test_expect_success \ + 'see if a branch still exists after git pack-refs --prune' \ + 'git-branch e && + git-pack-refs --all --prune && + echo "$SHA1 refs/heads/e" >expect && + git-show-ref e >result && + diff expect result' + +test_expect_failure \ + 'see if git pack-refs --prune remove ref files' \ + 'git-branch f && + git-pack-refs --all --prune && + ls .git/refs/heads/f' + +test_expect_success \ + 'git branch g should work when git branch g/h has been deleted' \ + 'git-branch g/h && + git-pack-refs --all --prune && + git-branch -d g/h && + git-branch g && + git-pack-refs --all && + git-branch -d g' + +test_expect_failure \ + 'git branch i/j/k should barf if branch i exists' \ + 'git-branch i && + git-pack-refs --all --prune && + git-branch i/j/k' + +test_expect_success \ + 'test git branch k after branch k/l/m and k/lm have been deleted' \ + 'git-branch k/l && + git-branch k/lm && + git-branch -d k/l && + git-branch k/l/m && + git-branch -d k/l/m && + git-branch -d k/lm && + git-branch k' + +test_expect_success \ + 'test git branch n after some branch deletion and pruning' \ + 'git-branch n/o && + git-branch n/op && + git-branch -d n/o && + git-branch n/o/p && + git-branch -d n/op && + git-pack-refs --all --prune && + git-branch -d n/o/p && + git-branch n' + +test_done diff --git a/t/t3401-rebase-partial.sh b/t/t3401-rebase-partial.sh index 360a67060e..8b19d3ccea 100755 --- a/t/t3401-rebase-partial.sh +++ b/t/t3401-rebase-partial.sh @@ -52,13 +52,10 @@ test_expect_success \ 'rebase topic branch against new master and check git-am did not get halted' \ 'git-rebase master && test ! -d .dotest' -if test -z "$no_python" -then - test_expect_success \ +test_expect_success \ 'rebase --merge topic branch that was partially merged upstream' \ 'git-checkout -f my-topic-branch-merge && git-rebase --merge master-merge && test ! -d .git/.dotest-merge' -fi test_done diff --git a/t/t3402-rebase-merge.sh b/t/t3402-rebase-merge.sh index d34c6cf6f3..0779aaa9ab 100755 --- a/t/t3402-rebase-merge.sh +++ b/t/t3402-rebase-merge.sh @@ -7,12 +7,6 @@ test_description='git rebase --merge test' . ./test-lib.sh -if test "$no_python"; then - echo "Skipping: no python => no recursive merge" - test_done - exit 0 -fi - T="A quick brown fox jumps over the lazy dog." for i in 1 2 3 4 5 6 7 8 9 10 diff --git a/t/t3403-rebase-skip.sh b/t/t3403-rebase-skip.sh index bb25315361..977c498f00 100755 --- a/t/t3403-rebase-skip.sh +++ b/t/t3403-rebase-skip.sh @@ -10,12 +10,6 @@ test_description='git rebase --merge --skip tests' # we assume the default git-am -3 --skip strategy is tested independently # and always works :) -if test "$no_python"; then - echo "Skipping: no python => no recursive merge" - test_done - exit 0 -fi - test_expect_success setup ' echo hello > hello && git add hello && diff --git a/t/t4013/diff.diff-tree_--pretty_--root_--summary_initial b/t/t4013/diff.diff-tree_--pretty_--root_--summary_initial index ea48205537..58e5f74aea 100644 --- a/t/t4013/diff.diff-tree_--pretty_--root_--summary_initial +++ b/t/t4013/diff.diff-tree_--pretty_--root_--summary_initial @@ -5,7 +5,7 @@ Date: Mon Jun 26 00:00:00 2006 +0000 Initial - create mode 040000 dir + create mode 100644 dir/sub create mode 100644 file0 create mode 100644 file2 $ diff --git a/t/t5000-tar-tree.sh b/t/t5000-tar-tree.sh index 278eb66701..cf08e9279c 100755 --- a/t/t5000-tar-tree.sh +++ b/t/t5000-tar-tree.sh @@ -26,6 +26,7 @@ commit id embedding: . ./test-lib.sh TAR=${TAR:-tar} +UNZIP=${UNZIP:-unzip} test_expect_success \ 'populate workdir' \ @@ -95,4 +96,38 @@ test_expect_success \ 'validate file contents with prefix' \ 'diff -r a c/prefix/a' +test_expect_success \ + 'git-archive --format=zip' \ + 'git-archive --format=zip HEAD >d.zip' + +test_expect_success \ + 'extract ZIP archive' \ + '(mkdir d && cd d && $UNZIP ../d.zip)' + +test_expect_success \ + 'validate filenames' \ + '(cd d/a && find .) | sort >d.lst && + diff a.lst d.lst' + +test_expect_success \ + 'validate file contents' \ + 'diff -r a d/a' + +test_expect_success \ + 'git-archive --format=zip with prefix' \ + 'git-archive --format=zip --prefix=prefix/ HEAD >e.zip' + +test_expect_success \ + 'extract ZIP archive with prefix' \ + '(mkdir e && cd e && $UNZIP ../e.zip)' + +test_expect_success \ + 'validate filenames with prefix' \ + '(cd e/prefix/a && find .) | sort >e.lst && + diff a.lst e.lst' + +test_expect_success \ + 'validate file contents with prefix' \ + 'diff -r a e/prefix/a' + test_done diff --git a/t/t6021-merge-criss-cross.sh b/t/t6021-merge-criss-cross.sh index 8f7366da8d..499cafb882 100755 --- a/t/t6021-merge-criss-cross.sh +++ b/t/t6021-merge-criss-cross.sh @@ -10,12 +10,6 @@ test_description='Test criss-cross merge' . ./test-lib.sh -if test "$no_python"; then - echo "Skipping: no python => no recursive merge" - test_done - exit 0 -fi - test_expect_success 'prepare repository' \ 'echo "1 2 diff --git a/t/t6022-merge-rename.sh b/t/t6022-merge-rename.sh index 5ac25647ab..497ef36ad8 100755 --- a/t/t6022-merge-rename.sh +++ b/t/t6022-merge-rename.sh @@ -3,12 +3,6 @@ test_description='Merge-recursive merging renames' . ./test-lib.sh -if test "$no_python"; then - echo "Skipping: no python => no recursive merge" - test_done - exit 0 -fi - test_expect_success setup \ ' cat >A <<\EOF && diff --git a/tree-diff.c b/tree-diff.c index 7e2f4f088a..37d235e06e 100644 --- a/tree-diff.c +++ b/tree-diff.c @@ -215,6 +215,24 @@ int diff_tree_sha1(const unsigned char *old, const unsigned char *new, const cha return retval; } +int diff_root_tree_sha1(const unsigned char *new, const char *base, struct diff_options *opt) +{ + int retval; + void *tree; + struct tree_desc empty, real; + + tree = read_object_with_reference(new, tree_type, &real.size, NULL); + if (!tree) + die("unable to read root tree (%s)", sha1_to_hex(new)); + real.buf = tree; + + empty.size = 0; + empty.buf = ""; + retval = diff_tree(&empty, &real, base, opt); + free(tree); + return retval; +} + static int count_paths(const char **paths) { int i = 0; diff --git a/upload-pack.c b/upload-pack.c index 189b239cc0..ddaa72f0a9 100644 --- a/upload-pack.c +++ b/upload-pack.c @@ -16,7 +16,7 @@ static const char upload_pack_usage[] = "git-upload-pack [--strict] [--timeout=n #define OUR_REF (1U << 1) #define WANTED (1U << 2) static int multi_ack, nr_our_refs; -static int use_thin_pack; +static int use_thin_pack, use_ofs_delta; static struct object_array have_obj; static struct object_array want_obj; static unsigned int timeout; @@ -137,7 +137,9 @@ static void create_pack_file(void) close(pu_pipe[1]); close(pe_pipe[0]); close(pe_pipe[1]); - execl_git_cmd("pack-objects", "--stdout", "--progress", NULL); + execl_git_cmd("pack-objects", "--stdout", "--progress", + use_ofs_delta ? "--delta-base-offset" : NULL, + NULL); kill(pid_rev_list, SIGKILL); die("git-upload-pack: unable to exec git-pack-objects"); } @@ -393,6 +395,8 @@ static void receive_needs(void) multi_ack = 1; if (strstr(line+45, "thin-pack")) use_thin_pack = 1; + if (strstr(line+45, "ofs-delta")) + use_ofs_delta = 1; if (strstr(line+45, "side-band-64k")) use_sideband = LARGE_PACKET_MAX; else if (strstr(line+45, "side-band")) @@ -416,9 +420,9 @@ static void receive_needs(void) } } -static int send_ref(const char *refname, const unsigned char *sha1) +static int send_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data) { - static const char *capabilities = "multi_ack thin-pack side-band side-band-64k"; + static const char *capabilities = "multi_ack thin-pack side-band side-band-64k ofs-delta"; struct object *o = parse_object(sha1); if (!o) @@ -444,8 +448,8 @@ static int send_ref(const char *refname, const unsigned char *sha1) static void upload_pack(void) { reset_timeout(); - head_ref(send_ref); - for_each_ref(send_ref); + head_ref(send_ref, NULL); + for_each_ref(send_ref, NULL); packet_flush(1); receive_needs(); if (want_obj.nr) { diff --git a/wt-status.c b/wt-status.c index 68ecb0b921..9692dfa325 100644 --- a/wt-status.c +++ b/wt-status.c @@ -41,10 +41,8 @@ void wt_status_prepare(struct wt_status *s) 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; + head = resolve_ref("HEAD", sha1, 0, NULL); + s->branch = head ? xstrdup(head) : NULL; s->reference = "HEAD"; s->amend = 0; @@ -72,25 +70,25 @@ static void wt_status_print_filepair(int t, struct diff_filepair *p) color_printf(color(WT_STATUS_HEADER), "#\t"); switch (p->status) { case DIFF_STATUS_ADDED: - color_printf(c, "new file: %s", p->one->path); break; + color_printf(c, "new file: %s", p->one->path); break; case DIFF_STATUS_COPIED: - color_printf(c, "copied: %s -> %s", + 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; + color_printf(c, "deleted: %s", p->one->path); break; case DIFF_STATUS_MODIFIED: - color_printf(c, "modified: %s", p->one->path); break; + color_printf(c, "modified: %s", p->one->path); break; case DIFF_STATUS_RENAMED: - color_printf(c, "renamed: %s -> %s", + 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; + color_printf(c, "unknown: %s", p->one->path); break; case DIFF_STATUS_UNMERGED: - color_printf(c, "unmerged: %s", p->one->path); break; + color_printf(c, "unmerged: %s", p->one->path); break; default: die("bug: unhandled diff status %c", p->status); } |